package main import ( "fmt" "reflect" "strconv" "strings" "github.com/z7zmey/php-parser/freefloating" "github.com/z7zmey/php-parser/node" "github.com/z7zmey/php-parser/node/expr" "github.com/z7zmey/php-parser/node/expr/assign" "github.com/z7zmey/php-parser/node/expr/binary" "github.com/z7zmey/php-parser/node/name" "github.com/z7zmey/php-parser/node/scalar" "github.com/z7zmey/php-parser/node/stmt" ) func nodeTypeString(n node.Node) string { return reflect.TypeOf(n).String() } type parseErr struct { n node.Node childErr error } func (pe parseErr) Error() string { return fmt.Sprintf("Parsing %s on line %d: %s", nodeTypeString(pe.n), pe.n.GetPosition().StartLine, pe.childErr) } func (pe parseErr) Unwrap() error { return pe.childErr } // type conversionState struct { currentClassName string currentClassParentName string } // func (this *conversionState) convert(n node.Node) (string, error) { // Get any whitespace/comments attached to this node freePrefix := "" freeSuffix := "" if ff := n.GetFreeFloating(); ff != nil && !ff.IsEmpty() { for positionType, elements := range *ff { element: for _, element := range elements { if element.StringType == freefloating.TokenType { // Skip 0 { ret += "func init() {\n" ret += "\t" + strings.Join(statements, "\t") // Statements already added their own newline ret += "}\n" } return ret, nil case *node.Identifier: return n.Value, nil case Literal: // We expect literal statements to act like a *Stmt, i.e. be emitted with a trailing NL return n.Value + "\n", nil // // stmt // case *stmt.StmtList: // TODO keep track of variable types within this scope ret := "{\n" // new variable scope for _, s := range n.Stmts { line, err := this.convert(s) if err != nil { return "", parseErr{s, err} } ret += line // Statements already added a trailing newline } return ret + "}\n", nil case *stmt.Class: ret := "" prevClassName := this.currentClassName // almost certainly empty-string prevClassParentName := this.currentClassParentName className := n.ClassName.(*node.Identifier).Value this.currentClassName = className memberVars := []string{} memberFuncs := []string{} if n.Extends != nil { parentName, err := this.resolveName(n.Extends.ClassName) if err != nil { return "", parseErr{n, err} } memberVars = append(memberVars, parentName+" // parent") this.currentClassParentName = parentName } else { this.currentClassParentName = "" } // Walk all child nodes of the class for _, s_ := range n.Stmts { switch s := s_.(type) { case *stmt.PropertyList: // Class member variable // Doc comment // TODO scan for `@var {type}` strings // Name prop, ok := s.Properties[0].(*stmt.Property) if !ok { return "", parseErr{s, fmt.Errorf("unexpected propertylist structure")} } name := prop.Variable.(*expr.Variable).VarName.(*node.Identifier).Value // Type (unknown) memberType := unknownVarType // 'Modifiers' - protected public readonly ... // prop.Modifiers memberVars = append(memberVars, name+" "+memberType) case *stmt.ClassMethod: // Function name // If function is public/private/protected, set the first character to upper/lowercase funcName, err := applyVisibilityModifier(s.MethodName.(*node.Identifier).Value, s.Modifiers) if err != nil { return "", parseErr{s, err} } // Doc comment // TODO scan for `@param {type}` strings isConstructor := (strings.ToLower(funcName) == `__construct` || strings.ToLower(funcName) == strings.ToLower(className)) if isConstructor { // Constructor functions get transformed to NewFoo() (*Foo, error) // We need to force the return type returnType := name.NewName([]node.Node{name.NewNamePart(className)}) // We also need prefix + suffix statements allStmts := make([]node.Node, 0, 2+len(s.Stmt.(*stmt.StmtList).Stmts)) allStmts = append(allStmts, Literal{`this := &` + className + `{}`}) // TODO also insert variable type into the scope allStmts = append(allStmts, s.Stmt.(*stmt.StmtList).Stmts...) allStmts = append(allStmts, Literal{`return this, nil`}) // Method body funcStmt, err := this.convertFunctionCommon(s.Params, returnType, true /* always use ptr return */, allStmts) if err != nil { return "", parseErr{s, err} } memberFuncStmt := "func " + constructorName(className) + funcStmt + "\n" memberFuncs = append(memberFuncs, memberFuncStmt) } else { // Check if this is a static method hasStatic, err := hasModifier(s.Modifiers, `static`) if err != nil { return "", parseErr{s, err} } // Method body funcStmt, err := this.convertFunctionCommon(s.Params, s.ReturnType, s.ReturnsRef, s.Stmt.(*stmt.StmtList).Stmts) if err != nil { return "", parseErr{s, err} } if hasStatic { memberFuncs = append(memberFuncs, "func "+className+funcName+funcStmt+"\n") } else { memberFuncs = append(memberFuncs, "func (this *"+className+") "+funcName+funcStmt+"\n") } } default: return "", parseErr{s, fmt.Errorf("Class '%s' contained unexpected AST node; expected PropertyList / ClassMethod", className)} } } // Create struct typedef containing all explicit properties ret += "type " + className + " struct {\n" ret += "\t" + strings.Join(memberVars, "\n\t") + "\n" ret += "}\n" // Create all member functions ret += strings.Join(memberFuncs, "\n\n") // Done // Reinstate parent state before returning this.currentClassName = prevClassName this.currentClassParentName = prevClassParentName return ret, nil case *stmt.Function: // Top-level function definition // TODO parse doc comment // FIXME is this the same as a closure? funcName := n.FunctionName.(*node.Identifier).Value if funcName == `super` { return "", parseErr{n, fmt.Errorf("Function name '%s' probably will not function correctly", funcName)} } // All top-level functions like this are public; ensure function name starts // with an uppercase letter funcName = toPublic(funcName) // Convert body funcStmt, err := this.convertFunctionCommon(n.Params, n.ReturnType, n.ReturnsRef, n.Stmts) if err != nil { return "", parseErr{n, err} } ret := "func " + funcName + funcStmt + "\n" return ret, nil case *stmt.Return: child, err := this.convert(n.Expr) if err != nil { return "", parseErr{n, err} } ret := "return " + child + ", nil\n" return ret, nil case *stmt.Throw: // throw (expr); // Treat as an err return // FIXME we don't know the default return type for the function we're in // If the expr is a string literal, we can convert it to errors.New() // Although we probably can't do this in general for stringly-typed expressions if str, ok := n.Expr.(*scalar.String); ok { return "return nil, errors.New(" + str.Value + ")\n", nil } child, err := this.convert(n.Expr) if err != nil { return "", parseErr{n, err} } return "return nil, " + child + "\n", nil case *stmt.For: var preinit, finit string var err error = nil if len(n.Init) == 0 { // No initialiser in loop } else if len(n.Init) == 1 { finit, err = this.convert(n.Init[0]) if err != nil { return "", parseErr{n, err} } } else { // We can handle the case of multiple init statements by hoisting them // above the loop. There is no negative impact on PHP scoping rules, but // it may cause an extra local variable after the loop that may result // in type mismatch (can be fixed by using an extra scope). for _, initStmt := range n.Init { singleInitStmt, err := this.convert(initStmt) if err != nil { return "", parseErr{initStmt, err} } preinit += singleInitStmt + "\n" } } if len(n.Cond) != 1 { return "", parseErr{n, fmt.Errorf("for loop can only have 1 cond clause, found %d", len(n.Cond))} } fcond, err := this.convert(n.Cond[0]) if err != nil { return "", parseErr{n, err} } if len(n.Loop) != 1 { return "", parseErr{n, fmt.Errorf("for loop can only have 1 loop clause, found %d", len(n.Loop))} } loopStmt := n.Loop[0] if preinc, ok := loopStmt.(*expr.PreInc); ok { // It's idiomatic to do for (,, ++i) but preincrement doesn't exist in Go // Luckily for the case of a for loop, we can just swap it to postincrement loopStmt = expr.NewPostInc(preinc.Variable) } else if predec, ok := loopStmt.(*expr.PreDec); ok { // Likewise loopStmt = expr.NewPostDec(predec.Variable) } floop, err := this.convert(loopStmt) if err != nil { return "", parseErr{n, err} } body, err := this.convert(convertToStmtList(n.Stmt)) if err != nil { return "", parseErr{n, err} } return preinit + "for " + finit + "; " + fcond + "; " + floop + " " + body + "\n", nil case *stmt.Foreach: iterand, err := this.convert(n.Expr) if err != nil { return "", parseErr{n, err} } valueReceiver, err := this.convert(n.Variable) if err != nil { return "", parseErr{n, err} } keyReceiver := `_` if n.Key != nil { keyReceiver, err = this.convert(n.Key) if err != nil { return "", parseErr{n, err} } } body, err := this.convert(convertToStmtList(n.Stmt)) if err != nil { return "", parseErr{n, err} } return "for " + keyReceiver + ", " + valueReceiver + " := range " + iterand + " " + body + "\n", nil case *stmt.While: cond, err := this.convert(n.Cond) if err != nil { return "", parseErr{n, err} } body, err := this.convert(convertToStmtList(n.Stmt)) if err != nil { return "", parseErr{n, err} } return "for " + cond + " " + body + "\n", nil case *stmt.Do: cond, err := this.convert(n.Cond) if err != nil { return "", parseErr{n, err} } bodyStmts := convertToStmtList(n.Stmt) bodyStmts.Stmts = append(bodyStmts.Stmts, Literal{"if " + cond + "{\nbreak\n}"}) body, err := this.convert(bodyStmts) if err != nil { return "", parseErr{n, err} } return "for " + cond + " " + body + "\n", nil case *stmt.Expression: // Special case if fncall, ok := n.Expr.(*expr.FunctionCall); ok { if fnname, err := this.resolveName(fncall.Function); err == nil && fnname == "super" { // Call to parent constructor if this.currentClassParentName == "" { return "", parseErr{n, fmt.Errorf("Call to parent constructor outside of class context")} } // We need to call the parent constructor function (NewX) with these arguments funcArgs, err := this.convertFuncCallArgsCommon(fncall.ArgumentList) if err != nil { return "", parseErr{n, err} } // Then we need to overwrite the embedded value type with the received pointer // That's not 100% safe in all cases if the *this pointer is leaked // within the constructor, but, our generated code will never do that // TODO replace our NewX constructors with split NewX + newXInPlace(??) // No need to use a child scope for the temporary name - // 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 += "}\n" ret += "this." + this.currentClassParentName + " = *super // copy by value\n" return ret, nil } } // Regular case child, err := this.convert(n.Expr) if err != nil { return "", parseErr{n, err} } ret := child + "\n" // standalone expression statement return ret, nil case *stmt.Echo: // Convert into fmt.Print args := make([]string, 0, len(n.Exprs)) for _, expr := range n.Exprs { exprGo, err := this.convert(expr) if err != nil { return "", parseErr{n, err} } args = append(args, exprGo) } return "fmt.Print(" + strings.Join(args, ", ") + ")\n", nil // newline - standalone statement case *stmt.InlineHtml: // Convert into fmt.Print var quoted string if !strings.Contains(n.Value, "`") && strings.Count(n.Value, "\n") >= 3 { // TODO make the heuristic configurable // Use backtick-delimited multiline string quoted = "`" + n.Value + "`" } else { // Can't trivially represent it with backticks, or it's not multiline "enough" to bother - use full Go quoting quoted = strconv.Quote(n.Value) } return "fmt.Print(" + quoted + ")\n", nil // newline - standalone statement case *stmt.If: hasCondAssign, err := hasInteriorAssignment(n.Cond) if err != nil { return "", parseErr{n, err} } if hasCondAssign { return "", parseErr{n.Cond, fmt.Errorf("please remove assignment from if-expression")} } cond, err := this.convert(n.Cond) if err != nil { return "", parseErr{n, err} } body, err := this.convert(convertToStmtList(n.Stmt)) if err != nil { return "", parseErr{n, err} } ret := "if " + cond + body for _, elif := range n.ElseIf { elif, ok := elif.(*stmt.ElseIf) if !ok { return "", parseErr{n, fmt.Errorf("expected stmt.ElseIf")} } cond, err := this.convert(elif.Cond) if err != nil { return "", parseErr{n, err} } body, err := this.convert(convertToStmtList(elif.Stmt)) if err != nil { return "", parseErr{n, err} } ret += " else if " + cond + body } if n.Else != nil { els, ok := n.Else.(*stmt.Else) if !ok { return "", parseErr{n, fmt.Errorf("expected stmt.Else")} } body, err := this.convert(convertToStmtList(els.Stmt)) if err != nil { return "", parseErr{n, err} } ret += " else " + body } return ret, nil case *stmt.Nop: return "", nil // // assign // case *assign.Assign: rvalue, err := this.convert(n.Expression) if err != nil { return "", parseErr{n, err} } if dimf, ok := n.Variable.(*expr.ArrayDimFetch); ok && dimf.Dim == nil { // Special handling for the case of foo[] = bar // Transform into append() arrayVar, err := this.convert(dimf.Variable) if err != nil { return "", parseErr{dimf, err} } return arrayVar + ` = append(` + arrayVar + `, ` + rvalue + `)`, nil } else { // Normal assignment lvalue, err := this.convert(n.Variable) // might be a more complicated lvalue if err != nil { return "", parseErr{n, err} } // TODO this may need to use `:=` return lvalue + " = " + rvalue, nil } // // expr // case *expr.FunctionCall: // All our generated functions return err, but this AST node may be in a single-rvalue context // TODO do something more intelligent here // We can't necessarily hoist the whole call, in case we are on the right-hand side of a && operator funcName, err := this.resolveName(n.Function) if err != nil { return "", parseErr{n, err} } if funcName == "super" { return "", parseErr{n, fmt.Errorf("Unexpected use of parent constructor in rvalue context")} } callParams, err := this.convertFuncCallArgsCommon(n.ArgumentList) if err != nil { return "", parseErr{n, err} } return funcName + callParams, nil // expr only, no semicolon/newline case *expr.StaticCall: className, err := this.resolveName(n.Class) if err != nil { return "", parseErr{n, err} } funcName, err := this.convert(n.Call) if err != nil { return "", parseErr{n, err} } callTarget := className + "." + funcName if className == "self" { if this.currentClassName == "" { return "", parseErr{n, fmt.Errorf("Made a self::Static method call while not in class context")} } // We're making a static call, and we renamed those to be top-level functions callTarget = this.currentClassName + funcName } callParams, err := this.convertFuncCallArgsCommon(n.ArgumentList) if err != nil { return "", parseErr{n, err} } return callTarget + callParams, nil // expr only, no semicolon/newline case *expr.New: // new foo(xx) // Transparently convert to calling constructor function. nn, err := this.resolveName(n.Class) if err != nil { return "", parseErr{n, err} } // FIXME if there is a package specifier embedded in the result name, // the `New` will appear in the wrong place nn = `New` + nn // Convert resolved back to node.Name transparentNameNode := name.NewName([]node.Node{name.NewNamePart(nn)}) return this.convert(expr.NewFunctionCall(transparentNameNode, n.ArgumentList)) case *expr.PreInc: // """In Go, i++ is a statement, not an expression. So you can't use its value in another expression such as a function call.""" v, err := this.convert(n.Variable) if err != nil { return "", parseErr{n, err} } return "++" + v, nil case *expr.PostInc: // """In Go, i++ is a statement, not an expression. So you can't use its value in another expression such as a function call.""" v, err := this.convert(n.Variable) if err != nil { return "", parseErr{n, err} } return v + "++", nil case *expr.MethodCall: // Foo->Bar(Baz) parent, err := this.convert(n.Variable) if err != nil { return "", parseErr{n, err} } child, err := this.convert(n.Method) if err != nil { return "", parseErr{n, err} } args, err := this.convertFuncCallArgsCommon(n.ArgumentList) if err != nil { return "", parseErr{n, err} } return parent + "." + child + args, nil case *expr.PropertyFetch: // Foo->Bar parent, err := this.convert(n.Variable) if err != nil { return "", parseErr{n, err} } child, err := this.convert(n.Property) if err != nil { return "", parseErr{n, err} } return parent + "." + child, nil case *expr.Variable: return n.VarName.(*node.Identifier).Value, nil case *expr.ConstFetch: return this.resolveName(n.Constant) case *expr.Array: return this.convertArrayLiteralCommon(n.Items) case *expr.ShortArray: return this.convertArrayLiteralCommon(n.Items) case *expr.ArrayDimFetch: // Might be x[foo], might be x[] (i.e. append() call) // In order to make the append() transformation, we need to lookahead // for ArrayDimFetch in the `*assign.Assign` case vv, err := this.convert(n.Variable) if err != nil { return "", parseErr{n, err} } if n.Dim == nil { return "", parseErr{n, fmt.Errorf("found '%s[]' outside of lvalue assignment context", vv)} } idx, err := this.convert(n.Dim) if err != nil { return "", parseErr{n, err} } return vv + `[` + idx + `]`, nil // Same syntax as PHP // // binary // case *binary.BitwiseAnd: return this.convertBinaryCommon(n.Left, n.Right, `&`) case *binary.BitwiseOr: return this.convertBinaryCommon(n.Left, n.Right, `|`) case *binary.BitwiseXor: return this.convertBinaryCommon(n.Left, n.Right, `^`) // n.b. Go only supports this for integers; PHP also supports it for bools case *binary.BooleanAnd: return this.convertBinaryCommon(n.Left, n.Right, `&&`) case *binary.BooleanOr: return this.convertBinaryCommon(n.Left, n.Right, `||`) //case *binary.Coalesce: // TODO this can't be expressed in an rvalue context in Go (unless we create a typed closure..?) case *binary.Concat: return this.convertBinaryCommon(n.Left, n.Right, `+`) // PHP uses + for numbers, `.` for strings; Go uses `+` in both cases case *binary.Div: return this.convertBinaryCommon(n.Left, n.Right, `/`) // PHP will upgrade ints to floats, Go won't case *binary.Equal: return this.convertBinaryCommon(n.Left, n.Right, `==`) // Type-lax equality comparator case *binary.GreaterOrEqual: return this.convertBinaryCommon(n.Left, n.Right, `>=`) case *binary.Greater: return this.convertBinaryCommon(n.Left, n.Right, `>`) case *binary.Identical: return this.convertBinaryCommon(n.Left, n.Right, `==`) // PHP uses `===`, Go is already type-safe case *binary.LogicalAnd: // This is the lexer token when using `and` in PHP. It's equivalent to // `&&` but has different precedence // e.g. $a = $b && $c ==> $a = ($b && $c) // $a = $b and $c ==> ($a = $b) and $c // So far, we are relying on the PHP parser having already having handled // the precedence difference - transform to `&&` unconditionally return this.convertBinaryCommon(n.Left, n.Right, `&&`) case *binary.LogicalOr: // As above return this.convertBinaryCommon(n.Left, n.Right, `||`) case *binary.LogicalXor: // As above return this.convertBinaryCommon(n.Left, n.Right, `^`) // n.b. Go only supports this for integers; PHP also supports it for bools case *binary.Minus: return this.convertBinaryCommon(n.Left, n.Right, `-`) case *binary.Mod: // Go doesn't have a built-in operator for mod - convert to a call to math.Mod() rval, err := this.convert(n.Left) if err != nil { return "", parseErr{n, err} } modulo, err := this.convert(n.Right) if err != nil { return "", parseErr{n, err} } return `math.Mod(` + rval + `, ` + modulo + `)`, nil case *binary.Mul: return this.convertBinaryCommon(n.Left, n.Right, `*`) case *binary.NotEqual: return this.convertBinaryCommon(n.Left, n.Right, `!=`) // Type-lax equality comparator case *binary.NotIdentical: return this.convertBinaryCommon(n.Left, n.Right, `!=`) // PHP uses `!==`, Go is already type-safe case *binary.Plus: return this.convertBinaryCommon(n.Left, n.Right, `+`) // PHP uses + for numbers, `.` for strings; Go uses `+` in both cases case *binary.Pow: // Go doesn't have a built-in operator for mod - convert to a call to math.Pow() base, err := this.convert(n.Left) if err != nil { return "", parseErr{n, err} } exponent, err := this.convert(n.Right) if err != nil { return "", parseErr{n, err} } return `math.Pow(` + base + `, ` + exponent + `)`, nil case *binary.ShiftLeft: return this.convertBinaryCommon(n.Left, n.Right, `<<`) case *binary.ShiftRight: return this.convertBinaryCommon(n.Left, n.Right, `>>`) case *binary.SmallerOrEqual: return this.convertBinaryCommon(n.Left, n.Right, `<=`) case *binary.Smaller: return this.convertBinaryCommon(n.Left, n.Right, `<`) case *binary.Spaceship: // The spaceship operator returns -1 / 0 / 1 based on a gteq/leq comparison // Go doesn't have a built-in spaceship operator // The primary use case is in user-definded sort comparators, where Go // uses bools instead ints anyway. // Subtraction is a reasonable substitute return this.convertBinaryCommon(n.Left, n.Right, `-`) // // scalar // case *scalar.Lnumber: return n.Value, nil // number formats are compatible case *scalar.String: return n.Value, nil // It's already quoted in PHP format // return strconv.Quote(n.Value), nil // Go source code quoting format // // // default: return "", fmt.Errorf("unsupported node type %s", nodeTypeString(n)) } } func hasModifier(modifiers []node.Node, search string) (bool, error) { for _, mod := range modifiers { ident, ok := mod.(*node.Identifier) if !ok { return false, parseErr{mod, fmt.Errorf("expected node.Identifier")} } if strings.ToLower(ident.Value) == strings.ToLower(search) { return true, nil } } return false, nil } // applyVisibilityModifier renames a function to use an upper/lowercase first // letter based on PHP visibility modifiers. func applyVisibilityModifier(funcName string, modifiers []node.Node) (string, error) { hasPublic, err := hasModifier(modifiers, "public") if err != nil { return "", err } hasPrivate, err := hasModifier(modifiers, "private") if err != nil { return "", err } hasProtected, err := hasModifier(modifiers, "protected") if err != nil { return "", err } if (hasPublic && !hasPrivate && !hasProtected) /* explicitly public */ || (!hasPublic && !hasPrivate && !hasProtected) /* no modifiers defaults to public */ { return toPublic(funcName), nil } else if !hasPublic && (hasPrivate || hasProtected) { return toPrivate(funcName), nil } else { return "", fmt.Errorf("unexpected combination of modifiers") } } func toPublic(name string) string { nFirst := name[0:1] uFirst := strings.ToUpper(nFirst) if nFirst == uFirst { return name // avoid making more heap garbage } return uFirst + name[1:] } func toPrivate(name string) string { nFirst := name[0:1] lFirst := strings.ToLower(nFirst) if nFirst == lFirst { return name // avoid making more heap garbage } return lFirst + name[1:] } func constructorName(className string) string { return `New` + className } // resolveName turns a `*name.Name` node into a Go string. 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))} } paramType = vt.Parts[0].(*name.NamePart).Value } // Handle class lookups if strings.ToLower(paramType) == "parent" { if this.currentClassParentName == "" { return "", parseErr{n, fmt.Errorf("Lookup of 'parent' while not in an inherited child class context")} } return `this.` + this.currentClassParentName, nil } else if strings.ToLower(paramType) == "self" { // Let it through as-is // return "", parseErr{n, fmt.Errorf("Lookup of 'self::' should have been resolved already")} /* if this.currentClassName == "" { return "", parseErr{n, fmt.Errorf("Lookup of 'self' while not in class context")} } return `this`, nil */ } else if strings.ToLower(paramType) == "static" { return "", parseErr{n, fmt.Errorf("'static::' is not yet supported")} } return paramType, nil } // convertToStmtList asserts that the node is either a StmtList or wraps it in a // single-stmt StmtList if not. // Loop bodies may be a StmtList if it is wrapped in {}, or a single statement // if it is not; we want to enforce the use of {} for all loop bodies func convertToStmtList(n node.Node) *stmt.StmtList { if sl, ok := n.(*stmt.StmtList); ok { return sl // It's already a StmtList } return stmt.NewStmtList([]node.Node{n}) } func (this *conversionState) convertBinaryCommon(left, right node.Node, goBinaryOperator string) (string, error) { // PHP uses + for numbers, `.` for strings; Go uses `+` in both cases // Assume PHP/Go have the same associativity here lhs, err := this.convert(left) if err != nil { return "", parseErr{left, err} } rhs, err := this.convert(right) if err != nil { return "", parseErr{right, err} } // In case of an rvalue assignment expression, we need extra parens if _, ok := left.(*assign.Assign); ok { lhs = "(" + lhs + ")" } if _, ok := right.(*assign.Assign); ok { rhs = "(" + rhs + ")" } return "(" + lhs + " " + goBinaryOperator + " " + rhs + ")", nil } func (this *conversionState) convertFuncCallArgsCommon(args *node.ArgumentList) (string, error) { callParams := make([]string, 0, len(args.Arguments)) for _, arg_ := range args.Arguments { arg, ok := arg_.(*node.Argument) if !ok { return "", parseErr{arg_, fmt.Errorf("expected node.Argument")} } rvalue, err := this.convert(arg.Expr) if err != nil { return "", parseErr{arg, err} } if arg.IsReference { rvalue = "&" + rvalue } if arg.Variadic { rvalue = "..." + rvalue } callParams = append(callParams, rvalue) } return "(" + strings.Join(callParams, `, `) + ")", nil // expr only, no semicolon/newline } func (this *conversionState) convertArrayLiteralCommon(items []node.Node) (string, error) { // Array literal // We need to know the type. See if we can guess it from the first child element // At least, we may be able to determine if this is a map or an array entries := []string{} keyType := unknownVarType valType := unknownVarType isMapType := false for idx, itm_ := range items { itm, ok := itm_.(*expr.ArrayItem) if !ok { return "", parseErr{itm_, fmt.Errorf("expected ArrayItem")} } if idx == 0 { isMapType = (itm.Key != nil) } else { if isMapType != (itm.Key != nil) { return "", parseErr{itm, fmt.Errorf("Can't represent array and map in a single type")} } } vv, err := this.convert(itm.Val) if err != nil { return "", parseErr{itm, err} } if itm.Key != nil { kv, err := this.convert(itm.Key) if err != nil { return "", parseErr{itm, err} } entries = append(entries, kv+`: `+vv+`,`) } else { entries = append(entries, vv+`,`) } } if isMapType { return `map[` + keyType + `]` + valType + `{` + strings.Join(entries, " ") + `}`, nil } else { return `[]` + valType + `{` + strings.Join(entries, " ") + `}`, nil } } func (this *conversionState) convertFunctionCommon(params []node.Node, returnType node.Node, returnsRef bool, bodyStmts []node.Node) (string, error) { // TODO scan function and see if it contains any return statements at all // If not, then we only need an err return parameter, not anything else funcParams := []string{} for _, param := range params { param, ok := param.(*node.Parameter) // shadow if !ok { return "", parseErr{param, fmt.Errorf("expected node.Parameter")} } // VariableType: might be nil for untyped parameters paramType, err := this.resolveName(param.VariableType) if err != nil { return "", parseErr{param, err} } if param.ByRef { paramType = "*" + paramType } if param.Variadic { paramType = "..." + paramType } // Name paramName := param.Variable.(*expr.Variable).VarName.(*node.Identifier).Value funcParams = append(funcParams, paramName+" "+paramType) } // ReturnType funcReturn, err := this.resolveName(returnType) if err != nil { return "", parseErr{returnType, err} } if returnsRef { funcReturn = "*" + funcReturn } // Build function prototype ret := "(" + strings.Join(funcParams, ", ") + ") (" + funcReturn + ", error) " // Recurse through body statements fullBody, err := this.convert(stmt.NewStmtList(bodyStmts)) if err != nil { return "", err } ret += fullBody + "\n" // Done // No extra trailing newline in case this is part of a large expression return ret, nil } // hasInteriorAssignment recursively walks a node, to determine if it contains // any assignment expressions func hasInteriorAssignment(n node.Node) (hasAnyAssign bool, err error) { err = walk(n, func(n node.Node) error { if _, ok := n.(*assign.Assign); ok { hasAnyAssign = true } return nil }) return // named return }