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