package main import ( "fmt" "github.com/z7zmey/php-parser/node" "github.com/z7zmey/php-parser/node/expr" "github.com/z7zmey/php-parser/node/expr/binary" "github.com/z7zmey/php-parser/node/stmt" "php2go/parseutil" ) type hoistPass struct { mw *parseutil.MutatingWalker nextHoist func(stmts ...node.Node) error } func (this *hoistPass) Enter(n_ *node.Node) error { n := *n_ // not a copy switch n := n.(type) { case *stmt.StmtList: return this.enterStmtList(&n.Stmts) // We can inject extra entries here case *stmt.Function: // We can inject extra entries here // Note that we are NOT walking the other parts of this AST node // (e.g. function parameters, function name, ...) since presumably they // can't contain any hoistable statements return this.enterStmtList(&n.Stmts) case *node.Root: return this.enterStmtList(&n.Stmts) case *binary.BooleanAnd: return this.enterShortCircuitingBinOp(&n.Left, &n.Right) case *binary.BooleanOr: return this.enterShortCircuitingBinOp(&n.Left, &n.Right) case *binary.LogicalAnd: return this.enterShortCircuitingBinOp(&n.Left, &n.Right) case *binary.LogicalOr: return this.enterShortCircuitingBinOp(&n.Left, &n.Right) case *expr.PreInc: // Hoist the whole increment expression up to statement context // We also have to convert it to a post-increment err := this.nextHoist(stmt.NewExpression(expr.NewPostInc(n.Variable))) if err != nil { return parseErr{n, err} } // We also need to replace our own node with just the value itself *n_ = n.Variable } return nil // TODO } func (this *hoistPass) enterShortCircuitingBinOp(left, right *node.Node) error { // These wreck our ability to hoist, since we can't neccessarily just move // the right-hand side operand out // Although it's still safe to walk the left-hand side err := this.mw.Walk(left) if err != nil { return err } lastHoist := this.nextHoist this.nextHoist = func(stmts ...node.Node) error { return parseErr{*right, fmt.Errorf("Can't determine how to hoist expression on right-hand side of && expression")} } err = this.mw.Walk(right) if err != nil { return err } this.nextHoist = lastHoist return parseutil.ErrDoNotWalkDeeper // since we already did it } func (this *hoistPass) enterStmtList(stmtList *[]node.Node) error { // We can inject extra entries here // In order to do so, we need to perform the interior Walk ourselves // so that we know exactly where to put the extra entries // Take stmtList by pointer so we can replace the slice outright anyMutations := false lastHoist := this.nextHoist ret := make([]node.Node, 0, len(*stmtList)) this.nextHoist = func(stmts ...node.Node) error { anyMutations = true ret = append(ret, stmts...) return nil } for i, _ := range *stmtList { err := this.mw.Walk(&(*stmtList)[i]) if err != nil { return err } ret = append(ret, (*stmtList)[i]) // *after* the hoisted statements } this.nextHoist = lastHoist if anyMutations { *stmtList = ret // our modified copy } return parseutil.ErrDoNotWalkDeeper // since we already did it } func runHoistPass(root *node.Node) error { hp := hoistPass{} hp.mw = &parseutil.MutatingWalker{ EnterNode: hp.Enter, LeaveNode: parseutil.MutatingWalkerNoop, } hp.nextHoist = func(stmts ...node.Node) error { return fmt.Errorf("nowhere available to hoist %d statements", len(stmts)) } return hp.mw.Walk(root) }