diff --git a/fixtures/0004-arrays.php b/fixtures/0004-arrays.php new file mode 100644 index 0000000..b5ae20f --- /dev/null +++ b/fixtures/0004-arrays.php @@ -0,0 +1,16 @@ + "b", "c" => "d"]; +$bar["x"] = 6; + +function lookupIndex(): int { + return 0; +} + +echo $bar[lookupIndex() + 1]; diff --git a/node.go b/node.go index 49d1ac4..75c030d 100644 --- a/node.go +++ b/node.go @@ -391,18 +391,34 @@ func convert(n_ node.Node) (string, error) { // case *assign.Assign: - lvalue, err := convert(n.Variable) // might be a more complicated lvalue - if err != nil { - return "", parseErr{n, err} - } rvalue, err := convert(n.Expression) if err != nil { return "", parseErr{n, err} } - // TODO this may need to use `:=` - return lvalue + " = " + rvalue, nil + if dimf, ok := n.Variable.(*expr.ArrayDimFetch); ok && dimf.Dim == nil { + // Special handling for the case of foo[] = bar + // Transform into append() + arrayVar, err := convert(dimf.Variable) + if err != nil { + return "", parseErr{dimf, err} + } + + return arrayVar + ` = append(` + arrayVar + `, ` + rvalue + `)`, nil + + } else { + + // Normal assignment + + lvalue, err := 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 @@ -497,6 +513,32 @@ func convert(n_ node.Node) (string, error) { case *expr.ConstFetch: return resolveName(n.Constant) + case *expr.Array: + return convertArrayLiteralCommon(n.Items) + + case *expr.ShortArray: + return 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 := 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 := convert(n.Dim) + if err != nil { + return "", parseErr{n, err} + } + + return vv + `[` + idx + `]`, nil // Same syntax as PHP + // // binary // @@ -754,6 +796,54 @@ func convertFuncCallArgsCommon(args *node.ArgumentList) (string, error) { return "(" + strings.Join(callParams, `, `) + ")", nil // expr only, no semicolon/newline } +func 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 := convert(itm.Val) + if err != nil { + return "", parseErr{itm, err} + } + + if itm.Key != nil { + kv, err := 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 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