240 lines
5.8 KiB
Go
240 lines
5.8 KiB
Go
|
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("`"))
|