authorgravatar for evan@lagerdata.comEvan Haas <evan@lagerdata.com> 2021-01-19 23:42:48-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-25 10:37:23-08:00
log57b2176e28fd336309922cc9182cffcf084c1879
treefe2b08e3caadde89b3b276ecf59d59c652c11adc
parent68a040aec7125adca64a16276ae1b088f2c6ba15

translate-c: Improve array support

1. For incomplete arrays with initializer list (`int x[] = {1};`) use the initializer size as the array size. 2. For arrays initialized with a string literal translate it as an array of character literals instead of `[*c]const u8` 3. Don't crash if an empty initializer is used for an incomplete array. 4. Add a test for multi-character character constants Additionally lay some groundwork for supporting wide string literals. fixes #4831 #7832 #7842

6 files changed, 279 insertions(+), 46 deletions(-)

src/clang.zig+9
......@@ -735,6 +735,15 @@ pub const StringLiteral = opaque {
735735 pub const getKind = ZigClangStringLiteral_getKind;
736736 extern fn ZigClangStringLiteral_getKind(*const StringLiteral) StringLiteral_StringKind;
737737
738 pub const getCodeUnit = ZigClangStringLiteral_getCodeUnit;
739 extern fn ZigClangStringLiteral_getCodeUnit(*const StringLiteral, usize) u32;
740
741 pub const getLength = ZigClangStringLiteral_getLength;
742 extern fn ZigClangStringLiteral_getLength(*const StringLiteral) c_uint;
743
744 pub const getCharByteWidth = ZigClangStringLiteral_getCharByteWidth;
745 extern fn ZigClangStringLiteral_getCharByteWidth(*const StringLiteral) c_uint;
746
738747 pub const getString_bytes_begin_size = ZigClangStringLiteral_getString_bytes_begin_size;
739748 extern fn ZigClangStringLiteral_getString_bytes_begin_size(*const StringLiteral, *usize) [*]const u8;
740749};
src/translate_c.zig+171-42
......@@ -710,6 +710,12 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
710710 return addTopLevelDecl(c, fn_name, &proto_node.base);
711711}
712712
713fn transQualTypeMaybeInitialized(rp: RestorePoint, qt: clang.QualType, decl_init: ?*const clang.Expr, loc: clang.SourceLocation) TransError!*ast.Node {
714 return if (decl_init) |init_expr|
715 transQualTypeInitialized(rp, qt, init_expr, loc)
716 else
717 transQualType(rp, qt, loc);
718}
713719/// if mangled_name is not null, this var decl was declared in a block scope.
714720fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]const u8) Error!void {
715721 const var_name = mangled_name orelse try c.str(@ptrCast(*const clang.NamedDecl, var_decl).getName_bytes_begin());
......@@ -734,6 +740,7 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co
734740 const storage_class = var_decl.getStorageClass();
735741 const is_const = qual_type.isConstQualified();
736742 const has_init = var_decl.hasInit();
743 const decl_init = var_decl.getInit();
737744
738745 // In C extern variables with initializers behave like Zig exports.
739746 // extern int foo = 2;
......@@ -755,8 +762,9 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co
755762 const name_tok = try appendIdentifier(c, checked_name);
756763
757764 _ = try appendToken(c, .Colon, ":");
758 const type_node = transQualType(rp, qual_type, var_decl_loc) catch |err| switch (err) {
759 error.UnsupportedType => {
765
766 const type_node = transQualTypeMaybeInitialized(rp, qual_type, decl_init, var_decl_loc) catch |err| switch (err) {
767 error.UnsupportedTranslation, error.UnsupportedType => {
760768 return failDecl(c, var_decl_loc, checked_name, "unable to resolve variable type", .{});
761769 },
762770 error.OutOfMemory => |e| return e,
......@@ -770,17 +778,22 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co
770778 // with the variable type.
771779 if (has_init) {
772780 eq_tok = try appendToken(c, .Equal, "=");
773 init_node = if (var_decl.getInit()) |expr|
774 transExprCoercing(rp, &c.global_scope.base, expr, .used, .r_value) catch |err| switch (err) {
781 if (decl_init) |expr| {
782 const node_or_error = if (expr.getStmtClass() == .StringLiteralClass)
783 transStringLiteralAsArray(rp, &c.global_scope.base, @ptrCast(*const clang.StringLiteral, expr), type_node)
784 else
785 transExprCoercing(rp, scope, expr, .used, .r_value);
786 init_node = node_or_error catch |err| switch (err) {
775787 error.UnsupportedTranslation,
776788 error.UnsupportedType,
777789 => {
778790 return failDecl(c, var_decl_loc, checked_name, "unable to translate initializer", .{});
779791 },
780792 error.OutOfMemory => |e| return e,
781 }
782 else
783 try transCreateNodeUndefinedLiteral(c);
793 };
794 } else {
795 init_node = try transCreateNodeUndefinedLiteral(c);
796 }
784797 } else if (storage_class != .Extern) {
785798 eq_tok = try appendToken(c, .Equal, "=");
786799 // The C language specification states that variables with static or threadlocal
......@@ -1620,6 +1633,7 @@ fn transDeclStmtOne(
16201633 switch (decl.getKind()) {
16211634 .Var => {
16221635 const var_decl = @ptrCast(*const clang.VarDecl, decl);
1636 const decl_init = var_decl.getInit();
16231637
16241638 const qual_type = var_decl.getTypeSourceInfo_getType();
16251639 const name = try c.str(@ptrCast(*const clang.NamedDecl, var_decl).getName_bytes_begin());
......@@ -1643,11 +1657,14 @@ fn transDeclStmtOne(
16431657
16441658 _ = try appendToken(c, .Colon, ":");
16451659 const loc = decl.getLocation();
1646 const type_node = try transQualType(rp, qual_type, loc);
1660 const type_node = try transQualTypeMaybeInitialized(rp, qual_type, decl_init, loc);
16471661
16481662 const eq_token = try appendToken(c, .Equal, "=");
1649 var init_node = if (var_decl.getInit()) |expr|
1650 try transExprCoercing(rp, scope, expr, .used, .r_value)
1663 var init_node = if (decl_init) |expr|
1664 if (expr.getStmtClass() == .StringLiteralClass)
1665 try transStringLiteralAsArray(rp, scope, @ptrCast(*const clang.StringLiteral, expr), type_node)
1666 else
1667 try transExprCoercing(rp, scope, expr, .used, .r_value)
16511668 else
16521669 try transCreateNodeUndefinedLiteral(c);
16531670 if (!qualTypeIsBoolean(qual_type) and isBoolRes(init_node)) {
......@@ -1740,7 +1757,7 @@ fn transImplicitCastExpr(
17401757 return maybeSuppressResult(rp, scope, result_used, sub_expr_node);
17411758 },
17421759 .ArrayToPointerDecay => {
1743 if (exprIsStringLiteral(sub_expr)) {
1760 if (exprIsNarrowStringLiteral(sub_expr)) {
17441761 const sub_expr_node = try transExpr(rp, scope, sub_expr, .used, .r_value);
17451762 return maybeSuppressResult(rp, scope, result_used, sub_expr_node);
17461763 }
......@@ -1841,17 +1858,20 @@ fn exprIsBooleanType(expr: *const clang.Expr) bool {
18411858 return qualTypeIsBoolean(expr.getType());
18421859}
18431860
1844fn exprIsStringLiteral(expr: *const clang.Expr) bool {
1861fn exprIsNarrowStringLiteral(expr: *const clang.Expr) bool {
18451862 switch (expr.getStmtClass()) {
1846 .StringLiteralClass => return true,
1863 .StringLiteralClass => {
1864 const string_lit = @ptrCast(*const clang.StringLiteral, expr);
1865 return string_lit.getCharByteWidth() == 1;
1866 },
18471867 .PredefinedExprClass => return true,
18481868 .UnaryOperatorClass => {
18491869 const op_expr = @ptrCast(*const clang.UnaryOperator, expr).getSubExpr();
1850 return exprIsStringLiteral(op_expr);
1870 return exprIsNarrowStringLiteral(op_expr);
18511871 },
18521872 .ParenExprClass => {
18531873 const op_expr = @ptrCast(*const clang.ParenExpr, expr).getSubExpr();
1854 return exprIsStringLiteral(op_expr);
1874 return exprIsNarrowStringLiteral(op_expr);
18551875 },
18561876 else => return false,
18571877 }
......@@ -2049,6 +2069,71 @@ fn transStringLiteral(
20492069 }
20502070}
20512071
2072/// Parse the size of an array back out from an ast Node.
2073fn zigArraySize(c: *Context, node: *ast.Node) TransError!usize {
2074 if (node.castTag(.ArrayType)) |array| {
2075 if (array.len_expr.castTag(.IntegerLiteral)) |int_lit| {
2076 const tok = tokenSlice(c, int_lit.token);
2077 return std.fmt.parseUnsigned(usize, tok, 10) catch error.UnsupportedTranslation;
2078 }
2079 }
2080 return error.UnsupportedTranslation;
2081}
2082
2083/// Translate a string literal to an array of integers. Used when an
2084/// array is initialized from a string literal. `target_node` is the
2085/// array being initialized. If the string literal is larger than the
2086/// array, truncate the string. If the array is larger than the string
2087/// literal, pad the array with 0's
2088fn transStringLiteralAsArray(
2089 rp: RestorePoint,
2090 scope: *Scope,
2091 stmt: *const clang.StringLiteral,
2092 target_node: *ast.Node,
2093) TransError!*ast.Node {
2094 const array_size = try zigArraySize(rp.c, target_node);
2095 const str_length = stmt.getLength();
2096
2097 const expr_base = @ptrCast(*const clang.Expr, stmt);
2098 const ty = expr_base.getType().getTypePtr();
2099 const const_arr_ty = @ptrCast(*const clang.ConstantArrayType, ty);
2100
2101 const ty_node = try rp.c.arena.create(ast.Node.ArrayType);
2102 const op_token = try appendToken(rp.c, .LBracket, "[");
2103 const len_expr = try transCreateNodeInt(rp.c, array_size);
2104 _ = try appendToken(rp.c, .RBracket, "]");
2105
2106 ty_node.* = .{
2107 .op_token = op_token,
2108 .rhs = try transQualType(rp, const_arr_ty.getElementType(), expr_base.getBeginLoc()),
2109 .len_expr = len_expr,
2110 };
2111 _ = try appendToken(rp.c, .LBrace, "{");
2112 var init_node = try ast.Node.ArrayInitializer.alloc(rp.c.arena, array_size);
2113 init_node.* = .{
2114 .lhs = &ty_node.base,
2115 .rtoken = undefined,
2116 .list_len = array_size,
2117 };
2118 const init_list = init_node.list();
2119
2120 var i: c_uint = 0;
2121 const kind = stmt.getKind();
2122 const narrow = kind == .Ascii or kind == .UTF8;
2123 while (i < str_length and i < array_size) : (i += 1) {
2124 const code_unit = stmt.getCodeUnit(i);
2125 init_list[i] = try transCreateCharLitNode(rp.c, narrow, code_unit);
2126 _ = try appendToken(rp.c, .Comma, ",");
2127 }
2128 while (i < array_size) : (i += 1) {
2129 init_list[i] = try transCreateNodeInt(rp.c, 0);
2130 _ = try appendToken(rp.c, .Comma, ",");
2131 }
2132 init_node.rtoken = try appendToken(rp.c, .RBrace, "}");
2133
2134 return &init_node.base;
2135}
2136
20522137fn cIsEnum(qt: clang.QualType) bool {
20532138 return qt.getCanonicalType().getTypeClass() == .Enum;
20542139}
......@@ -2343,6 +2428,18 @@ fn transCreateNodeArrayType(
23432428 return &node.base;
23442429}
23452430
2431fn transCreateEmptyArray(rp: RestorePoint, loc: clang.SourceLocation, ty: *const clang.Type) TransError!*ast.Node {
2432 const ty_node = try transCreateNodeArrayType(rp, loc, ty, 0);
2433 _ = try appendToken(rp.c, .LBrace, "{");
2434 const filler_init_node = try ast.Node.ArrayInitializer.alloc(rp.c.arena, 0);
2435 filler_init_node.* = .{
2436 .lhs = ty_node,
2437 .rtoken = try appendToken(rp.c, .RBrace, "}"),
2438 .list_len = 0,
2439 };
2440 return &filler_init_node.base;
2441}
2442
23462443fn transInitListExprArray(
23472444 rp: RestorePoint,
23482445 scope: *Scope,
......@@ -2360,6 +2457,10 @@ fn transInitListExprArray(
23602457 const all_count = size_ap_int.getLimitedValue(math.maxInt(usize));
23612458 const leftover_count = all_count - init_count;
23622459
2460 if (all_count == 0) {
2461 return transCreateEmptyArray(rp, loc, child_qt.getTypePtr());
2462 }
2463
23632464 var init_node: *ast.Node.ArrayInitializer = undefined;
23642465 var cat_tok: ast.TokenIndex = undefined;
23652466 if (init_count != 0) {
......@@ -2934,6 +3035,21 @@ fn transPredefinedExpr(rp: RestorePoint, scope: *Scope, expr: *const clang.Prede
29343035 return transStringLiteral(rp, scope, expr.getFunctionName(), used);
29353036}
29363037
3038fn transCreateCharLitNode(c: *Context, narrow: bool, val: u32) TransError!*ast.Node {
3039 const node = try c.arena.create(ast.Node.OneToken);
3040 node.* = .{
3041 .base = .{ .tag = .CharLiteral },
3042 .token = undefined,
3043 };
3044 if (narrow) {
3045 const val_array = [_]u8{@intCast(u8, val)};
3046 node.token = try appendTokenFmt(c, .CharLiteral, "'{}'", .{std.zig.fmtEscapes(&val_array)});
3047 } else {
3048 node.token = try appendTokenFmt(c, .CharLiteral, "'\\u{{{x}}}'", .{val});
3049 }
3050 return &node.base;
3051}
3052
29373053fn transCharLiteral(
29383054 rp: RestorePoint,
29393055 scope: *Scope,
......@@ -2943,33 +3059,14 @@ fn transCharLiteral(
29433059) TransError!*ast.Node {
29443060 const kind = stmt.getKind();
29453061 const val = stmt.getValue();
2946 const int_lit_node = switch (kind) {
2947 .Ascii, .UTF8 => blk: {
2948 if (kind == .Ascii) {
2949 // C has a somewhat obscure feature called multi-character character
2950 // constant
2951 if (val > 255)
2952 break :blk try transCreateNodeInt(rp.c, val);
2953 }
2954 const val_array = [_]u8 { @intCast(u8, val) };
2955 const token = try appendTokenFmt(rp.c, .CharLiteral, "'{}'", .{std.zig.fmtEscapes(&val_array)});
2956 const node = try rp.c.arena.create(ast.Node.OneToken);
2957 node.* = .{
2958 .base = .{ .tag = .CharLiteral },
2959 .token = token,
2960 };
2961 break :blk &node.base;
2962 },
2963 .Wide, .UTF16, .UTF32 => blk: {
2964 const token = try appendTokenFmt(rp.c, .CharLiteral, "'\\u{{{x}}}'", .{val});
2965 const node = try rp.c.arena.create(ast.Node.OneToken);
2966 node.* = .{
2967 .base = .{ .tag = .CharLiteral },
2968 .token = token,
2969 };
2970 break :blk &node.base;
2971 },
2972 };
3062 const narrow = kind == .Ascii or kind == .UTF8;
3063 // C has a somewhat obscure feature called multi-character character constant
3064 // e.g. 'abcd'
3065 const int_lit_node = if (kind == .Ascii and val > 255)
3066 try transCreateNodeInt(rp.c, val)
3067 else
3068 try transCreateCharLitNode(rp.c, narrow, val);
3069
29733070 if (suppress_as == .no_as) {
29743071 return maybeSuppressResult(rp, scope, result_used, int_lit_node);
29753072 }
......@@ -3891,6 +3988,38 @@ fn addTopLevelDecl(c: *Context, name: []const u8, decl_node: *ast.Node) !void {
38913988 _ = try c.global_scope.sym_table.put(name, decl_node);
38923989}
38933990
3991/// Translate a qual type for a variable with an initializer. The initializer
3992/// only matters for incomplete arrays, since the size of the array is determined
3993/// by the size of the initializer
3994fn transQualTypeInitialized(
3995 rp: RestorePoint,
3996 qt: clang.QualType,
3997 decl_init: *const clang.Expr,
3998 source_loc: clang.SourceLocation,
3999) TypeError!*ast.Node {
4000 const ty = qt.getTypePtr();
4001 if (ty.getTypeClass() == .IncompleteArray) {
4002 const incomplete_array_ty = @ptrCast(*const clang.IncompleteArrayType, ty);
4003 const elem_ty = incomplete_array_ty.getElementType().getTypePtr();
4004
4005 switch (decl_init.getStmtClass()) {
4006 .StringLiteralClass => {
4007 const string_lit = @ptrCast(*const clang.StringLiteral, decl_init);
4008 const string_lit_size = string_lit.getLength() + 1; // +1 for null terminator
4009 const array_size = @intCast(usize, string_lit_size);
4010 return transCreateNodeArrayType(rp, source_loc, elem_ty, array_size);
4011 },
4012 .InitListExprClass => {
4013 const init_expr = @ptrCast(*const clang.InitListExpr, decl_init);
4014 const size = init_expr.getNumInits();
4015 return transCreateNodeArrayType(rp, source_loc, elem_ty, size);
4016 },
4017 else => {},
4018 }
4019 }
4020 return transQualType(rp, qt, source_loc);
4021}
4022
38944023fn transQualType(rp: RestorePoint, qt: clang.QualType, source_loc: clang.SourceLocation) TypeError!*ast.Node {
38954024 return transType(rp, qt.getTypePtr(), source_loc);
38964025}
src/zig_clang.cpp+15
......@@ -2504,6 +2504,21 @@ enum ZigClangStringLiteral_StringKind ZigClangStringLiteral_getKind(const struct
25042504 return (ZigClangStringLiteral_StringKind)casted->getKind();
25052505}
25062506
2507uint32_t ZigClangStringLiteral_getCodeUnit(const struct ZigClangStringLiteral *self, size_t i) {
2508 auto casted = reinterpret_cast<const clang::StringLiteral *>(self);
2509 return casted->getCodeUnit(i);
2510}
2511
2512unsigned ZigClangStringLiteral_getLength(const struct ZigClangStringLiteral *self) {
2513 auto casted = reinterpret_cast<const clang::StringLiteral *>(self);
2514 return casted->getLength();
2515}
2516
2517unsigned ZigClangStringLiteral_getCharByteWidth(const struct ZigClangStringLiteral *self) {
2518 auto casted = reinterpret_cast<const clang::StringLiteral *>(self);
2519 return casted->getCharByteWidth();
2520}
2521
25072522const char *ZigClangStringLiteral_getString_bytes_begin_size(const struct ZigClangStringLiteral *self, size_t *len) {
25082523 auto casted = reinterpret_cast<const clang::StringLiteral *>(self);
25092524 llvm::StringRef str_ref = casted->getString();
src/zig_clang.h+4
......@@ -1126,6 +1126,10 @@ ZIG_EXTERN_C unsigned ZigClangAPFloat_convertToHexString(const struct ZigClangAP
11261126ZIG_EXTERN_C double ZigClangFloatingLiteral_getValueAsApproximateDouble(const ZigClangFloatingLiteral *self);
11271127
11281128ZIG_EXTERN_C enum ZigClangStringLiteral_StringKind ZigClangStringLiteral_getKind(const struct ZigClangStringLiteral *self);
1129ZIG_EXTERN_C uint32_t ZigClangStringLiteral_getCodeUnit(const struct ZigClangStringLiteral *self, size_t i);
1130ZIG_EXTERN_C unsigned ZigClangStringLiteral_getLength(const struct ZigClangStringLiteral *self);
1131ZIG_EXTERN_C unsigned ZigClangStringLiteral_getCharByteWidth(const struct ZigClangStringLiteral *self);
1132
11291133ZIG_EXTERN_C const char *ZigClangStringLiteral_getString_bytes_begin_size(const struct ZigClangStringLiteral *self,
11301134 size_t *len);
11311135
test/run_translated_c.zig+48
......@@ -746,4 +746,52 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
746746 \\ return 0;
747747 \\}
748748 , "1 2" ++ nl);
749
750 cases.add("multi-character character constant",
751 \\#include <stdlib.h>
752 \\int main(void) {
753 \\ int foo = 'abcd';
754 \\ switch (foo) {
755 \\ case 'abcd': break;
756 \\ default: abort();
757 \\ }
758 \\ return 0;
759 \\}
760 , "");
761
762 cases.add("Array initializers (string literals, incomplete arrays)",
763 \\#include <stdlib.h>
764 \\#include <string.h>
765 \\extern int foo[];
766 \\int global_arr[] = {1, 2, 3};
767 \\char global_string[] = "hello";
768 \\int main(int argc, char *argv[]) {
769 \\ if (global_arr[2] != 3) abort();
770 \\ if (strlen(global_string) != 5) abort();
771 \\ const char *const_str = "hello";
772 \\ if (strcmp(const_str, "hello") != 0) abort();
773 \\ char empty_str[] = "";
774 \\ if (strlen(empty_str) != 0) abort();
775 \\ char hello[] = "hello";
776 \\ if (strlen(hello) != 5 || sizeof(hello) != 6) abort();
777 \\ int empty[] = {};
778 \\ if (sizeof(empty) != 0) abort();
779 \\ int bar[] = {42};
780 \\ if (bar[0] != 42) abort();
781 \\ bar[0] = 43;
782 \\ if (bar[0] != 43) abort();
783 \\ int baz[] = {1, [42] = 123, 456};
784 \\ if (baz[42] != 123 || baz[43] != 456) abort();
785 \\ if (sizeof(baz) != sizeof(int) * 44) abort();
786 \\ const char *const names[] = {"first", "second", "third"};
787 \\ if (strcmp(names[2], "third") != 0) abort();
788 \\ char catted_str[] = "abc" "def";
789 \\ if (strlen(catted_str) != 6 || sizeof(catted_str) != 7) abort();
790 \\ char catted_trunc_str[2] = "abc" "def";
791 \\ if (sizeof(catted_trunc_str) != 2 || catted_trunc_str[0] != 'a' || catted_trunc_str[1] != 'b') abort();
792 \\ char big_array_utf8lit[10] = "💯";
793 \\ if (strcmp(big_array_utf8lit, "💯") != 0 || big_array_utf8lit[9] != 0) abort();
794 \\ return 0;
795 \\}
796 , "");
749797}
test/translate_c.zig+32-4
......@@ -539,7 +539,14 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
539539 \\ static const char v2[] = "2.2.2";
540540 \\}
541541 , &[_][]const u8{
542 \\const v2: [*c]const u8 = "2.2.2";
542 \\const v2: [6]u8 = [6]u8{
543 \\ '2',
544 \\ '.',
545 \\ '2',
546 \\ '.',
547 \\ '2',
548 \\ 0,
549 \\};
543550 \\pub export fn foo() void {
544551 \\ _ = v2;
545552 \\}
......@@ -1395,9 +1402,30 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
13951402 \\static char arr1[] = "hello";
13961403 \\char arr2[] = "hello";
13971404 , &[_][]const u8{
1398 \\pub export var arr0: [*c]u8 = "hello";
1399 \\pub var arr1: [*c]u8 = "hello";
1400 \\pub export var arr2: [*c]u8 = "hello";
1405 \\pub export var arr0: [6]u8 = [6]u8{
1406 \\ 'h',
1407 \\ 'e',
1408 \\ 'l',
1409 \\ 'l',
1410 \\ 'o',
1411 \\ 0,
1412 \\};
1413 \\pub var arr1: [6]u8 = [6]u8{
1414 \\ 'h',
1415 \\ 'e',
1416 \\ 'l',
1417 \\ 'l',
1418 \\ 'o',
1419 \\ 0,
1420 \\};
1421 \\pub export var arr2: [6]u8 = [6]u8{
1422 \\ 'h',
1423 \\ 'e',
1424 \\ 'l',
1425 \\ 'l',
1426 \\ 'o',
1427 \\ 0,
1428 \\};
14011429 });
14021430
14031431 cases.add("array initializer expr",