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, &macro_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}