go-jsonnet/parser.go
2016-02-19 22:25:18 -05:00

236 lines
5.3 KiB
Go

package jsonnet
import (
"fmt"
)
type precedence int
const (
applyPrecedence precedence = 2 // Function calls and indexing.
unaryPrecedence precedence = 4 // Logical and bitwise negation, unary + -
beforeElsePrecedence precedence = 15 // True branch of an if.
maxPrecedence precedence = 16 // Local, If, Import, Function, Error
)
// ---------------------------------------------------------------------------
func makeUnexpectedError(t token, while string) error {
return makeStaticError(
fmt.Sprintf("Unexpected: %v while %v", t, while), t.loc)
}
func locFromTokens(begin, end *token) LocationRange {
return makeLocationRange(begin.loc.FileName, begin.loc.Begin, end.loc.End)
}
func locFromTokenAST(begin *token, end astNode) LocationRange {
return makeLocationRange(begin.loc.FileName, begin.loc.Begin, end.Loc().End)
}
// ---------------------------------------------------------------------------
type parser struct {
t tokens
currT int
}
func makeParser(t tokens) *parser {
return &parser{
t: t,
}
}
func (p *parser) pop() *token {
t := &p.t[p.currT]
p.currT++
return t
}
func (p *parser) popExpect(tk tokenKind) (*token, error) {
t := p.pop()
if t.kind != tk {
return nil, makeStaticError(
fmt.Sprintf("Expected token %v but got %v", tk, t), t.loc)
}
return t, nil
}
func (p *parser) popExpectOp(op string) (*token, error) {
t := p.pop()
if t.kind != tokenOperator || t.data != op {
return nil, makeStaticError(
fmt.Sprintf("Expected operator %v but got %v", op, t), t.loc)
}
return t, nil
}
func (p *parser) peek() *token {
return &p.t[p.currT]
}
func (p *parser) parseIdentifierList(elementKind string) (identifiers, bool, error) {
_, exprs, got_comma, err := p.parseCommaList(tokenParenR, elementKind)
if err != nil {
return identifiers{}, false, err
}
var ids identifiers
for _, n := range exprs {
v, ok := n.(*astVar)
if !ok {
return identifiers{}, false, makeStaticError(fmt.Sprintf("Not an identifier: %v", n), *n.Loc())
}
ids = append(ids, v.id)
}
return ids, got_comma, nil
}
func (p *parser) parseCommaList(end tokenKind, elementKind string) (*token, astNodes, bool, error) {
var exprs astNodes
got_comma := false
first := true
for {
next := p.peek();
if !first && !got_comma {
if next.kind == tokenComma {
p.pop()
next = p.peek()
got_comma = true
}
}
if next.kind == end {
// got_comma can be true or false here.
return p.pop(), exprs, got_comma, nil
}
if !first && !got_comma {
return nil, nil, false, makeStaticError(fmt.Sprintf("Expected a comma before next %s.", elementKind), next.loc)
}
expr, err := p.parse(maxPrecedence)
if err != nil {
return nil, nil, false, err
}
exprs = append(exprs, expr)
got_comma = false
first = false
}
}
func (p *parser) parse(prec precedence) (astNode, error) {
begin := p.peek()
switch begin.kind {
// These cases have effectively maxPrecedence as the first
// call to parse will parse them.
case tokenAssert:
p.pop()
cond, err := p.parse(maxPrecedence)
if err != nil {
return nil, err
}
var msg astNode
if p.peek().kind == tokenColon {
p.pop()
msg, err = p.parse(maxPrecedence)
if err != nil {
return nil, err
}
}
_, err = p.popExpect(tokenSemicolon)
if err != nil {
return nil, err
}
rest, err := p.parse(maxPrecedence)
if err != nil {
return nil, err
}
return &astAssert{
astNodeBase: astNodeBase{locFromTokenAST(begin, rest)},
cond: cond,
message: msg,
rest: rest,
}, nil
case tokenError:
p.pop()
expr, err := p.parse(maxPrecedence)
if err != nil {
return nil, err
}
return &astError{
astNodeBase: astNodeBase{locFromTokenAST(begin, expr)},
expr: expr,
}, nil
case tokenIf:
p.pop()
cond, err := p.parse(maxPrecedence)
if err != nil {
return nil, err
}
_, err = p.popExpect(tokenThen)
if err != nil {
return nil, err
}
branchTrue, err := p.parse(maxPrecedence)
if err != nil {
return nil, err
}
var branchFalse astNode
lr := locFromTokenAST(begin, branchTrue)
if p.peek().kind == tokenElse {
p.pop()
branchFalse, err = p.parse(maxPrecedence)
if err != nil {
return nil, err
}
lr = locFromTokenAST(begin, branchFalse)
}
return &astConditional{
astNodeBase: astNodeBase{lr},
cond: cond,
branchTrue: branchTrue,
branchFalse: branchFalse,
}, nil
case tokenFunction:
p.pop()
next := p.pop()
if next.kind == tokenParenL {
params, got_comma, err := p.parseIdentifierList("function parameter")
if err != nil {
return nil, err
}
body, err := p.parse(maxPrecedence)
if err != nil {
return nil, err
}
return &astFunction{
astNodeBase: astNodeBase{locFromTokenAST(begin, body)},
parameters: params,
trailingComma: got_comma,
body: body,
}, nil
} else {
return nil, makeStaticError(fmt.Sprintf("Expected ( but got %v", next), next.loc)
}
}
return nil, nil
}
// ---------------------------------------------------------------------------
func parse(t tokens) (astNode, error) {
p := makeParser(t)
expr, err := p.parse(maxPrecedence)
if err != nil {
return nil, err
}
if p.peek().kind != tokenEndOfFile {
return nil, makeStaticError(fmt.Sprintf("Did not expect: %v", p.peek()), p.peek().loc)
}
return expr, nil
}