github.com/aretext/aretext@v1.3.0/syntax/languages/gotemplate.go (about)

     1  package languages
     2  
     3  import (
     4  	"unicode"
     5  
     6  	"github.com/aretext/aretext/syntax/parser"
     7  )
     8  
     9  type gotemplateParseState uint8
    10  
    11  const (
    12  	gotemplateParseStateInText = gotemplateParseState(iota)
    13  	gotemplateParseStateInAction
    14  )
    15  
    16  func (s gotemplateParseState) Equals(other parser.State) bool {
    17  	otherState, ok := other.(gotemplateParseState)
    18  	return ok && s == otherState
    19  }
    20  
    21  // GoTemplateParseFunc returns a parse func for Go templates.
    22  // See https://pkg.go.dev/text/template
    23  func GoTemplateParseFunc() parser.Func {
    24  	parseText := matchState(
    25  		gotemplateParseStateInText,
    26  		goTemplateTextParseFunc())
    27  
    28  	parseActionStartDelim := matchState(
    29  		gotemplateParseStateInText,
    30  		goTemplateActionStartDelimParseFunc().
    31  			Map(setState(gotemplateParseStateInAction)))
    32  
    33  	parseActionEndDelim := matchState(
    34  		gotemplateParseStateInAction,
    35  		goTemplateActionEndDelimParseFunc().
    36  			Map(setState(gotemplateParseStateInText)))
    37  
    38  	parseActionContents := matchState(
    39  		gotemplateParseStateInAction,
    40  		goTemplateActionContentsParseFunc())
    41  
    42  	return initialState(
    43  		gotemplateParseStateInText,
    44  		parseText.
    45  			Or(parseActionStartDelim).
    46  			Or(parseActionContents).
    47  			Or(parseActionEndDelim))
    48  }
    49  
    50  func goTemplateActionStartDelimParseFunc() parser.Func {
    51  	return consumeString("{{").
    52  		ThenMaybe(consumeString("-")).
    53  		Map(recognizeToken(parser.TokenRoleOperator))
    54  }
    55  
    56  func goTemplateActionEndDelimParseFunc() parser.Func {
    57  	return consumeString("-").
    58  		MaybeBefore(consumeString("}}")).
    59  		Map(recognizeToken(parser.TokenRoleOperator))
    60  }
    61  
    62  func goTemplateActionContentsParseFunc() parser.Func {
    63  	// Comments are supposed to start/end only at the beginning/end of an action,
    64  	// but we don't enforce that.
    65  	parseComment := golangGeneralCommentParseFunc()
    66  
    67  	parseString := golangRuneLiteralParseFunc().
    68  		Or(golangRawStringLiteralParseFunc()).
    69  		Or(golangInterpretedStringLiteralParseFunc())
    70  
    71  	parseOperator := consumeLongestMatchingOption([]string{"|", "$", ":="}).
    72  		Map(recognizeToken(parser.TokenRoleOperator))
    73  
    74  	isLetterOrPunct := func(r rune) bool { return unicode.IsLetter(r) || r == '_' || r == '.' }
    75  	isLetterPunctOrDigit := func(r rune) bool { return isLetterOrPunct(r) || unicode.IsDigit(r) }
    76  	keywords := []string{
    77  		"if", "else", "end", "range", "break", "continue", "template", "block", "with",
    78  		"and", "call", "html", "index", "slice", "js", "len", "not", "or",
    79  		"print", "printf", "println", "urlquery", "define",
    80  		"eq", "ne", "lt", "le", "gt", "ge",
    81  	}
    82  	parseKeywordOrIdentifier := consumeSingleRuneLike(isLetterOrPunct).
    83  		ThenMaybe(consumeRunesLike(isLetterPunctOrDigit)).
    84  		MapWithInput(recognizeKeywordOrConsume(keywords))
    85  
    86  	return parseString.
    87  		Or(parseComment).
    88  		Or(parseOperator).
    89  		Or(parseKeywordOrIdentifier)
    90  }
    91  
    92  func goTemplateTextParseFunc() parser.Func {
    93  	// Consume up to, but not including, the next '{' if it exists (may start an action).
    94  	// Otherwise, consume the rest of the line.
    95  	return func(iter parser.TrackingRuneIter, state parser.State) parser.Result {
    96  		var numConsumed uint64
    97  		for {
    98  			r, err := iter.NextRune()
    99  			if err != nil || r == '{' {
   100  				break
   101  			}
   102  
   103  			numConsumed++
   104  
   105  			if r == '\n' {
   106  				break
   107  			}
   108  		}
   109  		return parser.Result{
   110  			NumConsumed: numConsumed,
   111  			NextState:   state,
   112  		}
   113  	}
   114  }