func Fuzz(data []byte) int { str := string(data) ch := scanner.Scan(str, ast.Pos{Line: 1, Column: 1}) _, err := Parse(ch) if err != nil { return 0 } return 1 }
// ParseWithPosition is like Parse except that it overrides the source // row and column position of the first character in the string, which should // be 1-based. // // This can be used when HIL is embedded in another language and the outer // parser knows the row and column where the HIL expression started within // the overall source file. func ParseWithPosition(v string, pos ast.Pos) (ast.Node, error) { ch := scanner.Scan(v, pos) return parser.Parse(ch) }
func TestParser(t *testing.T) { cases := []struct { Input string Error bool Result ast.Node }{ { "", false, &ast.LiteralNode{ Value: "", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, }, { "$", false, &ast.LiteralNode{ Value: "$", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, }, { "foo", false, &ast.LiteralNode{ Value: "foo", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, }, { "$${var.foo}", false, &ast.LiteralNode{ Value: "${var.foo}", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, }, { "foo ${var.bar}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "foo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.VariableAccess{ Name: "var.bar", Posx: ast.Pos{Column: 7, Line: 1}, }, }, }, }, { "foo ${var.bar} baz", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "foo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.VariableAccess{ Name: "var.bar", Posx: ast.Pos{Column: 7, Line: 1}, }, &ast.LiteralNode{ Value: " baz", Typex: ast.TypeString, Posx: ast.Pos{Column: 15, Line: 1}, }, }, }, }, { "foo ${var.bar.0}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "foo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.VariableAccess{ Name: "var.bar.0", Posx: ast.Pos{Column: 7, Line: 1}, }, }, }, }, { "foo ${foo.foo-bar.baz.0.attr}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "foo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.VariableAccess{ Name: "foo.foo-bar.baz.0.attr", Posx: ast.Pos{Column: 7, Line: 1}, }, }, }, }, { `foo ${"bar"}`, false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "foo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.LiteralNode{ Value: "bar", Typex: ast.TypeString, Posx: ast.Pos{Column: 8, Line: 1}, }, }, }, }, { `foo ${"bar\nbaz"}`, false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "foo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.LiteralNode{ Value: "bar\nbaz", Typex: ast.TypeString, Posx: ast.Pos{Column: 8, Line: 1}, }, }, }, }, { `foo ${func('baz')}`, true, nil, }, { "foo ${42}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "foo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.LiteralNode{ Value: 42, Typex: ast.TypeInt, Posx: ast.Pos{Column: 7, Line: 1}, }, }, }, }, { "foo ${3.14159}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "foo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.LiteralNode{ Value: 3.14159, Typex: ast.TypeFloat, Posx: ast.Pos{Column: 7, Line: 1}, }, }, }, }, { "föo ${true}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "föo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.LiteralNode{ Value: true, Typex: ast.TypeBool, Posx: ast.Pos{Column: 7, Line: 1}, }, }, }, }, { "foo ${42+1}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "foo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.Arithmetic{ Op: ast.ArithmeticOpAdd, Exprs: []ast.Node{ &ast.LiteralNode{ Value: 42, Typex: ast.TypeInt, Posx: ast.Pos{Column: 7, Line: 1}, }, &ast.LiteralNode{ Value: 1, Typex: ast.TypeInt, Posx: ast.Pos{Column: 10, Line: 1}, }, }, Posx: ast.Pos{Column: 7, Line: 1}, }, }, }, }, { "foo ${-1}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "foo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.Arithmetic{ Op: ast.ArithmeticOpSub, Exprs: []ast.Node{ &ast.LiteralNode{ Value: 0, Typex: ast.TypeInt, Posx: ast.Pos{Column: 7, Line: 1}, }, &ast.LiteralNode{ Value: 1, Typex: ast.TypeInt, Posx: ast.Pos{Column: 8, Line: 1}, }, }, Posx: ast.Pos{Column: 7, Line: 1}, }, }, }, }, { "foo ${var.bar*1} baz", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "foo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.Arithmetic{ Op: ast.ArithmeticOpMul, Exprs: []ast.Node{ &ast.VariableAccess{ Name: "var.bar", Posx: ast.Pos{Column: 7, Line: 1}, }, &ast.LiteralNode{ Value: 1, Typex: ast.TypeInt, Posx: ast.Pos{Column: 15, Line: 1}, }, }, Posx: ast.Pos{Column: 7, Line: 1}, }, &ast.LiteralNode{ Value: " baz", Typex: ast.TypeString, Posx: ast.Pos{Column: 17, Line: 1}, }, }, }, }, { "${föo()}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.Call{ Func: "föo", Args: nil, Posx: ast.Pos{Column: 3, Line: 1}, }, }, }, }, { "${foo(bar)}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.Call{ Func: "foo", Posx: ast.Pos{Column: 3, Line: 1}, Args: []ast.Node{ &ast.VariableAccess{ Name: "bar", Posx: ast.Pos{Column: 7, Line: 1}, }, }, }, }, }, }, { "${foo(bar, baz)}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.Call{ Func: "foo", Posx: ast.Pos{Column: 3, Line: 1}, Args: []ast.Node{ &ast.VariableAccess{ Name: "bar", Posx: ast.Pos{Column: 7, Line: 1}, }, &ast.VariableAccess{ Name: "baz", Posx: ast.Pos{Column: 12, Line: 1}, }, }, }, }, }, }, { "${foo(bar(baz))}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.Call{ Func: "foo", Posx: ast.Pos{Column: 3, Line: 1}, Args: []ast.Node{ &ast.Call{ Func: "bar", Posx: ast.Pos{Column: 7, Line: 1}, Args: []ast.Node{ &ast.VariableAccess{ Name: "baz", Posx: ast.Pos{Column: 11, Line: 1}, }, }, }, }, }, }, }, }, { `foo ${"bar ${baz}"}`, false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "foo ", Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, &ast.Output{ Posx: ast.Pos{Column: 7, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ Value: "bar ", Typex: ast.TypeString, Posx: ast.Pos{Column: 8, Line: 1}, }, &ast.VariableAccess{ Name: "baz", Posx: ast.Pos{Column: 14, Line: 1}, }, }, }, }, }, }, { "${foo[1]}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.Index{ Posx: ast.Pos{Column: 6, Line: 1}, Target: &ast.VariableAccess{ Name: "foo", Posx: ast.Pos{Column: 3, Line: 1}, }, Key: &ast.LiteralNode{ Value: 1, Typex: ast.TypeInt, Posx: ast.Pos{Column: 7, Line: 1}, }, }, }, }, }, { "${foo[1]} - ${bar[0]}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.Index{ Posx: ast.Pos{Column: 6, Line: 1}, Target: &ast.VariableAccess{ Name: "foo", Posx: ast.Pos{Column: 3, Line: 1}, }, Key: &ast.LiteralNode{ Value: 1, Typex: ast.TypeInt, Posx: ast.Pos{Column: 7, Line: 1}, }, }, &ast.LiteralNode{ Value: " - ", Typex: ast.TypeString, Posx: ast.Pos{Column: 10, Line: 1}, }, &ast.Index{ Posx: ast.Pos{Column: 18, Line: 1}, Target: &ast.VariableAccess{ Name: "bar", Posx: ast.Pos{Column: 15, Line: 1}, }, Key: &ast.LiteralNode{ Value: 0, Typex: ast.TypeInt, Posx: ast.Pos{Column: 19, Line: 1}, }, }, }, }, }, { // * has higher precedence than + "${42+2*2}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.Arithmetic{ Posx: ast.Pos{Column: 3, Line: 1}, Op: ast.ArithmeticOpAdd, Exprs: []ast.Node{ &ast.LiteralNode{ Posx: ast.Pos{Column: 3, Line: 1}, Value: 42, Typex: ast.TypeInt, }, &ast.Arithmetic{ Posx: ast.Pos{Column: 6, Line: 1}, Op: ast.ArithmeticOpMul, Exprs: []ast.Node{ &ast.LiteralNode{ Posx: ast.Pos{Column: 6, Line: 1}, Value: 2, Typex: ast.TypeInt, }, &ast.LiteralNode{ Posx: ast.Pos{Column: 8, Line: 1}, Value: 2, Typex: ast.TypeInt, }, }, }, }, }, }, }, }, { // parentheses override precedence rules "${(42+2)*2}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.Arithmetic{ Posx: ast.Pos{Column: 3, Line: 1}, Op: ast.ArithmeticOpMul, Exprs: []ast.Node{ &ast.Arithmetic{ Posx: ast.Pos{Column: 4, Line: 1}, Op: ast.ArithmeticOpAdd, Exprs: []ast.Node{ &ast.LiteralNode{ Posx: ast.Pos{Column: 4, Line: 1}, Value: 42, Typex: ast.TypeInt, }, &ast.LiteralNode{ Posx: ast.Pos{Column: 7, Line: 1}, Value: 2, Typex: ast.TypeInt, }, }, }, &ast.LiteralNode{ Posx: ast.Pos{Column: 10, Line: 1}, Value: 2, Typex: ast.TypeInt, }, }, }, }, }, }, { // Left-associative parsing of operators with equal precedence "${42+2+2}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.Arithmetic{ Posx: ast.Pos{Column: 3, Line: 1}, Op: ast.ArithmeticOpAdd, Exprs: []ast.Node{ &ast.Arithmetic{ Posx: ast.Pos{Column: 3, Line: 1}, Op: ast.ArithmeticOpAdd, Exprs: []ast.Node{ &ast.LiteralNode{ Posx: ast.Pos{Column: 3, Line: 1}, Value: 42, Typex: ast.TypeInt, }, &ast.LiteralNode{ Posx: ast.Pos{Column: 6, Line: 1}, Value: 2, Typex: ast.TypeInt, }, }, }, &ast.LiteralNode{ Posx: ast.Pos{Column: 8, Line: 1}, Value: 2, Typex: ast.TypeInt, }, }, }, }, }, }, { // Unary - has higher precedence than addition "${42+-2+2}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.Arithmetic{ Posx: ast.Pos{Column: 3, Line: 1}, Op: ast.ArithmeticOpAdd, Exprs: []ast.Node{ &ast.Arithmetic{ Posx: ast.Pos{Column: 3, Line: 1}, Op: ast.ArithmeticOpAdd, Exprs: []ast.Node{ &ast.LiteralNode{ Posx: ast.Pos{Column: 3, Line: 1}, Value: 42, Typex: ast.TypeInt, }, &ast.Arithmetic{ Posx: ast.Pos{Column: 6, Line: 1}, Op: ast.ArithmeticOpSub, Exprs: []ast.Node{ &ast.LiteralNode{ Posx: ast.Pos{Column: 6, Line: 1}, Value: 0, Typex: ast.TypeInt, }, &ast.LiteralNode{ Posx: ast.Pos{Column: 7, Line: 1}, Value: 2, Typex: ast.TypeInt, }, }, }, }, }, &ast.LiteralNode{ Posx: ast.Pos{Column: 9, Line: 1}, Value: 2, Typex: ast.TypeInt, }, }, }, }, }, }, { "${-46+5}", false, &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.Arithmetic{ Posx: ast.Pos{Column: 3, Line: 1}, Op: ast.ArithmeticOpAdd, Exprs: []ast.Node{ &ast.Arithmetic{ Posx: ast.Pos{Column: 3, Line: 1}, Op: ast.ArithmeticOpSub, Exprs: []ast.Node{ &ast.LiteralNode{ Posx: ast.Pos{Column: 3, Line: 1}, Value: 0, Typex: ast.TypeInt, }, &ast.LiteralNode{ Posx: ast.Pos{Column: 4, Line: 1}, Value: 46, Typex: ast.TypeInt, }, }, }, &ast.LiteralNode{ Posx: ast.Pos{Column: 7, Line: 1}, Value: 5, Typex: ast.TypeInt, }, }, }, }, }, }, { "${foo[1][2]}", true, nil, }, { `foo ${bar ${baz}}`, true, nil, }, { `foo ${${baz}}`, true, nil, }, { "${var", true, nil, }, { `${"unclosed`, true, nil, }, { `${"bar\nbaz}`, true, nil, }, { `${ö(o("")`, true, nil, }, { `${"${"${"`, true, nil, }, { `${("$"`, true, nil, }, { `${("${("${"${"$"`, true, nil, }, { `${(p["$"`, true, nil, }, { `${e(e,e,`, true, nil, }, { "${file(/tmp/somefile)}", true, nil, }, } for _, tc := range cases { ch := scanner.Scan(tc.Input, ast.Pos{Line: 1, Column: 1}) actual, err := Parse(ch) if err != nil != tc.Error { t.Errorf("\nError: %s\n\nInput: %s\n", err, tc.Input) } if !reflect.DeepEqual(actual, tc.Result) { t.Errorf("\nGot: %#v\nWant: %#v\n\nInput: %s\n", actual, tc.Result, tc.Input) } } }