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 { 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