Example #1
0
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
}
Example #2
0
// 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)
}
Example #3
0
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)
		}
	}
}