Happy case tests for parser.

No testing of error cases or location reporting yet.
This commit is contained in:
Joe Beda 2016-03-07 15:25:34 -08:00
parent 2b23986b9a
commit 566a2cf4f2
3 changed files with 161 additions and 17 deletions

View File

@ -26,7 +26,6 @@ type precedence int
const ( const (
applyPrecedence precedence = 2 // Function calls and indexing. applyPrecedence precedence = 2 // Function calls and indexing.
unaryPrecedence precedence = 4 // Logical and bitwise negation, unary + - unaryPrecedence precedence = 4 // Logical and bitwise negation, unary + -
beforeElsePrecedence precedence = 15 // True branch of an if.
maxPrecedence precedence = 16 // Local, If, Import, Function, Error maxPrecedence precedence = 16 // Local, If, Import, Function, Error
) )
@ -189,6 +188,9 @@ func (p *parser) parseBind(binds *astLocalBinds) error {
}) })
} else { } else {
_, err = p.popExpectOp("=") _, err = p.popExpectOp("=")
if err != nil {
return err
}
body, err := p.parse(maxPrecedence) body, err := p.parse(maxPrecedence)
if err != nil { if err != nil {
return err return err
@ -248,7 +250,6 @@ func (p *parser) parseObjectRemainder(tok *token) (astNode, *token, error) {
literalFields := make(literalFieldSet) literalFields := make(literalFieldSet)
binds := make(identifierSet) binds := make(identifierSet)
_ = "breakpoint"
gotComma := false gotComma := false
first := true first := true
@ -914,6 +915,7 @@ func (p *parser) parse(prec precedence) (astNode, error) {
// the operator. // the operator.
switch p.peek().kind { switch p.peek().kind {
case tokenOperator: case tokenOperator:
_ = "breakpoint"
if p.peek().data == ":" { if p.peek().data == ":" {
// Special case for the colons in assert. Since COLON is no-longer a // 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 // 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: default:
return lhs, nil 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,
}
}
}
} }
} }

View File

@ -22,14 +22,90 @@ import (
"github.com/kr/pretty" "github.com/kr/pretty"
) )
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) { func TestParser(t *testing.T) {
tokens, err := lex("test", `{hello: "world"}`) for _, s := range tests {
tokens, err := lex("test", s)
if err != nil { if err != nil {
t.Errorf("Unexpected lex error: %v", err) t.Errorf("Unexpected lex error\n input: %v\n error: %v", s, err)
continue
} }
ast, err := parse(tokens) ast, err := parse(tokens)
if err != nil { if err != nil {
t.Errorf("Unexpected parse error: %v", err) 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))
}
} }
fmt.Printf("%# v", pretty.Formatter(ast))
} }