learnlytics-go/templ/parser/v2/expressionparser.go

240 lines
5.8 KiB
Go
Raw Normal View History

2025-03-20 12:35:13 +01:00
package parser
import (
"strings"
"github.com/a-h/parse"
)
// StripType takes the parser and throws away the return value.
func StripType[T any](p parse.Parser[T]) parse.Parser[any] {
return parse.Func(func(in *parse.Input) (out any, ok bool, err error) {
return p.Parse(in)
})
}
func ExpressionOf(p parse.Parser[string]) parse.Parser[Expression] {
return parse.Func(func(in *parse.Input) (out Expression, ok bool, err error) {
from := in.Position()
var exp string
if exp, ok, err = p.Parse(in); err != nil || !ok {
return
}
return NewExpression(exp, from, in.Position()), true, nil
})
}
var lt = parse.Rune('<')
var gt = parse.Rune('>')
var openBrace = parse.String("{")
var optionalSpaces = parse.StringFrom(parse.Optional(
parse.AtLeast(1, parse.Rune(' '))))
var openBraceWithPadding = parse.StringFrom(optionalSpaces,
openBrace,
optionalSpaces)
var openBraceWithOptionalPadding = parse.Any(openBraceWithPadding, openBrace)
var closeBrace = parse.String("}")
var closeBraceWithOptionalPadding = parse.StringFrom(optionalSpaces, closeBrace)
var dblCloseBrace = parse.String("}}")
var dblCloseBraceWithOptionalPadding = parse.StringFrom(optionalSpaces, dblCloseBrace)
var openBracket = parse.String("(")
var closeBracket = parse.String(")")
var stringUntilNewLine = parse.StringUntil(parse.NewLine)
var newLineOrEOF = parse.Or(parse.NewLine, parse.EOF[string]())
var stringUntilNewLineOrEOF = parse.StringUntil(newLineOrEOF)
var jsOrGoSingleLineComment = parse.StringFrom(parse.String("//"), parse.StringUntil(parse.Any(parse.NewLine, parse.EOF[string]())))
var jsOrGoMultiLineComment = parse.StringFrom(parse.String("/*"), parse.StringUntil(parse.String("*/")))
var exp = expressionParser{
startBraceCount: 1,
}
type expressionParser struct {
startBraceCount int
}
func (p expressionParser) Parse(pi *parse.Input) (s Expression, ok bool, err error) {
from := pi.Position()
braceCount := p.startBraceCount
sb := new(strings.Builder)
loop:
for {
var result string
// Try to parse a single line comment.
if result, ok, err = jsOrGoSingleLineComment.Parse(pi); err != nil {
return
}
if ok {
sb.WriteString(result)
continue
}
// Try to parse a multi-line comment.
if result, ok, err = jsOrGoMultiLineComment.Parse(pi); err != nil {
return
}
if ok {
sb.WriteString(result)
continue
}
// Try to read a string literal.
if result, ok, err = string_lit.Parse(pi); err != nil {
return
}
if ok {
sb.WriteString(result)
continue
}
// Also try for a rune literal.
if result, ok, err = rune_lit.Parse(pi); err != nil {
return
}
if ok {
sb.WriteString(result)
continue
}
// Try opener.
if result, ok, err = openBrace.Parse(pi); err != nil {
return
}
if ok {
braceCount++
sb.WriteString(result)
continue
}
// Try closer.
startOfCloseBrace := pi.Index()
if result, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil {
return
}
if ok {
braceCount--
if braceCount < 0 {
err = parse.Error("expression: too many closing braces", pi.Position())
return
}
if braceCount == 0 {
pi.Seek(startOfCloseBrace)
break loop
}
sb.WriteString(result)
continue
}
// Read anything else.
var c string
c, ok = pi.Take(1)
if !ok {
break loop
}
if rune(c[0]) == 65533 { // Invalid Unicode.
break loop
}
sb.WriteString(c)
}
if braceCount != 0 {
err = parse.Error("expression: unexpected brace count", pi.Position())
return
}
return NewExpression(sb.String(), from, pi.Position()), true, nil
}
// Letters and digits
var octal_digit = parse.RuneIn("01234567")
var hex_digit = parse.RuneIn("0123456789ABCDEFabcdef")
// https://go.dev/ref/spec#Rune_literals
var rune_lit = parse.StringFrom(
parse.Rune('\''),
parse.StringFrom(parse.Until(
parse.Any(unicode_value_rune, byte_value),
parse.Rune('\''),
)),
parse.Rune('\''),
)
var unicode_value_rune = parse.Any(little_u_value, big_u_value, escaped_char, parse.RuneNotIn("'"))
// byte_value = octal_byte_value | hex_byte_value .
var byte_value = parse.Any(octal_byte_value, hex_byte_value)
// octal_byte_value = `\` octal_digit octal_digit octal_digit .
var octal_byte_value = parse.StringFrom(
parse.String(`\`),
octal_digit, octal_digit, octal_digit,
)
// hex_byte_value = `\` "x" hex_digit hex_digit .
var hex_byte_value = parse.StringFrom(
parse.String(`\x`),
hex_digit, hex_digit,
)
// little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
var little_u_value = parse.StringFrom(
parse.String(`\u`),
hex_digit, hex_digit,
hex_digit, hex_digit,
)
// big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
var big_u_value = parse.StringFrom(
parse.String(`\U`),
hex_digit, hex_digit, hex_digit, hex_digit,
hex_digit, hex_digit, hex_digit, hex_digit,
)
// escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
var escaped_char = parse.StringFrom(
parse.Rune('\\'),
parse.Any(
parse.Rune('a'),
parse.Rune('b'),
parse.Rune('f'),
parse.Rune('n'),
parse.Rune('r'),
parse.Rune('t'),
parse.Rune('v'),
parse.Rune('\\'),
parse.Rune('\''),
parse.Rune('"'),
),
)
// https://go.dev/ref/spec#String_literals
var string_lit = parse.Any(parse.String(`""`), parse.String(`''`), interpreted_string_lit, raw_string_lit)
var interpreted_string_lit = parse.StringFrom(
parse.Rune('"'),
parse.StringFrom(parse.Until(
parse.Any(unicode_value_interpreted, byte_value),
parse.Rune('"'),
)),
parse.Rune('"'),
)
var unicode_value_interpreted = parse.Any(little_u_value, big_u_value, escaped_char, parse.RuneNotIn("\n\""))
var raw_string_lit = parse.StringFrom(
parse.Rune('`'),
parse.StringFrom(parse.Until(
unicode_value_raw,
parse.Rune('`'),
)),
parse.Rune('`'),
)
var unicode_value_raw = parse.Any(little_u_value, big_u_value, escaped_char, parse.RuneNotIn("`"))