155 lines
4.1 KiB
Go
155 lines
4.1 KiB
Go
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/scalar"
|
|
"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}
|
|
}
|
|
|
|
// FIXME detect interior function calls since we may be dangerously doubling up on that
|
|
// For a more complex expression it is probably OK to use a temporary variable
|
|
|
|
// We also need to replace our own node with just the value itself
|
|
*n_ = n.Variable
|
|
|
|
case *expr.PostInc:
|
|
// Hoist the whole increment expression up to statement context
|
|
err := this.nextHoist(stmt.NewExpression(n))
|
|
if err != nil {
|
|
return parseErr{n, err}
|
|
}
|
|
|
|
// We also need to replace our own node. The resulting value would normally
|
|
// be one less than the current value
|
|
// PHP only supports lvalues for pre/postinc expressions so it should
|
|
// be fine in all Go contexts too to reuse the variable expression
|
|
// FIXME except for $foo[rand()]++
|
|
|
|
*n_ = binary.NewPlus(n.Variable, scalar.NewLnumber(`1`))
|
|
|
|
}
|
|
|
|
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)
|
|
}
|