diff --git a/src/cli.zig b/src/cli.zig index fd981159f46ab157af9a83a7a403f460203ea21c..92b3ac8c447191afa673912d7e915f0461f25114 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -8,7 +8,7 @@ options: []const Option = &.{}, positionals: Positionals = .none, help: HelpConfig = .{}, -const Option = struct { +pub const Option = struct { /// Should be formatted as the flag name: '--dump-ir' name: []const u8, /// Displayed in generated help menu @@ -32,6 +32,10 @@ const Option = struct { } }; + pub fn optional(comptime cond: bool, opts: []const Option) []const Option { + return if (cond) opts else &.{}; + } + const help: Option = .{ .name = "--help", .short = 'h', diff --git a/src/cli_compiler.zig b/src/cli_compiler.zig new file mode 100644 index 0000000000000000000000000000000000000000..9f5fbd66dc895351e25461f0fe33bf29e7506942 --- /dev/null +++ b/src/cli_compiler.zig @@ -0,0 +1,145 @@ +//! For testing the core compiler with minimal feature and +//! additional debugging parameters. + +const config = @import("config"); + +const arg_config: Cli = .{ + .options = &.{ + .{ .short = 'i', .name = "--dump-ir", .desc = "Dump intermediate representations" }, + .{ .name = "--dump-state", .desc = "Dump compiler state to JSON", .kind = .string }, + .{ .short = 'r', .name = "--report-trace", .desc = "Print stack traces to errors" }, + .{ .short = 'L', .name = "--no-leak-checks", .desc = "" }, + }, + .positionals = .single, + .help = .{ + .usage = "[options...] [entry file]", + }, +}; + +pub fn main() !u8 { + try tracer.init(); + defer tracer.deinit(); + try tracer.init_thread(null); + defer tracer.deinit_thread(); + + // unicode props is asserted to be initialized before core.Tokenizer is used + // this will result in a static page allocation, which is never cleaned up. + try core.unicode_props.init(); + + const use_gpa = builtin.mode == .Debug; + var gpa = if (use_gpa) + std.heap.GeneralPurposeAllocator(.{ + .stack_trace_frames = 100, + }){} + else + std.heap.ArenaAllocator.init(std.heap.page_allocator); + var no_leak_checks = false; + defer _ = if (use_gpa and !no_leak_checks) gpa.deinit(); + const alloc = gpa.allocator(); + + // Current working directory is required for CLI resolution. + var cwd_buf: [std.fs.max_path_bytes]u8 = undefined; + const cwd = try std.posix.getcwd(&cwd_buf); + + // Command line argument parsing. + const program_name = "zig build compiler --"; + _, const args = try Cli.programArgsAlloc(alloc, program_name); + const opts = arg_config.parse(program_name, args); + + const file_path = try std.fs.path.resolve(alloc, &.{ + cwd, + opts.positionals orelse + arg_config.showHelpAndExit(program_name), + }); + defer alloc.free(file_path); + + no_leak_checks = opts.no_leak_checks orelse false; + + // Execute compiler + { + var js_backend = core.Js{}; + + var comp = try core.Comp.init(.{ + .gpa = tracer.tracingAllocator(&alloc), + .options = .{ + .dump_ir = opts.dump_ir orelse false, + }, + .backend = .{ .js = &js_backend }, + }); + defer comp.deinit(); + + defer if (opts.dump_state) |path| { + dumpState(&comp, path) catch |err| { + std.debug.print("Failed to dump compilation state: {}", .{err}); + }; + }; + + const main_file = try comp.getFile(file_path); + + var fail_report: Report = undefined; + const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) { + error.TransformFail => { + std.debug.print("Transform failed:\n", .{}); + try fail_report.dump(.initComp(&comp, main_file), comp.out); + fail_report.deinit(comp.gpa); + return 1; + }, + error.ParseFail => { + // Already printed. + return 1; + }, + else => return err, + }; + + if (comp.options.dump_ir) { + const stderr = std.io.getStdErr().writer(); + try teg.dumpToWriter(stderr, null); + } + + const entry_point = try comp.enqueueEntryModule(main_file); + comp.runAllJobs() catch |err| switch (err) { + error.AnalysisFail => { + // error is already printed + return 1; + }, + else => return err, + }; + + { + // Output to standard out + const output = std.io.getStdOut().writer(); + var bufw = std.io.bufferedWriter(output); + const writer = bufw.writer(); + try js_backend.link(&comp, entry_point, writer.any()); + try bufw.flush(); + } + } + + std.process.cleanExit(); + return 0; +} + +const core = @import("core"); +const Comp = core.Comp; +const Report = core.Report; + +const tracer = @import("tracer"); +pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)); + +const std = @import("std"); +const builtin = @import("builtin"); +const Cli = @import("Cli.zig"); + +fn dumpState(comp: *const Comp, dump_path: []const u8) !void { + const file = try std.fs.cwd().createFile(dump_path, .{}); + defer file.close(); + + var bufw = std.io.bufferedWriter(file.writer()); + var jw = std.json.writeStream(bufw.writer(), .{ + .whitespace = .indent_2, + }); + + try comp.jsonStringify(&jw); + try bufw.writer().writeAll("\n"); + try bufw.flush(); +} diff --git a/src/cli_transform.zig b/src/cli_transform.zig index 44920293e9b2d00e4fbde53476d3b694c332b957..76200d6457a22fefd34076def4e44cfe3c58c97e 100644 --- a/src/cli_transform.zig +++ b/src/cli_transform.zig @@ -25,8 +25,7 @@ pub fn main() !u8 { const program_name, const raw_args = try Cli.programArgsAlloc(alloc, "transform"); defer Cli.programArgsFree(alloc, raw_args); - const args = arg_config.parse(raw_args) catch - arg_config.showHelpAndExit(program_name); + const args = arg_config.parse(program_name, raw_args); const file_path = args.positionals orelse arg_config.fatalWithHelp(program_name, "missing filepath argument\n", .{}); diff --git a/src/main.zig b/src/main.zig index ebbe1e8800327ce7b1a243bec9b8b4780c79dd1d..797569df9ccb3d4ba6dbd65fa0fbe92bd1ace20d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,9 +4,18 @@ //! for example, test the `core` module's compilation features without //! needing to compile the formatter. -const have_debug_features = builtin.mode == .Debug; const config = @import("config"); +const arg_config: Cli = .{ + .options = .{ + .{ .short = 'i', .name = "--dump-ir", .desc = "Dump intermediate representations" }, + .{ .name = "--dump-state", .desc = "Dump compiler state to JSON", .kind = .string }, + } ++ Cli.Option.optional(config.have_embedded_quickjs, &.{ + .{ .name = "--run", .desc = "Execute with QuickJS" }, + }), + .positionals = .single, +}; + pub fn main() !u8 { try tracer.init(); defer tracer.deinit(); @@ -24,8 +33,7 @@ pub fn main() !u8 { }){} else std.heap.ArenaAllocator.init(std.heap.page_allocator); - var no_leak_checks = false; - defer _ = if (use_gpa and !no_leak_checks) gpa.deinit(); + defer _ = if (use_gpa) gpa.deinit(); const alloc = gpa.allocator(); // Current working directory is required for CLI resolution. @@ -33,95 +41,16 @@ pub fn main() !u8 { const cwd = try std.posix.getcwd(&cwd_buf); // Command line argument parsing. - var maybe_file_path: ?[]const u8 = null; - defer if (maybe_file_path) |path| alloc.free(path); - var exe_name: ?[]const u8 = null; - defer if (exe_name) |path| alloc.free(path); - var dump_ir = false; - var dump_state: ?[]const u8 = null; - var run = false; - var has_errors = false; - { - var args = try std.process.argsWithAllocator(alloc); - defer args.deinit(); - - exe_name = try alloc.dupe(u8, std.fs.path.basename(args.next() orelse "cli")); - - while (args.next()) |arg| { - if (std.mem.startsWith(u8, arg, "-")) { - if (have_debug_features) { - if (std.mem.eql(u8, arg, "--report-trace")) { - core.Report.debug_show_report_trace = true; - continue; - } - if (std.mem.eql(u8, arg, "--no-leak-checks")) { - no_leak_checks = true; - continue; - } - } - - if (std.mem.eql(u8, arg, "--dump-ir")) { - dump_ir = true; - continue; - } - - if (std.mem.startsWith(u8, arg, "--dump-state=")) { - dump_state = arg["--dump-state=".len..]; - continue; - } - - if (config.have_embedded_quickjs and std.mem.eql(u8, arg, "--run")) { - run = true; - continue; - } - - std.debug.print("error: unknown flag '{s}'\n", .{arg}); - has_errors = true; - break; - } - - if (maybe_file_path == null) { - maybe_file_path = try std.fs.path.resolve(alloc, &.{ cwd, arg }); - } else { - std.debug.print("error: multiple files specified\n", .{}); - maybe_file_path = null; - break; - } - } - } - - if (maybe_file_path == null) has_errors = true; - if (has_errors) { - std.debug.print("Usage: {s} [flags] \n", .{exe_name.?}); - { - std.debug.print( - // | 60 chars wrap point| - \\ - \\Flags: - \\ --dump-ir Dump intermediate representations - \\ --dump-state= Dump compiler state to JSON - \\ - , .{}); - } - if (config.have_embedded_quickjs) { - std.debug.print( - \\ --run Run program using QuickJS - \\ - , .{}); - } - if (have_debug_features) { - std.debug.print( - \\ - \\Debug Mode Flags: - \\ --report-trace Print stack traces per error - \\ --no-leak-checks - \\ - , .{}); - } - std.debug.print("\nLanguage SDK v" ++ config.version_string ++ "\n", .{}); - return 1; - } - const file_path = maybe_file_path.?; + const program_name = "zig build compiler --"; + _, const args = try Cli.programArgsAlloc(alloc, program_name); + const opts = arg_config.parse(program_name, args); + + const file_path = try std.fs.path.resolve(alloc, &.{ + cwd, + opts.positionals orelse + arg_config.showHelpAndExit(program_name), + }); + defer alloc.free(file_path); // Execute compiler { @@ -130,13 +59,13 @@ pub fn main() !u8 { var comp = try core.Comp.init(.{ .gpa = tracer.tracingAllocator(&alloc), .options = .{ - .dump_ir = dump_ir, + .dump_ir = opts.dump_ir orelse false, }, .backend = .{ .js = &js_backend }, }); defer comp.deinit(); - defer if (dump_state) |path| { + defer if (opts.dump_state) |path| { dumpState(&comp, path) catch |err| { std.debug.print("Failed to dump compilation state: {}", .{err}); }; @@ -173,7 +102,7 @@ pub fn main() !u8 { else => return err, }; - if (config.have_embedded_quickjs and run) { + if (config.have_embedded_quickjs and (opts.run orelse false)) { // Execute with QuickJS var out: std.ArrayListUnmanaged(u8) = .{}; defer out.deinit(alloc); @@ -244,6 +173,7 @@ pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)) const std = @import("std"); const builtin = @import("builtin"); +const Cli = @import("Cli.zig"); fn dumpState(comp: *const Comp, dump_path: []const u8) !void { const file = try std.fs.cwd().createFile(dump_path, .{});