// This is the userland implementation of translate-c which is used by both stage1 // and stage2. const std = @import("std"); const assert = std.debug.assert; const ast = std.zig.ast; const Token = std.zig.Token; usingnamespace @import("clang.zig"); const ctok = std.c.tokenizer; const CToken = std.c.Token; const mem = std.mem; const math = std.math; const CallingConvention = std.builtin.CallingConvention; pub const ClangErrMsg = Stage2ErrorMsg; pub const Error = error{OutOfMemory}; const TypeError = Error || error{UnsupportedType}; const TransError = TypeError || error{UnsupportedTranslation}; const DeclTable = std.AutoArrayHashMap(usize, []const u8); const SymbolTable = std.StringArrayHashMap(*ast.Node); const AliasList = std.ArrayList(struct { alias: []const u8, name: []const u8, }); const Scope = struct { id: Id, parent: ?*Scope, const Id = enum { Switch, Block, Root, Condition, Loop, }; /// Represents an in-progress ast.Node.Switch. This struct is stack-allocated. /// When it is deinitialized, it produces an ast.Node.Switch which is allocated /// into the main arena. const Switch = struct { base: Scope, pending_block: Block, cases: []*ast.Node, case_index: usize, switch_label: ?[]const u8, default_label: ?[]const u8, }; /// Used for the scope of condition expressions, for example `if (cond)`. /// The block is lazily initialised because it is only needed for rare /// cases of comma operators being used. const Condition = struct { base: Scope, block: ?Block = null, fn getBlockScope(self: *Condition, c: *Context) !*Block { if (self.block) |*b| return b; self.block = try Block.init(c, &self.base, true); return &self.block.?; } fn deinit(self: *Condition) void { if (self.block) |*b| b.deinit(); } }; /// Represents an in-progress ast.Node.Block. This struct is stack-allocated. /// When it is deinitialized, it produces an ast.Node.Block which is allocated /// into the main arena. const Block = struct { base: Scope, statements: std.ArrayList(*ast.Node), variables: AliasList, label: ?ast.TokenIndex, mangle_count: u32 = 0, lbrace: ast.TokenIndex, fn init(c: *Context, parent: *Scope, labeled: bool) !Block { var blk = Block{ .base = .{ .id = .Block, .parent = parent, }, .statements = std.ArrayList(*ast.Node).init(c.gpa), .variables = AliasList.init(c.gpa), .label = null, .lbrace = try appendToken(c, .LBrace, "{"), }; if (labeled) { blk.label = try appendIdentifier(c, try blk.makeMangledName(c, "blk")); _ = try appendToken(c, .Colon, ":"); } return blk; } fn deinit(self: *Block) void { self.statements.deinit(); self.variables.deinit(); self.* = undefined; } fn complete(self: *Block, c: *Context) !*ast.Node { // We reserve 1 extra statement if the parent is a Loop. This is in case of // do while, we want to put `if (cond) break;` at the end. const alloc_len = self.statements.items.len + @boolToInt(self.base.parent.?.id == .Loop); const rbrace = try appendToken(c, .RBrace, "}"); if (self.label) |label| { const node = try ast.Node.LabeledBlock.alloc(c.arena, alloc_len); node.* = .{ .statements_len = self.statements.items.len, .lbrace = self.lbrace, .rbrace = rbrace, .label = label, }; mem.copy(*ast.Node, node.statements(), self.statements.items); return &node.base; } else { const node = try ast.Node.Block.alloc(c.arena, alloc_len); node.* = .{ .statements_len = self.statements.items.len, .lbrace = self.lbrace, .rbrace = rbrace, }; mem.copy(*ast.Node, node.statements(), self.statements.items); return &node.base; } } /// Given the desired name, return a name that does not shadow anything from outer scopes. /// Inserts the returned name into the scope. fn makeMangledName(scope: *Block, c: *Context, name: []const u8) ![]const u8 { const name_copy = try c.arena.dupe(u8, name); var proposed_name = name_copy; while (scope.contains(proposed_name)) { scope.mangle_count += 1; proposed_name = try std.fmt.allocPrint(c.arena, "{}_{}", .{ name, scope.mangle_count }); } try scope.variables.append(.{ .name = name_copy, .alias = proposed_name }); return proposed_name; } fn getAlias(scope: *Block, name: []const u8) []const u8 { for (scope.variables.items) |p| { if (mem.eql(u8, p.name, name)) return p.alias; } return scope.base.parent.?.getAlias(name); } fn localContains(scope: *Block, name: []const u8) bool { for (scope.variables.items) |p| { if (mem.eql(u8, p.alias, name)) return true; } return false; } fn contains(scope: *Block, name: []const u8) bool { if (scope.localContains(name)) return true; return scope.base.parent.?.contains(name); } }; const Root = struct { base: Scope, sym_table: SymbolTable, macro_table: SymbolTable, context: *Context, fn init(c: *Context) Root { return .{ .base = .{ .id = .Root, .parent = null, }, .sym_table = SymbolTable.init(c.arena), .macro_table = SymbolTable.init(c.arena), .context = c, }; } /// Check if the global scope contains this name, without looking into the "future", e.g. /// ignore the preprocessed decl and macro names. fn containsNow(scope: *Root, name: []const u8) bool { return isZigPrimitiveType(name) or scope.sym_table.contains(name) or scope.macro_table.contains(name); } /// Check if the global scope contains the name, includes all decls that haven't been translated yet. fn contains(scope: *Root, name: []const u8) bool { return scope.containsNow(name) or scope.context.global_names.contains(name); } }; fn findBlockScope(inner: *Scope, c: *Context) !*Scope.Block { var scope = inner; while (true) { switch (scope.id) { .Root => unreachable, .Block => return @fieldParentPtr(Block, "base", scope), .Condition => return @fieldParentPtr(Condition, "base", scope).getBlockScope(c), else => scope = scope.parent.?, } } } fn getAlias(scope: *Scope, name: []const u8) []const u8 { return switch (scope.id) { .Root => return name, .Block => @fieldParentPtr(Block, "base", scope).getAlias(name), .Switch, .Loop, .Condition => scope.parent.?.getAlias(name), }; } fn contains(scope: *Scope, name: []const u8) bool { return switch (scope.id) { .Root => @fieldParentPtr(Root, "base", scope).contains(name), .Block => @fieldParentPtr(Block, "base", scope).contains(name), .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.?, } } } }; pub const Context = struct { gpa: *mem.Allocator, arena: *mem.Allocator, token_ids: std.ArrayListUnmanaged(Token.Id), token_locs: std.ArrayListUnmanaged(Token.Loc), errors: std.ArrayListUnmanaged(ast.Error), source_buffer: *std.ArrayList(u8), err: Error, source_manager: *ZigClangSourceManager, decl_table: DeclTable, alias_list: AliasList, global_scope: *Scope.Root, clang_context: *ZigClangASTContext, mangle_count: u32 = 0, root_decls: std.ArrayListUnmanaged(*ast.Node), /// This one is different than the root scope's name table. This contains /// a list of names that we found by visiting all the top level decls without /// translating them. The other maps are updated as we translate; this one is updated /// up front in a pre-processing step. global_names: std.StringArrayHashMap(void), fn getMangle(c: *Context) u32 { c.mangle_count += 1; return c.mangle_count; } /// Convert a null-terminated C string to a slice allocated in the arena fn str(c: *Context, s: [*:0]const u8) ![]u8 { return mem.dupe(c.arena, u8, mem.spanZ(s)); } /// Convert a clang source location to a file:line:column string fn locStr(c: *Context, loc: ZigClangSourceLocation) ![]u8 { const spelling_loc = ZigClangSourceManager_getSpellingLoc(c.source_manager, loc); const filename_c = ZigClangSourceManager_getFilename(c.source_manager, spelling_loc); const filename = if (filename_c) |s| try c.str(s) else @as([]const u8, "(no file)"); const line = ZigClangSourceManager_getSpellingLineNumber(c.source_manager, spelling_loc); const column = ZigClangSourceManager_getSpellingColumnNumber(c.source_manager, spelling_loc); return std.fmt.allocPrint(c.arena, "{}:{}:{}", .{ filename, line, column }); } fn createCall(c: *Context, fn_expr: *ast.Node, params_len: ast.NodeIndex) !*ast.Node.Call { _ = try appendToken(c, .LParen, "("); const node = try ast.Node.Call.alloc(c.arena, params_len); node.* = .{ .lhs = fn_expr, .params_len = params_len, .async_token = null, .rtoken = undefined, // set after appending args }; return node; } fn createBuiltinCall(c: *Context, name: []const u8, params_len: ast.NodeIndex) !*ast.Node.BuiltinCall { const builtin_token = try appendToken(c, .Builtin, name); _ = try appendToken(c, .LParen, "("); const node = try ast.Node.BuiltinCall.alloc(c.arena, params_len); node.* = .{ .builtin_token = builtin_token, .params_len = params_len, .rparen_token = undefined, // set after appending args }; return node; } fn createBlock(c: *Context, statements_len: ast.NodeIndex) !*ast.Node.Block { const block_node = try ast.Node.Block.alloc(c.arena, statements_len); block_node.* = .{ .lbrace = try appendToken(c, .LBrace, "{"), .statements_len = statements_len, .rbrace = undefined, }; return block_node; } }; pub fn translate( gpa: *mem.Allocator, args_begin: [*]?[*]const u8, args_end: [*]?[*]const u8, errors: *[]ClangErrMsg, resources_path: [*:0]const u8, ) !*ast.Tree { const ast_unit = ZigClangLoadFromCommandLine( args_begin, args_end, &errors.ptr, &errors.len, resources_path, ) orelse { if (errors.len == 0) return error.ASTUnitFailure; return error.SemanticAnalyzeFail; }; defer ZigClangASTUnit_delete(ast_unit); var source_buffer = std.ArrayList(u8).init(gpa); defer source_buffer.deinit(); // For memory that has the same lifetime as the Tree that we return // from this function. var arena = std.heap.ArenaAllocator.init(gpa); errdefer arena.deinit(); var context = Context{ .gpa = gpa, .arena = &arena.allocator, .source_buffer = &source_buffer, .source_manager = ZigClangASTUnit_getSourceManager(ast_unit), .err = undefined, .decl_table = DeclTable.init(gpa), .alias_list = AliasList.init(gpa), .global_scope = try arena.allocator.create(Scope.Root), .clang_context = ZigClangASTUnit_getASTContext(ast_unit).?, .global_names = std.StringArrayHashMap(void).init(gpa), .token_ids = .{}, .token_locs = .{}, .errors = .{}, .root_decls = .{}, }; context.global_scope.* = Scope.Root.init(&context); defer context.decl_table.deinit(); defer context.alias_list.deinit(); defer context.token_ids.deinit(gpa); defer context.token_locs.deinit(gpa); defer context.errors.deinit(gpa); defer context.global_names.deinit(); defer context.root_decls.deinit(gpa); try prepopulateGlobalNameTable(ast_unit, &context); if (!ZigClangASTUnit_visitLocalTopLevelDecls(ast_unit, &context, declVisitorC)) { return context.err; } try transPreprocessorEntities(&context, ast_unit); try addMacros(&context); for (context.alias_list.items) |alias| { if (!context.global_scope.sym_table.contains(alias.alias)) { try createAlias(&context, alias); } } const eof_token = try appendToken(&context, .Eof, ""); const root_node = try ast.Node.Root.create(&arena.allocator, context.root_decls.items.len, eof_token); mem.copy(*ast.Node, root_node.decls(), context.root_decls.items); if (false) { std.debug.warn("debug source:\n{}\n==EOF==\ntokens:\n", .{source_buffer.items}); for (context.token_ids.items) |token| { std.debug.warn("{}\n", .{token}); } } const tree = try arena.allocator.create(ast.Tree); tree.* = .{ .gpa = gpa, .source = try arena.allocator.dupe(u8, source_buffer.items), .token_ids = context.token_ids.toOwnedSlice(gpa), .token_locs = context.token_locs.toOwnedSlice(gpa), .errors = context.errors.toOwnedSlice(gpa), .root_node = root_node, .arena = arena.state, .generated = true, }; return tree; } fn prepopulateGlobalNameTable(ast_unit: *ZigClangASTUnit, c: *Context) !void { if (!ZigClangASTUnit_visitLocalTopLevelDecls(ast_unit, c, declVisitorNamesOnlyC)) { return c.err; } // TODO if we see #undef, delete it from the table var it = ZigClangASTUnit_getLocalPreprocessingEntities_begin(ast_unit); const it_end = ZigClangASTUnit_getLocalPreprocessingEntities_end(ast_unit); while (it.I != it_end.I) : (it.I += 1) { const entity = ZigClangPreprocessingRecord_iterator_deref(it); switch (ZigClangPreprocessedEntity_getKind(entity)) { .MacroDefinitionKind => { const macro = @ptrCast(*ZigClangMacroDefinitionRecord, entity); const raw_name = ZigClangMacroDefinitionRecord_getName_getNameStart(macro); const name = try c.str(raw_name); _ = try c.global_names.put(name, {}); }, else => {}, } } } fn declVisitorNamesOnlyC(context: ?*c_void, decl: *const ZigClangDecl) callconv(.C) bool { const c = @ptrCast(*Context, @alignCast(@alignOf(Context), context)); declVisitorNamesOnly(c, decl) catch |err| { c.err = err; return false; }; return true; } fn declVisitorC(context: ?*c_void, decl: *const ZigClangDecl) callconv(.C) bool { const c = @ptrCast(*Context, @alignCast(@alignOf(Context), context)); declVisitor(c, decl) catch |err| { c.err = err; return false; }; return true; } fn declVisitorNamesOnly(c: *Context, decl: *const ZigClangDecl) Error!void { if (ZigClangDecl_castToNamedDecl(decl)) |named_decl| { const decl_name = try c.str(ZigClangNamedDecl_getName_bytes_begin(named_decl)); _ = try c.global_names.put(decl_name, {}); } } fn declVisitor(c: *Context, decl: *const ZigClangDecl) Error!void { switch (ZigClangDecl_getKind(decl)) { .Function => { return visitFnDecl(c, @ptrCast(*const ZigClangFunctionDecl, decl)); }, .Typedef => { _ = try transTypeDef(c, @ptrCast(*const ZigClangTypedefNameDecl, decl), true); }, .Enum => { _ = try transEnumDecl(c, @ptrCast(*const ZigClangEnumDecl, decl)); }, .Record => { _ = try transRecordDecl(c, @ptrCast(*const ZigClangRecordDecl, decl)); }, .Var => { return visitVarDecl(c, @ptrCast(*const ZigClangVarDecl, decl), null); }, .Empty => { // Do nothing }, else => { const decl_name = try c.str(ZigClangDecl_getDeclKindName(decl)); try emitWarning(c, ZigClangDecl_getLocation(decl), "ignoring {} declaration", .{decl_name}); }, } } fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { const fn_name = try c.str(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, fn_decl))); if (c.global_scope.sym_table.contains(fn_name)) return; // Avoid processing this decl twice // Skip this declaration if a proper definition exists if (!ZigClangFunctionDecl_isThisDeclarationADefinition(fn_decl)) { if (ZigClangFunctionDecl_getDefinition(fn_decl)) |def| return visitFnDecl(c, def); } const rp = makeRestorePoint(c); const fn_decl_loc = ZigClangFunctionDecl_getLocation(fn_decl); 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, .is_export = switch (storage_class) { .None => has_body and !ZigClangFunctionDecl_isInlineSpecified(fn_decl), .Extern, .Static => false, .PrivateExtern => return failDecl(c, fn_decl_loc, fn_name, "unsupported storage class: private extern", .{}), .Auto => unreachable, // Not legal on functions .Register => unreachable, // Not legal on functions }, }; var fn_qt = ZigClangFunctionDecl_getType(fn_decl); const fn_type = while (true) { const fn_type = ZigClangQualType_getTypePtr(fn_qt); switch (ZigClangType_getTypeClass(fn_type)) { .Attributed => { const attr_type = @ptrCast(*const ZigClangAttributedType, fn_type); fn_qt = ZigClangAttributedType_getEquivalentType(attr_type); }, .Paren => { const paren_type = @ptrCast(*const ZigClangParenType, fn_type); fn_qt = ZigClangParenType_getInnerType(paren_type); }, else => break fn_type, } } else unreachable; const proto_node = switch (ZigClangType_getTypeClass(fn_type)) { .FunctionProto => blk: { const fn_proto_type = @ptrCast(*const ZigClangFunctionProtoType, fn_type); break :blk transFnProto(rp, fn_decl, fn_proto_type, fn_decl_loc, decl_ctx, true) catch |err| switch (err) { error.UnsupportedType => { return failDecl(c, fn_decl_loc, fn_name, "unable to resolve prototype of function", .{}); }, error.OutOfMemory => |e| return e, }; }, .FunctionNoProto => blk: { const fn_no_proto_type = @ptrCast(*const ZigClangFunctionType, fn_type); break :blk transFnNoProto(rp, fn_no_proto_type, fn_decl_loc, decl_ctx, true) catch |err| switch (err) { error.UnsupportedType => { return failDecl(c, fn_decl_loc, fn_name, "unable to resolve prototype of function", .{}); }, error.OutOfMemory => |e| return e, }; }, else => return failDecl(c, fn_decl_loc, fn_name, "unable to resolve function type {}", .{ZigClangType_getTypeClass(fn_type)}), }; if (!decl_ctx.has_body) { const semi_tok = try appendToken(c, .Semicolon, ";"); return addTopLevelDecl(c, fn_name, &proto_node.base); } // actual function definition with body const body_stmt = ZigClangFunctionDecl_getBody(fn_decl); var block_scope = try Scope.Block.init(rp.c, &c.global_scope.base, false); defer block_scope.deinit(); var scope = &block_scope.base; var param_id: c_uint = 0; for (proto_node.params()) |*param, i| { const param_name = if (param.name_token) |name_tok| tokenSlice(c, name_tok) else return failDecl(c, fn_decl_loc, fn_name, "function {} parameter has no name", .{fn_name}); const c_param = ZigClangFunctionDecl_getParamDecl(fn_decl, param_id); const qual_type = ZigClangParmVarDecl_getOriginalType(c_param); const is_const = ZigClangQualType_isConstQualified(qual_type); const mangled_param_name = try block_scope.makeMangledName(c, param_name); if (!is_const) { const bare_arg_name = try std.fmt.allocPrint(c.arena, "arg_{}", .{mangled_param_name}); const arg_name = try block_scope.makeMangledName(c, bare_arg_name); const mut_tok = try appendToken(c, .Keyword_var, "var"); const name_tok = try appendIdentifier(c, mangled_param_name); const eq_token = try appendToken(c, .Equal, "="); const init_node = try transCreateNodeIdentifier(c, arg_name); const semicolon_token = try appendToken(c, .Semicolon, ";"); const node = try ast.Node.VarDecl.create(c.arena, .{ .mut_token = mut_tok, .name_token = name_tok, .semicolon_token = semicolon_token, }, .{ .eq_token = eq_token, .init_node = init_node, }); try block_scope.statements.append(&node.base); param.name_token = try appendIdentifier(c, arg_name); _ = try appendToken(c, .Colon, ":"); } param_id += 1; } const casted_body = @ptrCast(*const ZigClangCompoundStmt, body_stmt); transCompoundStmtInline(rp, &block_scope.base, casted_body, &block_scope) 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", .{}), }; // add return statement if the function didn't have one blk: { const fn_ty = @ptrCast(*const ZigClangFunctionType, fn_type); if (ZigClangFunctionType_getNoReturnAttr(fn_ty)) break :blk; const return_qt = ZigClangFunctionType_getReturnType(fn_ty); if (isCVoid(return_qt)) break :blk; if (block_scope.statements.items.len > 0) { var last = block_scope.statements.items[block_scope.statements.items.len - 1]; while (true) { switch (last.tag) { .Block, .LabeledBlock => { const stmts = last.blockStatements(); if (stmts.len == 0) break; last = stmts[stmts.len - 1]; }, // no extra return needed .Return => break :blk, else => break, } } } const return_expr = try ast.Node.ControlFlowExpression.create(rp.c.arena, .{ .ltoken = try appendToken(rp.c, .Keyword_return, "return"), .tag = .Return, }, .{ .rhs = transZeroInitExpr(rp, scope, fn_decl_loc, ZigClangQualType_getTypePtr(return_qt)) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.UnsupportedTranslation, error.UnsupportedType, => return failDecl(c, fn_decl_loc, fn_name, "unable to create a return value for function", .{}), }, }); _ = try appendToken(rp.c, .Semicolon, ";"); try block_scope.statements.append(&return_expr.base); } const body_node = try block_scope.complete(rp.c); proto_node.setBodyNode(body_node); return addTopLevelDecl(c, fn_name, &proto_node.base); } /// if mangled_name is not null, this var decl was declared in a block scope. fn visitVarDecl(c: *Context, var_decl: *const ZigClangVarDecl, mangled_name: ?[]const u8) Error!void { const var_name = mangled_name orelse try c.str(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, var_decl))); if (c.global_scope.sym_table.contains(var_name)) return; // Avoid processing this decl twice const rp = makeRestorePoint(c); const visib_tok = if (mangled_name) |_| null else try appendToken(c, .Keyword_pub, "pub"); const thread_local_token = if (ZigClangVarDecl_getTLSKind(var_decl) == .None) null else try appendToken(c, .Keyword_threadlocal, "threadlocal"); const scope = &c.global_scope.base; // 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.arena, "{}_{}", .{ var_name, c.getMangle() }) else var_name; const var_decl_loc = ZigClangVarDecl_getLocation(var_decl); const qual_type = ZigClangVarDecl_getTypeSourceInfo_getType(var_decl); const storage_class = ZigClangVarDecl_getStorageClass(var_decl); const is_const = ZigClangQualType_isConstQualified(qual_type); const has_init = ZigClangVarDecl_hasInit(var_decl); // In C extern variables with initializers behave like Zig exports. // extern int foo = 2; // does the same as: // extern int foo; // int foo = 2; const extern_tok = if (storage_class == .Extern and !has_init) try appendToken(c, .Keyword_extern, "extern") else if (storage_class != .Static) try appendToken(c, .Keyword_export, "export") 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, 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, checked_name, "unable to resolve variable type", .{}); }, error.OutOfMemory => |e| return e, }; var eq_tok: ast.TokenIndex = undefined; var init_node: ?*ast.Node = null; // If the initialization expression is not present, initialize with undefined. // If it is an integer literal, we can skip the @as since it will be redundant // with the variable type. if (has_init) { eq_tok = try appendToken(c, .Equal, "="); init_node = if (ZigClangVarDecl_getInit(var_decl)) |expr| transExprCoercing(rp, &c.global_scope.base, expr, .used, .r_value) catch |err| switch (err) { error.UnsupportedTranslation, error.UnsupportedType, => { 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) { eq_tok = try appendToken(c, .Equal, "="); // The C language specification states that variables with static or threadlocal // storage without an initializer are initialized to a zero value. // @import("std").mem.zeroes(T) const import_fn_call = try c.createBuiltinCall("@import", 1); const std_node = try transCreateNodeStringLiteral(c, "\"std\""); import_fn_call.params()[0] = std_node; import_fn_call.rparen_token = try appendToken(c, .RParen, ")"); const inner_field_access = try transCreateNodeFieldAccess(c, &import_fn_call.base, "mem"); const outer_field_access = try transCreateNodeFieldAccess(c, inner_field_access, "zeroes"); const zero_init_call = try c.createCall(outer_field_access, 1); zero_init_call.params()[0] = type_node; zero_init_call.rtoken = try appendToken(c, .RParen, ")"); init_node = &zero_init_call.base; } const linksection_expr = blk: { var str_len: usize = undefined; if (ZigClangVarDecl_getSectionAttribute(var_decl, &str_len)) |str_ptr| { _ = try appendToken(rp.c, .Keyword_linksection, "linksection"); _ = try appendToken(rp.c, .LParen, "("); const expr = try transCreateNodeStringLiteral( rp.c, try std.fmt.allocPrint(rp.c.arena, "\"{}\"", .{str_ptr[0..str_len]}), ); _ = try appendToken(rp.c, .RParen, ")"); break :blk expr; } break :blk null; }; const align_expr = blk: { const alignment = ZigClangVarDecl_getAlignedAttribute(var_decl, rp.c.clang_context); if (alignment != 0) { _ = try appendToken(rp.c, .Keyword_align, "align"); _ = try appendToken(rp.c, .LParen, "("); // Clang reports the alignment in bits const expr = try transCreateNodeInt(rp.c, alignment / 8); _ = try appendToken(rp.c, .RParen, ")"); break :blk expr; } break :blk null; }; const node = try ast.Node.VarDecl.create(c.arena, .{ .name_token = name_tok, .mut_token = mut_tok, .semicolon_token = try appendToken(c, .Semicolon, ";"), }, .{ .visib_token = visib_tok, .thread_local_token = thread_local_token, .eq_token = eq_tok, .extern_export_token = extern_tok, .type_node = type_node, .align_node = align_expr, .section_node = linksection_expr, .init_node = init_node, }); return addTopLevelDecl(c, checked_name, &node.base); } 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 checkForBuiltinTypedef(checked_name: []const u8) ?[]const u8 { const table = [_][2][]const u8{ .{ "uint8_t", "u8" }, .{ "int8_t", "i8" }, .{ "uint16_t", "u16" }, .{ "int16_t", "i16" }, .{ "uint32_t", "u32" }, .{ "int32_t", "i32" }, .{ "uint64_t", "u64" }, .{ "int64_t", "i64" }, .{ "intptr_t", "isize" }, .{ "uintptr_t", "usize" }, .{ "ssize_t", "isize" }, .{ "size_t", "usize" }, }; for (table) |entry| { if (mem.eql(u8, checked_name, entry[0])) { return entry[1]; } } return null; } fn transTypeDef(c: *Context, typedef_decl: *const ZigClangTypedefNameDecl, top_level_visit: bool) Error!?*ast.Node { if (c.decl_table.get(@ptrToInt(ZigClangTypedefNameDecl_getCanonicalDecl(typedef_decl)))) |name| return transCreateNodeIdentifier(c, name); // Avoid processing this decl twice const rp = makeRestorePoint(c); const typedef_name = try c.str(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, typedef_decl))); // TODO https://github.com/ziglang/zig/issues/3756 // TODO https://github.com/ziglang/zig/issues/1802 const checked_name = if (isZigPrimitiveType(typedef_name)) try std.fmt.allocPrint(c.arena, "{}_{}", .{ typedef_name, c.getMangle() }) else typedef_name; if (checkForBuiltinTypedef(checked_name)) |builtin| { return transTypeDefAsBuiltin(c, typedef_decl, builtin); } if (!top_level_visit) { return transCreateNodeIdentifier(c, checked_name); } _ = try c.decl_table.put(@ptrToInt(ZigClangTypedefNameDecl_getCanonicalDecl(typedef_decl)), checked_name); const node = (try transCreateNodeTypedef(rp, typedef_decl, true, checked_name)) orelse return null; try addTopLevelDecl(c, checked_name, node); return transCreateNodeIdentifier(c, checked_name); } fn transCreateNodeTypedef( rp: RestorePoint, typedef_decl: *const ZigClangTypedefNameDecl, toplevel: bool, checked_name: []const u8, ) Error!?*ast.Node { const visib_tok = if (toplevel) try appendToken(rp.c, .Keyword_pub, "pub") else null; const mut_tok = try appendToken(rp.c, .Keyword_const, "const"); const name_tok = try appendIdentifier(rp.c, checked_name); const eq_token = try appendToken(rp.c, .Equal, "="); const child_qt = ZigClangTypedefNameDecl_getUnderlyingType(typedef_decl); const typedef_loc = ZigClangTypedefNameDecl_getLocation(typedef_decl); const init_node = transQualType(rp, child_qt, typedef_loc) catch |err| switch (err) { error.UnsupportedType => { try failDecl(rp.c, typedef_loc, checked_name, "unable to resolve typedef child type", .{}); return null; }, error.OutOfMemory => |e| return e, }; const semicolon_token = try appendToken(rp.c, .Semicolon, ";"); const node = try ast.Node.VarDecl.create(rp.c.arena, .{ .name_token = name_tok, .mut_token = mut_tok, .semicolon_token = semicolon_token, }, .{ .visib_token = visib_tok, .eq_token = eq_token, .init_node = init_node, }); return &node.base; } fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?*ast.Node { if (c.decl_table.get(@ptrToInt(ZigClangRecordDecl_getCanonicalDecl(record_decl)))) |name| return try transCreateNodeIdentifier(c, name); // Avoid processing this decl twice const record_loc = ZigClangRecordDecl_getLocation(record_decl); var bare_name = try c.str(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, record_decl))); var is_unnamed = false; // Record declarations such as `struct {...} x` have no name but they're not // anonymous hence here isAnonymousStructOrUnion is not needed if (bare_name.len == 0) { bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{}", .{c.getMangle()}); is_unnamed = true; } 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.arena, "{}_{}", .{ container_kind_name, bare_name }); _ = try c.decl_table.put(@ptrToInt(ZigClangRecordDecl_getCanonicalDecl(record_decl)), name); const visib_tok = if (!is_unnamed) try appendToken(c, .Keyword_pub, "pub") else null; const mut_tok = try appendToken(c, .Keyword_const, "const"); const name_tok = try appendIdentifier(c, name); const eq_token = try appendToken(c, .Equal, "="); var semicolon: ast.TokenIndex = undefined; const 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 layout_tok = try if (ZigClangRecordDecl_getPackedAttribute(record_decl)) appendToken(c, .Keyword_packed, "packed") else appendToken(c, .Keyword_extern, "extern"); const container_tok = try appendToken(c, container_kind, container_kind_name); const lbrace_token = try appendToken(c, .LBrace, "{"); var fields_and_decls = std.ArrayList(*ast.Node).init(c.gpa); defer fields_and_decls.deinit(); var unnamed_field_count: u32 = 0; 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); const field_qt = ZigClangFieldDecl_getType(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; } if (ZigClangType_isIncompleteOrZeroLengthArrayType(qualTypeCanon(field_qt), c.clang_context)) { const opaque = try transCreateNodeOpaqueType(c); semicolon = try appendToken(c, .Semicolon, ";"); try emitWarning(c, field_loc, "{} demoted to opaque type - has variable length array", .{container_kind_name}); break :blk opaque; } var is_anon = false; var raw_name = try c.str(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, field_decl))); if (ZigClangFieldDecl_isAnonymousStructOrUnion(field_decl) or raw_name.len == 0) { // Context.getMangle() is not used here because doing so causes unpredictable field names for anonymous fields. raw_name = try std.fmt.allocPrint(c.arena, "unnamed_{}", .{unnamed_field_count}); unnamed_field_count += 1; is_anon = true; } const field_name = try appendIdentifier(c, raw_name); _ = try appendToken(c, .Colon, ":"); const field_type = transQualType(rp, field_qt, field_loc) catch |err| switch (err) { error.UnsupportedType => { const opaque = try transCreateNodeOpaqueType(c); semicolon = try appendToken(c, .Semicolon, ";"); try emitWarning(c, record_loc, "{} demoted to opaque type - unable to translate type of field {}", .{ container_kind_name, raw_name }); break :blk opaque; }, else => |e| return e, }; const align_expr = blk_2: { const alignment = ZigClangFieldDecl_getAlignedAttribute(field_decl, rp.c.clang_context); if (alignment != 0) { _ = try appendToken(rp.c, .Keyword_align, "align"); _ = try appendToken(rp.c, .LParen, "("); // Clang reports the alignment in bits const expr = try transCreateNodeInt(rp.c, alignment / 8); _ = try appendToken(rp.c, .RParen, ")"); break :blk_2 expr; } break :blk_2 null; }; const field_node = try c.arena.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 = align_expr, }; if (is_anon) { _ = try c.decl_table.put( @ptrToInt(ZigClangFieldDecl_getCanonicalDecl(field_decl)), raw_name, ); } try fields_and_decls.append(&field_node.base); _ = try appendToken(c, .Comma, ","); } const container_node = try ast.Node.ContainerDecl.alloc(c.arena, fields_and_decls.items.len); container_node.* = .{ .layout_token = layout_tok, .kind_token = container_tok, .init_arg_expr = .None, .fields_and_decls_len = fields_and_decls.items.len, .lbrace_token = lbrace_token, .rbrace_token = try appendToken(c, .RBrace, "}"), }; mem.copy(*ast.Node, container_node.fieldsAndDecls(), fields_and_decls.items); semicolon = try appendToken(c, .Semicolon, ";"); break :blk &container_node.base; }; const node = try ast.Node.VarDecl.create(c.arena, .{ .name_token = name_tok, .mut_token = mut_tok, .semicolon_token = semicolon, }, .{ .visib_token = visib_tok, .eq_token = eq_token, .init_node = init_node, }); try addTopLevelDecl(c, name, &node.base); if (!is_unnamed) try c.alias_list.append(.{ .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); // Avoid processing this decl twice const rp = makeRestorePoint(c); const enum_loc = ZigClangEnumDecl_getLocation(enum_decl); var bare_name = try c.str(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, enum_decl))); var is_unnamed = false; if (bare_name.len == 0) { bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{}", .{c.getMangle()}); is_unnamed = true; } const name = try std.fmt.allocPrint(c.arena, "enum_{}", .{bare_name}); _ = try c.decl_table.put(@ptrToInt(ZigClangEnumDecl_getCanonicalDecl(enum_decl)), name); const visib_tok = if (!is_unnamed) try appendToken(c, .Keyword_pub, "pub") else null; const mut_tok = try appendToken(c, .Keyword_const, "const"); const name_tok = try appendIdentifier(c, name); const eq_token = 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"); var fields_and_decls = std.ArrayList(*ast.Node).init(c.gpa); defer fields_and_decls.deinit(); const int_type = ZigClangEnumDecl_getIntegerType(enum_decl); // The underlying type may be null in case of forward-declared enum // types, while that's not ISO-C compliant many compilers allow this and // default to the usual integer type used for all the enums. // default to c_int since msvc and gcc default to different types _ = try appendToken(c, .LParen, "("); const init_arg_expr = ast.Node.ContainerDecl.InitArg{ .Type = if (int_type.ptr != null and !isCBuiltinType(int_type, .UInt) and !isCBuiltinType(int_type, .Int)) 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, } else try transCreateNodeIdentifier(c, "c_int"), }; _ = try appendToken(c, .RParen, ")"); const 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(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, 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_2: { _ = try appendToken(c, .Colon, "="); break :blk_2 try transCreateNodeAPInt(c, ZigClangEnumConstantDecl_getInitVal(enum_const)); } else null; const field_node = try c.arena.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 fields_and_decls.append(&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_visib_tok = try appendToken(c, .Keyword_pub, "pub"); const tld_mut_tok = try appendToken(c, .Keyword_const, "const"); const tld_name_tok = try appendIdentifier(c, enum_val_name); const tld_eq_token = try appendToken(c, .Equal, "="); const cast_node = try rp.c.createBuiltinCall("@enumToInt", 1); const enum_ident = try transCreateNodeIdentifier(c, name); const period_tok = try appendToken(c, .Period, "."); const field_ident = try transCreateNodeIdentifier(c, field_name); const field_access_node = try c.arena.create(ast.Node.SimpleInfixOp); field_access_node.* = .{ .base = .{ .tag = .Period }, .op_token = period_tok, .lhs = enum_ident, .rhs = field_ident, }; cast_node.params()[0] = &field_access_node.base; cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); const tld_init_node = &cast_node.base; const tld_semicolon_token = try appendToken(c, .Semicolon, ";"); const tld_node = try ast.Node.VarDecl.create(c.arena, .{ .name_token = tld_name_tok, .mut_token = tld_mut_tok, .semicolon_token = tld_semicolon_token, }, .{ .visib_token = tld_visib_tok, .eq_token = tld_eq_token, .init_node = tld_init_node, }); try addTopLevelDecl(c, field_name, &tld_node.base); } // make non exhaustive const field_node = try c.arena.create(ast.Node.ContainerField); field_node.* = .{ .doc_comments = null, .comptime_token = null, .name_token = try appendIdentifier(c, "_"), .type_expr = null, .value_expr = null, .align_expr = null, }; try fields_and_decls.append(&field_node.base); _ = try appendToken(c, .Comma, ","); const container_node = try ast.Node.ContainerDecl.alloc(c.arena, fields_and_decls.items.len); container_node.* = .{ .layout_token = extern_tok, .kind_token = container_tok, .init_arg_expr = init_arg_expr, .fields_and_decls_len = fields_and_decls.items.len, .lbrace_token = lbrace_token, .rbrace_token = try appendToken(c, .RBrace, "}"), }; mem.copy(*ast.Node, container_node.fieldsAndDecls(), fields_and_decls.items); break :blk &container_node.base; } else try transCreateNodeOpaqueType(c); const semicolon_token = try appendToken(c, .Semicolon, ";"); const node = try ast.Node.VarDecl.create(c.arena, .{ .name_token = name_tok, .mut_token = mut_tok, .semicolon_token = semicolon_token, }, .{ .visib_token = visib_tok, .eq_token = eq_token, .init_node = init_node, }); try addTopLevelDecl(c, name, &node.base); if (!is_unnamed) try c.alias_list.append(.{ .alias = bare_name, .name = name }); return transCreateNodeIdentifier(c, name); } fn createAlias(c: *Context, alias: anytype) !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_token = try appendToken(c, .Equal, "="); const init_node = try transCreateNodeIdentifier(c, alias.name); const semicolon_token = try appendToken(c, .Semicolon, ";"); const node = try ast.Node.VarDecl.create(c.arena, .{ .name_token = name_tok, .mut_token = mut_tok, .semicolon_token = semicolon_token, }, .{ .visib_token = visib_tok, .eq_token = eq_token, .init_node = init_node, }); return addTopLevelDecl(c, alias.alias, &node.base); } const ResultUsed = enum { used, unused, }; const LRValue = enum { l_value, r_value, }; fn transStmt( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangStmt, result_used: ResultUsed, lrvalue: LRValue, ) TransError!*ast.Node { const sc = ZigClangStmt_getStmtClass(stmt); switch (sc) { .BinaryOperatorClass => return transBinaryOperator(rp, scope, @ptrCast(*const ZigClangBinaryOperator, stmt), result_used), .CompoundStmtClass => return transCompoundStmt(rp, scope, @ptrCast(*const ZigClangCompoundStmt, stmt)), .CStyleCastExprClass => return transCStyleCastExprClass(rp, scope, @ptrCast(*const ZigClangCStyleCastExpr, stmt), result_used, lrvalue), .DeclStmtClass => return transDeclStmt(rp, scope, @ptrCast(*const ZigClangDeclStmt, stmt)), .DeclRefExprClass => return transDeclRefExpr(rp, scope, @ptrCast(*const ZigClangDeclRefExpr, stmt), lrvalue), .ImplicitCastExprClass => return transImplicitCastExpr(rp, scope, @ptrCast(*const ZigClangImplicitCastExpr, stmt), result_used), .IntegerLiteralClass => return transIntegerLiteral(rp, scope, @ptrCast(*const ZigClangIntegerLiteral, stmt), result_used, .with_as), .ReturnStmtClass => return transReturnStmt(rp, scope, @ptrCast(*const ZigClangReturnStmt, stmt)), .StringLiteralClass => return transStringLiteral(rp, scope, @ptrCast(*const ZigClangStringLiteral, stmt), result_used), .ParenExprClass => { const expr = try transExpr(rp, scope, ZigClangParenExpr_getSubExpr(@ptrCast(*const ZigClangParenExpr, stmt)), .used, lrvalue); if (expr.tag == .GroupedExpression) return maybeSuppressResult(rp, scope, result_used, expr); const node = try rp.c.arena.create(ast.Node.GroupedExpression); node.* = .{ .lparen = try appendToken(rp.c, .LParen, "("), .expr = expr, .rparen = try appendToken(rp.c, .RParen, ")"), }; return maybeSuppressResult(rp, scope, result_used, &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 rp.c.createBlock(0); 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); }, .BinaryConditionalOperatorClass => { return transBinaryConditionalOperator(rp, scope, @ptrCast(*const ZigClangBinaryConditionalOperator, 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, .with_as), .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), .OpaqueValueExprClass => { const source_expr = ZigClangOpaqueValueExpr_getSourceExpr(@ptrCast(*const ZigClangOpaqueValueExpr, stmt)).?; const expr = try transExpr(rp, scope, source_expr, .used, lrvalue); if (expr.tag == .GroupedExpression) return maybeSuppressResult(rp, scope, result_used, expr); const node = try rp.c.arena.create(ast.Node.GroupedExpression); node.* = .{ .lparen = try appendToken(rp.c, .LParen, "("), .expr = expr, .rparen = try appendToken(rp.c, .RParen, ")"), }; return maybeSuppressResult(rp, scope, result_used, &node.base); }, else => { return revertAndWarn( rp, error.UnsupportedTranslation, ZigClangStmt_getBeginLoc(stmt), "TODO implement translation of stmt class {}", .{@tagName(sc)}, ); }, } } fn transBinaryOperator( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangBinaryOperator, result_used: ResultUsed, ) TransError!*ast.Node { const op = ZigClangBinaryOperator_getOpcode(stmt); const qt = ZigClangBinaryOperator_getType(stmt); var op_token: ast.TokenIndex = undefined; var op_id: ast.Node.Tag = undefined; switch (op) { .Assign => return try 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) try appendToken(rp.c, .LParen, "(") else undefined; const lhs = try transExpr(rp, &block_scope.base, ZigClangBinaryOperator_getLHS(stmt), .unused, .r_value); try block_scope.statements.append(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, rhs); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(rp.c); const rparen = try appendToken(rp.c, .RParen, ")"); const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = lparen, .expr = block_node, .rparen = rparen, }; return maybeSuppressResult(rp, scope, result_used, &grouped_expr.base); } else { return maybeSuppressResult(rp, scope, result_used, rhs); } }, .Div => { if (cIsSignedInteger(qt)) { // signed integer division uses @divTrunc const div_trunc_node = try rp.c.createBuiltinCall("@divTrunc", 2); div_trunc_node.params()[0] = 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); div_trunc_node.params()[1] = rhs; div_trunc_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return maybeSuppressResult(rp, scope, result_used, &div_trunc_node.base); } }, .Rem => { if (cIsSignedInteger(qt)) { // signed integer division uses @rem const rem_node = try rp.c.createBuiltinCall("@rem", 2); rem_node.params()[0] = 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); rem_node.params()[1] = rhs; rem_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return maybeSuppressResult(rp, scope, result_used, &rem_node.base); } }, .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, "|"); }, else => unreachable, } const rhs_node = try transExpr(rp, scope, ZigClangBinaryOperator_getRHS(stmt), .used, .r_value); const lhs = if (isBoolRes(lhs_node)) init: { const cast_node = try rp.c.createBuiltinCall("@boolToInt", 1); cast_node.params()[0] = lhs_node; cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); break :init &cast_node.base; } else lhs_node; const rhs = if (isBoolRes(rhs_node)) init: { const cast_node = try rp.c.createBuiltinCall("@boolToInt", 1); cast_node.params()[0] = rhs_node; cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); break :init &cast_node.base; } else rhs_node; return transCreateNodeInfixOp(rp, scope, lhs, op_id, op_token, rhs, result_used, true); } fn transCompoundStmtInline( rp: RestorePoint, parent_scope: *Scope, stmt: *const ZigClangCompoundStmt, block: *Scope.Block, ) TransError!void { var it = ZigClangCompoundStmt_body_begin(stmt); const end_it = ZigClangCompoundStmt_body_end(stmt); while (it != end_it) : (it += 1) { const result = try transStmt(rp, parent_scope, it[0], .unused, .r_value); try block.statements.append(result); } } fn transCompoundStmt(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangCompoundStmt) TransError!*ast.Node { var block_scope = try Scope.Block.init(rp.c, scope, false); defer block_scope.deinit(); try transCompoundStmtInline(rp, &block_scope.base, stmt, &block_scope); return try block_scope.complete(rp.c); } fn transCStyleCastExprClass( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangCStyleCastExpr, result_used: ResultUsed, lrvalue: LRValue, ) TransError!*ast.Node { const sub_expr = ZigClangCStyleCastExpr_getSubExpr(stmt); const cast_node = (try transCCast( rp, scope, ZigClangCStyleCastExpr_getBeginLoc(stmt), ZigClangCStyleCastExpr_getType(stmt), ZigClangExpr_getType(sub_expr), try transExpr(rp, scope, sub_expr, .used, lrvalue), )); return maybeSuppressResult(rp, scope, result_used, cast_node); } fn transDeclStmtOne( rp: RestorePoint, scope: *Scope, decl: *const ZigClangDecl, block_scope: *Scope.Block, ) TransError!*ast.Node { const c = rp.c; switch (ZigClangDecl_getKind(decl)) { .Var => { const var_decl = @ptrCast(*const ZigClangVarDecl, decl); const qual_type = ZigClangVarDecl_getTypeSourceInfo_getType(var_decl); const name = try c.str(ZigClangNamedDecl_getName_bytes_begin( @ptrCast(*const ZigClangNamedDecl, var_decl), )); const mangled_name = try block_scope.makeMangledName(c, name); switch (ZigClangVarDecl_getStorageClass(var_decl)) { .Extern, .Static => { // This is actually a global variable, put it in the global scope and reference it. // `_ = mangled_name;` try visitVarDecl(rp.c, var_decl, mangled_name); return try maybeSuppressResult(rp, scope, .unused, try transCreateNodeIdentifier(rp.c, mangled_name)); }, else => {}, } const mut_tok = if (ZigClangQualType_isConstQualified(qual_type)) try appendToken(c, .Keyword_const, "const") else try appendToken(c, .Keyword_var, "var"); const name_tok = try appendIdentifier(c, mangled_name); _ = try appendToken(c, .Colon, ":"); const loc = ZigClangDecl_getLocation(decl); const type_node = try transQualType(rp, qual_type, loc); const eq_token = try appendToken(c, .Equal, "="); var init_node = if (ZigClangVarDecl_getInit(var_decl)) |expr| try transExprCoercing(rp, scope, expr, .used, .r_value) else try transCreateNodeUndefinedLiteral(c); if (!qualTypeIsBoolean(qual_type) and isBoolRes(init_node)) { const builtin_node = try rp.c.createBuiltinCall("@boolToInt", 1); builtin_node.params()[0] = init_node; builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); init_node = &builtin_node.base; } const semicolon_token = try appendToken(c, .Semicolon, ";"); const node = try ast.Node.VarDecl.create(c.arena, .{ .name_token = name_tok, .mut_token = mut_tok, .semicolon_token = semicolon_token, }, .{ .eq_token = eq_token, .type_node = type_node, .init_node = init_node, }); return &node.base; }, .Typedef => { const typedef_decl = @ptrCast(*const ZigClangTypedefNameDecl, decl); const name = try c.str(ZigClangNamedDecl_getName_bytes_begin( @ptrCast(*const ZigClangNamedDecl, typedef_decl), )); const underlying_qual = ZigClangTypedefNameDecl_getUnderlyingType(typedef_decl); const underlying_type = ZigClangQualType_getTypePtr(underlying_qual); const mangled_name = try block_scope.makeMangledName(c, name); const node = (try transCreateNodeTypedef(rp, typedef_decl, false, mangled_name)) orelse return error.UnsupportedTranslation; return node; }, else => |kind| return revertAndWarn( rp, error.UnsupportedTranslation, ZigClangDecl_getLocation(decl), "TODO implement translation of DeclStmt kind {}", .{@tagName(kind)}, ), } } fn transDeclStmt(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangDeclStmt) TransError!*ast.Node { const block_scope = scope.findBlockScope(rp.c) catch unreachable; var it = ZigClangDeclStmt_decl_begin(stmt); const end_it = ZigClangDeclStmt_decl_end(stmt); assert(it != end_it); while (true) : (it += 1) { const node = try transDeclStmtOne(rp, scope, it[0], block_scope); if (it + 1 == end_it) { return node; } else { try block_scope.statements.append(node); } } unreachable; } fn transDeclRefExpr( rp: RestorePoint, scope: *Scope, expr: *const ZigClangDeclRefExpr, lrvalue: LRValue, ) TransError!*ast.Node { const value_decl = ZigClangDeclRefExpr_getDecl(expr); const name = try rp.c.str(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, value_decl))); const mangled_name = scope.getAlias(name); return transCreateNodeIdentifier(rp.c, mangled_name); } fn transImplicitCastExpr( rp: RestorePoint, scope: *Scope, expr: *const ZigClangImplicitCastExpr, result_used: ResultUsed, ) TransError!*ast.Node { const c = rp.c; const sub_expr = ZigClangImplicitCastExpr_getSubExpr(expr); const dest_type = getExprQualType(c, @ptrCast(*const ZigClangExpr, expr)); const src_type = getExprQualType(c, sub_expr); switch (ZigClangImplicitCastExpr_getCastKind(expr)) { .BitCast, .FloatingCast, .FloatingToIntegral, .IntegralToFloating, .IntegralCast, .PointerToIntegral, .IntegralToPointer => { const sub_expr_node = try transExpr(rp, scope, sub_expr, .used, .r_value); return try transCCast(rp, scope, ZigClangImplicitCastExpr_getBeginLoc(expr), dest_type, src_type, sub_expr_node); }, .LValueToRValue, .NoOp, .FunctionToPointerDecay => { const sub_expr_node = try transExpr(rp, scope, sub_expr, .used, .r_value); return maybeSuppressResult(rp, scope, result_used, sub_expr_node); }, .ArrayToPointerDecay => { if (exprIsStringLiteral(sub_expr)) { const sub_expr_node = try transExpr(rp, scope, sub_expr, .used, .r_value); return maybeSuppressResult(rp, scope, result_used, sub_expr_node); } const prefix_op = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); prefix_op.rhs = try transExpr(rp, scope, sub_expr, .used, .r_value); return maybeSuppressResult(rp, scope, result_used, &prefix_op.base); }, .NullToPointer => { return try transCreateNodeNullLiteral(rp.c); }, .PointerToBoolean => { // @ptrToInt(val) != 0 const ptr_to_int = try rp.c.createBuiltinCall("@ptrToInt", 1); ptr_to_int.params()[0] = 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 => { const sub_expr_node = try transExpr(rp, scope, sub_expr, .used, .r_value); // The expression is already a boolean one, return it as-is if (isBoolRes(sub_expr_node)) return sub_expr_node; // val != 0 const op_token = try appendToken(rp.c, .BangEqual, "!="); const rhs_node = try transCreateNodeInt(rp.c, 0); return transCreateNodeInfixOp(rp, scope, sub_expr_node, .BangEqual, op_token, rhs_node, result_used, false); }, else => |kind| return revertAndWarn( rp, error.UnsupportedTranslation, ZigClangStmt_getBeginLoc(@ptrCast(*const ZigClangStmt, expr)), "TODO implement translation of CastKind {}", .{@tagName(kind)}, ), } } fn transBoolExpr( rp: RestorePoint, scope: *Scope, expr: *const ZigClangExpr, used: ResultUsed, lrvalue: LRValue, grouped: bool, ) TransError!*ast.Node { if (ZigClangStmt_getStmtClass(@ptrCast(*const ZigClangStmt, expr)) == .IntegerLiteralClass) { var is_zero: bool = undefined; if (!ZigClangIntegerLiteral_isZero(@ptrCast(*const ZigClangIntegerLiteral, expr), &is_zero, rp.c.clang_context)) { return revertAndWarn(rp, error.UnsupportedTranslation, ZigClangExpr_getBeginLoc(expr), "invalid integer literal", .{}); } return try transCreateNodeBoolLiteral(rp.c, !is_zero); } 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.tag == .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(getExprQualType(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.arena.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 exprIsBooleanType(expr: *const ZigClangExpr) bool { return qualTypeIsBoolean(ZigClangExpr_getType(expr)); } fn exprIsStringLiteral(expr: *const ZigClangExpr) bool { switch (ZigClangExpr_getStmtClass(expr)) { .StringLiteralClass => return true, .PredefinedExprClass => return true, .UnaryOperatorClass => { const op_expr = ZigClangUnaryOperator_getSubExpr(@ptrCast(*const ZigClangUnaryOperator, expr)); return exprIsStringLiteral(op_expr); }, else => return false, } } fn isBoolRes(res: *ast.Node) bool { switch (res.tag) { .BoolOr, .BoolAnd, .EqualEqual, .BangEqual, .LessThan, .GreaterThan, .LessOrEqual, .GreaterOrEqual, .BoolNot, .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", .{}); } const SuppressCast = enum { with_as, no_as, }; fn transIntegerLiteral( rp: RestorePoint, scope: *Scope, expr: *const ZigClangIntegerLiteral, result_used: ResultUsed, suppress_as: SuppressCast, ) TransError!*ast.Node { var eval_result: ZigClangExprEvalResult = undefined; if (!ZigClangIntegerLiteral_EvaluateAsInt(expr, &eval_result, rp.c.clang_context)) { const loc = ZigClangIntegerLiteral_getBeginLoc(expr); return revertAndWarn(rp, error.UnsupportedTranslation, loc, "invalid integer literal", .{}); } if (suppress_as == .no_as) { const int_lit_node = try transCreateNodeAPInt(rp.c, ZigClangAPValue_getInt(&eval_result.Val)); return maybeSuppressResult(rp, scope, result_used, int_lit_node); } // Integer literals in C have types, and this can matter for several reasons. // For example, this is valid C: // unsigned char y = 256; // How this gets evaluated is the 256 is an integer, which gets truncated to signed char, then bit-casted // to unsigned char, resulting in 0. In order for this to work, we have to emit this zig code: // var y = @bitCast(u8, @truncate(i8, @as(c_int, 256))); // Ideally in translate-c we could flatten this out to simply: // var y: u8 = 0; // But the first step is to be correct, and the next step is to make the output more elegant. // @as(T, x) const expr_base = @ptrCast(*const ZigClangExpr, expr); const as_node = try rp.c.createBuiltinCall("@as", 2); const ty_node = try transQualType(rp, ZigClangExpr_getType(expr_base), ZigClangExpr_getBeginLoc(expr_base)); as_node.params()[0] = ty_node; _ = try appendToken(rp.c, .Comma, ","); as_node.params()[1] = try transCreateNodeAPInt(rp.c, ZigClangAPValue_getInt(&eval_result.Val)); as_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return maybeSuppressResult(rp, scope, result_used, &as_node.base); } fn transReturnStmt( rp: RestorePoint, scope: *Scope, expr: *const ZigClangReturnStmt, ) TransError!*ast.Node { const return_kw = try appendToken(rp.c, .Keyword_return, "return"); const rhs: ?*ast.Node = if (ZigClangReturnStmt_getRetValue(expr)) |val_expr| try transExprCoercing(rp, scope, val_expr, .used, .r_value) else null; const return_expr = try ast.Node.ControlFlowExpression.create(rp.c.arena, .{ .ltoken = return_kw, .tag = .Return, }, .{ .rhs = rhs, }); _ = try appendToken(rp.c, .Semicolon, ";"); return &return_expr.base; } fn transStringLiteral( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangStringLiteral, result_used: ResultUsed, ) TransError!*ast.Node { const kind = ZigClangStringLiteral_getKind(stmt); switch (kind) { .Ascii, .UTF8 => { var len: usize = undefined; const bytes_ptr = ZigClangStringLiteral_getString_bytes_begin_size(stmt, &len); const str = bytes_ptr[0..len]; var char_buf: [4]u8 = undefined; len = 0; for (str) |c| len += escapeChar(c, &char_buf).len; const buf = try rp.c.arena.alloc(u8, len + "\"\"".len); buf[0] = '"'; writeEscapedString(buf[1..], str); buf[buf.len - 1] = '"'; const token = try appendToken(rp.c, .StringLiteral, buf); const node = try rp.c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .StringLiteral }, .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 string literal kind {}", .{kind}, ), } } fn escapedStringLen(s: []const u8) usize { var len: usize = 0; var char_buf: [4]u8 = undefined; for (s) |c| len += escapeChar(c, &char_buf).len; return len; } fn writeEscapedString(buf: []u8, s: []const u8) void { var char_buf: [4]u8 = undefined; var i: usize = 0; for (s) |c| { const escaped = escapeChar(c, &char_buf); 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 { return switch (c) { '\"' => "\\\"", '\'' => "\\'", '\\' => "\\\\", '\n' => "\\n", '\r' => "\\r", '\t' => "\\t", // Handle the remaining escapes Zig doesn't support by turning them // into their respective hex representation else => if (std.ascii.isCntrl(c)) std.fmt.bufPrint(char_buf, "\\x{x:0>2}", .{c}) catch unreachable else std.fmt.bufPrint(char_buf, "{c}", .{c}) catch unreachable, }; } fn transCCast( rp: RestorePoint, scope: *Scope, loc: ZigClangSourceLocation, dst_type: ZigClangQualType, src_type: ZigClangQualType, expr: *ast.Node, ) !*ast.Node { if (ZigClangType_isVoidType(qualTypeCanon(dst_type))) return expr; if (ZigClangQualType_eq(dst_type, src_type)) return expr; if (qualTypeIsPtr(dst_type) and qualTypeIsPtr(src_type)) return transCPtrCast(rp, loc, dst_type, src_type, expr); if (cIsInteger(dst_type) and cIsInteger(src_type)) { // 1. Extend or truncate without changing signed-ness. // 2. Bit-cast to correct signed-ness // @bitCast(dest_type, intermediate_value) const cast_node = try rp.c.createBuiltinCall("@bitCast", 2); cast_node.params()[0] = try transQualType(rp, dst_type, loc); _ = try appendToken(rp.c, .Comma, ","); switch (cIntTypeCmp(dst_type, src_type)) { .lt => { // @truncate(SameSignSmallerInt, src_type) const trunc_node = try rp.c.createBuiltinCall("@truncate", 2); const ty_node = try transQualTypeIntWidthOf(rp.c, dst_type, cIsSignedInteger(src_type)); trunc_node.params()[0] = ty_node; _ = try appendToken(rp.c, .Comma, ","); trunc_node.params()[1] = expr; trunc_node.rparen_token = try appendToken(rp.c, .RParen, ")"); cast_node.params()[1] = &trunc_node.base; }, .gt => { // @as(SameSignBiggerInt, src_type) const as_node = try rp.c.createBuiltinCall("@as", 2); const ty_node = try transQualTypeIntWidthOf(rp.c, dst_type, cIsSignedInteger(src_type)); as_node.params()[0] = ty_node; _ = try appendToken(rp.c, .Comma, ","); as_node.params()[1] = expr; as_node.rparen_token = try appendToken(rp.c, .RParen, ")"); cast_node.params()[1] = &as_node.base; }, .eq => { cast_node.params()[1] = expr; }, } cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return &cast_node.base; } if (cIsInteger(dst_type) and qualTypeIsPtr(src_type)) { // @intCast(dest_type, @ptrToInt(val)) const cast_node = try rp.c.createBuiltinCall("@intCast", 2); cast_node.params()[0] = try transQualType(rp, dst_type, loc); _ = try appendToken(rp.c, .Comma, ","); const builtin_node = try rp.c.createBuiltinCall("@ptrToInt", 1); builtin_node.params()[0] = expr; builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); cast_node.params()[1] = &builtin_node.base; cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return &cast_node.base; } if (cIsInteger(src_type) and qualTypeIsPtr(dst_type)) { // @intToPtr(dest_type, val) const builtin_node = try rp.c.createBuiltinCall("@intToPtr", 2); builtin_node.params()[0] = try transQualType(rp, dst_type, loc); _ = try appendToken(rp.c, .Comma, ","); builtin_node.params()[1] = 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 rp.c.createBuiltinCall("@floatCast", 2); builtin_node.params()[0] = try transQualType(rp, dst_type, loc); _ = try appendToken(rp.c, .Comma, ","); builtin_node.params()[1] = 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 rp.c.createBuiltinCall("@floatToInt", 2); builtin_node.params()[0] = try transQualType(rp, dst_type, loc); _ = try appendToken(rp.c, .Comma, ","); builtin_node.params()[1] = 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 rp.c.createBuiltinCall("@intToFloat", 2); builtin_node.params()[0] = try transQualType(rp, dst_type, loc); _ = try appendToken(rp.c, .Comma, ","); builtin_node.params()[1] = expr; builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return &builtin_node.base; } if (ZigClangType_isBooleanType(qualTypeCanon(src_type)) and !ZigClangType_isBooleanType(qualTypeCanon(dst_type))) { // @boolToInt returns either a comptime_int or a u1 const builtin_node = try rp.c.createBuiltinCall("@boolToInt", 1); builtin_node.params()[0] = expr; builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); const inner_cast_node = try rp.c.createBuiltinCall("@intCast", 2); inner_cast_node.params()[0] = try transCreateNodeIdentifier(rp.c, "u1"); _ = try appendToken(rp.c, .Comma, ","); inner_cast_node.params()[1] = &builtin_node.base; inner_cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); const cast_node = try rp.c.createBuiltinCall("@intCast", 2); cast_node.params()[0] = try transQualType(rp, dst_type, loc); _ = try appendToken(rp.c, .Comma, ","); if (cIsSignedInteger(dst_type)) { const bitcast_node = try rp.c.createBuiltinCall("@bitCast", 2); bitcast_node.params()[0] = try transCreateNodeIdentifier(rp.c, "i1"); _ = try appendToken(rp.c, .Comma, ","); bitcast_node.params()[1] = &inner_cast_node.base; bitcast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); cast_node.params()[1] = &bitcast_node.base; } else { cast_node.params()[1] = &inner_cast_node.base; } cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return &cast_node.base; } if (ZigClangQualType_getTypeClass(ZigClangQualType_getCanonicalType(dst_type)) == .Enum) { const builtin_node = try rp.c.createBuiltinCall("@intToEnum", 2); builtin_node.params()[0] = try transQualType(rp, dst_type, loc); _ = try appendToken(rp.c, .Comma, ","); builtin_node.params()[1] = expr; builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return &builtin_node.base; } if (ZigClangQualType_getTypeClass(ZigClangQualType_getCanonicalType(src_type)) == .Enum and ZigClangQualType_getTypeClass(ZigClangQualType_getCanonicalType(dst_type)) != .Enum) { const builtin_node = try rp.c.createBuiltinCall("@enumToInt", 1); builtin_node.params()[0] = expr; builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return &builtin_node.base; } const cast_node = try rp.c.createBuiltinCall("@as", 2); cast_node.params()[0] = try transQualType(rp, dst_type, loc); _ = try appendToken(rp.c, .Comma, ","); cast_node.params()[1] = expr; cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return &cast_node.base; } fn transExpr( rp: RestorePoint, scope: *Scope, expr: *const ZigClangExpr, used: ResultUsed, lrvalue: LRValue, ) TransError!*ast.Node { return transStmt(rp, scope, @ptrCast(*const ZigClangStmt, expr), used, lrvalue); } /// Same as `transExpr` but with the knowledge that the operand will be type coerced, and therefore /// an `@as` would be redundant. This is used to prevent redundant `@as` in integer literals. fn transExprCoercing( rp: RestorePoint, scope: *Scope, expr: *const ZigClangExpr, used: ResultUsed, lrvalue: LRValue, ) TransError!*ast.Node { switch (ZigClangStmt_getStmtClass(@ptrCast(*const ZigClangStmt, expr))) { .IntegerLiteralClass => { return transIntegerLiteral(rp, scope, @ptrCast(*const ZigClangIntegerLiteral, expr), .used, .no_as); }, .CharacterLiteralClass => { return transCharLiteral(rp, scope, @ptrCast(*const ZigClangCharacterLiteral, expr), .used, .no_as); }, .UnaryOperatorClass => { const un_expr = @ptrCast(*const ZigClangUnaryOperator, expr); if (ZigClangUnaryOperator_getOpcode(un_expr) == .Extension) { return transExprCoercing(rp, scope, ZigClangUnaryOperator_getSubExpr(un_expr), used, lrvalue); } }, else => {}, } return transExpr(rp, scope, expr, .used, .r_value); } fn transInitListExprRecord( rp: RestorePoint, scope: *Scope, loc: ZigClangSourceLocation, expr: *const ZigClangInitListExpr, ty: *const ZigClangType, used: ResultUsed, ) TransError!*ast.Node { var is_union_type = false; // Unions and Structs are both represented as RecordDecl const record_ty = ZigClangType_getAsRecordType(ty) orelse blk: { is_union_type = true; break :blk ZigClangType_getAsUnionType(ty); } orelse unreachable; const record_decl = ZigClangRecordType_getDecl(record_ty); const record_def = ZigClangRecordDecl_getDefinition(record_decl) orelse unreachable; const ty_node = try transType(rp, ty, loc); const init_count = ZigClangInitListExpr_getNumInits(expr); var field_inits = std.ArrayList(*ast.Node).init(rp.c.gpa); defer field_inits.deinit(); _ = try appendToken(rp.c, .LBrace, "{"); var init_i: c_uint = 0; 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); // The initializer for a union type has a single entry only if (is_union_type and field_decl != ZigClangInitListExpr_getInitializedFieldInUnion(expr)) { continue; } assert(init_i < init_count); const elem_expr = ZigClangInitListExpr_getInit(expr, init_i); init_i += 1; // Generate the field assignment expression: // .field_name = expr const period_tok = try appendToken(rp.c, .Period, "."); var raw_name = try rp.c.str(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, field_decl))); if (ZigClangFieldDecl_isAnonymousStructOrUnion(field_decl)) { const name = rp.c.decl_table.get(@ptrToInt(ZigClangFieldDecl_getCanonicalDecl(field_decl))).?; raw_name = try mem.dupe(rp.c.arena, u8, name); } const field_name_tok = try appendIdentifier(rp.c, raw_name); _ = try appendToken(rp.c, .Equal, "="); const field_init_node = try rp.c.arena.create(ast.Node.FieldInitializer); field_init_node.* = .{ .period_token = period_tok, .name_token = field_name_tok, .expr = try transExpr(rp, scope, elem_expr, .used, .r_value), }; try field_inits.append(&field_init_node.base); _ = try appendToken(rp.c, .Comma, ","); } const node = try ast.Node.StructInitializer.alloc(rp.c.arena, field_inits.items.len); node.* = .{ .lhs = ty_node, .rtoken = try appendToken(rp.c, .RBrace, "}"), .list_len = field_inits.items.len, }; mem.copy(*ast.Node, node.list(), field_inits.items); return &node.base; } fn transCreateNodeArrayType( rp: RestorePoint, source_loc: ZigClangSourceLocation, ty: *const ZigClangType, len: anytype, ) !*ast.Node { const node = try rp.c.arena.create(ast.Node.ArrayType); const op_token = try appendToken(rp.c, .LBracket, "["); const len_expr = try transCreateNodeInt(rp.c, len); _ = try appendToken(rp.c, .RBracket, "]"); node.* = .{ .op_token = op_token, .rhs = try transType(rp, ty, source_loc), .len_expr = len_expr, }; return &node.base; } fn transInitListExprArray( rp: RestorePoint, scope: *Scope, loc: ZigClangSourceLocation, expr: *const ZigClangInitListExpr, ty: *const ZigClangType, used: ResultUsed, ) TransError!*ast.Node { const arr_type = ZigClangType_getAsArrayTypeUnsafe(ty); const child_qt = ZigClangArrayType_getElementType(arr_type); const init_count = ZigClangInitListExpr_getNumInits(expr); assert(ZigClangType_isConstantArrayType(@ptrCast(*const ZigClangType, arr_type))); const const_arr_ty = @ptrCast(*const ZigClangConstantArrayType, arr_type); const size_ap_int = ZigClangConstantArrayType_getSize(const_arr_ty); const all_count = ZigClangAPInt_getLimitedValue(size_ap_int, math.maxInt(usize)); const leftover_count = all_count - init_count; var init_node: *ast.Node.ArrayInitializer = undefined; var cat_tok: ast.TokenIndex = undefined; if (init_count != 0) { const ty_node = try transCreateNodeArrayType( rp, loc, ZigClangQualType_getTypePtr(child_qt), init_count, ); _ = try appendToken(rp.c, .LBrace, "{"); init_node = try ast.Node.ArrayInitializer.alloc(rp.c.arena, init_count); init_node.* = .{ .lhs = ty_node, .rtoken = undefined, .list_len = init_count, }; const init_list = init_node.list(); var i: c_uint = 0; while (i < init_count) : (i += 1) { const elem_expr = ZigClangInitListExpr_getInit(expr, i); init_list[i] = try transExpr(rp, scope, elem_expr, .used, .r_value); _ = try appendToken(rp.c, .Comma, ","); } init_node.rtoken = try appendToken(rp.c, .RBrace, "}"); if (leftover_count == 0) { return &init_node.base; } cat_tok = try appendToken(rp.c, .PlusPlus, "++"); } const ty_node = try transCreateNodeArrayType(rp, loc, ZigClangQualType_getTypePtr(child_qt), 1); _ = try appendToken(rp.c, .LBrace, "{"); const filler_init_node = try ast.Node.ArrayInitializer.alloc(rp.c.arena, 1); filler_init_node.* = .{ .lhs = ty_node, .rtoken = undefined, .list_len = 1, }; const filler_val_expr = ZigClangInitListExpr_getArrayFiller(expr); filler_init_node.list()[0] = try transExpr(rp, scope, filler_val_expr, .used, .r_value); filler_init_node.rtoken = try appendToken(rp.c, .RBrace, "}"); const rhs_node = if (leftover_count == 1) &filler_init_node.base else blk: { const mul_tok = try appendToken(rp.c, .AsteriskAsterisk, "**"); const mul_node = try rp.c.arena.create(ast.Node.SimpleInfixOp); mul_node.* = .{ .base = .{ .tag = .ArrayMult }, .op_token = mul_tok, .lhs = &filler_init_node.base, .rhs = try transCreateNodeInt(rp.c, leftover_count), }; break :blk &mul_node.base; }; if (init_count == 0) { return rhs_node; } const cat_node = try rp.c.arena.create(ast.Node.SimpleInfixOp); cat_node.* = .{ .base = .{ .tag = .ArrayCat }, .op_token = cat_tok, .lhs = &init_node.base, .rhs = rhs_node, }; return &cat_node.base; } fn transInitListExpr( rp: RestorePoint, scope: *Scope, expr: *const ZigClangInitListExpr, used: ResultUsed, ) TransError!*ast.Node { const qt = getExprQualType(rp.c, @ptrCast(*const ZigClangExpr, expr)); var qual_type = ZigClangQualType_getTypePtr(qt); const source_loc = ZigClangExpr_getBeginLoc(@ptrCast(*const ZigClangExpr, expr)); if (ZigClangType_isRecordType(qual_type)) { return transInitListExprRecord( rp, scope, source_loc, expr, qual_type, used, ); } else if (ZigClangType_isArrayType(qual_type)) { return transInitListExprArray( rp, scope, source_loc, expr, qual_type, used, ); } else { const type_name = rp.c.str(ZigClangType_getTypeClassName(qual_type)); return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported initlist type: '{}'", .{type_name}); } } fn transZeroInitExpr( rp: RestorePoint, scope: *Scope, source_loc: ZigClangSourceLocation, ty: *const ZigClangType, ) TransError!*ast.Node { switch (ZigClangType_getTypeClass(ty)) { .Builtin => { const builtin_ty = @ptrCast(*const ZigClangBuiltinType, ty); switch (ZigClangBuiltinType_getKind(builtin_ty)) { .Bool => return try transCreateNodeBoolLiteral(rp.c, false), .Char_U, .UChar, .Char_S, .Char8, .SChar, .UShort, .UInt, .ULong, .ULongLong, .Short, .Int, .Long, .LongLong, .UInt128, .Int128, .Float, .Double, .Float128, .Float16, .LongDouble, => return transCreateNodeInt(rp.c, 0), else => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported builtin type", .{}), } }, .Pointer => return transCreateNodeNullLiteral(rp.c), .Typedef => { const typedef_ty = @ptrCast(*const ZigClangTypedefType, ty); const typedef_decl = ZigClangTypedefType_getDecl(typedef_ty); return transZeroInitExpr( rp, scope, source_loc, ZigClangQualType_getTypePtr( ZigClangTypedefNameDecl_getUnderlyingType(typedef_decl), ), ); }, else => {}, } return revertAndWarn(rp, error.UnsupportedType, source_loc, "type does not have an implicit init value", .{}); } fn transImplicitValueInitExpr( rp: RestorePoint, scope: *Scope, expr: *const ZigClangExpr, used: ResultUsed, ) TransError!*ast.Node { const source_loc = ZigClangExpr_getBeginLoc(expr); const qt = getExprQualType(rp.c, expr); const ty = ZigClangQualType_getTypePtr(qt); return transZeroInitExpr(rp, scope, source_loc, ty); } 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.Condition{ .base = .{ .parent = scope, .id = .Condition, }, }; defer cond_scope.deinit(); const cond_expr = @ptrCast(*const ZigClangExpr, ZigClangIfStmt_getCond(stmt)); if_node.condition = try transBoolExpr(rp, &cond_scope.base, cond_expr, .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.Condition{ .base = .{ .parent = scope, .id = .Condition, }, }; defer cond_scope.deinit(); const cond_expr = @ptrCast(*const ZigClangExpr, ZigClangWhileStmt_getCond(stmt)); while_node.condition = try transBoolExpr(rp, &cond_scope.base, cond_expr, .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); _ = try appendToken(rp.c, .Semicolon, ";"); 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, }; // if (!cond) break; const if_node = try transCreateNodeIf(rp.c); var cond_scope = Scope.Condition{ .base = .{ .parent = scope, .id = .Condition, }, }; defer cond_scope.deinit(); const prefix_op = try transCreateNodeSimplePrefixOp(rp.c, .BoolNot, .Bang, "!"); prefix_op.rhs = try transBoolExpr(rp, &cond_scope.base, @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, null)).base; _ = try appendToken(rp.c, .Semicolon, ";"); 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: } const node = try transStmt(rp, &loop_scope, ZigClangDoStmt_getBody(stmt), .unused, .r_value); break :blk node.castTag(.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 rp.c.createBlock(2); block.statements_len = 1; // over-allocated so we can add another below block.statements()[0] = try transStmt(rp, &loop_scope, ZigClangDoStmt_getBody(stmt), .unused, .r_value); break :blk block; }; // In both cases above, we reserved 1 extra statement. body_node.statements_len += 1; body_node.statements()[body_node.statements_len - 1] = &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_scope: ?Scope.Block = null; defer if (block_scope) |*bs| bs.deinit(); if (ZigClangForStmt_getInit(stmt)) |init| { block_scope = try Scope.Block.init(rp.c, scope, false); loop_scope.parent = &block_scope.?.base; const init_node = try transStmt(rp, &block_scope.?.base, init, .unused, .r_value); try block_scope.?.statements.append(init_node); } var cond_scope = Scope.Condition{ .base = .{ .parent = &loop_scope, .id = .Condition, }, }; defer cond_scope.deinit(); const while_node = try transCreateNodeWhile(rp.c); while_node.condition = if (ZigClangForStmt_getCond(stmt)) |cond| try transBoolExpr(rp, &cond_scope.base, 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.base, 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) |*bs| { try bs.statements.append(&while_node.base); return try bs.complete(rp.c); } else { _ = try appendToken(rp.c, .Semicolon, ";"); return &while_node.base; } } fn getSwitchCaseCount(stmt: *const ZigClangSwitchStmt) usize { const body = ZigClangSwitchStmt_getBody(stmt); assert(ZigClangStmt_getStmtClass(body) == .CompoundStmtClass); const comp = @ptrCast(*const ZigClangCompoundStmt, body); // TODO https://github.com/ziglang/zig/issues/1738 // return ZigClangCompoundStmt_body_end(comp) - ZigClangCompoundStmt_body_begin(comp); const start_addr = @ptrToInt(ZigClangCompoundStmt_body_begin(comp)); const end_addr = @ptrToInt(ZigClangCompoundStmt_body_end(comp)); return (end_addr - start_addr) / @sizeOf(*ZigClangStmt); } fn transSwitch( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangSwitchStmt, ) TransError!*ast.Node { const switch_tok = try appendToken(rp.c, .Keyword_switch, "switch"); _ = try appendToken(rp.c, .LParen, "("); const cases_len = getSwitchCaseCount(stmt); var cond_scope = Scope.Condition{ .base = .{ .parent = scope, .id = .Condition, }, }; defer cond_scope.deinit(); const switch_expr = try transExpr(rp, &cond_scope.base, ZigClangSwitchStmt_getCond(stmt), .used, .r_value); _ = try appendToken(rp.c, .RParen, ")"); _ = try appendToken(rp.c, .LBrace, "{"); // reserve +1 case in case there is no default case const switch_node = try ast.Node.Switch.alloc(rp.c.arena, cases_len + 1); switch_node.* = .{ .switch_token = switch_tok, .expr = switch_expr, .cases_len = cases_len + 1, .rbrace = try appendToken(rp.c, .RBrace, "}"), }; var switch_scope = Scope.Switch{ .base = .{ .id = .Switch, .parent = scope, }, .cases = switch_node.cases(), .case_index = 0, .pending_block = undefined, .default_label = null, .switch_label = null, }; // tmp block that all statements will go before being picked up by a case or default var block_scope = try Scope.Block.init(rp.c, &switch_scope.base, false); defer block_scope.deinit(); // Note that we do not defer a deinit here; the switch_scope.pending_block field // has its own memory management. This resource is freed inside `transCase` and // then the final pending_block is freed at the bottom of this function with // pending_block.deinit(). switch_scope.pending_block = try Scope.Block.init(rp.c, scope, false); try switch_scope.pending_block.statements.append(&switch_node.base); const last = try transStmt(rp, &block_scope.base, ZigClangSwitchStmt_getBody(stmt), .unused, .r_value); _ = try appendToken(rp.c, .Semicolon, ";"); // take all pending statements const last_block_stmts = last.cast(ast.Node.Block).?.statements(); try switch_scope.pending_block.statements.ensureCapacity( switch_scope.pending_block.statements.items.len + last_block_stmts.len, ); for (last_block_stmts) |n| { switch_scope.pending_block.statements.appendAssumeCapacity(n); } if (switch_scope.default_label == null) { switch_scope.switch_label = try block_scope.makeMangledName(rp.c, "switch"); } if (switch_scope.switch_label) |l| { switch_scope.pending_block.label = try appendIdentifier(rp.c, l); _ = try appendToken(rp.c, .Colon, ":"); } if (switch_scope.default_label == null) { const else_prong = try transCreateNodeSwitchCase(rp.c, try transCreateNodeSwitchElse(rp.c)); else_prong.expr = blk: { var br = try CtrlFlow.init(rp.c, .Break, switch_scope.switch_label.?); break :blk &(try br.finish(null)).base; }; _ = try appendToken(rp.c, .Comma, ","); if (switch_scope.case_index >= switch_scope.cases.len) return revertAndWarn(rp, error.UnsupportedTranslation, ZigClangStmt_getBeginLoc(@ptrCast(*const ZigClangStmt, stmt)), "TODO complex switch cases", .{}); switch_scope.cases[switch_scope.case_index] = &else_prong.base; switch_scope.case_index += 1; } // We overallocated in case there was no default, so now we correct // the number of cases in the AST node. switch_node.cases_len = switch_scope.case_index; const result_node = try switch_scope.pending_block.complete(rp.c); switch_scope.pending_block.deinit(); return result_node; } 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 block_scope.makeMangledName(rp.c, "case"); _ = 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.arena.create(ast.Node.SimpleInfixOp); node.* = .{ .base = .{ .tag = .Range }, .op_token = ellips, .lhs = lhs_node, .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 = blk: { var br = try CtrlFlow.init(rp.c, .Break, label); break :blk &(try br.finish(null)).base; }; _ = try appendToken(rp.c, .Comma, ","); if (switch_scope.case_index >= switch_scope.cases.len) return revertAndWarn(rp, error.UnsupportedTranslation, ZigClangStmt_getBeginLoc(@ptrCast(*const ZigClangStmt, stmt)), "TODO complex switch cases", .{}); switch_scope.cases[switch_scope.case_index] = &switch_prong.base; switch_scope.case_index += 1; switch_scope.pending_block.label = try appendIdentifier(rp.c, label); _ = try appendToken(rp.c, .Colon, ":"); // take all pending statements try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items); block_scope.statements.shrink(0); const pending_node = try switch_scope.pending_block.complete(rp.c); switch_scope.pending_block.deinit(); switch_scope.pending_block = try Scope.Block.init(rp.c, scope, false); try switch_scope.pending_block.statements.append(pending_node); 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(); switch_scope.default_label = try block_scope.makeMangledName(rp.c, "default"); _ = try appendToken(rp.c, .Semicolon, ";"); const else_prong = try transCreateNodeSwitchCase(rp.c, try transCreateNodeSwitchElse(rp.c)); else_prong.expr = blk: { var br = try CtrlFlow.init(rp.c, .Break, switch_scope.default_label.?); break :blk &(try br.finish(null)).base; }; _ = try appendToken(rp.c, .Comma, ","); if (switch_scope.case_index >= switch_scope.cases.len) return revertAndWarn(rp, error.UnsupportedTranslation, ZigClangStmt_getBeginLoc(@ptrCast(*const ZigClangStmt, stmt)), "TODO complex switch cases", .{}); switch_scope.cases[switch_scope.case_index] = &else_prong.base; switch_scope.case_index += 1; switch_scope.pending_block.label = try appendIdentifier(rp.c, switch_scope.default_label.?); _ = try appendToken(rp.c, .Colon, ":"); // take all pending statements try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items); block_scope.statements.shrink(0); const pending_node = try switch_scope.pending_block.complete(rp.c); switch_scope.pending_block.deinit(); switch_scope.pending_block = try Scope.Block.init(rp.c, scope, false); try switch_scope.pending_block.statements.append(pending_node); 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", .{}); var val_node: ?*ast.Node = null; switch (ZigClangAPValue_getKind(&result.Val)) { .Int => { // See comment in `transIntegerLiteral` for why this code is here. // @as(T, x) const expr_base = @ptrCast(*const ZigClangExpr, expr); const as_node = try rp.c.createBuiltinCall("@as", 2); const ty_node = try transQualType(rp, ZigClangExpr_getType(expr_base), ZigClangExpr_getBeginLoc(expr_base)); as_node.params()[0] = ty_node; _ = try appendToken(rp.c, .Comma, ","); const int_lit_node = try transCreateNodeAPInt(rp.c, ZigClangAPValue_getInt(&result.Val)); as_node.params()[1] = int_lit_node; as_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return maybeSuppressResult(rp, scope, used, &as_node.base); }, else => { return revertAndWarn(rp, error.UnsupportedTranslation, ZigClangExpr_getBeginLoc(expr), "unsupported constant expression kind", .{}); }, } } 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, suppress_as: SuppressCast, ) TransError!*ast.Node { const kind = ZigClangCharacterLiteral_getKind(stmt); const int_lit_node = switch (kind) { .Ascii, .UTF8 => blk: { const val = ZigClangCharacterLiteral_getValue(stmt); if (kind == .Ascii) { // C has a somewhat obscure feature called multi-character character // constant if (val > 255) break :blk try 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.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .CharLiteral }, .token = token, }; break :blk &node.base; }, .UTF16, .UTF32, .Wide => return revertAndWarn( rp, error.UnsupportedTranslation, ZigClangStmt_getBeginLoc(@ptrCast(*const ZigClangStmt, stmt)), "TODO: support character literal kind {}", .{kind}, ), }; if (suppress_as == .no_as) { return maybeSuppressResult(rp, scope, result_used, int_lit_node); } // See comment in `transIntegerLiteral` for why this code is here. // @as(T, x) const expr_base = @ptrCast(*const ZigClangExpr, stmt); const as_node = try rp.c.createBuiltinCall("@as", 2); const ty_node = try transQualType(rp, ZigClangExpr_getType(expr_base), ZigClangExpr_getBeginLoc(expr_base)); as_node.params()[0] = ty_node; _ = try appendToken(rp.c, .Comma, ","); as_node.params()[1] = int_lit_node; as_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return maybeSuppressResult(rp, scope, result_used, &as_node.base); } 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, "("); var block_scope = try Scope.Block.init(rp.c, scope, true); defer block_scope.deinit(); 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); try block_scope.statements.append(result); } const break_node = blk: { var tmp = try CtrlFlow.init(rp.c, .Break, "blk"); const rhs = try transStmt(rp, &block_scope.base, it[0], .used, .r_value); break :blk try tmp.finish(rhs); }; _ = try appendToken(rp.c, .Semicolon, ";"); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(rp.c); const rparen = try appendToken(rp.c, .RParen, ")"); const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = lparen, .expr = block_node, .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 member_decl = ZigClangMemberExpr_getMemberDecl(stmt); const name = blk: { const decl_kind = ZigClangDecl_getKind(@ptrCast(*const ZigClangDecl, member_decl)); // If we're referring to a anonymous struct/enum find the bogus name // we've assigned to it during the RecordDecl translation if (decl_kind == .Field) { const field_decl = @ptrCast(*const struct_ZigClangFieldDecl, member_decl); if (ZigClangFieldDecl_isAnonymousStructOrUnion(field_decl)) { const name = rp.c.decl_table.get(@ptrToInt(ZigClangFieldDecl_getCanonicalDecl(field_decl))).?; break :blk try mem.dupe(rp.c.arena, u8, name); } } const decl = @ptrCast(*const ZigClangNamedDecl, member_decl); break :blk try rp.c.str(ZigClangNamedDecl_getName_bytes_begin(decl)); }; 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 { var base_stmt = ZigClangArraySubscriptExpr_getBase(stmt); // Unwrap the base statement if it's an array decayed to a bare pointer type // so that we index the array itself if (ZigClangStmt_getStmtClass(@ptrCast(*const ZigClangStmt, base_stmt)) == .ImplicitCastExprClass) { const implicit_cast = @ptrCast(*const ZigClangImplicitCastExpr, base_stmt); if (ZigClangImplicitCastExpr_getCastKind(implicit_cast) == .ArrayToPointerDecay) { base_stmt = ZigClangImplicitCastExpr_getSubExpr(implicit_cast); } } const container_node = try transExpr(rp, scope, base_stmt, .used, .r_value); const node = try transCreateNodeArrayAccess(rp.c, container_node); // cast if the index is long long or signed const subscr_expr = ZigClangArraySubscriptExpr_getIdx(stmt); const qt = getExprQualType(rp.c, subscr_expr); const is_longlong = cIsLongLongInteger(qt); const is_signed = cIsSignedInteger(qt); if (is_longlong or is_signed) { const cast_node = try rp.c.createBuiltinCall("@intCast", 2); // check if long long first so that signed long long doesn't just become unsigned long long var typeid_node = if (is_longlong) try transCreateNodeIdentifier(rp.c, "usize") else try transQualTypeIntWidthOf(rp.c, qt, false); cast_node.params()[0] = typeid_node; _ = try appendToken(rp.c, .Comma, ","); cast_node.params()[1] = try transExpr(rp, scope, subscr_expr, .used, .r_value); cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); node.rtoken = try appendToken(rp.c, .RBrace, "]"); node.index_expr = &cast_node.base; } else { node.index_expr = try transExpr(rp, scope, subscr_expr, .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 num_args = ZigClangCallExpr_getNumArgs(stmt); const node = try rp.c.createCall(fn_expr, num_args); const call_params = node.params(); const args = ZigClangCallExpr_getArgs(stmt); var i: usize = 0; while (i < num_args) : (i += 1) { if (i != 0) { _ = try appendToken(rp.c, .Comma, ","); } call_params[i] = try transExpr(rp, scope, args[i], .used, .r_value); } node.rtoken = try appendToken(rp.c, .RParen, ")"); if (fn_ty) |ty| { const canon = ZigClangQualType_getCanonicalType(ty.getReturnType()); 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); } const ClangFunctionType = union(enum) { Proto: *const ZigClangFunctionProtoType, NoProto: *const ZigClangFunctionType, fn getReturnType(self: @This()) ZigClangQualType { switch (@as(@TagType(@This()), self)) { .Proto => return ZigClangFunctionProtoType_getReturnType(self.Proto), .NoProto => return ZigClangFunctionType_getReturnType(self.NoProto), } } }; fn qualTypeGetFnProto(qt: ZigClangQualType, is_ptr: *bool) ?ClangFunctionType { 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 ClangFunctionType{ .Proto = @ptrCast(*const ZigClangFunctionProtoType, ty) }; } if (ZigClangType_getTypeClass(ty) == .FunctionNoProto) { return ClangFunctionType{ .NoProto = @ptrCast(*const ZigClangFunctionType, ty) }; } return null; } fn transUnaryExprOrTypeTraitExpr( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnaryExprOrTypeTraitExpr, result_used: ResultUsed, ) TransError!*ast.Node { const loc = ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(stmt); const type_node = try transQualType( rp, ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(stmt), loc, ); const kind = ZigClangUnaryExprOrTypeTraitExpr_getKind(stmt); const kind_str = switch (kind) { .SizeOf => "@sizeOf", .AlignOf => "@alignOf", .PreferredAlignOf, .VecStep, .OpenMPRequiredSimdAlign, => return revertAndWarn( rp, error.UnsupportedTranslation, loc, "Unsupported type trait kind {}", .{kind}, ), }; const builtin_node = try rp.c.createBuiltinCall(kind_str, 1); builtin_node.params()[0] = 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 (cIsUnsignedInteger(qt)) { // unsigned integer overflow wraps around. return true; } else { // float, signed integer, and pointer overflow is undefined behavior. return false; } } 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 transCreateNodeSimplePrefixOp(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 transCreateNodeSimplePrefixOp(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 transCreateNodeSimplePrefixOp(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 transCreateNodeSimplePrefixOp(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.Tag, 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); if (scope.id != .Condition) _ = 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: }) var block_scope = try Scope.Block.init(rp.c, scope, true); defer block_scope.deinit(); const ref = try block_scope.makeMangledName(rp.c, "ref"); const mut_tok = try appendToken(rp.c, .Keyword_const, "const"); const name_tok = try appendIdentifier(rp.c, ref); const eq_token = try appendToken(rp.c, .Equal, "="); const rhs_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); rhs_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value); const init_node = &rhs_node.base; const semicolon_token = try appendToken(rp.c, .Semicolon, ";"); const node = try ast.Node.VarDecl.create(rp.c.arena, .{ .name_token = name_tok, .mut_token = mut_tok, .semicolon_token = semicolon_token, }, .{ .eq_token = eq_token, .init_node = init_node, }); try block_scope.statements.append(&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.statements.append(assign); const break_node = try transCreateNodeBreak(rp.c, block_scope.label, ref_node); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(rp.c); // 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.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = try appendToken(rp.c, .LParen, "("), .expr = block_node, .rparen = try appendToken(rp.c, .RParen, ")"), }; return &grouped_expr.base; } fn transCreatePostCrement( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnaryOperator, op: ast.Node.Tag, 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); if (scope.id != .Condition) _ = 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: }) var block_scope = try Scope.Block.init(rp.c, scope, true); defer block_scope.deinit(); const ref = try block_scope.makeMangledName(rp.c, "ref"); const mut_tok = try appendToken(rp.c, .Keyword_const, "const"); const name_tok = try appendIdentifier(rp.c, ref); const eq_token = try appendToken(rp.c, .Equal, "="); const rhs_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); rhs_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value); const init_node = &rhs_node.base; const semicolon_token = try appendToken(rp.c, .Semicolon, ";"); const node = try ast.Node.VarDecl.create(rp.c.arena, .{ .name_token = name_tok, .mut_token = mut_tok, .semicolon_token = semicolon_token, }, .{ .eq_token = eq_token, .init_node = init_node, }); try block_scope.statements.append(&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 block_scope.makeMangledName(rp.c, "tmp"); const tmp_mut_tok = try appendToken(rp.c, .Keyword_const, "const"); const tmp_name_tok = try appendIdentifier(rp.c, tmp); const tmp_eq_token = try appendToken(rp.c, .Equal, "="); const tmp_init_node = ref_node; const tmp_semicolon_token = try appendToken(rp.c, .Semicolon, ";"); const tmp_node = try ast.Node.VarDecl.create(rp.c.arena, .{ .name_token = tmp_name_tok, .mut_token = tmp_mut_tok, .semicolon_token = semicolon_token, }, .{ .eq_token = tmp_eq_token, .init_node = tmp_init_node, }); try block_scope.statements.append(&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.statements.append(assign); const break_node = blk: { var tmp_ctrl_flow = try CtrlFlow.initToken(rp.c, .Break, block_scope.label); const rhs = try transCreateNodeIdentifier(rp.c, tmp); break :blk try tmp_ctrl_flow.finish(rhs); }; try block_scope.statements.append(&break_node.base); _ = try appendToken(rp.c, .Semicolon, ";"); const block_node = try block_scope.complete(rp.c); const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = try appendToken(rp.c, .LParen, "("), .expr = block_node, .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), .DivAssign => return transCreateCompoundAssign(rp, scope, stmt, .AssignDiv, .SlashEqual, "/=", .Div, .Slash, "/", used), .RemAssign => return transCreateCompoundAssign(rp, scope, stmt, .AssignMod, .PercentEqual, "%=", .Mod, .Percent, "%", 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.Tag, assign_tok_id: std.zig.Token.Id, assign_bytes: []const u8, bin_op: ast.Node.Tag, 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 is_div = bin_op == .Div; const is_mod = bin_op == .Mod; const lhs = ZigClangCompoundAssignOperator_getLHS(stmt); const rhs = ZigClangCompoundAssignOperator_getRHS(stmt); const loc = ZigClangCompoundAssignOperator_getBeginLoc(stmt); const lhs_qt = getExprQualType(rp.c, lhs); const rhs_qt = getExprQualType(rp.c, rhs); const is_signed = cIsSignedInteger(lhs_qt); const requires_int_cast = blk: { const are_integers = cIsInteger(lhs_qt) and cIsInteger(rhs_qt); const are_same_sign = cIsSignedInteger(lhs_qt) == cIsSignedInteger(rhs_qt); break :blk are_integers and !are_same_sign; }; if (used == .unused) { // common case // c: lhs += rhs // zig: lhs += rhs if ((is_mod or is_div) and is_signed) { const op_token = try appendToken(rp.c, .Equal, "="); const op_node = try rp.c.arena.create(ast.Node.SimpleInfixOp); const builtin = if (is_mod) "@rem" else "@divTrunc"; const builtin_node = try rp.c.createBuiltinCall(builtin, 2); const lhs_node = try transExpr(rp, scope, lhs, .used, .l_value); builtin_node.params()[0] = lhs_node; _ = try appendToken(rp.c, .Comma, ","); builtin_node.params()[1] = try transExpr(rp, scope, rhs, .used, .r_value); builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); op_node.* = .{ .base = .{ .tag = .Assign }, .op_token = op_token, .lhs = lhs_node, .rhs = &builtin_node.base, }; _ = try appendToken(rp.c, .Semicolon, ";"); return &op_node.base; } 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 = if (is_shift or requires_int_cast) try transExprCoercing(rp, scope, rhs, .used, .r_value) else try transExpr(rp, scope, rhs, .used, .r_value); if (is_shift or requires_int_cast) { const cast_node = try rp.c.createBuiltinCall("@intCast", 2); const cast_to_type = if (is_shift) try qualTypeToLog2IntRef(rp, getExprQualType(rp.c, rhs), loc) else try transQualType(rp, getExprQualType(rp.c, lhs), loc); cast_node.params()[0] = cast_to_type; _ = try appendToken(rp.c, .Comma, ","); cast_node.params()[1] = rhs_node; cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); rhs_node = &cast_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: }) var block_scope = try Scope.Block.init(rp.c, scope, true); defer block_scope.deinit(); const ref = try block_scope.makeMangledName(rp.c, "ref"); const mut_tok = try appendToken(rp.c, .Keyword_const, "const"); const name_tok = try appendIdentifier(rp.c, ref); const eq_token = try appendToken(rp.c, .Equal, "="); const addr_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); addr_node.rhs = try transExpr(rp, scope, lhs, .used, .l_value); const init_node = &addr_node.base; const semicolon_token = try appendToken(rp.c, .Semicolon, ";"); const node = try ast.Node.VarDecl.create(rp.c.arena, .{ .name_token = name_tok, .mut_token = mut_tok, .semicolon_token = semicolon_token, }, .{ .eq_token = eq_token, .init_node = init_node, }); try block_scope.statements.append(&node.base); const lhs_node = try transCreateNodeIdentifier(rp.c, ref); const ref_node = try transCreateNodePtrDeref(rp.c, lhs_node); _ = try appendToken(rp.c, .Semicolon, ";"); if ((is_mod or is_div) and is_signed) { const op_token = try appendToken(rp.c, .Equal, "="); const op_node = try rp.c.arena.create(ast.Node.SimpleInfixOp); const builtin = if (is_mod) "@rem" else "@divTrunc"; const builtin_node = try rp.c.createBuiltinCall(builtin, 2); builtin_node.params()[0] = try transCreateNodePtrDeref(rp.c, lhs_node); _ = try appendToken(rp.c, .Comma, ","); builtin_node.params()[1] = try transExpr(rp, scope, rhs, .used, .r_value); builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); _ = try appendToken(rp.c, .Semicolon, ";"); op_node.* = .{ .base = .{ .tag = .Assign }, .op_token = op_token, .lhs = ref_node, .rhs = &builtin_node.base, }; _ = try appendToken(rp.c, .Semicolon, ";"); try block_scope.statements.append(&op_node.base); } else { 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 or requires_int_cast) { const cast_node = try rp.c.createBuiltinCall("@intCast", 2); const cast_to_type = if (is_shift) try qualTypeToLog2IntRef(rp, getExprQualType(rp.c, rhs), loc) else try transQualType(rp, getExprQualType(rp.c, lhs), loc); cast_node.params()[0] = cast_to_type; _ = try appendToken(rp.c, .Comma, ","); cast_node.params()[1] = rhs_node; cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); rhs_node = &cast_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 ass_eq_token = try appendToken(rp.c, .Equal, "="); const assign = try transCreateNodeInfixOp(rp, scope, ref_node, .Assign, ass_eq_token, rhs_bin, .used, false); try block_scope.statements.append(assign); } const break_node = try transCreateNodeBreak(rp.c, block_scope.label, ref_node); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(rp.c); const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = try appendToken(rp.c, .LParen, "("), .expr = block_node, .rparen = try appendToken(rp.c, .RParen, ")"), }; return &grouped_expr.base; } fn transCPtrCast( rp: RestorePoint, loc: ZigClangSourceLocation, dst_type: ZigClangQualType, src_type: ZigClangQualType, expr: *ast.Node, ) !*ast.Node { const ty = ZigClangQualType_getTypePtr(dst_type); const child_type = ZigClangType_getPointeeType(ty); const src_ty = ZigClangQualType_getTypePtr(src_type); const src_child_type = ZigClangType_getPointeeType(src_ty); if ((ZigClangQualType_isConstQualified(src_child_type) and !ZigClangQualType_isConstQualified(child_type)) or (ZigClangQualType_isVolatileQualified(src_child_type) and !ZigClangQualType_isVolatileQualified(child_type))) { // Casting away const or volatile requires us to use @intToPtr const inttoptr_node = try rp.c.createBuiltinCall("@intToPtr", 2); const dst_type_node = try transType(rp, ty, loc); inttoptr_node.params()[0] = dst_type_node; _ = try appendToken(rp.c, .Comma, ","); const ptrtoint_node = try rp.c.createBuiltinCall("@ptrToInt", 1); ptrtoint_node.params()[0] = expr; ptrtoint_node.rparen_token = try appendToken(rp.c, .RParen, ")"); inttoptr_node.params()[1] = &ptrtoint_node.base; inttoptr_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return &inttoptr_node.base; } else { // Implicit downcasting from higher to lower alignment values is forbidden, // use @alignCast to side-step this problem const ptrcast_node = try rp.c.createBuiltinCall("@ptrCast", 2); const dst_type_node = try transType(rp, ty, loc); ptrcast_node.params()[0] = dst_type_node; _ = try appendToken(rp.c, .Comma, ","); if (ZigClangType_isVoidType(qualTypeCanon(child_type))) { // void has 1-byte alignment, so @alignCast is not needed ptrcast_node.params()[1] = expr; } else if (typeIsOpaque(rp.c, qualTypeCanon(child_type), loc)) { // For opaque types a ptrCast is enough ptrcast_node.params()[1] = expr; } else { const aligncast_node = try rp.c.createBuiltinCall("@alignCast", 2); const alignof_node = try rp.c.createBuiltinCall("@alignOf", 1); const child_type_node = try transQualType(rp, child_type, loc); alignof_node.params()[0] = child_type_node; alignof_node.rparen_token = try appendToken(rp.c, .RParen, ")"); aligncast_node.params()[0] = &alignof_node.base; _ = try appendToken(rp.c, .Comma, ","); aligncast_node.params()[1] = expr; aligncast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); ptrcast_node.params()[1] = &aligncast_node.base; } ptrcast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); return &ptrcast_node.base; } } fn transBreak(rp: RestorePoint, scope: *Scope) TransError!*ast.Node { const break_scope = scope.getBreakableScope(); const label_text: ?[]const u8 = if (break_scope.id == .Switch) blk: { const swtch = @fieldParentPtr(Scope.Switch, "base", break_scope); const block_scope = try scope.findBlockScope(rp.c); swtch.switch_label = try block_scope.makeMangledName(rp.c, "switch"); break :blk swtch.switch_label; } else null; var cf = try CtrlFlow.init(rp.c, .Break, label_text); const br = try cf.finish(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.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .FloatLiteral }, .token = try appendTokenFmt(rp.c, .FloatLiteral, "{d}", .{dbl}), }; return maybeSuppressResult(rp, scope, used, &node.base); } fn transBinaryConditionalOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangBinaryConditionalOperator, used: ResultUsed) TransError!*ast.Node { // GNU extension of the ternary operator where the middle expression is // omitted, the conditition itself is returned if it evaluates to true const casted_stmt = @ptrCast(*const ZigClangAbstractConditionalOperator, stmt); const cond_expr = ZigClangAbstractConditionalOperator_getCond(casted_stmt); const true_expr = ZigClangAbstractConditionalOperator_getTrueExpr(casted_stmt); const false_expr = ZigClangAbstractConditionalOperator_getFalseExpr(casted_stmt); // c: (cond_expr)?:(false_expr) // zig: (blk: { // const _cond_temp = (cond_expr); // break :blk if (_cond_temp) _cond_temp else (false_expr); // }) const lparen = try appendToken(rp.c, .LParen, "("); var block_scope = try Scope.Block.init(rp.c, scope, true); defer block_scope.deinit(); const mangled_name = try block_scope.makeMangledName(rp.c, "cond_temp"); const mut_tok = try appendToken(rp.c, .Keyword_const, "const"); const name_tok = try appendIdentifier(rp.c, mangled_name); const eq_token = try appendToken(rp.c, .Equal, "="); const init_node = try transExpr(rp, &block_scope.base, cond_expr, .used, .r_value); const semicolon_token = try appendToken(rp.c, .Semicolon, ";"); const tmp_var = try ast.Node.VarDecl.create(rp.c.arena, .{ .name_token = name_tok, .mut_token = mut_tok, .semicolon_token = semicolon_token, }, .{ .eq_token = eq_token, .init_node = init_node, }); try block_scope.statements.append(&tmp_var.base); var break_node_tmp = try CtrlFlow.initToken(rp.c, .Break, block_scope.label); const if_node = try transCreateNodeIf(rp.c); var cond_scope = Scope.Condition{ .base = .{ .parent = &block_scope.base, .id = .Condition, }, }; defer cond_scope.deinit(); const tmp_var_node = try transCreateNodeIdentifier(rp.c, mangled_name); const ty = ZigClangQualType_getTypePtr(getExprQualType(rp.c, cond_expr)); const cond_node = try finishBoolExpr(rp, &cond_scope.base, ZigClangExpr_getBeginLoc(cond_expr), ty, tmp_var_node, used); if_node.condition = cond_node; _ = try appendToken(rp.c, .RParen, ")"); if_node.body = try transCreateNodeIdentifier(rp.c, mangled_name); if_node.@"else" = try transCreateNodeElse(rp.c); if_node.@"else".?.body = try transExpr(rp, &block_scope.base, false_expr, .used, .r_value); _ = try appendToken(rp.c, .Semicolon, ";"); const break_node = try break_node_tmp.finish(&if_node.base); _ = try appendToken(rp.c, .Semicolon, ";"); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(rp.c); const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = lparen, .expr = block_node, .rparen = try appendToken(rp.c, .RParen, ")"), }; return maybeSuppressResult(rp, scope, used, &grouped_expr.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.Condition{ .base = .{ .parent = scope, .id = .Condition, }, }; defer cond_scope.deinit(); const casted_stmt = @ptrCast(*const ZigClangAbstractConditionalOperator, stmt); const cond_expr = ZigClangAbstractConditionalOperator_getCond(casted_stmt); const true_expr = ZigClangAbstractConditionalOperator_getTrueExpr(casted_stmt); const false_expr = ZigClangAbstractConditionalOperator_getFalseExpr(casted_stmt); if_node.condition = try transBoolExpr(rp, &cond_scope.base, 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.arena.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, used: ResultUsed, result: *ast.Node, ) TransError!*ast.Node { if (used == .used) return result; 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.arena.create(ast.Node.SimpleInfixOp); op_node.* = .{ .base = .{ .tag = .Assign }, .op_token = op_token, .lhs = lhs, .rhs = result, }; return &op_node.base; } fn addTopLevelDecl(c: *Context, name: []const u8, decl_node: *ast.Node) !void { try c.root_decls.append(c.gpa, decl_node); _ = try c.global_scope.sym_table.put(name, decl_node); } fn transQualType(rp: RestorePoint, qt: ZigClangQualType, source_loc: ZigClangSourceLocation) TypeError!*ast.Node { return transType(rp, ZigClangQualType_getTypePtr(qt), source_loc); } /// Produces a Zig AST node by translating a Clang QualType, respecting the width, but modifying the signed-ness. /// Asserts the type is an integer. fn transQualTypeIntWidthOf(c: *Context, ty: ZigClangQualType, is_signed: bool) TypeError!*ast.Node { return transTypeIntWidthOf(c, qualTypeCanon(ty), is_signed); } /// Produces a Zig AST node by translating a Clang Type, respecting the width, but modifying the signed-ness. /// Asserts the type is an integer. fn transTypeIntWidthOf(c: *Context, ty: *const ZigClangType, is_signed: bool) TypeError!*ast.Node { assert(ZigClangType_getTypeClass(ty) == .Builtin); const builtin_ty = @ptrCast(*const ZigClangBuiltinType, ty); return transCreateNodeIdentifier(c, switch (ZigClangBuiltinType_getKind(builtin_ty)) { .Char_U, .Char_S, .UChar, .SChar, .Char8 => if (is_signed) "i8" else "u8", .UShort, .Short => if (is_signed) "c_short" else "c_ushort", .UInt, .Int => if (is_signed) "c_int" else "c_uint", .ULong, .Long => if (is_signed) "c_long" else "c_ulong", .ULongLong, .LongLong => if (is_signed) "c_longlong" else "c_ulonglong", .UInt128, .Int128 => if (is_signed) "i128" else "u128", .Char16 => if (is_signed) "i16" else "u16", .Char32 => if (is_signed) "i32" else "u32", else => unreachable, // only call this function when it has already been determined the type is int }); } fn isCBuiltinType(qt: ZigClangQualType, kind: ZigClangBuiltinTypeKind) bool { const c_type = qualTypeCanon(qt); if (ZigClangType_getTypeClass(c_type) != .Builtin) return false; const builtin_ty = @ptrCast(*const ZigClangBuiltinType, c_type); return ZigClangBuiltinType_getKind(builtin_ty) == kind; } fn qualTypeIsPtr(qt: ZigClangQualType) bool { return ZigClangType_getTypeClass(qualTypeCanon(qt)) == .Pointer; } fn qualTypeIsBoolean(qt: ZigClangQualType) bool { return ZigClangType_isBooleanType(qualTypeCanon(qt)); } 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(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, 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 = math.log2_int(u64, int_bit_width); const node = try rp.c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .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 rp.c.createBuiltinCall("@import", 1); const std_token = try appendToken(rp.c, .StringLiteral, "\"std\""); const std_node = try rp.c.arena.create(ast.Node.OneToken); std_node.* = .{ .base = .{ .tag = .StringLiteral }, .token = std_token, }; import_fn_call.params()[0] = &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 rp.c.createCall(outer_field_access, 1); log2int_fn_call.params()[0] = zig_type_node; log2int_fn_call.rtoken = try appendToken(rp.c, .RParen, ")"); return &log2int_fn_call.base; } fn qualTypeChildIsFnProto(qt: ZigClangQualType) bool { const ty = qualTypeCanon(qt); switch (ZigClangType_getTypeClass(ty)) { .FunctionProto, .FunctionNoProto => return true, else => return false, } } fn qualTypeCanon(qt: ZigClangQualType) *const ZigClangType { const canon = ZigClangQualType_getCanonicalType(qt); return ZigClangQualType_getTypePtr(canon); } fn getExprQualType(c: *Context, expr: *const ZigClangExpr) ZigClangQualType { blk: { // If this is a C `char *`, turn it into a `const char *` if (ZigClangExpr_getStmtClass(expr) != .ImplicitCastExprClass) break :blk; const cast_expr = @ptrCast(*const ZigClangImplicitCastExpr, expr); if (ZigClangImplicitCastExpr_getCastKind(cast_expr) != .ArrayToPointerDecay) break :blk; const sub_expr = ZigClangImplicitCastExpr_getSubExpr(cast_expr); if (ZigClangExpr_getStmtClass(sub_expr) != .StringLiteralClass) break :blk; const array_qt = ZigClangExpr_getType(sub_expr); const array_type = @ptrCast(*const ZigClangArrayType, ZigClangQualType_getTypePtr(array_qt)); var pointee_qt = ZigClangArrayType_getElementType(array_type); ZigClangQualType_addConst(&pointee_qt); return ZigClangASTContext_getPointerType(c.clang_context, pointee_qt); } return ZigClangExpr_getType(expr); } fn typeIsOpaque(c: *Context, ty: *const ZigClangType, loc: ZigClangSourceLocation) bool { switch (ZigClangType_getTypeClass(ty)) { .Builtin => { const builtin_ty = @ptrCast(*const ZigClangBuiltinType, ty); return ZigClangBuiltinType_getKind(builtin_ty) == .Void; }, .Record => { const record_ty = @ptrCast(*const ZigClangRecordType, ty); const record_decl = ZigClangRecordType_getDecl(record_ty); const record_def = ZigClangRecordDecl_getDefinition(record_decl) orelse return true; 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); if (ZigClangFieldDecl_isBitField(field_decl)) { return true; } } return false; }, .Elaborated => { const elaborated_ty = @ptrCast(*const ZigClangElaboratedType, ty); const qt = ZigClangElaboratedType_getNamedType(elaborated_ty); return typeIsOpaque(c, ZigClangQualType_getTypePtr(qt), loc); }, .Typedef => { const typedef_ty = @ptrCast(*const ZigClangTypedefType, ty); const typedef_decl = ZigClangTypedefType_getDecl(typedef_ty); const underlying_type = ZigClangTypedefNameDecl_getUnderlyingType(typedef_decl); return typeIsOpaque(c, ZigClangQualType_getTypePtr(underlying_type), loc); }, else => return false, } } fn cIsInteger(qt: ZigClangQualType) bool { return cIsSignedInteger(qt) or cIsUnsignedInteger(qt); } fn cIsUnsignedInteger(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)) { .Char_U, .UChar, .Char_S, .UShort, .UInt, .ULong, .ULongLong, .UInt128, .WChar_U, => true, else => false, }; } fn cIntTypeToIndex(qt: ZigClangQualType) u8 { const c_type = qualTypeCanon(qt); assert(ZigClangType_getTypeClass(c_type) == .Builtin); const builtin_ty = @ptrCast(*const ZigClangBuiltinType, c_type); return switch (ZigClangBuiltinType_getKind(builtin_ty)) { .Bool, .Char_U, .Char_S, .UChar, .SChar, .Char8 => 1, .WChar_U, .WChar_S => 2, .UShort, .Short, .Char16 => 3, .UInt, .Int, .Char32 => 4, .ULong, .Long => 5, .ULongLong, .LongLong => 6, .UInt128, .Int128 => 7, else => unreachable, }; } fn cIntTypeCmp(a: ZigClangQualType, b: ZigClangQualType) math.Order { const a_index = cIntTypeToIndex(a); const b_index = cIntTypeToIndex(b); return math.order(a_index, b_index); } 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 cIsLongLongInteger(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)) { .LongLong, .ULongLong, .Int128, .UInt128 => true, else => false, }; } fn transCreateNodeAssign( rp: RestorePoint, scope: *Scope, result_used: ResultUsed, lhs: *const ZigClangExpr, rhs: *const ZigClangExpr, ) !*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, "="); var rhs_node = try transExprCoercing(rp, scope, rhs, .used, .r_value); if (!exprIsBooleanType(lhs) and isBoolRes(rhs_node)) { const builtin_node = try rp.c.createBuiltinCall("@boolToInt", 1); builtin_node.params()[0] = 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: (blk: { // zig: const _tmp = rhs; // zig: lhs = _tmp; // zig: break :blk _tmp // zig: }) var block_scope = try Scope.Block.init(rp.c, scope, true); defer block_scope.deinit(); const tmp = try block_scope.makeMangledName(rp.c, "tmp"); const mut_tok = try appendToken(rp.c, .Keyword_const, "const"); const name_tok = try appendIdentifier(rp.c, tmp); const eq_token = try appendToken(rp.c, .Equal, "="); var rhs_node = try transExpr(rp, &block_scope.base, rhs, .used, .r_value); if (!exprIsBooleanType(lhs) and isBoolRes(rhs_node)) { const builtin_node = try rp.c.createBuiltinCall("@boolToInt", 1); builtin_node.params()[0] = rhs_node; builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); rhs_node = &builtin_node.base; } const init_node = rhs_node; const semicolon_token = try appendToken(rp.c, .Semicolon, ";"); const node = try ast.Node.VarDecl.create(rp.c.arena, .{ .name_token = name_tok, .mut_token = mut_tok, .semicolon_token = semicolon_token, }, .{ .eq_token = eq_token, .init_node = init_node, }); try block_scope.statements.append(&node.base); const lhs_node = try transExpr(rp, &block_scope.base, lhs, .used, .l_value); const lhs_eq_token = try appendToken(rp.c, .Equal, "="); const ident = try transCreateNodeIdentifier(rp.c, tmp); _ = try appendToken(rp.c, .Semicolon, ";"); const assign = try transCreateNodeInfixOp(rp, &block_scope.base, lhs_node, .Assign, lhs_eq_token, ident, .used, false); try block_scope.statements.append(assign); const break_node = blk: { var tmp_ctrl_flow = try CtrlFlow.init(rp.c, .Break, tokenSlice(rp.c, block_scope.label.?)); const rhs_expr = try transCreateNodeIdentifier(rp.c, tmp); break :blk try tmp_ctrl_flow.finish(rhs_expr); }; _ = try appendToken(rp.c, .Semicolon, ";"); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(rp.c); // semicolon must immediately follow rbrace because it is the last token in a block _ = try appendToken(rp.c, .Semicolon, ";"); return block_node; } fn transCreateNodeFieldAccess(c: *Context, container: *ast.Node, field_name: []const u8) !*ast.Node { const field_access_node = try c.arena.create(ast.Node.SimpleInfixOp); field_access_node.* = .{ .base = .{ .tag = .Period }, .op_token = try appendToken(c, .Period, "."), .lhs = container, .rhs = try transCreateNodeIdentifier(c, field_name), }; return &field_access_node.base; } fn transCreateNodeSimplePrefixOp( c: *Context, comptime tag: ast.Node.Tag, op_tok_id: std.zig.Token.Id, bytes: []const u8, ) !*ast.Node.SimplePrefixOp { const node = try c.arena.create(ast.Node.SimplePrefixOp); node.* = .{ .base = .{ .tag = tag }, .op_token = try appendToken(c, op_tok_id, bytes), .rhs = undefined, // translate and set afterward }; return node; } fn transCreateNodeInfixOp( rp: RestorePoint, scope: *Scope, lhs_node: *ast.Node, op: ast.Node.Tag, op_token: ast.TokenIndex, rhs_node: *ast.Node, used: ResultUsed, grouped: bool, ) !*ast.Node { var lparen = if (grouped) try appendToken(rp.c, .LParen, "(") else null; const node = try rp.c.arena.create(ast.Node.SimpleInfixOp); node.* = .{ .base = .{ .tag = op }, .op_token = op_token, .lhs = lhs_node, .rhs = rhs_node, }; if (!grouped) return maybeSuppressResult(rp, scope, used, &node.base); const rparen = try appendToken(rp.c, .RParen, ")"); const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = lparen.?, .expr = &node.base, .rparen = rparen, }; return maybeSuppressResult(rp, scope, used, &grouped_expr.base); } fn transCreateNodeBoolInfixOp( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangBinaryOperator, op: ast.Node.Tag, 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( c: *Context, is_const: bool, is_volatile: bool, op_tok_id: std.zig.Token.Id, ) !*ast.Node.PtrType { const node = try c.arena.create(ast.Node.PtrType); const op_token = switch (op_tok_id) { .LBracket => blk: { const lbracket = try appendToken(c, .LBracket, "["); _ = try appendToken(c, .Asterisk, "*"); _ = try appendToken(c, .RBracket, "]"); break :blk lbracket; }, .Identifier => blk: { const lbracket = try appendToken(c, .LBracket, "["); // Rendering checks if this token + 2 == .Identifier, so needs to return this token _ = try appendToken(c, .Asterisk, "*"); _ = try appendIdentifier(c, "c"); _ = try appendToken(c, .RBracket, "]"); break :blk lbracket; }, .Asterisk => try appendToken(c, .Asterisk, "*"), else => unreachable, }; node.* = .{ .op_token = op_token, .ptr_info = .{ .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, }, .rhs = undefined, // translate and set afterward }; return node; } fn transCreateNodeAPInt(c: *Context, int: *const ZigClangAPSInt) !*ast.Node { const num_limbs = math.cast(usize, ZigClangAPSInt_getNumWords(int)) catch |err| switch (err) { error.Overflow => return error.OutOfMemory, }; var aps_int = int; const is_negative = ZigClangAPSInt_isSigned(int) and ZigClangAPSInt_isNegative(int); if (is_negative) aps_int = ZigClangAPSInt_negate(aps_int); defer if (is_negative) { ZigClangAPSInt_free(aps_int); }; const limbs = try c.arena.alloc(math.big.Limb, num_limbs); defer c.arena.free(limbs); const data = ZigClangAPSInt_getRawData(aps_int); switch (@sizeOf(math.big.Limb)) { 8 => { var i: usize = 0; while (i < num_limbs) : (i += 1) { limbs[i] = data[i]; } }, 4 => { var limb_i: usize = 0; var data_i: usize = 0; while (limb_i < num_limbs) : ({ limb_i += 2; data_i += 1; }) { limbs[limb_i] = @truncate(u32, data[data_i]); limbs[limb_i + 1] = @truncate(u32, data[data_i] >> 32); } }, else => @compileError("unimplemented"), } const big: math.big.int.Const = .{ .limbs = limbs, .positive = !is_negative }; const str = big.toStringAlloc(c.arena, 10, false) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, }; defer c.arena.free(str); const token = try appendToken(c, .IntegerLiteral, str); const node = try c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .IntegerLiteral }, .token = token, }; return &node.base; } fn transCreateNodeUndefinedLiteral(c: *Context) !*ast.Node { const token = try appendToken(c, .Keyword_undefined, "undefined"); const node = try c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .UndefinedLiteral }, .token = token, }; return &node.base; } fn transCreateNodeNullLiteral(c: *Context) !*ast.Node { const token = try appendToken(c, .Keyword_null, "null"); const node = try c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .NullLiteral }, .token = token, }; return &node.base; } fn transCreateNodeBoolLiteral(c: *Context, value: bool) !*ast.Node { const token = if (value) try appendToken(c, .Keyword_true, "true") else try appendToken(c, .Keyword_false, "false"); const node = try c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .BoolLiteral }, .token = token, }; return &node.base; } fn transCreateNodeInt(c: *Context, int: anytype) !*ast.Node { const token = try appendTokenFmt(c, .IntegerLiteral, "{}", .{int}); const node = try c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .IntegerLiteral }, .token = token, }; return &node.base; } fn transCreateNodeFloat(c: *Context, int: anytype) !*ast.Node { const token = try appendTokenFmt(c, .FloatLiteral, "{}", .{int}); const node = try c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .FloatLiteral }, .token = token, }; return &node.base; } fn transCreateNodeOpaqueType(c: *Context) !*ast.Node { const call_node = try c.createBuiltinCall("@Type", 1); call_node.params()[0] = try transCreateNodeEnumLiteral(c, "Opaque"); call_node.rparen_token = try appendToken(c, .RParen, ")"); return &call_node.base; } 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"); const inline_tok = try appendToken(c, .Keyword_inline, "inline"); const fn_tok = try appendToken(c, .Keyword_fn, "fn"); const name_tok = try appendIdentifier(c, name); _ = try appendToken(c, .LParen, "("); var fn_params = std.ArrayList(ast.Node.FnProto.ParamDecl).init(c.gpa); defer fn_params.deinit(); for (proto_alias.params()) |param, i| { if (i != 0) { _ = try appendToken(c, .Comma, ","); } const param_name_tok = param.name_token orelse try appendTokenFmt(c, .Identifier, "arg_{}", .{c.getMangle()}); _ = try appendToken(c, .Colon, ":"); (try fn_params.addOne()).* = .{ .doc_comments = null, .comptime_token = null, .noalias_token = param.noalias_token, .name_token = param_name_tok, .param_type = param.param_type, }; } _ = try appendToken(c, .RParen, ")"); const block_lbrace = try appendToken(c, .LBrace, "{"); const return_kw = try appendToken(c, .Keyword_return, "return"); const unwrap_expr = try transCreateNodeUnwrapNull(c, ref.cast(ast.Node.VarDecl).?.getInitNode().?); const call_expr = try c.createCall(unwrap_expr, fn_params.items.len); const call_params = call_expr.params(); for (fn_params.items) |param, i| { if (i != 0) { _ = try appendToken(c, .Comma, ","); } call_params[i] = try transCreateNodeIdentifier(c, tokenSlice(c, param.name_token.?)); } call_expr.rtoken = try appendToken(c, .RParen, ")"); const return_expr = try ast.Node.ControlFlowExpression.create(c.arena, .{ .ltoken = return_kw, .tag = .Return, }, .{ .rhs = &call_expr.base, }); _ = try appendToken(c, .Semicolon, ";"); const block = try ast.Node.Block.alloc(c.arena, 1); block.* = .{ .lbrace = block_lbrace, .statements_len = 1, .rbrace = try appendToken(c, .RBrace, "}"), }; block.statements()[0] = &return_expr.base; const fn_proto = try ast.Node.FnProto.create(c.arena, .{ .params_len = fn_params.items.len, .fn_token = fn_tok, .return_type = proto_alias.return_type, }, .{ .visib_token = pub_tok, .name_token = name_tok, .extern_export_inline_token = inline_tok, .body_node = &block.base, }); mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items); return &fn_proto.base; } fn transCreateNodeUnwrapNull(c: *Context, wrapped: *ast.Node) !*ast.Node { _ = try appendToken(c, .Period, "."); const qm = try appendToken(c, .QuestionMark, "?"); const node = try c.arena.create(ast.Node.SimpleSuffixOp); node.* = .{ .base = .{ .tag = .UnwrapOptional }, .lhs = wrapped, .rtoken = qm, }; return &node.base; } fn transCreateNodeEnumLiteral(c: *Context, name: []const u8) !*ast.Node { const node = try c.arena.create(ast.Node.EnumLiteral); node.* = .{ .dot = try appendToken(c, .Period, "."), .name = try appendIdentifier(c, name), }; return &node.base; } fn transCreateNodeStringLiteral(c: *Context, str: []const u8) !*ast.Node { const node = try c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .StringLiteral }, .token = try appendToken(c, .StringLiteral, str), }; return &node.base; } fn transCreateNodeIf(c: *Context) !*ast.Node.If { const if_tok = try appendToken(c, .Keyword_if, "if"); _ = try appendToken(c, .LParen, "("); const node = try c.arena.create(ast.Node.If); node.* = .{ .if_token = if_tok, .condition = undefined, .payload = null, .body = undefined, .@"else" = null, }; return node; } fn transCreateNodeElse(c: *Context) !*ast.Node.Else { const node = try c.arena.create(ast.Node.Else); node.* = .{ .else_token = try appendToken(c, .Keyword_else, "else"), .payload = null, .body = undefined, }; return node; } fn transCreateNodeBreak( c: *Context, label: ?ast.TokenIndex, rhs: ?*ast.Node, ) !*ast.Node.ControlFlowExpression { var ctrl_flow = try CtrlFlow.init(c, .Break, if (label) |l| tokenSlice(c, l) else null); return ctrl_flow.finish(rhs); } const CtrlFlow = struct { c: *Context, ltoken: ast.TokenIndex, label_token: ?ast.TokenIndex, tag: ast.Node.Tag, /// Does everything except the RHS. fn init(c: *Context, tag: ast.Node.Tag, label: ?[]const u8) !CtrlFlow { const kw: Token.Id = switch (tag) { .Break => .Keyword_break, .Continue => .Keyword_continue, .Return => .Keyword_return, else => unreachable, }; const kw_text = switch (tag) { .Break => "break", .Continue => "continue", .Return => "return", else => unreachable, }; const ltoken = try appendToken(c, kw, kw_text); const label_token = if (label) |l| blk: { _ = try appendToken(c, .Colon, ":"); break :blk try appendIdentifier(c, l); } else null; return CtrlFlow{ .c = c, .ltoken = ltoken, .label_token = label_token, .tag = tag, }; } fn initToken(c: *Context, tag: ast.Node.Tag, label: ?ast.TokenIndex) !CtrlFlow { const other_token = label orelse return init(c, tag, null); const loc = c.token_locs.items[other_token]; const label_name = c.source_buffer.items[loc.start..loc.end]; return init(c, tag, label_name); } fn finish(self: *CtrlFlow, rhs: ?*ast.Node) !*ast.Node.ControlFlowExpression { return ast.Node.ControlFlowExpression.create(self.c.arena, .{ .ltoken = self.ltoken, .tag = self.tag, }, .{ .label = self.label_token, .rhs = rhs, }); } }; fn transCreateNodeWhile(c: *Context) !*ast.Node.While { const while_tok = try appendToken(c, .Keyword_while, "while"); _ = try appendToken(c, .LParen, "("); const node = try c.arena.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 ast.Node.ControlFlowExpression.create(c.arena, .{ .ltoken = ltoken, .tag = .Continue, }, .{}); _ = try appendToken(c, .Semicolon, ";"); return &node.base; } fn transCreateNodeSwitchCase(c: *Context, lhs: *ast.Node) !*ast.Node.SwitchCase { const arrow_tok = try appendToken(c, .EqualAngleBracketRight, "=>"); const node = try ast.Node.SwitchCase.alloc(c.arena, 1); node.* = .{ .items_len = 1, .arrow_token = arrow_tok, .payload = null, .expr = undefined, }; node.items()[0] = lhs; return node; } fn transCreateNodeSwitchElse(c: *Context) !*ast.Node { const node = try c.arena.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.Tag, 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 cast_node = try rp.c.createBuiltinCall("@intCast", 2); const rhs_type = try qualTypeToLog2IntRef(rp, ZigClangBinaryOperator_getType(stmt), rhs_location); cast_node.params()[0] = rhs_type; _ = try appendToken(rp.c, .Comma, ","); const rhs = try transExprCoercing(rp, scope, rhs_expr, .used, .r_value); cast_node.params()[1] = rhs; cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); const node = try rp.c.arena.create(ast.Node.SimpleInfixOp); node.* = .{ .base = .{ .tag = op }, .op_token = op_token, .lhs = lhs, .rhs = &cast_node.base, }; return &node.base; } fn transCreateNodePtrDeref(c: *Context, lhs: *ast.Node) !*ast.Node { const node = try c.arena.create(ast.Node.SimpleSuffixOp); node.* = .{ .base = .{ .tag = .Deref }, .lhs = lhs, .rtoken = try appendToken(c, .PeriodAsterisk, ".*"), }; return &node.base; } fn transCreateNodeArrayAccess(c: *Context, lhs: *ast.Node) !*ast.Node.ArrayAccess { _ = try appendToken(c, .LBrace, "["); const node = try c.arena.create(ast.Node.ArrayAccess); node.* = .{ .lhs = lhs, .index_expr = undefined, .rtoken = undefined, }; return node; } const RestorePoint = struct { c: *Context, token_index: ast.TokenIndex, src_buf_index: usize, fn activate(self: RestorePoint) void { self.c.token_ids.shrink(self.c.gpa, self.token_index); self.c.token_locs.shrink(self.c.gpa, self.token_index); self.c.source_buffer.shrink(self.src_buf_index); } }; fn makeRestorePoint(c: *Context) RestorePoint { return RestorePoint{ .c = c, .token_index = c.token_ids.items.len, .src_buf_index = c.source_buffer.items.len, }; } fn transType(rp: RestorePoint, ty: *const ZigClangType, source_loc: ZigClangSourceLocation) TypeError!*ast.Node { switch (ZigClangType_getTypeClass(ty)) { .Builtin => { const builtin_ty = @ptrCast(*const ZigClangBuiltinType, ty); return transCreateNodeIdentifier(rp.c, switch (ZigClangBuiltinType_getKind(builtin_ty)) { .Void => "c_void", .Bool => "bool", .Char_U, .UChar, .Char_S, .Char8 => "u8", .SChar => "i8", .UShort => "c_ushort", .UInt => "c_uint", .ULong => "c_ulong", .ULongLong => "c_ulonglong", .Short => "c_short", .Int => "c_int", .Long => "c_long", .LongLong => "c_longlong", .UInt128 => "u128", .Int128 => "i128", .Float => "f32", .Double => "f64", .Float128 => "f128", .Float16 => "f16", .LongDouble => "c_longdouble", else => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported builtin type", .{}), }); }, .FunctionProto => { 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; }, .FunctionNoProto => { const fn_no_proto_ty = @ptrCast(*const ZigClangFunctionType, ty); const fn_proto = try transFnNoProto(rp, fn_no_proto_ty, source_loc, null, false); return &fn_proto.base; }, .Paren => { const paren_ty = @ptrCast(*const ZigClangParenType, ty); return transQualType(rp, ZigClangParenType_getInnerType(paren_ty), source_loc); }, .Pointer => { const child_qt = ZigClangType_getPointeeType(ty); if (qualTypeChildIsFnProto(child_qt)) { const optional_node = try transCreateNodeSimplePrefixOp(rp.c, .OptionalType, .QuestionMark, "?"); optional_node.rhs = try transQualType(rp, child_qt, source_loc); return &optional_node.base; } if (typeIsOpaque(rp.c, ZigClangQualType_getTypePtr(child_qt), source_loc)) { const optional_node = try transCreateNodeSimplePrefixOp(rp.c, .OptionalType, .QuestionMark, "?"); const pointer_node = try transCreateNodePtrType( rp.c, ZigClangQualType_isConstQualified(child_qt), ZigClangQualType_isVolatileQualified(child_qt), .Asterisk, ); optional_node.rhs = &pointer_node.base; pointer_node.rhs = try transQualType(rp, child_qt, source_loc); return &optional_node.base; } const pointer_node = try transCreateNodePtrType( rp.c, ZigClangQualType_isConstQualified(child_qt), ZigClangQualType_isVolatileQualified(child_qt), .Identifier, ); pointer_node.rhs = try transQualType(rp, child_qt, source_loc); return &pointer_node.base; }, .ConstantArray => { const const_arr_ty = @ptrCast(*const ZigClangConstantArrayType, ty); const size_ap_int = ZigClangConstantArrayType_getSize(const_arr_ty); const size = ZigClangAPInt_getLimitedValue(size_ap_int, math.maxInt(usize)); const elem_ty = ZigClangQualType_getTypePtr(ZigClangConstantArrayType_getElementType(const_arr_ty)); return try transCreateNodeArrayType(rp, source_loc, elem_ty, size); }, .IncompleteArray => { const incomplete_array_ty = @ptrCast(*const ZigClangIncompleteArrayType, ty); const child_qt = ZigClangIncompleteArrayType_getElementType(incomplete_array_ty); var node = try transCreateNodePtrType( rp.c, ZigClangQualType_isConstQualified(child_qt), ZigClangQualType_isVolatileQualified(child_qt), .Identifier, ); node.rhs = try transQualType(rp, child_qt, source_loc); return &node.base; }, .Typedef => { const typedef_ty = @ptrCast(*const ZigClangTypedefType, ty); const typedef_decl = ZigClangTypedefType_getDecl(typedef_ty); return (try transTypeDef(rp.c, typedef_decl, false)) orelse revertAndWarn(rp, error.UnsupportedType, source_loc, "unable to translate typedef declaration", .{}); }, .Record => { const record_ty = @ptrCast(*const ZigClangRecordType, ty); const record_decl = ZigClangRecordType_getDecl(record_ty); 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); const enum_decl = ZigClangEnumType_getDecl(enum_ty); return (try transEnumDecl(rp.c, enum_decl)) orelse revertAndWarn(rp, error.UnsupportedType, source_loc, "unable to translate enum declaration", .{}); }, .Elaborated => { const elaborated_ty = @ptrCast(*const ZigClangElaboratedType, ty); return transQualType(rp, ZigClangElaboratedType_getNamedType(elaborated_ty), source_loc); }, .Decayed => { const decayed_ty = @ptrCast(*const ZigClangDecayedType, ty); return transQualType(rp, ZigClangDecayedType_getDecayedType(decayed_ty), source_loc); }, .Attributed => { 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}); }, } } fn isCVoid(qt: ZigClangQualType) bool { const ty = ZigClangQualType_getTypePtr(qt); if (ZigClangType_getTypeClass(ty) == .Builtin) { const builtin_ty = @ptrCast(*const ZigClangBuiltinType, ty); return ZigClangBuiltinType_getKind(builtin_ty) == .Void; } return false; } const FnDeclContext = struct { fn_name: []const u8, has_body: bool, storage_class: ZigClangStorageClass, is_export: bool, }; fn transCC( rp: RestorePoint, fn_ty: *const ZigClangFunctionType, source_loc: ZigClangSourceLocation, ) !CallingConvention { const clang_cc = ZigClangFunctionType_getCallConv(fn_ty); switch (clang_cc) { .C => return CallingConvention.C, .X86StdCall => return CallingConvention.Stdcall, .X86FastCall => return CallingConvention.Fastcall, .X86VectorCall, .AArch64VectorCall => return CallingConvention.Vectorcall, .X86ThisCall => return CallingConvention.Thiscall, .AAPCS => return CallingConvention.AAPCS, .AAPCS_VFP => return CallingConvention.AAPCSVFP, else => return revertAndWarn( rp, error.UnsupportedType, source_loc, "unsupported calling convention: {}", .{@tagName(clang_cc)}, ), } } fn transFnProto( rp: RestorePoint, fn_decl: ?*const ZigClangFunctionDecl, fn_proto_ty: *const ZigClangFunctionProtoType, source_loc: ZigClangSourceLocation, fn_decl_context: ?FnDeclContext, is_pub: bool, ) !*ast.Node.FnProto { const fn_ty = @ptrCast(*const ZigClangFunctionType, fn_proto_ty); const cc = try transCC(rp, fn_ty, source_loc); const is_var_args = ZigClangFunctionProtoType_isVariadic(fn_proto_ty); return finishTransFnProto(rp, fn_decl, fn_proto_ty, fn_ty, source_loc, fn_decl_context, is_var_args, cc, is_pub); } fn transFnNoProto( rp: RestorePoint, fn_ty: *const ZigClangFunctionType, source_loc: ZigClangSourceLocation, fn_decl_context: ?FnDeclContext, is_pub: bool, ) !*ast.Node.FnProto { const cc = try transCC(rp, fn_ty, source_loc); const is_var_args = if (fn_decl_context) |ctx| !ctx.is_export else true; return finishTransFnProto(rp, null, null, fn_ty, source_loc, fn_decl_context, is_var_args, cc, is_pub); } fn finishTransFnProto( rp: RestorePoint, fn_decl: ?*const ZigClangFunctionDecl, fn_proto_ty: ?*const ZigClangFunctionProtoType, fn_ty: *const ZigClangFunctionType, source_loc: ZigClangSourceLocation, fn_decl_context: ?FnDeclContext, is_var_args: bool, cc: CallingConvention, is_pub: bool, ) !*ast.Node.FnProto { const is_export = if (fn_decl_context) |ctx| ctx.is_export else false; const is_extern = if (fn_decl_context) |ctx| !ctx.has_body else false; // TODO check for always_inline attribute // TODO check for align attribute // pub extern fn name(...) T const pub_tok = if (is_pub) try appendToken(rp.c, .Keyword_pub, "pub") else null; const extern_export_inline_tok = if (is_export) try appendToken(rp.c, .Keyword_export, "export") else if (is_extern) try appendToken(rp.c, .Keyword_extern, "extern") else null; const fn_tok = try appendToken(rp.c, .Keyword_fn, "fn"); const name_tok = if (fn_decl_context) |ctx| try appendIdentifier(rp.c, ctx.fn_name) else null; const lparen_tok = try appendToken(rp.c, .LParen, "("); var fn_params = std.ArrayList(ast.Node.FnProto.ParamDecl).init(rp.c.gpa); defer fn_params.deinit(); const param_count: usize = if (fn_proto_ty != null) ZigClangFunctionProtoType_getNumParams(fn_proto_ty.?) else 0; try fn_params.ensureCapacity(param_count + 1); // +1 for possible var args node var i: usize = 0; while (i < param_count) : (i += 1) { const param_qt = ZigClangFunctionProtoType_getParamType(fn_proto_ty.?, @intCast(c_uint, i)); const noalias_tok = if (ZigClangQualType_isRestrictQualified(param_qt)) try appendToken(rp.c, .Keyword_noalias, "noalias") else null; const param_name_tok: ?ast.TokenIndex = blk: { if (fn_decl) |decl| { const param = ZigClangFunctionDecl_getParamDecl(decl, @intCast(c_uint, i)); const param_name: []const u8 = try rp.c.str(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, param))); if (param_name.len < 1) break :blk null; const result = try appendIdentifier(rp.c, param_name); _ = try appendToken(rp.c, .Colon, ":"); break :blk result; } break :blk null; }; const type_node = try transQualType(rp, param_qt, source_loc); fn_params.addOneAssumeCapacity().* = .{ .doc_comments = null, .comptime_token = null, .noalias_token = noalias_tok, .name_token = param_name_tok, .param_type = .{ .type_expr = type_node }, }; if (i + 1 < param_count) { _ = try appendToken(rp.c, .Comma, ","); } } const var_args_token: ?ast.TokenIndex = if (is_var_args) blk: { if (param_count > 0) { _ = try appendToken(rp.c, .Comma, ","); } break :blk try appendToken(rp.c, .Ellipsis3, "..."); } else null; const rparen_tok = try appendToken(rp.c, .RParen, ")"); const linksection_expr = blk: { if (fn_decl) |decl| { var str_len: usize = undefined; if (ZigClangFunctionDecl_getSectionAttribute(decl, &str_len)) |str_ptr| { _ = try appendToken(rp.c, .Keyword_linksection, "linksection"); _ = try appendToken(rp.c, .LParen, "("); const expr = try transCreateNodeStringLiteral( rp.c, try std.fmt.allocPrint(rp.c.arena, "\"{}\"", .{str_ptr[0..str_len]}), ); _ = try appendToken(rp.c, .RParen, ")"); break :blk expr; } } break :blk null; }; const align_expr = blk: { if (fn_decl) |decl| { const alignment = ZigClangFunctionDecl_getAlignedAttribute(decl, rp.c.clang_context); if (alignment != 0) { _ = try appendToken(rp.c, .Keyword_align, "align"); _ = try appendToken(rp.c, .LParen, "("); // Clang reports the alignment in bits const expr = try transCreateNodeInt(rp.c, alignment / 8); _ = try appendToken(rp.c, .RParen, ")"); break :blk expr; } } break :blk null; }; const callconv_expr = if ((is_export or is_extern) and cc == .C) null else blk: { _ = try appendToken(rp.c, .Keyword_callconv, "callconv"); _ = try appendToken(rp.c, .LParen, "("); const expr = try transCreateNodeEnumLiteral(rp.c, @tagName(cc)); _ = try appendToken(rp.c, .RParen, ")"); break :blk expr; }; const return_type_node = blk: { if (ZigClangFunctionType_getNoReturnAttr(fn_ty)) { break :blk try transCreateNodeIdentifier(rp.c, "noreturn"); } else { const return_qt = ZigClangFunctionType_getReturnType(fn_ty); if (isCVoid(return_qt)) { // convert primitive c_void to actual void (only for return type) break :blk try transCreateNodeIdentifier(rp.c, "void"); } else { break :blk transQualType(rp, return_qt, source_loc) catch |err| switch (err) { error.UnsupportedType => { try emitWarning(rp.c, source_loc, "unsupported function proto return type", .{}); return err; }, error.OutOfMemory => |e| return e, }; } } }; // We need to reserve an undefined (but non-null) body node to set later. var body_node: ?*ast.Node = null; if (fn_decl_context) |ctx| { if (ctx.has_body) { // TODO: we should be able to use undefined here but // it causes a bug. This is undefined without zig language // being aware of it. body_node = @intToPtr(*ast.Node, 0x08); } } const fn_proto = try ast.Node.FnProto.create(rp.c.arena, .{ .params_len = fn_params.items.len, .return_type = .{ .Explicit = return_type_node }, .fn_token = fn_tok, }, .{ .visib_token = pub_tok, .name_token = name_tok, .extern_export_inline_token = extern_export_inline_tok, .align_expr = align_expr, .section_expr = linksection_expr, .callconv_expr = callconv_expr, .body_node = body_node, .var_args_token = var_args_token, }); mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items); return fn_proto; } fn revertAndWarn( rp: RestorePoint, err: anytype, source_loc: ZigClangSourceLocation, comptime format: []const u8, args: anytype, ) (@TypeOf(err) || error{OutOfMemory}) { rp.activate(); try emitWarning(rp.c, source_loc, format, args); return err; } fn emitWarning(c: *Context, loc: ZigClangSourceLocation, comptime format: []const u8, args: anytype) !void { const args_prefix = .{c.locStr(loc)}; _ = try appendTokenFmt(c, .LineComment, "// {}: warning: " ++ format, args_prefix ++ args); } pub fn failDecl(c: *Context, loc: ZigClangSourceLocation, name: []const u8, comptime format: []const u8, args: anytype) !void { // pub const name = @compileError(msg); const pub_tok = try appendToken(c, .Keyword_pub, "pub"); const const_tok = try appendToken(c, .Keyword_const, "const"); const name_tok = try appendIdentifier(c, name); const eq_tok = try appendToken(c, .Equal, "="); const builtin_tok = try appendToken(c, .Builtin, "@compileError"); const lparen_tok = try appendToken(c, .LParen, "("); const msg_tok = try appendTokenFmt(c, .StringLiteral, "\"" ++ format ++ "\"", args); const rparen_tok = try appendToken(c, .RParen, ")"); const semi_tok = try appendToken(c, .Semicolon, ";"); _ = try appendTokenFmt(c, .LineComment, "// {}", .{c.locStr(loc)}); const msg_node = try c.arena.create(ast.Node.OneToken); msg_node.* = .{ .base = .{ .tag = .StringLiteral }, .token = msg_tok, }; const call_node = try ast.Node.BuiltinCall.alloc(c.arena, 1); call_node.* = .{ .builtin_token = builtin_tok, .params_len = 1, .rparen_token = rparen_tok, }; call_node.params()[0] = &msg_node.base; const var_decl_node = try ast.Node.VarDecl.create(c.arena, .{ .name_token = name_tok, .mut_token = const_tok, .semicolon_token = semi_tok, }, .{ .visib_token = pub_tok, .eq_token = eq_tok, .init_node = &call_node.base, }); try addTopLevelDecl(c, name, &var_decl_node.base); } fn appendToken(c: *Context, token_id: Token.Id, bytes: []const u8) !ast.TokenIndex { std.debug.assert(token_id != .Identifier); // use appendIdentifier return appendTokenFmt(c, token_id, "{}", .{bytes}); } fn appendTokenFmt(c: *Context, token_id: Token.Id, comptime format: []const u8, args: anytype) !ast.TokenIndex { assert(token_id != .Invalid); try c.token_ids.ensureCapacity(c.gpa, c.token_ids.items.len + 1); try c.token_locs.ensureCapacity(c.gpa, c.token_locs.items.len + 1); const start_index = c.source_buffer.items.len; try c.source_buffer.outStream().print(format ++ " ", args); c.token_ids.appendAssumeCapacity(token_id); c.token_locs.appendAssumeCapacity(.{ .start = start_index, .end = c.source_buffer.items.len - 1, // back up before the space }); return c.token_ids.items.len - 1; } // 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) { '_', 'a'...'z', 'A'...'Z' => {}, '0'...'9' => if (i == 0) return false, else => return false, } } return true; } fn appendIdentifier(c: *Context, name: []const u8) !ast.TokenIndex { if (!isValidZigIdentifier(name) or std.zig.Token.getKeyword(name) != null) { return appendTokenFmt(c, .Identifier, "@\"{}\"", .{name}); } else { return appendTokenFmt(c, .Identifier, "{}", .{name}); } } fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node { const token_index = try appendIdentifier(c, name); const identifier = try c.arena.create(ast.Node.OneToken); identifier.* = .{ .base = .{ .tag = .Identifier }, .token = token_index, }; return &identifier.base; } fn transCreateNodeIdentifierUnchecked(c: *Context, name: []const u8) !*ast.Node { const token_index = try appendTokenFmt(c, .Identifier, "{}", .{name}); const identifier = try c.arena.create(ast.Node.OneToken); identifier.* = .{ .base = .{ .tag = .Identifier }, .token = token_index, }; return &identifier.base; } pub fn freeErrors(errors: []ClangErrMsg) void { ZigClangErrorMsg_delete(errors.ptr, errors.len); } const MacroCtx = struct { source: []const u8, list: []const CToken, i: usize = 0, loc: ZigClangSourceLocation, name: []const u8, fn peek(self: *MacroCtx) ?CToken.Id { if (self.i >= self.list.len) return null; return self.list[self.i + 1].id; } fn next(self: *MacroCtx) ?CToken.Id { if (self.i >= self.list.len) return null; self.i += 1; return self.list[self.i].id; } fn slice(self: *MacroCtx) []const u8 { const tok = self.list[self.i]; return self.source[tok.start..tok.end]; } fn fail(self: *MacroCtx, c: *Context, comptime fmt: []const u8, args: anytype) !void { return failDecl(c, self.loc, self.name, fmt, args); } }; fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { // TODO if we see #undef, delete it from the table var it = ZigClangASTUnit_getLocalPreprocessingEntities_begin(unit); const it_end = ZigClangASTUnit_getLocalPreprocessingEntities_end(unit); var tok_list = std.ArrayList(CToken).init(c.gpa); defer tok_list.deinit(); const scope = c.global_scope; while (it.I != it_end.I) : (it.I += 1) { const entity = ZigClangPreprocessingRecord_iterator_deref(it); tok_list.items.len = 0; switch (ZigClangPreprocessedEntity_getKind(entity)) { .MacroDefinitionKind => { const macro = @ptrCast(*ZigClangMacroDefinitionRecord, entity); const raw_name = ZigClangMacroDefinitionRecord_getName_getNameStart(macro); const begin_loc = ZigClangMacroDefinitionRecord_getSourceRange_getBegin(macro); const name = try c.str(raw_name); // TODO https://github.com/ziglang/zig/issues/3756 // TODO https://github.com/ziglang/zig/issues/1802 const mangled_name = if (isZigPrimitiveType(name)) try std.fmt.allocPrint(c.arena, "{}_{}", .{ name, c.getMangle() }) else name; if (scope.containsNow(mangled_name)) { continue; } const begin_c = ZigClangSourceManager_getCharacterData(c.source_manager, begin_loc); const slice = begin_c[0..mem.len(begin_c)]; var tokenizer = std.c.Tokenizer{ .buffer = slice, }; while (true) { const tok = tokenizer.next(); switch (tok.id) { .Nl, .Eof => { try tok_list.append(tok); break; }, .LineComment, .MultiLineComment => continue, else => {}, } try tok_list.append(tok); } var macro_ctx = MacroCtx{ .source = slice, .list = tok_list.items, .name = mangled_name, .loc = begin_loc, }; assert(mem.eql(u8, macro_ctx.slice(), name)); var macro_fn = false; switch (macro_ctx.peek().?) { .Identifier => { // if it equals itself, ignore. for example, from stdio.h: // #define stdin stdin const tok = macro_ctx.list[1]; if (mem.eql(u8, name, slice[tok.start..tok.end])) { continue; } }, .Nl, .Eof => { // this means it is a macro without a value // we don't care about such things continue; }, .LParen => { // if the name is immediately followed by a '(' then it is a function macro_fn = macro_ctx.list[0].end == macro_ctx.list[1].start; }, else => {}, } (if (macro_fn) transMacroFnDefine(c, ¯o_ctx) else transMacroDefine(c, ¯o_ctx)) catch |err| switch (err) { error.ParseError => continue, error.OutOfMemory => |e| return e, }; }, else => {}, } } } fn transMacroDefine(c: *Context, m: *MacroCtx) ParseError!void { 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, m.name); const eq_token = try appendToken(c, .Equal, "="); const init_node = try parseCExpr(c, m, scope); const last = m.next().?; if (last != .Eof and last != .Nl) return m.fail(c, "unable to translate C expr: unexpected token .{}", .{@tagName(last)}); const semicolon_token = try appendToken(c, .Semicolon, ";"); const node = try ast.Node.VarDecl.create(c.arena, .{ .name_token = name_tok, .mut_token = mut_tok, .semicolon_token = semicolon_token, }, .{ .visib_token = visib_tok, .eq_token = eq_token, .init_node = init_node, }); _ = try c.global_scope.macro_table.put(m.name, &node.base); } fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void { var block_scope = try Scope.Block.init(c, &c.global_scope.base, false); defer block_scope.deinit(); const scope = &block_scope.base; const pub_tok = try appendToken(c, .Keyword_pub, "pub"); const inline_tok = try appendToken(c, .Keyword_inline, "inline"); const fn_tok = try appendToken(c, .Keyword_fn, "fn"); const name_tok = try appendIdentifier(c, m.name); _ = try appendToken(c, .LParen, "("); if (m.next().? != .LParen) { return m.fail(c, "unable to translate C expr: expected '('", .{}); } var fn_params = std.ArrayList(ast.Node.FnProto.ParamDecl).init(c.gpa); defer fn_params.deinit(); while (true) { if (m.next().? != .Identifier) { return m.fail(c, "unable to translate C expr: expected identifier", .{}); } const mangled_name = try block_scope.makeMangledName(c, m.slice()); const param_name_tok = try appendIdentifier(c, mangled_name); _ = try appendToken(c, .Colon, ":"); const any_type = try c.arena.create(ast.Node.OneToken); any_type.* = .{ .base = .{ .tag = .AnyType }, .token = try appendToken(c, .Keyword_anytype, "anytype"), }; (try fn_params.addOne()).* = .{ .doc_comments = null, .comptime_token = null, .noalias_token = null, .name_token = param_name_tok, .param_type = .{ .any_type = &any_type.base }, }; if (m.peek().? != .Comma) break; _ = m.next(); _ = try appendToken(c, .Comma, ","); } if (m.next().? != .RParen) { return m.fail(c, "unable to translate C expr: expected ')'", .{}); } _ = try appendToken(c, .RParen, ")"); const type_of = try c.createBuiltinCall("@TypeOf", 1); const return_kw = try appendToken(c, .Keyword_return, "return"); const expr = try parseCExpr(c, m, scope); const last = m.next().?; if (last != .Eof and last != .Nl) return m.fail(c, "unable to translate C expr: unexpected token .{}", .{@tagName(last)}); _ = try appendToken(c, .Semicolon, ";"); const type_of_arg = if (!expr.tag.isBlock()) expr else blk: { const stmts = expr.blockStatements(); const blk_last = stmts[stmts.len - 1]; const br = blk_last.cast(ast.Node.ControlFlowExpression).?; break :blk br.getRHS().?; }; type_of.params()[0] = type_of_arg; type_of.rparen_token = try appendToken(c, .RParen, ")"); const return_expr = try ast.Node.ControlFlowExpression.create(c.arena, .{ .ltoken = return_kw, .tag = .Return, }, .{ .rhs = expr, }); try block_scope.statements.append(&return_expr.base); const block_node = try block_scope.complete(c); const fn_proto = try ast.Node.FnProto.create(c.arena, .{ .fn_token = fn_tok, .params_len = fn_params.items.len, .return_type = .{ .Explicit = &type_of.base }, }, .{ .visib_token = pub_tok, .extern_export_inline_token = inline_tok, .name_token = name_tok, .body_node = block_node, }); mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items); _ = try c.global_scope.macro_table.put(m.name, &fn_proto.base); } const ParseError = Error || error{ParseError}; fn parseCExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { const node = try parseCPrefixOpExpr(c, m, scope); switch (m.next().?) { .QuestionMark => { // must come immediately after expr _ = try appendToken(c, .RParen, ")"); const if_node = try transCreateNodeIf(c); if_node.condition = node; if_node.body = try parseCPrimaryExpr(c, m, scope); if (m.next().? != .Colon) { try m.fail(c, "unable to translate C expr: expected ':'", .{}); return error.ParseError; } if_node.@"else" = try transCreateNodeElse(c); if_node.@"else".?.body = try parseCPrimaryExpr(c, m, scope); return &if_node.base; }, .Comma => { _ = try appendToken(c, .Semicolon, ";"); var block_scope = try Scope.Block.init(c, scope, true); defer block_scope.deinit(); var last = node; while (true) { // suppress result const lhs = try transCreateNodeIdentifier(c, "_"); const op_token = try appendToken(c, .Equal, "="); const op_node = try c.arena.create(ast.Node.SimpleInfixOp); op_node.* = .{ .base = .{ .tag = .Assign }, .op_token = op_token, .lhs = lhs, .rhs = last, }; try block_scope.statements.append(&op_node.base); last = try parseCPrefixOpExpr(c, m, scope); _ = try appendToken(c, .Semicolon, ";"); if (m.next().? != .Comma) { m.i -= 1; break; } } const break_node = try transCreateNodeBreak(c, block_scope.label, last); try block_scope.statements.append(&break_node.base); return try block_scope.complete(c); }, else => { m.i -= 1; return node; }, } } fn parseCNumLit(c: *Context, m: *MacroCtx) ParseError!*ast.Node { var lit_bytes = m.slice(); switch (m.list[m.i].id) { .IntegerLiteral => |suffix| { if (lit_bytes.len > 2 and lit_bytes[0] == '0') { switch (lit_bytes[1]) { '0'...'7' => { // Octal lit_bytes = try std.fmt.allocPrint(c.arena, "0o{}", .{lit_bytes}); }, 'X' => { // Hexadecimal with capital X, valid in C but not in Zig lit_bytes = try std.fmt.allocPrint(c.arena, "0x{}", .{lit_bytes[2..]}); }, else => {}, } } if (suffix == .none) { return transCreateNodeInt(c, lit_bytes); } const cast_node = try c.createBuiltinCall("@as", 2); cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (suffix) { .u => "c_uint", .l => "c_long", .lu => "c_ulong", .ll => "c_longlong", .llu => "c_ulonglong", else => unreachable, }); lit_bytes = lit_bytes[0 .. lit_bytes.len - switch (suffix) { .u, .l => @as(u8, 1), .lu, .ll => 2, .llu => 3, else => unreachable, }]; _ = try appendToken(c, .Comma, ","); cast_node.params()[1] = try transCreateNodeInt(c, lit_bytes); cast_node.rparen_token = try appendToken(c, .RParen, ")"); return &cast_node.base; }, .FloatLiteral => |suffix| { if (lit_bytes[0] == '.') lit_bytes = try std.fmt.allocPrint(c.arena, "0{}", .{lit_bytes}); if (suffix == .none) { return transCreateNodeFloat(c, lit_bytes); } const cast_node = try c.createBuiltinCall("@as", 2); cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (suffix) { .f => "f32", .l => "c_longdouble", else => unreachable, }); _ = try appendToken(c, .Comma, ","); cast_node.params()[1] = try transCreateNodeFloat(c, lit_bytes[0 .. lit_bytes.len - 1]); cast_node.rparen_token = try appendToken(c, .RParen, ")"); return &cast_node.base; }, else => unreachable, } } fn zigifyEscapeSequences(ctx: *Context, m: *MacroCtx) ![]const u8 { var source = m.slice(); for (source) |c, i| { if (c == '\"' or c == '\'') { source = source[i..]; break; } } for (source) |c| { if (c == '\\') { break; } } else return source; var bytes = try ctx.arena.alloc(u8, source.len * 2); var state: enum { Start, Escape, Hex, Octal, } = .Start; var i: usize = 0; var count: u8 = 0; var num: u8 = 0; for (source) |c| { 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' => { try m.fail(ctx, "macro tokenizing failed: TODO unicode escape sequences", .{}); return error.ParseError; }, else => { try m.fail(ctx, "macro tokenizing failed: unknown escape sequence", .{}); return error.ParseError; }, } 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 { try m.fail(ctx, "macro tokenizing failed: hex literal overflowed", .{}); return error.ParseError; }; num += c - '0'; }, 'a'...'f' => { num = std.math.mul(u8, num, 16) catch { try m.fail(ctx, "macro tokenizing failed: hex literal overflowed", .{}); return error.ParseError; }; num += c - 'a' + 10; }, 'A'...'F' => { num = std.math.mul(u8, num, 16) catch { try m.fail(ctx, "macro tokenizing failed: hex literal overflowed", .{}); return error.ParseError; }; 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 => { const accept_digit = switch (c) { // The maximum length of a octal literal is 3 digits '0'...'7' => count < 3, else => false, }; if (accept_digit) { count += 1; num = std.math.mul(u8, num, 8) catch { try m.fail(ctx, "macro tokenizing failed: octal literal overflowed", .{}); return error.ParseError; }; num += c - '0'; } else { i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 }); num = 0; count = 0; if (c == '\\') state = .Escape else state = .Start; bytes[i] = c; i += 1; } }, } } if (state == .Hex or state == .Octal) i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 }); return bytes[0..i]; } fn parseCPrimaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { const tok = m.next().?; const slice = m.slice(); switch (tok) { .CharLiteral => { if (slice[0] != '\'' or slice[1] == '\\' or slice.len == 3) { const token = try appendToken(c, .CharLiteral, try zigifyEscapeSequences(c, m)); const node = try c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .CharLiteral }, .token = token, }; return &node.base; } else { const token = try appendTokenFmt(c, .IntegerLiteral, "0x{x}", .{slice[1 .. slice.len - 1]}); const node = try c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .IntegerLiteral }, .token = token, }; return &node.base; } }, .StringLiteral => { const token = try appendToken(c, .StringLiteral, try zigifyEscapeSequences(c, m)); const node = try c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .StringLiteral }, .token = token, }; return &node.base; }, .IntegerLiteral, .FloatLiteral => { return parseCNumLit(c, m); }, // eventually this will be replaced by std.c.parse which will handle these correctly .Keyword_void => return transCreateNodeIdentifierUnchecked(c, "c_void"), .Keyword_bool => return transCreateNodeIdentifierUnchecked(c, "bool"), .Keyword_double => return transCreateNodeIdentifierUnchecked(c, "f64"), .Keyword_long => return transCreateNodeIdentifierUnchecked(c, "c_long"), .Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_int"), .Keyword_float => return transCreateNodeIdentifierUnchecked(c, "f32"), .Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_short"), .Keyword_char => return transCreateNodeIdentifierUnchecked(c, "u8"), .Keyword_unsigned => if (m.next()) |t| switch (t) { .Keyword_char => return transCreateNodeIdentifierUnchecked(c, "u8"), .Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_ushort"), .Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_uint"), .Keyword_long => if (m.peek() != null and m.peek().? == .Keyword_long) { _ = m.next(); return transCreateNodeIdentifierUnchecked(c, "c_ulonglong"); } else return transCreateNodeIdentifierUnchecked(c, "c_ulong"), else => { m.i -= 1; return transCreateNodeIdentifierUnchecked(c, "c_uint"); }, } else { return transCreateNodeIdentifierUnchecked(c, "c_uint"); }, .Keyword_signed => if (m.next()) |t| switch (t) { .Keyword_char => return transCreateNodeIdentifierUnchecked(c, "i8"), .Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_short"), .Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_int"), .Keyword_long => if (m.peek() != null and m.peek().? == .Keyword_long) { _ = m.next(); return transCreateNodeIdentifierUnchecked(c, "c_longlong"); } else return transCreateNodeIdentifierUnchecked(c, "c_long"), else => { m.i -= 1; return transCreateNodeIdentifierUnchecked(c, "c_int"); }, } else { return transCreateNodeIdentifierUnchecked(c, "c_int"); }, .Identifier => { const mangled_name = scope.getAlias(slice); return transCreateNodeIdentifier(c, checkForBuiltinTypedef(mangled_name) orelse mangled_name); }, .LParen => { const inner_node = try parseCExpr(c, m, scope); const next_id = m.next().?; if (next_id != .RParen) { try m.fail(c, "unable to translate C expr: expected ')'' instead got: {}", .{@tagName(next_id)}); return error.ParseError; } var saw_l_paren = false; var saw_integer_literal = false; switch (m.peek().?) { // (type)(to_cast) .LParen => { saw_l_paren = true; _ = m.next(); }, // (type)sizeof(x) .Keyword_sizeof, // (type)alignof(x) .Keyword_alignof, // (type)identifier .Identifier => {}, // (type)integer .IntegerLiteral => { saw_integer_literal = true; }, else => return inner_node, } // hack to get zig fmt to render a comma in builtin calls _ = try appendToken(c, .Comma, ","); const node_to_cast = try parseCExpr(c, m, scope); if (saw_l_paren and m.next().? != .RParen) { try m.fail(c, "unable to translate C expr: expected ')''", .{}); return error.ParseError; } const lparen = try appendToken(c, .LParen, "("); //(@import("std").meta.cast(dest, x)) const import_fn_call = try c.createBuiltinCall("@import", 1); const std_node = try transCreateNodeStringLiteral(c, "\"std\""); import_fn_call.params()[0] = std_node; import_fn_call.rparen_token = try appendToken(c, .RParen, ")"); const inner_field_access = try transCreateNodeFieldAccess(c, &import_fn_call.base, "meta"); const outer_field_access = try transCreateNodeFieldAccess(c, inner_field_access, "cast"); const cast_fn_call = try c.createCall(outer_field_access, 2); cast_fn_call.params()[0] = inner_node; cast_fn_call.params()[1] = node_to_cast; cast_fn_call.rtoken = try appendToken(c, .RParen, ")"); const group_node = try c.arena.create(ast.Node.GroupedExpression); group_node.* = .{ .lparen = lparen, .expr = &cast_fn_call.base, .rparen = try appendToken(c, .RParen, ")"), }; return &group_node.base; }, else => { try m.fail(c, "unable to translate C expr: unexpected token .{}", .{@tagName(tok)}); return error.ParseError; }, } } fn nodeIsInfixOp(tag: ast.Node.Tag) bool { return switch (tag) { .Add, .AddWrap, .ArrayCat, .ArrayMult, .Assign, .AssignBitAnd, .AssignBitOr, .AssignBitShiftLeft, .AssignBitShiftRight, .AssignBitXor, .AssignDiv, .AssignSub, .AssignSubWrap, .AssignMod, .AssignAdd, .AssignAddWrap, .AssignMul, .AssignMulWrap, .BangEqual, .BitAnd, .BitOr, .BitShiftLeft, .BitShiftRight, .BitXor, .BoolAnd, .BoolOr, .Div, .EqualEqual, .ErrorUnion, .GreaterOrEqual, .GreaterThan, .LessOrEqual, .LessThan, .MergeErrorSets, .Mod, .Mul, .MulWrap, .Period, .Range, .Sub, .SubWrap, .UnwrapOptional, .Catch, => true, else => false, }; } fn macroBoolToInt(c: *Context, node: *ast.Node) !*ast.Node { if (!isBoolRes(node)) { if (!nodeIsInfixOp(node.tag)) return node; const group_node = try c.arena.create(ast.Node.GroupedExpression); group_node.* = .{ .lparen = try appendToken(c, .LParen, "("), .expr = node, .rparen = try appendToken(c, .RParen, ")"), }; return &group_node.base; } const builtin_node = try c.createBuiltinCall("@boolToInt", 1); builtin_node.params()[0] = node; builtin_node.rparen_token = try appendToken(c, .RParen, ")"); return &builtin_node.base; } fn macroIntToBool(c: *Context, node: *ast.Node) !*ast.Node { if (isBoolRes(node)) { if (!nodeIsInfixOp(node.tag)) return node; const group_node = try c.arena.create(ast.Node.GroupedExpression); group_node.* = .{ .lparen = try appendToken(c, .LParen, "("), .expr = node, .rparen = try appendToken(c, .RParen, ")"), }; return &group_node.base; } const op_token = try appendToken(c, .BangEqual, "!="); const zero = try transCreateNodeInt(c, 0); const res = try c.arena.create(ast.Node.SimpleInfixOp); res.* = .{ .base = .{ .tag = .BangEqual }, .op_token = op_token, .lhs = node, .rhs = zero, }; const group_node = try c.arena.create(ast.Node.GroupedExpression); group_node.* = .{ .lparen = try appendToken(c, .LParen, "("), .expr = &res.base, .rparen = try appendToken(c, .RParen, ")"), }; return &group_node.base; } fn parseCSuffixOpExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { var node = try parseCPrimaryExpr(c, m, scope); while (true) { var op_token: ast.TokenIndex = undefined; var op_id: ast.Node.Tag = undefined; var bool_op = false; switch (m.next().?) { .Period => { if (m.next().? != .Identifier) { try m.fail(c, "unable to translate C expr: expected identifier", .{}); return error.ParseError; } node = try transCreateNodeFieldAccess(c, node, m.slice()); continue; }, .Arrow => { if (m.next().? != .Identifier) { try m.fail(c, "unable to translate C expr: expected identifier", .{}); return error.ParseError; } const deref = try transCreateNodePtrDeref(c, node); node = try transCreateNodeFieldAccess(c, deref, m.slice()); continue; }, .Asterisk => { if (m.peek().? == .RParen) { // type *) // hack to get zig fmt to render a comma in builtin calls _ = try appendToken(c, .Comma, ","); // last token of `node` const prev_id = m.list[m.i - 1].id; if (prev_id == .Keyword_void) { const ptr = try transCreateNodePtrType(c, false, false, .Asterisk); ptr.rhs = node; const optional_node = try transCreateNodeSimplePrefixOp(c, .OptionalType, .QuestionMark, "?"); optional_node.rhs = &ptr.base; return &optional_node.base; } else { const ptr = try transCreateNodePtrType(c, false, false, Token.Id.Identifier); ptr.rhs = node; return &ptr.base; } } else { // expr * expr op_token = try appendToken(c, .Asterisk, "*"); op_id = .BitShiftLeft; } }, .AngleBracketAngleBracketLeft => { op_token = try appendToken(c, .AngleBracketAngleBracketLeft, "<<"); op_id = .BitShiftLeft; }, .AngleBracketAngleBracketRight => { op_token = try appendToken(c, .AngleBracketAngleBracketRight, ">>"); op_id = .BitShiftRight; }, .Pipe => { op_token = try appendToken(c, .Pipe, "|"); op_id = .BitOr; }, .Ampersand => { op_token = try appendToken(c, .Ampersand, "&"); op_id = .BitAnd; }, .Plus => { op_token = try appendToken(c, .Plus, "+"); op_id = .Add; }, .Minus => { op_token = try appendToken(c, .Minus, "-"); op_id = .Sub; }, .AmpersandAmpersand => { op_token = try appendToken(c, .Keyword_and, "and"); op_id = .BoolAnd; bool_op = true; }, .PipePipe => { op_token = try appendToken(c, .Keyword_or, "or"); op_id = .BoolOr; bool_op = true; }, .AngleBracketRight => { op_token = try appendToken(c, .AngleBracketRight, ">"); op_id = .GreaterThan; }, .AngleBracketRightEqual => { op_token = try appendToken(c, .AngleBracketRightEqual, ">="); op_id = .GreaterOrEqual; }, .AngleBracketLeft => { op_token = try appendToken(c, .AngleBracketLeft, "<"); op_id = .LessThan; }, .AngleBracketLeftEqual => { op_token = try appendToken(c, .AngleBracketLeftEqual, "<="); op_id = .LessOrEqual; }, .LBracket => { const arr_node = try transCreateNodeArrayAccess(c, node); arr_node.index_expr = try parseCPrefixOpExpr(c, m, scope); arr_node.rtoken = try appendToken(c, .RBracket, "]"); node = &arr_node.base; if (m.next().? != .RBracket) { try m.fail(c, "unable to translate C expr: expected ']'", .{}); return error.ParseError; } continue; }, .LParen => { _ = try appendToken(c, .LParen, "("); var call_params = std.ArrayList(*ast.Node).init(c.gpa); defer call_params.deinit(); while (true) { const arg = try parseCPrefixOpExpr(c, m, scope); try call_params.append(arg); switch (m.next().?) { .Comma => _ = try appendToken(c, .Comma, ","), .RParen => break, else => { try m.fail(c, "unable to translate C expr: expected ',' or ')'", .{}); return error.ParseError; }, } } const call_node = try ast.Node.Call.alloc(c.arena, call_params.items.len); call_node.* = .{ .lhs = node, .params_len = call_params.items.len, .async_token = null, .rtoken = try appendToken(c, .RParen, ")"), }; mem.copy(*ast.Node, call_node.params(), call_params.items); node = &call_node.base; continue; }, .LBrace => { // must come immediately after `node` _ = try appendToken(c, .Comma, ","); const dot = try appendToken(c, .Period, "."); _ = try appendToken(c, .LBrace, "{"); var init_vals = std.ArrayList(*ast.Node).init(c.gpa); defer init_vals.deinit(); while (true) { const val = try parseCPrefixOpExpr(c, m, scope); try init_vals.append(val); switch (m.next().?) { .Comma => _ = try appendToken(c, .Comma, ","), .RBrace => break, else => { try m.fail(c, "unable to translate C expr: expected ',' or '}}'", .{}); return error.ParseError; }, } } const tuple_node = try ast.Node.StructInitializerDot.alloc(c.arena, init_vals.items.len); tuple_node.* = .{ .dot = dot, .list_len = init_vals.items.len, .rtoken = try appendToken(c, .RBrace, "}"), }; mem.copy(*ast.Node, tuple_node.list(), init_vals.items); //(@import("std").mem.zeroInit(T, .{x})) const import_fn_call = try c.createBuiltinCall("@import", 1); const std_node = try transCreateNodeStringLiteral(c, "\"std\""); import_fn_call.params()[0] = std_node; import_fn_call.rparen_token = try appendToken(c, .RParen, ")"); const inner_field_access = try transCreateNodeFieldAccess(c, &import_fn_call.base, "mem"); const outer_field_access = try transCreateNodeFieldAccess(c, inner_field_access, "zeroInit"); const zero_init_call = try c.createCall(outer_field_access, 2); zero_init_call.params()[0] = node; zero_init_call.params()[1] = &tuple_node.base; zero_init_call.rtoken = try appendToken(c, .RParen, ")"); node = &zero_init_call.base; continue; }, .BangEqual => { op_token = try appendToken(c, .BangEqual, "!="); op_id = .BangEqual; }, .EqualEqual => { op_token = try appendToken(c, .EqualEqual, "=="); op_id = .EqualEqual; }, .Slash => { op_id = .Div; op_token = try appendToken(c, .Slash, "/"); }, .Percent => { op_id = .Mod; op_token = try appendToken(c, .Percent, "%"); }, .StringLiteral => { op_id = .ArrayCat; op_token = try appendToken(c, .PlusPlus, "++"); m.i -= 1; }, .Identifier => { op_id = .ArrayCat; op_token = try appendToken(c, .PlusPlus, "++"); m.i -= 1; }, else => { m.i -= 1; return node; }, } const cast_fn = if (bool_op) macroIntToBool else macroBoolToInt; const lhs_node = try cast_fn(c, node); const rhs_node = try parseCPrefixOpExpr(c, m, scope); const op_node = try c.arena.create(ast.Node.SimpleInfixOp); op_node.* = .{ .base = .{ .tag = op_id }, .op_token = op_token, .lhs = lhs_node, .rhs = try cast_fn(c, rhs_node), }; node = &op_node.base; } } fn parseCPrefixOpExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { switch (m.next().?) { .Bang => { const node = try transCreateNodeSimplePrefixOp(c, .BoolNot, .Bang, "!"); node.rhs = try parseCPrefixOpExpr(c, m, scope); return &node.base; }, .Minus => { const node = try transCreateNodeSimplePrefixOp(c, .Negation, .Minus, "-"); node.rhs = try parseCPrefixOpExpr(c, m, scope); return &node.base; }, .Plus => return try parseCPrefixOpExpr(c, m, scope), .Tilde => { const node = try transCreateNodeSimplePrefixOp(c, .BitNot, .Tilde, "~"); node.rhs = try parseCPrefixOpExpr(c, m, scope); return &node.base; }, .Asterisk => { const node = try parseCPrefixOpExpr(c, m, scope); return try transCreateNodePtrDeref(c, node); }, .Ampersand => { const node = try transCreateNodeSimplePrefixOp(c, .AddressOf, .Ampersand, "&"); node.rhs = try parseCPrefixOpExpr(c, m, scope); return &node.base; }, .Keyword_sizeof => { const inner = if (m.peek().? == .LParen) blk: { _ = m.next(); const inner = try parseCExpr(c, m, scope); if (m.next().? != .RParen) { try m.fail(c, "unable to translate C expr: expected ')'", .{}); return error.ParseError; } break :blk inner; } else try parseCPrefixOpExpr(c, m, scope); //(@import("std").meta.sizeof(dest, x)) const import_fn_call = try c.createBuiltinCall("@import", 1); const std_node = try transCreateNodeStringLiteral(c, "\"std\""); import_fn_call.params()[0] = std_node; import_fn_call.rparen_token = try appendToken(c, .RParen, ")"); const inner_field_access = try transCreateNodeFieldAccess(c, &import_fn_call.base, "meta"); const outer_field_access = try transCreateNodeFieldAccess(c, inner_field_access, "sizeof"); const sizeof_call = try c.createCall(outer_field_access, 1); sizeof_call.params()[0] = inner; sizeof_call.rtoken = try appendToken(c, .RParen, ")"); return &sizeof_call.base; }, .Keyword_alignof => { // TODO this won't work if using 's // #define alignof _Alignof if (m.next().? != .LParen) { try m.fail(c, "unable to translate C expr: expected '('", .{}); return error.ParseError; } const inner = try parseCExpr(c, m, scope); if (m.next().? != .RParen) { try m.fail(c, "unable to translate C expr: expected ')'", .{}); return error.ParseError; } const builtin_call = try c.createBuiltinCall("@alignOf", 1); builtin_call.params()[0] = inner; builtin_call.rparen_token = try appendToken(c, .RParen, ")"); return &builtin_call.base; }, else => { m.i -= 1; return try parseCSuffixOpExpr(c, m, scope); }, } } fn tokenSlice(c: *Context, token: ast.TokenIndex) []u8 { const tok = c.token_locs.items[token]; const slice = c.source_buffer.span()[tok.start..tok.end]; return if (mem.startsWith(u8, slice, "@\"")) slice[2 .. slice.len - 1] else slice; } fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node { switch (node.tag) { .ContainerDecl, .AddressOf, .Await, .BitNot, .BoolNot, .OptionalType, .Negation, .NegationWrap, .Resume, .Try, .ArrayType, .ArrayTypeSentinel, .PtrType, .SliceType, => return node, .Identifier => { const ident = node.castTag(.Identifier).?; if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| { if (value.cast(ast.Node.VarDecl)) |var_decl| return getContainer(c, var_decl.getInitNode().?); } }, .Period => { const infix = node.castTag(.Period).?; if (getContainerTypeOf(c, infix.lhs)) |ty_node| { if (ty_node.cast(ast.Node.ContainerDecl)) |container| { for (container.fieldsAndDecls()) |field_ref| { const field = field_ref.cast(ast.Node.ContainerField).?; const ident = infix.rhs.castTag(.Identifier).?; if (mem.eql(u8, tokenSlice(c, field.name_token), tokenSlice(c, ident.token))) { return getContainer(c, field.type_expr.?); } } } } }, else => {}, } return null; } fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node { if (ref.castTag(.Identifier)) |ident| { if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| { if (value.cast(ast.Node.VarDecl)) |var_decl| { if (var_decl.getTypeNode()) |ty| return getContainer(c, ty); } } } else if (ref.castTag(.Period)) |infix| { if (getContainerTypeOf(c, infix.lhs)) |ty_node| { if (ty_node.cast(ast.Node.ContainerDecl)) |container| { for (container.fieldsAndDecls()) |field_ref| { const field = field_ref.cast(ast.Node.ContainerField).?; const ident = infix.rhs.castTag(.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.getInitNode().? else return null; if (getContainerTypeOf(c, init)) |ty_node| { if (ty_node.castTag(.OptionalType)) |prefix| { if (prefix.rhs.cast(ast.Node.FnProto)) |fn_proto| { return fn_proto; } } } return null; } fn addMacros(c: *Context) !void { var it = c.global_scope.macro_table.iterator(); while (it.next()) |kv| { 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. try addTopLevelDecl(c, kv.key, try transCreateNodeMacroFn(c, kv.key, kv.value, proto_node)); } else { try addTopLevelDecl(c, kv.key, kv.value); } } }