func TestList(t *testing.T) { testStr := `<? list($one, $two) = array(1, 2);` p := NewParser(testStr) a, errs := p.Parse() if len(errs) != 0 { t.Fatalf("Did not parse list correctly: %s", errs) } tree := ast.ExpressionStmt{&ast.ListStatement{ Operator: "=", Assignees: []ast.Assignable{ ast.NewVariable("one"), ast.NewVariable("two"), }, Value: &ast.ArrayExpression{ Pairs: []ast.ArrayPair{ {Key: nil, Value: &ast.Literal{Value: "1", Type: ast.Float}}, {Key: nil, Value: &ast.Literal{Value: "2", Type: ast.Float}}, }, }, }} if !assertEquals(a[0], tree) { t.Fatalf("Array bracked did not parse correctly") } }
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 (p *Parser) parseForeach() ast.Statement { stmt := &ast.ForeachStmt{} p.expect(token.OpenParen) stmt.Source = p.parseNextExpression() p.expect(token.AsOperator) if p.peek().typ == token.AmpersandOperator { p.expect(token.AmpersandOperator) } p.expect(token.VariableOperator) p.next() first := ast.NewVariable(p.current.val) if p.peek().typ == token.ArrayKeyOperator { stmt.Key = first p.expect(token.ArrayKeyOperator) if p.peek().typ == token.AmpersandOperator { p.expect(token.AmpersandOperator) } p.expect(token.VariableOperator) p.next() stmt.Value = ast.NewVariable(p.current.val) } else { stmt.Value = first } p.expect(token.CloseParen) p.next() stmt.LoopBlock = p.parseControlBlock(token.EndForeach) return stmt }
func TestProperty(t *testing.T) { testStr := `<? $res = $var->go; $var->go = $res;` p := NewParser(testStr) p.Debug = true p.MaxErrors = 0 a, _ := p.Parse() if len(a) != 2 { t.Fatalf("Property did not correctly parse") } tree := ast.ExpressionStmt{ast.AssignmentExpression{ Assignee: ast.NewVariable("res"), Operator: "=", Value: &ast.PropertyExpression{ Receiver: ast.NewVariable("var"), Name: ast.Identifier{Value: "go"}, }, }} if !assertEquals(a[0], tree) { t.Fatalf("Property did not correctly parse") } tree = ast.ExpressionStmt{ast.AssignmentExpression{ Assignee: &ast.PropertyExpression{ Receiver: ast.NewVariable("var"), Name: ast.Identifier{Value: "go"}, }, Operator: "=", Value: ast.NewVariable("res"), }} if !assertEquals(a[1], tree) { t.Fatalf("Property 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 TestInstantiation(t *testing.T) { testStr := `<? $obj = new Obj::$classes['obj']($arg);` p := NewParser(testStr) a, errs := p.Parse() if len(errs) != 0 { t.Fatalf("Did not parse instantiation correctly: %s", errs) } tree := ast.ExpressionStmt{ast.AssignmentExpression{ Operator: "=", Assignee: ast.NewVariable("obj"), Value: &ast.NewExpression{ Class: ast.NewClassExpression("Obj", &ast.ArrayLookupExpression{ Array: ast.NewVariable("classes"), Index: &ast.Literal{Type: ast.String, Value: `'obj'`}, }), Arguments: []ast.Expression{ ast.NewVariable("arg"), }, }, }} if !assertEquals(a[0], tree) { t.Fatalf("Instantiation did not parse correctly") } }
func TestMethodCall(t *testing.T) { testStr := `<? $res = $var->go();` p := NewParser(testStr) p.Debug = true p.MaxErrors = 0 a, _ := p.Parse() if len(a) == 0 { t.Fatalf("Method call did not correctly parse") } tree := ast.ExpressionStmt{ast.AssignmentExpression{ Assignee: ast.NewVariable("res"), Operator: "=", Value: &ast.MethodCallExpression{ Receiver: ast.NewVariable("var"), FunctionCallExpression: &ast.FunctionCallExpression{ FunctionName: ast.Identifier{Value: "go"}, Arguments: make([]ast.Expression, 0), }, }, }} if !assertEquals(a[0], tree) { t.Fatalf("Method call did not correctly parse") } }
func TestGlobal(t *testing.T) { testStr := `<? global $var, $otherVar;` p := NewParser(testStr) a, _ := p.Parse() tree := &ast.GlobalDeclaration{ Identifiers: []*ast.Variable{ ast.NewVariable("var"), ast.NewVariable("otherVar"), }, } if !assertEquals(a[0], tree) { t.Fatalf("Global did not parse correctly") } }
func TestArrayKeys(t *testing.T) { testStr := `<? $var = array(1 => "one", 2 => "two", 3 => "three");` p := NewParser(testStr) a, _ := p.Parse() if len(a) == 0 { t.Fatalf("Array did not correctly parse") } tree := ast.ExpressionStmt{ast.AssignmentExpression{ Assignee: ast.NewVariable("var"), Operator: "=", Value: &ast.ArrayExpression{ ast.BaseNode{}, ast.ArrayType{}, []ast.ArrayPair{ {Key: &ast.Literal{Type: ast.Float, Value: "1"}, Value: &ast.Literal{Type: ast.String, Value: `"one"`}}, {Key: &ast.Literal{Type: ast.Float, Value: "2"}, Value: &ast.Literal{Type: ast.String, Value: `"two"`}}, {Key: &ast.Literal{Type: ast.Float, Value: "3"}, Value: &ast.Literal{Type: ast.String, Value: `"three"`}}, }, }, }} if !assertEquals(a[0], tree) { t.Fatalf("Array did not correctly parse") } }
func TestArray(t *testing.T) { testStr := `<? $var = array("one", "two", "three");` p := NewParser(testStr) p.Debug = true a, _ := p.Parse() if len(a) == 0 { t.Fatalf("Array did not correctly parse") } tree := ast.ExpressionStmt{ ast.AssignmentExpression{ Assignee: ast.NewVariable("var"), Operator: "=", Value: &ast.ArrayExpression{ ast.BaseNode{}, ast.ArrayType{}, []ast.ArrayPair{ {Value: &ast.Literal{Type: ast.String, Value: `"one"`}}, {Value: &ast.Literal{Type: ast.String, Value: `"two"`}}, {Value: &ast.Literal{Type: ast.String, Value: `"three"`}}, }, }, }, } if !reflect.DeepEqual(a[0], tree) { fmt.Printf("Found: %+v\n", a[0]) fmt.Printf("Expected: %+v\n", tree) t.Fatalf("Array did not correctly parse") } }
func TestArrayBracket(t *testing.T) { testStr := `<? $arr = ["one", "two"]; $arr2 = ["one" => 1, "two" => 2];` p := NewParser(testStr) a, errs := p.Parse() if len(errs) != 0 { t.Fatalf("Did not parse array bracket correctly: %s", errs) } tree := []ast.Statement{ ast.ExpressionStmt{ast.AssignmentExpression{ Operator: "=", Assignee: ast.NewVariable("arr"), Value: &ast.ArrayExpression{ Pairs: []ast.ArrayPair{ {Key: nil, Value: &ast.Literal{Value: `"one"`, Type: ast.String}}, {Key: nil, Value: &ast.Literal{Value: `"two"`, Type: ast.String}}, }, }, }}, ast.ExpressionStmt{ast.AssignmentExpression{ Operator: "=", Assignee: ast.NewVariable("arr2"), Value: &ast.ArrayExpression{ Pairs: []ast.ArrayPair{ { Key: &ast.Literal{Value: `"one"`, Type: ast.String}, Value: &ast.Literal{Value: "1", Type: ast.Float}, }, { Key: &ast.Literal{Value: `"two"`, Type: ast.String}, Value: &ast.Literal{Value: "2", Type: ast.Float}, }, }, }, }}, } if !assertEquals(a[0], tree[0]) { t.Fatalf("Array bracked did not parse correctly") } if !assertEquals(a[1], tree[1]) { t.Fatalf("Array bracked did not parse correctly") } }
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 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 TestArrayLookup(t *testing.T) { testStr := `<? echo $arr['one'][$two]; $var->arr[] = 2; echo $arr[2 + 1];` p := NewParser(testStr) p.Debug = true p.MaxErrors = 0 a, _ := p.Parse() if len(a) == 0 { t.Fatalf("Array lookup did not correctly parse") } tree := []ast.Node{ ast.EchoStmt{ Expressions: []ast.Expression{&ast.ArrayLookupExpression{ Array: &ast.ArrayLookupExpression{ Array: ast.NewVariable("arr"), Index: &ast.Literal{Type: ast.String, Value: `'one'`}, }, Index: ast.NewVariable("two"), }}, }, ast.ExpressionStmt{ ast.AssignmentExpression{ Assignee: ast.ArrayAppendExpression{ Array: &ast.PropertyExpression{ Receiver: ast.NewVariable("var"), Name: ast.Identifier{Value: "arr"}, }, }, Operator: "=", Value: &ast.Literal{Type: ast.Float, Value: "2"}, }, }, } if !assertEquals(a[0], tree[0]) { t.Fatalf("Array lookup did not correctly parse") } if !assertEquals(a[1], tree[1]) { t.Fatalf("Array append expression did not correctly parse") } }
func TestLiterals(t *testing.T) { testStr := `<? $var = "one"; $var = 2; $var = true; $var = null;` p := NewParser(testStr) a, _ := p.Parse() if len(a) != 4 { t.Fatalf("Literals did not correctly parse") } tree := []ast.Node{ ast.ExpressionStmt{ast.AssignmentExpression{ Assignee: ast.NewVariable("var"), Value: &ast.Literal{Type: ast.String, Value: `"one"`}, Operator: "=", }}, ast.ExpressionStmt{ast.AssignmentExpression{ Assignee: ast.NewVariable("var"), Value: &ast.Literal{Type: ast.Float, Value: "2"}, Operator: "=", }}, ast.ExpressionStmt{ast.AssignmentExpression{ Assignee: ast.NewVariable("var"), Value: &ast.Literal{Type: ast.Boolean, Value: "true"}, Operator: "=", }}, ast.ExpressionStmt{ast.AssignmentExpression{ Assignee: ast.NewVariable("var"), Value: &ast.Literal{Type: ast.Null, Value: "null"}, Operator: "=", }}, } 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) parseInterface() *ast.Interface { i := &ast.Interface{ Inherits: make([]string, 0), } p.expect(token.Identifier) i.Name = p.current.val if p.peek().typ == token.Extends { p.expect(token.Extends) for { p.expect(token.Identifier) i.Inherits = append(i.Inherits, p.current.val) if p.peek().typ != token.Comma { break } p.expect(token.Comma) } } p.expect(token.BlockBegin) for p.peek().typ != token.BlockEnd { vis, _ := p.parseVisibility() if p.peek().typ == token.Static { p.next() } p.next() switch p.current.typ { case token.Function: f := p.parseFunctionDefinition() m := ast.Method{ Visibility: vis, FunctionStmt: &ast.FunctionStmt{FunctionDefinition: f}, } i.Methods = append(i.Methods, m) p.expect(token.StatementEnd) case token.Const: constant := ast.Constant{} p.expect(token.Identifier) constant.Variable = ast.NewVariable(p.current.val) if p.peek().typ == token.AssignmentOperator { p.expect(token.AssignmentOperator) constant.Value = p.parseNextExpression() } i.Constants = append(i.Constants, constant) p.expect(token.StatementEnd) default: p.errorf("unexpected interface member %v", p.current) } } p.expect(token.BlockEnd) return i }
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 (p *Parser) parseFunctionArgument() ast.FunctionArgument { arg := ast.FunctionArgument{} switch p.peek().typ { case token.Identifier, token.Array, token.Self: p.next() arg.TypeHint = p.current.val } if p.peek().typ == token.AmpersandOperator { p.next() } p.expect(token.VariableOperator) p.next() arg.Variable = ast.NewVariable(p.current.val) if p.peek().typ == token.AssignmentOperator { p.expect(token.AssignmentOperator) p.next() arg.Default = p.parseExpression() } return arg }
func TestCastOperator(t *testing.T) { testStr := `<? $var = (double) 1.0; ?>` p := NewParser(testStr) a, _ := p.Parse() tree := []ast.Node{ ast.ExpressionStmt{ast.AssignmentExpression{ Assignee: ast.NewVariable("var"), Value: ast.OperatorExpression{ Operand1: &ast.Literal{Type: ast.Float, Value: "1.0"}, Operator: "(double)", Type: ast.Numeric, }, Operator: "=", }}, } if !assertEquals(a[0], tree[0]) { t.Fatalf("Cast operator parsing failed") } }
func (p *Parser) parseVariable() ast.Expression { p.expectCurrent(token.VariableOperator) switch p.next(); { case isKeyword(p.current.typ, p.current.val): // keywords are all valid variable names fallthrough case p.current.typ == token.Identifier: expr := ast.NewVariable(p.current.val) return expr case p.current.typ == token.BlockBegin: expr := &ast.Variable{Name: p.parseNextExpression()} p.expect(token.BlockEnd) return expr case p.current.typ == token.VariableOperator: return &ast.Variable{Name: p.parseVariable()} default: p.errorf("unexpected variable operand %s", p.current) return nil } }
func (p *Parser) parseIdentifier() (expr ast.Expression) { switch p.peek().typ { case token.OpenParen: // Function calls are okay here because we know they came with // a non-dynamic identifier. expr = p.parseFunctionCall(ast.Identifier{Value: p.current.val}) p.next() case token.ScopeResolutionOperator: classIdent := p.current.val p.next() // get onto ::, then we get to the next expr p.next() expr = ast.NewClassExpression(classIdent, p.parseOperand()) p.next() default: expr = ast.ConstantExpression{ Variable: ast.NewVariable(p.current.val), } p.next() } return expr }
func (p *Parser) parseClassFields(c ast.Class) ast.Class { // Starting on BlockBegin c.Methods = make([]ast.Method, 0) c.Properties = make([]ast.Property, 0) for p.peek().typ != token.BlockEnd { vis, _, _, abstract := p.parseClassMemberSettings() p.next() switch p.current.typ { case token.Function: if abstract { f := p.parseFunctionDefinition() m := ast.Method{ Visibility: vis, FunctionStmt: &ast.FunctionStmt{FunctionDefinition: f}, } c.Methods = append(c.Methods, m) p.expect(token.StatementEnd) } else { c.Methods = append(c.Methods, ast.Method{ Visibility: vis, FunctionStmt: p.parseFunctionStmt(), }) } case token.Var: p.expect(token.VariableOperator) fallthrough case token.VariableOperator: for { p.expect(token.Identifier) prop := ast.Property{ Visibility: vis, Name: "$" + p.current.val, } if p.peek().typ == token.AssignmentOperator { p.expect(token.AssignmentOperator) prop.Initialization = p.parseNextExpression() } c.Properties = append(c.Properties, prop) if p.accept(token.StatementEnd) { break } p.expect(token.Comma) p.expect(token.VariableOperator) } case token.Const: constant := ast.Constant{} p.expect(token.Identifier) constant.Variable = ast.NewVariable(p.current.val) if p.peek().typ == token.AssignmentOperator { p.expect(token.AssignmentOperator) constant.Value = p.parseNextExpression() } c.Constants = append(c.Constants, constant) p.expect(token.StatementEnd) default: p.errorf("unexpected class member %v", p.current) return c } } p.expect(token.BlockEnd) return c }
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") } }