136 lines
3.4 KiB
Go
136 lines
3.4 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/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)
|
||
|
}
|