diff --git a/lexer_test.go b/lexer_test.go index 9390de3..91e880b 100644 --- a/lexer_test.go +++ b/lexer_test.go @@ -43,7 +43,7 @@ var lexTests = []lexTest{ {"colon3", ":::", tokens{{kind: tokenOperator, data: ":::"}}, ""}, {"arrow right", "->", tokens{{kind: tokenOperator, data: "->"}}, ""}, {"less than minus", "<-", tokens{{kind: tokenOperator, data: "<"}, - {kind: tokenOperator, data: "-"}}, ""}, + {kind: tokenOperator, data: "-"}}, ""}, {"comma", ",", tokens{{kind: tokenComma, data: ","}}, ""}, {"dollar", "$", tokens{{kind: tokenDollar, data: "$"}}, ""}, {"dot", ".", tokens{{kind: tokenDot, data: "."}}, ""}, diff --git a/parser.go b/parser.go index 7f43b49..44d9721 100644 --- a/parser.go +++ b/parser.go @@ -24,10 +24,9 @@ import ( 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 + applyPrecedence precedence = 2 // Function calls and indexing. + unaryPrecedence precedence = 4 // Logical and bitwise negation, unary + - + maxPrecedence precedence = 16 // Local, If, Import, Function, Error ) var bopPrecedence = map[binaryOp]precedence{ @@ -189,6 +188,9 @@ func (p *parser) parseBind(binds *astLocalBinds) error { }) } else { _, err = p.popExpectOp("=") + if err != nil { + return err + } body, err := p.parse(maxPrecedence) if err != nil { return err @@ -248,7 +250,6 @@ func (p *parser) parseObjectRemainder(tok *token) (astNode, *token, error) { literalFields := make(literalFieldSet) binds := make(identifierSet) - _ = "breakpoint" gotComma := false first := true @@ -914,6 +915,7 @@ func (p *parser) parse(prec precedence) (astNode, error) { // the operator. switch p.peek().kind { case tokenOperator: + _ = "breakpoint" if p.peek().data == ":" { // Special case for the colons in assert. Since COLON is no-longer a // special token, we have to make sure it does not trip the @@ -939,8 +941,74 @@ func (p *parser) parse(prec precedence) (astNode, error) { default: return lhs, nil } - } + op := p.pop() + switch op.kind { + case tokenBracketL: + index, err := p.parse(maxPrecedence) + if err != nil { + return nil, err + } + end, err := p.popExpect(tokenBracketR) + if err != nil { + return nil, err + } + lhs = &astIndex{ + astNodeBase: astNodeBase{loc: locFromTokens(begin, end)}, + target: lhs, + index: index, + } + case tokenDot: + fieldID, err := p.popExpect(tokenIdentifier) + if err != nil { + return nil, err + } + id := identifier(fieldID.data) + lhs = &astIndex{ + astNodeBase: astNodeBase{loc: locFromTokens(begin, fieldID)}, + target: lhs, + id: &id, + } + case tokenParenL: + end, args, gotComma, err := p.parseCommaList(tokenParenR, "function argument") + if err != nil { + return nil, err + } + tailStrict := false + if p.peek().kind == tokenTailStrict { + p.pop() + tailStrict = true + } + lhs = &astApply{ + astNodeBase: astNodeBase{loc: locFromTokens(begin, end)}, + target: lhs, + arguments: args, + trailingComma: gotComma, + tailStrict: tailStrict, + } + case tokenBraceL: + obj, end, err := p.parseObjectRemainder(op) + if err != nil { + return nil, err + } + lhs = &astApplyBrace{ + astNodeBase: astNodeBase{loc: locFromTokens(begin, end)}, + left: lhs, + right: obj, + } + default: + rhs, err := p.parse(prec - 1) + if err != nil { + return nil, err + } + lhs = &astBinary{ + astNodeBase: astNodeBase{loc: locFromTokenAST(begin, rhs)}, + left: lhs, + op: bop, + right: rhs, + } + } + } } } diff --git a/parser_test.go b/parser_test.go index d84e7bb..02c0d8e 100644 --- a/parser_test.go +++ b/parser_test.go @@ -22,14 +22,90 @@ import ( "github.com/kr/pretty" ) -func TestParser(t *testing.T) { - tokens, err := lex("test", `{hello: "world"}`) - if err != nil { - t.Errorf("Unexpected lex error: %v", err) - } - ast, err := parse(tokens) - if err != nil { - t.Errorf("Unexpected parse error: %v", err) - } - fmt.Printf("%# v", pretty.Formatter(ast)) +var tests = []string{ + `true`, + `1`, + `1.2e3`, + `!true`, + `null`, + + `$.foo.bar`, + `self.foo.bar`, + `super.foo.bar`, + `super[1]`, + `error "Error!"`, + + `"world"`, + `'world'`, + `||| + world +|||`, + + `foo(bar)`, + `foo.bar`, + `foo[bar]`, + + `true || false`, + `0 && 1 || 0`, + `0 && (1 || 0)`, + + `local foo = "bar"; foo`, + `local foo(bar) = bar; foo(1)`, + `{ local foo = "bar", baz: 1}`, + `{ local foo(bar) = bar, baz: foo(1)}`, + + `{ foo(bar, baz): bar+baz }`, + + `{ ["foo" + "bar"]: 3 }`, + `{ ["field" + x]: x for x in [1, 2, 3] }`, + `{ ["field" + x]: x for x in [1, 2, 3] if x <= 2 }`, + `{ ["field" + x + y]: x + y for x in [1, 2, 3] if x <= 2 for y in [4, 5, 6]}`, + + `[]`, + `[a, b, c]`, + `[x for x in [1,2,3] ]`, + `[x for x in [1,2,3] if x <= 2]`, + `[x+y for x in [1,2,3] if x <= 2 for y in [4, 5, 6]]`, + + `{}`, + `{ hello: "world" }`, + `{ hello +: "world" }`, + `{ + hello: "world", + "name":: joe, + 'mood'::: "happy", + ||| + key type +|||: "block", +}`, + + `assert true: 'woah!'; true`, + `{ assert true: 'woah!', foo: bar }`, + + `if n > 1 then 'foos' else 'foo'`, + + `local foo = function(x) x + 1; true`, + + `import 'foo.jsonnet'`, + `importstr 'foo.text'`, + + `{a: b} + {c: d}`, + `{a: b}{c: d}`, +} + +func TestParser(t *testing.T) { + for _, s := range tests { + tokens, err := lex("test", s) + if err != nil { + t.Errorf("Unexpected lex error\n input: %v\n error: %v", s, err) + continue + } + ast, err := parse(tokens) + if err != nil { + t.Errorf("Unexpected parse error\n input: %v\n error: %v", s, err) + } + if false { + fmt.Printf("input: %v\nast: %# v\n\n", s, pretty.Formatter(ast)) + } + } }