diff --git a/fixtures/0008-try-catch-finally.php b/fixtures/0008-try-catch-finally.php new file mode 100644 index 0000000..f273ec6 --- /dev/null +++ b/fixtures/0008-try-catch-finally.php @@ -0,0 +1,16 @@ +getMessage() . "\n"; +} catch (\Exception $e) { + echo "other exception " . $e->getMessage() . "\n"; +} finally { + echo "finally"; +} diff --git a/node.go b/node.go index 85c764e..1ba49a8 100644 --- a/node.go +++ b/node.go @@ -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 + } + + 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 = vt.Parts[0].(*name.NamePart).Value + 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