blob: f9163322d55494b41cc82259681cfe797df53e02 [file] [edit]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package parser
import (
"fmt"
"strconv"
"strings"
"cel.dev/cel-go/common"
"cel.dev/cel-go/common/ast"
"cel.dev/cel-go/common/operators"
"cel.dev/cel-go/common/runes"
"cel.dev/cel-go/common/types"
)
type binaryOpInfo struct {
precedence int
name string
kind tokenKind
}
var (
opLogicalOr = binaryOpInfo{precedence: 1, name: operators.LogicalOr, kind: tokLogicalOr}
opLogicalAnd = binaryOpInfo{precedence: 2, name: operators.LogicalAnd, kind: tokLogicalAnd}
opLess = binaryOpInfo{precedence: 3, name: operators.Less, kind: tokLess}
opLessEqual = binaryOpInfo{precedence: 3, name: operators.LessEquals, kind: tokLessEqual}
opGreater = binaryOpInfo{precedence: 3, name: operators.Greater, kind: tokGreater}
opGreaterEqual = binaryOpInfo{precedence: 3, name: operators.GreaterEquals, kind: tokGreaterEqual}
opEqualEqual = binaryOpInfo{precedence: 3, name: operators.Equals, kind: tokEqualEqual}
opExclamationEqual = binaryOpInfo{precedence: 3, name: operators.NotEquals, kind: tokExclamationEqual}
opIn = binaryOpInfo{precedence: 3, name: operators.In, kind: tokIn}
opPlus = binaryOpInfo{precedence: 4, name: operators.Add, kind: tokPlus}
opMinus = binaryOpInfo{precedence: 4, name: operators.Subtract, kind: tokMinus}
opAsterisk = binaryOpInfo{precedence: 5, name: operators.Multiply, kind: tokAsterisk}
opSlash = binaryOpInfo{precedence: 5, name: operators.Divide, kind: tokSlash}
opPercent = binaryOpInfo{precedence: 5, name: operators.Modulo, kind: tokPercent}
opDefault = binaryOpInfo{precedence: 0, name: "", kind: tokError}
)
func getBinaryOpInfo(kind tokenKind) binaryOpInfo {
switch kind {
case tokLogicalOr:
return opLogicalOr
case tokLogicalAnd:
return opLogicalAnd
case tokLess:
return opLess
case tokLessEqual:
return opLessEqual
case tokGreater:
return opGreater
case tokGreaterEqual:
return opGreaterEqual
case tokEqualEqual:
return opEqualEqual
case tokExclamationEqual:
return opExclamationEqual
case tokIn:
return opIn
case tokPlus:
return opPlus
case tokMinus:
return opMinus
case tokAsterisk:
return opAsterisk
case tokSlash:
return opSlash
case tokPercent:
return opPercent
default:
return opDefault
}
}
type prattParserWorker struct {
content runes.Buffer
length int32
helper *parserHelper
errors *parseErrors
exprFactory ast.ExprFactory
lexer *lexer
currTok token
peekTok token
macros map[string]Macro
recursionDepth int
recursionLimitExceeded bool
errorCount int
maxRecursionDepth int
maxExpressionNodeCount int
errorReportingLimit int
errorRecoveryLimit int
populateMacroCalls bool
enableOptionalSyntax bool
enableVariadicOperatorASTs bool
enableIdentEscapeSyntax bool
}
// prattParser encapsulates the context necessary to perform Pratt parsing for different expressions.
type prattParser struct {
options
}
// Parse parses the expression represented by source using the Pratt parser and returns the result.
func (p *prattParser) Parse(source common.Source) (*ast.AST, *common.Errors) {
errs := common.NewErrors(source)
buf, ok := source.(runes.Buffer)
if !ok {
buf = runes.NewBuffer(source.Content())
}
if buf.Len() > p.expressionSizeCodePointLimit {
errs.ReportError(common.NoLocation,
"expression code point size exceeds limit: size: %d, limit %d",
buf.Len(), p.expressionSizeCodePointLimit)
return nil, errs
}
accu := AccumulatorName
if p.enableHiddenAccumulatorName {
accu = HiddenAccumulatorName
}
fac := ast.NewExprFactoryWithAccumulator(accu)
pratt := &prattParserWorker{
content: buf,
length: int32(buf.Len()),
helper: newParserHelper(source, fac),
errors: &parseErrors{errs},
exprFactory: fac,
lexer: newLexer(buf),
macros: p.macros,
maxRecursionDepth: p.maxRecursionDepth,
maxExpressionNodeCount: p.maxExpressionNodeCount,
errorReportingLimit: p.errorReportingLimit,
errorRecoveryLimit: p.errorRecoveryLimit,
populateMacroCalls: p.populateMacroCalls,
enableOptionalSyntax: p.enableOptionalSyntax,
enableVariadicOperatorASTs: p.enableVariadicOperatorASTs,
enableIdentEscapeSyntax: p.enableIdentEscapeSyntax,
}
pratt.initTokenStream()
out := pratt.parse()
if len(errs.GetErrors()) > 0 {
return nil, errs
}
return ast.NewAST(out, pratt.helper.getSourceInfo()), errs
}
func (p *prattParserWorker) initTokenStream() {
p.currTok = token{kind: tokError, start: 0, end: 0}
p.peekTok = p.nextSignificantToken(true)
}
func (p *prattParserWorker) isRecoveryLimitExceeded() bool {
return p.errorCount > p.errorRecoveryLimit
}
func (p *prattParserWorker) nextSignificantToken(reportError bool) token {
if p.isRecoveryLimitExceeded() {
return token{kind: tokEnd, start: p.length, end: p.length}
}
for {
tok := p.lexer.Lex()
if tok.kind == tokWhitespace || tok.kind == tokComment {
continue
}
if tok.kind == tokError && reportError {
p.reportError(tok, "%s", p.lexer.GetError().message)
if p.isRecoveryLimitExceeded() {
return token{kind: tokEnd, start: p.length, end: p.length}
}
}
return tok
}
}
func (p *prattParserWorker) nextToken() token {
p.currTok = p.peekTok
if p.isRecoveryLimitExceeded() {
p.peekTok = token{kind: tokEnd, start: p.length, end: p.length}
return p.currTok
}
if p.peekTok.kind != tokEnd {
p.peekTok = p.nextSignificantToken(true)
}
return p.currTok
}
func (p *prattParserWorker) tokenText(tok token) string {
if tok.start >= 0 && tok.end >= tok.start && tok.end <= p.length {
return p.content.Slice(int(tok.start), int(tok.end))
}
return ""
}
func (p *prattParserWorker) nextID(tok token) int64 {
return p.helper.idFromOffsets(tok.start, tok.end)
}
func (p *prattParserWorker) expect(kind tokenKind, msg string) bool {
if p.peekTok.kind == kind {
p.nextToken()
return true
}
if p.isRecoveryLimitExceeded() {
return false
}
if p.peekTok.kind != tokError {
if msg == "" {
tokText := p.tokenText(p.peekTok)
formattedTok := fmt.Sprintf("'%s'", tokText)
if p.peekTok.kind == tokEnd {
formattedTok = "<EOF>"
}
msg = fmt.Sprintf("Syntax error: mismatched input %s expecting '%s'", formattedTok, kind.String())
}
p.reportError(p.peekTok, "%s", msg)
}
p.synchronizeOnDelimiter()
return false
}
func (p *prattParserWorker) synchronizeOnDelimiter() {
if p.isRecoveryLimitExceeded() {
p.peekTok = token{kind: tokEnd, start: p.length, end: p.length}
return
}
for p.peekTok.kind != tokEnd {
if p.peekTok.kind == tokComma ||
p.peekTok.kind == tokRightParen ||
p.peekTok.kind == tokRightBracket ||
p.peekTok.kind == tokRightBrace {
break
}
p.nextToken()
}
}
func (p *prattParserWorker) reportError(ctx any, format string, args ...any) ast.Expr {
if p.isRecoveryLimitExceeded() {
return p.helper.newExpr(common.NoLocation)
}
p.errorCount++
var location common.Location
err := p.helper.newExpr(ctx)
switch c := ctx.(type) {
case common.Location:
location = c
case token:
location = p.helper.getLocation(err.ID())
default:
location = p.helper.getLocation(err.ID())
}
if p.errorCount <= p.errorReportingLimit {
p.errors.reportErrorAtID(err.ID(), location, format, args...)
}
if p.isRecoveryLimitExceeded() {
p.peekTok = token{kind: tokEnd, start: p.length, end: p.length}
}
return err
}
func (p *prattParserWorker) newLogicManager(function string, term ast.Expr) *logicManager {
if p.enableVariadicOperatorASTs {
return newVariadicLogicManager(p.exprFactory, function, term)
}
return newBalancingLogicManager(p.exprFactory, function, term)
}
func (p *prattParserWorker) globalCallOrMacro(exprID int64, function string, args ...ast.Expr) ast.Expr {
if expr, found := p.expandMacro(exprID, function, nil, args...); found {
return expr
}
return p.helper.newGlobalCall(exprID, function, args...)
}
func (p *prattParserWorker) receiverCallOrMacro(exprID int64, function string, target ast.Expr, args ...ast.Expr) ast.Expr {
if expr, found := p.expandMacro(exprID, function, target, args...); found {
return expr
}
return p.helper.newReceiverCall(exprID, function, target, args...)
}
func (p *prattParserWorker) expandMacro(exprID int64, function string, target ast.Expr, args ...ast.Expr) (ast.Expr, bool) {
if len(p.macros) == 0 {
return nil, false
}
macro, found := p.macros[makeMacroKey(function, len(args), target != nil)]
if !found {
macro, found = p.macros[makeVarArgMacroKey(function, target != nil)]
if !found {
return nil, false
}
}
if int(p.helper.expressionCount()) > p.maxExpressionNodeCount {
loc := p.helper.getLocation(exprID)
p.helper.deleteID(exprID)
return p.reportError(loc, "expression count exceeds limit of %d while expanding macro '%s'", p.maxExpressionNodeCount, function), true
}
eh := exprHelperPool.Get().(*exprHelper)
eh.parserHelper = p.helper
eh.id = exprID
expr, err := macro.Expander()(eh, target, args)
exprHelperPool.Put(eh)
if int(p.helper.expressionCount()) > p.maxExpressionNodeCount {
loc := p.helper.getLocation(exprID)
p.helper.deleteID(exprID)
return p.reportError(loc, "expression count exceeds limit of %d while expanding macro '%s'", p.maxExpressionNodeCount, function), true
}
if err != nil {
loc := err.Location
if loc == nil {
loc = p.helper.getLocation(exprID)
}
p.helper.deleteID(exprID)
return p.reportError(loc, "%s", err.Message), true
}
if expr == nil {
return nil, false
}
if p.populateMacroCalls {
p.helper.addMacroCall(expr.ID(), function, target, args...)
}
p.helper.deleteID(exprID)
return expr, true
}
func (p *prattParserWorker) normalizeIdent(tok token, allowQuoted bool) string {
text := p.tokenText(tok)
if len(text) == 0 {
return ""
}
if text[0] == '`' {
if !allowQuoted {
p.reportError(tok, "unexpected quoted identifier")
return ""
}
if !p.enableIdentEscapeSyntax {
p.reportError(tok, "unsupported syntax: '`'")
}
if len(text) < 2 || text[len(text)-1] != '`' {
p.reportError(tok, "unterminated quoted identifier")
return ""
}
// Validate the quoted identifier syntax:
// ESC_IDENTIFIER : '`' (LETTER | DIGIT | '_' | '.' | '-' | '/' | ' ')+ '`';
inner := text[1 : len(text)-1]
if len(inner) == 0 {
p.reportError(tok, "unexpected quoted identifier")
return ""
}
for _, c := range inner {
if !isAlpha(c) && !isDigit(c) && c != '_' && c != '.' && c != '-' && c != '/' && c != ' ' {
p.reportError(tok, "unexpected quoted identifier")
return ""
}
}
return inner
}
return text
}
func (p *prattParserWorker) parse() ast.Expr {
expr := p.parseExpr()
if p.recursionLimitExceeded || p.isRecoveryLimitExceeded() {
return expr
}
if p.peekTok.kind != tokEnd {
if p.peekTok.kind != tokError {
p.reportError(p.peekTok, "Syntax error: mismatched input '%s' expecting <EOF>", p.tokenText(p.peekTok))
}
for p.peekTok.kind != tokEnd && !p.isRecoveryLimitExceeded() {
p.nextToken()
}
}
return expr
}
func (p *prattParserWorker) parseExpr() ast.Expr {
if p.recursionLimitExceeded || p.isRecoveryLimitExceeded() {
return p.helper.newExpr(common.NoLocation)
}
if p.recursionDepth > p.maxRecursionDepth {
p.recursionLimitExceeded = true
p.errors.internalError(fmt.Sprintf("expression recursion limit exceeded: %d", p.maxRecursionDepth))
return p.helper.newExpr(common.NoLocation)
}
p.recursionDepth++
expr := p.parseBinaryAndTernary(0)
p.recursionDepth--
return expr
}
func (p *prattParserWorker) parseBinaryAndTernary(minPrec int) ast.Expr {
lhs := p.parseSelectorChain()
for {
tok := p.peekTok.kind
if tok == tokQuestion && minPrec <= 0 {
lhs = p.parseTernary(lhs)
continue
}
opInfo := getBinaryOpInfo(tok)
if opInfo.kind == tokError || opInfo.precedence < minPrec {
break
}
if opInfo.name == operators.LogicalOr || opInfo.name == operators.LogicalAnd {
lhs = p.parseLogicalChain(lhs, opInfo)
continue
}
opTok := p.nextToken()
opID := p.nextID(opTok)
rhs := p.parseBinaryAndTernary(opInfo.precedence + 1)
lhs = p.helper.newGlobalCall(opID, opInfo.name, lhs, rhs)
}
return lhs
}
func (p *prattParserWorker) parseTernary(lhs ast.Expr) ast.Expr {
qTok := p.nextToken()
opID := p.nextID(qTok)
trueExpr := p.parseBinaryAndTernary(1)
if !p.expect(tokColon, "expected ':' in conditional expression") {
return lhs
}
falseExpr := p.parseBinaryAndTernary(0)
return p.helper.newGlobalCall(opID, operators.Conditional, lhs, trueExpr, falseExpr)
}
func (p *prattParserWorker) parseLogicalChain(lhs ast.Expr, opInfo binaryOpInfo) ast.Expr {
l := p.newLogicManager(opInfo.name, lhs)
for p.peekTok.kind == opInfo.kind {
opTok := p.nextToken()
rhs := p.parseBinaryAndTernary(opInfo.precedence + 1)
opID := p.nextID(opTok)
l.addTerm(opID, rhs)
}
return l.toExpr()
}
func (p *prattParserWorker) parseSelectorChain() ast.Expr {
lhs := p.parseUnary()
return p.parseSelectorChainTail(lhs)
}
func (p *prattParserWorker) parseSelectorChainTail(lhs ast.Expr) ast.Expr {
for {
switch p.peekTok.kind {
case tokDot:
dotTok := p.nextToken()
optional := false
if p.peekTok.kind == tokQuestion {
p.nextToken()
optional = true
if !p.enableOptionalSyntax {
p.reportError(dotTok, "unsupported syntax '.?'")
}
}
fieldTok := p.nextToken()
if fieldTok.kind != tokIdent && fieldTok.kind != tokReservedWord {
if fieldTok.kind != tokError {
p.reportError(fieldTok, "expected identifier after '.'")
}
p.synchronizeOnDelimiter()
return lhs
}
isMemberCall := p.peekTok.kind == tokLeftParen
field := p.normalizeIdent(fieldTok, !isMemberCall)
if optional {
opID := p.nextID(dotTok)
fieldID := p.nextID(fieldTok)
lhs = p.helper.newGlobalCall(opID, operators.OptSelect, lhs, p.helper.newLiteralString(fieldID, field))
} else if isMemberCall {
lparen := p.nextToken()
callID := p.nextID(lparen)
args := p.parseArguments(tokRightParen)
lhs = p.receiverCallOrMacro(callID, field, lhs, args...)
} else {
dotID := p.nextID(dotTok)
lhs = p.helper.newSelect(dotID, lhs, field)
}
case tokLeftBracket:
bracketTok := p.nextToken()
opID := p.nextID(bracketTok)
optional := false
if p.peekTok.kind == tokQuestion {
p.nextToken()
optional = true
if !p.enableOptionalSyntax {
p.reportError(bracketTok, "unsupported syntax '?'")
}
}
index := p.parseExpr()
p.expect(tokRightBracket, "expected ']'")
opName := operators.Index
if optional {
opName = operators.OptIndex
}
lhs = p.helper.newGlobalCall(opID, opName, lhs, index)
case tokLeftBrace:
if rng, found := p.helper.sourceInfo.GetOffsetRange(lhs.ID()); found {
if structName, ok := p.extractStructName(lhs); ok {
objID := p.helper.id(rng)
lhs = p.parseStruct(objID, structName)
} else {
return lhs
}
} else {
return lhs
}
default:
return lhs
}
}
}
func (p *prattParserWorker) extractStructName(expr ast.Expr) (string, bool) {
if expr == nil || expr.Kind() == ast.LiteralKind {
return "", false
}
if expr.Kind() == ast.IdentKind {
name := expr.AsIdent()
p.helper.deleteID(expr.ID())
return name, true
}
if expr.Kind() == ast.SelectKind {
sel := expr.AsSelect()
if sel.IsTestOnly() {
return "", false
}
prefix, ok := p.extractStructName(sel.Operand())
if !ok {
return "", false
}
p.helper.deleteID(expr.ID())
return prefix + "." + sel.FieldName(), true
}
return "", false
}
func (p *prattParserWorker) parseStruct(objID int64, structName string) ast.Expr {
p.nextToken() // consumes {
var fields []ast.EntryExpr
for p.peekTok.kind != tokRightBrace && p.peekTok.kind != tokEnd {
optional := false
if p.peekTok.kind == tokQuestion {
q := p.nextToken()
optional = true
if !p.enableOptionalSyntax {
p.reportError(q, "unsupported syntax '?'")
}
}
fieldTok := p.nextToken()
if fieldTok.kind != tokIdent && fieldTok.kind != tokReservedWord {
p.reportError(fieldTok, "expected struct field name")
p.synchronizeOnDelimiter()
break
}
fieldName := p.normalizeIdent(fieldTok, true)
colonTok := p.peekTok
if !p.expect(tokColon, "expected ':' in struct field") {
break
}
fieldID := p.nextID(colonTok)
val := p.parseExpr()
fields = append(fields, p.helper.newObjectField(fieldID, fieldName, val, optional))
if p.peekTok.kind == tokComma {
p.nextToken()
} else {
break
}
}
p.expect(tokRightBrace, "expected '}'")
return p.helper.newObject(objID, structName, fields...)
}
func (p *prattParserWorker) parseUnary() ast.Expr {
tok := p.peekTok.kind
if tok == tokExclamation || tok == tokMinus {
return p.parseUnaryOps()
}
return p.parsePrimary()
}
func (p *prattParserWorker) parseUnaryOps() ast.Expr {
op := p.nextToken()
if p.peekTok.kind == tokExclamation || p.peekTok.kind == tokMinus {
return p.parseUnaryOpsChain(op)
}
if op.kind == tokMinus {
if p.peekTok.kind == tokInt {
return p.parseNegativeIntLiteral(p.nextID(op))
}
if p.peekTok.kind == tokFloat {
return p.parseNegativeDoubleLiteral(p.nextID(op))
}
}
opID := p.nextID(op)
operand := p.parseSelectorChain()
opName := operators.LogicalNot
if op.kind == tokMinus {
opName = operators.Negate
}
return p.globalCallOrMacro(opID, opName, operand)
}
func (p *prattParserWorker) parseUnaryOpsChain(firstOp token) ast.Expr {
type unaryOpInfo struct {
kind tokenKind
id int64
}
ops := []unaryOpInfo{{kind: firstOp.kind, id: p.nextID(firstOp)}}
for p.peekTok.kind == tokExclamation || p.peekTok.kind == tokMinus {
op := p.nextToken()
ops = append(ops, unaryOpInfo{kind: op.kind, id: p.nextID(op)})
}
var operand ast.Expr
if len(ops) > 0 && ops[len(ops)-1].kind == tokMinus {
switch p.peekTok.kind {
case tokInt:
lastOp := ops[len(ops)-1]
ops = ops[:len(ops)-1]
operand = p.parseNegativeIntLiteral(lastOp.id)
case tokFloat:
lastOp := ops[len(ops)-1]
ops = ops[:len(ops)-1]
operand = p.parseNegativeDoubleLiteral(lastOp.id)
default:
operand = p.parseSelectorChain()
}
} else {
operand = p.parseSelectorChain()
}
for i := len(ops) - 1; i >= 0; i-- {
opName := operators.LogicalNot
if ops[i].kind == tokMinus {
opName = operators.Negate
}
operand = p.helper.newGlobalCall(ops[i].id, opName, operand)
}
return operand
}
func (p *prattParserWorker) countGroupingParentheses() int {
if p.peekTok.kind != tokLeftParen {
return 0
}
saved := p.lexer.SavePosition()
leadingOpenParens := 1
tok := p.nextSignificantToken(false)
for tok.kind == tokLeftParen {
leadingOpenParens++
tok = p.nextSignificantToken(false)
}
if leadingOpenParens == 1 {
p.lexer.RestorePosition(saved)
return 1
}
openParens := leadingOpenParens
consecutiveLeadingClosed := 0
for openParens > 0 {
if tok.kind == tokEnd || tok.kind == tokError {
p.lexer.RestorePosition(saved)
return 1
}
switch tok.kind {
case tokLeftParen:
openParens++
consecutiveLeadingClosed = 0
case tokRightParen:
if leadingOpenParens == openParens {
leadingOpenParens--
consecutiveLeadingClosed++
} else {
consecutiveLeadingClosed = 0
}
openParens--
default:
consecutiveLeadingClosed = 0
}
if openParens > 0 {
tok = p.nextSignificantToken(false)
}
}
p.lexer.RestorePosition(saved)
if consecutiveLeadingClosed > 1 {
return consecutiveLeadingClosed
}
return 1
}
func (p *prattParserWorker) parsePrimary() ast.Expr {
switch p.peekTok.kind {
case tokLeftParen:
groupingCount := p.countGroupingParentheses()
for i := 0; i < groupingCount; i++ {
p.nextToken()
}
expr := p.parseExpr()
for i := 0; i < groupingCount; i++ {
p.expect(tokRightParen, "expected ')'")
}
return expr
case tokNull:
return p.helper.exprFactory.NewLiteral(p.nextID(p.nextToken()), types.NullValue)
case tokTrue:
tok := p.nextToken()
return p.helper.newLiteralBool(p.nextID(tok), true)
case tokFalse:
tok := p.nextToken()
return p.helper.newLiteralBool(p.nextID(tok), false)
case tokInt:
return p.parseIntLiteral()
case tokUint:
return p.parseUintLiteral()
case tokFloat:
return p.parseDoubleLiteral()
case tokString:
return p.parseStringLiteral()
case tokBytes:
return p.parseBytesLiteral()
case tokLeftBracket:
return p.parseList()
case tokLeftBrace:
return p.parseMap()
case tokDot, tokIdent, tokReservedWord:
return p.parseIdentOrCall()
default:
badTok := p.nextToken()
if badTok.kind != tokError {
if badTok.kind == tokEnd {
p.reportError(badTok, "Syntax error: mismatched input '<EOF>' expecting expression")
} else {
p.reportError(badTok, "unexpected token")
}
}
return p.helper.newExpr(badTok)
}
}
func (p *prattParserWorker) parseList() ast.Expr {
openTok := p.nextToken()
listID := p.nextID(openTok)
var elems []ast.Expr
var optionals []int32
for p.peekTok.kind != tokRightBracket && p.peekTok.kind != tokEnd {
optional := false
if p.peekTok.kind == tokQuestion {
q := p.nextToken()
optional = true
if !p.enableOptionalSyntax {
p.reportError(q, "unsupported syntax '?'")
}
}
if optional {
optionals = append(optionals, int32(len(elems)))
}
elem := p.parseExpr()
elems = append(elems, elem)
if p.peekTok.kind == tokComma {
p.nextToken()
if p.peekTok.kind == tokRightBracket {
break
}
continue
}
break
}
p.expect(tokRightBracket, "expected ']'")
return p.helper.newList(listID, elems, optionals...)
}
func (p *prattParserWorker) parseMap() ast.Expr {
openTok := p.nextToken()
mapID := p.nextID(openTok)
var entries []ast.EntryExpr
for p.peekTok.kind != tokRightBrace && p.peekTok.kind != tokEnd {
optional := false
if p.peekTok.kind == tokQuestion {
q := p.nextToken()
optional = true
if !p.enableOptionalSyntax {
p.reportError(q, "unsupported syntax '?'")
}
}
key := p.parseExpr()
colonTok := p.peekTok
if !p.expect(tokColon, "expected ':' in map entry") {
break
}
entryID := p.nextID(colonTok)
val := p.parseExpr()
entries = append(entries, p.helper.newMapEntry(entryID, key, val, optional))
if p.peekTok.kind == tokComma {
p.nextToken()
if p.peekTok.kind == tokRightBrace {
break
}
continue
}
break
}
p.expect(tokRightBrace, "expected '}'")
return p.helper.newMap(mapID, entries...)
}
func (p *prattParserWorker) parseIdentOrCall() ast.Expr {
leadingDot := false
firstTok := p.peekTok
if p.peekTok.kind == tokDot {
p.nextToken()
leadingDot = true
}
idTok := p.nextToken()
if idTok.kind != tokIdent && idTok.kind != tokReservedWord {
if idTok.kind != tokError {
p.reportError(idTok, "expected identifier")
}
return p.helper.newExpr(idTok)
}
idText := p.normalizeIdent(idTok, false)
if idTok.kind == tokReservedWord {
if _, ok := reservedIds[idText]; ok {
p.reportError(idTok, "reserved identifier: %s", idText)
}
}
name := idText
if leadingDot {
name = "." + idText
}
id := p.nextID(firstTok)
if p.peekTok.kind == tokLeftParen {
p.nextToken()
args := p.parseArguments(tokRightParen)
return p.globalCallOrMacro(id, name, args...)
}
return p.helper.newIdent(id, name)
}
func (p *prattParserWorker) parseArguments(closeTok tokenKind) []ast.Expr {
var args []ast.Expr
if p.peekTok.kind != closeTok && p.peekTok.kind != tokEnd {
for {
args = append(args, p.parseExpr())
if p.peekTok.kind == tokComma {
p.nextToken()
if p.peekTok.kind == closeTok {
break
}
continue
}
break
}
}
p.expect(closeTok, "")
return args
}
func (p *prattParserWorker) parseIntLiteral() ast.Expr {
tok := p.nextToken()
id := p.nextID(tok)
text := p.tokenText(tok)
base := 10
if strings.HasPrefix(text, "0x") || strings.HasPrefix(text, "0X") {
base = 16
text = text[2:]
}
val, err := strconv.ParseInt(text, base, 64)
if err != nil {
return p.reportError(tok, "invalid int literal")
}
return p.helper.newLiteralInt(id, val)
}
func (p *prattParserWorker) parseNegativeIntLiteral(opID int64) ast.Expr {
tok := p.nextToken()
text := p.tokenText(tok)
base := 10
if strings.HasPrefix(text, "0x") || strings.HasPrefix(text, "0X") {
base = 16
text = text[2:]
}
val, err := strconv.ParseInt("-"+text, base, 64)
if err != nil {
return p.reportError(tok, "invalid int literal")
}
return p.helper.newLiteralInt(opID, val)
}
func (p *prattParserWorker) parseUintLiteral() ast.Expr {
tok := p.nextToken()
id := p.nextID(tok)
text := p.tokenText(tok)
text = text[:len(text)-1]
base := 10
if strings.HasPrefix(text, "0x") || strings.HasPrefix(text, "0X") {
base = 16
text = text[2:]
}
val, err := strconv.ParseUint(text, base, 64)
if err != nil {
return p.reportError(tok, "invalid uint literal")
}
return p.helper.newLiteralUint(id, val)
}
func (p *prattParserWorker) parseDoubleLiteral() ast.Expr {
tok := p.nextToken()
id := p.nextID(tok)
text := p.tokenText(tok)
val, err := strconv.ParseFloat(text, 64)
if err != nil {
return p.reportError(tok, "invalid double literal")
}
return p.helper.newLiteralDouble(id, val)
}
func (p *prattParserWorker) parseNegativeDoubleLiteral(opID int64) ast.Expr {
tok := p.nextToken()
text := p.tokenText(tok)
val, err := strconv.ParseFloat(text, 64)
if err != nil {
return p.reportError(tok, "invalid double literal")
}
return p.helper.newLiteralDouble(opID, -val)
}
func (p *prattParserWorker) parseStringLiteral() ast.Expr {
tok := p.nextToken()
id := p.nextID(tok)
text := p.tokenText(tok)
unescaped, err := unescape(text, false)
if err != nil {
return p.reportError(tok, "%s", err.Error())
}
return p.helper.newLiteralString(id, unescaped)
}
func (p *prattParserWorker) parseBytesLiteral() ast.Expr {
tok := p.nextToken()
id := p.nextID(tok)
text := p.tokenText(tok)
if strings.HasPrefix(text, "b") || strings.HasPrefix(text, "B") {
text = text[1:]
} else if strings.HasPrefix(text, "rb") || strings.HasPrefix(text, "RB") || strings.HasPrefix(text, "rB") || strings.HasPrefix(text, "Rb") {
text = "r" + text[2:]
} else if strings.HasPrefix(text, "br") || strings.HasPrefix(text, "BR") || strings.HasPrefix(text, "bR") || strings.HasPrefix(text, "Br") {
text = text[1:]
}
unescaped, err := unescape(text, true)
if err != nil {
return p.reportError(tok, "%s", err.Error())
}
return p.helper.newLiteralBytes(id, []byte(unescaped))
}