diff --git a/fixtures/0015-hoist.php b/fixtures/0015-hoist.php new file mode 100644 index 0000000..b886a1c --- /dev/null +++ b/fixtures/0015-hoist.php @@ -0,0 +1,17 @@ += 1) { + echo "OK in func"; + } + +} + +$y = 0; + +if (++$y >= 1) { + echo "OK in root"; +} diff --git a/hoistpass.go b/hoistpass.go new file mode 100644 index 0000000..fff0465 --- /dev/null +++ b/hoistpass.go @@ -0,0 +1,135 @@ +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) +} diff --git a/main.go b/main.go index 809bedc..9874e62 100644 --- a/main.go +++ b/main.go @@ -59,6 +59,12 @@ func ConvertFile(filename string) (string, error) { return "", err } + // Pass 1.5: Hoist some expressions out of rvalue contexts + err = runHoistPass(&n) + if err != nil { + return "", err + } + // Debug pass: Walk and print JSON... if fh, err := os.OpenFile(filename+`.parse2.json`, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err == nil { v := visitor.NewPrettyJsonDumper(fh, namespaces)