604 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			604 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
| package jmespath
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| type astNodeType int
 | |
| 
 | |
| //go:generate stringer -type astNodeType
 | |
| const (
 | |
| 	ASTEmpty astNodeType = iota
 | |
| 	ASTComparator
 | |
| 	ASTCurrentNode
 | |
| 	ASTExpRef
 | |
| 	ASTFunctionExpression
 | |
| 	ASTField
 | |
| 	ASTFilterProjection
 | |
| 	ASTFlatten
 | |
| 	ASTIdentity
 | |
| 	ASTIndex
 | |
| 	ASTIndexExpression
 | |
| 	ASTKeyValPair
 | |
| 	ASTLiteral
 | |
| 	ASTMultiSelectHash
 | |
| 	ASTMultiSelectList
 | |
| 	ASTOrExpression
 | |
| 	ASTAndExpression
 | |
| 	ASTNotExpression
 | |
| 	ASTPipe
 | |
| 	ASTProjection
 | |
| 	ASTSubexpression
 | |
| 	ASTSlice
 | |
| 	ASTValueProjection
 | |
| )
 | |
| 
 | |
| // ASTNode represents the abstract syntax tree of a JMESPath expression.
 | |
| type ASTNode struct {
 | |
| 	nodeType astNodeType
 | |
| 	value    interface{}
 | |
| 	children []ASTNode
 | |
| }
 | |
| 
 | |
| func (node ASTNode) String() string {
 | |
| 	return node.PrettyPrint(0)
 | |
| }
 | |
| 
 | |
| // PrettyPrint will pretty print the parsed AST.
 | |
| // The AST is an implementation detail and this pretty print
 | |
| // function is provided as a convenience method to help with
 | |
| // debugging.  You should not rely on its output as the internal
 | |
| // structure of the AST may change at any time.
 | |
| func (node ASTNode) PrettyPrint(indent int) string {
 | |
| 	spaces := strings.Repeat(" ", indent)
 | |
| 	output := fmt.Sprintf("%s%s {\n", spaces, node.nodeType)
 | |
| 	nextIndent := indent + 2
 | |
| 	if node.value != nil {
 | |
| 		if converted, ok := node.value.(fmt.Stringer); ok {
 | |
| 			// Account for things like comparator nodes
 | |
| 			// that are enums with a String() method.
 | |
| 			output += fmt.Sprintf("%svalue: %s\n", strings.Repeat(" ", nextIndent), converted.String())
 | |
| 		} else {
 | |
| 			output += fmt.Sprintf("%svalue: %#v\n", strings.Repeat(" ", nextIndent), node.value)
 | |
| 		}
 | |
| 	}
 | |
| 	lastIndex := len(node.children)
 | |
| 	if lastIndex > 0 {
 | |
| 		output += fmt.Sprintf("%schildren: {\n", strings.Repeat(" ", nextIndent))
 | |
| 		childIndent := nextIndent + 2
 | |
| 		for _, elem := range node.children {
 | |
| 			output += elem.PrettyPrint(childIndent)
 | |
| 		}
 | |
| 	}
 | |
| 	output += fmt.Sprintf("%s}\n", spaces)
 | |
| 	return output
 | |
| }
 | |
| 
 | |
| var bindingPowers = map[tokType]int{
 | |
| 	tEOF:                0,
 | |
| 	tUnquotedIdentifier: 0,
 | |
| 	tQuotedIdentifier:   0,
 | |
| 	tRbracket:           0,
 | |
| 	tRparen:             0,
 | |
| 	tComma:              0,
 | |
| 	tRbrace:             0,
 | |
| 	tNumber:             0,
 | |
| 	tCurrent:            0,
 | |
| 	tExpref:             0,
 | |
| 	tColon:              0,
 | |
| 	tPipe:               1,
 | |
| 	tOr:                 2,
 | |
| 	tAnd:                3,
 | |
| 	tEQ:                 5,
 | |
| 	tLT:                 5,
 | |
| 	tLTE:                5,
 | |
| 	tGT:                 5,
 | |
| 	tGTE:                5,
 | |
| 	tNE:                 5,
 | |
| 	tFlatten:            9,
 | |
| 	tStar:               20,
 | |
| 	tFilter:             21,
 | |
| 	tDot:                40,
 | |
| 	tNot:                45,
 | |
| 	tLbrace:             50,
 | |
| 	tLbracket:           55,
 | |
| 	tLparen:             60,
 | |
| }
 | |
| 
 | |
| // Parser holds state about the current expression being parsed.
 | |
| type Parser struct {
 | |
| 	expression string
 | |
| 	tokens     []token
 | |
| 	index      int
 | |
| }
 | |
| 
 | |
| // NewParser creates a new JMESPath parser.
 | |
| func NewParser() *Parser {
 | |
| 	p := Parser{}
 | |
| 	return &p
 | |
| }
 | |
| 
 | |
| // Parse will compile a JMESPath expression.
 | |
| func (p *Parser) Parse(expression string) (ASTNode, error) {
 | |
| 	lexer := NewLexer()
 | |
| 	p.expression = expression
 | |
| 	p.index = 0
 | |
| 	tokens, err := lexer.tokenize(expression)
 | |
| 	if err != nil {
 | |
| 		return ASTNode{}, err
 | |
| 	}
 | |
| 	p.tokens = tokens
 | |
| 	parsed, err := p.parseExpression(0)
 | |
| 	if err != nil {
 | |
| 		return ASTNode{}, err
 | |
| 	}
 | |
| 	if p.current() != tEOF {
 | |
| 		return ASTNode{}, p.syntaxError(fmt.Sprintf(
 | |
| 			"Unexpected token at the end of the expression: %s", p.current()))
 | |
| 	}
 | |
| 	return parsed, nil
 | |
| }
 | |
| 
 | |
| func (p *Parser) parseExpression(bindingPower int) (ASTNode, error) {
 | |
| 	var err error
 | |
| 	leftToken := p.lookaheadToken(0)
 | |
| 	p.advance()
 | |
| 	leftNode, err := p.nud(leftToken)
 | |
| 	if err != nil {
 | |
| 		return ASTNode{}, err
 | |
| 	}
 | |
| 	currentToken := p.current()
 | |
| 	for bindingPower < bindingPowers[currentToken] {
 | |
| 		p.advance()
 | |
| 		leftNode, err = p.led(currentToken, leftNode)
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		currentToken = p.current()
 | |
| 	}
 | |
| 	return leftNode, nil
 | |
| }
 | |
| 
 | |
| func (p *Parser) parseIndexExpression() (ASTNode, error) {
 | |
| 	if p.lookahead(0) == tColon || p.lookahead(1) == tColon {
 | |
| 		return p.parseSliceExpression()
 | |
| 	}
 | |
| 	indexStr := p.lookaheadToken(0).value
 | |
| 	parsedInt, err := strconv.Atoi(indexStr)
 | |
| 	if err != nil {
 | |
| 		return ASTNode{}, err
 | |
| 	}
 | |
| 	indexNode := ASTNode{nodeType: ASTIndex, value: parsedInt}
 | |
| 	p.advance()
 | |
| 	if err := p.match(tRbracket); err != nil {
 | |
| 		return ASTNode{}, err
 | |
| 	}
 | |
| 	return indexNode, nil
 | |
| }
 | |
| 
 | |
| func (p *Parser) parseSliceExpression() (ASTNode, error) {
 | |
| 	parts := []*int{nil, nil, nil}
 | |
| 	index := 0
 | |
| 	current := p.current()
 | |
| 	for current != tRbracket && index < 3 {
 | |
| 		if current == tColon {
 | |
| 			index++
 | |
| 			p.advance()
 | |
| 		} else if current == tNumber {
 | |
| 			parsedInt, err := strconv.Atoi(p.lookaheadToken(0).value)
 | |
| 			if err != nil {
 | |
| 				return ASTNode{}, err
 | |
| 			}
 | |
| 			parts[index] = &parsedInt
 | |
| 			p.advance()
 | |
| 		} else {
 | |
| 			return ASTNode{}, p.syntaxError(
 | |
| 				"Expected tColon or tNumber" + ", received: " + p.current().String())
 | |
| 		}
 | |
| 		current = p.current()
 | |
| 	}
 | |
| 	if err := p.match(tRbracket); err != nil {
 | |
| 		return ASTNode{}, err
 | |
| 	}
 | |
| 	return ASTNode{
 | |
| 		nodeType: ASTSlice,
 | |
| 		value:    parts,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (p *Parser) match(tokenType tokType) error {
 | |
| 	if p.current() == tokenType {
 | |
| 		p.advance()
 | |
| 		return nil
 | |
| 	}
 | |
| 	return p.syntaxError("Expected " + tokenType.String() + ", received: " + p.current().String())
 | |
| }
 | |
| 
 | |
| func (p *Parser) led(tokenType tokType, node ASTNode) (ASTNode, error) {
 | |
| 	switch tokenType {
 | |
| 	case tDot:
 | |
| 		if p.current() != tStar {
 | |
| 			right, err := p.parseDotRHS(bindingPowers[tDot])
 | |
| 			return ASTNode{
 | |
| 				nodeType: ASTSubexpression,
 | |
| 				children: []ASTNode{node, right},
 | |
| 			}, err
 | |
| 		}
 | |
| 		p.advance()
 | |
| 		right, err := p.parseProjectionRHS(bindingPowers[tDot])
 | |
| 		return ASTNode{
 | |
| 			nodeType: ASTValueProjection,
 | |
| 			children: []ASTNode{node, right},
 | |
| 		}, err
 | |
| 	case tPipe:
 | |
| 		right, err := p.parseExpression(bindingPowers[tPipe])
 | |
| 		return ASTNode{nodeType: ASTPipe, children: []ASTNode{node, right}}, err
 | |
| 	case tOr:
 | |
| 		right, err := p.parseExpression(bindingPowers[tOr])
 | |
| 		return ASTNode{nodeType: ASTOrExpression, children: []ASTNode{node, right}}, err
 | |
| 	case tAnd:
 | |
| 		right, err := p.parseExpression(bindingPowers[tAnd])
 | |
| 		return ASTNode{nodeType: ASTAndExpression, children: []ASTNode{node, right}}, err
 | |
| 	case tLparen:
 | |
| 		name := node.value
 | |
| 		var args []ASTNode
 | |
| 		for p.current() != tRparen {
 | |
| 			expression, err := p.parseExpression(0)
 | |
| 			if err != nil {
 | |
| 				return ASTNode{}, err
 | |
| 			}
 | |
| 			if p.current() == tComma {
 | |
| 				if err := p.match(tComma); err != nil {
 | |
| 					return ASTNode{}, err
 | |
| 				}
 | |
| 			}
 | |
| 			args = append(args, expression)
 | |
| 		}
 | |
| 		if err := p.match(tRparen); err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		return ASTNode{
 | |
| 			nodeType: ASTFunctionExpression,
 | |
| 			value:    name,
 | |
| 			children: args,
 | |
| 		}, nil
 | |
| 	case tFilter:
 | |
| 		return p.parseFilter(node)
 | |
| 	case tFlatten:
 | |
| 		left := ASTNode{nodeType: ASTFlatten, children: []ASTNode{node}}
 | |
| 		right, err := p.parseProjectionRHS(bindingPowers[tFlatten])
 | |
| 		return ASTNode{
 | |
| 			nodeType: ASTProjection,
 | |
| 			children: []ASTNode{left, right},
 | |
| 		}, err
 | |
| 	case tEQ, tNE, tGT, tGTE, tLT, tLTE:
 | |
| 		right, err := p.parseExpression(bindingPowers[tokenType])
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		return ASTNode{
 | |
| 			nodeType: ASTComparator,
 | |
| 			value:    tokenType,
 | |
| 			children: []ASTNode{node, right},
 | |
| 		}, nil
 | |
| 	case tLbracket:
 | |
| 		tokenType := p.current()
 | |
| 		var right ASTNode
 | |
| 		var err error
 | |
| 		if tokenType == tNumber || tokenType == tColon {
 | |
| 			right, err = p.parseIndexExpression()
 | |
| 			if err != nil {
 | |
| 				return ASTNode{}, err
 | |
| 			}
 | |
| 			return p.projectIfSlice(node, right)
 | |
| 		}
 | |
| 		// Otherwise this is a projection.
 | |
| 		if err := p.match(tStar); err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		if err := p.match(tRbracket); err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		right, err = p.parseProjectionRHS(bindingPowers[tStar])
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		return ASTNode{
 | |
| 			nodeType: ASTProjection,
 | |
| 			children: []ASTNode{node, right},
 | |
| 		}, nil
 | |
| 	}
 | |
| 	return ASTNode{}, p.syntaxError("Unexpected token: " + tokenType.String())
 | |
| }
 | |
| 
 | |
| func (p *Parser) nud(token token) (ASTNode, error) {
 | |
| 	switch token.tokenType {
 | |
| 	case tJSONLiteral:
 | |
| 		var parsed interface{}
 | |
| 		err := json.Unmarshal([]byte(token.value), &parsed)
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		return ASTNode{nodeType: ASTLiteral, value: parsed}, nil
 | |
| 	case tStringLiteral:
 | |
| 		return ASTNode{nodeType: ASTLiteral, value: token.value}, nil
 | |
| 	case tUnquotedIdentifier:
 | |
| 		return ASTNode{
 | |
| 			nodeType: ASTField,
 | |
| 			value:    token.value,
 | |
| 		}, nil
 | |
| 	case tQuotedIdentifier:
 | |
| 		node := ASTNode{nodeType: ASTField, value: token.value}
 | |
| 		if p.current() == tLparen {
 | |
| 			return ASTNode{}, p.syntaxErrorToken("Can't have quoted identifier as function name.", token)
 | |
| 		}
 | |
| 		return node, nil
 | |
| 	case tStar:
 | |
| 		left := ASTNode{nodeType: ASTIdentity}
 | |
| 		var right ASTNode
 | |
| 		var err error
 | |
| 		if p.current() == tRbracket {
 | |
| 			right = ASTNode{nodeType: ASTIdentity}
 | |
| 		} else {
 | |
| 			right, err = p.parseProjectionRHS(bindingPowers[tStar])
 | |
| 		}
 | |
| 		return ASTNode{nodeType: ASTValueProjection, children: []ASTNode{left, right}}, err
 | |
| 	case tFilter:
 | |
| 		return p.parseFilter(ASTNode{nodeType: ASTIdentity})
 | |
| 	case tLbrace:
 | |
| 		return p.parseMultiSelectHash()
 | |
| 	case tFlatten:
 | |
| 		left := ASTNode{
 | |
| 			nodeType: ASTFlatten,
 | |
| 			children: []ASTNode{{nodeType: ASTIdentity}},
 | |
| 		}
 | |
| 		right, err := p.parseProjectionRHS(bindingPowers[tFlatten])
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		return ASTNode{nodeType: ASTProjection, children: []ASTNode{left, right}}, nil
 | |
| 	case tLbracket:
 | |
| 		tokenType := p.current()
 | |
| 		//var right ASTNode
 | |
| 		if tokenType == tNumber || tokenType == tColon {
 | |
| 			right, err := p.parseIndexExpression()
 | |
| 			if err != nil {
 | |
| 				return ASTNode{}, nil
 | |
| 			}
 | |
| 			return p.projectIfSlice(ASTNode{nodeType: ASTIdentity}, right)
 | |
| 		} else if tokenType == tStar && p.lookahead(1) == tRbracket {
 | |
| 			p.advance()
 | |
| 			p.advance()
 | |
| 			right, err := p.parseProjectionRHS(bindingPowers[tStar])
 | |
| 			if err != nil {
 | |
| 				return ASTNode{}, err
 | |
| 			}
 | |
| 			return ASTNode{
 | |
| 				nodeType: ASTProjection,
 | |
| 				children: []ASTNode{{nodeType: ASTIdentity}, right},
 | |
| 			}, nil
 | |
| 		} else {
 | |
| 			return p.parseMultiSelectList()
 | |
| 		}
 | |
| 	case tCurrent:
 | |
| 		return ASTNode{nodeType: ASTCurrentNode}, nil
 | |
| 	case tExpref:
 | |
| 		expression, err := p.parseExpression(bindingPowers[tExpref])
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		return ASTNode{nodeType: ASTExpRef, children: []ASTNode{expression}}, nil
 | |
| 	case tNot:
 | |
| 		expression, err := p.parseExpression(bindingPowers[tNot])
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		return ASTNode{nodeType: ASTNotExpression, children: []ASTNode{expression}}, nil
 | |
| 	case tLparen:
 | |
| 		expression, err := p.parseExpression(0)
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		if err := p.match(tRparen); err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		return expression, nil
 | |
| 	case tEOF:
 | |
| 		return ASTNode{}, p.syntaxErrorToken("Incomplete expression", token)
 | |
| 	}
 | |
| 
 | |
| 	return ASTNode{}, p.syntaxErrorToken("Invalid token: "+token.tokenType.String(), token)
 | |
| }
 | |
| 
 | |
| func (p *Parser) parseMultiSelectList() (ASTNode, error) {
 | |
| 	var expressions []ASTNode
 | |
| 	for {
 | |
| 		expression, err := p.parseExpression(0)
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		expressions = append(expressions, expression)
 | |
| 		if p.current() == tRbracket {
 | |
| 			break
 | |
| 		}
 | |
| 		err = p.match(tComma)
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 	}
 | |
| 	err := p.match(tRbracket)
 | |
| 	if err != nil {
 | |
| 		return ASTNode{}, err
 | |
| 	}
 | |
| 	return ASTNode{
 | |
| 		nodeType: ASTMultiSelectList,
 | |
| 		children: expressions,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (p *Parser) parseMultiSelectHash() (ASTNode, error) {
 | |
| 	var children []ASTNode
 | |
| 	for {
 | |
| 		keyToken := p.lookaheadToken(0)
 | |
| 		if err := p.match(tUnquotedIdentifier); err != nil {
 | |
| 			if err := p.match(tQuotedIdentifier); err != nil {
 | |
| 				return ASTNode{}, p.syntaxError("Expected tQuotedIdentifier or tUnquotedIdentifier")
 | |
| 			}
 | |
| 		}
 | |
| 		keyName := keyToken.value
 | |
| 		err := p.match(tColon)
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		value, err := p.parseExpression(0)
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		node := ASTNode{
 | |
| 			nodeType: ASTKeyValPair,
 | |
| 			value:    keyName,
 | |
| 			children: []ASTNode{value},
 | |
| 		}
 | |
| 		children = append(children, node)
 | |
| 		if p.current() == tComma {
 | |
| 			err := p.match(tComma)
 | |
| 			if err != nil {
 | |
| 				return ASTNode{}, nil
 | |
| 			}
 | |
| 		} else if p.current() == tRbrace {
 | |
| 			err := p.match(tRbrace)
 | |
| 			if err != nil {
 | |
| 				return ASTNode{}, nil
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return ASTNode{
 | |
| 		nodeType: ASTMultiSelectHash,
 | |
| 		children: children,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (p *Parser) projectIfSlice(left ASTNode, right ASTNode) (ASTNode, error) {
 | |
| 	indexExpr := ASTNode{
 | |
| 		nodeType: ASTIndexExpression,
 | |
| 		children: []ASTNode{left, right},
 | |
| 	}
 | |
| 	if right.nodeType == ASTSlice {
 | |
| 		right, err := p.parseProjectionRHS(bindingPowers[tStar])
 | |
| 		return ASTNode{
 | |
| 			nodeType: ASTProjection,
 | |
| 			children: []ASTNode{indexExpr, right},
 | |
| 		}, err
 | |
| 	}
 | |
| 	return indexExpr, nil
 | |
| }
 | |
| func (p *Parser) parseFilter(node ASTNode) (ASTNode, error) {
 | |
| 	var right, condition ASTNode
 | |
| 	var err error
 | |
| 	condition, err = p.parseExpression(0)
 | |
| 	if err != nil {
 | |
| 		return ASTNode{}, err
 | |
| 	}
 | |
| 	if err := p.match(tRbracket); err != nil {
 | |
| 		return ASTNode{}, err
 | |
| 	}
 | |
| 	if p.current() == tFlatten {
 | |
| 		right = ASTNode{nodeType: ASTIdentity}
 | |
| 	} else {
 | |
| 		right, err = p.parseProjectionRHS(bindingPowers[tFilter])
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return ASTNode{
 | |
| 		nodeType: ASTFilterProjection,
 | |
| 		children: []ASTNode{node, right, condition},
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (p *Parser) parseDotRHS(bindingPower int) (ASTNode, error) {
 | |
| 	lookahead := p.current()
 | |
| 	if tokensOneOf([]tokType{tQuotedIdentifier, tUnquotedIdentifier, tStar}, lookahead) {
 | |
| 		return p.parseExpression(bindingPower)
 | |
| 	} else if lookahead == tLbracket {
 | |
| 		if err := p.match(tLbracket); err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		return p.parseMultiSelectList()
 | |
| 	} else if lookahead == tLbrace {
 | |
| 		if err := p.match(tLbrace); err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		return p.parseMultiSelectHash()
 | |
| 	}
 | |
| 	return ASTNode{}, p.syntaxError("Expected identifier, lbracket, or lbrace")
 | |
| }
 | |
| 
 | |
| func (p *Parser) parseProjectionRHS(bindingPower int) (ASTNode, error) {
 | |
| 	current := p.current()
 | |
| 	if bindingPowers[current] < 10 {
 | |
| 		return ASTNode{nodeType: ASTIdentity}, nil
 | |
| 	} else if current == tLbracket {
 | |
| 		return p.parseExpression(bindingPower)
 | |
| 	} else if current == tFilter {
 | |
| 		return p.parseExpression(bindingPower)
 | |
| 	} else if current == tDot {
 | |
| 		err := p.match(tDot)
 | |
| 		if err != nil {
 | |
| 			return ASTNode{}, err
 | |
| 		}
 | |
| 		return p.parseDotRHS(bindingPower)
 | |
| 	} else {
 | |
| 		return ASTNode{}, p.syntaxError("Error")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (p *Parser) lookahead(number int) tokType {
 | |
| 	return p.lookaheadToken(number).tokenType
 | |
| }
 | |
| 
 | |
| func (p *Parser) current() tokType {
 | |
| 	return p.lookahead(0)
 | |
| }
 | |
| 
 | |
| func (p *Parser) lookaheadToken(number int) token {
 | |
| 	return p.tokens[p.index+number]
 | |
| }
 | |
| 
 | |
| func (p *Parser) advance() {
 | |
| 	p.index++
 | |
| }
 | |
| 
 | |
| func tokensOneOf(elements []tokType, token tokType) bool {
 | |
| 	for _, elem := range elements {
 | |
| 		if elem == token {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (p *Parser) syntaxError(msg string) SyntaxError {
 | |
| 	return SyntaxError{
 | |
| 		msg:        msg,
 | |
| 		Expression: p.expression,
 | |
| 		Offset:     p.lookaheadToken(0).position,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Create a SyntaxError based on the provided token.
 | |
| // This differs from syntaxError() which creates a SyntaxError
 | |
| // based on the current lookahead token.
 | |
| func (p *Parser) syntaxErrorToken(msg string, t token) SyntaxError {
 | |
| 	return SyntaxError{
 | |
| 		msg:        msg,
 | |
| 		Expression: p.expression,
 | |
| 		Offset:     t.position,
 | |
| 	}
 | |
| }
 |