add hoisting pass for preincrement rvalue expressions

This commit is contained in:
mappu 2020-04-15 19:55:31 +12:00
parent abc031683e
commit 3f8377d9fe
3 changed files with 158 additions and 0 deletions

17
fixtures/0015-hoist.php Normal file
View 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
View 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)
}

View File

@ -59,6 +59,12 @@ func ConvertFile(filename string) (string, error) {
return "", err 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... // 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 { 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) v := visitor.NewPrettyJsonDumper(fh, namespaces)