master
1const std = @import("std");
2const builtin = @import("builtin");
3const Allocator = std.mem.Allocator;
4const cli = @import("cli.zig");
5const Dependencies = @import("compile.zig").Dependencies;
6const aro = @import("aro");
7
8const PreprocessError = error{ ArgError, GeneratedSourceError, PreprocessError, FileTooBig, OutOfMemory, WriteFailed };
9
10pub fn preprocess(
11 comp: *aro.Compilation,
12 writer: *std.Io.Writer,
13 /// Expects argv[0] to be the command name
14 argv: []const []const u8,
15 maybe_dependencies: ?*Dependencies,
16) PreprocessError!void {
17 try comp.addDefaultPragmaHandlers();
18
19 var driver: aro.Driver = .{ .comp = comp, .diagnostics = comp.diagnostics, .aro_name = "arocc" };
20 defer driver.deinit();
21
22 var macro_buf: std.ArrayList(u8) = .empty;
23 defer macro_buf.deinit(comp.gpa);
24
25 var discard_buffer: [64]u8 = undefined;
26 var discarding: std.Io.Writer.Discarding = .init(&discard_buffer);
27 _ = driver.parseArgs(&discarding.writer, ¯o_buf, argv) catch |err| switch (err) {
28 error.FatalError => return error.ArgError,
29 error.OutOfMemory => |e| return e,
30 error.WriteFailed => unreachable,
31 };
32 try comp.initSearchPath(driver.includes.items, false);
33
34 if (hasAnyErrors(comp)) return error.ArgError;
35
36 // .include_system_defines gives us things like _WIN32
37 const builtin_macros = comp.generateBuiltinMacros(.include_system_defines) catch |err| switch (err) {
38 error.FatalError => return error.GeneratedSourceError,
39 else => |e| return e,
40 };
41 const user_macros = comp.addSourceFromBuffer("<command line>", macro_buf.items) catch |err| switch (err) {
42 error.FatalError => return error.GeneratedSourceError,
43 else => |e| return e,
44 };
45 const source = driver.inputs.items[0];
46
47 if (hasAnyErrors(comp)) return error.GeneratedSourceError;
48
49 comp.generated_buf.items.len = 0;
50 var pp = aro.Preprocessor.initDefault(comp) catch |err| switch (err) {
51 error.FatalError => return error.GeneratedSourceError,
52 error.OutOfMemory => |e| return e,
53 };
54 defer pp.deinit();
55
56 if (comp.langopts.ms_extensions) {
57 comp.ms_cwd_source_id = source.id;
58 }
59
60 pp.preserve_whitespace = true;
61 pp.linemarkers = .line_directives;
62
63 pp.preprocessSources(.{ .main = source, .builtin = builtin_macros, .command_line = user_macros }) catch |err| switch (err) {
64 error.FatalError => return error.PreprocessError,
65 else => |e| return e,
66 };
67
68 if (hasAnyErrors(comp)) return error.PreprocessError;
69
70 try pp.prettyPrintTokens(writer, .result_only);
71
72 if (maybe_dependencies) |dependencies| {
73 for (comp.sources.values()) |comp_source| {
74 if (comp_source.id == builtin_macros.id or comp_source.id == user_macros.id) continue;
75 if (comp_source.id.index == .unused or comp_source.id.index == .generated) continue;
76 const duped_path = try dependencies.allocator.dupe(u8, comp_source.path);
77 errdefer dependencies.allocator.free(duped_path);
78 try dependencies.list.append(dependencies.allocator, duped_path);
79 }
80 }
81}
82
83fn hasAnyErrors(comp: *aro.Compilation) bool {
84 return comp.diagnostics.errors != 0;
85}
86
87/// `arena` is used for temporary -D argument strings and the INCLUDE environment variable.
88/// The arena should be kept alive at least as long as `argv`.
89pub fn appendAroArgs(arena: Allocator, argv: *std.ArrayList([]const u8), options: cli.Options, system_include_paths: []const []const u8) !void {
90 try argv.appendSlice(arena, &.{
91 "-E",
92 "--comments",
93 "-fuse-line-directives",
94 "-fgnuc-version=4.2.1",
95 "--target=x86_64-windows-msvc",
96 "--emulate=msvc",
97 "-nostdinc",
98 "-DRC_INVOKED",
99 "-D_WIN32", // undocumented, but defined by default
100 });
101 for (options.extra_include_paths.items) |extra_include_path| {
102 try argv.append(arena, "-I");
103 try argv.append(arena, extra_include_path);
104 }
105
106 for (system_include_paths) |include_path| {
107 try argv.append(arena, "-isystem");
108 try argv.append(arena, include_path);
109 }
110
111 if (!options.ignore_include_env_var) {
112 const INCLUDE = std.process.getEnvVarOwned(arena, "INCLUDE") catch "";
113
114 // The only precedence here is llvm-rc which also uses the platform-specific
115 // delimiter. There's no precedence set by `rc.exe` since it's Windows-only.
116 const delimiter = switch (builtin.os.tag) {
117 .windows => ';',
118 else => ':',
119 };
120 var it = std.mem.tokenizeScalar(u8, INCLUDE, delimiter);
121 while (it.next()) |include_path| {
122 try argv.append(arena, "-isystem");
123 try argv.append(arena, include_path);
124 }
125 }
126
127 var symbol_it = options.symbols.iterator();
128 while (symbol_it.next()) |entry| {
129 switch (entry.value_ptr.*) {
130 .define => |value| {
131 try argv.append(arena, "-D");
132 const define_arg = try std.fmt.allocPrint(arena, "{s}={s}", .{ entry.key_ptr.*, value });
133 try argv.append(arena, define_arg);
134 },
135 .undefine => {
136 try argv.append(arena, "-U");
137 try argv.append(arena, entry.key_ptr.*);
138 },
139 }
140 }
141}