node: initial try/catch support, track the error handler (WIP)

This commit is contained in:
mappu 2020-04-07 22:52:42 +12:00
parent 0d7024dbee
commit 52ccea5d65
2 changed files with 121 additions and 5 deletions

View 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
View File

@ -38,6 +38,7 @@ func (pe parseErr) Unwrap() error {
type conversionState struct {
currentClassName string
currentClassParentName string
currentErrHandler string
}
//
@ -100,6 +101,15 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
statements := []string{}
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)
if err != nil {
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
ret := "super, err := " + constructorName(this.currentClassParentName) + funcArgs + "\n"
ret += "if err != nil {\n"
ret += "return err\n"
ret += this.currentErrHandler //"return err\n"
ret += "}\n"
ret += "this." + this.currentClassParentName + " = *super // copy by value\n"
return ret, nil
@ -611,6 +621,76 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
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:
return "", nil
@ -670,6 +750,8 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
return "", parseErr{n, err}
}
// FIXME need to support `this.currentErrHandler`
return funcName + callParams, nil // expr only, no semicolon/newline
case *expr.StaticCall:
@ -697,6 +779,8 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
return "", parseErr{n, err}
}
// FIXME need to support `this.currentErrHandler`
return callTarget + callParams, nil // expr only, no semicolon/newline
case *expr.New:
@ -750,6 +834,8 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
return "", parseErr{n, err}
}
// FIXME need to support `this.currentErrHandler`
return parent + "." + child + args, nil
case *expr.PropertyFetch:
@ -1021,11 +1107,25 @@ func (this *conversionState) resolveName(n node.Node) (string, error) {
// TODO support namespace lookups
paramType := unknownVarType
if vt, ok := n.(*name.Name); ok {
if len(vt.Parts) != 1 {
return "", parseErr{n, fmt.Errorf("name has %d parts, expected 1", len(vt.Parts))}
if n == nil || n == node.Node(nil) {
return paramType, nil
}
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