1const std = @import("std");
2const assert = std.debug.assert;
3const mem = std.mem;
4const process = std.process;
5const Io = std.Io;
6
7const aro = @import("aro");
8const compiler_util = @import("../util.zig");
9
10const Translator = @import("Translator.zig");
11
12const fast_exit = @import("builtin").mode != .debug;
13
14pub fn main(init: process.Init) u8 {
15 const gpa = init.gpa;
16 const arena = init.arena.allocator();
17 const io = init.io;
18 const environ_map = init.environ_map;
19
20 const args = init.minimal.args.toSlice(arena) catch {
21 std.debug.print("ran out of memory allocating arguments\n", .{});
22 if (fast_exit) process.exit(1);
23 return 1;
24 };
25
26 var zig_integration = false;
27 if (args.len > 1 and std.mem.eql(u8, args[1], "--zig-integration")) {
28 zig_integration = true;
29 }
30
31 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(environ_map);
32 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(environ_map);
33
34 var stderr_buf: [1024]u8 = undefined;
35 var stderr = Io.File.stderr().writer(io, &stderr_buf);
36 var diagnostics: aro.Diagnostics = switch (zig_integration) {
37 false => .{ .output = .{ .to_writer = .{
38 .mode = Io.Terminal.Mode.detect(io, stderr.file, NO_COLOR, CLICOLOR_FORCE) catch .no_color,
39 .writer = &stderr.interface,
40 } } },
41 true => .{ .output = .{ .to_list = .{ .arena = .init(gpa) } } },
42 };
43 defer diagnostics.deinit();
44
45 var comp = aro.Compilation.init(.{
46 .gpa = gpa,
47 .arena = arena,
48 .io = io,
49 .diagnostics = &diagnostics,
50 .environ_map = environ_map,
51 }) catch |err| switch (err) {
52 error.OutOfMemory => {
53 std.debug.print("ran out of memory initializing C compilation\n", .{});
54 if (fast_exit) process.exit(1);
55 return 1;
56 },
57 };
58 defer comp.deinit();
59
60 var driver: aro.Driver = .{ .comp = &comp, .diagnostics = &diagnostics, .aro_name = "aro" };
61 defer driver.deinit();
62
63 var toolchain: aro.Toolchain = .{ .driver = &driver };
64 defer toolchain.deinit();
65
66 translate(&driver, &toolchain, args, zig_integration) catch |err| switch (err) {
67 error.OutOfMemory => {
68 std.debug.print("ran out of memory translating\n", .{});
69 if (fast_exit) process.exit(1);
70 return 1;
71 },
72 error.FatalError => if (zig_integration) {
73 serveErrorBundle(arena, io, &diagnostics) catch |bundle_err| {
74 std.debug.print("unable to serve error bundle: {}\n", .{bundle_err});
75 if (fast_exit) process.exit(1);
76 return 1;
77 };
78
79 if (fast_exit) process.exit(0);
80 return 0;
81 } else {
82 if (fast_exit) process.exit(1);
83 return 1;
84 },
85 error.WriteFailed => {
86 std.debug.print("unable to write to stdout\n", .{});
87 if (fast_exit) process.exit(1);
88 return 1;
89 },
90 };
91 assert(comp.diagnostics.errors == 0 or !zig_integration);
92 if (fast_exit) process.exit(@intFromBool(comp.diagnostics.errors != 0));
93 return @intFromBool(comp.diagnostics.errors != 0);
94}
95
96fn serveErrorBundle(arena: std.mem.Allocator, io: Io, diagnostics: *const aro.Diagnostics) !void {
97 const error_bundle = try compiler_util.aroDiagnosticsToErrorBundle(
98 diagnostics,
99 arena,
100 "translation failure",
101 );
102 var stdout_buffer: [1024]u8 = undefined;
103 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
104 var server: std.zig.Server = .{
105 .out = &stdout_writer.interface,
106 .in = undefined,
107 };
108 try server.serveErrorBundle(error_bundle);
109}
110
111pub const usage =
112 \\Usage {s}: [options] file [CC options]
113 \\
114 \\Options:
115 \\ --help Print this message
116 \\ --version Print translate-c version
117 \\ -fmodule-libs Import libraries as modules
118 \\ -fno-module-libs (default) Install libraries next to output file
119 \\ -fpub-static (default) Translate static functions as pub
120 \\ -fno-pub-static Do not translate static functions as pub
121 \\ -ffunc-bodies (default) Translate function bodies
122 \\ -fno-func-bodies Do not translate function bodies
123 \\ -fkeep-macro-literals (default) Preserve macro names for literals
124 \\ -fno-keep-macro-literals Do not preserve macro names for literals
125 \\ -fdefault-init Default initialize struct fields
126 \\ -fno-default-init (default) Do not default initialize struct fields
127 \\ -fstrict-flex-arrays=<n> Control when to treat a trailing array as a flexible array member (default: 2)
128 \\ 0: any trailing array
129 \\ 1: size [0]/[1]/[]
130 \\ 2: size [0]/[]
131 \\ 3: [] only
132 \\
133 \\
134;
135
136fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: []const [:0]const u8, zig_integration: bool) !void {
137 const gpa = d.comp.gpa;
138 const io = d.comp.io;
139
140 var module_libs = true;
141 var pub_static = true;
142 var func_bodies = true;
143 var keep_macro_literals = true;
144 var default_init = true;
145 var strict_flex_arrays: Translator.StrictFlexArraysLevel = .@"2";
146
147 var aro_args: std.ArrayList([:0]const u8) = try .initCapacity(gpa, args.len);
148 defer aro_args.deinit(gpa);
149
150 for (args, 0..) |arg, i| {
151 if (mem.eql(u8, arg, "--help")) {
152 var stdout_buf: [512]u8 = undefined;
153 var stdout = Io.File.stdout().writer(io, &stdout_buf);
154 try stdout.interface.print(usage, .{args[0]});
155 try stdout.interface.flush();
156 return;
157 } else if (mem.eql(u8, arg, "--version")) {
158 var stdout_buf: [512]u8 = undefined;
159 var stdout = Io.File.stdout().writer(io, &stdout_buf);
160 // TODO add version
161 try stdout.interface.writeAll("0.0.0-dev\n");
162 try stdout.interface.flush();
163 return;
164 } else if (mem.eql(u8, arg, "--zig-integration")) {
165 if (i != 1 or !zig_integration)
166 return d.fatal("--zig-integration must be the first argument", .{});
167 } else if (mem.eql(u8, arg, "-fmodule-libs")) {
168 module_libs = true;
169 } else if (mem.eql(u8, arg, "-fno-module-libs")) {
170 module_libs = false;
171 } else if (mem.eql(u8, arg, "-fpub-static")) {
172 pub_static = true;
173 } else if (mem.eql(u8, arg, "-fno-pub-static")) {
174 pub_static = false;
175 } else if (mem.eql(u8, arg, "-ffunc-bodies")) {
176 func_bodies = true;
177 } else if (mem.eql(u8, arg, "-fno-func-bodies")) {
178 func_bodies = false;
179 } else if (mem.eql(u8, arg, "-fkeep-macro-literals")) {
180 keep_macro_literals = true;
181 } else if (mem.eql(u8, arg, "-fno-keep-macro-literals")) {
182 keep_macro_literals = false;
183 } else if (mem.eql(u8, arg, "-fdefault-init")) {
184 default_init = true;
185 } else if (mem.eql(u8, arg, "-fno-default-init")) {
186 default_init = false;
187 } else if (mem.startsWith(u8, arg, "-fstrict-flex-arrays=")) {
188 const val_str = arg["-fstrict-flex-arrays=".len..];
189 if (val_str.len != 1 or val_str[0] < '0' or val_str[0] > '3') {
190 return d.fatal("-fstrict-flex-arrays= requires a value of '0', '1', '2', or '3'", .{});
191 }
192 strict_flex_arrays = @fromBackingInt(@intCast(val_str[0] - '0'));
193 } else {
194 aro_args.appendAssumeCapacity(arg);
195 }
196 }
197 const user_macros = macros: {
198 var macro_buf: std.ArrayList(u8) = .empty;
199 defer macro_buf.deinit(gpa);
200
201 var discard_buf: [256]u8 = undefined;
202 var discarding: Io.Writer.Discarding = .init(&discard_buf);
203 assert(!try d.parseArgs(&discarding.writer, &macro_buf, aro_args.items));
204 if (macro_buf.items.len > std.math.maxInt(u32)) {
205 return d.fatal("user provided macro source exceeded max size", .{});
206 }
207
208 const has_output_file = if (d.output_name) |path|
209 !std.mem.eql(u8, path, "-")
210 else
211 false;
212 if (zig_integration and !has_output_file) {
213 return d.fatal("--zig-integration requires specifying an output file", .{});
214 }
215
216 const content = try macro_buf.toOwnedSlice(gpa);
217 errdefer gpa.free(content);
218
219 break :macros try d.comp.addSourceFromOwnedBuffer("<command line>", content, .user);
220 };
221
222 if (d.inputs.items.len != 1) {
223 return d.fatal("expected exactly one input file", .{});
224 }
225 const source = d.inputs.items[0];
226
227 tc.discover() catch |er| switch (er) {
228 error.OutOfMemory => |e| return e,
229 error.TooManyMultilibs => return d.fatal("found more than one multilib with the same priority", .{}),
230 };
231 try tc.defineSystemIncludes();
232 try d.comp.initSearchPath(d.includes.items, d.verbose_search_path);
233
234 const builtin_macros = d.comp.generateBuiltinMacros(d.system_defines) catch |err| switch (err) {
235 error.FileTooBig => return d.fatal("builtin macro source exceeded max size", .{}),
236 else => |e| return e,
237 };
238
239 var pp = try aro.Preprocessor.init(d.comp, .{
240 .base_file = source.id,
241 });
242 defer pp.deinit();
243
244 var name_buf: [std.fs.max_name_bytes]u8 = undefined;
245 // Omit the source file from the dep file so that it can be tracked separately.
246 // In the Zig compiler we want to omit it from the cache hash since it will
247 // be written to a tmp file then renamed into place, meaning the path will be
248 // wrong as soon as the work is done.
249 var opt_dep_file = try d.initDepFile(source, &name_buf, true);
250 defer if (opt_dep_file) |*dep_file| dep_file.deinit(gpa);
251
252 if (opt_dep_file) |*dep_file| pp.dep_file = dep_file;
253
254 try pp.preprocessSources(.{
255 .main = source,
256 .builtin = builtin_macros,
257 .command_line = user_macros,
258 .imacros = d.imacros.items,
259 .implicit_includes = d.implicit_includes.items,
260 });
261
262 var c_tree = try pp.parse();
263 defer c_tree.deinit();
264
265 if (d.diagnostics.errors != 0) {
266 if (fast_exit and !zig_integration) process.exit(1);
267 return error.FatalError;
268 }
269
270 var out_buf: [4096]u8 = undefined;
271 if (opt_dep_file) |dep_file| {
272 const dep_file_name = try d.getDepFileName(source, out_buf[0..std.fs.max_name_bytes]);
273
274 const file = if (dep_file_name) |path|
275 d.comp.cwd.createFile(io, path, .{}) catch |er|
276 return d.fatal("unable to create dependency file '{s}': {s}", .{ path, aro.Driver.errorDescription(er) })
277 else
278 Io.File.stdout();
279 defer if (dep_file_name != null) file.close(io);
280
281 var file_writer = file.writer(io, &out_buf);
282 dep_file.write(&file_writer.interface) catch
283 return d.fatal("unable to write dependency file: {s}", .{aro.Driver.errorDescription(file_writer.err.?)});
284 }
285
286 const rendered_zig = try Translator.translate(.{
287 .gpa = gpa,
288 .comp = d.comp,
289 .pp = &pp,
290 .tree = &c_tree,
291 .module_libs = module_libs,
292 .pub_static = pub_static,
293 .func_bodies = func_bodies,
294 .keep_macro_literals = keep_macro_literals,
295 .default_init = default_init,
296 .strict_flex_arrays = strict_flex_arrays,
297 });
298 defer gpa.free(rendered_zig);
299
300 var close_out_file = false;
301 var out_file_path: []const u8 = "<stdout>";
302 var out_file: Io.File = .stdout();
303 defer if (close_out_file) out_file.close(io);
304
305 if (d.output_name) |path| blk: {
306 if (std.mem.eql(u8, path, "-")) break :blk;
307 if (std.fs.path.dirname(path)) |dirname| {
308 Io.Dir.cwd().createDirPath(io, dirname) catch |err|
309 return d.fatal("failed to create path to '{s}': {s}", .{ path, aro.Driver.errorDescription(err) });
310 }
311 out_file = Io.Dir.cwd().createFile(io, path, .{}) catch |err| {
312 return d.fatal("failed to create output file '{s}': {s}", .{ path, aro.Driver.errorDescription(err) });
313 };
314 close_out_file = true;
315 out_file_path = path;
316 }
317
318 var out_writer = out_file.writer(io, &out_buf);
319 out_writer.interface.writeAll(rendered_zig) catch {};
320 out_writer.interface.flush() catch {};
321 if (out_writer.err) |write_err|
322 return d.fatal("failed to write result to '{s}': {s}", .{ out_file_path, aro.Driver.errorDescription(write_err) });
323
324 if (fast_exit and !zig_integration) process.exit(0);
325}
326
327test {
328 _ = Translator;
329 _ = @import("helpers.zig");
330 _ = @import("PatternList.zig");
331}