| 1 | const std = @import("std"); |
| 2 | const assert = std.debug.assert; |
| 3 | const mem = std.mem; |
| 4 | const process = std.process; |
| 5 | const Io = std.Io; |
| 6 | |
| 7 | const aro = @import("aro"); |
| 8 | const compiler_util = @import("../util.zig"); |
| 9 | |
| 10 | const Translator = @import("Translator.zig"); |
| 11 | |
| 12 | const fast_exit = @import("builtin").mode != .debug; |
| 13 | |
| 14 | pub 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 | |
| 96 | fn 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 | |
| 111 | pub 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 | |
| 136 | fn 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 | |
| 327 | test { |
| 328 | _ = Translator; |
| 329 | _ = @import("helpers.zig"); |
| 330 | _ = @import("PatternList.zig"); |
| 331 | } |