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 }