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 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 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 TestSwitch(t *testing.T) { testStr := `<? switch ($var) { case 1: echo "one"; case 2: { echo "two"; } default: echo "def"; }` p := NewParser(testStr) a, _ := p.Parse() if len(a) == 0 { t.Fatalf("Array lookup did not correctly parse") } tree := ast.SwitchStmt{ Expression: ast.NewVariable("var"), Cases: []*ast.SwitchCase{ { Expression: &ast.Literal{Type: ast.Float, Value: "1"}, Block: ast.Block{ Statements: []ast.Statement{ ast.Echo(&ast.Literal{Type: ast.String, Value: `"one"`}), }, }, }, { Expression: &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[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(testStr) a, _ := p.Parse() tree := []ast.Node{ ast.ExpressionStmt{ &ast.ClassExpression{ Receiver: ast.Identifier{Value: "MyClass"}, Expression: &ast.FunctionCallExpression{ FunctionName: ast.Identifier{Value: "myfunc"}, Arguments: []ast.Expression{ ast.NewVariable("var"), }, }, }, }, ast.Echo(&ast.ClassExpression{ Receiver: ast.Identifier{Value: "MyClass"}, Expression: ast.ConstantExpression{ ast.NewVariable("myconst"), }, }), ast.Echo(&ast.ClassExpression{ Receiver: ast.NewVariable("var"), Expression: &ast.FunctionCallExpression{ FunctionName: ast.Identifier{Value: "myfunc"}, Arguments: []ast.Expression{}, }, }), } if !assertEquals(a[0], tree[0]) { t.Fatalf("Scope resolution operator function call did not correctly parse") } if !assertEquals(a[1], tree[1]) { t.Fatalf("Scope resolution operator expression did not correctly parse") } if !assertEquals(a[2], tree[2]) { t.Fatalf("Scope resolution operator function call on identifier 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.parseStmt() }
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(testStr) a, _ := p.Parse() 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.ExpressionStmt{ ast.AssignmentExpression{ Assignee: ast.NewVariable("var"), Value: &ast.FunctionCallExpression{ FunctionName: ast.Identifier{Value: "TestFn"}, Arguments: []ast.Expression{ &ast.Literal{Type: ast.String, Value: `"world"`}, &ast.Literal{Type: ast.Float, Value: "0"}, }, }, Operator: "=", }, }, } if len(a) != 2 { t.Fatalf("Function did not correctly parse") } if !assertEquals(a[0], tree[0]) { t.Fatalf("Function did not correctly parse") } if !assertEquals(a[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(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 (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.Namespace: p.expect(token.Identifier) p.expectStmtEnd() // We are ignoring this for now return nil case token.Use: p.expect(token.Identifier) if p.peek().typ == token.AsOperator { p.expect(token.AsOperator) p.expect(token.Identifier) } p.expectStmtEnd() // We are ignoring this for now return nil case token.Static: if p.peek().typ == token.ScopeResolutionOperator { expr := p.parseExpression() p.expectStmtEnd() return expr } s := &ast.StaticVariableDeclaration{Declarations: make([]ast.Expression, 0)} for { p.expect(token.VariableOperator) p.expect(token.Identifier) v := ast.NewVariable(p.current.val) 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.AssignmentExpression{Assignee: v, Value: p.parseArrayDeclaration(), Operator: op}) default: s.Declarations = append(s.Declarations, &ast.AssignmentExpression{Assignee: v, Value: p.parseLiteral(), Operator: op}) } } 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.ExpressionStmt{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() 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.Expression{ p.parseNextExpression(), } for p.peek().typ == token.Comma { p.expect(token.Comma) exprs = append(exprs, p.parseNextExpression()) } p.expectStmtEnd() return ast.Echo(exprs...) 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.Expression = p.parseExpression() p.expectStmtEnd() } return stmt case token.Break: p.next() stmt := ast.BreakStmt{} if p.current.typ != token.StatementEnd { stmt.Expression = p.parseExpression() p.expectStmtEnd() } return stmt case token.Continue: p.next() stmt := ast.ContinueStmt{} if p.current.typ != token.StatementEnd { stmt.Expression = p.parseExpression() p.expectStmtEnd() } return stmt case token.Throw: stmt := ast.ThrowStmt{Expression: 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.Expression = 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.ExpressionStmt{expr} } p.errorf("Found %s, statement or expression", p.current) return nil } }
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 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") } }