func TestIf(t *testing.T) { testStr := `<?php if (true) echo "hello world"; else if (false) echo "no hello world";` p := NewParser() p.disableScoping = true a, _ := p.Parse("test.php", testStr) tree := &ast.IfStmt{ Branches: []ast.IfBranch{ { Condition: &ast.Literal{Type: ast.Boolean, Value: "true"}, Block: ast.Echo(&ast.Literal{Type: ast.String, Value: `"hello world"`}), }, { Condition: &ast.Literal{Type: ast.Boolean, Value: "false"}, Block: ast.Echo(&ast.Literal{Type: ast.String, Value: `"no hello world"`}), }, }, } if !assertEquals(a.Nodes[0], tree) { t.Fatalf("If did not correctly parse") } }
func TestIfBraces(t *testing.T) { testStr := `<?php if (true) { echo "hello world"; } else if (false) { echo "no hello world"; }` p := NewParser(testStr) a, _ := p.Parse() tree := &ast.IfStmt{ Condition: &ast.Literal{Type: ast.Boolean, Value: "true"}, TrueBranch: &ast.Block{ Statements: []ast.Statement{ast.Echo(&ast.Literal{Type: ast.String, Value: `"hello world"`})}, }, FalseBranch: &ast.IfStmt{ Condition: &ast.Literal{Type: ast.Boolean, Value: "false"}, TrueBranch: &ast.Block{ Statements: []ast.Statement{ast.Echo(&ast.Literal{Type: ast.String, Value: `"no hello world"`})}, }, FalseBranch: ast.Block{}, }, } if !assertEquals(a[0], tree) { t.Fatalf("If with braces did not correctly parse") } }
func TestForeachLoop(t *testing.T) { testStr := `<? foreach ($arr as $key => $val) { echo $key . $val; } ?>` p := NewParser(testStr) a, _ := p.Parse() if len(a) == 0 { t.Fatalf("While loop did not correctly parse") } tree := &ast.ForeachStmt{ Source: ast.NewVariable("arr"), Key: ast.NewVariable("key"), Value: ast.NewVariable("val"), LoopBlock: &ast.Block{ Statements: []ast.Statement{ast.Echo(ast.OperatorExpression{ Operator: ".", Operand1: ast.NewVariable("key"), Operand2: ast.NewVariable("val"), Type: ast.String, })}, }, } if !assertEquals(a[0], tree) { t.Fatalf("Foreach did not correctly parse") } }
func TestForeachLoop(t *testing.T) { testStr := `<? foreach ($arr as $key => $val) { echo $key . $val; } ?>` p := NewParser() p.disableScoping = true a, _ := p.Parse("test.php", testStr) if len(a.Nodes) == 0 { t.Fatalf("While loop did not correctly parse") } tree := &ast.ForeachStmt{ Source: ast.NewVariable("arr"), Key: ast.NewVariable("key"), Value: ast.NewVariable("val"), LoopBlock: &ast.Block{ Statements: []ast.Statement{ast.Echo(ast.BinaryExpr{ Operator: ".", Antecedent: ast.NewVariable("key"), Subsequent: ast.NewVariable("val"), Type: ast.String, })}, }, } if !assertEquals(a.Nodes[0], tree) { t.Fatalf("Foreach did not correctly parse") } }
func TestWhileLoopWithAssignment(t *testing.T) { testStr := `<? while ($var = mysql_assoc()) { echo $var; }` p := NewParser(testStr) p.Debug = true p.MaxErrors = 0 a, _ := p.Parse() if len(a) == 0 { t.Fatalf("While loop did not correctly parse") } tree := &ast.WhileStmt{ Termination: ast.AssignmentExpression{ Assignee: ast.NewVariable("var"), Value: &ast.FunctionCallExpression{ FunctionName: ast.Identifier{Value: "mysql_assoc"}, Arguments: make([]ast.Expression, 0), }, Operator: "=", }, LoopBlock: &ast.Block{ Statements: []ast.Statement{ ast.Echo(ast.NewVariable("var")), }, }, } if !assertEquals(a[0], tree) { t.Fatalf("While loop with assignment did not correctly parse") } }
func TestSwitch(t *testing.T) { testStr := `<? switch ($var) { case 1: echo "one"; case 2: { echo "two"; } default: echo "def"; }` p := NewParser() p.disableScoping = true a, _ := p.Parse("test.php", testStr) if len(a.Nodes) == 0 { t.Fatalf("Array lookup did not correctly parse") } tree := ast.SwitchStmt{ Expr: ast.NewVariable("var"), Cases: []*ast.SwitchCase{ { Expr: &ast.Literal{Type: ast.Float, Value: "1"}, Block: ast.Block{ Statements: []ast.Statement{ ast.Echo(&ast.Literal{Type: ast.String, Value: `"one"`}), }, }, }, { Expr: &ast.Literal{Type: ast.Float, Value: "2"}, Block: ast.Block{ Statements: []ast.Statement{ ast.Echo(&ast.Literal{Type: ast.String, Value: `"two"`}), }, }, }, }, DefaultCase: &ast.Block{ Statements: []ast.Statement{ ast.Echo(&ast.Literal{Type: ast.String, Value: `"def"`}), }, }, } if !assertEquals(a.Nodes[0], tree) { t.Fatalf("Switch did not correctly parse") } }
func TestPHPParserHW(t *testing.T) { testStr := `hello world` p := NewParser(testStr) a, _ := p.Parse() tree := ast.Echo(ast.Literal{Type: ast.String, Value: `hello world`}) if !assertEquals(a[0], tree) { t.Fatalf("Hello world did not correctly parse") } }
func TestScopeResolutionOperator(t *testing.T) { testStr := `<? MyClass::myfunc($var); echo MyClass::myconst; echo $var::myfunc();` p := NewParser() p.disableScoping = true a, _ := p.Parse("test.php", testStr) tree := []ast.Node{ ast.ExprStmt{ &ast.ClassExpr{ Receiver: &ast.Identifier{Value: "MyClass"}, Expr: &ast.FunctionCallExpr{ FunctionName: &ast.Identifier{Value: "myfunc"}, Arguments: []ast.Expr{ ast.NewVariable("var"), }, }, }, }, ast.Echo(&ast.ClassExpr{ Receiver: &ast.Identifier{Value: "MyClass"}, Expr: ast.ConstantExpr{ ast.NewVariable("myconst"), }, }), ast.Echo(&ast.ClassExpr{ Receiver: ast.NewVariable("var"), Expr: &ast.FunctionCallExpr{ FunctionName: &ast.Identifier{Value: "myfunc"}, Arguments: []ast.Expr{}, }, }), } if !assertEquals(a.Nodes[0], tree[0]) { t.Fatalf("Scope resolution operator function call did not correctly parse") } if !assertEquals(a.Nodes[1], tree[1]) { t.Fatalf("Scope resolution operator expression did not correctly parse") } if !assertEquals(a.Nodes[2], tree[2]) { t.Fatalf("Scope resolution operator function call on identifier did not correctly parse") } }
func TestPHPParserHW(t *testing.T) { testStr := `hello world` p := NewParser() p.disableScoping = true a, _ := p.Parse("test.php", testStr) tree := ast.Echo(ast.Literal{Type: ast.String, Value: `hello world`}) if !assertEquals(a.Nodes[0], tree) { t.Fatalf("Hello world did not correctly parse") } }
func (p *Parser) parseNode() ast.Node { switch p.current.Typ { case token.HTML: return ast.Echo(ast.Literal{Type: ast.String, Value: p.current.Val}) case token.PHPBegin: return nil case token.PHPEnd: return nil } return p.parseTopStmt() }
func TestPHPParserHWPHP(t *testing.T) { testStr := `<?php echo "hello world", "!";` p := NewParser(testStr) a, _ := p.Parse() tree := ast.Echo( &ast.Literal{Type: ast.String, Value: `"hello world"`}, &ast.Literal{Type: ast.String, Value: `"!"`}, ) if !assertEquals(a[0], tree) { t.Fatalf("Hello world did not correctly parse") } }
func TestFunction(t *testing.T) { testStr := `<?php function TestFn($arg) { echo $arg; } $var = TestFn("world", 0);` p := NewParser() p.disableScoping = true a, _ := p.Parse("test.php", testStr) tree := []ast.Node{ &ast.FunctionStmt{ FunctionDefinition: &ast.FunctionDefinition{ Name: "TestFn", Arguments: []*ast.FunctionArgument{ { Variable: ast.NewVariable("arg"), }, }, }, Body: &ast.Block{ Statements: []ast.Statement{ast.Echo(ast.NewVariable("arg"))}, }, }, ast.ExprStmt{ ast.AssignmentExpr{ Assignee: ast.NewVariable("var"), Value: &ast.FunctionCallExpr{ FunctionName: &ast.Identifier{Value: "TestFn"}, Arguments: []ast.Expr{ &ast.Literal{Type: ast.String, Value: `"world"`}, &ast.Literal{Type: ast.Float, Value: "0"}, }, }, Operator: "=", }, }, } if len(a.Nodes) != 2 { t.Fatalf("Function did not correctly parse") } if !assertEquals(a.Nodes[0], tree[0]) { t.Fatalf("Function did not correctly parse") } if !assertEquals(a.Nodes[1], tree[1]) { t.Fatalf("Function assignment did not correctly parse") } }
func TestComments(t *testing.T) { testStr := `<? // comment line /* block */ #line ?>html` tree := []ast.Node{ ast.Echo(ast.Literal{Type: ast.String, Value: "html"}), } p := NewParser(testStr) a, _ := p.Parse() if !reflect.DeepEqual(a, tree) { fmt.Printf("Found: %+v\n", a) fmt.Printf("Expected: %+v\n", tree) t.Fatalf("Literals did not correctly parse") } }
func TestForLoop(t *testing.T) { testStr := `<? for ($i = 0; $i < 10; $i++) { echo $i; }` p := NewParser() p.disableScoping = true p.Debug = true p.MaxErrors = 0 a, _ := p.Parse("test.php", testStr) if len(a.Nodes) == 0 { t.Fatalf("For loop did not correctly parse") } tree := &ast.ForStmt{ Initialization: []ast.Expr{ast.AssignmentExpr{ Assignee: ast.NewVariable("i"), Value: &ast.Literal{Type: ast.Float, Value: "0"}, Operator: "=", }}, Termination: []ast.Expr{ast.BinaryExpr{ Antecedent: ast.NewVariable("i"), Subsequent: &ast.Literal{Type: ast.Float, Value: "10"}, Operator: "<", Type: ast.Boolean, }}, Iteration: []ast.Expr{ast.UnaryCallExpr{ Operator: "++", Operand: ast.NewVariable("i"), Preceding: false, }}, LoopBlock: &ast.Block{ Statements: []ast.Statement{ ast.Echo(ast.NewVariable("i")), }, }, } if !assertEquals(a.Nodes[0], tree) { t.Fatalf("For did not correctly parse") } }
func TestForLoop(t *testing.T) { testStr := `<? for ($i = 0; $i < 10; $i++) { echo $i; }` p := NewParser(testStr) p.Debug = true p.MaxErrors = 0 a, _ := p.Parse() if len(a) == 0 { t.Fatalf("For loop did not correctly parse") } tree := &ast.ForStmt{ Initialization: []ast.Expression{ast.AssignmentExpression{ Assignee: ast.NewVariable("i"), Value: &ast.Literal{Type: ast.Float, Value: "0"}, Operator: "=", }}, Termination: []ast.Expression{ast.OperatorExpression{ Operand1: ast.NewVariable("i"), Operand2: &ast.Literal{Type: ast.Float, Value: "10"}, Operator: "<", Type: ast.Boolean, }}, Iteration: []ast.Expression{ast.OperatorExpression{ Operator: "++", Operand1: ast.NewVariable("i"), Type: ast.Numeric, }}, LoopBlock: &ast.Block{ Statements: []ast.Statement{ ast.Echo(ast.NewVariable("i")), }, }, } if !assertEquals(a[0], tree) { t.Fatalf("For did not correctly parse") } }
func TestWhileLoop(t *testing.T) { testStr := `<? while ($otherVar) { echo $var; }` p := NewParser(testStr) a, _ := p.Parse() if len(a) == 0 { t.Fatalf("While loop did not correctly parse") } tree := &ast.WhileStmt{ Termination: ast.NewVariable("otherVar"), LoopBlock: &ast.Block{ Statements: []ast.Statement{ ast.Echo(ast.NewVariable("var")), }, }, } if !assertEquals(a[0], tree) { t.Fatalf("TestLoop did not correctly parse") } }
func TestDoLoop(t *testing.T) { testStr := `<? do { echo $var; } while ($otherVar);` p := NewParser() p.disableScoping = true a, _ := p.Parse("test.php", testStr) if len(a.Nodes) == 0 { t.Fatalf("Do loop did not correctly parse") } tree := &ast.DoWhileStmt{ Termination: ast.NewVariable("otherVar"), LoopBlock: &ast.Block{ Statements: []ast.Statement{ ast.Echo(ast.NewVariable("var")), }, }, } if !assertEquals(a.Nodes[0], tree) { t.Fatalf("TestLoop did not correctly parse") } }
func TestClass(t *testing.T) { testStr := `<?php abstract class TestClass { public $myProp; protected $myProp2; const my_const = "test"; private $arr = array("one", "two"); abstract public function method0($arg); public function method1($arg) { echo $arg; } private function method2(TestClass $arg, $arg2 = false) { echo $arg; return $arg; } }` p := NewParser(testStr) p.Debug = true a, errs := p.Parse() if len(errs) > 0 { t.Fatal(errs) } if len(a) != 1 { t.Fatalf("Class did not correctly parse") } tree := ast.Class{ Name: "TestClass", Constants: []ast.Constant{ { Variable: ast.NewVariable("my_const"), Value: &ast.Literal{Type: ast.String, Value: `"test"`}, }, }, Properties: []ast.Property{ { Visibility: ast.Public, Name: "$myProp", }, { Visibility: ast.Protected, Name: "$myProp2", }, { Visibility: ast.Private, Name: "$arr", Initialization: &ast.ArrayExpression{ Pairs: []ast.ArrayPair{ {Value: &ast.Literal{Type: ast.String, Value: `"one"`}}, {Value: &ast.Literal{Type: ast.String, Value: `"two"`}}, }, }, }, }, Methods: []ast.Method{ { Visibility: ast.Public, FunctionStmt: &ast.FunctionStmt{ FunctionDefinition: &ast.FunctionDefinition{ Name: "method0", Arguments: []ast.FunctionArgument{ { Variable: ast.NewVariable("arg"), }, }, }, }, }, { Visibility: ast.Public, FunctionStmt: &ast.FunctionStmt{ FunctionDefinition: &ast.FunctionDefinition{ Name: "method1", Arguments: []ast.FunctionArgument{ { Variable: ast.NewVariable("arg"), }, }, }, Body: &ast.Block{ Statements: []ast.Statement{ ast.Echo(ast.NewVariable("arg")), }, }, }, }, { Visibility: ast.Private, FunctionStmt: &ast.FunctionStmt{ FunctionDefinition: &ast.FunctionDefinition{ Name: "method2", Arguments: []ast.FunctionArgument{ { TypeHint: "TestClass", Variable: ast.NewVariable("arg"), }, { Variable: ast.NewVariable("arg2"), Default: &ast.Literal{Type: ast.Boolean, Value: "false"}, }, }, }, Body: &ast.Block{ Statements: []ast.Statement{ ast.Echo(ast.NewVariable("arg")), ast.ReturnStmt{Expression: ast.NewVariable("arg")}, }, }, }, }, }, } if !assertEquals(a[0], tree) { t.Fatalf("Class did not parse correctly") } }
func (p *Parser) parseStmt() ast.Statement { switch p.current.Typ { case token.BlockBegin: p.backup() return p.parseBlock() case token.Global: p.next() g := &ast.GlobalDeclaration{ Identifiers: make([]*ast.Variable, 0, 1), } for p.current.Typ == token.VariableOperator { variable, ok := p.parseVariable().(*ast.Variable) if !ok { p.errorf("global declarations must be of standard variables") break } g.Identifiers = append(g.Identifiers, variable) if p.peek().Typ != token.Comma { break } p.expect(token.Comma) p.next() } p.expectStmtEnd() return g case token.Static: if p.peek().Typ == token.ScopeResolutionOperator { p.errorf("static keyword outside of class context") expr := p.parseExpression() p.expectStmtEnd() return expr } s := &ast.StaticVariableDeclaration{Declarations: make([]ast.Dynamic, 0)} for { p.next() v, ok := p.parseVariable().(*ast.Variable) if !ok { p.errorf("global static declaration must be a variable") return nil } if _, ok := v.Name.(*ast.Identifier); !ok { p.errorf("static variable declarations must not be dynamic") } // check if there's an initial assignment if p.peek().Typ == token.AssignmentOperator { p.expect(token.AssignmentOperator) op := p.current.Val p.expect(token.Null, token.StringLiteral, token.BooleanLiteral, token.NumberLiteral, token.Array) switch p.current.Typ { case token.Array: s.Declarations = append(s.Declarations, &ast.AssignmentExpr{Assignee: v, Value: p.parseArrayDeclaration(), Operator: op}) default: s.Declarations = append(s.Declarations, &ast.AssignmentExpr{Assignee: v, Value: p.parseLiteral(), Operator: op}) } } else { s.Declarations = append(s.Declarations, v) } if p.peek().Typ != token.Comma { break } p.next() } p.expectStmtEnd() return s case token.VariableOperator, token.UnaryOperator: expr := ast.ExprStmt{p.parseExpression()} p.expectStmtEnd() return expr case token.Print: requireParen := false if p.peek().Typ == token.OpenParen { p.expect(token.OpenParen) requireParen = true } stmt := ast.Echo(p.parseNextExpression()) if requireParen { p.expect(token.CloseParen) } p.expectStmtEnd() return stmt case token.Function: return p.parseFunctionStmt(false) case token.PHPEnd: if p.peek().Typ == token.EOF { return nil } var expr ast.Statement if p.accept(token.HTML) { expr = ast.Echo(&ast.Literal{Type: ast.String, Value: p.current.Val}) } p.next() if p.current.Typ != token.EOF { p.expectCurrent(token.PHPBegin) } return expr case token.Echo: exprs := []ast.Expr{ p.parseNextExpression(), } for p.peek().Typ == token.Comma { p.expect(token.Comma) exprs = append(exprs, p.parseNextExpression()) } p.expectStmtEnd() echo := ast.Echo(exprs...) return echo case token.If: return p.parseIf() case token.While: return p.parseWhile() case token.Do: return p.parseDo() case token.For: return p.parseFor() case token.Foreach: return p.parseForeach() case token.Switch: return p.parseSwitch() case token.Abstract, token.Final, token.Class: return p.parseClass() case token.Interface: return p.parseInterface() case token.Return: p.next() stmt := &ast.ReturnStmt{} if p.current.Typ != token.StatementEnd { stmt.Expr = p.parseExpression() p.expectStmtEnd() } return stmt case token.Break: p.next() stmt := &ast.BreakStmt{} if p.current.Typ != token.StatementEnd { stmt.Expr = p.parseExpression() p.expectStmtEnd() } return stmt case token.Continue: p.next() stmt := &ast.ContinueStmt{} if p.current.Typ != token.StatementEnd { stmt.Expr = p.parseExpression() p.expectStmtEnd() } return stmt case token.Throw: stmt := ast.ThrowStmt{Expr: p.parseNextExpression()} p.expectStmtEnd() return stmt case token.Exit: stmt := &ast.ExitStmt{} if p.peek().Typ == token.OpenParen { p.expect(token.OpenParen) if p.peek().Typ != token.CloseParen { stmt.Expr = p.parseNextExpression() } p.expect(token.CloseParen) } p.expectStmtEnd() return stmt case token.Try: stmt := &ast.TryStmt{} stmt.TryBlock = p.parseBlock() for p.expect(token.Catch); p.current.Typ == token.Catch; p.next() { caught := &ast.CatchStmt{} p.expect(token.OpenParen) p.expect(token.Identifier) caught.CatchType = p.current.Val p.expect(token.VariableOperator) p.expect(token.Identifier) caught.CatchVar = ast.NewVariable(p.current.Val) p.expect(token.CloseParen) caught.CatchBlock = p.parseBlock() stmt.CatchStmts = append(stmt.CatchStmts, caught) } p.backup() return stmt case token.IgnoreErrorOperator: // Ignore this operator p.next() return p.parseStmt() case token.StatementEnd: // this is an empty statement return &ast.EmptyStatement{} default: expr := p.parseExpression() if expr != nil { p.expectStmtEnd() return ast.ExprStmt{expr} } p.errorf("Found %s, statement or expression", p.current) return nil } }
func TestExpressionParsing(t *testing.T) { p := NewParser() p.disableScoping = true testStr := `<? if (1 + 2 > 3) echo "good"; ` a, _ := p.Parse("test.php", testStr) ifStmt := ast.IfStmt{ Branches: []ast.IfBranch{ { Condition: ast.BinaryExpr{ Antecedent: ast.BinaryExpr{ Antecedent: &ast.Literal{Type: ast.Float, Value: "1"}, Subsequent: &ast.Literal{Type: ast.Float, Value: "2"}, Type: ast.Numeric, Operator: "+", }, Subsequent: &ast.Literal{Type: ast.Float, Value: "3"}, Type: ast.Boolean, Operator: ">", }, Block: ast.Echo(&ast.Literal{Type: ast.String, Value: `"good"`}), }, }, } if len(a.Nodes) != 1 { t.Fatalf("If did not correctly parse") } parsedIf, ok := a.Nodes[0].(*ast.IfStmt) if !ok { t.Fatalf("If did not correctly parse") } if !assertEquals(*parsedIf, ifStmt) { t.Fatalf("If did not correctly parse") } p = NewParser() p.disableScoping = true testStr = `<? if (4 + 5 * 6) echo "bad"; ` a, _ = p.Parse("test.php", testStr) ifStmt = ast.IfStmt{ Branches: []ast.IfBranch{ { Condition: ast.BinaryExpr{ Subsequent: ast.BinaryExpr{ Antecedent: &ast.Literal{Type: ast.Float, Value: "5"}, Subsequent: &ast.Literal{Type: ast.Float, Value: "6"}, Type: ast.Numeric, Operator: "*", }, Antecedent: &ast.Literal{Type: ast.Float, Value: "4"}, Type: ast.Numeric, Operator: "+", }, Block: ast.Echo(&ast.Literal{Type: ast.String, Value: `"bad"`}), }, }, } if len(a.Nodes) != 1 { t.Fatalf("If did not correctly parse") } parsedIf, ok = a.Nodes[0].(*ast.IfStmt) if !ok { t.Fatalf("If did not correctly parse") } if !reflect.DeepEqual(*parsedIf, ifStmt) { t.Fatalf("If did not correctly parse") } p = NewParser() p.disableScoping = true testStr = `<? if (1 > 2 * 3 + 4) echo "good"; ` a, _ = p.Parse("test.php", testStr) ifStmt = ast.IfStmt{ Branches: []ast.IfBranch{ { Condition: ast.BinaryExpr{ Antecedent: &ast.Literal{Type: ast.Float, Value: `1`}, Subsequent: ast.BinaryExpr{ Antecedent: ast.BinaryExpr{ Antecedent: &ast.Literal{Type: ast.Float, Value: `2`}, Subsequent: &ast.Literal{Type: ast.Float, Value: `3`}, Type: ast.Numeric, Operator: "*", }, Subsequent: &ast.Literal{Type: ast.Float, Value: `4`}, Operator: "+", Type: ast.Numeric, }, Type: ast.Boolean, Operator: ">", }, Block: ast.Echo(&ast.Literal{Type: ast.String, Value: `"good"`}), }, }, } if len(a.Nodes) != 1 { t.Fatalf("If did not correctly parse") } parsedIf, ok = a.Nodes[0].(*ast.IfStmt) if !ok { t.Fatalf("If did not correctly parse") } if !reflect.DeepEqual(*parsedIf, ifStmt) { t.Fatalf("If did not correctly parse") } p = NewParser() p.disableScoping = true testStr = `<? if ($var = &$var2 > 2 * (3 + 4) - 2 & 3 && 4 ^ 8 or 14 xor 10 and 13 >> 18 << 10 ? true : false) echo "good"; ` p.Debug = true a, _ = p.Parse("test.php", testStr) if len(a.Nodes) != 1 { t.Fatalf("Expression did not correctly parse") } }
func TestExpressionParsing(t *testing.T) { p := NewParser(`<? if (1 + 2 > 3) echo "good"; `) a, _ := p.Parse() ifStmt := ast.IfStmt{ Condition: ast.OperatorExpression{ Operand1: ast.OperatorExpression{ Operand1: &ast.Literal{Type: ast.Float, Value: "1"}, Operand2: &ast.Literal{Type: ast.Float, Value: "2"}, Type: ast.Numeric, Operator: "+", }, Operand2: &ast.Literal{Type: ast.Float, Value: "3"}, Type: ast.Boolean, Operator: ">", }, TrueBranch: ast.Echo(&ast.Literal{Type: ast.String, Value: `"good"`}), FalseBranch: ast.Block{}, } if len(a) != 1 { t.Fatalf("If did not correctly parse") } parsedIf, ok := a[0].(*ast.IfStmt) if !ok { t.Fatalf("If did not correctly parse") } if !assertEquals(*parsedIf, ifStmt) { t.Fatalf("If did not correctly parse") } p = NewParser(`<? if (4 + 5 * 6) echo "bad"; `) a, _ = p.Parse() ifStmt = ast.IfStmt{ Condition: ast.OperatorExpression{ Operand2: ast.OperatorExpression{ Operand1: &ast.Literal{Type: ast.Float, Value: "5"}, Operand2: &ast.Literal{Type: ast.Float, Value: "6"}, Type: ast.Numeric, Operator: "*", }, Operand1: &ast.Literal{Type: ast.Float, Value: "4"}, Type: ast.Numeric, Operator: "+", }, TrueBranch: ast.Echo(&ast.Literal{Type: ast.String, Value: `"bad"`}), FalseBranch: ast.Block{}, } if len(a) != 1 { t.Fatalf("If did not correctly parse") } parsedIf, ok = a[0].(*ast.IfStmt) if !ok { t.Fatalf("If did not correctly parse") } if !reflect.DeepEqual(*parsedIf, ifStmt) { t.Fatalf("If did not correctly parse") } p = NewParser(`<? if (1 > 2 * 3 + 4) echo "good"; `) a, _ = p.Parse() ifStmt = ast.IfStmt{ Condition: ast.OperatorExpression{ Operand1: &ast.Literal{Type: ast.Float, Value: `1`}, Operand2: ast.OperatorExpression{ Operand1: ast.OperatorExpression{ Operand1: &ast.Literal{Type: ast.Float, Value: `2`}, Operand2: &ast.Literal{Type: ast.Float, Value: `3`}, Type: ast.Numeric, Operator: "*", }, Operand2: &ast.Literal{Type: ast.Float, Value: `4`}, Operator: "+", Type: ast.Numeric, }, Type: ast.Boolean, Operator: ">", }, TrueBranch: ast.Echo(&ast.Literal{Type: ast.String, Value: `"good"`}), FalseBranch: ast.Block{}, } if len(a) != 1 { t.Fatalf("If did not correctly parse") } parsedIf, ok = a[0].(*ast.IfStmt) if !ok { t.Fatalf("If did not correctly parse") } if !reflect.DeepEqual(*parsedIf, ifStmt) { t.Fatalf("If did not correctly parse") } p = NewParser(`<? if ($var = &$var2 > 2 * (3 + 4) - 2 & 3 && 4 ^ 8 or 14 xor 10 and 13 >> 18 << 10 ? true : false) echo "good"; `) p.Debug = true a, _ = p.Parse() if len(a) != 1 { t.Fatalf("Expression did not correctly parse") } }