node: initial try/catch support, track the error handler (WIP)
This commit is contained in:
parent
0d7024dbee
commit
52ccea5d65
16
fixtures/0008-try-catch-finally.php
Normal file
16
fixtures/0008-try-catch-finally.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
function Foo(): int {
|
||||||
|
throw new \Exception('error');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
echo Foo();
|
||||||
|
} catch (\BarException | \BazException $e) {
|
||||||
|
echo "bar or baz exception " . $e->getMessage() . "\n";
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo "other exception " . $e->getMessage() . "\n";
|
||||||
|
} finally {
|
||||||
|
echo "finally";
|
||||||
|
}
|
110
node.go
110
node.go
@ -38,6 +38,7 @@ func (pe parseErr) Unwrap() error {
|
|||||||
type conversionState struct {
|
type conversionState struct {
|
||||||
currentClassName string
|
currentClassName string
|
||||||
currentClassParentName string
|
currentClassParentName string
|
||||||
|
currentErrHandler string
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -100,6 +101,15 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
|
|||||||
statements := []string{}
|
statements := []string{}
|
||||||
|
|
||||||
for _, s := range n.Stmts {
|
for _, s := range n.Stmts {
|
||||||
|
|
||||||
|
switch s.(type) {
|
||||||
|
case *stmt.Class, *stmt.Function, *stmt.Interface:
|
||||||
|
this.currentErrHandler = "return nil, err\n"
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.currentErrHandler = "panic(err)\n" // top-level init/main behaviour
|
||||||
|
}
|
||||||
|
|
||||||
sm, err := this.convert(s)
|
sm, err := this.convert(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", parseErr{s, err}
|
return "", parseErr{s, err}
|
||||||
@ -511,7 +521,7 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
|
|||||||
// super() is a reserved name in PHP anyway
|
// super() is a reserved name in PHP anyway
|
||||||
ret := "super, err := " + constructorName(this.currentClassParentName) + funcArgs + "\n"
|
ret := "super, err := " + constructorName(this.currentClassParentName) + funcArgs + "\n"
|
||||||
ret += "if err != nil {\n"
|
ret += "if err != nil {\n"
|
||||||
ret += "return err\n"
|
ret += this.currentErrHandler //"return err\n"
|
||||||
ret += "}\n"
|
ret += "}\n"
|
||||||
ret += "this." + this.currentClassParentName + " = *super // copy by value\n"
|
ret += "this." + this.currentClassParentName + " = *super // copy by value\n"
|
||||||
return ret, nil
|
return ret, nil
|
||||||
@ -611,6 +621,76 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
|
|||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
|
||||||
|
case *stmt.Try:
|
||||||
|
|
||||||
|
// Generate an if err != nil {} block that we can bring up whenever
|
||||||
|
// an error is raised in a body function call
|
||||||
|
// TODO This will produce shadowed error handlers - need to make sure we
|
||||||
|
// use err2, err3 etc based on our current level of nesting
|
||||||
|
|
||||||
|
// TODO put finally() code somewhere (before/after err checking??)
|
||||||
|
|
||||||
|
handler := ""
|
||||||
|
if len(n.Catches) == 0 {
|
||||||
|
handler = "// suppress error"
|
||||||
|
} else {
|
||||||
|
|
||||||
|
catchVars := ""
|
||||||
|
catchIfs := []string{}
|
||||||
|
|
||||||
|
for _, catch_ := range n.Catches {
|
||||||
|
catch, ok := catch_.(*stmt.Catch)
|
||||||
|
if !ok {
|
||||||
|
return "", parseErr{n, fmt.Errorf("expected stmt.Catch")}
|
||||||
|
}
|
||||||
|
|
||||||
|
catchVar := catch.Variable.(*expr.Variable).VarName.(*node.Identifier).Value
|
||||||
|
|
||||||
|
catchBody, err := this.convert(stmt.NewStmtList(catch.Stmts))
|
||||||
|
if err != nil {
|
||||||
|
return "", parseErr{catch_, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, typename_ := range catch.Types {
|
||||||
|
typename, err := this.resolveName(typename_)
|
||||||
|
if !ok {
|
||||||
|
return "", parseErr{catch_, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
tempCatchVar := catchVar
|
||||||
|
if len(catch.Types) > 1 {
|
||||||
|
tempCatchVar += "_" + typename
|
||||||
|
}
|
||||||
|
|
||||||
|
catchVars += "var " + tempCatchVar + "*" + typename + "\n"
|
||||||
|
|
||||||
|
catchStmt := "if errors.As(err, &" + tempCatchVar + ") {\n"
|
||||||
|
if len(catch.Types) > 1 {
|
||||||
|
catchStmt += catchVar + " := " + tempCatchVar + " // rename\n"
|
||||||
|
}
|
||||||
|
catchStmt += catchBody // contains its own {}
|
||||||
|
catchStmt += "} " // but without trailing NL
|
||||||
|
|
||||||
|
catchIfs = append(catchIfs, catchStmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler = catchVars + strings.Join(catchIfs, "else") + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the handler to be used going forwards
|
||||||
|
previousErrHandler := this.currentErrHandler
|
||||||
|
this.currentErrHandler = handler
|
||||||
|
|
||||||
|
body, err := this.convert(stmt.NewStmtList(n.Stmts))
|
||||||
|
if err != nil {
|
||||||
|
return "", parseErr{n, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reinstate parent error handler
|
||||||
|
this.currentErrHandler = previousErrHandler
|
||||||
|
return body, nil // Try/catch behaviour is transparently encoded in err handlers
|
||||||
|
|
||||||
case *stmt.Nop:
|
case *stmt.Nop:
|
||||||
return "", nil
|
return "", nil
|
||||||
|
|
||||||
@ -670,6 +750,8 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
|
|||||||
return "", parseErr{n, err}
|
return "", parseErr{n, err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME need to support `this.currentErrHandler`
|
||||||
|
|
||||||
return funcName + callParams, nil // expr only, no semicolon/newline
|
return funcName + callParams, nil // expr only, no semicolon/newline
|
||||||
|
|
||||||
case *expr.StaticCall:
|
case *expr.StaticCall:
|
||||||
@ -697,6 +779,8 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
|
|||||||
return "", parseErr{n, err}
|
return "", parseErr{n, err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME need to support `this.currentErrHandler`
|
||||||
|
|
||||||
return callTarget + callParams, nil // expr only, no semicolon/newline
|
return callTarget + callParams, nil // expr only, no semicolon/newline
|
||||||
|
|
||||||
case *expr.New:
|
case *expr.New:
|
||||||
@ -750,6 +834,8 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
|
|||||||
return "", parseErr{n, err}
|
return "", parseErr{n, err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME need to support `this.currentErrHandler`
|
||||||
|
|
||||||
return parent + "." + child + args, nil
|
return parent + "." + child + args, nil
|
||||||
|
|
||||||
case *expr.PropertyFetch:
|
case *expr.PropertyFetch:
|
||||||
@ -1021,11 +1107,25 @@ func (this *conversionState) resolveName(n node.Node) (string, error) {
|
|||||||
// TODO support namespace lookups
|
// TODO support namespace lookups
|
||||||
|
|
||||||
paramType := unknownVarType
|
paramType := unknownVarType
|
||||||
if vt, ok := n.(*name.Name); ok {
|
if n == nil || n == node.Node(nil) {
|
||||||
if len(vt.Parts) != 1 {
|
return paramType, nil
|
||||||
return "", parseErr{n, fmt.Errorf("name has %d parts, expected 1", len(vt.Parts))}
|
|
||||||
}
|
}
|
||||||
paramType = vt.Parts[0].(*name.NamePart).Value
|
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *name.FullyQualified:
|
||||||
|
if len(n.Parts) != 1 {
|
||||||
|
return "", parseErr{n, fmt.Errorf("name has %d parts, expected 1", len(n.Parts))}
|
||||||
|
}
|
||||||
|
paramType = n.Parts[0].(*name.NamePart).Value
|
||||||
|
|
||||||
|
case *name.Name:
|
||||||
|
if len(n.Parts) != 1 {
|
||||||
|
return "", parseErr{n, fmt.Errorf("name has %d parts, expected 1", len(n.Parts))}
|
||||||
|
}
|
||||||
|
paramType = n.Parts[0].(*name.NamePart).Value
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unexpected name type %#v", n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle class lookups
|
// Handle class lookups
|
||||||
|
Loading…
Reference in New Issue
Block a user