add hoisting pass for preincrement rvalue expressions
This commit is contained in:
parent
abc031683e
commit
3f8377d9fe
17
fixtures/0015-hoist.php
Normal file
17
fixtures/0015-hoist.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
function foo() {
|
||||
|
||||
$x = 0;
|
||||
|
||||
if (++$x >= 1) {
|
||||
echo "OK in func";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$y = 0;
|
||||
|
||||
if (++$y >= 1) {
|
||||
echo "OK in root";
|
||||
}
|
135
hoistpass.go
Normal file
135
hoistpass.go
Normal file
@ -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)
|
||||
}
|
6
main.go
6
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)
|
||||
|
Loading…
Reference in New Issue
Block a user