// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package parser

import (
	"reflect"
	"testing"

	"github.com/hashicorp/hil/ast"
	"github.com/hashicorp/hil/scanner"
)

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},
			},
		},

		// Identifier starting with a number
		{
			`foo ${123abcd}`,
			true,
			nil,
		},

		// Identifier starting with a *
		{
			`foo ${*abcd}`,
			true,
			nil,
		},

		{
			"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.*.baz",
						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 ${"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.LiteralNode{
						Value: `bar "baz"`,
						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},
					},
				},
			},
		},

		{
			"${!a}",
			false,
			&ast.Output{
				Posx: ast.Pos{Column: 1, Line: 1},
				Exprs: []ast.Node{
					// !a parses as (false == a)
					&ast.Arithmetic{
						Op: ast.ArithmeticOpEqual,
						Exprs: []ast.Node{
							&ast.LiteralNode{
								Value: false,
								Typex: ast.TypeBool,
								Posx:  ast.Pos{Column: 3, Line: 1},
							},
							&ast.VariableAccess{
								Name: "a",
								Posx: ast.Pos{Column: 4, Line: 1},
							},
						},
						Posx: ast.Pos{Column: 3, Line: 1},
					},
				},
			},
		},

		{
			"${a==b}",
			false,
			&ast.Output{
				Posx: ast.Pos{Column: 1, Line: 1},
				Exprs: []ast.Node{
					&ast.Arithmetic{
						Op: ast.ArithmeticOpEqual,
						Exprs: []ast.Node{
							&ast.VariableAccess{
								Name: "a",
								Posx: ast.Pos{Column: 3, Line: 1},
							},
							&ast.VariableAccess{
								Name: "b",
								Posx: ast.Pos{Column: 6, Line: 1},
							},
						},
						Posx: ast.Pos{Column: 3, Line: 1},
					},
				},
			},
		},

		{
			"${a!=b}",
			false,
			&ast.Output{
				Posx: ast.Pos{Column: 1, Line: 1},
				Exprs: []ast.Node{
					&ast.Arithmetic{
						Op: ast.ArithmeticOpNotEqual,
						Exprs: []ast.Node{
							&ast.VariableAccess{
								Name: "a",
								Posx: ast.Pos{Column: 3, Line: 1},
							},
							&ast.VariableAccess{
								Name: "b",
								Posx: ast.Pos{Column: 6, Line: 1},
							},
						},
						Posx: ast.Pos{Column: 3, Line: 1},
					},
				},
			},
		},

		{
			"${a < 5 ? a + 5 : a + 10}",
			false,
			&ast.Output{
				Posx: ast.Pos{Column: 1, Line: 1},
				Exprs: []ast.Node{
					&ast.Conditional{
						CondExpr: &ast.Arithmetic{
							Op: ast.ArithmeticOpLessThan,
							Exprs: []ast.Node{
								&ast.VariableAccess{
									Name: "a",
									Posx: ast.Pos{Column: 3, Line: 1},
								},
								&ast.LiteralNode{
									Value: 5,
									Typex: ast.TypeInt,
									Posx:  ast.Pos{Column: 7, Line: 1},
								},
							},
							Posx: ast.Pos{Column: 3, Line: 1},
						},
						TrueExpr: &ast.Arithmetic{
							Op: ast.ArithmeticOpAdd,
							Exprs: []ast.Node{
								&ast.VariableAccess{
									Name: "a",
									Posx: ast.Pos{Column: 11, Line: 1},
								},
								&ast.LiteralNode{
									Value: 5,
									Typex: ast.TypeInt,
									Posx:  ast.Pos{Column: 15, Line: 1},
								},
							},
							Posx: ast.Pos{Column: 11, Line: 1},
						},
						FalseExpr: &ast.Arithmetic{
							Op: ast.ArithmeticOpAdd,
							Exprs: []ast.Node{
								&ast.VariableAccess{
									Name: "a",
									Posx: ast.Pos{Column: 19, Line: 1},
								},
								&ast.LiteralNode{
									Value: 10,
									Typex: ast.TypeInt,
									Posx:  ast.Pos{Column: 23, Line: 1},
								},
							},
							Posx: ast.Pos{Column: 19, Line: 1},
						},
						Posx: ast.Pos{Column: 3, Line: 1},
					},
				},
			},
		},

		{
			"${true&&false}",
			false,
			&ast.Output{
				Posx: ast.Pos{Column: 1, Line: 1},
				Exprs: []ast.Node{
					&ast.Arithmetic{
						Op: ast.ArithmeticOpLogicalAnd,
						Exprs: []ast.Node{
							&ast.LiteralNode{
								Value: true,
								Typex: ast.TypeBool,
								Posx:  ast.Pos{Column: 3, Line: 1},
							},
							&ast.LiteralNode{
								Value: false,
								Typex: ast.TypeBool,
								Posx:  ast.Pos{Column: 9, Line: 1},
							},
						},
						Posx: ast.Pos{Column: 3, Line: 1},
					},
				},
			},
		},

		{
			"${true||false}",
			false,
			&ast.Output{
				Posx: ast.Pos{Column: 1, Line: 1},
				Exprs: []ast.Node{
					&ast.Arithmetic{
						Op: ast.ArithmeticOpLogicalOr,
						Exprs: []ast.Node{
							&ast.LiteralNode{
								Value: true,
								Typex: ast.TypeBool,
								Posx:  ast.Pos{Column: 3, Line: 1},
							},
							&ast.LiteralNode{
								Value: false,
								Typex: ast.TypeBool,
								Posx:  ast.Pos{Column: 9, Line: 1},
							},
						},
						Posx: ast.Pos{Column: 3, Line: 1},
					},
				},
			},
		},

		{
			"${a||b&&c}",
			false,
			&ast.Output{
				Posx: ast.Pos{Column: 1, Line: 1},
				Exprs: []ast.Node{
					&ast.Arithmetic{
						Op: ast.ArithmeticOpLogicalOr,
						Exprs: []ast.Node{
							&ast.VariableAccess{
								Name: "a",
								Posx: ast.Pos{Column: 3, Line: 1},
							},
							&ast.Arithmetic{
								Op: ast.ArithmeticOpLogicalAnd,
								Exprs: []ast.Node{
									&ast.VariableAccess{
										Name: "b",
										Posx: ast.Pos{Column: 6, Line: 1},
									},
									&ast.VariableAccess{
										Name: "c",
										Posx: ast.Pos{Column: 9, Line: 1},
									},
								},
								Posx: ast.Pos{Column: 6, Line: 1},
							},
						},
						Posx: ast.Pos{Column: 3, Line: 1},
					},
				},
			},
		},

		{
			"${a&&b||c}",
			false,
			&ast.Output{
				Posx: ast.Pos{Column: 1, Line: 1},
				Exprs: []ast.Node{
					&ast.Arithmetic{
						Op: ast.ArithmeticOpLogicalOr,
						Exprs: []ast.Node{
							&ast.Arithmetic{
								Op: ast.ArithmeticOpLogicalAnd,
								Exprs: []ast.Node{
									&ast.VariableAccess{
										Name: "a",
										Posx: ast.Pos{Column: 3, Line: 1},
									},
									&ast.VariableAccess{
										Name: "b",
										Posx: ast.Pos{Column: 6, Line: 1},
									},
								},
								Posx: ast.Pos{Column: 3, Line: 1},
							},
							&ast.VariableAccess{
								Name: "c",
								Posx: ast.Pos{Column: 9, Line: 1},
							},
						},
						Posx: ast.Pos{Column: 3, Line: 1},
					},
				},
			},
		},

		{
			"${a<5||b>2}",
			false,
			&ast.Output{
				Posx: ast.Pos{Column: 1, Line: 1},
				Exprs: []ast.Node{
					&ast.Arithmetic{
						Op: ast.ArithmeticOpLogicalOr,
						Exprs: []ast.Node{
							&ast.Arithmetic{
								Op: ast.ArithmeticOpLessThan,
								Exprs: []ast.Node{
									&ast.VariableAccess{
										Name: "a",
										Posx: ast.Pos{Column: 3, Line: 1},
									},
									&ast.LiteralNode{
										Value: 5,
										Typex: ast.TypeInt,
										Posx:  ast.Pos{Column: 5, Line: 1},
									},
								},
								Posx: ast.Pos{Column: 3, Line: 1},
							},
							&ast.Arithmetic{
								Op: ast.ArithmeticOpGreaterThan,
								Exprs: []ast.Node{
									&ast.VariableAccess{
										Name: "b",
										Posx: ast.Pos{Column: 8, Line: 1},
									},
									&ast.LiteralNode{
										Value: 2,
										Typex: ast.TypeInt,
										Posx:  ast.Pos{Column: 10, Line: 1},
									},
								},
								Posx: ast.Pos{Column: 8, Line: 1},
							},
						},
						Posx: ast.Pos{Column: 3, Line: 1},
					},
				},
			},
		},

		{
			"${a<5&&b>2}",
			false,
			&ast.Output{
				Posx: ast.Pos{Column: 1, Line: 1},
				Exprs: []ast.Node{
					&ast.Arithmetic{
						Op: ast.ArithmeticOpLogicalAnd,
						Exprs: []ast.Node{
							&ast.Arithmetic{
								Op: ast.ArithmeticOpLessThan,
								Exprs: []ast.Node{
									&ast.VariableAccess{
										Name: "a",
										Posx: ast.Pos{Column: 3, Line: 1},
									},
									&ast.LiteralNode{
										Value: 5,
										Typex: ast.TypeInt,
										Posx:  ast.Pos{Column: 5, Line: 1},
									},
								},
								Posx: ast.Pos{Column: 3, Line: 1},
							},
							&ast.Arithmetic{
								Op: ast.ArithmeticOpGreaterThan,
								Exprs: []ast.Node{
									&ast.VariableAccess{
										Name: "b",
										Posx: ast.Pos{Column: 8, Line: 1},
									},
									&ast.LiteralNode{
										Value: 2,
										Typex: ast.TypeInt,
										Posx:  ast.Pos{Column: 10, Line: 1},
									},
								},
								Posx: ast.Pos{Column: 8, Line: 1},
							},
						},
						Posx: ast.Pos{Column: 3, 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=baz}",
			true,
			nil,
		},

		{
			"${foo&baz}",
			true,
			nil,
		},

		{
			"${foo|baz}",
			true,
			nil,
		},

		{
			"${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)
		}
	}
}
