diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 2eef92a201..f1249afa97 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -582,7 +582,9 @@ pub fn formatAsciiChar( comptime Errors: type, output: fn (@TypeOf(context), []const u8) Errors!void, ) Errors!void { - return output(context, @as(*const [1]u8, &c)[0..]); + if (std.ascii.isPrint(c)) + return output(context, @as(*const [1]u8, &c)[0..]); + return format(context, Errors, output, "\\x{x:0<2}", .{c}); } pub fn formatBuf( diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index e27e62c094..349ae914c4 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -1431,13 +1431,13 @@ pub const Node = struct { AssignBitShiftRight, AssignBitXor, AssignDiv, - AssignMinus, - AssignMinusWrap, + AssignSub, + AssignSubWrap, AssignMod, - AssignPlus, - AssignPlusWrap, - AssignTimes, - AssignTimesWarp, + AssignAdd, + AssignAddWrap, + AssignMul, + AssignMulWrap, BangEqual, BitAnd, BitOr, @@ -1456,8 +1456,8 @@ pub const Node = struct { LessThan, MergeErrorSets, Mod, - Mult, - MultWrap, + Mul, + MulWrap, Period, Range, Sub, @@ -1490,13 +1490,13 @@ pub const Node = struct { Op.AssignBitShiftRight, Op.AssignBitXor, Op.AssignDiv, - Op.AssignMinus, - Op.AssignMinusWrap, + Op.AssignSub, + Op.AssignSubWrap, Op.AssignMod, - Op.AssignPlus, - Op.AssignPlusWrap, - Op.AssignTimes, - Op.AssignTimesWarp, + Op.AssignAdd, + Op.AssignAddWrap, + Op.AssignMul, + Op.AssignMulWrap, Op.BangEqual, Op.BitAnd, Op.BitOr, @@ -1514,8 +1514,8 @@ pub const Node = struct { Op.LessThan, Op.MergeErrorSets, Op.Mod, - Op.Mult, - Op.MultWrap, + Op.Mul, + Op.MulWrap, Op.Period, Op.Range, Op.Sub, diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 5b168be2e1..14d5a0a400 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -1981,19 +1981,19 @@ fn parseAssignOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { const token = nextToken(it); const op = switch (token.ptr.id) { - .AsteriskEqual => Op{ .AssignTimes = {} }, + .AsteriskEqual => Op{ .AssignMul = {} }, .SlashEqual => Op{ .AssignDiv = {} }, .PercentEqual => Op{ .AssignMod = {} }, - .PlusEqual => Op{ .AssignPlus = {} }, - .MinusEqual => Op{ .AssignMinus = {} }, + .PlusEqual => Op{ .AssignAdd = {} }, + .MinusEqual => Op{ .AssignSub = {} }, .AngleBracketAngleBracketLeftEqual => Op{ .AssignBitShiftLeft = {} }, .AngleBracketAngleBracketRightEqual => Op{ .AssignBitShiftRight = {} }, .AmpersandEqual => Op{ .AssignBitAnd = {} }, .CaretEqual => Op{ .AssignBitXor = {} }, .PipeEqual => Op{ .AssignBitOr = {} }, - .AsteriskPercentEqual => Op{ .AssignTimesWarp = {} }, - .PlusPercentEqual => Op{ .AssignPlusWrap = {} }, - .MinusPercentEqual => Op{ .AssignMinusWrap = {} }, + .AsteriskPercentEqual => Op{ .AssignMulWrap = {} }, + .PlusPercentEqual => Op{ .AssignAddWrap = {} }, + .MinusPercentEqual => Op{ .AssignSubWrap = {} }, .Equal => Op{ .Assign = {} }, else => { putBackToken(it, token.index); @@ -2120,11 +2120,11 @@ fn parseMultiplyOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { const token = nextToken(it); const op = switch (token.ptr.id) { .PipePipe => ops{ .BoolOr = {} }, - .Asterisk => ops{ .Mult = {} }, + .Asterisk => ops{ .Mul = {} }, .Slash => ops{ .Div = {} }, .Percent => ops{ .Mod = {} }, .AsteriskAsterisk => ops{ .ArrayMult = {} }, - .AsteriskPercent => ops{ .MultWrap = {} }, + .AsteriskPercent => ops{ .MulWrap = {} }, else => { putBackToken(it, token.index); return null; diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 263f45a88b..d65d9bfc57 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -1635,7 +1635,7 @@ fn renderExpression( .If => { const if_node = @fieldParentPtr(ast.Node.If, "base", base); - const lparen = tree.prevToken(if_node.condition.firstToken()); + const lparen = tree.nextToken(if_node.if_token); const rparen = tree.nextToken(if_node.condition.lastToken()); try renderToken(tree, stream, if_node.if_token, indent, start_col, Space.Space); // if diff --git a/src-self-hosted/c_tokenizer.zig b/src-self-hosted/c_tokenizer.zig index 9715661cd3..cc862a0e67 100644 --- a/src-self-hosted/c_tokenizer.zig +++ b/src-self-hosted/c_tokenizer.zig @@ -27,6 +27,10 @@ pub const CToken = struct { Lt, Comma, Fn, + Arrow, + LBrace, + RBrace, + Pipe, }; pub const NumLitSuffix = enum { @@ -71,69 +75,130 @@ fn zigifyEscapeSequences(allocator: *std.mem.Allocator, tok: CToken) !CToken { } } else return tok; var bytes = try allocator.alloc(u8, tok.bytes.len * 2); - var escape = false; + var state: enum { + Start, + Escape, + Hex, + Octal, + } = .Start; var i: usize = 0; + var count: u8 = 0; + var num: u8 = 0; for (tok.bytes) |c| { - if (escape) { - switch (c) { - 'n', 'r', 't', '\\', '\'', '\"', 'x' => { - bytes[i] = c; - }, - 'a' => { - bytes[i] = 'x'; - i += 1; - bytes[i] = '0'; - i += 1; - bytes[i] = '7'; - }, - 'b' => { - bytes[i] = 'x'; - i += 1; - bytes[i] = '0'; - i += 1; - bytes[i] = '8'; - }, - 'f' => { - bytes[i] = 'x'; - i += 1; - bytes[i] = '0'; - i += 1; - bytes[i] = 'C'; - }, - 'v' => { - bytes[i] = 'x'; - i += 1; - bytes[i] = '0'; - i += 1; - bytes[i] = 'B'; - }, - '?' => { - i -= 1; - bytes[i] = '?'; - }, - 'u', 'U' => { - // TODO unicode escape sequences - return error.TokenizingFailed; - }, - '0'...'7' => { - // TODO octal escape sequences - return error.TokenizingFailed; - }, - else => { - // unknown escape sequence - return error.TokenizingFailed; - }, - } - i += 1; - escape = false; - } else { - if (c == '\\') { - escape = true; - } - bytes[i] = c; - i += 1; + switch (state) { + .Escape => { + switch (c) { + 'n', 'r', 't', '\\', '\'', '\"' => { + bytes[i] = c; + }, + '0'...'7' => { + count += 1; + num += c - '0'; + state = .Octal; + bytes[i] = 'x'; + }, + 'x' => { + state = .Hex; + bytes[i] = 'x'; + }, + 'a' => { + bytes[i] = 'x'; + i += 1; + bytes[i] = '0'; + i += 1; + bytes[i] = '7'; + }, + 'b' => { + bytes[i] = 'x'; + i += 1; + bytes[i] = '0'; + i += 1; + bytes[i] = '8'; + }, + 'f' => { + bytes[i] = 'x'; + i += 1; + bytes[i] = '0'; + i += 1; + bytes[i] = 'C'; + }, + 'v' => { + bytes[i] = 'x'; + i += 1; + bytes[i] = '0'; + i += 1; + bytes[i] = 'B'; + }, + '?' => { + i -= 1; + bytes[i] = '?'; + }, + 'u', 'U' => { + // TODO unicode escape sequences + return error.TokenizingFailed; + }, + else => { + // unknown escape sequence + return error.TokenizingFailed; + }, + } + i += 1; + if (state == .Escape) + state = .Start; + }, + .Start => { + if (c == '\\') { + state = .Escape; + } + bytes[i] = c; + i += 1; + }, + .Hex => { + switch (c) { + '0'...'9' => { + num = std.math.mul(u8, num, 16) catch return error.TokenizingFailed; + num += c - '0'; + }, + 'a'...'f' => { + num = std.math.mul(u8, num, 16) catch return error.TokenizingFailed; + num += c - 'a' + 10; + }, + 'A'...'F' => { + num = std.math.mul(u8, num, 16) catch return error.TokenizingFailed; + num += c - 'A' + 10; + }, + else => { + i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{.fill = '0', .width = 2}); + num = 0; + if (c == '\\') + state = .Escape + else + state = .Start; + bytes[i] = c; + i += 1; + }, + } + }, + .Octal => { + switch (c) { + '0'...'7' => { + count += 1; + num = std.math.mul(u8, num, 8) catch return error.TokenizingFailed; + num += c - '0'; + if (count < 3) + continue; + }, + else => {}, + } + i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{.fill = '0', .width = 2}); + state = .Start; + count = 0; + num = 0; + }, } } + if (state == .Hex or state == .Octal) + i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{.fill = '0', .width = 2}); return CToken{ .id = tok.id, .bytes = bytes[0..i], @@ -164,6 +229,8 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { NumLitIntSuffixL, NumLitIntSuffixLL, NumLitIntSuffixUL, + Minus, + Done, } = .Start; var result = CToken{ @@ -178,9 +245,6 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { const c = chars[i.*]; if (c == 0) { switch (state) { - .Start => { - return result; - }, .Identifier, .Decimal, .Hex, @@ -193,6 +257,9 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { result.bytes = chars[begin_index..i.*]; return result; }, + .Start, + .Minus, + .Done, .NumLitIntSuffixU, .NumLitIntSuffixL, .NumLitIntSuffixUL, @@ -212,7 +279,6 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { => return error.TokenizingFailed, } } - i.* += 1; switch (state) { .Start => { switch (c) { @@ -220,12 +286,12 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { '\'' => { state = .CharLit; result.id = .CharLit; - begin_index = i.* - 1; + begin_index = i.*; }, '\"' => { state = .String; result.id = .StrLit; - begin_index = i.* - 1; + begin_index = i.*; }, '/' => { state = .OpenComment; @@ -239,21 +305,21 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { 'a'...'z', 'A'...'Z', '_' => { state = .Identifier; result.id = .Identifier; - begin_index = i.* - 1; + begin_index = i.*; }, '1'...'9' => { state = .Decimal; result.id = .NumLitInt; - begin_index = i.* - 1; + begin_index = i.*; }, '0' => { state = .GotZero; result.id = .NumLitInt; - begin_index = i.* - 1; + begin_index = i.*; }, '.' => { result.id = .Dot; - return result; + state = .Done; }, '<' => { result.id = .Lt; @@ -261,40 +327,64 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { }, '(' => { result.id = .LParen; - return result; + state = .Done; }, ')' => { result.id = .RParen; - return result; + state = .Done; }, '*' => { result.id = .Asterisk; - return result; + state = .Done; }, '-' => { + state = .Minus; result.id = .Minus; - return result; }, '!' => { result.id = .Bang; - return result; + state = .Done; }, '~' => { result.id = .Tilde; - return result; + state = .Done; }, ',' => { result.id = .Comma; - return result; + state = .Done; + }, + '[' => { + result.id = .LBrace; + state = .Done; + }, + ']' => { + result.id = .RBrace; + state = .Done; + }, + '|' => { + result.id = .Pipe; + state = .Done; }, else => return error.TokenizingFailed, } }, + .Done => return result, + .Minus => { + switch (c) { + '>' => { + result.id = .Arrow; + state = .Done; + }, + else => { + return result; + }, + } + }, .GotLt => { switch (c) { '<' => { result.id = .Shl; - return result; + state = .Done; }, else => { return result; @@ -310,19 +400,16 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { 'f', 'F', => { - i.* -= 1; result.num_lit_suffix = .F; result.bytes = chars[begin_index..i.*]; - return result; + state = .Done; }, 'l', 'L' => { - i.* -= 1; result.num_lit_suffix = .L; result.bytes = chars[begin_index..i.*]; - return result; + state = .Done; }, else => { - i.* -= 1; result.bytes = chars[begin_index..i.*]; return result; }, @@ -352,16 +439,15 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { '0'...'9' => {}, 'f', 'F' => { result.num_lit_suffix = .F; - result.bytes = chars[begin_index .. i.* - 1]; - return result; + result.bytes = chars[begin_index..i.*]; + state = .Done; }, 'l', 'L' => { result.num_lit_suffix = .L; - result.bytes = chars[begin_index .. i.* - 1]; - return result; + result.bytes = chars[begin_index..i.*]; + state = .Done; }, else => { - i.* -= 1; result.bytes = chars[begin_index..i.*]; return result; }, @@ -374,19 +460,18 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { 'u', 'U' => { state = .NumLitIntSuffixU; result.num_lit_suffix = .U; - result.bytes = chars[begin_index .. i.* - 1]; + result.bytes = chars[begin_index..i.*]; }, 'l', 'L' => { state = .NumLitIntSuffixL; result.num_lit_suffix = .L; - result.bytes = chars[begin_index .. i.* - 1]; + result.bytes = chars[begin_index..i.*]; }, '.' => { result.id = .NumLitFloat; state = .Float; }, else => { - i.* -= 1; result.bytes = chars[begin_index..i.*]; return result; }, @@ -407,12 +492,12 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { 'u', 'U' => { state = .NumLitIntSuffixU; result.num_lit_suffix = .U; - result.bytes = chars[begin_index .. i.* - 1]; + result.bytes = chars[begin_index..i.*]; }, 'l', 'L' => { state = .NumLitIntSuffixL; result.num_lit_suffix = .L; - result.bytes = chars[begin_index .. i.* - 1]; + result.bytes = chars[begin_index..i.*]; }, else => { i.* -= 1; @@ -425,7 +510,6 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { '0'...'7' => {}, '8', '9' => return error.TokenizingFailed, else => { - i.* -= 1; result.bytes = chars[begin_index..i.*]; return result; }, @@ -438,16 +522,15 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { // marks the number literal as unsigned state = .NumLitIntSuffixU; result.num_lit_suffix = .U; - result.bytes = chars[begin_index .. i.* - 1]; + result.bytes = chars[begin_index..i.*]; }, 'l', 'L' => { // marks the number literal as long state = .NumLitIntSuffixL; result.num_lit_suffix = .L; - result.bytes = chars[begin_index .. i.* - 1]; + result.bytes = chars[begin_index..i.*]; }, else => { - i.* -= 1; result.bytes = chars[begin_index..i.*]; return result; }, @@ -461,16 +544,15 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { // marks the number literal as unsigned state = .NumLitIntSuffixU; result.num_lit_suffix = .U; - result.bytes = chars[begin_index .. i.* - 1]; + result.bytes = chars[begin_index..i.*]; }, 'l', 'L' => { // marks the number literal as long state = .NumLitIntSuffixL; result.num_lit_suffix = .L; - result.bytes = chars[begin_index .. i.* - 1]; + result.bytes = chars[begin_index..i.*]; }, else => { - i.* -= 1; result.bytes = chars[begin_index..i.*]; return result; }, @@ -483,7 +565,6 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { state = .NumLitIntSuffixUL; }, else => { - i.* -= 1; return result; }, } @@ -496,10 +577,9 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { }, 'u', 'U' => { result.num_lit_suffix = .LU; - return result; + state = .Done; }, else => { - i.* -= 1; return result; }, } @@ -508,10 +588,9 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { switch (c) { 'u', 'U' => { result.num_lit_suffix = .LLU; - return result; + state = .Done; }, else => { - i.* -= 1; return result; }, } @@ -520,10 +599,9 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { switch (c) { 'l', 'L' => { result.num_lit_suffix = .LLU; - return result; + state = .Done; }, else => { - i.* -= 1; return result; }, } @@ -532,17 +610,16 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { switch (c) { '_', 'a'...'z', 'A'...'Z', '0'...'9' => {}, else => { - i.* -= 1; result.bytes = chars[begin_index..i.*]; return result; }, } }, - .String => { // TODO char escapes + .String => { switch (c) { '\"' => { - result.bytes = chars[begin_index..i.*]; - return result; + result.bytes = chars[begin_index .. i.* + 1]; + state = .Done; }, else => {}, } @@ -550,8 +627,8 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { .CharLit => { switch (c) { '\'' => { - result.bytes = chars[begin_index..i.*]; - return result; + result.bytes = chars[begin_index .. i.* + 1]; + state = .Done; }, else => {}, } @@ -566,7 +643,7 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { }, else => { result.id = .Slash; - return result; + state = .Done; }, } }, @@ -598,6 +675,7 @@ fn next(chars: [*:0]const u8, i: *usize) !CToken { } }, } + i.* += 1; } unreachable; } @@ -645,7 +723,7 @@ test "tokenize macro" { expect(it.next() == null); tl.shrink(0); - const src5 = "FOO 0l"; + const src5 = "FOO 0ull"; try tokenizeCMacro(&tl, src5); it = tl.iterator(0); expect(it.next().?.id == .Identifier); @@ -654,3 +732,25 @@ test "tokenize macro" { expect(it.next() == null); tl.shrink(0); } + +test "escape sequences" { + var buf: [1024]u8 = undefined; + var alloc = std.heap.FixedBufferAllocator.init(buf[0..]); + const a = &alloc.allocator; + expect(std.mem.eql(u8, (try zigifyEscapeSequences(a, .{ + .id = .StrLit, + .bytes = "\\x0077", + })).bytes, "\\x77")); + expect(std.mem.eql(u8, (try zigifyEscapeSequences(a, .{ + .id = .StrLit, + .bytes = "\\24500", + })).bytes, "\\xa500")); + expect(std.mem.eql(u8, (try zigifyEscapeSequences(a, .{ + .id = .StrLit, + .bytes = "\\x0077 abc", + })).bytes, "\\x77 abc")); + expect(std.mem.eql(u8, (try zigifyEscapeSequences(a, .{ + .id = .StrLit, + .bytes = "\\045abc", + })).bytes, "\\x25abc")); +} diff --git a/src-self-hosted/clang.zig b/src-self-hosted/clang.zig index b9435f96f4..87970168e8 100644 --- a/src-self-hosted/clang.zig +++ b/src-self-hosted/clang.zig @@ -76,6 +76,10 @@ pub const struct_ZigClangFunctionType = @OpaqueType(); pub const struct_ZigClangPredefinedExpr = @OpaqueType(); pub const struct_ZigClangInitListExpr = @OpaqueType(); pub const ZigClangPreprocessingRecord = @OpaqueType(); +pub const ZigClangFloatingLiteral = @OpaqueType(); +pub const ZigClangConstantExpr = @OpaqueType(); +pub const ZigClangCharacterLiteral = @OpaqueType(); +pub const ZigClangStmtExpr = @OpaqueType(); pub const ZigClangBO = extern enum { PtrMemD, @@ -710,6 +714,14 @@ pub const ZigClangStringLiteral_StringKind = extern enum { UTF32, }; +pub const ZigClangCharacterLiteral_CharacterKind = extern enum { + Ascii, + Wide, + UTF8, + UTF16, + UTF32, +}; + pub const ZigClangRecordDecl_field_iterator = extern struct { opaque: *c_void, }; @@ -730,6 +742,11 @@ pub const ZigClangPreprocessedEntity_EntityKind = extern enum { InclusionDirectiveKind, }; +pub const ZigClangExpr_ConstExprUsage = extern enum { + EvaluateForCodeGen, + EvaluateForMangling, +}; + pub extern fn ZigClangSourceManager_getSpellingLoc(self: ?*const struct_ZigClangSourceManager, Loc: struct_ZigClangSourceLocation) struct_ZigClangSourceLocation; pub extern fn ZigClangSourceManager_getFilename(self: *const struct_ZigClangSourceManager, SpellingLoc: struct_ZigClangSourceLocation) ?[*:0]const u8; pub extern fn ZigClangSourceManager_getSpellingLineNumber(self: ?*const struct_ZigClangSourceManager, Loc: struct_ZigClangSourceLocation) c_uint; @@ -744,6 +761,8 @@ pub extern fn ZigClangEnumType_getDecl(record_ty: ?*const struct_ZigClangEnumTyp pub extern fn ZigClangRecordDecl_getCanonicalDecl(record_decl: ?*const struct_ZigClangRecordDecl) ?*const struct_ZigClangTagDecl; pub extern fn ZigClangEnumDecl_getCanonicalDecl(self: ?*const struct_ZigClangEnumDecl) ?*const struct_ZigClangTagDecl; pub extern fn ZigClangTypedefNameDecl_getCanonicalDecl(self: ?*const struct_ZigClangTypedefNameDecl) ?*const struct_ZigClangTypedefNameDecl; +pub extern fn ZigClangFunctionDecl_getCanonicalDecl(self: ?*const struct_ZigClangFunctionDecl) ?*const struct_ZigClangFunctionDecl; +pub extern fn ZigClangVarDecl_getCanonicalDecl(self: ?*const struct_ZigClangVarDecl) ?*const struct_ZigClangVarDecl; pub extern fn ZigClangRecordDecl_getDefinition(self: ?*const struct_ZigClangRecordDecl) ?*const struct_ZigClangRecordDecl; pub extern fn ZigClangEnumDecl_getDefinition(self: ?*const struct_ZigClangEnumDecl) ?*const struct_ZigClangEnumDecl; pub extern fn ZigClangRecordDecl_getLocation(self: ?*const struct_ZigClangRecordDecl) struct_ZigClangSourceLocation; @@ -791,16 +810,16 @@ pub extern fn ZigClangInitListExpr_getInit(self: ?*const struct_ZigClangInitList pub extern fn ZigClangInitListExpr_getArrayFiller(self: ?*const struct_ZigClangInitListExpr) *const ZigClangExpr; pub extern fn ZigClangInitListExpr_getNumInits(self: ?*const struct_ZigClangInitListExpr) c_uint; pub extern fn ZigClangAPValue_getKind(self: ?*const struct_ZigClangAPValue) ZigClangAPValueKind; -pub extern fn ZigClangAPValue_getInt(self: ?*const struct_ZigClangAPValue) ?*const struct_ZigClangAPSInt; +pub extern fn ZigClangAPValue_getInt(self: ?*const struct_ZigClangAPValue) *const struct_ZigClangAPSInt; pub extern fn ZigClangAPValue_getArrayInitializedElts(self: ?*const struct_ZigClangAPValue) c_uint; pub extern fn ZigClangAPValue_getArraySize(self: ?*const struct_ZigClangAPValue) c_uint; pub extern fn ZigClangAPValue_getLValueBase(self: ?*const struct_ZigClangAPValue) struct_ZigClangAPValueLValueBase; -pub extern fn ZigClangAPSInt_isSigned(self: ?*const struct_ZigClangAPSInt) bool; -pub extern fn ZigClangAPSInt_isNegative(self: ?*const struct_ZigClangAPSInt) bool; -pub extern fn ZigClangAPSInt_negate(self: ?*const struct_ZigClangAPSInt) ?*const struct_ZigClangAPSInt; -pub extern fn ZigClangAPSInt_free(self: ?*const struct_ZigClangAPSInt) void; -pub extern fn ZigClangAPSInt_getRawData(self: ?*const struct_ZigClangAPSInt) [*:0]const u64; -pub extern fn ZigClangAPSInt_getNumWords(self: ?*const struct_ZigClangAPSInt) c_uint; +pub extern fn ZigClangAPSInt_isSigned(self: *const struct_ZigClangAPSInt) bool; +pub extern fn ZigClangAPSInt_isNegative(self: *const struct_ZigClangAPSInt) bool; +pub extern fn ZigClangAPSInt_negate(self: *const struct_ZigClangAPSInt) *const struct_ZigClangAPSInt; +pub extern fn ZigClangAPSInt_free(self: *const struct_ZigClangAPSInt) void; +pub extern fn ZigClangAPSInt_getRawData(self: *const struct_ZigClangAPSInt) [*:0]const u64; +pub extern fn ZigClangAPSInt_getNumWords(self: *const struct_ZigClangAPSInt) c_uint; pub extern fn ZigClangAPInt_getLimitedValue(self: *const struct_ZigClangAPInt, limit: u64) u64; pub extern fn ZigClangAPValueLValueBase_dyn_cast_Expr(self: struct_ZigClangAPValueLValueBase) ?*const struct_ZigClangExpr; @@ -822,6 +841,7 @@ pub extern fn ZigClangFunctionType_getReturnType(self: *const ZigClangFunctionTy pub extern fn ZigClangFunctionProtoType_isVariadic(self: *const struct_ZigClangFunctionProtoType) bool; pub extern fn ZigClangFunctionProtoType_getNumParams(self: *const struct_ZigClangFunctionProtoType) c_uint; pub extern fn ZigClangFunctionProtoType_getParamType(self: *const struct_ZigClangFunctionProtoType, i: c_uint) ZigClangQualType; +pub extern fn ZigClangFunctionProtoType_getReturnType(self: *const ZigClangFunctionProtoType) ZigClangQualType; pub const ZigClangSourceLocation = struct_ZigClangSourceLocation; pub const ZigClangQualType = struct_ZigClangQualType; @@ -976,6 +996,7 @@ pub extern fn ZigClangIncompleteArrayType_getElementType(*const ZigClangIncomple pub extern fn ZigClangConstantArrayType_getElementType(self: *const struct_ZigClangConstantArrayType) ZigClangQualType; pub extern fn ZigClangConstantArrayType_getSize(self: *const struct_ZigClangConstantArrayType) *const struct_ZigClangAPInt; pub extern fn ZigClangDeclRefExpr_getDecl(*const ZigClangDeclRefExpr) *const ZigClangValueDecl; +pub extern fn ZigClangDeclRefExpr_getFoundDecl(*const ZigClangDeclRefExpr) *const ZigClangNamedDecl; pub extern fn ZigClangParenType_getInnerType(*const ZigClangParenType) ZigClangQualType; @@ -1036,3 +1057,74 @@ pub extern fn ZigClangPreprocessedEntity_getKind(*const ZigClangPreprocessedEnti pub extern fn ZigClangMacroDefinitionRecord_getName_getNameStart(*const ZigClangMacroDefinitionRecord) [*:0]const u8; pub extern fn ZigClangMacroDefinitionRecord_getSourceRange_getBegin(*const ZigClangMacroDefinitionRecord) ZigClangSourceLocation; pub extern fn ZigClangMacroDefinitionRecord_getSourceRange_getEnd(*const ZigClangMacroDefinitionRecord) ZigClangSourceLocation; + +pub extern fn ZigClangIfStmt_getThen(*const ZigClangIfStmt) *const ZigClangStmt; +pub extern fn ZigClangIfStmt_getElse(*const ZigClangIfStmt) ?*const ZigClangStmt; +pub extern fn ZigClangIfStmt_getCond(*const ZigClangIfStmt) *const ZigClangStmt; + +pub extern fn ZigClangWhileStmt_getCond(*const ZigClangWhileStmt) *const ZigClangExpr; +pub extern fn ZigClangWhileStmt_getBody(*const ZigClangWhileStmt) *const ZigClangStmt; + +pub extern fn ZigClangDoStmt_getCond(*const ZigClangDoStmt) *const ZigClangExpr; +pub extern fn ZigClangDoStmt_getBody(*const ZigClangDoStmt) *const ZigClangStmt; + +pub extern fn ZigClangForStmt_getInit(*const ZigClangForStmt) ?*const ZigClangStmt; +pub extern fn ZigClangForStmt_getCond(*const ZigClangForStmt) ?*const ZigClangExpr; +pub extern fn ZigClangForStmt_getInc(*const ZigClangForStmt) ?*const ZigClangExpr; +pub extern fn ZigClangForStmt_getBody(*const ZigClangForStmt) *const ZigClangStmt; + +pub extern fn ZigClangAPFloat_toString(self: *const ZigClangAPFloat, precision: c_uint, maxPadding: c_uint, truncateZero: bool) [*:0]const u8; +pub extern fn ZigClangAPFloat_getValueAsApproximateDouble(*const ZigClangFloatingLiteral) f64; + +pub extern fn ZigClangConditionalOperator_getCond(*const ZigClangConditionalOperator) *const ZigClangExpr; +pub extern fn ZigClangConditionalOperator_getTrueExpr(*const ZigClangConditionalOperator) *const ZigClangExpr; +pub extern fn ZigClangConditionalOperator_getFalseExpr(*const ZigClangConditionalOperator) *const ZigClangExpr; + +pub extern fn ZigClangSwitchStmt_getConditionVariableDeclStmt(*const ZigClangSwitchStmt) ?*const ZigClangDeclStmt; +pub extern fn ZigClangSwitchStmt_getCond(*const ZigClangSwitchStmt) *const ZigClangExpr; +pub extern fn ZigClangSwitchStmt_getBody(*const ZigClangSwitchStmt) *const ZigClangStmt; +pub extern fn ZigClangSwitchStmt_isAllEnumCasesCovered(*const ZigClangSwitchStmt) bool; + +pub extern fn ZigClangCaseStmt_getLHS(*const ZigClangCaseStmt) *const ZigClangExpr; +pub extern fn ZigClangCaseStmt_getRHS(*const ZigClangCaseStmt) ?*const ZigClangExpr; +pub extern fn ZigClangCaseStmt_getBeginLoc(*const ZigClangCaseStmt) ZigClangSourceLocation; +pub extern fn ZigClangCaseStmt_getSubStmt(*const ZigClangCaseStmt) *const ZigClangStmt; + +pub extern fn ZigClangDefaultStmt_getSubStmt(*const ZigClangDefaultStmt) *const ZigClangStmt; + +pub extern fn ZigClangExpr_EvaluateAsConstantExpr(*const ZigClangExpr, *ZigClangExprEvalResult, ZigClangExpr_ConstExprUsage, *const ZigClangASTContext) bool; + +pub extern fn ZigClangPredefinedExpr_getFunctionName(*const ZigClangPredefinedExpr) *const ZigClangStringLiteral; + +pub extern fn ZigClangCharacterLiteral_getBeginLoc(*const ZigClangCharacterLiteral) ZigClangSourceLocation; +pub extern fn ZigClangCharacterLiteral_getKind(*const ZigClangCharacterLiteral) ZigClangCharacterLiteral_CharacterKind; +pub extern fn ZigClangCharacterLiteral_getValue(*const ZigClangCharacterLiteral) c_uint; + +pub extern fn ZigClangStmtExpr_getSubStmt(*const ZigClangStmtExpr) *const ZigClangCompoundStmt; + +pub extern fn ZigClangMemberExpr_getBase(*const ZigClangMemberExpr) *const ZigClangExpr; +pub extern fn ZigClangMemberExpr_isArrow(*const ZigClangMemberExpr) bool; +pub extern fn ZigClangMemberExpr_getMemberDecl(*const ZigClangMemberExpr) *const ZigClangValueDecl; + +pub extern fn ZigClangArraySubscriptExpr_getBase(*const ZigClangArraySubscriptExpr) *const ZigClangExpr; +pub extern fn ZigClangArraySubscriptExpr_getIdx(*const ZigClangArraySubscriptExpr) *const ZigClangExpr; + +pub extern fn ZigClangCallExpr_getCallee(*const ZigClangCallExpr) *const ZigClangExpr; +pub extern fn ZigClangCallExpr_getNumArgs(*const ZigClangCallExpr) c_uint; +pub extern fn ZigClangCallExpr_getArgs(*const ZigClangCallExpr) [*]const *const ZigClangExpr; + +pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangQualType; +pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangSourceLocation; + +pub extern fn ZigClangUnaryOperator_getOpcode(*const ZigClangUnaryOperator) ZigClangUO; +pub extern fn ZigClangUnaryOperator_getType(*const ZigClangUnaryOperator) ZigClangQualType; +pub extern fn ZigClangUnaryOperator_getSubExpr(*const ZigClangUnaryOperator) *const ZigClangExpr; +pub extern fn ZigClangUnaryOperator_getBeginLoc(*const ZigClangUnaryOperator) ZigClangSourceLocation; + +pub extern fn ZigClangCompoundAssignOperator_getType(*const ZigClangCompoundAssignOperator) ZigClangQualType; +pub extern fn ZigClangCompoundAssignOperator_getComputationLHSType(*const ZigClangCompoundAssignOperator) ZigClangQualType; +pub extern fn ZigClangCompoundAssignOperator_getComputationResultType(*const ZigClangCompoundAssignOperator) ZigClangQualType; +pub extern fn ZigClangCompoundAssignOperator_getBeginLoc(*const ZigClangCompoundAssignOperator) ZigClangSourceLocation; +pub extern fn ZigClangCompoundAssignOperator_getOpcode(*const ZigClangCompoundAssignOperator) ZigClangBO; +pub extern fn ZigClangCompoundAssignOperator_getLHS(*const ZigClangCompoundAssignOperator) *const ZigClangExpr; +pub extern fn ZigClangCompoundAssignOperator_getRHS(*const ZigClangCompoundAssignOperator) *const ZigClangExpr; diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index 54ef53353d..478232b20a 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -8,6 +8,7 @@ const Token = std.zig.Token; usingnamespace @import("clang.zig"); const ctok = @import("c_tokenizer.zig"); const CToken = ctok.CToken; +const mem = std.mem; const CallingConvention = std.builtin.TypeInfo.CallingConvention; @@ -47,35 +48,34 @@ const Scope = struct { Switch, Block, Root, - While, - FnDef, - Ref, + Condition, + Loop, }; const Switch = struct { base: Scope, - }; - - /// used when getting a member `a.b` - const Ref = struct { - base: Scope, + pending_block: *ast.Node.Block, + cases: *ast.Node.Switch.CaseList, + has_default: bool = false, }; const Block = struct { base: Scope, block_node: *ast.Node.Block, variables: AliasList, + label: ?[]const u8, - /// Don't forget to set rbrace token later - fn init(c: *Context, parent: *Scope, block_node: *ast.Node.Block) !*Block { + /// Don't forget to set rbrace token and block_node later + fn init(c: *Context, parent: *Scope, label: ?[]const u8) !*Block { const block = try c.a().create(Block); block.* = .{ .base = .{ .id = .Block, .parent = parent, }, - .block_node = block_node, + .block_node = undefined, .variables = AliasList.init(c.a()), + .label = label, }; return block; } @@ -83,7 +83,7 @@ const Scope = struct { fn getAlias(scope: *Block, name: []const u8) ?[]const u8 { var it = scope.variables.iterator(0); while (it.next()) |p| { - if (std.mem.eql(u8, p.name, name)) + if (mem.eql(u8, p.name, name)) return p.alias; } return scope.base.parent.?.getAlias(name); @@ -92,7 +92,7 @@ const Scope = struct { fn contains(scope: *Block, name: []const u8) bool { var it = scope.variables.iterator(0); while (it.next()) |p| { - if (std.mem.eql(u8, p.name, name)) + if (mem.eql(u8, p.name, name)) return true; } return scope.base.parent.?.contains(name); @@ -120,52 +120,23 @@ const Scope = struct { } }; - const While = struct { - base: Scope, - }; - - const FnDef = struct { - base: Scope, - params: AliasList, - - fn init(c: *Context) FnDef { - return .{ - .base = .{ - .id = .FnDef, - .parent = &c.global_scope.base, - }, - .params = AliasList.init(c.a()), - }; - } - - fn getAlias(scope: *FnDef, name: []const u8) ?[]const u8 { - var it = scope.params.iterator(0); - while (it.next()) |p| { - if (std.mem.eql(u8, p.name, name)) - return p.alias; - } - return scope.base.parent.?.getAlias(name); - } - - fn contains(scope: *FnDef, name: []const u8) bool { - var it = scope.params.iterator(0); - while (it.next()) |p| { - if (std.mem.eql(u8, p.name, name)) - return true; - } - return scope.base.parent.?.contains(name); - } - }; - - fn findBlockScope(inner: *Scope) *Scope.Block { + fn findBlockScope(inner: *Scope, c: *Context) !*Scope.Block { var scope = inner; - while (true) : (scope = scope.parent orelse unreachable) { - if (scope.id == .Block) return @fieldParentPtr(Scope.Block, "base", scope); + while (true) { + switch (scope.id) { + .Root => unreachable, + .Block => return @fieldParentPtr(Block, "base", scope), + .Condition => { + // comma operator used + return try Block.init(c, scope, "blk"); + }, + else => scope = scope.parent.?, + } } } fn createAlias(scope: *Scope, c: *Context, name: []const u8) !?[]const u8 { - if (scope.contains(name)) { + if (isZigPrimitiveType(name) or scope.contains(name)) { return try std.fmt.allocPrint(c.a(), "{}_{}", .{ name, c.getMangle() }); } return null; @@ -174,22 +145,41 @@ const Scope = struct { fn getAlias(scope: *Scope, name: []const u8) ?[]const u8 { return switch (scope.id) { .Root => null, - .Ref => null, - .FnDef => @fieldParentPtr(FnDef, "base", scope).getAlias(name), .Block => @fieldParentPtr(Block, "base", scope).getAlias(name), - else => @panic("TODO Scope.getAlias"), + .Switch, .Loop, .Condition => scope.parent.?.getAlias(name), }; } fn contains(scope: *Scope, name: []const u8) bool { return switch (scope.id) { - .Ref => false, .Root => @fieldParentPtr(Root, "base", scope).contains(name), - .FnDef => @fieldParentPtr(FnDef, "base", scope).contains(name), .Block => @fieldParentPtr(Block, "base", scope).contains(name), - else => @panic("TODO Scope.contains"), + .Switch, .Loop, .Condition => scope.parent.?.contains(name), }; } + + fn getBreakableScope(inner: *Scope) *Scope { + var scope = inner; + while (true) { + switch (scope.id) { + .Root => unreachable, + .Switch => return scope, + .Loop => return scope, + else => scope = scope.parent.?, + } + } + } + + fn getSwitch(inner: *Scope) *Scope.Switch { + var scope = inner; + while (true) { + switch (scope.id) { + .Root => unreachable, + .Switch => return @fieldParentPtr(Switch, "base", scope), + else => scope = scope.parent.?, + } + } + } }; const Context = struct { @@ -200,7 +190,6 @@ const Context = struct { decl_table: DeclTable, alias_list: AliasList, global_scope: *Scope.Root, - ptr_params: std.BufSet, clang_context: *ZigClangASTContext, mangle_count: u64 = 0, @@ -209,13 +198,13 @@ const Context = struct { return c.mangle_count; } - fn a(c: *Context) *std.mem.Allocator { + fn a(c: *Context) *mem.Allocator { return &c.tree.arena_allocator.allocator; } /// Convert a null-terminated C string to a slice allocated in the arena fn str(c: *Context, s: [*:0]const u8) ![]u8 { - return std.mem.dupe(c.a(), u8, std.mem.toSliceConst(u8, s)); + return mem.dupe(c.a(), u8, mem.toSliceConst(u8, s)); } /// Convert a clang source location to a file:line:column string @@ -231,7 +220,7 @@ const Context = struct { }; pub fn translate( - backing_allocator: *std.mem.Allocator, + backing_allocator: *mem.Allocator, args_begin: [*]?[*]const u8, args_end: [*]?[*]const u8, errors: *[]ClangErrMsg, @@ -286,7 +275,6 @@ pub fn translate( .decl_table = DeclTable.init(arena), .alias_list = AliasList.init(arena), .global_scope = try arena.create(Scope.Root), - .ptr_params = std.BufSet.init(arena), .clang_context = ZigClangASTUnit_getASTContext(ast_unit).?, }; context.global_scope.* = Scope.Root.init(&context); @@ -333,13 +321,13 @@ fn declVisitor(c: *Context, decl: *const ZigClangDecl) Error!void { return visitFnDecl(c, @ptrCast(*const ZigClangFunctionDecl, decl)); }, .Typedef => { - return resolveTypeDef(c, @ptrCast(*const ZigClangTypedefNameDecl, decl)); + _ = try transTypeDef(c, @ptrCast(*const ZigClangTypedefNameDecl, decl)); }, .Enum => { _ = try transEnumDecl(c, @ptrCast(*const ZigClangEnumDecl, decl)); }, .Record => { - return resolveRecordDecl(c, @ptrCast(*const ZigClangRecordDecl, decl)); + _ = try transRecordDecl(c, @ptrCast(*const ZigClangRecordDecl, decl)); }, .Var => { return visitVarDecl(c, @ptrCast(*const ZigClangVarDecl, decl)); @@ -352,22 +340,17 @@ fn declVisitor(c: *Context, decl: *const ZigClangDecl) Error!void { } fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { - if (c.decl_table.contains(@ptrToInt(fn_decl))) return; // Avoid processing this decl twice - const rp = makeRestorePoint(c); const fn_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, fn_decl))); - _ = try c.decl_table.put(@ptrToInt(fn_decl), fn_name); + if (c.global_scope.sym_table.contains(fn_name)) + return; // Avoid processing this decl twice + const rp = makeRestorePoint(c); const fn_decl_loc = ZigClangFunctionDecl_getLocation(fn_decl); - const fn_qt = ZigClangFunctionDecl_getType(fn_decl); - const fn_type = ZigClangQualType_getTypePtr(fn_qt); - var fndef_scope = Scope.FnDef.init(c); - var scope = &fndef_scope.base; const has_body = ZigClangFunctionDecl_hasBody(fn_decl); const storage_class = ZigClangFunctionDecl_getStorageClass(fn_decl); const decl_ctx = FnDeclContext{ .fn_name = fn_name, .has_body = has_body, .storage_class = storage_class, - .scope = &scope, .is_export = switch (storage_class) { .None => has_body, .Extern, .Static => false, @@ -377,6 +360,15 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { else => unreachable, }, }; + + var fn_qt = ZigClangFunctionDecl_getType(fn_decl); + var fn_type = ZigClangQualType_getTypePtr(fn_qt); + if (ZigClangType_getTypeClass(fn_type) == .Attributed) { + const attr_type = @ptrCast(*const ZigClangAttributedType, fn_type); + fn_qt = ZigClangAttributedType_getEquivalentType(attr_type); + fn_type = ZigClangQualType_getTypePtr(fn_qt); + } + const proto_node = switch (ZigClangType_getTypeClass(fn_type)) { .FunctionProto => blk: { const fn_proto_type = @ptrCast(*const ZigClangFunctionProtoType, fn_type); @@ -396,7 +388,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { error.OutOfMemory => |e| return e, }; }, - else => unreachable, + else => return failDecl(c, fn_decl_loc, fn_name, "unable to resolve function type {}", .{ZigClangType_getTypeClass(fn_type)}), }; if (!decl_ctx.has_body) { @@ -406,20 +398,47 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { // actual function definition with body const body_stmt = ZigClangFunctionDecl_getBody(fn_decl); - const body_node = transStmt(rp, scope, body_stmt, .unused, .r_value) catch |err| switch (err) { + const block_scope = try Scope.Block.init(rp.c, &c.global_scope.base, null); + var scope = &block_scope.base; + const block_node = try transCreateNodeBlock(rp.c, null); + block_scope.block_node = block_node; + + var it = proto_node.params.iterator(0); + while (it.next()) |p| { + const param = @fieldParentPtr(ast.Node.ParamDecl, "base", p.*); + const param_name = tokenSlice(c, param.name_token orelse + return failDecl(c, fn_decl_loc, fn_name, "function {} parameter has no name", .{fn_name})); + + const checked_param_name = if (try scope.createAlias(rp.c, param_name)) |a| blk: { + try block_scope.variables.push(.{ .name = param_name, .alias = a }); + break :blk a; + } else param_name; + const arg_name = try std.fmt.allocPrint(c.a(), "_arg_{}", .{checked_param_name}); + + const node = try transCreateNodeVarDecl(c, false, false, checked_param_name); + node.eq_token = try appendToken(c, .Equal, "="); + node.init_node = try transCreateNodeIdentifier(c, arg_name); + node.semicolon_token = try appendToken(c, .Semicolon, ";"); + try block_node.statements.push(&node.base); + param.name_token = try appendIdentifier(c, arg_name); + _ = try appendToken(c, .Colon, ":"); + } + + transCompoundStmtInline(rp, &block_scope.base, @ptrCast(*const ZigClangCompoundStmt, body_stmt), block_node) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.UnsupportedTranslation, error.UnsupportedType, => return failDecl(c, fn_decl_loc, fn_name, "unable to translate function", .{}), }; - assert(body_node.id == .Block); - proto_node.body_node = body_node; - + block_node.rbrace = try appendToken(rp.c, .RBrace, "}"); + proto_node.body_node = &block_node.base; return addTopLevelDecl(c, fn_name, &proto_node.base); } fn visitVarDecl(c: *Context, var_decl: *const ZigClangVarDecl) Error!void { - if (c.decl_table.contains(@ptrToInt(var_decl))) return; // Avoid processing this decl twice + const var_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, var_decl))); + if (c.global_scope.sym_table.contains(var_name)) + return; // Avoid processing this decl twice const rp = makeRestorePoint(c); const visib_tok = try appendToken(c, .Keyword_pub, "pub"); @@ -429,8 +448,10 @@ fn visitVarDecl(c: *Context, var_decl: *const ZigClangVarDecl) Error!void { try appendToken(c, .Keyword_threadlocal, "threadlocal"); const scope = &c.global_scope.base; - const var_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, var_decl))); - _ = try c.decl_table.put(@ptrToInt(var_decl), var_name); + + // TODO https://github.com/ziglang/zig/issues/3756 + // TODO https://github.com/ziglang/zig/issues/1802 + const checked_name = if (isZigPrimitiveType(var_name)) try std.fmt.allocPrint(c.a(), "_{}", .{var_name}) else var_name; const var_decl_loc = ZigClangVarDecl_getLocation(var_decl); const qual_type = ZigClangVarDecl_getTypeSourceInfo_getType(var_decl); @@ -449,12 +470,12 @@ fn visitVarDecl(c: *Context, var_decl: *const ZigClangVarDecl) Error!void { else try appendToken(c, .Keyword_var, "var"); - const name_tok = try appendIdentifier(c, var_name); + const name_tok = try appendIdentifier(c, checked_name); _ = try appendToken(c, .Colon, ":"); const type_node = transQualType(rp, qual_type, var_decl_loc) catch |err| switch (err) { error.UnsupportedType => { - return failDecl(c, var_decl_loc, var_name, "unable to resolve variable type", .{}); + return failDecl(c, var_decl_loc, checked_name, "unable to resolve variable type", .{}); }, error.OutOfMemory => |e| return e, }; @@ -469,14 +490,14 @@ fn visitVarDecl(c: *Context, var_decl: *const ZigClangVarDecl) Error!void { error.UnsupportedTranslation, error.UnsupportedType, => { - return failDecl(c, var_decl_loc, var_name, "unable to translate initializer", .{}); + return failDecl(c, var_decl_loc, checked_name, "unable to translate initializer", .{}); }, error.OutOfMemory => |e| return e, } else try transCreateNodeUndefinedLiteral(c); } else if (storage_class != .Extern) { - return failDecl(c, var_decl_loc, var_name, "non-extern variable has no initializer", .{}); + return failDecl(c, var_decl_loc, checked_name, "non-extern variable has no initializer", .{}); } const node = try c.a().create(ast.Node.VarDecl); @@ -496,130 +517,295 @@ fn visitVarDecl(c: *Context, var_decl: *const ZigClangVarDecl) Error!void { .init_node = init_node, .semicolon_token = try appendToken(c, .Semicolon, ";"), }; - return addTopLevelDecl(c, var_name, &node.base); + return addTopLevelDecl(c, checked_name, &node.base); } -fn resolveTypeDef(c: *Context, typedef_decl: *const ZigClangTypedefNameDecl) Error!void { - if (c.decl_table.contains( - @ptrToInt(ZigClangTypedefNameDecl_getCanonicalDecl(typedef_decl)), - )) return; // Avoid processing this decl twice +fn transTypeDefAsBuiltin(c: *Context, typedef_decl: *const ZigClangTypedefNameDecl, builtin_name: []const u8) !*ast.Node { + _ = try c.decl_table.put(@ptrToInt(ZigClangTypedefNameDecl_getCanonicalDecl(typedef_decl)), builtin_name); + return transCreateNodeIdentifier(c, builtin_name); +} + +fn transTypeDef(c: *Context, typedef_decl: *const ZigClangTypedefNameDecl) Error!?*ast.Node { + if (c.decl_table.get(@ptrToInt(ZigClangTypedefNameDecl_getCanonicalDecl(typedef_decl)))) |kv| + return try transCreateNodeIdentifier(c, kv.value); // Avoid processing this decl twice const rp = makeRestorePoint(c); - const visib_tok = try appendToken(c, .Keyword_pub, "pub"); - const const_tok = try appendToken(c, .Keyword_const, "const"); const typedef_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, typedef_decl))); + + if (mem.eql(u8, typedef_name, "uint8_t")) + return transTypeDefAsBuiltin(c, typedef_decl, "u8") + else if (mem.eql(u8, typedef_name, "int8_t")) + return transTypeDefAsBuiltin(c, typedef_decl, "i8") + else if (mem.eql(u8, typedef_name, "uint16_t")) + return transTypeDefAsBuiltin(c, typedef_decl, "u16") + else if (mem.eql(u8, typedef_name, "int16_t")) + return transTypeDefAsBuiltin(c, typedef_decl, "i16") + else if (mem.eql(u8, typedef_name, "uint32_t")) + return transTypeDefAsBuiltin(c, typedef_decl, "u32") + else if (mem.eql(u8, typedef_name, "int32_t")) + return transTypeDefAsBuiltin(c, typedef_decl, "i32") + else if (mem.eql(u8, typedef_name, "uint64_t")) + return transTypeDefAsBuiltin(c, typedef_decl, "u64") + else if (mem.eql(u8, typedef_name, "int64_t")) + return transTypeDefAsBuiltin(c, typedef_decl, "i64") + else if (mem.eql(u8, typedef_name, "intptr_t")) + return transTypeDefAsBuiltin(c, typedef_decl, "isize") + else if (mem.eql(u8, typedef_name, "uintptr_t")) + return transTypeDefAsBuiltin(c, typedef_decl, "usize") + else if (mem.eql(u8, typedef_name, "ssize_t")) + return transTypeDefAsBuiltin(c, typedef_decl, "isize") + else if (mem.eql(u8, typedef_name, "size_t")) + return transTypeDefAsBuiltin(c, typedef_decl, "usize"); + _ = try c.decl_table.put(@ptrToInt(ZigClangTypedefNameDecl_getCanonicalDecl(typedef_decl)), typedef_name); - const name_tok = try appendIdentifier(c, typedef_name); - const eq_tok = try appendToken(c, .Equal, "="); + const visib_tok = try appendToken(c, .Keyword_pub, "pub"); + const const_tok = try appendToken(c, .Keyword_const, "const"); + const node = try transCreateNodeVarDecl(c, true, true, typedef_name); + node.eq_token = try appendToken(c, .Equal, "="); const child_qt = ZigClangTypedefNameDecl_getUnderlyingType(typedef_decl); const typedef_loc = ZigClangTypedefNameDecl_getLocation(typedef_decl); - const type_node = transQualType(rp, child_qt, typedef_loc) catch |err| switch (err) { + node.init_node = transQualType(rp, child_qt, typedef_loc) catch |err| switch (err) { error.UnsupportedType => { - return failDecl(c, typedef_loc, typedef_name, "unable to resolve typedef child type", .{}); + try failDecl(c, typedef_loc, typedef_name, "unable to resolve typedef child type", .{}); + return null; }, error.OutOfMemory => |e| return e, }; - - const node = try c.a().create(ast.Node.VarDecl); - node.* = ast.Node.VarDecl{ - .doc_comments = null, - .visib_token = visib_tok, - .thread_local_token = null, - .name_token = name_tok, - .eq_token = eq_tok, - .mut_token = const_tok, - .comptime_token = null, - .extern_export_token = null, - .lib_name = null, - .type_node = null, - .align_node = null, - .section_node = null, - .init_node = type_node, - .semicolon_token = try appendToken(c, .Semicolon, ";"), - }; + node.semicolon_token = try appendToken(c, .Semicolon, ";"); try addTopLevelDecl(c, typedef_name, &node.base); + return transCreateNodeIdentifier(c, typedef_name); } -fn resolveRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!void { - if (c.decl_table.contains(@ptrToInt(ZigClangRecordDecl_getCanonicalDecl(record_decl)))) return; // Avoid processing this decl twice - const rp = makeRestorePoint(c); +fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?*ast.Node { + if (c.decl_table.get(@ptrToInt(ZigClangRecordDecl_getCanonicalDecl(record_decl)))) |kv| + return try transCreateNodeIdentifier(c, kv.value); // Avoid processing this decl twice + const record_loc = ZigClangRecordDecl_getLocation(record_decl); - const bare_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, record_decl))); + var bare_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, record_decl))); + var is_unnamed = false; + if (ZigClangRecordDecl_isAnonymousStructOrUnion(record_decl) or bare_name.len == 0) { + bare_name = try std.fmt.allocPrint(c.a(), "unnamed_{}", .{c.getMangle()}); + is_unnamed = true; + } - const container_kind_name = if (ZigClangRecordDecl_isUnion(record_decl)) - "union" - else if (ZigClangRecordDecl_isStruct(record_decl)) - "struct" - else - return emitWarning(c, ZigClangRecordDecl_getLocation(record_decl), "record {} is not a struct or union", .{bare_name}); - - if (ZigClangRecordDecl_isAnonymousStructOrUnion(record_decl) or bare_name.len == 0) - return; - - const visib_tok = try appendToken(c, .Keyword_pub, "pub"); - const const_tok = try appendToken(c, .Keyword_const, "const"); + var container_kind_name: []const u8 = undefined; + var container_kind: std.zig.Token.Id = undefined; + if (ZigClangRecordDecl_isUnion(record_decl)) { + container_kind_name = "union"; + container_kind = .Keyword_union; + } else if (ZigClangRecordDecl_isStruct(record_decl)) { + container_kind_name = "struct"; + container_kind = .Keyword_struct; + } else { + try emitWarning(c, record_loc, "record {} is not a struct or union", .{bare_name}); + return null; + } const name = try std.fmt.allocPrint(c.a(), "{}_{}", .{ container_kind_name, bare_name }); _ = try c.decl_table.put(@ptrToInt(ZigClangRecordDecl_getCanonicalDecl(record_decl)), name); - const name_tok = try appendIdentifier(c, name); - const eq_tok = try appendToken(c, .Equal, "="); - const init_node = transRecordDecl(c, record_decl) catch |err| switch (err) { - error.UnsupportedType => { - return failDecl(c, ZigClangRecordDecl_getLocation(record_decl), name, "unable to resolve record type", .{}); - }, - error.OutOfMemory => |e| return e, - }; - const semicolon_token = try appendToken(c, .Semicolon, ";"); + const node = try transCreateNodeVarDecl(c, !is_unnamed, true, name); - const node = try c.a().create(ast.Node.VarDecl); - node.* = ast.Node.VarDecl{ - .doc_comments = null, - .visib_token = visib_tok, - .thread_local_token = null, - .name_token = name_tok, - .eq_token = eq_tok, - .mut_token = const_tok, - .comptime_token = null, - .extern_export_token = null, - .lib_name = null, - .type_node = null, - .align_node = null, - .section_node = null, - .init_node = init_node, - .semicolon_token = semicolon_token, + node.eq_token = try appendToken(c, .Equal, "="); + + var semicolon: ast.TokenIndex = undefined; + node.init_node = blk: { + const rp = makeRestorePoint(c); + const record_def = ZigClangRecordDecl_getDefinition(record_decl) orelse { + const opaque = try transCreateNodeOpaqueType(c); + semicolon = try appendToken(c, .Semicolon, ";"); + break :blk opaque; + }; + + const extern_tok = try appendToken(c, .Keyword_extern, "extern"); + const container_tok = try appendToken(c, container_kind, container_kind_name); + const lbrace_token = try appendToken(c, .LBrace, "{"); + + const container_node = try c.a().create(ast.Node.ContainerDecl); + container_node.* = .{ + .layout_token = extern_tok, + .kind_token = container_tok, + .init_arg_expr = .None, + .fields_and_decls = ast.Node.ContainerDecl.DeclList.init(c.a()), + .lbrace_token = lbrace_token, + .rbrace_token = undefined, + }; + + var it = ZigClangRecordDecl_field_begin(record_def); + const end_it = ZigClangRecordDecl_field_end(record_def); + while (ZigClangRecordDecl_field_iterator_neq(it, end_it)) : (it = ZigClangRecordDecl_field_iterator_next(it)) { + const field_decl = ZigClangRecordDecl_field_iterator_deref(it); + const field_loc = ZigClangFieldDecl_getLocation(field_decl); + + if (ZigClangFieldDecl_isBitField(field_decl)) { + const opaque = try transCreateNodeOpaqueType(c); + semicolon = try appendToken(c, .Semicolon, ";"); + try emitWarning(c, field_loc, "{} demoted to opaque type - has bitfield", .{container_kind_name}); + break :blk opaque; + } + const raw_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, field_decl))); + if (raw_name.len < 1) continue; // fix weird windows bug? + const field_name = try appendIdentifier(c, raw_name); + _ = try appendToken(c, .Colon, ":"); + const field_type = transQualType(rp, ZigClangFieldDecl_getType(field_decl), field_loc) catch |err| switch (err) { + error.UnsupportedType => { + try failDecl(c, record_loc, name, "unable to translate {} member type", .{container_kind_name}); + return null; + }, + else => |e| return e, + }; + + const field_node = try c.a().create(ast.Node.ContainerField); + field_node.* = .{ + .doc_comments = null, + .comptime_token = null, + .name_token = field_name, + .type_expr = field_type, + .value_expr = null, + .align_expr = null, + }; + + try container_node.fields_and_decls.push(&field_node.base); + _ = try appendToken(c, .Comma, ","); + } + container_node.rbrace_token = try appendToken(c, .RBrace, "}"); + semicolon = try appendToken(c, .Semicolon, ";"); + break :blk &container_node.base; }; + node.semicolon_token = semicolon; try addTopLevelDecl(c, name, &node.base); - try c.alias_list.push(.{ .alias = bare_name, .name = name }); + if (!is_unnamed) + try c.alias_list.push(.{ .alias = bare_name, .name = name }); + return transCreateNodeIdentifier(c, name); +} + +fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.Node { + if (c.decl_table.get(@ptrToInt(ZigClangEnumDecl_getCanonicalDecl(enum_decl)))) |name| + return try transCreateNodeIdentifier(c, name.value); // Avoid processing this decl twice + const rp = makeRestorePoint(c); + const enum_loc = ZigClangEnumDecl_getLocation(enum_decl); + + var bare_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, enum_decl))); + var is_unnamed = false; + if (bare_name.len == 0) { + bare_name = try std.fmt.allocPrint(c.a(), "unnamed_{}", .{c.getMangle()}); + is_unnamed = true; + } + + const name = try std.fmt.allocPrint(c.a(), "enum_{}", .{bare_name}); + _ = try c.decl_table.put(@ptrToInt(ZigClangEnumDecl_getCanonicalDecl(enum_decl)), name); + const node = try transCreateNodeVarDecl(c, !is_unnamed, true, name); + node.eq_token = try appendToken(c, .Equal, "="); + + node.init_node = if (ZigClangEnumDecl_getDefinition(enum_decl)) |enum_def| blk: { + var pure_enum = true; + var it = ZigClangEnumDecl_enumerator_begin(enum_def); + var end_it = ZigClangEnumDecl_enumerator_end(enum_def); + while (ZigClangEnumDecl_enumerator_iterator_neq(it, end_it)) : (it = ZigClangEnumDecl_enumerator_iterator_next(it)) { + const enum_const = ZigClangEnumDecl_enumerator_iterator_deref(it); + if (ZigClangEnumConstantDecl_getInitExpr(enum_const)) |_| { + pure_enum = false; + break; + } + } + + const extern_tok = try appendToken(c, .Keyword_extern, "extern"); + const container_tok = try appendToken(c, .Keyword_enum, "enum"); + + const container_node = try c.a().create(ast.Node.ContainerDecl); + container_node.* = .{ + .layout_token = extern_tok, + .kind_token = container_tok, + .init_arg_expr = .None, + .fields_and_decls = ast.Node.ContainerDecl.DeclList.init(c.a()), + .lbrace_token = undefined, + .rbrace_token = undefined, + }; + + const int_type = ZigClangEnumDecl_getIntegerType(enum_decl); + + // TODO only emit this tag type if the enum tag type is not the default. + // I don't know what the default is, need to figure out how clang is deciding. + // it appears to at least be different across gcc/msvc + if (!isCBuiltinType(int_type, .UInt) and + !isCBuiltinType(int_type, .Int)) + { + _ = try appendToken(c, .LParen, "("); + container_node.init_arg_expr = .{ + .Type = transQualType(rp, int_type, enum_loc) catch |err| switch (err) { + error.UnsupportedType => { + try failDecl(c, enum_loc, name, "unable to translate enum tag type", .{}); + return null; + }, + else => |e| return e, + }, + }; + _ = try appendToken(c, .RParen, ")"); + } + + container_node.lbrace_token = try appendToken(c, .LBrace, "{"); + + it = ZigClangEnumDecl_enumerator_begin(enum_def); + end_it = ZigClangEnumDecl_enumerator_end(enum_def); + while (ZigClangEnumDecl_enumerator_iterator_neq(it, end_it)) : (it = ZigClangEnumDecl_enumerator_iterator_next(it)) { + const enum_const = ZigClangEnumDecl_enumerator_iterator_deref(it); + + const enum_val_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, enum_const))); + + const field_name = if (!is_unnamed and mem.startsWith(u8, enum_val_name, bare_name)) + enum_val_name[bare_name.len..] + else + enum_val_name; + + const field_name_tok = try appendIdentifier(c, field_name); + + const int_node = if (!pure_enum) blk: { + _ = try appendToken(c, .Colon, "="); + break :blk try transCreateNodeAPInt(c, ZigClangEnumConstantDecl_getInitVal(enum_const)); + } else + null; + + const field_node = try c.a().create(ast.Node.ContainerField); + field_node.* = .{ + .doc_comments = null, + .comptime_token = null, + .name_token = field_name_tok, + .type_expr = null, + .value_expr = int_node, + .align_expr = null, + }; + + try container_node.fields_and_decls.push(&field_node.base); + _ = try appendToken(c, .Comma, ","); + // In C each enum value is in the global namespace. So we put them there too. + // At this point we can rely on the enum emitting successfully. + const tld_node = try transCreateNodeVarDecl(c, true, true, enum_val_name); + tld_node.eq_token = try appendToken(c, .Equal, "="); + tld_node.init_node = try transCreateNodeAPInt(c, ZigClangEnumConstantDecl_getInitVal(enum_const)); + tld_node.semicolon_token = try appendToken(c, .Semicolon, ";"); + try addTopLevelDecl(c, field_name, &tld_node.base); + } + container_node.rbrace_token = try appendToken(c, .RBrace, "}"); + + break :blk &container_node.base; + } else + try transCreateNodeOpaqueType(c); + + node.semicolon_token = try appendToken(c, .Semicolon, ";"); + + try addTopLevelDecl(c, name, &node.base); + if (!is_unnamed) + try c.alias_list.push(.{ .alias = bare_name, .name = name }); + return transCreateNodeIdentifier(c, name); } fn createAlias(c: *Context, alias: var) !void { - const visib_tok = try appendToken(c, .Keyword_pub, "pub"); - const mut_tok = try appendToken(c, .Keyword_const, "const"); - const name_tok = try appendIdentifier(c, alias.alias); - - const eq_tok = try appendToken(c, .Equal, "="); - const init_node = try transCreateNodeIdentifier(c, alias.name); - - const node = try c.a().create(ast.Node.VarDecl); - node.* = ast.Node.VarDecl{ - .doc_comments = null, - .visib_token = visib_tok, - .thread_local_token = null, - .name_token = name_tok, - .eq_token = eq_tok, - .mut_token = mut_tok, - .comptime_token = null, - .extern_export_token = null, - .lib_name = null, - .type_node = null, - .align_node = null, - .section_node = null, - .init_node = init_node, - .semicolon_token = try appendToken(c, .Semicolon, ";"), - }; + const node = try transCreateNodeVarDecl(c, true, true, alias.alias); + node.eq_token = try appendToken(c, .Equal, "="); + node.init_node = try transCreateNodeIdentifier(c, alias.name); + node.semicolon_token = try appendToken(c, .Semicolon, ";"); return addTopLevelDecl(c, alias.alias, &node.base); } @@ -651,9 +837,45 @@ fn transStmt( .IntegerLiteralClass => return transIntegerLiteral(rp, scope, @ptrCast(*const ZigClangIntegerLiteral, stmt), result_used), .ReturnStmtClass => return transReturnStmt(rp, scope, @ptrCast(*const ZigClangReturnStmt, stmt)), .StringLiteralClass => return transStringLiteral(rp, scope, @ptrCast(*const ZigClangStringLiteral, stmt), result_used), - .ParenExprClass => return transExpr(rp, scope, ZigClangParenExpr_getSubExpr(@ptrCast(*const ZigClangParenExpr, stmt)), result_used, lrvalue), + .ParenExprClass => { + const expr = try transExpr(rp, scope, ZigClangParenExpr_getSubExpr(@ptrCast(*const ZigClangParenExpr, stmt)), result_used, lrvalue); + if (expr.id == .GroupedExpression) return expr; + const node = try rp.c.a().create(ast.Node.GroupedExpression); + node.* = .{ + .lparen = try appendToken(rp.c, .LParen, "("), + .expr = expr, + .rparen = try appendToken(rp.c, .RParen, ")"), + }; + return &node.base; + }, .InitListExprClass => return transInitListExpr(rp, scope, @ptrCast(*const ZigClangInitListExpr, stmt), result_used), .ImplicitValueInitExprClass => return transImplicitValueInitExpr(rp, scope, @ptrCast(*const ZigClangExpr, stmt), result_used), + .IfStmtClass => return transIfStmt(rp, scope, @ptrCast(*const ZigClangIfStmt, stmt)), + .WhileStmtClass => return transWhileLoop(rp, scope, @ptrCast(*const ZigClangWhileStmt, stmt)), + .DoStmtClass => return transDoWhileLoop(rp, scope, @ptrCast(*const ZigClangDoStmt, stmt)), + .NullStmtClass => { + const block = try transCreateNodeBlock(rp.c, null); + block.rbrace = try appendToken(rp.c, .RBrace, "}"); + return &block.base; + }, + .ContinueStmtClass => return try transCreateNodeContinue(rp.c), + .BreakStmtClass => return transBreak(rp, scope), + .ForStmtClass => return transForLoop(rp, scope, @ptrCast(*const ZigClangForStmt, stmt)), + .FloatingLiteralClass => return transFloatingLiteral(rp, scope, @ptrCast(*const ZigClangFloatingLiteral, stmt), result_used), + .ConditionalOperatorClass => return transConditionalOperator(rp, scope, @ptrCast(*const ZigClangConditionalOperator, stmt), result_used), + .SwitchStmtClass => return transSwitch(rp, scope, @ptrCast(*const ZigClangSwitchStmt, stmt)), + .CaseStmtClass => return transCase(rp, scope, @ptrCast(*const ZigClangCaseStmt, stmt)), + .DefaultStmtClass => return transDefault(rp, scope, @ptrCast(*const ZigClangDefaultStmt, stmt)), + .ConstantExprClass => return transConstantExpr(rp, scope, @ptrCast(*const ZigClangExpr, stmt), result_used), + .PredefinedExprClass => return transPredefinedExpr(rp, scope, @ptrCast(*const ZigClangPredefinedExpr, stmt), result_used), + .CharacterLiteralClass => return transCharLiteral(rp, scope, @ptrCast(*const ZigClangCharacterLiteral, stmt), result_used), + .StmtExprClass => return transStmtExpr(rp, scope, @ptrCast(*const ZigClangStmtExpr, stmt), result_used), + .MemberExprClass => return transMemberExpr(rp, scope, @ptrCast(*const ZigClangMemberExpr, stmt), result_used), + .ArraySubscriptExprClass => return transArrayAccess(rp, scope, @ptrCast(*const ZigClangArraySubscriptExpr, stmt), result_used), + .CallExprClass => return transCallExpr(rp, scope, @ptrCast(*const ZigClangCallExpr, stmt), result_used), + .UnaryExprOrTypeTraitExprClass => return transUnaryExprOrTypeTraitExpr(rp, scope, @ptrCast(*const ZigClangUnaryExprOrTypeTraitExpr, stmt), result_used), + .UnaryOperatorClass => return transUnaryOperator(rp, scope, @ptrCast(*const ZigClangUnaryOperator, stmt), result_used), + .CompoundAssignOperatorClass => return transCompoundAssignOperator(rp, scope, @ptrCast(*const ZigClangCompoundAssignOperator, stmt), result_used), else => { return revertAndWarn( rp, @@ -674,91 +896,159 @@ fn transBinaryOperator( ) TransError!*ast.Node { const op = ZigClangBinaryOperator_getOpcode(stmt); const qt = ZigClangBinaryOperator_getType(stmt); + var op_token: ast.TokenIndex = undefined; + var op_id: ast.Node.InfixOp.Op = undefined; switch (op) { - .PtrMemD, .PtrMemI, .Cmp => return revertAndWarn( - rp, - error.UnsupportedTranslation, - ZigClangBinaryOperator_getBeginLoc(stmt), - "TODO: handle more C binary operators: {}", - .{op}, - ), - .Assign => return &(try transCreateNodeAssign(rp, scope, result_used, ZigClangBinaryOperator_getLHS(stmt), ZigClangBinaryOperator_getRHS(stmt))).base, - .Add => { - const node = if (cIsUnsignedInteger(qt)) - try transCreateNodeInfixOp(rp, scope, stmt, .AddWrap, .PlusPercent, "+%", true) - else - try transCreateNodeInfixOp(rp, scope, stmt, .Add, .Plus, "+", true); - return maybeSuppressResult(rp, scope, result_used, node); - }, - .Sub => { - const node = if (cIsUnsignedInteger(qt)) - try transCreateNodeInfixOp(rp, scope, stmt, .SubWrap, .MinusPercent, "-%", true) - else - try transCreateNodeInfixOp(rp, scope, stmt, .Sub, .Minus, "-", true); - return maybeSuppressResult(rp, scope, result_used, node); - }, - .Mul => { - const node = if (cIsUnsignedInteger(qt)) - try transCreateNodeInfixOp(rp, scope, stmt, .MultWrap, .AsteriskPercent, "*%", true) - else - try transCreateNodeInfixOp(rp, scope, stmt, .Mult, .Asterisk, "*", true); - return maybeSuppressResult(rp, scope, result_used, node); + .Assign => return transCreateNodeAssign(rp, scope, result_used, ZigClangBinaryOperator_getLHS(stmt), ZigClangBinaryOperator_getRHS(stmt)), + .Comma => { + const block_scope = try scope.findBlockScope(rp.c); + const expr = block_scope.base.parent == scope; + const lparen = if (expr) blk: { + const l = try appendToken(rp.c, .LParen, "("); + block_scope.block_node = try transCreateNodeBlock(rp.c, block_scope.label); + break :blk l; + } else undefined; + + const lhs = try transExpr(rp, &block_scope.base, ZigClangBinaryOperator_getLHS(stmt), .unused, .r_value); + try block_scope.block_node.statements.push(lhs); + + const rhs = try transExpr(rp, &block_scope.base, ZigClangBinaryOperator_getRHS(stmt), .used, .r_value); + if (expr) { + _ = try appendToken(rp.c, .Semicolon, ";"); + const break_node = try transCreateNodeBreak(rp.c, block_scope.label); + break_node.rhs = rhs; + try block_scope.block_node.statements.push(&break_node.base); + block_scope.block_node.rbrace = try appendToken(rp.c, .RBrace, "}"); + const rparen = try appendToken(rp.c, .RParen, ")"); + const grouped_expr = try rp.c.a().create(ast.Node.GroupedExpression); + grouped_expr.* = .{ + .lparen = lparen, + .expr = &block_scope.block_node.base, + .rparen = rparen, + }; + return maybeSuppressResult(rp, scope, result_used, &grouped_expr.base); + } else { + return maybeSuppressResult(rp, scope, result_used, rhs); + } }, .Div => { if (!cIsUnsignedInteger(qt)) { // signed integer division uses @divTrunc const div_trunc_node = try transCreateNodeBuiltinFnCall(rp.c, "@divTrunc"); - const lhs = try transExpr(rp, scope, ZigClangBinaryOperator_getLHS(stmt), .used, .l_value); - try div_trunc_node.params.push(lhs); + try div_trunc_node.params.push(try transExpr(rp, scope, ZigClangBinaryOperator_getLHS(stmt), .used, .l_value)); _ = try appendToken(rp.c, .Comma, ","); const rhs = try transExpr(rp, scope, ZigClangBinaryOperator_getRHS(stmt), .used, .r_value); try div_trunc_node.params.push(rhs); div_trunc_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return maybeSuppressResult(rp, scope, result_used, &div_trunc_node.base); - } else { - // unsigned/float division uses the operator - const node = try transCreateNodeInfixOp(rp, scope, stmt, .Div, .Slash, "/", true); - return maybeSuppressResult(rp, scope, result_used, node); } }, .Rem => { if (!cIsUnsignedInteger(qt)) { // signed integer division uses @rem const rem_node = try transCreateNodeBuiltinFnCall(rp.c, "@rem"); - const lhs = try transExpr(rp, scope, ZigClangBinaryOperator_getLHS(stmt), .used, .l_value); - try rem_node.params.push(lhs); + try rem_node.params.push(try transExpr(rp, scope, ZigClangBinaryOperator_getLHS(stmt), .used, .l_value)); _ = try appendToken(rp.c, .Comma, ","); const rhs = try transExpr(rp, scope, ZigClangBinaryOperator_getRHS(stmt), .used, .r_value); try rem_node.params.push(rhs); rem_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return maybeSuppressResult(rp, scope, result_used, &rem_node.base); - } else { - // unsigned/float division uses the operator - const node = try transCreateNodeInfixOp(rp, scope, stmt, .Mod, .Percent, "%", true); - return maybeSuppressResult(rp, scope, result_used, node); } }, - .Shl, - .Shr, - .LT, - .GT, - .LE, - .GE, - .EQ, - .NE, - .And, - .Xor, - .Or, - .LAnd, - .LOr, - .Comma, - => return revertAndWarn( - rp, - error.UnsupportedTranslation, - ZigClangBinaryOperator_getBeginLoc(stmt), - "TODO: handle more C binary operators: {}", - .{op}, - ), + .Shl => { + const node = try transCreateNodeShiftOp(rp, scope, stmt, .BitShiftLeft, .AngleBracketAngleBracketLeft, "<<"); + return maybeSuppressResult(rp, scope, result_used, node); + }, + .Shr => { + const node = try transCreateNodeShiftOp(rp, scope, stmt, .BitShiftRight, .AngleBracketAngleBracketRight, ">>"); + return maybeSuppressResult(rp, scope, result_used, node); + }, + .LAnd => { + const node = try transCreateNodeBoolInfixOp(rp, scope, stmt, .BoolAnd, result_used, true); + return maybeSuppressResult(rp, scope, result_used, node); + }, + .LOr => { + const node = try transCreateNodeBoolInfixOp(rp, scope, stmt, .BoolOr, result_used, true); + return maybeSuppressResult(rp, scope, result_used, node); + }, + else => {}, + } + const lhs_node = try transExpr(rp, scope, ZigClangBinaryOperator_getLHS(stmt), .used, .l_value); + switch (op) { + .Add => { + if (cIsUnsignedInteger(qt)) { + op_token = try appendToken(rp.c, .PlusPercent, "+%"); + op_id = .AddWrap; + } else { + op_token = try appendToken(rp.c, .Plus, "+"); + op_id = .Add; + } + }, + .Sub => { + if (cIsUnsignedInteger(qt)) { + op_token = try appendToken(rp.c, .MinusPercent, "-%"); + op_id = .SubWrap; + } else { + op_token = try appendToken(rp.c, .Minus, "-"); + op_id = .Sub; + } + }, + .Mul => { + if (cIsUnsignedInteger(qt)) { + op_token = try appendToken(rp.c, .AsteriskPercent, "*%"); + op_id = .MulWrap; + } else { + op_token = try appendToken(rp.c, .Asterisk, "*"); + op_id = .Mul; + } + }, + .Div => { + // unsigned/float division uses the operator + op_id = .Div; + op_token = try appendToken(rp.c, .Slash, "/"); + }, + .Rem => { + // unsigned/float division uses the operator + op_id = .Mod; + op_token = try appendToken(rp.c, .Percent, "%"); + }, + .LT => { + op_id = .LessThan; + op_token = try appendToken(rp.c, .AngleBracketLeft, "<"); + }, + .GT => { + op_id = .GreaterThan; + op_token = try appendToken(rp.c, .AngleBracketRight, ">"); + }, + .LE => { + op_id = .LessOrEqual; + op_token = try appendToken(rp.c, .AngleBracketLeftEqual, "<="); + }, + .GE => { + op_id = .GreaterOrEqual; + op_token = try appendToken(rp.c, .AngleBracketRightEqual, ">="); + }, + .EQ => { + op_id = .EqualEqual; + op_token = try appendToken(rp.c, .EqualEqual, "=="); + }, + .NE => { + op_id = .BangEqual; + op_token = try appendToken(rp.c, .BangEqual, "!="); + }, + .And => { + op_id = .BitAnd; + op_token = try appendToken(rp.c, .Ampersand, "&"); + }, + .Xor => { + op_id = .BitXor; + op_token = try appendToken(rp.c, .Caret, "^"); + }, + .Or => { + op_id = .BitOr; + op_token = try appendToken(rp.c, .Pipe, "|"); + }, + .Assign, .MulAssign, .DivAssign, .RemAssign, @@ -772,6 +1062,9 @@ fn transBinaryOperator( => unreachable, else => unreachable, } + + const rhs_node = try transExpr(rp, scope, ZigClangBinaryOperator_getRHS(stmt), .used, .r_value); + return transCreateNodeInfixOp(rp, scope, lhs_node, op_id, op_token, rhs_node, result_used, true); } fn transCompoundStmtInline( @@ -790,11 +1083,11 @@ fn transCompoundStmtInline( } fn transCompoundStmt(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangCompoundStmt) TransError!*ast.Node { - const block_node = try transCreateNodeBlock(rp.c, null); - const block_scope = try Scope.Block.init(rp.c, scope, block_node); - try transCompoundStmtInline(rp, &block_scope.base, stmt, block_node); - block_node.rbrace = try appendToken(rp.c, .RBrace, "}"); - return &block_node.base; + const block_scope = try Scope.Block.init(rp.c, scope, null); + block_scope.block_node = try transCreateNodeBlock(rp.c, null); + try transCompoundStmtInline(rp, &block_scope.base, stmt, block_scope.block_node); + block_scope.block_node.rbrace = try appendToken(rp.c, .RBrace, "}"); + return &block_scope.block_node.base; } fn transCStyleCastExprClass( @@ -818,7 +1111,7 @@ fn transCStyleCastExprClass( fn transDeclStmt(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangDeclStmt) TransError!*ast.Node { const c = rp.c; - const block_scope = scope.findBlockScope(); + const block_scope = scope.findBlockScope(c) catch unreachable; var it = ZigClangDeclStmt_decl_begin(stmt); const end_it = ZigClangDeclStmt_decl_end(stmt); @@ -832,10 +1125,6 @@ fn transDeclStmt(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangDeclStmt) else try appendToken(c, .Keyword_threadlocal, "threadlocal"); const qual_type = ZigClangVarDecl_getTypeSourceInfo_getType(var_decl); - const mut_token = if (ZigClangQualType_isConstQualified(qual_type)) - try appendToken(c, .Keyword_const, "const") - else - try appendToken(c, .Keyword_var, "var"); const name = try c.str(ZigClangDecl_getName_bytes_begin( @ptrCast(*const ZigClangDecl, var_decl), )); @@ -843,36 +1132,25 @@ fn transDeclStmt(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangDeclStmt) try block_scope.variables.push(.{ .name = name, .alias = a }); break :blk a; } else name; - const name_token = try appendIdentifier(c, checked_name); + const node = try transCreateNodeVarDecl(c, false, ZigClangQualType_isConstQualified(qual_type), checked_name); - const colon_token = try appendToken(c, .Colon, ":"); + _ = try appendToken(c, .Colon, ":"); const loc = ZigClangStmt_getBeginLoc(@ptrCast(*const ZigClangStmt, stmt)); - const type_node = try transQualType(rp, qual_type, loc); + node.type_node = try transQualType(rp, qual_type, loc); - const eq_token = try appendToken(c, .Equal, "="); - const init_node = if (ZigClangVarDecl_getInit(var_decl)) |expr| + node.eq_token = try appendToken(c, .Equal, "="); + var init_node = if (ZigClangVarDecl_getInit(var_decl)) |expr| try transExpr(rp, scope, expr, .used, .r_value) else try transCreateNodeUndefinedLiteral(c); - const semicolon_token = try appendToken(c, .Semicolon, ";"); - - const node = try c.a().create(ast.Node.VarDecl); - node.* = ast.Node.VarDecl{ - .doc_comments = null, - .visib_token = null, - .thread_local_token = thread_local_token, - .name_token = name_token, - .eq_token = eq_token, - .mut_token = mut_token, - .comptime_token = null, - .extern_export_token = null, - .lib_name = null, - .type_node = type_node, - .align_node = null, // TODO ?*Node, - .section_node = null, - .init_node = init_node, - .semicolon_token = semicolon_token, - }; + if (isBoolRes(init_node)) { + const builtin_node = try transCreateNodeBuiltinFnCall(rp.c, "@boolToInt"); + try builtin_node.params.push(init_node); + builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + init_node = &builtin_node.base; + } + node.init_node = init_node; + node.semicolon_token = try appendToken(c, .Semicolon, ";"); try block_scope.block_node.statements.push(&node.base); }, else => |kind| return revertAndWarn( @@ -896,7 +1174,6 @@ fn transDeclRefExpr( const value_decl = ZigClangDeclRefExpr_getDecl(expr); const name = try rp.c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, value_decl))); const checked_name = if (scope.getAlias(name)) |a| a else name; - if (lrvalue == .l_value) try rp.c.ptr_params.put(checked_name); return transCreateNodeIdentifier(rp.c, checked_name); } @@ -909,25 +1186,58 @@ fn transImplicitCastExpr( const c = rp.c; const sub_expr = ZigClangImplicitCastExpr_getSubExpr(expr); const sub_expr_node = try transExpr(rp, scope, @ptrCast(*const ZigClangExpr, sub_expr), .used, .r_value); + const dest_type = getExprQualType(c, @ptrCast(*const ZigClangExpr, expr)); + const src_type = getExprQualType(c, sub_expr); switch (ZigClangImplicitCastExpr_getCastKind(expr)) { - .BitCast => { - const dest_type = getExprQualType(c, @ptrCast(*const ZigClangExpr, expr)); - const src_type = getExprQualType(c, sub_expr); + .BitCast, .FloatingCast, .FloatingToIntegral, .IntegralToFloating, .IntegralCast => { return transCCast(rp, scope, ZigClangImplicitCastExpr_getBeginLoc(expr), dest_type, src_type, sub_expr_node); }, - .IntegralCast => { - const dest_type = ZigClangExpr_getType(@ptrCast(*const ZigClangExpr, expr)); - const src_type = ZigClangExpr_getType(sub_expr); - return transCCast(rp, scope, ZigClangImplicitCastExpr_getBeginLoc(expr), dest_type, src_type, sub_expr_node); - }, - .FunctionToPointerDecay, .ArrayToPointerDecay => { + .LValueToRValue, .NoOp, .FunctionToPointerDecay, .ArrayToPointerDecay => { return maybeSuppressResult(rp, scope, result_used, sub_expr_node); }, - .LValueToRValue, .NoOp => { - return transExpr(rp, scope, sub_expr, .used, .r_value); - }, .NullToPointer => { - return transCreateNodeNullLiteral(rp.c); + return try transCreateNodeNullLiteral(rp.c); + }, + .PointerToBoolean => { + // @ptrToInt(val) != 0 + const ptr_to_int = try transCreateNodeBuiltinFnCall(rp.c, "@ptrToInt"); + try ptr_to_int.params.push(try transExpr(rp, scope, sub_expr, .used, .r_value)); + ptr_to_int.rparen_token = try appendToken(rp.c, .RParen, ")"); + + const op_token = try appendToken(rp.c, .BangEqual, "!="); + const rhs_node = try transCreateNodeInt(rp.c, 0); + return transCreateNodeInfixOp(rp, scope, &ptr_to_int.base, .BangEqual, op_token, rhs_node, result_used, false); + }, + .IntegralToBoolean => { + // val != 0 + const node = try transExpr(rp, scope, sub_expr, .used, .r_value); + + const op_token = try appendToken(rp.c, .BangEqual, "!="); + const rhs_node = try transCreateNodeInt(rp.c, 0); + return transCreateNodeInfixOp(rp, scope, node, .BangEqual, op_token, rhs_node, result_used, false); + }, + .PointerToIntegral => { + // @intCast(dest_type, @ptrToInt(val)) + const cast_node = try transCreateNodeBuiltinFnCall(rp.c, "@intCast"); + try cast_node.params.push(try transQualType(rp, dest_type, ZigClangImplicitCastExpr_getBeginLoc(expr))); + _ = try appendToken(rp.c, .Comma, ","); + + const ptr_to_int = try transCreateNodeBuiltinFnCall(rp.c, "@ptrToInt"); + try ptr_to_int.params.push(try transExpr(rp, scope, sub_expr, .used, .r_value)); + ptr_to_int.rparen_token = try appendToken(rp.c, .RParen, ")"); + try cast_node.params.push(&ptr_to_int.base); + cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + return maybeSuppressResult(rp, scope, result_used, &cast_node.base); + }, + .IntegralToPointer => { + // @intToPtr(dest_type, val) + const int_to_ptr = try transCreateNodeBuiltinFnCall(rp.c, "@intToPtr"); + try int_to_ptr.params.push(try transQualType(rp, dest_type, ZigClangImplicitCastExpr_getBeginLoc(expr))); + _ = try appendToken(rp.c, .Comma, ","); + + try int_to_ptr.params.push(try transExpr(rp, scope, sub_expr, .used, .r_value)); + int_to_ptr.rparen_token = try appendToken(rp.c, .RParen, ")"); + return maybeSuppressResult(rp, scope, result_used, &int_to_ptr.base); }, else => |kind| return revertAndWarn( rp, @@ -939,6 +1249,151 @@ fn transImplicitCastExpr( } } +fn transBoolExpr( + rp: RestorePoint, + scope: *Scope, + expr: *const ZigClangExpr, + used: ResultUsed, + lrvalue: LRValue, + grouped: bool, +) TransError!*ast.Node { + const lparen = if (grouped) + try appendToken(rp.c, .LParen, "(") + else + undefined; + var res = try transExpr(rp, scope, expr, used, lrvalue); + + if (isBoolRes(res)) { + if (!grouped and res.id == .GroupedExpression) { + const group = @fieldParentPtr(ast.Node.GroupedExpression, "base", res); + res = group.expr; + // get zig fmt to work properly + tokenSlice(rp.c, group.lparen)[0] = ')'; + } + return res; + } + + const ty = ZigClangQualType_getTypePtr(getExprQualTypeBeforeImplicitCast(rp.c, expr)); + const node = try finishBoolExpr(rp, scope, ZigClangExpr_getBeginLoc(expr), ty, res, used); + + if (grouped) { + const rparen = try appendToken(rp.c, .RParen, ")"); + const grouped_expr = try rp.c.a().create(ast.Node.GroupedExpression); + grouped_expr.* = .{ + .lparen = lparen, + .expr = node, + .rparen = rparen, + }; + return maybeSuppressResult(rp, scope, used, &grouped_expr.base); + } else { + return maybeSuppressResult(rp, scope, used, node); + } +} + +fn isBoolRes(res: *ast.Node) bool { + switch (res.id) { + .InfixOp => switch (@fieldParentPtr(ast.Node.InfixOp, "base", res).op) { + .BoolOr, + .BoolAnd, + .EqualEqual, + .BangEqual, + .LessThan, + .GreaterThan, + .LessOrEqual, + .GreaterOrEqual, + => return true, + + else => {}, + }, + .PrefixOp => switch (@fieldParentPtr(ast.Node.PrefixOp, "base", res).op) { + .BoolNot => return true, + + else => {}, + }, + .BoolLiteral => return true, + .GroupedExpression => return isBoolRes(@fieldParentPtr(ast.Node.GroupedExpression, "base", res).expr), + else => {}, + } + return false; +} + +fn finishBoolExpr( + rp: RestorePoint, + scope: *Scope, + loc: ZigClangSourceLocation, + ty: *const ZigClangType, + node: *ast.Node, + used: ResultUsed, +) TransError!*ast.Node { + switch (ZigClangType_getTypeClass(ty)) { + .Builtin => { + const builtin_ty = @ptrCast(*const ZigClangBuiltinType, ty); + + switch (ZigClangBuiltinType_getKind(builtin_ty)) { + .Bool => return node, + .Char_U, + .UChar, + .Char_S, + .SChar, + .UShort, + .UInt, + .ULong, + .ULongLong, + .Short, + .Int, + .Long, + .LongLong, + .UInt128, + .Int128, + .Float, + .Double, + .Float128, + .LongDouble, + .WChar_U, + .Char8, + .Char16, + .Char32, + .WChar_S, + .Float16, + => { + const op_token = try appendToken(rp.c, .BangEqual, "!="); + const rhs_node = try transCreateNodeInt(rp.c, 0); + return transCreateNodeInfixOp(rp, scope, node, .BangEqual, op_token, rhs_node, used, false); + }, + .NullPtr => { + const op_token = try appendToken(rp.c, .EqualEqual, "=="); + const rhs_node = try transCreateNodeNullLiteral(rp.c); + return transCreateNodeInfixOp(rp, scope, node, .EqualEqual, op_token, rhs_node, used, false); + }, + else => {}, + } + }, + .Pointer => { + const op_token = try appendToken(rp.c, .BangEqual, "!="); + const rhs_node = try transCreateNodeNullLiteral(rp.c); + return transCreateNodeInfixOp(rp, scope, node, .BangEqual, op_token, rhs_node, used, false); + }, + .Typedef => { + const typedef_ty = @ptrCast(*const ZigClangTypedefType, ty); + const typedef_decl = ZigClangTypedefType_getDecl(typedef_ty); + const underlying_type = ZigClangTypedefNameDecl_getUnderlyingType(typedef_decl); + return finishBoolExpr(rp, scope, loc, ZigClangQualType_getTypePtr(underlying_type), node, used); + }, + .Enum => { + const op_token = try appendToken(rp.c, .BangEqual, "!="); + const rhs_node = try transCreateNodeInt(rp.c, 0); + return transCreateNodeInfixOp(rp, scope, node, .BangEqual, op_token, rhs_node, used, false); + }, + .Elaborated => { + const elaborated_ty = @ptrCast(*const ZigClangElaboratedType, ty); + const named_type = ZigClangElaboratedType_getNamedType(elaborated_ty); + return finishBoolExpr(rp, scope, loc, ZigClangQualType_getTypePtr(named_type), node, used); + }, + else => {}, + } + return revertAndWarn(rp, error.UnsupportedType, loc, "unsupported bool expression type", .{}); +} + fn transIntegerLiteral( rp: RestorePoint, scope: *Scope, @@ -991,7 +1446,7 @@ fn transStringLiteral( const token = try appendToken(rp.c, .StringLiteral, buf); const node = try rp.c.a().create(ast.Node.StringLiteral); - node.* = ast.Node.StringLiteral{ + node.* = .{ .token = token, }; return maybeSuppressResult(rp, scope, result_used, &node.base); @@ -1019,25 +1474,22 @@ fn writeEscapedString(buf: []u8, s: []const u8) void { var i: usize = 0; for (s) |c| { const escaped = escapeChar(c, &char_buf); - std.mem.copy(u8, buf[i..], escaped); + mem.copy(u8, buf[i..], escaped); i += escaped.len; } } // Returns either a string literal or a slice of `buf`. fn escapeChar(c: u8, char_buf: *[4]u8) []const u8 { - // TODO: https://github.com/ziglang/zig/issues/2749 - const escaped = switch (c) { - // Printable ASCII except for ' " \ - ' ', '!', '#'...'&', '('...'[', ']'...'~' => ([_]u8{c})[0..], - '\'', '\"', '\\' => ([_]u8{ '\\', c })[0..], - '\n' => return "\\n"[0..], - '\r' => return "\\r"[0..], - '\t' => return "\\t"[0..], - else => return std.fmt.bufPrint(char_buf[0..], "\\x{x:2}", .{c}) catch unreachable, + return switch (c) { + '\"' => "\\\""[0..], + '\'' => "\\'"[0..], + '\\' => "\\\\"[0..], + '\n' => "\\n"[0..], + '\r' => "\\r"[0..], + '\t' => "\\t"[0..], + else => std.fmt.bufPrint(char_buf[0..], "{c}", .{c}) catch unreachable, }; - std.mem.copy(u8, char_buf, escaped); - return char_buf[0..escaped.len]; } fn transCCast( @@ -1071,6 +1523,55 @@ fn transCCast( builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return &builtin_node.base; } + if (cIsFloating(src_type) and cIsFloating(dst_type)) { + const builtin_node = try transCreateNodeBuiltinFnCall(rp.c, "@floatCast"); + try builtin_node.params.push(try transQualType(rp, dst_type, loc)); + _ = try appendToken(rp.c, .Comma, ","); + try builtin_node.params.push(expr); + builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + return &builtin_node.base; + } + if (cIsFloating(src_type) and !cIsFloating(dst_type)) { + const builtin_node = try transCreateNodeBuiltinFnCall(rp.c, "@floatToInt"); + try builtin_node.params.push(try transQualType(rp, dst_type, loc)); + _ = try appendToken(rp.c, .Comma, ","); + try builtin_node.params.push(expr); + builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + return &builtin_node.base; + } + if (!cIsFloating(src_type) and cIsFloating(dst_type)) { + const builtin_node = try transCreateNodeBuiltinFnCall(rp.c, "@intToFloat"); + try builtin_node.params.push(try transQualType(rp, dst_type, loc)); + _ = try appendToken(rp.c, .Comma, ","); + try builtin_node.params.push(expr); + builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + return &builtin_node.base; + } + if (ZigClangQualType_getTypeClass(src_type) == .Elaborated) { + const elaborated_ty = @ptrCast(*const ZigClangElaboratedType, ZigClangQualType_getTypePtr(src_type)); + return transCCast(rp, scope, loc, dst_type, ZigClangElaboratedType_getNamedType(elaborated_ty), expr); + } + if (ZigClangQualType_getTypeClass(dst_type) == .Elaborated) { + const elaborated_ty = @ptrCast(*const ZigClangElaboratedType, ZigClangQualType_getTypePtr(dst_type)); + return transCCast(rp, scope, loc, ZigClangElaboratedType_getNamedType(elaborated_ty), src_type, expr); + } + if (ZigClangQualType_getTypeClass(dst_type) == .Enum) + { + const builtin_node = try transCreateNodeBuiltinFnCall(rp.c, "@intToEnum"); + try builtin_node.params.push(try transQualType(rp, dst_type, loc)); + _ = try appendToken(rp.c, .Comma, ","); + try builtin_node.params.push(expr); + builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + return &builtin_node.base; + } + if (ZigClangQualType_getTypeClass(src_type) == .Enum and + ZigClangQualType_getTypeClass(dst_type) != .Enum) + { + const builtin_node = try transCreateNodeBuiltinFnCall(rp.c, "@enumToInt"); + try builtin_node.params.push(expr); + builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + return &builtin_node.base; + } // TODO: maybe widen to increase size // TODO: maybe bitcast to change sign // TODO: maybe truncate to reduce size @@ -1124,7 +1625,7 @@ fn transInitListExpr( var cat_tok: ast.TokenIndex = undefined; if (init_count != 0) { const dot_tok = try appendToken(rp.c, .Period, "."); - init_node = try transCreateNodeArrayInitializer(rp.c, dot_tok); + init_node = try transCreateNodeContainerInitializer(rp.c, dot_tok); var i: c_uint = 0; while (i < init_count) : (i += 1) { const elem_expr = ZigClangInitListExpr_getInit(expr, i); @@ -1139,7 +1640,7 @@ fn transInitListExpr( } const dot_tok = try appendToken(rp.c, .Period, "."); - var filler_init_node = try transCreateNodeArrayInitializer(rp.c, dot_tok); + var filler_init_node = try transCreateNodeContainerInitializer(rp.c, dot_tok); const filler_val_expr = ZigClangInitListExpr_getArrayFiller(expr); try filler_init_node.op.ArrayInitializer.push(try transExpr(rp, scope, filler_val_expr, .used, .r_value)); filler_init_node.rtoken = try appendToken(rp.c, .RBrace, "}"); @@ -1185,7 +1686,7 @@ fn transImplicitValueInitExpr( .Builtin => blk: { const builtin_ty = @ptrCast(*const ZigClangBuiltinType, ty); switch (ZigClangBuiltinType_getKind(builtin_ty)) { - .Bool => return transCreateNodeBoolLiteral(rp.c, false), + .Bool => return try transCreateNodeBoolLiteral(rp.c, false), .Char_U, .UChar, .Char_S, @@ -1215,6 +1716,813 @@ fn transImplicitValueInitExpr( }; } +fn transIfStmt( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangIfStmt, +) TransError!*ast.Node { + // if (c) t + // if (c) t else e + const if_node = try transCreateNodeIf(rp.c); + + var cond_scope = Scope{ + .parent = scope, + .id = .Condition, + }; + if_node.condition = try transBoolExpr(rp, &cond_scope, @ptrCast(*const ZigClangExpr, ZigClangIfStmt_getCond(stmt)), .used, .r_value, false); + _ = try appendToken(rp.c, .RParen, ")"); + + if_node.body = try transStmt(rp, scope, ZigClangIfStmt_getThen(stmt), .unused, .r_value); + + if (ZigClangIfStmt_getElse(stmt)) |expr| { + if_node.@"else" = try transCreateNodeElse(rp.c); + if_node.@"else".?.body = try transStmt(rp, scope, expr, .unused, .r_value); + } + _ = try appendToken(rp.c, .Semicolon, ";"); + return &if_node.base; +} + +fn transWhileLoop( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangWhileStmt, +) TransError!*ast.Node { + const while_node = try transCreateNodeWhile(rp.c); + + var cond_scope = Scope{ + .parent = scope, + .id = .Condition, + }; + while_node.condition = try transBoolExpr(rp, &cond_scope, @ptrCast(*const ZigClangExpr, ZigClangWhileStmt_getCond(stmt)), .used, .r_value, false); + _ = try appendToken(rp.c, .RParen, ")"); + + var loop_scope = Scope{ + .parent = scope, + .id = .Loop, + }; + while_node.body = try transStmt(rp, &loop_scope, ZigClangWhileStmt_getBody(stmt), .unused, .r_value); + return &while_node.base; +} + +fn transDoWhileLoop( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangDoStmt, +) TransError!*ast.Node { + const while_node = try transCreateNodeWhile(rp.c); + + while_node.condition = try transCreateNodeBoolLiteral(rp.c, true); + _ = try appendToken(rp.c, .RParen, ")"); + var new = false; + var loop_scope = Scope{ + .parent = scope, + .id = .Loop, + }; + + const body_node = if (ZigClangStmt_getStmtClass(ZigClangDoStmt_getBody(stmt)) == .CompoundStmtClass) blk: { + // there's already a block in C, so we'll append our condition to it. + // c: do { + // c: a; + // c: b; + // c: } while(c); + // zig: while (true) { + // zig: a; + // zig: b; + // zig: if (!cond) break; + // zig: } + break :blk (try transStmt(rp, &loop_scope, ZigClangDoStmt_getBody(stmt), .unused, .r_value)).cast(ast.Node.Block).?; + } else blk: { + // the C statement is without a block, so we need to create a block to contain it. + // c: do + // c: a; + // c: while(c); + // zig: while (true) { + // zig: a; + // zig: if (!cond) break; + // zig: } + new = true; + const block = try transCreateNodeBlock(rp.c, null); + try block.statements.push(try transStmt(rp, &loop_scope, ZigClangDoStmt_getBody(stmt), .unused, .r_value)); + break :blk block; + }; + + // if (!cond) break; + const if_node = try transCreateNodeIf(rp.c); + var cond_scope = Scope{ + .parent = scope, + .id = .Condition, + }; + const prefix_op = try transCreateNodePrefixOp(rp.c, .BoolNot, .Bang, "!"); + prefix_op.rhs = try transBoolExpr(rp, &cond_scope, @ptrCast(*const ZigClangExpr, ZigClangDoStmt_getCond(stmt)), .used, .r_value, true); + _ = try appendToken(rp.c, .RParen, ")"); + if_node.condition = &prefix_op.base; + if_node.body = &(try transCreateNodeBreak(rp.c, null)).base; + _ = try appendToken(rp.c, .Semicolon, ";"); + + try body_node.statements.push(&if_node.base); + if (new) + body_node.rbrace = try appendToken(rp.c, .RBrace, "}"); + while_node.body = &body_node.base; + return &while_node.base; +} + +fn transForLoop( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangForStmt, +) TransError!*ast.Node { + var loop_scope = Scope{ + .parent = scope, + .id = .Loop, + }; + var block = false; + var block_scope: ?*Scope.Block = null; + if (ZigClangForStmt_getInit(stmt)) |init| { + block_scope = try Scope.Block.init(rp.c, scope, null); + block_scope.?.block_node = try transCreateNodeBlock(rp.c, null); + loop_scope.parent = &block_scope.?.base; + _ = try transStmt(rp, &loop_scope, init, .unused, .r_value); + } + var cond_scope = Scope{ + .parent = scope, + .id = .Condition, + }; + + const while_node = try transCreateNodeWhile(rp.c); + while_node.condition = if (ZigClangForStmt_getCond(stmt)) |cond| + try transBoolExpr(rp, &cond_scope, cond, .used, .r_value, false) + else + try transCreateNodeBoolLiteral(rp.c, true); + _ = try appendToken(rp.c, .RParen, ")"); + + if (ZigClangForStmt_getInc(stmt)) |incr| { + _ = try appendToken(rp.c, .Colon, ":"); + _ = try appendToken(rp.c, .LParen, "("); + while_node.continue_expr = try transExpr(rp, &cond_scope, incr, .unused, .r_value); + _ = try appendToken(rp.c, .RParen, ")"); + } + + while_node.body = try transStmt(rp, &loop_scope, ZigClangForStmt_getBody(stmt), .unused, .r_value); + if (block_scope != null) { + try block_scope.?.block_node.statements.push(&while_node.base); + block_scope.?.block_node.rbrace = try appendToken(rp.c, .RBrace, "}"); + return &block_scope.?.block_node.base; + } else + return &while_node.base; +} + +fn transSwitch( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangSwitchStmt, +) TransError!*ast.Node { + const switch_node = try transCreateNodeSwitch(rp.c); + var switch_scope = Scope.Switch{ + .base = .{ + .id = .Switch, + .parent = scope, + }, + .cases = &switch_node.cases, + .pending_block = undefined, + }; + + var cond_scope = Scope{ + .parent = scope, + .id = .Condition, + }; + switch_node.expr = try transExpr(rp, &cond_scope, ZigClangSwitchStmt_getCond(stmt), .used, .r_value); + _ = try appendToken(rp.c, .RParen, ")"); + _ = try appendToken(rp.c, .LBrace, "{"); + switch_node.rbrace = try appendToken(rp.c, .RBrace, "}"); + + const block_scope = try Scope.Block.init(rp.c, &switch_scope.base, null); + // tmp block that all statements will go before being picked up by a case or default + const block = try transCreateNodeBlock(rp.c, null); + block_scope.block_node = block; + + const switch_block = try transCreateNodeBlock(rp.c, null); + try switch_block.statements.push(&switch_node.base); + switch_scope.pending_block = switch_block; + + const last = try transStmt(rp, &block_scope.base, ZigClangSwitchStmt_getBody(stmt), .unused, .r_value); + _ = try appendToken(rp.c, .Semicolon, ";"); + + // take all pending statements + var it = last.cast(ast.Node.Block).?.statements.iterator(0); + while (it.next()) |n| { + try switch_scope.pending_block.statements.push(n.*); + } + + switch_scope.pending_block.label = try appendIdentifier(rp.c, "__switch"); + _ = try appendToken(rp.c, .Colon, ":"); + if (!switch_scope.has_default) { + const else_prong = try transCreateNodeSwitchCase(rp.c, try transCreateNodeSwitchElse(rp.c)); + else_prong.expr = &(try transCreateNodeBreak(rp.c, "__switch")).base; + _ = try appendToken(rp.c, .Comma, ","); + try switch_node.cases.push(&else_prong.base); + } + switch_scope.pending_block.rbrace = try appendToken(rp.c, .RBrace, "}"); + return &switch_scope.pending_block.base; +} + +fn transCase( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangCaseStmt, +) TransError!*ast.Node { + const block_scope = scope.findBlockScope(rp.c) catch unreachable; + const switch_scope = scope.getSwitch(); + const label = try std.fmt.allocPrint(rp.c.a(), "__case_{}", .{switch_scope.cases.len - @boolToInt(switch_scope.has_default)}); + _ = try appendToken(rp.c, .Semicolon, ";"); + + const expr = if (ZigClangCaseStmt_getRHS(stmt)) |rhs| blk: { + const lhs_node = try transExpr(rp, scope, ZigClangCaseStmt_getLHS(stmt), .used, .r_value); + const ellips = try appendToken(rp.c, .Ellipsis3, "..."); + const rhs_node = try transExpr(rp, scope, rhs, .used, .r_value); + + const node = try rp.c.a().create(ast.Node.InfixOp); + node.* = .{ + .op_token = ellips, + .lhs = lhs_node, + .op = .Range, + .rhs = rhs_node, + }; + break :blk &node.base; + } else + try transExpr(rp, scope, ZigClangCaseStmt_getLHS(stmt), .used, .r_value); + + const switch_prong = try transCreateNodeSwitchCase(rp.c, expr); + switch_prong.expr = &(try transCreateNodeBreak(rp.c, label)).base; + _ = try appendToken(rp.c, .Comma, ","); + try switch_scope.cases.push(&switch_prong.base); + + const block = try transCreateNodeBlock(rp.c, null); + switch_scope.pending_block.label = try appendIdentifier(rp.c, label); + _ = try appendToken(rp.c, .Colon, ":"); + switch_scope.pending_block.rbrace = try appendToken(rp.c, .RBrace, "}"); + try block.statements.push(&switch_scope.pending_block.base); + + // take all pending statements + var it = block_scope.block_node.statements.iterator(0); + while (it.next()) |n| { + try switch_scope.pending_block.statements.push(n.*); + } + block_scope.block_node.statements.shrink(0); + + switch_scope.pending_block = block; + + return transStmt(rp, scope, ZigClangCaseStmt_getSubStmt(stmt), .unused, .r_value); +} + +fn transDefault( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangDefaultStmt, +) TransError!*ast.Node { + const block_scope = scope.findBlockScope(rp.c) catch unreachable; + const switch_scope = scope.getSwitch(); + const label = "__default"; + switch_scope.has_default = true; + _ = try appendToken(rp.c, .Semicolon, ";"); + + const else_prong = try transCreateNodeSwitchCase(rp.c, try transCreateNodeSwitchElse(rp.c)); + else_prong.expr = &(try transCreateNodeBreak(rp.c, label)).base; + _ = try appendToken(rp.c, .Comma, ","); + try switch_scope.cases.push(&else_prong.base); + + const block = try transCreateNodeBlock(rp.c, null); + switch_scope.pending_block.label = try appendIdentifier(rp.c, label); + _ = try appendToken(rp.c, .Colon, ":"); + switch_scope.pending_block.rbrace = try appendToken(rp.c, .RBrace, "}"); + try block.statements.push(&switch_scope.pending_block.base); + + // take all pending statements + var it = block_scope.block_node.statements.iterator(0); + while (it.next()) |n| { + try switch_scope.pending_block.statements.push(n.*); + } + block_scope.block_node.statements.shrink(0); + + switch_scope.pending_block = block; + return transStmt(rp, scope, ZigClangDefaultStmt_getSubStmt(stmt), .unused, .r_value); +} + +fn transConstantExpr(rp: RestorePoint, scope: *Scope, expr: *const ZigClangExpr, used: ResultUsed) TransError!*ast.Node { + var result: ZigClangExprEvalResult = undefined; + if (!ZigClangExpr_EvaluateAsConstantExpr(expr, &result, .EvaluateForCodeGen, rp.c.clang_context)) + return revertAndWarn(rp, error.UnsupportedTranslation, ZigClangExpr_getBeginLoc(expr), "invalid constant expression", .{}); + return maybeSuppressResult(rp, scope, used, try transCreateNodeAPInt(rp.c, ZigClangAPValue_getInt(&result.Val))); +} + +fn transPredefinedExpr(rp: RestorePoint, scope: *Scope, expr: *const ZigClangPredefinedExpr, used: ResultUsed) TransError!*ast.Node { + return transStringLiteral(rp, scope, ZigClangPredefinedExpr_getFunctionName(expr), used); +} + +fn transCharLiteral( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangCharacterLiteral, + result_used: ResultUsed, +) TransError!*ast.Node { + const kind = ZigClangCharacterLiteral_getKind(stmt); + switch (kind) { + .Ascii, .UTF8 => { + const val = ZigClangCharacterLiteral_getValue(stmt); + if (kind == .Ascii) { + // C has a somewhat obscure feature called multi-character character + // constant + if (val > 255) + return transCreateNodeInt(rp.c, val); + } + var char_buf: [4]u8 = undefined; + const token = try appendTokenFmt(rp.c, .CharLiteral, "'{}'", .{escapeChar(@intCast(u8, val), &char_buf)}); + const node = try rp.c.a().create(ast.Node.CharLiteral); + node.* = .{ + .token = token, + }; + return maybeSuppressResult(rp, scope, result_used, &node.base); + }, + .UTF16, .UTF32, .Wide => return revertAndWarn( + rp, + error.UnsupportedTranslation, + ZigClangStmt_getBeginLoc(@ptrCast(*const ZigClangStmt, stmt)), + "TODO: support character literal kind {}", + .{kind}, + ), + else => unreachable, + } +} + +fn transStmtExpr(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangStmtExpr, used: ResultUsed) TransError!*ast.Node { + const comp = ZigClangStmtExpr_getSubStmt(stmt); + if (used == .unused) { + return transCompoundStmt(rp, scope, comp); + } + const lparen = try appendToken(rp.c, .LParen, "("); + const block_scope = try Scope.Block.init(rp.c, scope, "blk"); + const block = try transCreateNodeBlock(rp.c, "blk"); + block_scope.block_node = block; + + var it = ZigClangCompoundStmt_body_begin(comp); + const end_it = ZigClangCompoundStmt_body_end(comp); + while (it != end_it - 1) : (it += 1) { + const result = try transStmt(rp, &block_scope.base, it[0], .unused, .r_value); + if (result != &block.base) + try block.statements.push(result); + } + const break_node = try transCreateNodeBreak(rp.c, "blk"); + break_node.rhs = try transStmt(rp, &block_scope.base, it[0], .used, .r_value); + _ = try appendToken(rp.c, .Semicolon, ";"); + try block.statements.push(&break_node.base); + block.rbrace = try appendToken(rp.c, .RBrace, "}"); + const rparen = try appendToken(rp.c, .RParen, ")"); + const grouped_expr = try rp.c.a().create(ast.Node.GroupedExpression); + grouped_expr.* = .{ + .lparen = lparen, + .expr = &block.base, + .rparen = rparen, + }; + return maybeSuppressResult(rp, scope, used, &grouped_expr.base); +} + +fn transMemberExpr(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangMemberExpr, result_used: ResultUsed) TransError!*ast.Node { + var container_node = try transExpr(rp, scope, ZigClangMemberExpr_getBase(stmt), .used, .r_value); + + if (ZigClangMemberExpr_isArrow(stmt)) { + container_node = try transCreateNodePtrDeref(rp.c, container_node); + } + + const name = try rp.c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, ZigClangMemberExpr_getMemberDecl(stmt)))); + const node = try transCreateNodeFieldAccess(rp.c, container_node, name); + return maybeSuppressResult(rp, scope, result_used, node); +} + +fn transArrayAccess(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangArraySubscriptExpr, result_used: ResultUsed) TransError!*ast.Node { + const container_node = try transExpr(rp, scope, ZigClangArraySubscriptExpr_getBase(stmt), .used, .r_value); + const node = try transCreateNodeArrayAccess(rp.c, container_node); + node.op.ArrayAccess = try transExpr(rp, scope, ZigClangArraySubscriptExpr_getIdx(stmt), .used, .r_value); + node.rtoken = try appendToken(rp.c, .RBrace, "]"); + return maybeSuppressResult(rp, scope, result_used, &node.base); +} + +fn transCallExpr(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangCallExpr, result_used: ResultUsed) TransError!*ast.Node { + const callee = ZigClangCallExpr_getCallee(stmt); + var raw_fn_expr = try transExpr(rp, scope, callee, .used, .r_value); + + var is_ptr = false; + const fn_ty = qualTypeGetFnProto(ZigClangExpr_getType(callee), &is_ptr); + + const fn_expr = if (is_ptr and fn_ty != null) blk: { + if (ZigClangExpr_getStmtClass(callee) == .ImplicitCastExprClass) { + const implicit_cast = @ptrCast(*const ZigClangImplicitCastExpr, callee); + + if (ZigClangImplicitCastExpr_getCastKind(implicit_cast) == .FunctionToPointerDecay) { + const subexpr = ZigClangImplicitCastExpr_getSubExpr(implicit_cast); + if (ZigClangExpr_getStmtClass(subexpr) == .DeclRefExprClass) { + const decl_ref = @ptrCast(*const ZigClangDeclRefExpr, subexpr); + const named_decl = ZigClangDeclRefExpr_getFoundDecl(decl_ref); + if (ZigClangDecl_getKind(@ptrCast(*const ZigClangDecl, named_decl)) == .Function) { + break :blk raw_fn_expr; + } + } + } + } + break :blk try transCreateNodeUnwrapNull(rp.c, raw_fn_expr); + } else + raw_fn_expr; + const node = try transCreateNodeFnCall(rp.c, fn_expr); + + const num_args = ZigClangCallExpr_getNumArgs(stmt); + const args = ZigClangCallExpr_getArgs(stmt); + var i: usize = 0; + while (i < num_args) : (i += 1) { + if (i != 0) { + _ = try appendToken(rp.c, .Comma, ","); + } + const arg = try transExpr(rp, scope, args[i], .used, .r_value); + try node.op.Call.params.push(arg); + } + node.rtoken = try appendToken(rp.c, .RParen, ")"); + + if (fn_ty) |ty| { + const canon = ZigClangQualType_getCanonicalType(ZigClangFunctionProtoType_getReturnType(ty)); + const ret_ty = ZigClangQualType_getTypePtr(canon); + if (ZigClangType_isVoidType(ret_ty)) { + _ = try appendToken(rp.c, .Semicolon, ";"); + return &node.base; + } + } + + return maybeSuppressResult(rp, scope, result_used, &node.base); +} + +fn qualTypeGetFnProto(qt: ZigClangQualType, is_ptr: *bool) ?*const ZigClangFunctionProtoType { + const canon = ZigClangQualType_getCanonicalType(qt); + var ty = ZigClangQualType_getTypePtr(canon); + is_ptr.* = false; + + if (ZigClangType_getTypeClass(ty) == .Pointer) { + is_ptr.* = true; + const child_qt = ZigClangType_getPointeeType(ty); + ty = ZigClangQualType_getTypePtr(child_qt); + } + if (ZigClangType_getTypeClass(ty) == .FunctionProto) { + return @ptrCast(*const ZigClangFunctionProtoType, ty); + } + return null; +} + +fn transUnaryExprOrTypeTraitExpr( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangUnaryExprOrTypeTraitExpr, + result_used: ResultUsed, +) TransError!*ast.Node { + const type_node = try transQualType( + rp, + ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(stmt), + ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(stmt), + ); + + const builtin_node = try transCreateNodeBuiltinFnCall(rp.c, "@sizeOf"); + try builtin_node.params.push(type_node); + builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + return maybeSuppressResult(rp, scope, result_used, &builtin_node.base); +} + +fn qualTypeHaswrappingOverflow(qt: ZigClangQualType) bool { + if (cIsSignedInteger(qt) or cIsFloating(qt)) { + // float and signed integer overflow is undefined behavior. + return false; + } else { + // unsigned integer overflow wraps around. + return true; + } +} + +fn transUnaryOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnaryOperator, used: ResultUsed) TransError!*ast.Node { + const op_expr = ZigClangUnaryOperator_getSubExpr(stmt); + switch (ZigClangUnaryOperator_getOpcode(stmt)) { + .PostInc => if (qualTypeHaswrappingOverflow(ZigClangUnaryOperator_getType(stmt))) + return transCreatePostCrement(rp, scope, stmt, .AssignAddWrap, .PlusPercentEqual, "+%=", used) + else + return transCreatePostCrement(rp, scope, stmt, .AssignAdd, .PlusEqual, "+=", used), + .PostDec => if (qualTypeHaswrappingOverflow(ZigClangUnaryOperator_getType(stmt))) + return transCreatePostCrement(rp, scope, stmt, .AssignSubWrap, .MinusPercentEqual, "-%=", used) + else + return transCreatePostCrement(rp, scope, stmt, .AssignSub, .MinusEqual, "-=", used), + .PreInc => if (qualTypeHaswrappingOverflow(ZigClangUnaryOperator_getType(stmt))) + return transCreatePreCrement(rp, scope, stmt, .AssignAddWrap, .PlusPercentEqual, "+%=", used) + else + return transCreatePreCrement(rp, scope, stmt, .AssignAdd, .PlusEqual, "+=", used), + .PreDec => if (qualTypeHaswrappingOverflow(ZigClangUnaryOperator_getType(stmt))) + return transCreatePreCrement(rp, scope, stmt, .AssignSubWrap, .MinusPercentEqual, "-%=", used) + else + return transCreatePreCrement(rp, scope, stmt, .AssignSub, .MinusEqual, "-=", used), + .AddrOf => { + const op_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); + op_node.rhs = try transExpr(rp, scope, op_expr, used, .r_value); + return &op_node.base; + }, + .Deref => { + const value_node = try transExpr(rp, scope, op_expr, used, .r_value); + var is_ptr = false; + const fn_ty = qualTypeGetFnProto(ZigClangExpr_getType(op_expr), &is_ptr); + if (fn_ty != null and is_ptr) + return value_node; + const unwrapped = try transCreateNodeUnwrapNull(rp.c, value_node); + return transCreateNodePtrDeref(rp.c, unwrapped); + }, + .Plus => return transExpr(rp, scope, op_expr, used, .r_value), + .Minus => { + if (!qualTypeHaswrappingOverflow(ZigClangExpr_getType(op_expr))) { + const op_node = try transCreateNodePrefixOp(rp.c, .Negation, .Minus, "-"); + op_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value); + return &op_node.base; + } else if (cIsUnsignedInteger(ZigClangExpr_getType(op_expr))) { + // we gotta emit 0 -% x + const zero = try transCreateNodeInt(rp.c, 0); + const token = try appendToken(rp.c, .MinusPercent, "-%"); + const expr = try transExpr(rp, scope, op_expr, .used, .r_value); + return transCreateNodeInfixOp(rp, scope, zero, .SubWrap, token, expr, used, true); + } else + return revertAndWarn(rp, error.UnsupportedTranslation, ZigClangUnaryOperator_getBeginLoc(stmt), "C negation with non float non integer", .{}); + }, + .Not => { + const op_node = try transCreateNodePrefixOp(rp.c, .BitNot, .Tilde, "~"); + op_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value); + return &op_node.base; + }, + .LNot => { + const op_node = try transCreateNodePrefixOp(rp.c, .BoolNot, .Bang, "!"); + op_node.rhs = try transBoolExpr(rp, scope, op_expr, .used, .r_value, true); + return &op_node.base; + }, + .Extension => { + return transExpr(rp, scope, ZigClangUnaryOperator_getSubExpr(stmt), used, .l_value); + }, + else => return revertAndWarn(rp, error.UnsupportedTranslation, ZigClangUnaryOperator_getBeginLoc(stmt), "unsupported C translation {}", .{ZigClangUnaryOperator_getOpcode(stmt)}), + } +} + +fn transCreatePreCrement( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangUnaryOperator, + op: ast.Node.InfixOp.Op, + op_tok_id: std.zig.Token.Id, + bytes: []const u8, + used: ResultUsed, +) TransError!*ast.Node { + const op_expr = ZigClangUnaryOperator_getSubExpr(stmt); + + if (used == .unused) { + // common case + // c: ++expr + // zig: expr += 1 + const expr = try transExpr(rp, scope, op_expr, .used, .r_value); + const token = try appendToken(rp.c, op_tok_id, bytes); + const one = try transCreateNodeInt(rp.c, 1); + _ = try appendToken(rp.c, .Semicolon, ";"); + return transCreateNodeInfixOp(rp, scope, expr, op, token, one, .used, false); + } + // worst case + // c: ++expr + // zig: (blk: { + // zig: const _ref = &expr; + // zig: _ref.* += 1; + // zig: break :blk _ref.* + // zig: }) + const block_scope = try Scope.Block.init(rp.c, scope, "blk"); + block_scope.block_node = try transCreateNodeBlock(rp.c, block_scope.label); + const ref = try std.fmt.allocPrint(rp.c.a(), "_ref_{}", .{rp.c.getMangle()}); + + const node = try transCreateNodeVarDecl(rp.c, false, true, ref); + node.eq_token = try appendToken(rp.c, .Equal, "="); + const rhs_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); + rhs_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value); + node.init_node = &rhs_node.base; + node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + try block_scope.block_node.statements.push(&node.base); + + const lhs_node = try transCreateNodeIdentifier(rp.c, ref); + const ref_node = try transCreateNodePtrDeref(rp.c, lhs_node); + _ = try appendToken(rp.c, .Semicolon, ";"); + const token = try appendToken(rp.c, op_tok_id, bytes); + const one = try transCreateNodeInt(rp.c, 1); + _ = try appendToken(rp.c, .Semicolon, ";"); + const assign = try transCreateNodeInfixOp(rp, scope, ref_node, op, token, one, .used, false); + try block_scope.block_node.statements.push(assign); + + const break_node = try transCreateNodeBreak(rp.c, block_scope.label); + break_node.rhs = ref_node; + try block_scope.block_node.statements.push(&break_node.base); + block_scope.block_node.rbrace = try appendToken(rp.c, .RBrace, "}"); + // semicolon must immediately follow rbrace because it is the last token in a block + _ = try appendToken(rp.c, .Semicolon, ";"); + const grouped_expr = try rp.c.a().create(ast.Node.GroupedExpression); + grouped_expr.* = .{ + .lparen = try appendToken(rp.c, .LParen, "("), + .expr = &block_scope.block_node.base, + .rparen = try appendToken(rp.c, .RParen, ")"), + }; + return &grouped_expr.base; +} + +fn transCreatePostCrement( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangUnaryOperator, + op: ast.Node.InfixOp.Op, + op_tok_id: std.zig.Token.Id, + bytes: []const u8, + used: ResultUsed, +) TransError!*ast.Node { + const op_expr = ZigClangUnaryOperator_getSubExpr(stmt); + + if (used == .unused) { + // common case + // c: ++expr + // zig: expr += 1 + const expr = try transExpr(rp, scope, op_expr, .used, .r_value); + const token = try appendToken(rp.c, op_tok_id, bytes); + const one = try transCreateNodeInt(rp.c, 1); + _ = try appendToken(rp.c, .Semicolon, ";"); + return transCreateNodeInfixOp(rp, scope, expr, op, token, one, .used, false); + } + // worst case + // c: expr++ + // zig: (blk: { + // zig: const _ref = &expr; + // zig: const _tmp = _ref.*; + // zig: _ref.* += 1; + // zig: break :blk _tmp + // zig: }) + const block_scope = try Scope.Block.init(rp.c, scope, "blk"); + block_scope.block_node = try transCreateNodeBlock(rp.c, block_scope.label); + const ref = try std.fmt.allocPrint(rp.c.a(), "_ref_{}", .{rp.c.getMangle()}); + + const node = try transCreateNodeVarDecl(rp.c, false, true, ref); + node.eq_token = try appendToken(rp.c, .Equal, "="); + const rhs_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); + rhs_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value); + node.init_node = &rhs_node.base; + node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + try block_scope.block_node.statements.push(&node.base); + + const lhs_node = try transCreateNodeIdentifier(rp.c, ref); + const ref_node = try transCreateNodePtrDeref(rp.c, lhs_node); + _ = try appendToken(rp.c, .Semicolon, ";"); + + const tmp = try std.fmt.allocPrint(rp.c.a(), "_tmp_{}", .{rp.c.getMangle()}); + const tmp_node = try transCreateNodeVarDecl(rp.c, false, true, tmp); + tmp_node.eq_token = try appendToken(rp.c, .Equal, "="); + tmp_node.init_node = ref_node; + tmp_node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + try block_scope.block_node.statements.push(&tmp_node.base); + + const token = try appendToken(rp.c, op_tok_id, bytes); + const one = try transCreateNodeInt(rp.c, 1); + _ = try appendToken(rp.c, .Semicolon, ";"); + const assign = try transCreateNodeInfixOp(rp, scope, ref_node, op, token, one, .used, false); + try block_scope.block_node.statements.push(assign); + + const break_node = try transCreateNodeBreak(rp.c, block_scope.label); + break_node.rhs = try transCreateNodeIdentifier(rp.c, tmp); + try block_scope.block_node.statements.push(&break_node.base); + _ = try appendToken(rp.c, .Semicolon, ";"); + block_scope.block_node.rbrace = try appendToken(rp.c, .RBrace, "}"); + const grouped_expr = try rp.c.a().create(ast.Node.GroupedExpression); + grouped_expr.* = .{ + .lparen = try appendToken(rp.c, .LParen, "("), + .expr = &block_scope.block_node.base, + .rparen = try appendToken(rp.c, .RParen, ")"), + }; + return &grouped_expr.base; +} + +fn transCompoundAssignOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangCompoundAssignOperator, used: ResultUsed) TransError!*ast.Node { + switch (ZigClangCompoundAssignOperator_getOpcode(stmt)) { + .MulAssign => if (qualTypeHaswrappingOverflow(ZigClangCompoundAssignOperator_getType(stmt))) + return transCreateCompoundAssign(rp, scope, stmt, .AssignMulWrap, .AsteriskPercentEqual, "*%=", .MulWrap, .AsteriskPercent, "*%", used) + else + return transCreateCompoundAssign(rp, scope, stmt, .AssignMul, .AsteriskEqual, "*=", .Mul, .Asterisk, "*", used), + .AddAssign => if (qualTypeHaswrappingOverflow(ZigClangCompoundAssignOperator_getType(stmt))) + return transCreateCompoundAssign(rp, scope, stmt, .AssignAddWrap, .PlusPercentEqual, "+%=", .AddWrap, .PlusPercent, "+%", used) + else + return transCreateCompoundAssign(rp, scope, stmt, .AssignAdd, .PlusEqual, "+=", .Add, .Plus, "+", used), + .SubAssign => if (qualTypeHaswrappingOverflow(ZigClangCompoundAssignOperator_getType(stmt))) + return transCreateCompoundAssign(rp, scope, stmt, .AssignSubWrap, .MinusPercentEqual, "-%=", .SubWrap, .MinusPercent, "-%", used) + else + return transCreateCompoundAssign(rp, scope, stmt, .AssignSub, .MinusPercentEqual, "-=", .Sub, .Minus, "-", used), + .ShlAssign => return transCreateCompoundAssign(rp, scope, stmt, .AssignBitShiftLeft, .AngleBracketAngleBracketLeftEqual, "<<=", .BitShiftLeft, .AngleBracketAngleBracketLeft, "<<", used), + .ShrAssign => return transCreateCompoundAssign(rp, scope, stmt, .AssignBitShiftRight, .AngleBracketAngleBracketRightEqual, ">>=", .BitShiftRight, .AngleBracketAngleBracketRight, ">>", used), + .AndAssign => return transCreateCompoundAssign(rp, scope, stmt, .AssignBitAnd, .AmpersandEqual, "&=", .BitAnd, .Ampersand, "&", used), + .XorAssign => return transCreateCompoundAssign(rp, scope, stmt, .AssignBitXor, .CaretEqual, "^=", .BitXor, .Caret, "^", used), + .OrAssign => return transCreateCompoundAssign(rp, scope, stmt, .AssignBitOr, .PipeEqual, "|=", .BitOr, .Pipe, "|", used), + else => return revertAndWarn( + rp, + error.UnsupportedTranslation, + ZigClangCompoundAssignOperator_getBeginLoc(stmt), + "unsupported C translation {}", + .{ZigClangCompoundAssignOperator_getOpcode(stmt)}, + ), + } +} + +fn transCreateCompoundAssign( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangCompoundAssignOperator, + assign_op: ast.Node.InfixOp.Op, + assign_tok_id: std.zig.Token.Id, + assign_bytes: []const u8, + bin_op: ast.Node.InfixOp.Op, + bin_tok_id: std.zig.Token.Id, + bin_bytes: []const u8, + used: ResultUsed, +) TransError!*ast.Node { + const is_shift = bin_op == .BitShiftLeft or bin_op == .BitShiftRight; + const lhs = ZigClangCompoundAssignOperator_getLHS(stmt); + const rhs = ZigClangCompoundAssignOperator_getRHS(stmt); + const loc = ZigClangCompoundAssignOperator_getBeginLoc(stmt); + if (used == .unused) { + // common case + // c: lhs += rhs + // zig: lhs += rhs + const lhs_node = try transExpr(rp, scope, lhs, .used, .l_value); + const eq_token = try appendToken(rp.c, assign_tok_id, assign_bytes); + var rhs_node = try transExpr(rp, scope, rhs, .used, .r_value); + + if (is_shift) { + const as_node = try transCreateNodeBuiltinFnCall(rp.c, "@as"); + const rhs_type = try qualTypeToLog2IntRef(rp, getExprQualType(rp.c, rhs), loc); + try as_node.params.push(rhs_type); + _ = try appendToken(rp.c, .Comma, ","); + try as_node.params.push(rhs_node); + as_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + rhs_node = &as_node.base; + } + if (scope.id != .Condition) + _ = try appendToken(rp.c, .Semicolon, ";"); + return transCreateNodeInfixOp(rp, scope, lhs_node, assign_op, eq_token, rhs_node, .used, false); + } + // worst case + // c: lhs += rhs + // zig: (blk: { + // zig: const _ref = &lhs; + // zig: _ref.* = _ref.* + rhs; + // zig: break :blk _ref.* + // zig: }) + const block_scope = try Scope.Block.init(rp.c, scope, "blk"); + block_scope.block_node = try transCreateNodeBlock(rp.c, block_scope.label); + const ref = try std.fmt.allocPrint(rp.c.a(), "_ref_{}", .{rp.c.getMangle()}); + + const node = try transCreateNodeVarDecl(rp.c, false, true, ref); + node.eq_token = try appendToken(rp.c, .Equal, "="); + const addr_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); + addr_node.rhs = try transExpr(rp, scope, lhs, .used, .l_value); + node.init_node = &addr_node.base; + node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + try block_scope.block_node.statements.push(&node.base); + + const lhs_node = try transCreateNodeIdentifier(rp.c, ref); + const ref_node = try transCreateNodePtrDeref(rp.c, lhs_node); + _ = try appendToken(rp.c, .Semicolon, ";"); + const bin_token = try appendToken(rp.c, bin_tok_id, bin_bytes); + var rhs_node = try transExpr(rp, scope, rhs, .used, .r_value); + if (is_shift) { + const as_node = try transCreateNodeBuiltinFnCall(rp.c, "@as"); + const rhs_type = try qualTypeToLog2IntRef(rp, getExprQualType(rp.c, rhs), loc); + try as_node.params.push(rhs_type); + _ = try appendToken(rp.c, .Comma, ","); + try as_node.params.push(rhs_node); + as_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + rhs_node = &as_node.base; + } + const rhs_bin = try transCreateNodeInfixOp(rp, scope, ref_node, bin_op, bin_token, rhs_node, .used, false); + + _ = try appendToken(rp.c, .Semicolon, ";"); + + const eq_token = try appendToken(rp.c, .Equal, "="); + const assign = try transCreateNodeInfixOp(rp, scope, ref_node, .Assign, eq_token, rhs_bin, .used, false); + try block_scope.block_node.statements.push(assign); + + const break_node = try transCreateNodeBreak(rp.c, block_scope.label); + break_node.rhs = ref_node; + try block_scope.block_node.statements.push(&break_node.base); + block_scope.block_node.rbrace = try appendToken(rp.c, .RBrace, "}"); + // semicolon must immediately follow rbrace because it is the last token in a block + _ = try appendToken(rp.c, .Semicolon, ";"); + const grouped_expr = try rp.c.a().create(ast.Node.GroupedExpression); + grouped_expr.* = .{ + .lparen = try appendToken(rp.c, .LParen, "("), + .expr = &block_scope.block_node.base, + .rparen = try appendToken(rp.c, .RParen, ")"), + }; + return &grouped_expr.base; +} + fn transCPtrCast( rp: RestorePoint, loc: ZigClangSourceLocation, @@ -1252,6 +2560,61 @@ fn transCPtrCast( return &ptrcast_node.base; } +fn transBreak(rp: RestorePoint, scope: *Scope) TransError!*ast.Node { + const break_scope = scope.getBreakableScope(); + const br = try transCreateNodeBreak(rp.c, if (break_scope.id == .Switch) + "__switch" + else + null); + _ = try appendToken(rp.c, .Semicolon, ";"); + return &br.base; +} + +fn transFloatingLiteral(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangFloatingLiteral, used: ResultUsed) TransError!*ast.Node { + // TODO use something more accurate + const dbl = ZigClangAPFloat_getValueAsApproximateDouble(stmt); + const node = try rp.c.a().create(ast.Node.FloatLiteral); + node.* = .{ + .token = try appendTokenFmt(rp.c, .FloatLiteral, "{d}", .{dbl}), + }; + return maybeSuppressResult(rp, scope, used, &node.base); +} + +fn transConditionalOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangConditionalOperator, used: ResultUsed) TransError!*ast.Node { + const grouped = scope.id == .Condition; + const lparen = if (grouped) try appendToken(rp.c, .LParen, "(") else undefined; + const if_node = try transCreateNodeIf(rp.c); + var cond_scope = Scope{ + .parent = scope, + .id = .Condition, + }; + + const cond_expr = ZigClangConditionalOperator_getCond(stmt); + const true_expr = ZigClangConditionalOperator_getTrueExpr(stmt); + const false_expr = ZigClangConditionalOperator_getFalseExpr(stmt); + + if_node.condition = try transBoolExpr(rp, &cond_scope, cond_expr, .used, .r_value, false); + _ = try appendToken(rp.c, .RParen, ")"); + + if_node.body = try transExpr(rp, scope, true_expr, .used, .r_value); + + if_node.@"else" = try transCreateNodeElse(rp.c); + if_node.@"else".?.body = try transExpr(rp, scope, false_expr, .used, .r_value); + + if (grouped) { + const rparen = try appendToken(rp.c, .RParen, ")"); + const grouped_expr = try rp.c.a().create(ast.Node.GroupedExpression); + grouped_expr.* = .{ + .lparen = lparen, + .expr = &if_node.base, + .rparen = rparen, + }; + return maybeSuppressResult(rp, scope, used, &grouped_expr.base); + } else { + return maybeSuppressResult(rp, scope, used, &if_node.base); + } +} + fn maybeSuppressResult( rp: RestorePoint, scope: *Scope, @@ -1259,12 +2622,20 @@ fn maybeSuppressResult( result: *ast.Node, ) TransError!*ast.Node { if (used == .used) return result; - // NOTE: This is backwards, but the semicolon must immediately follow the node. - _ = try appendToken(rp.c, .Semicolon, ";"); + if (scope.id != .Condition) { + // NOTE: This is backwards, but the semicolon must immediately follow the node. + _ = try appendToken(rp.c, .Semicolon, ";"); + } else { // TODO is there a way to avoid this hack? + // this parenthesis must come immediately following the node + _ = try appendToken(rp.c, .RParen, ")"); + // these need to come before _ + _ = try appendToken(rp.c, .Colon, ":"); + _ = try appendToken(rp.c, .LParen, "("); + } const lhs = try transCreateNodeIdentifier(rp.c, "_"); const op_token = try appendToken(rp.c, .Equal, "="); const op_node = try rp.c.a().create(ast.Node.InfixOp); - op_node.* = ast.Node.InfixOp{ + op_node.* = .{ .op_token = op_token, .lhs = lhs, .op = .Assign, @@ -1282,264 +2653,6 @@ fn transQualType(rp: RestorePoint, qt: ZigClangQualType, source_loc: ZigClangSou return transType(rp, ZigClangQualType_getTypePtr(qt), source_loc); } -fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) TypeError!*ast.Node { - const rp = makeRestorePoint(c); - - const record_loc = ZigClangRecordDecl_getLocation(record_decl); - - var container_kind_name: []const u8 = undefined; - var container_kind: std.zig.Token.Id = undefined; - - if (ZigClangRecordDecl_isUnion(record_decl)) { - container_kind_name = "union"; - container_kind = .Keyword_union; - } else if (ZigClangRecordDecl_isStruct(record_decl)) { - container_kind_name = "struct"; - container_kind = .Keyword_struct; - } else { - return revertAndWarn( - rp, - error.UnsupportedType, - record_loc, - "unsupported record type", - .{}, - ); - } - - const record_def = ZigClangRecordDecl_getDefinition(record_decl) orelse { - return transCreateNodeOpaqueType(c); - }; - - const extern_tok = try appendToken(c, .Keyword_extern, "extern"); - const container_tok = try appendToken(c, container_kind, container_kind_name); - const lbrace_token = try appendToken(c, .LBrace, "{"); - - const container_node = try c.a().create(ast.Node.ContainerDecl); - container_node.* = .{ - .layout_token = extern_tok, - .kind_token = container_tok, - .init_arg_expr = .None, - .fields_and_decls = ast.Node.ContainerDecl.DeclList.init(c.a()), - .lbrace_token = lbrace_token, - .rbrace_token = undefined, - }; - - var it = ZigClangRecordDecl_field_begin(record_def); - const end_it = ZigClangRecordDecl_field_end(record_def); - while (ZigClangRecordDecl_field_iterator_neq(it, end_it)) : (it = ZigClangRecordDecl_field_iterator_next(it)) { - const field_decl = ZigClangRecordDecl_field_iterator_deref(it); - const field_loc = ZigClangFieldDecl_getLocation(field_decl); - - if (ZigClangFieldDecl_isBitField(field_decl)) { - rp.activate(); - const node = try transCreateNodeOpaqueType(c); - try emitWarning(c, field_loc, "{} demoted to opaque type - has bitfield", .{container_kind_name}); - return node; - } - - const field_name = try appendIdentifier(c, try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, field_decl)))); - _ = try appendToken(c, .Colon, ":"); - const field_type = try transQualType(rp, ZigClangFieldDecl_getType(field_decl), field_loc); - - const field_node = try c.a().create(ast.Node.ContainerField); - field_node.* = .{ - .doc_comments = null, - .comptime_token = null, - .name_token = field_name, - .type_expr = field_type, - .value_expr = null, - .align_expr = null, - }; - - try container_node.fields_and_decls.push(&field_node.base); - _ = try appendToken(c, .Comma, ","); - } - - container_node.rbrace_token = try appendToken(c, .RBrace, "}"); - return &container_node.base; -} - -fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.Node { - if (c.decl_table.get(@ptrToInt(ZigClangEnumDecl_getCanonicalDecl(enum_decl)))) |name| - return try transCreateNodeIdentifier(c, name.value); // Avoid processing this decl twice - const rp = makeRestorePoint(c); - const enum_loc = ZigClangEnumDecl_getLocation(enum_decl); - - const visib_tok = try appendToken(c, .Keyword_pub, "pub"); - const const_tok = try appendToken(c, .Keyword_const, "const"); - - var bare_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, enum_decl))); - var is_unnamed = false; - if (bare_name.len == 0) { - bare_name = try std.fmt.allocPrint(c.a(), "unnamed_{}", .{c.getMangle()}); - is_unnamed = true; - } - - const name = try std.fmt.allocPrint(c.a(), "enum_{}", .{bare_name}); - _ = try c.decl_table.put(@ptrToInt(ZigClangEnumDecl_getCanonicalDecl(enum_decl)), name); - const name_tok = try appendIdentifier(c, name); - const eq_tok = try appendToken(c, .Equal, "="); - - const init_node = if (ZigClangEnumDecl_getDefinition(enum_decl)) |enum_def| blk: { - var pure_enum = true; - var it = ZigClangEnumDecl_enumerator_begin(enum_def); - var end_it = ZigClangEnumDecl_enumerator_end(enum_def); - while (ZigClangEnumDecl_enumerator_iterator_neq(it, end_it)) : (it = ZigClangEnumDecl_enumerator_iterator_next(it)) { - const enum_const = ZigClangEnumDecl_enumerator_iterator_deref(it); - if (ZigClangEnumConstantDecl_getInitExpr(enum_const)) |_| { - pure_enum = false; - break; - } - } - - const extern_tok = try appendToken(c, .Keyword_extern, "extern"); - const container_tok = try appendToken(c, .Keyword_enum, "enum"); - - const container_node = try c.a().create(ast.Node.ContainerDecl); - container_node.* = .{ - .layout_token = extern_tok, - .kind_token = container_tok, - .init_arg_expr = .None, - .fields_and_decls = ast.Node.ContainerDecl.DeclList.init(c.a()), - .lbrace_token = undefined, - .rbrace_token = undefined, - }; - - const int_type = ZigClangEnumDecl_getIntegerType(enum_decl); - - // TODO only emit this tag type if the enum tag type is not the default. - // I don't know what the default is, need to figure out how clang is deciding. - // it appears to at least be different across gcc/msvc - if (!isCBuiltinType(int_type, .UInt) and - !isCBuiltinType(int_type, .Int)) - { - _ = try appendToken(c, .LParen, "("); - container_node.init_arg_expr = .{ - .Type = transQualType(rp, int_type, enum_loc) catch |err| switch (err) { - error.UnsupportedType => { - if (is_unnamed) { - try emitWarning(c, enum_loc, "unable to translate enum tag type", .{}); - } else { - try failDecl(c, enum_loc, name, "unable to translate enum tag type", .{}); - } - return null; - }, - else => |e| return e, - }, - }; - _ = try appendToken(c, .RParen, ")"); - } - - container_node.lbrace_token = try appendToken(c, .LBrace, "{"); - - it = ZigClangEnumDecl_enumerator_begin(enum_def); - end_it = ZigClangEnumDecl_enumerator_end(enum_def); - while (ZigClangEnumDecl_enumerator_iterator_neq(it, end_it)) : (it = ZigClangEnumDecl_enumerator_iterator_next(it)) { - const enum_const = ZigClangEnumDecl_enumerator_iterator_deref(it); - - const enum_val_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, enum_const))); - - const field_name = if (!is_unnamed and std.mem.startsWith(u8, enum_val_name, bare_name)) - enum_val_name[bare_name.len..] - else - enum_val_name; - - const field_name_tok = try appendIdentifier(c, field_name); - - const int_node = if (!pure_enum) blk: { - _ = try appendToken(c, .Colon, "="); - break :blk try transCreateNodeAPInt(c, ZigClangEnumConstantDecl_getInitVal(enum_const)); - } else - null; - - const field_node = try c.a().create(ast.Node.ContainerField); - field_node.* = .{ - .doc_comments = null, - .comptime_token = null, - .name_token = field_name_tok, - .type_expr = null, - .value_expr = int_node, - .align_expr = null, - }; - - try container_node.fields_and_decls.push(&field_node.base); - _ = try appendToken(c, .Comma, ","); - // In C each enum value is in the global namespace. So we put them there too. - // At this point we can rely on the enum emitting successfully. - try addEnumTopLevel(c, name, field_name, enum_val_name); - } - container_node.rbrace_token = try appendToken(c, .RBrace, "}"); - - break :blk &container_node.base; - } else - try transCreateNodeOpaqueType(c); - - const semicolon_token = try appendToken(c, .Semicolon, ";"); - - const node = try c.a().create(ast.Node.VarDecl); - node.* = ast.Node.VarDecl{ - .visib_token = visib_tok, - .mut_token = const_tok, - .name_token = name_tok, - .eq_token = eq_tok, - .init_node = init_node, - .semicolon_token = semicolon_token, - .doc_comments = null, - .comptime_token = null, - .extern_export_token = null, - .thread_local_token = null, - .lib_name = null, - .type_node = null, - .align_node = null, - .section_node = null, - }; - - try addTopLevelDecl(c, name, &node.base); - if (!is_unnamed) - try c.alias_list.push(.{ .alias = bare_name, .name = name }); - return transCreateNodeIdentifier(c, name); -} - -fn addEnumTopLevel(c: *Context, enum_name: []const u8, field_name: []const u8, enum_val_name: []const u8) !void { - const visib_tok = try appendToken(c, .Keyword_pub, "pub"); - const const_tok = try appendToken(c, .Keyword_const, "const"); - const name_tok = try appendIdentifier(c, enum_val_name); - const eq_tok = try appendToken(c, .Equal, "="); - - const enum_ident = try transCreateNodeIdentifier(c, enum_name); - const period_tok = try appendToken(c, .Period, "."); - const field_ident = try transCreateNodeIdentifier(c, field_name); - - const field_access_node = try c.a().create(ast.Node.InfixOp); - field_access_node.* = .{ - .op_token = period_tok, - .lhs = enum_ident, - .op = .Period, - .rhs = field_ident, - }; - const semicolon_token = try appendToken(c, .Semicolon, ";"); - - const node = try c.a().create(ast.Node.VarDecl); - node.* = ast.Node.VarDecl{ - .visib_token = visib_tok, - .mut_token = const_tok, - .name_token = name_tok, - .eq_token = eq_tok, - .init_node = &field_access_node.base, - .semicolon_token = semicolon_token, - .thread_local_token = null, - .doc_comments = null, - .comptime_token = null, - .extern_export_token = null, - .lib_name = null, - .type_node = null, - .align_node = null, - .section_node = null, - }; - - try addTopLevelDecl(c, field_name, &node.base); -} - fn isCBuiltinType(qt: ZigClangQualType, kind: ZigClangBuiltinTypeKind) bool { const c_type = qualTypeCanon(qt); if (ZigClangType_getTypeClass(c_type) != .Builtin) @@ -1552,6 +2665,95 @@ fn qualTypeIsPtr(qt: ZigClangQualType) bool { return ZigClangType_getTypeClass(qualTypeCanon(qt)) == .Pointer; } +fn qualTypeIntBitWidth(rp: RestorePoint, qt: ZigClangQualType, source_loc: ZigClangSourceLocation) !u32 { + const ty = ZigClangQualType_getTypePtr(qt); + + switch (ZigClangType_getTypeClass(ty)) { + .Builtin => { + const builtin_ty = @ptrCast(*const ZigClangBuiltinType, ty); + + switch (ZigClangBuiltinType_getKind(builtin_ty)) { + .Char_U, + .UChar, + .Char_S, + .SChar, + => return 8, + .UInt128, + .Int128, + => return 128, + else => return 0, + } + + unreachable; + }, + .Typedef => { + const typedef_ty = @ptrCast(*const ZigClangTypedefType, ty); + const typedef_decl = ZigClangTypedefType_getDecl(typedef_ty); + const type_name = try rp.c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, typedef_decl))); + + if (mem.eql(u8, type_name, "uint8_t") or mem.eql(u8, type_name, "int8_t")) { + return 8; + } else if (mem.eql(u8, type_name, "uint16_t") or mem.eql(u8, type_name, "int16_t")) { + return 16; + } else if (mem.eql(u8, type_name, "uint32_t") or mem.eql(u8, type_name, "int32_t")) { + return 32; + } else if (mem.eql(u8, type_name, "uint64_t") or mem.eql(u8, type_name, "int64_t")) { + return 64; + } else { + return 0; + } + }, + else => return 0, + } + + unreachable; +} + +fn qualTypeToLog2IntRef(rp: RestorePoint, qt: ZigClangQualType, source_loc: ZigClangSourceLocation) !*ast.Node { + const int_bit_width = try qualTypeIntBitWidth(rp, qt, source_loc); + + if (int_bit_width != 0) { + // we can perform the log2 now. + const cast_bit_width = std.math.log2_int(u64, int_bit_width); + const node = try rp.c.a().create(ast.Node.IntegerLiteral); + node.* = ast.Node.IntegerLiteral{ + .token = try appendTokenFmt(rp.c, .Identifier, "u{}", .{cast_bit_width}), + }; + return &node.base; + } + + const zig_type_node = try transQualType(rp, qt, source_loc); + + // @import("std").math.Log2Int(c_long); + // + // FnCall + // FieldAccess + // FieldAccess + // FnCall (.builtin = true) + // Symbol "import" + // StringLiteral "std" + // Symbol "math" + // Symbol "Log2Int" + // Symbol (var from above) + + const import_fn_call = try transCreateNodeBuiltinFnCall(rp.c, "@import"); + const std_token = try appendToken(rp.c, .StringLiteral, "\"std\""); + const std_node = try rp.c.a().create(ast.Node.StringLiteral); + std_node.* = ast.Node.StringLiteral{ + .token = std_token, + }; + try import_fn_call.params.push(&std_node.base); + import_fn_call.rparen_token = try appendToken(rp.c, .RParen, ")"); + + const inner_field_access = try transCreateNodeFieldAccess(rp.c, &import_fn_call.base, "math"); + const outer_field_access = try transCreateNodeFieldAccess(rp.c, inner_field_access, "Log2Int"); + const log2int_fn_call = try transCreateNodeFnCall(rp.c, outer_field_access); + try @fieldParentPtr(ast.Node.SuffixOp, "base", &log2int_fn_call.base).op.Call.params.push(zig_type_node); + log2int_fn_call.rtoken = try appendToken(rp.c, .RParen, ")"); + + return &log2int_fn_call.base; +} + fn qualTypeChildIsFnProto(qt: ZigClangQualType) bool { const ty = ZigClangQualType_getTypePtr(qt); @@ -1601,6 +2803,14 @@ fn getExprQualType(c: *Context, expr: *const ZigClangExpr) ZigClangQualType { return ZigClangExpr_getType(expr); } +fn getExprQualTypeBeforeImplicitCast(c: *Context, expr: *const ZigClangExpr) ZigClangQualType { + if (ZigClangExpr_getStmtClass(expr) == .ImplicitCastExprClass) { + const cast_expr = @ptrCast(*const ZigClangImplicitCastExpr, expr); + return getExprQualType(c, ZigClangImplicitCastExpr_getSubExpr(cast_expr)); + } + return ZigClangExpr_getType(expr); +} + fn typeIsOpaque(c: *Context, ty: *const ZigClangType, loc: ZigClangSourceLocation) bool { switch (ZigClangType_getTypeClass(ty)) { .Builtin => { @@ -1657,53 +2867,109 @@ fn cIsUnsignedInteger(qt: ZigClangQualType) bool { }; } +fn cIsSignedInteger(qt: ZigClangQualType) bool { + const c_type = qualTypeCanon(qt); + if (ZigClangType_getTypeClass(c_type) != .Builtin) return false; + const builtin_ty = @ptrCast(*const ZigClangBuiltinType, c_type); + return switch (ZigClangBuiltinType_getKind(builtin_ty)) { + .SChar, + .Short, + .Int, + .Long, + .LongLong, + .Int128, + .WChar_S, + => true, + else => false, + }; +} + +fn cIsFloating(qt: ZigClangQualType) bool { + const c_type = qualTypeCanon(qt); + if (ZigClangType_getTypeClass(c_type) != .Builtin) return false; + const builtin_ty = @ptrCast(*const ZigClangBuiltinType, c_type); + return switch (ZigClangBuiltinType_getKind(builtin_ty)) { + .Float, + .Double, + .Float128, + .LongDouble, + => true, + else => false, + }; +} + fn transCreateNodeAssign( rp: RestorePoint, scope: *Scope, result_used: ResultUsed, lhs: *const ZigClangExpr, rhs: *const ZigClangExpr, -) !*ast.Node.InfixOp { +) !*ast.Node { // common case // c: lhs = rhs // zig: lhs = rhs if (result_used == .unused) { const lhs_node = try transExpr(rp, scope, lhs, .used, .l_value); const eq_token = try appendToken(rp.c, .Equal, "="); - const rhs_node = try transExpr(rp, scope, rhs, .used, .r_value); - _ = try appendToken(rp.c, .Semicolon, ";"); - - const node = try rp.c.a().create(ast.Node.InfixOp); - node.* = .{ - .op_token = eq_token, - .lhs = lhs_node, - .op = .Assign, - .rhs = rhs_node, - }; - return node; + var rhs_node = try transExpr(rp, scope, rhs, .used, .r_value); + if (isBoolRes(rhs_node)) { + const builtin_node = try transCreateNodeBuiltinFnCall(rp.c, "@boolToInt"); + try builtin_node.params.push(rhs_node); + builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + rhs_node = &builtin_node.base; + } + if (scope.id != .Condition) + _ = try appendToken(rp.c, .Semicolon, ";"); + return transCreateNodeInfixOp(rp, scope, lhs_node, .Assign, eq_token, rhs_node, .used, false); } // worst case // c: lhs = rhs - // zig: (x: { + // zig: (blk: { // zig: const _tmp = rhs; // zig: lhs = _tmp; - // zig: break :x _tmp + // zig: break :blk _tmp // zig: }) - return revertAndWarn( - rp, - error.UnsupportedTranslation, - ZigClangExpr_getBeginLoc(lhs), - "TODO: worst case assign op expr", - .{}, - ); + const block_scope = try Scope.Block.init(rp.c, scope, "blk"); + block_scope.block_node = try transCreateNodeBlock(rp.c, block_scope.label); + const tmp = try std.fmt.allocPrint(rp.c.a(), "_tmp_{}", .{rp.c.getMangle()}); + + const node = try transCreateNodeVarDecl(rp.c, false, true, tmp); + node.eq_token = try appendToken(rp.c, .Equal, "="); + var rhs_node = try transExpr(rp, scope, rhs, .used, .r_value); + if (isBoolRes(rhs_node)) { + const builtin_node = try transCreateNodeBuiltinFnCall(rp.c, "@boolToInt"); + try builtin_node.params.push(rhs_node); + builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + rhs_node = &builtin_node.base; + } + node.init_node = rhs_node; + node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + try block_scope.block_node.statements.push(&node.base); + + const lhs_node = try transExpr(rp, scope, lhs, .used, .l_value); + const eq_token = try appendToken(rp.c, .Equal, "="); + const ident = try transCreateNodeIdentifier(rp.c, tmp); + _ = try appendToken(rp.c, .Semicolon, ";"); + + const assign = try transCreateNodeInfixOp(rp, scope, lhs_node, .Assign, eq_token, ident, .used, false); + try block_scope.block_node.statements.push(assign); + + const break_node = try transCreateNodeBreak(rp.c, block_scope.label); + break_node.rhs = try transCreateNodeIdentifier(rp.c, tmp); + _ = try appendToken(rp.c, .Semicolon, ";"); + try block_scope.block_node.statements.push(&break_node.base); + block_scope.block_node.rbrace = try appendToken(rp.c, .RBrace, "}"); + // semicolon must immediately follow rbrace because it is the last token in a block + _ = try appendToken(rp.c, .Semicolon, ";"); + return &block_scope.block_node.base; } fn transCreateNodeBuiltinFnCall(c: *Context, name: []const u8) !*ast.Node.BuiltinCall { const builtin_token = try appendToken(c, .Builtin, name); _ = try appendToken(c, .LParen, "("); const node = try c.a().create(ast.Node.BuiltinCall); - node.* = ast.Node.BuiltinCall{ + node.* = .{ .builtin_token = builtin_token, .params = ast.Node.BuiltinCall.ParamList.init(c.a()), .rparen_token = undefined, // set after appending args @@ -1714,10 +2980,10 @@ fn transCreateNodeBuiltinFnCall(c: *Context, name: []const u8) !*ast.Node.Builti fn transCreateNodeFnCall(c: *Context, fn_expr: *ast.Node) !*ast.Node.SuffixOp { _ = try appendToken(c, .LParen, "("); const node = try c.a().create(ast.Node.SuffixOp); - node.* = ast.Node.SuffixOp{ + node.* = .{ .lhs = .{ .node = fn_expr }, - .op = ast.Node.SuffixOp.Op{ - .Call = ast.Node.SuffixOp.Op.Call{ + .op = .{ + .Call = .{ .params = ast.Node.SuffixOp.Op.Call.ParamList.init(c.a()), .async_token = null, }, @@ -1727,6 +2993,17 @@ fn transCreateNodeFnCall(c: *Context, fn_expr: *ast.Node) !*ast.Node.SuffixOp { return node; } +fn transCreateNodeFieldAccess(c: *Context, container: *ast.Node, field_name: []const u8) !*ast.Node { + const field_access_node = try c.a().create(ast.Node.InfixOp); + field_access_node.* = .{ + .op_token = try appendToken(c, .Period, "."), + .lhs = container, + .op = .Period, + .rhs = try transCreateNodeIdentifier(c, field_name), + }; + return &field_access_node.base; +} + fn transCreateNodePrefixOp( c: *Context, op: ast.Node.PrefixOp.Op, @@ -1745,32 +3022,62 @@ fn transCreateNodePrefixOp( fn transCreateNodeInfixOp( rp: RestorePoint, scope: *Scope, - stmt: *const ZigClangBinaryOperator, + lhs_node: *ast.Node, op: ast.Node.InfixOp.Op, - op_tok_id: std.zig.Token.Id, - bytes: []const u8, + op_token: ast.TokenIndex, + rhs_node: *ast.Node, + used: ResultUsed, grouped: bool, ) !*ast.Node { - const lparen = if (grouped) try appendToken(rp.c, .LParen, "(") else undefined; - const lhs = try transExpr(rp, scope, ZigClangBinaryOperator_getLHS(stmt), .used, .l_value); - const op_token = try appendToken(rp.c, op_tok_id, bytes); - const rhs = try transExpr(rp, scope, ZigClangBinaryOperator_getRHS(stmt), .used, .r_value); + var lparen = if (grouped) + try appendToken(rp.c, .LParen, "(") + else + null; const node = try rp.c.a().create(ast.Node.InfixOp); - node.* = ast.Node.InfixOp{ + node.* = .{ .op_token = op_token, - .lhs = lhs, + .lhs = lhs_node, .op = op, - .rhs = rhs, + .rhs = rhs_node, }; - if (!grouped) return &node.base; + if (!grouped) return maybeSuppressResult(rp, scope, used, &node.base); const rparen = try appendToken(rp.c, .RParen, ")"); const grouped_expr = try rp.c.a().create(ast.Node.GroupedExpression); - grouped_expr.* = ast.Node.GroupedExpression{ - .lparen = lparen, + grouped_expr.* = .{ + .lparen = lparen.?, .expr = &node.base, .rparen = rparen, }; - return &grouped_expr.base; + return maybeSuppressResult(rp, scope, used, &grouped_expr.base); +} + +fn transCreateNodeBoolInfixOp( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangBinaryOperator, + op: ast.Node.InfixOp.Op, + used: ResultUsed, + grouped: bool, +) !*ast.Node { + std.debug.assert(op == .BoolAnd or op == .BoolOr); + + const lhs_hode = try transBoolExpr(rp, scope, ZigClangBinaryOperator_getLHS(stmt), .used, .l_value, true); + const op_token = if (op == .BoolAnd) + try appendToken(rp.c, .Keyword_and, "and") + else + try appendToken(rp.c, .Keyword_or, "or"); + const rhs = try transBoolExpr(rp, scope, ZigClangBinaryOperator_getRHS(stmt), .used, .r_value, true); + + return transCreateNodeInfixOp( + rp, + scope, + lhs_hode, + op, + op_token, + rhs, + used, + grouped, + ); } fn transCreateNodePtrType( @@ -1797,9 +3104,9 @@ fn transCreateNodePtrType( .Asterisk => try appendToken(c, .Asterisk, "*"), else => unreachable, }; - node.* = ast.Node.PrefixOp{ + node.* = .{ .op_token = op_token, - .op = ast.Node.PrefixOp.Op{ + .op = .{ .PtrType = .{ .const_token = if (is_const) try appendToken(c, .Keyword_const, "const") else null, .volatile_token = if (is_volatile) try appendToken(c, .Keyword_volatile, "volatile") else null, @@ -1810,11 +3117,17 @@ fn transCreateNodePtrType( return node; } -fn transCreateNodeAPInt(c: *Context, int: ?*const ZigClangAPSInt) !*ast.Node { - const num_limbs = ZigClangAPSInt_getNumWords(int.?); +fn transCreateNodeAPInt(c: *Context, int: *const ZigClangAPSInt) !*ast.Node { + const num_limbs = ZigClangAPSInt_getNumWords(int); + var aps_int = int; + const is_negative = ZigClangAPSInt_isSigned(int) and ZigClangAPSInt_isNegative(int); + if (is_negative) + aps_int = ZigClangAPSInt_negate(aps_int); var big = try std.math.big.Int.initCapacity(c.a(), num_limbs); + if (is_negative) + big.negate(); defer big.deinit(); - const data = ZigClangAPSInt_getRawData(int.?); + const data = ZigClangAPSInt_getRawData(aps_int); var i: @TypeOf(num_limbs) = 0; while (i < num_limbs) : (i += 1) big.limbs[i] = data[i]; const str = big.toString(c.a(), 10) catch |err| switch (err) { @@ -1823,16 +3136,18 @@ fn transCreateNodeAPInt(c: *Context, int: ?*const ZigClangAPSInt) !*ast.Node { }; const token = try appendToken(c, .IntegerLiteral, str); const node = try c.a().create(ast.Node.IntegerLiteral); - node.* = ast.Node.IntegerLiteral{ + node.* = .{ .token = token, }; + if (is_negative) + ZigClangAPSInt_free(aps_int); return &node.base; } fn transCreateNodeReturnExpr(c: *Context) !*ast.Node.ControlFlowExpression { const ltoken = try appendToken(c, .Keyword_return, "return"); const node = try c.a().create(ast.Node.ControlFlowExpression); - node.* = ast.Node.ControlFlowExpression{ + node.* = .{ .ltoken = ltoken, .kind = .Return, .rhs = null, @@ -1843,7 +3158,7 @@ fn transCreateNodeReturnExpr(c: *Context) !*ast.Node.ControlFlowExpression { fn transCreateNodeUndefinedLiteral(c: *Context) !*ast.Node { const token = try appendToken(c, .Keyword_undefined, "undefined"); const node = try c.a().create(ast.Node.UndefinedLiteral); - node.* = ast.Node.UndefinedLiteral{ + node.* = .{ .token = token, }; return &node.base; @@ -1852,7 +3167,7 @@ fn transCreateNodeUndefinedLiteral(c: *Context) !*ast.Node { fn transCreateNodeNullLiteral(c: *Context) !*ast.Node { const token = try appendToken(c, .Keyword_null, "null"); const node = try c.a().create(ast.Node.NullLiteral); - node.* = ast.Node.NullLiteral{ + node.* = .{ .token = token, }; return &node.base; @@ -1864,13 +3179,13 @@ fn transCreateNodeBoolLiteral(c: *Context, value: bool) !*ast.Node { else try appendToken(c, .Keyword_false, "false"); const node = try c.a().create(ast.Node.BoolLiteral); - node.* = ast.Node.BoolLiteral{ + node.* = .{ .token = token, }; return &node.base; } -fn transCreateNodeArrayInitializer(c: *Context, dot_tok: ast.TokenIndex) !*ast.Node.SuffixOp { +fn transCreateNodeContainerInitializer(c: *Context, dot_tok: ast.TokenIndex) !*ast.Node.SuffixOp { _ = try appendToken(c, .LBrace, "{"); const node = try c.a().create(ast.Node.SuffixOp); node.* = ast.Node.SuffixOp{ @@ -1886,7 +3201,7 @@ fn transCreateNodeArrayInitializer(c: *Context, dot_tok: ast.TokenIndex) !*ast.N fn transCreateNodeInt(c: *Context, int: var) !*ast.Node { const token = try appendTokenFmt(c, .IntegerLiteral, "{}", .{int}); const node = try c.a().create(ast.Node.IntegerLiteral); - node.* = ast.Node.IntegerLiteral{ + node.* = .{ .token = token, }; return &node.base; @@ -1907,7 +3222,7 @@ fn transCreateNodeOpaqueType(c: *Context) !*ast.Node { return &call_node.base; } -fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_alias_node: *ast.Node) !*ast.Node { +fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_alias: *ast.Node.FnProto) !*ast.Node { const scope = &c.global_scope.base; const pub_tok = try appendToken(c, .Keyword_pub, "pub"); @@ -1916,8 +3231,6 @@ fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_a const name_tok = try appendIdentifier(c, name); _ = try appendToken(c, .LParen, "("); - const proto_alias = proto_alias_node.cast(ast.Node.FnProto).?; - var fn_params = ast.Node.FnProto.ParamList.init(c.a()); var it = proto_alias.params.iterator(0); while (it.next()) |pn| { @@ -2046,6 +3359,172 @@ fn transCreateNodeBlock(c: *Context, label: ?[]const u8) !*ast.Node.Block { return block_node; } +fn transCreateNodeBreak(c: *Context, label: ?[]const u8) !*ast.Node.ControlFlowExpression { + const ltoken = try appendToken(c, .Keyword_break, "break"); + const label_node = if (label) |l| blk: { + _ = try appendToken(c, .Colon, ":"); + break :blk try transCreateNodeIdentifier(c, l); + } else null; + const node = try c.a().create(ast.Node.ControlFlowExpression); + node.* = .{ + .ltoken = ltoken, + .kind = .{ .Break = label_node }, + .rhs = null, + }; + return node; +} + +fn transCreateNodeVarDecl(c: *Context, is_pub: bool, is_const: bool, name: []const u8) !*ast.Node.VarDecl { + const visib_tok = if (is_pub) try appendToken(c, .Keyword_pub, "pub") else null; + const mut_tok = if (is_const) try appendToken(c, .Keyword_const, "const") else try appendToken(c, .Keyword_var, "var"); + const name_tok = try appendIdentifier(c, name); + + const node = try c.a().create(ast.Node.VarDecl); + node.* = .{ + .doc_comments = null, + .visib_token = visib_tok, + .thread_local_token = null, + .name_token = name_tok, + .eq_token = undefined, + .mut_token = mut_tok, + .comptime_token = null, + .extern_export_token = null, + .lib_name = null, + .type_node = null, + .align_node = null, + .section_node = null, + .init_node = null, + .semicolon_token = undefined, + }; + return node; +} + +fn transCreateNodeWhile(c: *Context) !*ast.Node.While { + const while_tok = try appendToken(c, .Keyword_while, "while"); + _ = try appendToken(c, .LParen, "("); + + const node = try c.a().create(ast.Node.While); + node.* = .{ + .label = null, + .inline_token = null, + .while_token = while_tok, + .condition = undefined, + .payload = null, + .continue_expr = null, + .body = undefined, + .@"else" = null, + }; + return node; +} + +fn transCreateNodeContinue(c: *Context) !*ast.Node { + const ltoken = try appendToken(c, .Keyword_continue, "continue"); + const node = try c.a().create(ast.Node.ControlFlowExpression); + node.* = .{ + .ltoken = ltoken, + .kind = .{ .Continue = null }, + .rhs = null, + }; + _ = try appendToken(c, .Semicolon, ";"); + return &node.base; +} + +fn transCreateNodeSwitch(c: *Context) !*ast.Node.Switch { + const switch_tok = try appendToken(c, .Keyword_switch, "switch"); + _ = try appendToken(c, .LParen, "("); + + const node = try c.a().create(ast.Node.Switch); + node.* = .{ + .switch_token = switch_tok, + .expr = undefined, + .cases = ast.Node.Switch.CaseList.init(c.a()), + .rbrace = undefined, + }; + return node; +} + +fn transCreateNodeSwitchCase(c: *Context, lhs: *ast.Node) !*ast.Node.SwitchCase { + const arrow_tok = try appendToken(c, .EqualAngleBracketRight, "=>"); + + const node = try c.a().create(ast.Node.SwitchCase); + node.* = .{ + .items = ast.Node.SwitchCase.ItemList.init(c.a()), + .arrow_token = arrow_tok, + .payload = null, + .expr = undefined, + }; + try node.items.push(lhs); + return node; +} + +fn transCreateNodeSwitchElse(c: *Context) !*ast.Node { + const node = try c.a().create(ast.Node.SwitchElse); + node.* = .{ + .token = try appendToken(c, .Keyword_else, "else"), + }; + return &node.base; +} + +fn transCreateNodeShiftOp( + rp: RestorePoint, + scope: *Scope, + stmt: *const ZigClangBinaryOperator, + op: ast.Node.InfixOp.Op, + op_tok_id: std.zig.Token.Id, + bytes: []const u8, +) !*ast.Node { + std.debug.assert(op == .BitShiftLeft or op == .BitShiftRight); + + const lhs_expr = ZigClangBinaryOperator_getLHS(stmt); + const rhs_expr = ZigClangBinaryOperator_getRHS(stmt); + const rhs_location = ZigClangExpr_getBeginLoc(rhs_expr); + // lhs >> @as(u5, rh) + + const lhs = try transExpr(rp, scope, lhs_expr, .used, .l_value); + const op_token = try appendToken(rp.c, op_tok_id, bytes); + + const as_node = try transCreateNodeBuiltinFnCall(rp.c, "@as"); + const rhs_type = try qualTypeToLog2IntRef(rp, ZigClangBinaryOperator_getType(stmt), rhs_location); + try as_node.params.push(rhs_type); + _ = try appendToken(rp.c, .Comma, ","); + const rhs = try transExpr(rp, scope, rhs_expr, .used, .r_value); + try as_node.params.push(rhs); + as_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + + const node = try rp.c.a().create(ast.Node.InfixOp); + node.* = ast.Node.InfixOp{ + .op_token = op_token, + .lhs = lhs, + .op = op, + .rhs = &as_node.base, + }; + + return &node.base; +} + +fn transCreateNodePtrDeref(c: *Context, lhs: *ast.Node) !*ast.Node { + const node = try c.a().create(ast.Node.SuffixOp); + node.* = .{ + .lhs = .{ .node = lhs }, + .op = .Deref, + .rtoken = try appendToken(c, .PeriodAsterisk, ".*"), + }; + return &node.base; +} + +fn transCreateNodeArrayAccess(c: *Context, lhs: *ast.Node) !*ast.Node.SuffixOp { + _ = try appendToken(c, .LBrace, "["); + const node = try c.a().create(ast.Node.SuffixOp); + node.* = .{ + .lhs = .{ .node = lhs }, + .op = .{ + .ArrayAccess = undefined, + }, + .rtoken = undefined, + }; + return node; +} + const RestorePoint = struct { c: *Context, token_index: ast.TokenIndex, @@ -2092,7 +3571,7 @@ fn transType(rp: RestorePoint, ty: *const ZigClangType, source_loc: ZigClangSour else => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported builtin type", .{}), }); }, - .FunctionProto => { + .FunctionProto, .FunctionNoProto => { const fn_proto_ty = @ptrCast(*const ZigClangFunctionProtoType, ty); const fn_proto = try transFnProto(rp, null, fn_proto_ty, source_loc, null, false); return &fn_proto.base; @@ -2167,24 +3646,15 @@ fn transType(rp: RestorePoint, ty: *const ZigClangType, source_loc: ZigClangSour const typedef_ty = @ptrCast(*const ZigClangTypedefType, ty); const typedef_decl = ZigClangTypedefType_getDecl(typedef_ty); - const typedef_name = try rp.c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, typedef_decl))); - return transCreateNodeIdentifier(rp.c, typedef_name); + return (try transTypeDef(rp.c, typedef_decl)) orelse + revertAndWarn(rp, error.UnsupportedType, source_loc, "unable to translate typedef declaration", .{}); }, .Record => { const record_ty = @ptrCast(*const ZigClangRecordType, ty); - // TODO this sould get the name from decl_table - // struct Foo { - // struct Bar{ - // int b; - // }; - // struct Bar c; - // }; const record_decl = ZigClangRecordType_getDecl(record_ty); - if (try getContainerName(rp, record_decl)) |name| - return transCreateNodeIdentifier(rp.c, name) - else - return transRecordDecl(rp.c, record_decl); + return (try transRecordDecl(rp.c, record_decl)) orelse + revertAndWarn(rp, error.UnsupportedType, source_loc, "unable to resolve record declaration", .{}); }, .Enum => { const enum_ty = @ptrCast(*const ZigClangEnumType, ty); @@ -2205,6 +3675,10 @@ fn transType(rp: RestorePoint, ty: *const ZigClangType, source_loc: ZigClangSour const attributed_ty = @ptrCast(*const ZigClangAttributedType, ty); return transQualType(rp, ZigClangAttributedType_getEquivalentType(attributed_ty), source_loc); }, + .MacroQualified => { + const macroqualified_ty = @ptrCast(*const ZigClangMacroQualifiedType, ty); + return transQualType(rp, ZigClangMacroQualifiedType_getModifiedType(macroqualified_ty), source_loc); + }, else => { const type_name = rp.c.str(ZigClangType_getTypeClassName(ty)); return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported type: '{}'", .{type_name}); @@ -2212,22 +3686,6 @@ fn transType(rp: RestorePoint, ty: *const ZigClangType, source_loc: ZigClangSour } } -fn getContainerName(rp: RestorePoint, record_decl: *const ZigClangRecordDecl) !?[]const u8 { - const bare_name = try rp.c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, record_decl))); - - const container_kind_name = if (ZigClangRecordDecl_isUnion(record_decl)) - "union" - else if (ZigClangRecordDecl_isStruct(record_decl)) - "struct" - else - return revertAndWarn(rp, error.UnsupportedType, ZigClangRecordDecl_getLocation(record_decl), "record {} is not a struct or union", .{bare_name}); - - if (ZigClangRecordDecl_isAnonymousStructOrUnion(record_decl) or bare_name.len == 0) - return null; - - return try std.fmt.allocPrint(rp.c.a(), "{}_{}", .{ container_kind_name, bare_name }); -} - fn isCVoid(qt: ZigClangQualType) bool { const ty = ZigClangQualType_getTypePtr(qt); if (ZigClangType_getTypeClass(ty) == .Builtin) { @@ -2241,7 +3699,6 @@ const FnDeclContext = struct { fn_name: []const u8, has_body: bool, storage_class: ZigClangStorageClass, - scope: **Scope, is_export: bool, }; @@ -2307,9 +3764,6 @@ fn finishTransFnProto( // TODO check for always_inline attribute // TODO check for align attribute - var fndef_scope = Scope.FnDef.init(rp.c); - const scope = &fndef_scope.base; - // pub extern fn name(...) T const pub_tok = if (is_pub) try appendToken(rp.c, .Keyword_pub, "pub") else null; const cc_tok = if (cc == .Stdcall) try appendToken(rp.c, .Keyword_stdcallcc, "stdcallcc") else null; @@ -2335,15 +3789,11 @@ fn finishTransFnProto( const param_name_tok: ?ast.TokenIndex = blk: { if (fn_decl != null) { const param = ZigClangFunctionDecl_getParamDecl(fn_decl.?, @intCast(c_uint, i)); - var param_name: []const u8 = try rp.c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, param))); + const param_name: []const u8 = try rp.c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, param))); if (param_name.len < 1) - param_name = "arg"[0..]; - const checked_param_name = if (try scope.createAlias(rp.c, param_name)) |a| blk: { - try fndef_scope.params.push(.{ .name = param_name, .alias = a }); - break :blk a; - } else param_name; + break :blk null; - const result = try appendIdentifier(rp.c, checked_param_name); + const result = try appendIdentifier(rp.c, param_name); _ = try appendToken(rp.c, .Colon, ":"); break :blk result; } @@ -2410,14 +3860,13 @@ fn finishTransFnProto( }; const fn_proto = try rp.c.a().create(ast.Node.FnProto); - fn_proto.* = ast.Node.FnProto{ - .base = ast.Node{ .id = ast.Node.Id.FnProto }, + fn_proto.* = .{ .doc_comments = null, .visib_token = pub_tok, .fn_token = fn_tok, .name_token = name_tok, .params = fn_params, - .return_type = ast.Node.FnProto.ReturnType{ .Explicit = return_type_node }, + .return_type = .{ .Explicit = return_type_node }, .var_args_token = null, // TODO this field is broken in the AST data model .extern_export_inline_token = extern_export_inline_tok, .cc_token = cc_tok, @@ -2523,6 +3972,41 @@ fn appendTokenFmt(c: *Context, token_id: Token.Id, comptime format: []const u8, return token_index; } +// TODO hook up with codegen +fn isZigPrimitiveType(name: []const u8) bool { + if (name.len > 1 and (name[0] == 'u' or name[0] == 'i')) { + for (name[1..]) |c| { + switch (c) { + '0'...'9' => {}, + else => return false, + } + } + return true; + } + // void is invalid in c so it doesn't need to be checked. + return mem.eql(u8, name, "comptime_float") or + mem.eql(u8, name, "comptime_int") or + mem.eql(u8, name, "bool") or + mem.eql(u8, name, "isize") or + mem.eql(u8, name, "usize") or + mem.eql(u8, name, "f16") or + mem.eql(u8, name, "f32") or + mem.eql(u8, name, "f64") or + mem.eql(u8, name, "f128") or + mem.eql(u8, name, "c_longdouble") or + mem.eql(u8, name, "noreturn") or + mem.eql(u8, name, "type") or + mem.eql(u8, name, "anyerror") or + mem.eql(u8, name, "c_short") or + mem.eql(u8, name, "c_ushort") or + mem.eql(u8, name, "c_int") or + mem.eql(u8, name, "c_uint") or + mem.eql(u8, name, "c_long") or + mem.eql(u8, name, "c_ulong") or + mem.eql(u8, name, "c_longlong") or + mem.eql(u8, name, "c_ulonglong"); +} + fn isValidZigIdentifier(name: []const u8) bool { for (name) |c, i| { switch (c) { @@ -2573,27 +4057,31 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { const begin_loc = ZigClangMacroDefinitionRecord_getSourceRange_getBegin(macro); const name = try c.str(raw_name); - if (scope.contains(name)) { + + // TODO https://github.com/ziglang/zig/issues/3756 + // TODO https://github.com/ziglang/zig/issues/1802 + const checked_name = if (isZigPrimitiveType(name)) try std.fmt.allocPrint(c.a(), "_{}", .{name}) else name; + if (scope.contains(checked_name)) { continue; } const begin_c = ZigClangSourceManager_getCharacterData(c.source_manager, begin_loc); ctok.tokenizeCMacro(&tok_list, begin_c) catch |err| switch (err) { error.OutOfMemory => |e| return e, else => { - try failDecl(c, begin_loc, name, "unable to tokenize macro definition", .{}); + try failDecl(c, begin_loc, checked_name, "unable to tokenize macro definition", .{}); continue; }, }; var tok_it = tok_list.iterator(0); const first_tok = tok_it.next().?; - assert(first_tok.id == .Identifier and std.mem.eql(u8, first_tok.bytes, name)); + assert(first_tok.id == .Identifier and mem.eql(u8, first_tok.bytes, name)); const next = tok_it.peek().?; switch (next.id) { .Identifier => { // if it equals itself, ignore. for example, from stdio.h: // #define stdin stdin - if (std.mem.eql(u8, name, next.bytes)) { + if (mem.eql(u8, checked_name, next.bytes)) { continue; } }, @@ -2610,12 +4098,12 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { } else false; (if (macro_fn) - transMacroFnDefine(c, &tok_it, name, begin_loc) + transMacroFnDefine(c, &tok_it, checked_name, begin_loc) else - transMacroDefine(c, &tok_it, name, begin_loc)) catch |err| switch (err) { + transMacroDefine(c, &tok_it, checked_name, begin_loc)) catch |err| switch (err) { error.UnsupportedTranslation, error.ParseError, - => try failDecl(c, begin_loc, name, "unable to translate macro", .{}), + => try failDecl(c, begin_loc, checked_name, "unable to translate macro", .{}), error.OutOfMemory => |e| return e, }; }, @@ -2628,37 +4116,28 @@ fn transMacroDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u8, const rp = makeRestorePoint(c); const scope = &c.global_scope.base; - const visib_tok = try appendToken(c, .Keyword_pub, "pub"); - const mut_tok = try appendToken(c, .Keyword_const, "const"); - const name_tok = try appendIdentifier(c, name); - const eq_tok = try appendToken(c, .Equal, "="); + const node = try transCreateNodeVarDecl(c, true, true, name); + node.eq_token = try appendToken(c, .Equal, "="); - const init_node = try parseCExpr(rp, it, source_loc, scope); + node.init_node = try parseCExpr(rp, it, source_loc, scope); + const last = it.next().?; + if (last.id != .Eof) + return revertAndWarn( + rp, + error.UnsupportedTranslation, + source_loc, + "unable to translate C expr, unexpected token: {}", + .{last.id}, + ); - const node = try c.a().create(ast.Node.VarDecl); - node.* = ast.Node.VarDecl{ - .doc_comments = null, - .visib_token = visib_tok, - .thread_local_token = null, - .name_token = name_tok, - .eq_token = eq_tok, - .mut_token = mut_tok, - .comptime_token = null, - .extern_export_token = null, - .lib_name = null, - .type_node = null, - .align_node = null, - .section_node = null, - .init_node = init_node, - .semicolon_token = try appendToken(c, .Semicolon, ";"), - }; + node.semicolon_token = try appendToken(c, .Semicolon, ";"); _ = try c.global_scope.macro_table.put(name, &node.base); } fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void { const rp = makeRestorePoint(c); - var fndef_scope = Scope.FnDef.init(c); - const scope = &fndef_scope.base; + const block_scope = try Scope.Block.init(c, &c.global_scope.base, null); + const scope = &block_scope.base; const pub_tok = try appendToken(c, .Keyword_pub, "pub"); const inline_tok = try appendToken(c, .Keyword_inline, "inline"); @@ -2676,7 +4155,7 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u return error.ParseError; const checked_name = if (try scope.createAlias(c, param_tok.bytes)) |alias| blk: { - try fndef_scope.params.push(.{ .name = param_tok.bytes, .alias = alias }); + try block_scope.variables.push(.{ .name = param_tok.bytes, .alias = alias }); break :blk alias; } else param_tok.bytes; @@ -2737,6 +4216,15 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u const return_expr = try transCreateNodeReturnExpr(c); const expr = try parseCExpr(rp, it, source_loc, scope); + const last = it.next().?; + if (last.id != .Eof) + return revertAndWarn( + rp, + error.UnsupportedTranslation, + source_loc, + "unable to translate C expr, unexpected token: {}", + .{last.id}, + ); _ = try appendToken(c, .Semicolon, ";"); try type_of.params.push(expr); return_expr.rhs = expr; @@ -2838,7 +4326,10 @@ fn parseCPrimaryExpr(rp: RestorePoint, it: *ctok.TokenList.Iterator, source_loc: if (it.peek().?.id == .RParen) { _ = it.next(); - return inner_node; + if (it.peek().?.id != .LParen) { + return inner_node; + } + _ = it.next(); } // hack to get zig fmt to render a comma in builtin calls @@ -2930,8 +4421,8 @@ fn parseCPrimaryExpr(rp: RestorePoint, it: *ctok.TokenList.Iterator, source_loc: rp, error.UnsupportedTranslation, source_loc, - "unable to translate C expr", - .{}, + "unable to translate C expr, unexpected token: {}", + .{tok.id}, ), } } @@ -2944,24 +4435,17 @@ fn parseCSuffixOpExpr(rp: RestorePoint, it: *ctok.TokenList.Iterator, source_loc .Dot => { const name_tok = it.next().?; if (name_tok.id != .Identifier) - return revertAndWarn( - rp, - error.ParseError, - source_loc, - "unable to translate C expr", - .{}, - ); + return error.ParseError; - const op_token = try appendToken(rp.c, .Period, "."); - const rhs = try transCreateNodeIdentifier(rp.c, name_tok.bytes); - const access_node = try rp.c.a().create(ast.Node.InfixOp); - access_node.* = .{ - .op_token = op_token, - .lhs = node, - .op = .Period, - .rhs = rhs, - }; - node = &access_node.base; + node = try transCreateNodeFieldAccess(rp.c, node, name_tok.bytes); + }, + .Arrow => { + const name_tok = it.next().?; + if (name_tok.id != .Identifier) + return error.ParseError; + + const deref = try transCreateNodePtrDeref(rp.c, node); + node = try transCreateNodeFieldAccess(rp.c, deref, name_tok.bytes); }, .Asterisk => { if (it.peek().?.id == .RParen) { @@ -2977,19 +4461,19 @@ fn parseCSuffixOpExpr(rp: RestorePoint, it: *ctok.TokenList.Iterator, source_loc // expr * expr const op_token = try appendToken(rp.c, .Asterisk, "*"); const rhs = try parseCPrimaryExpr(rp, it, source_loc, scope); - const bitshift_node = try rp.c.a().create(ast.Node.InfixOp); - bitshift_node.* = .{ + const mul_node = try rp.c.a().create(ast.Node.InfixOp); + mul_node.* = .{ .op_token = op_token, .lhs = node, .op = .BitShiftLeft, .rhs = rhs, }; - node = &bitshift_node.base; + node = &mul_node.base; } }, .Shl => { const op_token = try appendToken(rp.c, .AngleBracketAngleBracketLeft, "<<"); - const rhs = try parseCPrimaryExpr(rp, it, source_loc, scope); + const rhs = try parseCExpr(rp, it, source_loc, scope); const bitshift_node = try rp.c.a().create(ast.Node.InfixOp); bitshift_node.* = .{ .op_token = op_token, @@ -2999,6 +4483,42 @@ fn parseCSuffixOpExpr(rp: RestorePoint, it: *ctok.TokenList.Iterator, source_loc }; node = &bitshift_node.base; }, + .Pipe => { + const op_token = try appendToken(rp.c, .Pipe, "|"); + const rhs = try parseCExpr(rp, it, source_loc, scope); + const or_node = try rp.c.a().create(ast.Node.InfixOp); + or_node.* = .{ + .op_token = op_token, + .lhs = node, + .op = .BitOr, + .rhs = rhs, + }; + node = &or_node.base; + }, + .LBrace => { + const arr_node = try transCreateNodeArrayAccess(rp.c, node); + arr_node.op.ArrayAccess = try parseCExpr(rp, it, source_loc, scope); + arr_node.rtoken = try appendToken(rp.c, .RBrace, "]"); + node = &arr_node.base; + if (it.next().?.id != .RBrace) + return error.ParseError; + }, + .LParen => { + const call_node = try transCreateNodeFnCall(rp.c, node); + while (true) { + const arg = try parseCExpr(rp, it, source_loc, scope); + try call_node.op.Call.params.push(arg); + const next = it.next().?; + if (next.id == .Comma) + _ = try appendToken(rp.c, .Comma, ",") + else if (next.id == .RParen) + break + else + return error.ParseError; + } + call_node.rtoken = try appendToken(rp.c, .RParen, ")"); + node = &call_node.base; + }, else => { _ = it.prev(); return node; @@ -3028,13 +4548,7 @@ fn parseCPrefixOpExpr(rp: RestorePoint, it: *ctok.TokenList.Iterator, source_loc }, .Asterisk => { const prefix_op_expr = try parseCPrefixOpExpr(rp, it, source_loc, scope); - const node = try rp.c.a().create(ast.Node.SuffixOp); - node.* = .{ - .lhs = .{ .node = prefix_op_expr }, - .op = .Deref, - .rtoken = try appendToken(rp.c, .PeriodAsterisk, ".*"), - }; - return &node.base; + return try transCreateNodePtrDeref(rp.c, prefix_op_expr); }, else => { _ = it.prev(); @@ -3043,24 +4557,32 @@ fn parseCPrefixOpExpr(rp: RestorePoint, it: *ctok.TokenList.Iterator, source_loc } } -fn tokenSlice(c: *Context, token: ast.TokenIndex) []const u8 { +fn tokenSlice(c: *Context, token: ast.TokenIndex) []u8 { const tok = c.tree.tokens.at(token); - return c.source_buffer.toSliceConst()[tok.start..tok.end]; + return c.source_buffer.toSlice()[tok.start..tok.end]; } -fn getFnDecl(c: *Context, ref: *ast.Node) ?*ast.Node { - const init = if (ref.cast(ast.Node.VarDecl)) |v| v.init_node.? else return null; - const name = if (init.cast(ast.Node.Identifier)) |id| - tokenSlice(c, id.token) - else - return null; - // TODO a.b.c - if (c.global_scope.sym_table.get(name)) |kv| { - if (kv.value.cast(ast.Node.VarDecl)) |val| { - if (val.type_node) |type_node| { - if (type_node.cast(ast.Node.PrefixOp)) |casted| { - if (casted.rhs.id == .FnProto) { - return casted.rhs; +fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node { + if (node.id == .ContainerDecl) { + return node; + } else if (node.id == .PrefixOp) { + return node; + } else if (node.cast(ast.Node.Identifier)) |ident| { + if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |kv| { + if (kv.value.cast(ast.Node.VarDecl)) |var_decl| + return getContainer(c, var_decl.init_node.?); + } + } else if (node.cast(ast.Node.InfixOp)) |infix| { + if (infix.op != .Period) + return null; + if (getContainerTypeOf(c, infix.lhs)) |ty_node| { + if (ty_node.cast(ast.Node.ContainerDecl)) |container| { + var it = container.fields_and_decls.iterator(0); + while (it.next()) |field_ref| { + const field = field_ref.*.cast(ast.Node.ContainerField).?; + const ident = infix.rhs.cast(ast.Node.Identifier).?; + if (mem.eql(u8, tokenSlice(c, field.name_token), tokenSlice(c, ident.token))) { + return getContainer(c, field.type_expr.?); } } } @@ -3069,10 +4591,52 @@ fn getFnDecl(c: *Context, ref: *ast.Node) ?*ast.Node { return null; } +fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node { + if (ref.cast(ast.Node.Identifier)) |ident| { + if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |kv| { + if (kv.value.cast(ast.Node.VarDecl)) |var_decl| { + if (var_decl.type_node) |ty| + return getContainer(c, ty); + } + } + } else if (ref.cast(ast.Node.InfixOp)) |infix| { + if (infix.op != .Period) + return null; + if (getContainerTypeOf(c, infix.lhs)) |ty_node| { + if (ty_node.cast(ast.Node.ContainerDecl)) |container| { + var it = container.fields_and_decls.iterator(0); + while (it.next()) |field_ref| { + const field = field_ref.*.cast(ast.Node.ContainerField).?; + const ident = infix.rhs.cast(ast.Node.Identifier).?; + if (mem.eql(u8, tokenSlice(c, field.name_token), tokenSlice(c, ident.token))) { + return getContainer(c, field.type_expr.?); + } + } + } else + return ty_node; + } + } + return null; +} + +fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto { + const init = if (ref.cast(ast.Node.VarDecl)) |v| v.init_node.? else return null; + if (getContainerTypeOf(c, init)) |ty_node| { + if (ty_node.cast(ast.Node.PrefixOp)) |prefix| { + if (prefix.op == .OptionalType) { + if (prefix.rhs.cast(ast.Node.FnProto)) |fn_proto| { + return fn_proto; + } + } + } + } + return null; +} + fn addMacros(c: *Context) !void { var macro_it = c.global_scope.macro_table.iterator(); while (macro_it.next()) |kv| { - if (getFnDecl(c, kv.value)) |proto_node| { + if (getFnProto(c, kv.value)) |proto_node| { // If a macro aliases a global variable which is a function pointer, we conclude that // the macro is intended to represent a function that assumes the function pointer // variable is non-null and calls it. diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp index 86ef17ffbe..93646de97b 100644 --- a/src/zig_clang.cpp +++ b/src/zig_clang.cpp @@ -1571,6 +1571,16 @@ const ZigClangTypedefNameDecl *ZigClangTypedefNameDecl_getCanonicalDecl(const Zi return reinterpret_cast(decl); } +const ZigClangFunctionDecl *ZigClangFunctionDecl_getCanonicalDecl(const ZigClangFunctionDecl *self) { + const clang::FunctionDecl *decl = reinterpret_cast(self)->getCanonicalDecl(); + return reinterpret_cast(decl); +} + +const ZigClangVarDecl *ZigClangVarDecl_getCanonicalDecl(const ZigClangVarDecl *self) { + const clang::VarDecl *decl = reinterpret_cast(self)->getCanonicalDecl(); + return reinterpret_cast(decl); +} + const ZigClangRecordDecl *ZigClangRecordDecl_getDefinition(const ZigClangRecordDecl *zig_record_decl) { const clang::RecordDecl *record_decl = reinterpret_cast(zig_record_decl); const clang::RecordDecl *definition = record_decl->getDefinition(); @@ -2161,6 +2171,11 @@ unsigned ZigClangAPFloat_convertToHexString(const ZigClangAPFloat *self, char *D return casted->convertToHexString(DST, HexDigits, UpperCase, (llvm::APFloat::roundingMode)RM); } +double ZigClangAPFloat_getValueAsApproximateDouble(const ZigClangFloatingLiteral *self) { + auto casted = reinterpret_cast(self); + return casted->getValueAsApproximateDouble(); +} + enum ZigClangStringLiteral_StringKind ZigClangStringLiteral_getKind(const struct ZigClangStringLiteral *self) { auto casted = reinterpret_cast(self); return (ZigClangStringLiteral_StringKind)casted->getKind(); diff --git a/src/zig_clang.h b/src/zig_clang.h index cf60fdfca9..ce71612468 100644 --- a/src/zig_clang.h +++ b/src/zig_clang.h @@ -856,6 +856,8 @@ ZIG_EXTERN_C const struct ZigClangEnumDecl *ZigClangEnumType_getDecl(const struc ZIG_EXTERN_C const struct ZigClangTagDecl *ZigClangRecordDecl_getCanonicalDecl(const struct ZigClangRecordDecl *record_decl); ZIG_EXTERN_C const struct ZigClangTagDecl *ZigClangEnumDecl_getCanonicalDecl(const struct ZigClangEnumDecl *); ZIG_EXTERN_C const struct ZigClangTypedefNameDecl *ZigClangTypedefNameDecl_getCanonicalDecl(const struct ZigClangTypedefNameDecl *); +ZIG_EXTERN_C const struct ZigClangFunctionDecl *ZigClangFunctionDecl_getCanonicalDecl(const ZigClangFunctionDecl *self); +ZIG_EXTERN_C const struct ZigClangVarDecl *ZigClangVarDecl_getCanonicalDecl(const ZigClangVarDecl *self); ZIG_EXTERN_C const struct ZigClangRecordDecl *ZigClangRecordDecl_getDefinition(const struct ZigClangRecordDecl *); ZIG_EXTERN_C const struct ZigClangEnumDecl *ZigClangEnumDecl_getDefinition(const struct ZigClangEnumDecl *); @@ -985,6 +987,7 @@ ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangDeclStmt_getBeginLoc(const st ZIG_EXTERN_C unsigned ZigClangAPFloat_convertToHexString(const struct ZigClangAPFloat *self, char *DST, unsigned HexDigits, bool UpperCase, enum ZigClangAPFloat_roundingMode RM); +ZIG_EXTERN_C double ZigClangAPFloat_getValueAsApproximateDouble(const ZigClangFloatingLiteral *self); ZIG_EXTERN_C enum ZigClangStringLiteral_StringKind ZigClangStringLiteral_getKind(const struct ZigClangStringLiteral *self); ZIG_EXTERN_C const char *ZigClangStringLiteral_getString_bytes_begin_size(const struct ZigClangStringLiteral *self, diff --git a/test/translate_c.zig b/test/translate_c.zig index 9a4c364d1d..917bd98d09 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -127,16 +127,16 @@ pub fn addCases(cases: *tests.TranslateCContext) void { }); cases.addC_both("add, sub, mul, div, rem", - \\int s(int a, int b) { - \\ int c; + \\int s() { + \\ int a, b, c; \\ c = a + b; \\ c = a - b; \\ c = a * b; \\ c = a / b; \\ c = a % b; \\} - \\unsigned u(unsigned a, unsigned b) { - \\ unsigned c; + \\unsigned u() { + \\ unsigned a, b, c; \\ c = a + b; \\ c = a - b; \\ c = a * b; @@ -144,7 +144,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ c = a % b; \\} , &[_][]const u8{ - \\pub export fn s(a: c_int, b: c_int) c_int { + \\pub export fn s() c_int { + \\ var a: c_int = undefined; + \\ var b: c_int = undefined; \\ var c: c_int = undefined; \\ c = (a + b); \\ c = (a - b); @@ -152,7 +154,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ c = @divTrunc(a, b); \\ c = @rem(a, b); \\} - \\pub export fn u(a: c_uint, b: c_uint) c_uint { + \\pub export fn u() c_uint { + \\ var a: c_uint = undefined; + \\ var b: c_uint = undefined; \\ var c: c_uint = undefined; \\ c = (a +% b); \\ c = (a -% b); @@ -162,50 +166,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - cases.add_both("enums", - \\enum Foo { - \\ FooA, - \\ FooB, - \\ Foo1, - \\}; - , &[_][]const u8{ - \\pub const enum_Foo = extern enum { - \\ A, - \\ B, - \\ @"1", - \\}; - , - \\pub const FooA = enum_Foo.A; - , - \\pub const FooB = enum_Foo.B; - , - \\pub const Foo1 = enum_Foo.@"1"; - , - \\pub const Foo = enum_Foo; - }); - - cases.add_both("enums", - \\enum Foo { - \\ FooA = 2, - \\ FooB = 5, - \\ Foo1, - \\}; - , &[_][]const u8{ - \\pub const enum_Foo = extern enum { - \\ A = 2, - \\ B = 5, - \\ @"1" = 6, - \\}; - , - \\pub const FooA = enum_Foo.A; - , - \\pub const FooB = enum_Foo.B; - , - \\pub const Foo1 = enum_Foo.@"1"; - , - \\pub const Foo = enum_Foo; - }); - cases.add_both("typedef of function in struct field", \\typedef void lws_callback_function(void); \\struct Foo { @@ -228,7 +188,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ struct Foo *foo; \\}; , &[_][]const u8{ - \\pub const struct_Foo = @OpaqueType() + \\pub const struct_Foo = @OpaqueType(); , \\pub const struct_Bar = extern struct { \\ foo: ?*struct_Foo, @@ -426,6 +386,432 @@ pub fn addCases(cases: *tests.TranslateCContext) void { }, ); + cases.addC_both("null statements", + \\void foo(void) { + \\ ;;;;; + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ {} + \\ {} + \\ {} + \\ {} + \\ {} + \\} + }); + + if (builtin.os != builtin.Os.windows) { + // Windows treats this as an enum with type c_int + cases.add_both("big negative enum init values when C ABI supports long long enums", + \\enum EnumWithInits { + \\ VAL01 = 0, + \\ VAL02 = 1, + \\ VAL03 = 2, + \\ VAL04 = 3, + \\ VAL05 = -1, + \\ VAL06 = -2, + \\ VAL07 = -3, + \\ VAL08 = -4, + \\ VAL09 = VAL02 + VAL08, + \\ VAL10 = -1000012000, + \\ VAL11 = -1000161000, + \\ VAL12 = -1000174001, + \\ VAL13 = VAL09, + \\ VAL14 = VAL10, + \\ VAL15 = VAL11, + \\ VAL16 = VAL13, + \\ VAL17 = (VAL16 - VAL10 + 1), + \\ VAL18 = 0x1000000000000000L, + \\ VAL19 = VAL18 + VAL18 + VAL18 - 1, + \\ VAL20 = VAL19 + VAL19, + \\ VAL21 = VAL20 + 0xFFFFFFFFFFFFFFFF, + \\ VAL22 = 0xFFFFFFFFFFFFFFFF + 1, + \\ VAL23 = 0xFFFFFFFFFFFFFFFF, + \\}; + , &[_][]const u8{ + \\pub const enum_EnumWithInits = extern enum(c_longlong) { + \\ VAL01 = 0, + \\ VAL02 = 1, + \\ VAL03 = 2, + \\ VAL04 = 3, + \\ VAL05 = -1, + \\ VAL06 = -2, + \\ VAL07 = -3, + \\ VAL08 = -4, + \\ VAL09 = -3, + \\ VAL10 = -1000012000, + \\ VAL11 = -1000161000, + \\ VAL12 = -1000174001, + \\ VAL13 = -3, + \\ VAL14 = -1000012000, + \\ VAL15 = -1000161000, + \\ VAL16 = -3, + \\ VAL17 = 1000011998, + \\ VAL18 = 1152921504606846976, + \\ VAL19 = 3458764513820540927, + \\ VAL20 = 6917529027641081854, + \\ VAL21 = 6917529027641081853, + \\ VAL22 = 0, + \\ VAL23 = -1, + \\}; + }); + } + + cases.addC_both("predefined expressions", + \\void foo(void) { + \\ __func__; + \\ __FUNCTION__; + \\ __PRETTY_FUNCTION__; + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ _ = "foo"; + \\ _ = "foo"; + \\ _ = "void foo(void)"; + \\} + }); + + cases.addC_both("ignore result, no function arguments", + \\void foo() { + \\ int a; + \\ 1; + \\ "hey"; + \\ 1 + 1; + \\ 1 - 1; + \\ a = 1; + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ var a: c_int = undefined; + \\ _ = 1; + \\ _ = "hey"; + \\ _ = (1 + 1); + \\ _ = (1 - 1); + \\ a = 1; + \\} + }); + + cases.add_both("constant size array", + \\void func(int array[20]); + , &[_][]const u8{ + \\pub extern fn func(array: [*c]c_int) void; + }); + + cases.add_both("__cdecl doesn't mess up function pointers", + \\void foo(void (__cdecl *fn_ptr)(void)); + , &[_][]const u8{ + \\pub extern fn foo(fn_ptr: ?extern fn () void) void; + }); + + cases.addC_both("void cast", + \\void foo() { + \\ int a; + \\ (void) a; + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ var a: c_int = undefined; + \\ _ = a; + \\} + }); + + cases.addC_both("implicit cast to void *", + \\void *foo() { + \\ unsigned short *x; + \\ return x; + \\} + , &[_][]const u8{ + \\pub export fn foo() ?*c_void { + \\ var x: [*c]c_ushort = undefined; + \\ return @ptrCast(?*c_void, x); + \\} + }); + + cases.addC_both("null pointer implicit cast", + \\int* foo(void) { + \\ return 0; + \\} + , &[_][]const u8{ + \\pub export fn foo() [*c]c_int { + \\ return null; + \\} + }); + + cases.add_both("simple union", + \\union Foo { + \\ int x; + \\ double y; + \\}; + , &[_][]const u8{ + \\pub const union_Foo = extern union { + \\ x: c_int, + \\ y: f64, + \\}; + , + \\pub const Foo = union_Foo; + }); + + cases.addC_both("string literal", + \\const char *foo(void) { + \\ return "bar"; + \\} + , &[_][]const u8{ + \\pub export fn foo() [*c]const u8 { + \\ return "bar"; + \\} + }); + + cases.addC_both("return void", + \\void foo(void) { + \\ return; + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ return; + \\} + }); + + cases.addC_both("for loop", + \\void foo(void) { + \\ for (int i = 0; i; i = i + 1) { } + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ { + \\ var i: c_int = 0; + \\ while (i != 0) : (i = (i + 1)) {} + \\ } + \\} + }); + + cases.addC_both("empty for loop", + \\void foo(void) { + \\ for (;;) { } + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ while (true) {} + \\} + }); + + cases.addC_both("break statement", + \\void foo(void) { + \\ for (;;) { + \\ break; + \\ } + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ while (true) { + \\ break; + \\ } + \\} + }); + + cases.addC_both("continue statement", + \\void foo(void) { + \\ for (;;) { + \\ continue; + \\ } + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ while (true) { + \\ continue; + \\ } + \\} + }); + + cases.addC_both("pointer casting", + \\float *ptrcast() { + \\ int *a; + \\ return (float *)a; + \\} + , &[_][]const u8{ + \\pub export fn ptrcast() [*c]f32 { + \\ var a: [*c]c_int = undefined; + \\ return @ptrCast([*c]f32, @alignCast(@alignOf(f32), a)); + \\} + }); + + cases.addC_both("pointer conversion with different alignment", + \\void test_ptr_cast() { + \\ void *p; + \\ { + \\ char *to_char = (char *)p; + \\ short *to_short = (short *)p; + \\ int *to_int = (int *)p; + \\ long long *to_longlong = (long long *)p; + \\ } + \\ { + \\ char *to_char = p; + \\ short *to_short = p; + \\ int *to_int = p; + \\ long long *to_longlong = p; + \\ } + \\} + , &[_][]const u8{ + \\pub export fn test_ptr_cast() void { + \\ var p: ?*c_void = undefined; + \\ { + \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@alignOf(u8), p)); + \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@alignOf(c_short), p)); + \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@alignOf(c_int), p)); + \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@alignOf(c_longlong), p)); + \\ } + \\ { + \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@alignOf(u8), p)); + \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@alignOf(c_short), p)); + \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@alignOf(c_int), p)); + \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@alignOf(c_longlong), p)); + \\ } + \\} + }); + + cases.addC_both("while on non-bool", + \\int while_none_bool() { + \\ int a; + \\ float b; + \\ void *c; + \\ while (a) return 0; + \\ while (b) return 1; + \\ while (c) return 2; + \\ return 3; + \\} + , &[_][]const u8{ + \\pub export fn while_none_bool() c_int { + \\ var a: c_int = undefined; + \\ var b: f32 = undefined; + \\ var c: ?*c_void = undefined; + \\ while (a != 0) return 0; + \\ while (b != 0) return 1; + \\ while (c != null) return 2; + \\ return 3; + \\} + }); + + cases.addC_both("for on non-bool", + \\int for_none_bool() { + \\ int a; + \\ float b; + \\ void *c; + \\ for (;a;) return 0; + \\ for (;b;) return 1; + \\ for (;c;) return 2; + \\ return 3; + \\} + , &[_][]const u8{ + \\pub export fn for_none_bool() c_int { + \\ var a: c_int = undefined; + \\ var b: f32 = undefined; + \\ var c: ?*c_void = undefined; + \\ while (a != 0) return 0; + \\ while (b != 0) return 1; + \\ while (c != null) return 2; + \\ return 3; + \\} + }); + + cases.addC_both("bitshift", + \\int foo(void) { + \\ return (1 << 2) >> 1; + \\} + , &[_][]const u8{ + \\pub export fn foo() c_int { + \\ return (1 << @as(@import("std").math.Log2Int(c_int), 2)) >> @as(@import("std").math.Log2Int(c_int), 1); + \\} + }); + + cases.addC_both("sizeof", + \\#include + \\size_t size_of(void) { + \\ return sizeof(int); + \\} + , &[_][]const u8{ + \\pub export fn size_of() usize { + \\ return @sizeOf(c_int); + \\} + }); + + cases.addC_both("normal deref", + \\void foo() { + \\ int *x; + \\ *x = 1; + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ var x: [*c]c_int = undefined; + \\ x.?.* = 1; + \\} + }); + + cases.addC_both("address of operator", + \\int foo(void) { + \\ int x = 1234; + \\ int *ptr = &x; + \\ return *ptr; + \\} + , &[_][]const u8{ + \\pub export fn foo() c_int { + \\ var x: c_int = 1234; + \\ var ptr: [*c]c_int = &x; + \\ return ptr.?.*; + \\} + }); + + cases.addC_both("bin not", + \\int foo() { + \\ int x; + \\ return ~x; + \\} + , &[_][]const u8{ + \\pub export fn foo() c_int { + \\ var x: c_int = undefined; + \\ return ~x; + \\} + }); + + cases.addC_both("bool not", + \\int foo() { + \\ int a; + \\ float b; + \\ void *c; + \\ return !(a == 0); + \\ return !a; + \\ return !b; + \\ return !c; + \\} + , &[_][]const u8{ + \\pub export fn foo() c_int { + \\ var a: c_int = undefined; + \\ var b: f32 = undefined; + \\ var c: ?*c_void = undefined; + \\ return !(a == 0); + \\ return !(a != 0); + \\ return !(b != 0); + \\ return !(c != null); + \\} + }); + + cases.addC("__extension__ cast", + \\int foo(void) { + \\ return __extension__ 1; + \\} + , &[_][]const u8{ + \\pub export fn foo() c_int { + \\ return 1; + \\} + }); + + if (builtin.os != builtin.Os.windows) { + // sysv_abi not currently supported on windows + cases.add_both("Macro qualified functions", + \\void __attribute__((sysv_abi)) foo(void); + , &[_][]const u8{ + \\pub extern fn foo() void; + }); + } + /////////////// Cases that pass for only stage2 //////////////// cases.add_2("Parameterless function prototypes", @@ -491,28 +877,28 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ p, \\}; , &[_][]const u8{ - \\pub const a = enum_unnamed_1.a; - \\pub const b = enum_unnamed_1.b; - \\pub const c = enum_unnamed_1.c; - \\pub const enum_unnamed_1 = extern enum { + \\pub const a = 0; + \\pub const b = 1; + \\pub const c = 2; + \\const enum_unnamed_1 = extern enum { \\ a, \\ b, \\ c, \\}; \\pub const d = enum_unnamed_1; - \\pub const e = enum_unnamed_2.e; - \\pub const f = enum_unnamed_2.f; - \\pub const g = enum_unnamed_2.g; - \\pub const enum_unnamed_2 = extern enum { + \\pub const e = 0; + \\pub const f = 4; + \\pub const g = 5; + \\const enum_unnamed_2 = extern enum { \\ e = 0, \\ f = 4, \\ g = 5, \\}; - \\pub export var h: enum_unnamed_2 = @as(enum_unnamed_2, e); - \\pub const i = enum_unnamed_3.i; - \\pub const j = enum_unnamed_3.j; - \\pub const k = enum_unnamed_3.k; - \\pub const enum_unnamed_3 = extern enum { + \\pub export var h: enum_unnamed_2 = @intToEnum(enum_unnamed_2, e); + \\pub const i = 0; + \\pub const j = 1; + \\pub const k = 2; + \\const enum_unnamed_3 = extern enum { \\ i, \\ j, \\ k, @@ -521,9 +907,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ l: enum_unnamed_3, \\ m: d, \\}; - \\pub const n = enum_i.n; - \\pub const o = enum_i.o; - \\pub const p = enum_i.p; + \\pub const n = 0; + \\pub const o = 1; + \\pub const p = 2; \\pub const enum_i = extern enum { \\ n, \\ o, @@ -622,25 +1008,22 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define glClearPFN PFNGLCLEARPROC , &[_][]const u8{ \\pub const GLbitfield = c_uint; - , \\pub const PFNGLCLEARPROC = ?extern fn (GLbitfield) void; - , \\pub const OpenGLProc = ?extern fn () void; - , + \\const struct_unnamed_1 = extern struct { + \\ Clear: PFNGLCLEARPROC, + \\}; \\pub const union_OpenGLProcs = extern union { \\ ptr: [1]OpenGLProc, - \\ gl: extern struct { - \\ Clear: PFNGLCLEARPROC, - \\ }, + \\ gl: struct_unnamed_1, \\}; - , \\pub extern var glProcs: union_OpenGLProcs; , \\pub const glClearPFN = PFNGLCLEARPROC; - // , // TODO - // \\pub inline fn glClearUnion(arg_1: GLbitfield) void { - // \\ return glProcs.gl.Clear.?(arg_1); - // \\} + , + \\pub inline fn glClearUnion(arg_2: GLbitfield) void { + \\ return glProcs.gl.Clear.?(arg_2); + \\} , \\pub const OpenGLProcs = union_OpenGLProcs; }); @@ -662,13 +1045,16 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - cases.add_2("macro escape sequences", + cases.add_2("macro defines string literal with hex", \\#define FOO "aoeu\xab derp" - \\#define FOO2 "aoeu\a derp" + \\#define FOO2 "aoeu\x0007a derp" + \\#define FOO_CHAR '\xfF' , &[_][]const u8{ \\pub const FOO = "aoeu\xab derp"; , - \\pub const FOO2 = "aoeu\x07 derp"; + \\pub const FOO2 = "aoeu\x7a derp"; + , + \\pub const FOO_CHAR = '\xff'; }); cases.add_2("variable aliasing", @@ -688,7 +1074,8 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub var a: c_long = @as(c_long, 2); \\pub var b: c_long = @as(c_long, 2); \\pub var c: c_int = 4; - \\pub export fn foo(c_1: u8) void { + \\pub export fn foo(_arg_c_1: u8) void { + \\ var c_1 = _arg_c_1; \\ var a_2: c_int = undefined; \\ var b_3: u8 = @as(u8, 123); \\ b_3 = @as(u8, a_2); @@ -699,96 +1086,1258 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - /////////////// Cases for only stage1 which are TODO items for stage2 //////////////// - - if (builtin.os != builtin.Os.windows) { - // Windows treats this as an enum with type c_int - cases.add("big negative enum init values when C ABI supports long long enums", - \\enum EnumWithInits { - \\ VAL01 = 0, - \\ VAL02 = 1, - \\ VAL03 = 2, - \\ VAL04 = 3, - \\ VAL05 = -1, - \\ VAL06 = -2, - \\ VAL07 = -3, - \\ VAL08 = -4, - \\ VAL09 = VAL02 + VAL08, - \\ VAL10 = -1000012000, - \\ VAL11 = -1000161000, - \\ VAL12 = -1000174001, - \\ VAL13 = VAL09, - \\ VAL14 = VAL10, - \\ VAL15 = VAL11, - \\ VAL16 = VAL13, - \\ VAL17 = (VAL16 - VAL10 + 1), - \\ VAL18 = 0x1000000000000000L, - \\ VAL19 = VAL18 + VAL18 + VAL18 - 1, - \\ VAL20 = VAL19 + VAL19, - \\ VAL21 = VAL20 + 0xFFFFFFFFFFFFFFFF, - \\ VAL22 = 0xFFFFFFFFFFFFFFFF + 1, - \\ VAL23 = 0xFFFFFFFFFFFFFFFF, - \\}; - , &[_][]const u8{ - \\pub const enum_EnumWithInits = extern enum(c_longlong) { - \\ VAL01 = 0, - \\ VAL02 = 1, - \\ VAL03 = 2, - \\ VAL04 = 3, - \\ VAL05 = -1, - \\ VAL06 = -2, - \\ VAL07 = -3, - \\ VAL08 = -4, - \\ VAL09 = -3, - \\ VAL10 = -1000012000, - \\ VAL11 = -1000161000, - \\ VAL12 = -1000174001, - \\ VAL13 = -3, - \\ VAL14 = -1000012000, - \\ VAL15 = -1000161000, - \\ VAL16 = -3, - \\ VAL17 = 1000011998, - \\ VAL18 = 1152921504606846976, - \\ VAL19 = 3458764513820540927, - \\ VAL20 = 6917529027641081854, - \\ VAL21 = 6917529027641081853, - \\ VAL22 = 0, - \\ VAL23 = -1, - \\}; - }); - } - - cases.add("predefined expressions", - \\void foo(void) { - \\ __func__; - \\ __FUNCTION__; - \\ __PRETTY_FUNCTION__; + cases.add_2("comma operator", + \\int foo(char c) { + \\ 2, 4; + \\ return 2, 4, 6; \\} , &[_][]const u8{ - \\pub fn foo() void { - \\ _ = "foo"; - \\ _ = "foo"; - \\ _ = "void foo(void)"; + \\pub export fn foo(_arg_c: u8) c_int { + \\ var c = _arg_c; + \\ _ = 2; + \\ _ = 4; + \\ _ = 2; + \\ _ = 4; + \\ return 6; \\} }); - cases.add("ignore result, no function arguments", - \\void foo() { + cases.add_2("wors-case assign", + \\int foo(char c) { \\ int a; - \\ 1; - \\ "hey"; - \\ 1 + 1; - \\ 1 - 1; - \\ a = 1; + \\ int b; + \\ a = b = 2; \\} , &[_][]const u8{ - \\pub fn foo() void { + \\pub export fn foo(_arg_c: u8) c_int { + \\ var c = _arg_c; \\ var a: c_int = undefined; - \\ _ = 1; - \\ _ = "hey"; - \\ _ = (1 + 1); - \\ _ = (1 - 1); - \\ a = 1; + \\ var b: c_int = undefined; + \\ a = blk: { + \\ const _tmp_1 = 2; + \\ b = _tmp_1; + \\ break :blk _tmp_1; + \\ }; + \\} + }); + + cases.add_2("if statements", + \\int foo(char c) { + \\ if (2) { + \\ int a = 2; + \\ } + \\ if (2, 5) { + \\ int a = 2; + \\ } + \\} + , &[_][]const u8{ + \\pub export fn foo(_arg_c: u8) c_int { + \\ var c = _arg_c; + \\ if (2 != 0) { + \\ var a: c_int = 2; + \\ } + \\ if ((blk: { + \\ _ = 2; + \\ break :blk 5; + \\ }) != 0) { + \\ var a: c_int = 2; + \\ } + \\} + }); + + cases.add_2("while loops", + \\int foo() { + \\ int a = 5; + \\ while (2) + \\ a = 2; + \\ while (4) { + \\ int a = 4; + \\ a = 9; + \\ return 6, a; + \\ } + \\ do { + \\ int a = 2; + \\ a = 12; + \\ } while (4); + \\ do + \\ a = 7; + \\ while (4); + \\} + , &[_][]const u8{ + \\pub export fn foo() c_int { + \\ var a: c_int = 5; + \\ while (2 != 0) a = 2; + \\ while (4 != 0) { + \\ var a: c_int = 4; + \\ a = 9; + \\ _ = 6; + \\ return a; + \\ } + \\ while (true) { + \\ var a: c_int = 2; + \\ a = 12; + \\ if (!(4 != 0)) break; + \\ } + \\ while (true) { + \\ a = 7; + \\ if (!(4 != 0)) break; + \\ } + \\} + }); + + cases.add_2("for loops", + \\int foo() { + \\ for (int i = 2, b = 4; i + 2; i = 2) { + \\ int a = 2; + \\ a = 6, 5, 7; + \\ } + \\ char i = 2; + \\} + , &[_][]const u8{ + \\pub export fn foo() c_int { + \\ { + \\ var i: c_int = 2; + \\ var b: c_int = 4; + \\ while ((i + 2) != 0) : (i = 2) { + \\ var a: c_int = 2; + \\ a = 6; + \\ _ = 5; + \\ _ = 7; + \\ } + \\ } + \\ var i: u8 = @as(u8, 2); + \\} + }); + + cases.add_2("shadowing primitive types", + \\unsigned anyerror = 2; + , &[_][]const u8{ + \\pub export var _anyerror: c_uint = @as(c_uint, 2); + }); + + cases.add_2("floats", + \\float a = 3.1415; + \\double b = 3.1415; + \\int c = 3.1415; + \\double d = 3; + , &[_][]const u8{ + \\pub export var a: f32 = @floatCast(f32, 3.1415); + \\pub export var b: f64 = 3.1415; + \\pub export var c: c_int = @floatToInt(c_int, 3.1415); + \\pub export var d: f64 = @intToFloat(f64, 3); + }); + + cases.add_2("conditional operator", + \\int bar(void) { + \\ if (2 ? 5 : 5 ? 4 : 6) 2; + \\ return 2 ? 5 : 5 ? 4 : 6; + \\} + , &[_][]const u8{ + \\pub export fn bar() c_int { + \\ if ((if (2 != 0) 5 else (if (5 != 0) 4 else 6)) != 0) _ = 2; + \\ return if (2 != 0) 5 else if (5 != 0) 4 else 6; + \\} + }); + + cases.add_2("switch on int", + \\int switch_fn(int i) { + \\ int res = 0; + \\ switch (i) { + \\ case 0: + \\ res = 1; + \\ case 1 ... 3: + \\ res = 2; + \\ default: + \\ res = 3 * i; + \\ break; + \\ case 4: + \\ res = 5; + \\ } + \\} + , &[_][]const u8{ + \\pub export fn switch_fn(_arg_i: c_int) c_int { + \\ var i = _arg_i; + \\ var res: c_int = 0; + \\ __switch: { + \\ __case_2: { + \\ __default: { + \\ __case_1: { + \\ __case_0: { + \\ switch (i) { + \\ 0 => break :__case_0, + \\ 1...3 => break :__case_1, + \\ else => break :__default, + \\ 4 => break :__case_2, + \\ } + \\ } + \\ res = 1; + \\ } + \\ res = 2; + \\ } + \\ res = (3 * i); + \\ break :__switch; + \\ } + \\ res = 5; + \\ } + \\} + }); + + cases.add_2("type referenced struct", + \\struct Foo { + \\ struct Bar{ + \\ int b; + \\ }; + \\ struct Bar c; + \\}; + , &[_][]const u8{ + \\pub const struct_Bar = extern struct { + \\ b: c_int, + \\}; + \\pub const struct_Foo = extern struct { + \\ c: struct_Bar, + \\}; + }); + + cases.add_2("undefined array global", + \\int array[100] = {}; + , &[_][]const u8{ + \\pub export var array: [100]c_int = .{0} ** 100; + }); + + cases.add_2("restrict -> noalias", + \\void foo(void *restrict bar, void *restrict); + , &[_][]const u8{ + \\pub extern fn foo(noalias bar: ?*c_void, noalias ?*c_void) void; + }); + + cases.add_2("assign", + \\int max(int a) { + \\ int tmp; + \\ tmp = a; + \\ a = tmp; + \\} + , &[_][]const u8{ + \\pub export fn max(_arg_a: c_int) c_int { + \\ var a = _arg_a; + \\ var tmp: c_int = undefined; + \\ tmp = a; + \\ a = tmp; + \\} + }); + + cases.add_2("chaining assign", + \\void max(int a) { + \\ int b, c; + \\ c = b = a; + \\} + , &[_][]const u8{ + \\pub export fn max(_arg_a: c_int) void { + \\ var a = _arg_a; + \\ var b: c_int = undefined; + \\ var c: c_int = undefined; + \\ c = blk: { + \\ const _tmp_1 = a; + \\ b = _tmp_1; + \\ break :blk _tmp_1; + \\ }; + \\} + }); + + cases.add_2("anonymous enum", + \\enum { + \\ One, + \\ Two, + \\}; + , &[_][]const u8{ + \\pub const One = 0; + \\pub const Two = 1; + \\const enum_unnamed_1 = extern enum { + \\ One, + \\ Two, + \\}; + }); + + cases.add_2("c style cast", + \\int float_to_int(float a) { + \\ return (int)a; + \\} + , &[_][]const u8{ + \\pub export fn float_to_int(_arg_a: f32) c_int { + \\ var a = _arg_a; + \\ return @floatToInt(c_int, a); + \\} + }); + + cases.add_2("escape sequences", + \\const char *escapes() { + \\char a = '\'', + \\ b = '\\', + \\ c = '\a', + \\ d = '\b', + \\ e = '\f', + \\ f = '\n', + \\ g = '\r', + \\ h = '\t', + \\ i = '\v', + \\ j = '\0', + \\ k = '\"'; + \\ return "\'\\\a\b\f\n\r\t\v\0\""; + \\} + \\ + , &[_][]const u8{ + \\pub export fn escapes() [*c]const u8 { + \\ var a: u8 = @as(u8, '\''); + \\ var b: u8 = @as(u8, '\\'); + \\ var c: u8 = @as(u8, '\x07'); + \\ var d: u8 = @as(u8, '\x08'); + \\ var e: u8 = @as(u8, '\x0c'); + \\ var f: u8 = @as(u8, '\n'); + \\ var g: u8 = @as(u8, '\r'); + \\ var h: u8 = @as(u8, '\t'); + \\ var i: u8 = @as(u8, '\x0b'); + \\ var j: u8 = @as(u8, '\x00'); + \\ var k: u8 = @as(u8, '\"'); + \\ return "\'\\\x07\x08\x0c\n\r\t\x0b\x00\""; + \\} + }); + + cases.add_2("do loop", + \\void foo(void) { + \\ int a = 2; + \\ do { + \\ a = a - 1; + \\ } while (a); + \\ + \\ int b = 2; + \\ do + \\ b = b -1; + \\ while (b); + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ var a: c_int = 2; + \\ while (true) { + \\ a = (a - 1); + \\ if (!(a != 0)) break; + \\ } + \\ var b: c_int = 2; + \\ while (true) { + \\ b = (b - 1); + \\ if (!(b != 0)) break; + \\ } + \\} + }); + + cases.add_2("logical and, logical or, on non-bool values, extra parens", + \\enum Foo { + \\ FooA, + \\ FooB, + \\ FooC, + \\}; + \\typedef int SomeTypedef; + \\int and_or_non_bool(int a, float b, void *c) { + \\ enum Foo d = FooA; + \\ int e = (a && b); + \\ int f = (b && c); + \\ int g = (a && c); + \\ int h = (a || b); + \\ int i = (b || c); + \\ int j = (a || c); + \\ int k = (a || d); + \\ int l = (d && b); + \\ int m = (c || d); + \\ SomeTypedef td = 44; + \\ int o = (td || b); + \\ int p = (c && td); + \\ return ((((((((((e + f) + g) + h) + i) + j) + k) + l) + m) + o) + p); + \\} + , &[_][]const u8{ + \\pub const enum_Foo = extern enum { + \\ A, + \\ B, + \\ C, + \\}; + \\pub const SomeTypedef = c_int; + \\pub export fn and_or_non_bool(_arg_a: c_int, _arg_b: f32, _arg_c: ?*c_void) c_int { + \\ var a = _arg_a; + \\ var b = _arg_b; + \\ var c = _arg_c; + \\ var d: enum_Foo = @intToEnum(enum_Foo, FooA); + \\ var e: c_int = @boolToInt(((a != 0) and (b != 0))); + \\ var f: c_int = @boolToInt(((b != 0) and (c != null))); + \\ var g: c_int = @boolToInt(((a != 0) and (c != null))); + \\ var h: c_int = @boolToInt(((a != 0) or (b != 0))); + \\ var i: c_int = @boolToInt(((b != 0) or (c != null))); + \\ var j: c_int = @boolToInt(((a != 0) or (c != null))); + \\ var k: c_int = @boolToInt(((a != 0) or (@enumToInt(d) != 0))); + \\ var l: c_int = @boolToInt(((@enumToInt(d) != 0) and (b != 0))); + \\ var m: c_int = @boolToInt(((c != null) or (@enumToInt(d) != 0))); + \\ var td: SomeTypedef = 44; + \\ var o: c_int = @boolToInt(((td != 0) or (b != 0))); + \\ var p: c_int = @boolToInt(((c != null) and (td != 0))); + \\ return ((((((((((e + f) + g) + h) + i) + j) + k) + l) + m) + o) + p); + \\} + , + \\pub const Foo = enum_Foo; + }); + + cases.add_2("qualified struct and enum", + \\struct Foo { + \\ int x; + \\ int y; + \\}; + \\enum Bar { + \\ BarA, + \\ BarB, + \\}; + \\void func(struct Foo *a, enum Bar **b); + , &[_][]const u8{ + \\pub const struct_Foo = extern struct { + \\ x: c_int, + \\ y: c_int, + \\}; + , + \\pub const enum_Bar = extern enum { + \\ A, + \\ B, + \\}; + \\pub extern fn func(a: [*c]struct_Foo, b: [*c][*c]enum_Bar) void; + , + \\pub const Foo = struct_Foo; + \\pub const Bar = enum_Bar; + }); + + cases.add_2("bitwise binary operators, simpler parens", + \\int max(int a, int b) { + \\ return (a & b) ^ (a | b); + \\} + , &[_][]const u8{ + \\pub export fn max(_arg_a: c_int, _arg_b: c_int) c_int { + \\ var a = _arg_a; + \\ var b = _arg_b; + \\ return ((a & b) ^ (a | b)); + \\} + }); + + cases.add_2("comparison operators (no if)", // TODO Come up with less contrived tests? Make sure to cover all these comparisons. + \\int test_comparisons(int a, int b) { + \\ int c = (a < b); + \\ int d = (a > b); + \\ int e = (a <= b); + \\ int f = (a >= b); + \\ int g = (c < d); + \\ int h = (e < f); + \\ int i = (g < h); + \\ return i; + \\} + , &[_][]const u8{ + \\pub export fn test_comparisons(_arg_a: c_int, _arg_b: c_int) c_int { + \\ var a = _arg_a; + \\ var b = _arg_b; + \\ var c: c_int = @boolToInt((a < b)); + \\ var d: c_int = @boolToInt((a > b)); + \\ var e: c_int = @boolToInt((a <= b)); + \\ var f: c_int = @boolToInt((a >= b)); + \\ var g: c_int = @boolToInt((c < d)); + \\ var h: c_int = @boolToInt((e < f)); + \\ var i: c_int = @boolToInt((g < h)); + \\ return i; + \\} + }); + + cases.add_2("==, !=", + \\int max(int a, int b) { + \\ if (a == b) + \\ return a; + \\ if (a != b) + \\ return b; + \\ return a; + \\} + , &[_][]const u8{ + \\pub export fn max(_arg_a: c_int, _arg_b: c_int) c_int { + \\ var a = _arg_a; + \\ var b = _arg_b; + \\ if (a == b) return a; + \\ if (a != b) return b; + \\ return a; + \\} + }); + + cases.add_2("typedeffed bool expression", + \\typedef char* yes; + \\void foo(void) { + \\ yes a; + \\ if (a) 2; + \\} + , &[_][]const u8{ + \\pub const yes = [*c]u8; + \\pub export fn foo() void { + \\ var a: yes = undefined; + \\ if (a != null) _ = 2; + \\} + }); + + cases.add_2("statement expression", + \\int foo(void) { + \\ return ({ + \\ int a = 1; + \\ a; + \\ a; + \\ }); + \\} + , &[_][]const u8{ + \\pub export fn foo() c_int { + \\ return (blk: { + \\ var a: c_int = 1; + \\ _ = a; + \\ break :blk a; + \\ }); + \\} + }); + + cases.add_2("field access expression", + \\#define ARROW a->b + \\#define DOT a.b + \\extern struct Foo { + \\ int b; + \\}a; + \\float b = 2.0f; + \\int foo(void) { + \\ struct Foo *c; + \\ a.b; + \\ c->b; + \\} + , &[_][]const u8{ + \\pub const struct_Foo = extern struct { + \\ b: c_int, + \\}; + \\pub extern var a: struct_Foo; + \\pub export var b: f32 = 2; + \\pub export fn foo() c_int { + \\ var c: [*c]struct_Foo = undefined; + \\ _ = a.b; + \\ _ = c.*.b; + \\} + , + \\pub const DOT = a.b; + , + \\pub const ARROW = a.*.b; + }); + + cases.add_2("array access", + \\#define ACCESS array[2] + \\int array[100] = {}; + \\int foo(int index) { + \\ return array[index]; + \\} + , &[_][]const u8{ + \\pub export var array: [100]c_int = .{0} ** 100; + \\pub export fn foo(_arg_index: c_int) c_int { + \\ var index = _arg_index; + \\ return array[index]; + \\} + , + \\pub const ACCESS = array[2]; + }); + + cases.add_2("macro call", + \\#define CALL(arg) bar(arg) + , &[_][]const u8{ + \\pub inline fn CALL(arg: var) @TypeOf(bar(arg)) { + \\ return bar(arg); + \\} + }); + + cases.add_2("logical and, logical or", + \\int max(int a, int b) { + \\ if (a < b || a == b) + \\ return b; + \\ if (a >= b && a == b) + \\ return a; + \\ return a; + \\} + , &[_][]const u8{ + \\pub export fn max(_arg_a: c_int, _arg_b: c_int) c_int { + \\ var a = _arg_a; + \\ var b = _arg_b; + \\ if ((a < b) or (a == b)) return b; + \\ if ((a >= b) and (a == b)) return a; + \\ return a; + \\} + }); + + cases.add_2("if statement", + \\int max(int a, int b) { + \\ if (a < b) + \\ return b; + \\ + \\ if (a < b) + \\ return b; + \\ else + \\ return a; + \\ + \\ if (a < b) ; else ; + \\} + , &[_][]const u8{ + \\pub export fn max(_arg_a: c_int, _arg_b: c_int) c_int { + \\ var a = _arg_a; + \\ var b = _arg_b; + \\ if (a < b) return b; + \\ if (a < b) return b else return a; + \\ if (a < b) {} else {} + \\} + }); + + cases.add_2("if on non-bool", + \\enum SomeEnum { A, B, C }; + \\int if_none_bool(int a, float b, void *c, enum SomeEnum d) { + \\ if (a) return 0; + \\ if (b) return 1; + \\ if (c) return 2; + \\ if (d) return 3; + \\ return 4; + \\} + , &[_][]const u8{ + \\pub const enum_SomeEnum = extern enum { + \\ A, + \\ B, + \\ C, + \\}; + \\pub export fn if_none_bool(_arg_a: c_int, _arg_b: f32, _arg_c: ?*c_void, _arg_d: enum_SomeEnum) c_int { + \\ var a = _arg_a; + \\ var b = _arg_b; + \\ var c = _arg_c; + \\ var d = _arg_d; + \\ if (a != 0) return 0; + \\ if (b != 0) return 1; + \\ if (c != null) return 2; + \\ if (d != 0) return 3; + \\ return 4; + \\} + }); + + cases.add_2("simple data types", + \\#include + \\int foo(char a, unsigned char b, signed char c); + \\int foo(char a, unsigned char b, signed char c); // test a duplicate prototype + \\void bar(uint8_t a, uint16_t b, uint32_t c, uint64_t d); + \\void baz(int8_t a, int16_t b, int32_t c, int64_t d); + , &[_][]const u8{ + \\pub extern fn foo(a: u8, b: u8, c: i8) c_int; + \\pub extern fn bar(a: u8, b: u16, c: u32, d: u64) void; + \\pub extern fn baz(a: i8, b: i16, c: i32, d: i64) void; + }); + + cases.add_2("simple function", + \\int abs(int a) { + \\ return a < 0 ? -a : a; + \\} + , &[_][]const u8{ + \\pub export fn abs(_arg_a: c_int) c_int { + \\ var a = _arg_a; + \\ return if (a < 0) -a else a; + \\} + }); + + cases.add_2("post increment", + \\unsigned foo1(unsigned a) { + \\ a++; + \\ return a; + \\} + \\int foo2(int a) { + \\ a++; + \\ return a; + \\} + , &[_][]const u8{ + \\pub export fn foo1(_arg_a: c_uint) c_uint { + \\ var a = _arg_a; + \\ a +%= 1; + \\ return a; + \\} + \\pub export fn foo2(_arg_a: c_int) c_int { + \\ var a = _arg_a; + \\ a += 1; + \\ return a; + \\} + }); + + cases.add_2("deref function pointer", + \\void foo(void) {} + \\int baz(void) { return 0; } + \\void bar(void) { + \\ void(*f)(void) = foo; + \\ int(*b)(void) = baz; + \\ f(); + \\ (*(f))(); + \\ foo(); + \\ b(); + \\ (*(b))(); + \\ baz(); + \\} + , &[_][]const u8{ + \\pub export fn foo() void {} + \\pub export fn baz() c_int { + \\ return 0; + \\} + \\pub export fn bar() void { + \\ var f: ?extern fn () void = foo; + \\ var b: ?extern fn () c_int = baz; + \\ f.?(); + \\ (f).?(); + \\ foo(); + \\ _ = b.?(); + \\ _ = (b).?(); + \\ _ = baz(); + \\} + }); + + cases.add_2("pre increment/decrement", + \\void foo(void) { + \\ int i = 0; + \\ unsigned u = 0; + \\ ++i; + \\ --i; + \\ ++u; + \\ --u; + \\ i = ++i; + \\ i = --i; + \\ u = ++u; + \\ u = --u; + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ var i: c_int = 0; + \\ var u: c_uint = @as(c_uint, 0); + \\ i += 1; + \\ i -= 1; + \\ u +%= 1; + \\ u -%= 1; + \\ i = (blk: { + \\ const _ref_1 = &i; + \\ _ref_1.* += 1; + \\ break :blk _ref_1.*; + \\ }); + \\ i = (blk: { + \\ const _ref_2 = &i; + \\ _ref_2.* -= 1; + \\ break :blk _ref_2.*; + \\ }); + \\ u = (blk: { + \\ const _ref_3 = &u; + \\ _ref_3.* +%= 1; + \\ break :blk _ref_3.*; + \\ }); + \\ u = (blk: { + \\ const _ref_4 = &u; + \\ _ref_4.* -%= 1; + \\ break :blk _ref_4.*; + \\ }); + \\} + }); + + cases.add_2("shift right assign", + \\int log2(unsigned a) { + \\ int i = 0; + \\ while (a > 0) { + \\ a >>= 1; + \\ } + \\ return i; + \\} + , &[_][]const u8{ + \\pub export fn log2(_arg_a: c_uint) c_int { + \\ var a = _arg_a; + \\ var i: c_int = 0; + \\ while (a > @as(c_uint, 0)) { + \\ a >>= @as(@import("std").math.Log2Int(c_int), 1); + \\ } + \\ return i; + \\} + }); + + cases.add_2("shift right assign with a fixed size type", + \\#include + \\int log2(uint32_t a) { + \\ int i = 0; + \\ while (a > 0) { + \\ a >>= 1; + \\ } + \\ return i; + \\} + , &[_][]const u8{ + \\pub export fn log2(_arg_a: u32) c_int { + \\ var a = _arg_a; + \\ var i: c_int = 0; + \\ while (a > @as(c_uint, 0)) { + \\ a >>= @as(@import("std").math.Log2Int(c_int), 1); + \\ } + \\ return i; + \\} + }); + + cases.add_2("compound assignment operators", + \\void foo(void) { + \\ int a = 0; + \\ a += (a += 1); + \\ a -= (a -= 1); + \\ a *= (a *= 1); + \\ a &= (a &= 1); + \\ a |= (a |= 1); + \\ a ^= (a ^= 1); + \\ a >>= (a >>= 1); + \\ a <<= (a <<= 1); + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ var a: c_int = 0; + \\ a += (blk: { + \\ const _ref_1 = &a; + \\ _ref_1.* = _ref_1.* + 1; + \\ break :blk _ref_1.*; + \\ }); + \\ a -= (blk: { + \\ const _ref_2 = &a; + \\ _ref_2.* = _ref_2.* - 1; + \\ break :blk _ref_2.*; + \\ }); + \\ a *= (blk: { + \\ const _ref_3 = &a; + \\ _ref_3.* = _ref_3.* * 1; + \\ break :blk _ref_3.*; + \\ }); + \\ a &= (blk: { + \\ const _ref_4 = &a; + \\ _ref_4.* = _ref_4.* & 1; + \\ break :blk _ref_4.*; + \\ }); + \\ a |= (blk: { + \\ const _ref_5 = &a; + \\ _ref_5.* = _ref_5.* | 1; + \\ break :blk _ref_5.*; + \\ }); + \\ a ^= (blk: { + \\ const _ref_6 = &a; + \\ _ref_6.* = _ref_6.* ^ 1; + \\ break :blk _ref_6.*; + \\ }); + \\ a >>= @as(@import("std").math.Log2Int(c_int), (blk: { + \\ const _ref_7 = &a; + \\ _ref_7.* = _ref_7.* >> @as(@import("std").math.Log2Int(c_int), 1); + \\ break :blk _ref_7.*; + \\ })); + \\ a <<= @as(@import("std").math.Log2Int(c_int), (blk: { + \\ const _ref_8 = &a; + \\ _ref_8.* = _ref_8.* << @as(@import("std").math.Log2Int(c_int), 1); + \\ break :blk _ref_8.*; + \\ })); + \\} + }); + + cases.add_2("compound assignment operators unsigned", + \\void foo(void) { + \\ unsigned a = 0; + \\ a += (a += 1); + \\ a -= (a -= 1); + \\ a *= (a *= 1); + \\ a &= (a &= 1); + \\ a |= (a |= 1); + \\ a ^= (a ^= 1); + \\ a >>= (a >>= 1); + \\ a <<= (a <<= 1); + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ var a: c_uint = @as(c_uint, 0); + \\ a +%= (blk: { + \\ const _ref_1 = &a; + \\ _ref_1.* = _ref_1.* +% @as(c_uint, 1); + \\ break :blk _ref_1.*; + \\ }); + \\ a -%= (blk: { + \\ const _ref_2 = &a; + \\ _ref_2.* = _ref_2.* -% @as(c_uint, 1); + \\ break :blk _ref_2.*; + \\ }); + \\ a *%= (blk: { + \\ const _ref_3 = &a; + \\ _ref_3.* = _ref_3.* *% @as(c_uint, 1); + \\ break :blk _ref_3.*; + \\ }); + \\ a &= (blk: { + \\ const _ref_4 = &a; + \\ _ref_4.* = _ref_4.* & @as(c_uint, 1); + \\ break :blk _ref_4.*; + \\ }); + \\ a |= (blk: { + \\ const _ref_5 = &a; + \\ _ref_5.* = _ref_5.* | @as(c_uint, 1); + \\ break :blk _ref_5.*; + \\ }); + \\ a ^= (blk: { + \\ const _ref_6 = &a; + \\ _ref_6.* = _ref_6.* ^ @as(c_uint, 1); + \\ break :blk _ref_6.*; + \\ }); + \\ a >>= @as(@import("std").math.Log2Int(c_uint), (blk: { + \\ const _ref_7 = &a; + \\ _ref_7.* = _ref_7.* >> @as(@import("std").math.Log2Int(c_int), 1); + \\ break :blk _ref_7.*; + \\ })); + \\ a <<= @as(@import("std").math.Log2Int(c_uint), (blk: { + \\ const _ref_8 = &a; + \\ _ref_8.* = _ref_8.* << @as(@import("std").math.Log2Int(c_int), 1); + \\ break :blk _ref_8.*; + \\ })); + \\} + }); + + cases.add_2("post increment/decrement", + \\void foo(void) { + \\ int i = 0; + \\ unsigned u = 0; + \\ i++; + \\ i--; + \\ u++; + \\ u--; + \\ i = i++; + \\ i = i--; + \\ u = u++; + \\ u = u--; + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ var i: c_int = 0; + \\ var u: c_uint = @as(c_uint, 0); + \\ i += 1; + \\ i -= 1; + \\ u +%= 1; + \\ u -%= 1; + \\ i = (blk: { + \\ const _ref_1 = &i; + \\ const _tmp_2 = _ref_1.*; + \\ _ref_1.* += 1; + \\ break :blk _tmp_2; + \\ }); + \\ i = (blk: { + \\ const _ref_3 = &i; + \\ const _tmp_4 = _ref_3.*; + \\ _ref_3.* -= 1; + \\ break :blk _tmp_4; + \\ }); + \\ u = (blk: { + \\ const _ref_5 = &u; + \\ const _tmp_6 = _ref_5.*; + \\ _ref_5.* +%= 1; + \\ break :blk _tmp_6; + \\ }); + \\ u = (blk: { + \\ const _ref_7 = &u; + \\ const _tmp_8 = _ref_7.*; + \\ _ref_7.* -%= 1; + \\ break :blk _tmp_8; + \\ }); + \\} + }); + + cases.add_2("implicit casts", + \\#include + \\ + \\void fn_int(int x); + \\void fn_f32(float x); + \\void fn_f64(double x); + \\void fn_char(char x); + \\void fn_bool(bool x); + \\void fn_ptr(void *x); + \\ + \\void call(int q) { + \\ fn_int(3.0f); + \\ fn_int(3.0); + \\ fn_int(3.0L); + \\ fn_int('ABCD'); + \\ fn_f32(3); + \\ fn_f64(3); + \\ fn_char('3'); + \\ fn_char('\x1'); + \\ fn_char(0); + \\ fn_f32(3.0f); + \\ fn_f64(3.0); + \\ fn_bool(123); + \\ fn_bool(0); + \\ fn_bool(&fn_int); + \\ fn_int(&fn_int); + \\ fn_ptr(42); + \\} + , &[_][]const u8{ + \\pub extern fn fn_int(x: c_int) void; + \\pub extern fn fn_f32(x: f32) void; + \\pub extern fn fn_f64(x: f64) void; + \\pub extern fn fn_char(x: u8) void; + \\pub extern fn fn_bool(x: bool) void; + \\pub extern fn fn_ptr(x: ?*c_void) void; + \\pub export fn call(_arg_q: c_int) void { + \\ var q = _arg_q; + \\ fn_int(@floatToInt(c_int, 3)); + \\ fn_int(@floatToInt(c_int, 3)); + \\ fn_int(@floatToInt(c_int, 3)); + \\ fn_int(1094861636); + \\ fn_f32(@intToFloat(f32, 3)); + \\ fn_f64(@intToFloat(f64, 3)); + \\ fn_char(@as(u8, '3')); + \\ fn_char(@as(u8, '\x01')); + \\ fn_char(@as(u8, 0)); + \\ fn_f32(3); + \\ fn_f64(3); + \\ fn_bool(123 != 0); + \\ fn_bool(0 != 0); + \\ fn_bool(@ptrToInt(&fn_int) != 0); + \\ fn_int(@intCast(c_int, @ptrToInt(&fn_int))); + \\ fn_ptr(@intToPtr(?*c_void, 42)); + \\} + }); + + cases.add_2("function call", + \\static void bar(void) { } + \\void foo(int *(baz)(void)) { + \\ bar(); + \\ baz(); + \\} + , &[_][]const u8{ + \\pub fn bar() void {} + \\pub export fn foo(_arg_baz: ?extern fn () [*c]c_int) void { + \\ var baz = _arg_baz; + \\ bar(); + \\ _ = baz.?(); + \\} + }); + + cases.add_2("macro defines string literal with octal", + \\#define FOO "aoeu\023 derp" + \\#define FOO2 "aoeu\0234 derp" + \\#define FOO_CHAR '\077' + , &[_][]const u8{ + \\pub const FOO = "aoeu\x13 derp"; + , + \\pub const FOO2 = "aoeu\x134 derp"; + , + \\pub const FOO_CHAR = '\x3f'; + }); + + cases.add_2("enums", + \\enum Foo { + \\ FooA, + \\ FooB, + \\ Foo1, + \\}; + , &[_][]const u8{ + \\pub const enum_Foo = extern enum { + \\ A, + \\ B, + \\ @"1", + \\}; + , + \\pub const FooA = 0; + , + \\pub const FooB = 1; + , + \\pub const Foo1 = 2; + , + \\pub const Foo = enum_Foo; + }); + + cases.add_2("enums", + \\enum Foo { + \\ FooA = 2, + \\ FooB = 5, + \\ Foo1, + \\}; + , &[_][]const u8{ + \\pub const enum_Foo = extern enum { + \\ A = 2, + \\ B = 5, + \\ @"1" = 6, + \\}; + , + \\pub const FooA = 2; + , + \\pub const FooB = 5; + , + \\pub const Foo1 = 6; + , + \\pub const Foo = enum_Foo; + }); + + cases.add_2("macro cast", + \\#define FOO(bar) baz((void *)(baz)) + , &[_][]const u8{ + \\pub inline fn FOO(bar: var) @TypeOf(baz(if (@typeId(@TypeOf(baz)) == .Pointer) @ptrCast([*c]void, baz) else if (@typeId(@TypeOf(baz)) == .Int) @intToPtr([*c]void, baz) else @as([*c]void, baz))) { + \\ return baz(if (@typeId(@TypeOf(baz)) == .Pointer) @ptrCast([*c]void, baz) else if (@typeId(@TypeOf(baz)) == .Int) @intToPtr([*c]void, baz) else @as([*c]void, baz)); + \\} + }); + + /////////////// Cases for only stage1 because stage2 behavior is better //////////////// + cases.addC("Parameterless function prototypes", + \\void foo() {} + \\void bar(void) {} + , &[_][]const u8{ + \\pub export fn foo() void {} + \\pub export fn bar() void {} + }); + + cases.add("#define a char literal", + \\#define A_CHAR 'a' + , &[_][]const u8{ + \\pub const A_CHAR = 97; + }); + + cases.add("generate inline func for #define global extern fn", + \\extern void (*fn_ptr)(void); + \\#define foo fn_ptr + \\ + \\extern char (*fn_ptr2)(int, float); + \\#define bar fn_ptr2 + , &[_][]const u8{ + \\pub extern var fn_ptr: ?extern fn () void; + , + \\pub inline fn foo() void { + \\ return fn_ptr.?(); + \\} + , + \\pub extern var fn_ptr2: ?extern fn (c_int, f32) u8; + , + \\pub inline fn bar(arg0: c_int, arg1: f32) u8 { + \\ return fn_ptr2.?(arg0, arg1); + \\} + }); + cases.add("comment after integer literal", + \\#define SDL_INIT_VIDEO 0x00000020 /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ + , &[_][]const u8{ + \\pub const SDL_INIT_VIDEO = 32; + }); + + cases.add("u integer suffix after hex literal", + \\#define SDL_INIT_VIDEO 0x00000020u /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ + , &[_][]const u8{ + \\pub const SDL_INIT_VIDEO = @as(c_uint, 32); + }); + + cases.add("l integer suffix after hex literal", + \\#define SDL_INIT_VIDEO 0x00000020l /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ + , &[_][]const u8{ + \\pub const SDL_INIT_VIDEO = @as(c_long, 32); + }); + + cases.add("ul integer suffix after hex literal", + \\#define SDL_INIT_VIDEO 0x00000020ul /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ + , &[_][]const u8{ + \\pub const SDL_INIT_VIDEO = @as(c_ulong, 32); + }); + + cases.add("lu integer suffix after hex literal", + \\#define SDL_INIT_VIDEO 0x00000020lu /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ + , &[_][]const u8{ + \\pub const SDL_INIT_VIDEO = @as(c_ulong, 32); + }); + + cases.add("ll integer suffix after hex literal", + \\#define SDL_INIT_VIDEO 0x00000020ll /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ + , &[_][]const u8{ + \\pub const SDL_INIT_VIDEO = @as(c_longlong, 32); + }); + + cases.add("ull integer suffix after hex literal", + \\#define SDL_INIT_VIDEO 0x00000020ull /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ + , &[_][]const u8{ + \\pub const SDL_INIT_VIDEO = @as(c_ulonglong, 32); + }); + + cases.add("llu integer suffix after hex literal", + \\#define SDL_INIT_VIDEO 0x00000020llu /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ + , &[_][]const u8{ + \\pub const SDL_INIT_VIDEO = @as(c_ulonglong, 32); + }); + + cases.add("macros with field targets", + \\typedef unsigned int GLbitfield; + \\typedef void (*PFNGLCLEARPROC) (GLbitfield mask); + \\typedef void(*OpenGLProc)(void); + \\union OpenGLProcs { + \\ OpenGLProc ptr[1]; + \\ struct { + \\ PFNGLCLEARPROC Clear; + \\ } gl; + \\}; + \\extern union OpenGLProcs glProcs; + \\#define glClearUnion glProcs.gl.Clear + \\#define glClearPFN PFNGLCLEARPROC + , &[_][]const u8{ + \\pub const GLbitfield = c_uint; + , + \\pub const PFNGLCLEARPROC = ?extern fn (GLbitfield) void; + , + \\pub const OpenGLProc = ?extern fn () void; + , + \\pub const union_OpenGLProcs = extern union { + \\ ptr: [1]OpenGLProc, + \\ gl: extern struct { + \\ Clear: PFNGLCLEARPROC, + \\ }, + \\}; + , + \\pub extern var glProcs: union_OpenGLProcs; + , + \\pub const glClearPFN = PFNGLCLEARPROC; + , + \\pub inline fn glClearUnion(arg0: GLbitfield) void { + \\ return glProcs.gl.Clear.?(arg0); + \\} + , + \\pub const OpenGLProcs = union_OpenGLProcs; + }); + + cases.add("macro pointer cast", + \\#define NRF_GPIO ((NRF_GPIO_Type *) NRF_GPIO_BASE) + , &[_][]const u8{ + \\pub const NRF_GPIO = if (@typeId(@TypeOf(NRF_GPIO_BASE)) == @import("builtin").TypeId.Pointer) @ptrCast([*c]NRF_GPIO_Type, NRF_GPIO_BASE) else if (@typeId(@TypeOf(NRF_GPIO_BASE)) == @import("builtin").TypeId.Int) @intToPtr([*c]NRF_GPIO_Type, NRF_GPIO_BASE) else @as([*c]NRF_GPIO_Type, NRF_GPIO_BASE); + }); + + cases.add("switch on int", + \\int switch_fn(int i) { + \\ int res = 0; + \\ switch (i) { + \\ case 0: + \\ res = 1; + \\ case 1: + \\ res = 2; + \\ default: + \\ res = 3 * i; + \\ break; + \\ case 2: + \\ res = 5; + \\ } + \\} + , &[_][]const u8{ + \\pub fn switch_fn(i: c_int) c_int { + \\ var res: c_int = 0; + \\ __switch: { + \\ __case_2: { + \\ __default: { + \\ __case_1: { + \\ __case_0: { + \\ switch (i) { + \\ 0 => break :__case_0, + \\ 1 => break :__case_1, + \\ else => break :__default, + \\ 2 => break :__case_2, + \\ } + \\ } + \\ res = 1; + \\ } + \\ res = 2; + \\ } + \\ res = (3 * i); + \\ break :__switch; + \\ } + \\ res = 5; + \\ } \\} }); @@ -838,34 +2387,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - cases.addAllowWarnings("simple data types", - \\#include - \\int foo(char a, unsigned char b, signed char c); - \\int foo(char a, unsigned char b, signed char c); // test a duplicate prototype - \\void bar(uint8_t a, uint16_t b, uint32_t c, uint64_t d); - \\void baz(int8_t a, int16_t b, int32_t c, int64_t d); + cases.add("undefined array global", + \\int array[100]; , &[_][]const u8{ - \\pub extern fn foo(a: u8, b: u8, c: i8) c_int; - , - \\pub extern fn bar(a: u8, b: u16, c: u32, d: u64) void; - , - \\pub extern fn baz(a: i8, b: i16, c: i32, d: i64) void; - }); - - cases.addC("simple function", - \\int abs(int a) { - \\ return a < 0 ? -a : a; - \\} - , &[_][]const u8{ - \\export fn abs(a: c_int) c_int { - \\ return if (a < 0) -a else a; - \\} - }); - - cases.add("restrict -> noalias", - \\void foo(void *restrict bar, void *restrict); - , &[_][]const u8{ - \\pub extern fn foo(noalias bar: ?*c_void, noalias arg1: ?*c_void) void; + \\pub var array: [100]c_int = undefined; }); cases.add("qualified struct and enum", @@ -900,165 +2425,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const Bar = enum_Bar; }); - cases.add("constant size array", - \\void func(int array[20]); + cases.add("restrict -> noalias", + \\void foo(void *restrict bar, void *restrict); , &[_][]const u8{ - \\pub extern fn func(array: [*c]c_int) void; - }); - - cases.add("__cdecl doesn't mess up function pointers", - \\void foo(void (__cdecl *fn_ptr)(void)); - , &[_][]const u8{ - \\pub extern fn foo(fn_ptr: ?extern fn () void) void; - }); - - cases.add("macro defines string literal with hex", - \\#define FOO "aoeu\xab derp" - \\#define FOO2 "aoeu\x0007a derp" - \\#define FOO_CHAR '\xfF' - , &[_][]const u8{ - \\pub const FOO = "aoeu\xab derp"; - , - \\pub const FOO2 = "aoeuz derp"; - , - \\pub const FOO_CHAR = 255; - }); - - cases.add("macro defines string literal with octal", - \\#define FOO "aoeu\023 derp" - \\#define FOO2 "aoeu\0234 derp" - \\#define FOO_CHAR '\077' - , &[_][]const u8{ - \\pub const FOO = "aoeu\x13 derp"; - , - \\pub const FOO2 = "aoeu\x134 derp"; - , - \\pub const FOO_CHAR = 63; - }); - - cases.addC("post increment", - \\unsigned foo1(unsigned a) { - \\ a++; - \\ return a; - \\} - \\int foo2(int a) { - \\ a++; - \\ return a; - \\} - , &[_][]const u8{ - \\pub export fn foo1(_arg_a: c_uint) c_uint { - \\ var a = _arg_a; - \\ a +%= 1; - \\ return a; - \\} - \\pub export fn foo2(_arg_a: c_int) c_int { - \\ var a = _arg_a; - \\ a += 1; - \\ return a; - \\} - }); - - cases.addC("shift right assign", - \\int log2(unsigned a) { - \\ int i = 0; - \\ while (a > 0) { - \\ a >>= 1; - \\ } - \\ return i; - \\} - , &[_][]const u8{ - \\pub export fn log2(_arg_a: c_uint) c_int { - \\ var a = _arg_a; - \\ var i: c_int = 0; - \\ while (a > @as(c_uint, 0)) { - \\ a >>= @as(@import("std").math.Log2Int(c_uint), 1); - \\ } - \\ return i; - \\} - }); - - cases.addC("if statement", - \\int max(int a, int b) { - \\ if (a < b) - \\ return b; - \\ - \\ if (a < b) - \\ return b; - \\ else - \\ return a; - \\ - \\ if (a < b) ; else ; - \\} - , &[_][]const u8{ - \\pub export fn max(a: c_int, b: c_int) c_int { - \\ if (a < b) return b; - \\ if (a < b) return b else return a; - \\ if (a < b) {} else {} - \\} - }); - - cases.addC("==, !=", - \\int max(int a, int b) { - \\ if (a == b) - \\ return a; - \\ if (a != b) - \\ return b; - \\ return a; - \\} - , &[_][]const u8{ - \\pub export fn max(a: c_int, b: c_int) c_int { - \\ if (a == b) return a; - \\ if (a != b) return b; - \\ return a; - \\} - }); - - cases.addC("bitwise binary operators", - \\int max(int a, int b) { - \\ return (a & b) ^ (a | b); - \\} - , &[_][]const u8{ - \\pub export fn max(a: c_int, b: c_int) c_int { - \\ return (a & b) ^ (a | b); - \\} - }); - - cases.addC("logical and, logical or", - \\int max(int a, int b) { - \\ if (a < b || a == b) - \\ return b; - \\ if (a >= b && a == b) - \\ return a; - \\ return a; - \\} - , &[_][]const u8{ - \\pub export fn max(a: c_int, b: c_int) c_int { - \\ if ((a < b) or (a == b)) return b; - \\ if ((a >= b) and (a == b)) return a; - \\ return a; - \\} - }); - - cases.addC("logical and, logical or on none bool values", - \\int and_or_none_bool(int a, float b, void *c) { - \\ if (a && b) return 0; - \\ if (b && c) return 1; - \\ if (a && c) return 2; - \\ if (a || b) return 3; - \\ if (b || c) return 4; - \\ if (a || c) return 5; - \\ return 6; - \\} - , &[_][]const u8{ - \\pub export fn and_or_none_bool(a: c_int, b: f32, c: ?*c_void) c_int { - \\ if ((a != 0) and (b != 0)) return 0; - \\ if ((b != 0) and (c != null)) return 1; - \\ if ((a != 0) and (c != null)) return 2; - \\ if ((a != 0) or (b != 0)) return 3; - \\ if ((b != 0) or (c != null)) return 4; - \\ if ((a != 0) or (c != null)) return 5; - \\ return 6; - \\} + \\pub extern fn foo(noalias bar: ?*c_void, noalias arg1: ?*c_void) void; }); cases.addC("assign", @@ -1093,26 +2463,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - cases.addC("shift right assign with a fixed size type", - \\#include - \\int log2(uint32_t a) { - \\ int i = 0; - \\ while (a > 0) { - \\ a >>= 1; - \\ } - \\ return i; - \\} - , &[_][]const u8{ - \\pub export fn log2(_arg_a: u32) c_int { - \\ var a = _arg_a; - \\ var i: c_int = 0; - \\ while (a > @as(c_uint, 0)) { - \\ a >>= @as(u5, 1); - \\ } - \\ return i; - \\} - }); - cases.add("anonymous enum", \\enum { \\ One, @@ -1123,72 +2473,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const Two = 1; }); - cases.addC("function call", - \\static void bar(void) { } - \\static int baz(void) { return 0; } - \\void foo(void) { - \\ bar(); - \\ baz(); - \\} - , &[_][]const u8{ - \\pub fn bar() void {} - \\pub fn baz() c_int { - \\ return 0; - \\} - \\pub export fn foo() void { - \\ bar(); - \\ _ = baz(); - \\} - }); - - cases.addC("field access expression", - \\struct Foo { - \\ int field; - \\}; - \\int read_field(struct Foo *foo) { - \\ return foo->field; - \\} - , &[_][]const u8{ - \\pub const struct_Foo = extern struct { - \\ field: c_int, - \\}; - \\pub export fn read_field(foo: [*c]struct_Foo) c_int { - \\ return foo.*.field; - \\} - }); - - cases.addC("null statements", - \\void foo(void) { - \\ ;;;;; - \\} - , &[_][]const u8{ - \\pub export fn foo() void { - \\ {} - \\ {} - \\ {} - \\ {} - \\ {} - \\} - }); - - cases.add("undefined array global", - \\int array[100]; - , &[_][]const u8{ - \\pub var array: [100]c_int = undefined; - }); - - cases.addC("array access", - \\int array[100]; - \\int foo(int index) { - \\ return array[index]; - \\} - , &[_][]const u8{ - \\pub var array: [100]c_int = undefined; - \\pub export fn foo(index: c_int) c_int { - \\ return array[index]; - \\} - }); - cases.addC("c style cast", \\int float_to_int(float a) { \\ return (int)a; @@ -1199,47 +2483,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - cases.addC("void cast", - \\void foo(int a) { - \\ (void) a; - \\} - , &[_][]const u8{ - \\pub export fn foo(a: c_int) void { - \\ _ = a; - \\} - }); - - cases.addC("implicit cast to void *", - \\void *foo(unsigned short *x) { - \\ return x; - \\} - , &[_][]const u8{ - \\pub export fn foo(x: [*c]c_ushort) ?*c_void { - \\ return @ptrCast(?*c_void, x); - \\} - }); - - cases.addC("sizeof", - \\#include - \\size_t size_of(void) { - \\ return sizeof(int); - \\} - , &[_][]const u8{ - \\pub export fn size_of() usize { - \\ return @sizeOf(c_int); - \\} - }); - - cases.addC("null pointer implicit cast", - \\int* foo(void) { - \\ return 0; - \\} - , &[_][]const u8{ - \\pub export fn foo() [*c]c_int { - \\ return null; - \\} - }); - cases.addC("comma operator", \\int foo(void) { \\ return 1, 2; @@ -1253,6 +2496,93 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); + cases.addC("escape sequences", + \\const char *escapes() { + \\char a = '\'', + \\ b = '\\', + \\ c = '\a', + \\ d = '\b', + \\ e = '\f', + \\ f = '\n', + \\ g = '\r', + \\ h = '\t', + \\ i = '\v', + \\ j = '\0', + \\ k = '\"'; + \\ return "\'\\\a\b\f\n\r\t\v\0\""; + \\} + \\ + , &[_][]const u8{ + \\pub export fn escapes() [*c]const u8 { + \\ var a: u8 = @as(u8, '\''); + \\ var b: u8 = @as(u8, '\\'); + \\ var c: u8 = @as(u8, '\x07'); + \\ var d: u8 = @as(u8, '\x08'); + \\ var e: u8 = @as(u8, '\x0c'); + \\ var f: u8 = @as(u8, '\n'); + \\ var g: u8 = @as(u8, '\r'); + \\ var h: u8 = @as(u8, '\t'); + \\ var i: u8 = @as(u8, '\x0b'); + \\ var j: u8 = @as(u8, '\x00'); + \\ var k: u8 = @as(u8, '\"'); + \\ return "\'\\\x07\x08\x0c\n\r\t\x0b\x00\""; + \\} + \\ + }); + + cases.addC("do loop", + \\void foo(void) { + \\ int a = 2; + \\ do { + \\ a--; + \\ } while (a != 0); + \\ + \\ int b = 2; + \\ do + \\ b--; + \\ while (b != 0); + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ var a: c_int = 2; + \\ while (true) { + \\ a -= 1; + \\ if (!(a != 0)) break; + \\ } + \\ var b: c_int = 2; + \\ while (true) { + \\ b -= 1; + \\ if (!(b != 0)) break; + \\ } + \\} + }); + + cases.addC("==, !=", + \\int max(int a, int b) { + \\ if (a == b) + \\ return a; + \\ if (a != b) + \\ return b; + \\ return a; + \\} + , &[_][]const u8{ + \\pub export fn max(a: c_int, b: c_int) c_int { + \\ if (a == b) return a; + \\ if (a != b) return b; + \\ return a; + \\} + }); + + cases.addC("bitwise binary operators", + \\int max(int a, int b) { + \\ return (a & b) ^ (a | b); + \\} + , &[_][]const u8{ + \\pub export fn max(a: c_int, b: c_int) c_int { + \\ return (a & b) ^ (a | b); + \\} + }); + cases.addC("statement expression", \\int foo(void) { \\ return ({ @@ -1269,23 +2599,273 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - cases.addC("__extension__ cast", - \\int foo(void) { - \\ return __extension__ 1; + cases.addC("field access expression", + \\struct Foo { + \\ int field; + \\}; + \\int read_field(struct Foo *foo) { + \\ return foo->field; \\} , &[_][]const u8{ - \\pub export fn foo() c_int { - \\ return 1; + \\pub const struct_Foo = extern struct { + \\ field: c_int, + \\}; + \\pub export fn read_field(foo: [*c]struct_Foo) c_int { + \\ return foo.*.field; \\} }); - cases.addC("bitshift", - \\int foo(void) { - \\ return (1 << 2) >> 1; + cases.addC("array access", + \\int array[100]; + \\int foo(int index) { + \\ return array[index]; \\} , &[_][]const u8{ - \\pub export fn foo() c_int { - \\ return (1 << @as(@import("std").math.Log2Int(c_int), 2)) >> @as(@import("std").math.Log2Int(c_int), 1); + \\pub var array: [100]c_int = undefined; + \\pub export fn foo(index: c_int) c_int { + \\ return array[index]; + \\} + }); + + cases.addC("logical and, logical or", + \\int max(int a, int b) { + \\ if (a < b || a == b) + \\ return b; + \\ if (a >= b && a == b) + \\ return a; + \\ return a; + \\} + , &[_][]const u8{ + \\pub export fn max(a: c_int, b: c_int) c_int { + \\ if ((a < b) or (a == b)) return b; + \\ if ((a >= b) and (a == b)) return a; + \\ return a; + \\} + }); + + cases.addC("if statement", + \\int max(int a, int b) { + \\ if (a < b) + \\ return b; + \\ + \\ if (a < b) + \\ return b; + \\ else + \\ return a; + \\ + \\ if (a < b) ; else ; + \\} + , &[_][]const u8{ + \\pub export fn max(a: c_int, b: c_int) c_int { + \\ if (a < b) return b; + \\ if (a < b) return b else return a; + \\ if (a < b) {} else {} + \\} + }); + + cases.add("variable name shadowing", + \\int foo(void) { + \\ int x = 1; + \\ { + \\ int x = 2; + \\ x += 1; + \\ } + \\ return x; + \\} + , &[_][]const u8{ + \\pub fn foo() c_int { + \\ var x: c_int = 1; + \\ { + \\ var x_0: c_int = 2; + \\ x_0 += 1; + \\ } + \\ return x; + \\} + }); + + cases.add("if on non-bool", + \\enum SomeEnum { A, B, C }; + \\int if_none_bool(int a, float b, void *c, enum SomeEnum d) { + \\ if (a) return 0; + \\ if (b) return 1; + \\ if (c) return 2; + \\ if (d) return 3; + \\ return 4; + \\} + , &[_][]const u8{ + \\pub const A = enum_SomeEnum.A; + \\pub const B = enum_SomeEnum.B; + \\pub const C = enum_SomeEnum.C; + \\pub const enum_SomeEnum = extern enum { + \\ A, + \\ B, + \\ C, + \\}; + \\pub fn if_none_bool(a: c_int, b: f32, c: ?*c_void, d: enum_SomeEnum) c_int { + \\ if (a != 0) return 0; + \\ if (b != 0) return 1; + \\ if (c != null) return 2; + \\ if (d != @bitCast(enum_SomeEnum, @as(@TagType(enum_SomeEnum), 0))) return 3; + \\ return 4; + \\} + }); + + cases.addAllowWarnings("simple data types", + \\#include + \\int foo(char a, unsigned char b, signed char c); + \\int foo(char a, unsigned char b, signed char c); // test a duplicate prototype + \\void bar(uint8_t a, uint16_t b, uint32_t c, uint64_t d); + \\void baz(int8_t a, int16_t b, int32_t c, int64_t d); + , &[_][]const u8{ + \\pub extern fn foo(a: u8, b: u8, c: i8) c_int; + , + \\pub extern fn bar(a: u8, b: u16, c: u32, d: u64) void; + , + \\pub extern fn baz(a: i8, b: i16, c: i32, d: i64) void; + }); + + cases.addC("simple function", + \\int abs(int a) { + \\ return a < 0 ? -a : a; + \\} + , &[_][]const u8{ + \\pub export fn abs(a: c_int) c_int { + \\ return if (a < 0) -a else a; + \\} + }); + + cases.addC("post increment", + \\unsigned foo1(unsigned a) { + \\ a++; + \\ return a; + \\} + \\int foo2(int a) { + \\ a++; + \\ return a; + \\} + , &[_][]const u8{ + \\pub export fn foo1(_arg_a: c_uint) c_uint { + \\ var a = _arg_a; + \\ a +%= 1; + \\ return a; + \\} + \\pub export fn foo2(_arg_a: c_int) c_int { + \\ var a = _arg_a; + \\ a += 1; + \\ return a; + \\} + }); + + cases.addC("deref function pointer", + \\void foo(void) {} + \\int baz(void) { return 0; } + \\void bar(void) { + \\ void(*f)(void) = foo; + \\ int(*b)(void) = baz; + \\ f(); + \\ (*(f))(); + \\ foo(); + \\ b(); + \\ (*(b))(); + \\ baz(); + \\} + , &[_][]const u8{ + \\pub export fn foo() void {} + \\pub export fn baz() c_int { + \\ return 0; + \\} + \\pub export fn bar() void { + \\ var f: ?extern fn () void = foo; + \\ var b: ?extern fn () c_int = baz; + \\ f.?(); + \\ f.?(); + \\ foo(); + \\ _ = b.?(); + \\ _ = b.?(); + \\ _ = baz(); + \\} + }); + + cases.addC("pre increment/decrement", + \\void foo(void) { + \\ int i = 0; + \\ unsigned u = 0; + \\ ++i; + \\ --i; + \\ ++u; + \\ --u; + \\ i = ++i; + \\ i = --i; + \\ u = ++u; + \\ u = --u; + \\} + , &[_][]const u8{ + \\pub export fn foo() void { + \\ var i: c_int = 0; + \\ var u: c_uint = @as(c_uint, 0); + \\ i += 1; + \\ i -= 1; + \\ u +%= 1; + \\ u -%= 1; + \\ i = (x: { + \\ const _ref = &i; + \\ _ref.* += 1; + \\ break :x _ref.*; + \\ }); + \\ i = (x: { + \\ const _ref = &i; + \\ _ref.* -= 1; + \\ break :x _ref.*; + \\ }); + \\ u = (x: { + \\ const _ref = &u; + \\ _ref.* +%= 1; + \\ break :x _ref.*; + \\ }); + \\ u = (x: { + \\ const _ref = &u; + \\ _ref.* -%= 1; + \\ break :x _ref.*; + \\ }); + \\} + }); + + cases.addC("shift right assign", + \\int log2(unsigned a) { + \\ int i = 0; + \\ while (a > 0) { + \\ a >>= 1; + \\ } + \\ return i; + \\} + , &[_][]const u8{ + \\pub export fn log2(_arg_a: c_uint) c_int { + \\ var a = _arg_a; + \\ var i: c_int = 0; + \\ while (a > @as(c_uint, 0)) { + \\ a >>= @as(@import("std").math.Log2Int(c_uint), 1); + \\ } + \\ return i; + \\} + }); + + cases.addC("shift right assign with a fixed size type", + \\#include + \\int log2(uint32_t a) { + \\ int i = 0; + \\ while (a > 0) { + \\ a >>= 1; + \\ } + \\ return i; + \\} + , &[_][]const u8{ + \\pub export fn log2(_arg_a: u32) c_int { + \\ var a = _arg_a; + \\ var i: c_int = 0; + \\ while (a > @as(c_uint, 0)) { + \\ a >>= @as(u5, 1); + \\ } + \\ return i; \\} }); @@ -1453,383 +3033,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - cases.addC("pre increment/decrement", - \\void foo(void) { - \\ int i = 0; - \\ unsigned u = 0; - \\ ++i; - \\ --i; - \\ ++u; - \\ --u; - \\ i = ++i; - \\ i = --i; - \\ u = ++u; - \\ u = --u; - \\} - , &[_][]const u8{ - \\pub export fn foo() void { - \\ var i: c_int = 0; - \\ var u: c_uint = @as(c_uint, 0); - \\ i += 1; - \\ i -= 1; - \\ u +%= 1; - \\ u -%= 1; - \\ i = (x: { - \\ const _ref = &i; - \\ _ref.* += 1; - \\ break :x _ref.*; - \\ }); - \\ i = (x: { - \\ const _ref = &i; - \\ _ref.* -= 1; - \\ break :x _ref.*; - \\ }); - \\ u = (x: { - \\ const _ref = &u; - \\ _ref.* +%= 1; - \\ break :x _ref.*; - \\ }); - \\ u = (x: { - \\ const _ref = &u; - \\ _ref.* -%= 1; - \\ break :x _ref.*; - \\ }); - \\} - }); - - cases.addC("do loop", - \\void foo(void) { - \\ int a = 2; - \\ do { - \\ a--; - \\ } while (a != 0); - \\ - \\ int b = 2; - \\ do - \\ b--; - \\ while (b != 0); - \\} - , &[_][]const u8{ - \\pub export fn foo() void { - \\ var a: c_int = 2; - \\ while (true) { - \\ a -= 1; - \\ if (!(a != 0)) break; - \\ } - \\ var b: c_int = 2; - \\ while (true) { - \\ b -= 1; - \\ if (!(b != 0)) break; - \\ } - \\} - }); - - cases.addC("deref function pointer", - \\void foo(void) {} - \\int baz(void) { return 0; } - \\void bar(void) { - \\ void(*f)(void) = foo; - \\ int(*b)(void) = baz; - \\ f(); - \\ (*(f))(); - \\ foo(); - \\ b(); - \\ (*(b))(); - \\ baz(); - \\} - , &[_][]const u8{ - \\pub export fn foo() void {} - \\pub export fn baz() c_int { - \\ return 0; - \\} - \\pub export fn bar() void { - \\ var f: ?extern fn () void = foo; - \\ var b: ?extern fn () c_int = baz; - \\ f.?(); - \\ f.?(); - \\ foo(); - \\ _ = b.?(); - \\ _ = b.?(); - \\ _ = baz(); - \\} - }); - - cases.addC("normal deref", - \\void foo(int *x) { - \\ *x = 1; - \\} - , &[_][]const u8{ - \\pub export fn foo(x: [*c]c_int) void { - \\ x.?.* = 1; - \\} - }); - - cases.add("simple union", - \\union Foo { - \\ int x; - \\ double y; - \\}; - , &[_][]const u8{ - \\pub const union_Foo = extern union { - \\ x: c_int, - \\ y: f64, - \\}; - , - \\pub const Foo = union_Foo; - }); - - cases.add("address of operator", - \\int foo(void) { - \\ int x = 1234; - \\ int *ptr = &x; - \\ return *ptr; - \\} - , &[_][]const u8{ - \\pub fn foo() c_int { - \\ var x: c_int = 1234; - \\ var ptr: [*c]c_int = &x; - \\ return ptr.?.*; - \\} - }); - - cases.add("string literal", - \\const char *foo(void) { - \\ return "bar"; - \\} - , &[_][]const u8{ - \\pub fn foo() [*c]const u8 { - \\ return "bar"; - \\} - }); - - cases.add("return void", - \\void foo(void) { - \\ return; - \\} - , &[_][]const u8{ - \\pub fn foo() void { - \\ return; - \\} - }); - - cases.add("for loop", - \\void foo(void) { - \\ for (int i = 0; i < 10; i += 1) { } - \\} - , &[_][]const u8{ - \\pub fn foo() void { - \\ { - \\ var i: c_int = 0; - \\ while (i < 10) : (i += 1) {} - \\ } - \\} - }); - - cases.add("empty for loop", - \\void foo(void) { - \\ for (;;) { } - \\} - , &[_][]const u8{ - \\pub fn foo() void { - \\ while (true) {} - \\} - }); - - cases.add("break statement", - \\void foo(void) { - \\ for (;;) { - \\ break; - \\ } - \\} - , &[_][]const u8{ - \\pub fn foo() void { - \\ while (true) { - \\ break; - \\ } - \\} - }); - - cases.add("continue statement", - \\void foo(void) { - \\ for (;;) { - \\ continue; - \\ } - \\} - , &[_][]const u8{ - \\pub fn foo() void { - \\ while (true) { - \\ continue; - \\ } - \\} - }); - - cases.add("variable name shadowing", - \\int foo(void) { - \\ int x = 1; - \\ { - \\ int x = 2; - \\ x += 1; - \\ } - \\ return x; - \\} - , &[_][]const u8{ - \\pub fn foo() c_int { - \\ var x: c_int = 1; - \\ { - \\ var x_0: c_int = 2; - \\ x_0 += 1; - \\ } - \\ return x; - \\} - }); - - cases.add("pointer casting", - \\float *ptrcast(int *a) { - \\ return (float *)a; - \\} - , &[_][]const u8{ - \\fn ptrcast(a: [*c]c_int) [*c]f32 { - \\ return @ptrCast([*c]f32, @alignCast(@alignOf(f32), a)); - \\} - }); - - cases.add("bin not", - \\int foo(int x) { - \\ return ~x; - \\} - , &[_][]const u8{ - \\pub fn foo(x: c_int) c_int { - \\ return ~x; - \\} - }); - - cases.add("bool not", - \\int foo(int a, float b, void *c) { - \\ return !(a == 0); - \\ return !a; - \\ return !b; - \\ return !c; - \\} - , &[_][]const u8{ - \\pub fn foo(a: c_int, b: f32, c: ?*c_void) c_int { - \\ return !(a == 0); - \\ return !(a != 0); - \\ return !(b != 0); - \\ return !(c != null); - \\} - }); - - cases.add("primitive types included in defined symbols", - \\int foo(int u32) { - \\ return u32; - \\} - , &[_][]const u8{ - \\pub fn foo(u32_0: c_int) c_int { - \\ return u32_0; - \\} - }); - - cases.add("if on non-bool", - \\enum SomeEnum { A, B, C }; - \\int if_none_bool(int a, float b, void *c, enum SomeEnum d) { - \\ if (a) return 0; - \\ if (b) return 1; - \\ if (c) return 2; - \\ if (d) return 3; - \\ return 4; - \\} - , &[_][]const u8{ - \\pub const A = enum_SomeEnum.A; - \\pub const B = enum_SomeEnum.B; - \\pub const C = enum_SomeEnum.C; - \\pub const enum_SomeEnum = extern enum { - \\ A, - \\ B, - \\ C, - \\}; - \\pub fn if_none_bool(a: c_int, b: f32, c: ?*c_void, d: enum_SomeEnum) c_int { - \\ if (a != 0) return 0; - \\ if (b != 0) return 1; - \\ if (c != null) return 2; - \\ if (d != @bitCast(enum_SomeEnum, @as(@TagType(enum_SomeEnum), 0))) return 3; - \\ return 4; - \\} - }); - - cases.add("while on non-bool", - \\int while_none_bool(int a, float b, void *c) { - \\ while (a) return 0; - \\ while (b) return 1; - \\ while (c) return 2; - \\ return 3; - \\} - , &[_][]const u8{ - \\pub fn while_none_bool(a: c_int, b: f32, c: ?*c_void) c_int { - \\ while (a != 0) return 0; - \\ while (b != 0) return 1; - \\ while (c != null) return 2; - \\ return 3; - \\} - }); - - cases.add("for on non-bool", - \\int for_none_bool(int a, float b, void *c) { - \\ for (;a;) return 0; - \\ for (;b;) return 1; - \\ for (;c;) return 2; - \\ return 3; - \\} - , &[_][]const u8{ - \\pub fn for_none_bool(a: c_int, b: f32, c: ?*c_void) c_int { - \\ while (a != 0) return 0; - \\ while (b != 0) return 1; - \\ while (c != null) return 2; - \\ return 3; - \\} - }); - - cases.add("switch on int", - \\int switch_fn(int i) { - \\ int res = 0; - \\ switch (i) { - \\ case 0: - \\ res = 1; - \\ case 1: - \\ res = 2; - \\ default: - \\ res = 3 * i; - \\ break; - \\ case 2: - \\ res = 5; - \\ } - \\} - , &[_][]const u8{ - \\pub fn switch_fn(i: c_int) c_int { - \\ var res: c_int = 0; - \\ __switch: { - \\ __case_2: { - \\ __default: { - \\ __case_1: { - \\ __case_0: { - \\ switch (i) { - \\ 0 => break :__case_0, - \\ 1 => break :__case_1, - \\ else => break :__default, - \\ 2 => break :__case_2, - \\ } - \\ } - \\ res = 1; - \\ } - \\ res = 2; - \\ } - \\ res = (3 * i); - \\ break :__switch; - \\ } - \\ res = 5; - \\ } - \\} - }); - cases.addC("implicit casts", \\#include \\ @@ -1885,206 +3088,85 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - cases.addC("pointer conversion with different alignment", - \\void test_ptr_cast() { - \\ void *p; - \\ { - \\ char *to_char = (char *)p; - \\ short *to_short = (short *)p; - \\ int *to_int = (int *)p; - \\ long long *to_longlong = (long long *)p; - \\ } - \\ { - \\ char *to_char = p; - \\ short *to_short = p; - \\ int *to_int = p; - \\ long long *to_longlong = p; - \\ } + cases.addC("function call", + \\static void bar(void) { } + \\void foo(int *(baz)(void)) { + \\ bar(); + \\ baz(); \\} , &[_][]const u8{ - \\pub export fn test_ptr_cast() void { - \\ var p: ?*c_void = undefined; - \\ { - \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@alignOf(u8), p)); - \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@alignOf(c_short), p)); - \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@alignOf(c_int), p)); - \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@alignOf(c_longlong), p)); - \\ } - \\ { - \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@alignOf(u8), p)); - \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@alignOf(c_short), p)); - \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@alignOf(c_int), p)); - \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@alignOf(c_longlong), p)); - \\ } + \\pub fn bar() void {} + \\pub export fn foo(baz: ?extern fn () [*c]c_int) void { + \\ bar(); + \\ _ = baz.?(); \\} }); - cases.addC("escape sequences", - \\const char *escapes() { - \\char a = '\'', - \\ b = '\\', - \\ c = '\a', - \\ d = '\b', - \\ e = '\f', - \\ f = '\n', - \\ g = '\r', - \\ h = '\t', - \\ i = '\v', - \\ j = '\0', - \\ k = '\"'; - \\ return "\'\\\a\b\f\n\r\t\v\0\""; - \\} - \\ + cases.add("macro defines string literal with hex", + \\#define FOO "aoeu\xab derp" + \\#define FOO2 "aoeu\x0007a derp" + \\#define FOO_CHAR '\xfF' , &[_][]const u8{ - \\pub export fn escapes() [*c]const u8 { - \\ var a: u8 = @as(u8, '\''); - \\ var b: u8 = @as(u8, '\\'); - \\ var c: u8 = @as(u8, '\x07'); - \\ var d: u8 = @as(u8, '\x08'); - \\ var e: u8 = @as(u8, '\x0c'); - \\ var f: u8 = @as(u8, '\n'); - \\ var g: u8 = @as(u8, '\r'); - \\ var h: u8 = @as(u8, '\t'); - \\ var i: u8 = @as(u8, '\x0b'); - \\ var j: u8 = @as(u8, '\x00'); - \\ var k: u8 = @as(u8, '\"'); - \\ return "\'\\\x07\x08\x0c\n\r\t\x0b\x00\""; - \\} - \\ - }); - - if (builtin.os != builtin.Os.windows) { - // sysv_abi not currently supported on windows - cases.add("Macro qualified functions", - \\void __attribute__((sysv_abi)) foo(void); - , &[_][]const u8{ - \\pub extern fn foo() void; - }); - } - - /////////////// Cases for only stage1 because stage2 behavior is better //////////////// - cases.addC("Parameterless function prototypes", - \\void foo() {} - \\void bar(void) {} - , &[_][]const u8{ - \\pub export fn foo() void {} - \\pub export fn bar() void {} - }); - - cases.add("#define a char literal", - \\#define A_CHAR 'a' - , &[_][]const u8{ - \\pub const A_CHAR = 97; - }); - - cases.add("generate inline func for #define global extern fn", - \\extern void (*fn_ptr)(void); - \\#define foo fn_ptr - \\ - \\extern char (*fn_ptr2)(int, float); - \\#define bar fn_ptr2 - , &[_][]const u8{ - \\pub extern var fn_ptr: ?extern fn () void; + \\pub const FOO = "aoeu\xab derp"; , - \\pub inline fn foo() void { - \\ return fn_ptr.?(); - \\} + \\pub const FOO2 = "aoeuz derp"; , - \\pub extern var fn_ptr2: ?extern fn (c_int, f32) u8; + \\pub const FOO_CHAR = 255; + }); + + cases.add("macro defines string literal with octal", + \\#define FOO "aoeu\023 derp" + \\#define FOO2 "aoeu\0234 derp" + \\#define FOO_CHAR '\077' + , &[_][]const u8{ + \\pub const FOO = "aoeu\x13 derp"; , - \\pub inline fn bar(arg0: c_int, arg1: f32) u8 { - \\ return fn_ptr2.?(arg0, arg1); - \\} - }); - cases.add("comment after integer literal", - \\#define SDL_INIT_VIDEO 0x00000020 /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ - , &[_][]const u8{ - \\pub const SDL_INIT_VIDEO = 32; + \\pub const FOO2 = "aoeu\x134 derp"; + , + \\pub const FOO_CHAR = 63; }); - cases.add("u integer suffix after hex literal", - \\#define SDL_INIT_VIDEO 0x00000020u /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ - , &[_][]const u8{ - \\pub const SDL_INIT_VIDEO = @as(c_uint, 32); - }); - - cases.add("l integer suffix after hex literal", - \\#define SDL_INIT_VIDEO 0x00000020l /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ - , &[_][]const u8{ - \\pub const SDL_INIT_VIDEO = @as(c_long, 32); - }); - - cases.add("ul integer suffix after hex literal", - \\#define SDL_INIT_VIDEO 0x00000020ul /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ - , &[_][]const u8{ - \\pub const SDL_INIT_VIDEO = @as(c_ulong, 32); - }); - - cases.add("lu integer suffix after hex literal", - \\#define SDL_INIT_VIDEO 0x00000020lu /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ - , &[_][]const u8{ - \\pub const SDL_INIT_VIDEO = @as(c_ulong, 32); - }); - - cases.add("ll integer suffix after hex literal", - \\#define SDL_INIT_VIDEO 0x00000020ll /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ - , &[_][]const u8{ - \\pub const SDL_INIT_VIDEO = @as(c_longlong, 32); - }); - - cases.add("ull integer suffix after hex literal", - \\#define SDL_INIT_VIDEO 0x00000020ull /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ - , &[_][]const u8{ - \\pub const SDL_INIT_VIDEO = @as(c_ulonglong, 32); - }); - - cases.add("llu integer suffix after hex literal", - \\#define SDL_INIT_VIDEO 0x00000020llu /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ - , &[_][]const u8{ - \\pub const SDL_INIT_VIDEO = @as(c_ulonglong, 32); - }); - - cases.add("macros with field targets", - \\typedef unsigned int GLbitfield; - \\typedef void (*PFNGLCLEARPROC) (GLbitfield mask); - \\typedef void(*OpenGLProc)(void); - \\union OpenGLProcs { - \\ OpenGLProc ptr[1]; - \\ struct { - \\ PFNGLCLEARPROC Clear; - \\ } gl; + cases.add("enums", + \\enum Foo { + \\ FooA, + \\ FooB, + \\ Foo1, \\}; - \\extern union OpenGLProcs glProcs; - \\#define glClearUnion glProcs.gl.Clear - \\#define glClearPFN PFNGLCLEARPROC , &[_][]const u8{ - \\pub const GLbitfield = c_uint; - , - \\pub const PFNGLCLEARPROC = ?extern fn (GLbitfield) void; - , - \\pub const OpenGLProc = ?extern fn () void; - , - \\pub const union_OpenGLProcs = extern union { - \\ ptr: [1]OpenGLProc, - \\ gl: extern struct { - \\ Clear: PFNGLCLEARPROC, - \\ }, + \\pub const enum_Foo = extern enum { + \\ A, + \\ B, + \\ @"1", \\}; , - \\pub extern var glProcs: union_OpenGLProcs; + \\pub const FooA = enum_Foo.A; , - \\pub const glClearPFN = PFNGLCLEARPROC; + \\pub const FooB = enum_Foo.B; , - \\pub inline fn glClearUnion(arg0: GLbitfield) void { - \\ return glProcs.gl.Clear.?(arg0); - \\} + \\pub const Foo1 = enum_Foo.@"1"; , - \\pub const OpenGLProcs = union_OpenGLProcs; + \\pub const Foo = enum_Foo; }); - cases.add("macro pointer cast", - \\#define NRF_GPIO ((NRF_GPIO_Type *) NRF_GPIO_BASE) + cases.add("enums", + \\enum Foo { + \\ FooA = 2, + \\ FooB = 5, + \\ Foo1, + \\}; , &[_][]const u8{ - \\pub const NRF_GPIO = if (@typeId(@TypeOf(NRF_GPIO_BASE)) == @import("builtin").TypeId.Pointer) @ptrCast([*c]NRF_GPIO_Type, NRF_GPIO_BASE) else if (@typeId(@TypeOf(NRF_GPIO_BASE)) == @import("builtin").TypeId.Int) @intToPtr([*c]NRF_GPIO_Type, NRF_GPIO_BASE) else @as([*c]NRF_GPIO_Type, NRF_GPIO_BASE); + \\pub const enum_Foo = extern enum { + \\ A = 2, + \\ B = 5, + \\ @"1" = 6, + \\}; + , + \\pub const FooA = enum_Foo.A; + , + \\pub const FooB = enum_Foo.B; + , + \\pub const Foo1 = enum_Foo.@"1"; + , + \\pub const Foo = enum_Foo; }); }