// flashc — the Flash compiler driver. // // Flash is a small systems language whose backend lowers to Zig (the // "Tier 0" strategy): the long game is to rewrite FlashOS and // its shell in Flash, reusing the Zig toolchain for code generation while // Flash and Zig sources coexist module by module during the migration. // // This driver wires the pipeline — lex -> parse -> sema -> lower — end to // end: it reads a .flash file, parses it, runs the semantic checks, // and writes the lowered Zig to stdout. Tokens can still be inspected on // their own with `--dump-tokens`, and a file can be reformatted in place with // `fmt`. Parse and semantic diagnostics are written to stderr, so the lowered // source on stdout stays clean for redirection (`flashc file.flash > out.zig`). // // The `main(init: std.process.Init)` entry, the `std.Io` reader/writer // interfaces, and `init.minimal.args` follow the host-tool conventions in // the FlashOS tree (scripts/generate_syms.zig, tools/gen_shadow.zig). // // Usage: // flashc --version // flashc --dump-tokens // flashc fmt [--check] (reformat a .flash file in place) // flashc (transpile to Zig, written to stdout) const std = @import("std"); const Io = std.Io; const build_options = @import("build_options"); const Lexer = @import("lexer.zig").Lexer; const parser = @import("parser.zig"); const sema = @import("sema.zig"); const lower = @import("lower.zig"); const fmt = @import("fmt.zig"); const usage = \\flashc — the Flash compiler (Flash -> Zig) \\ \\usage: \\ flashc --version \\ flashc --dump-tokens \\ flashc fmt [--check] \\ flashc \\ ; pub fn main(init: std.process.Init) !void { const io = init.io; const arena = init.arena.allocator(); var stdout_buf: [4096]u8 = undefined; var stdout_obj = std.Io.File.stdout().writer(io, &stdout_buf); const out = &stdout_obj.interface; defer out.flush() catch {}; var stderr_buf: [4096]u8 = undefined; var stderr_obj = std.Io.File.stderr().writer(io, &stderr_buf); const err_out = &stderr_obj.interface; defer err_out.flush() catch {}; const args = try init.minimal.args.toSlice(arena); if (args.len < 2) { try out.writeAll(usage); return error.NoArguments; } const cmd = args[1]; if (std.mem.eql(u8, cmd, "--version")) { try out.print("flashc {s}\n", .{build_options.version}); return; } if (std.mem.eql(u8, cmd, "--help") or std.mem.eql(u8, cmd, "-h")) { try out.writeAll(usage); return; } if (std.mem.eql(u8, cmd, "--dump-tokens")) { if (args.len < 3) { try out.writeAll("--dump-tokens needs a file\n"); return error.NoInput; } try dumpTokens(out, try readFile(io, arena, args[2])); return; } if (std.mem.eql(u8, cmd, "fmt")) { // `flashc fmt ` rewrites the file to canonical Flash; `--check` // writes nothing and exits non-zero when the file would change. var check_mode = false; var fmt_path: ?[]const u8 = null; var ai: usize = 2; while (ai < args.len) : (ai += 1) { const a = args[ai]; if (std.mem.eql(u8, a, "--check")) { check_mode = true; } else if (fmt_path == null) { fmt_path = a; } } const fpath = fmt_path orelse { try err_out.writeAll("fmt needs a file\n"); try err_out.flush(); return error.NoInput; }; const fsrc = try readFile(io, arena, fpath); var fp = parser.Parser.init(arena, fsrc); const fprog = fp.parseProgram() catch |err| switch (err) { // A parse error refuses the file untouched — a formatter must never // destroy code. Print the one-line diagnostic and exit non-zero. error.UnexpectedToken => { if (fp.diag) |d| { try err_out.print("flashc: {s}:{d}: error: {s}\n", .{ fpath, d.line, d.msg }); } else { try err_out.print("flashc: {s}: parse error\n", .{fpath}); } try err_out.flush(); std.process.exit(1); }, else => return err, }; const formatted = try fmt.render(arena, fprog, fp.comments, fsrc); if (std.mem.eql(u8, formatted, fsrc)) return; // already canonical if (check_mode) { // Not canonical: report the path and exit non-zero, writing nothing. // exit() skips deferred flushes, so flush stdout explicitly. try out.print("{s}\n", .{fpath}); try out.flush(); std.process.exit(1); } try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = fpath, .data = formatted }); return; } // Otherwise treat the argument as an input file and run the pipeline. const path = cmd; const src = try readFile(io, arena, path); var p = parser.Parser.init(arena, src); const program = p.parseProgram() catch |err| switch (err) { // A user-facing syntax error: print the one-line diagnostic and exit // non-zero. exit() skips deferred flushes, so flush stderr explicitly. error.UnexpectedToken => { if (p.diag) |d| { try err_out.print("flashc: {s}:{d}: error: {s}\n", .{ path, d.line, d.msg }); } else { try err_out.print("flashc: {s}: parse error\n", .{path}); } try err_out.flush(); std.process.exit(1); }, else => return err, // OutOfMemory and the like are exceptional }; const diags = try sema.check(arena, program); if (diags.len > 0) { // Report every diagnostic in source order (by anchor offset), then exit // non-zero. exit() skips deferred flushes, so flush stderr explicitly. std.mem.sort(sema.Diag, diags, src, lessByAnchor); for (diags) |d| { const loc = sema.locate(src, d.anchor); try err_out.print("flashc: {s}:{d}:{d}: error: {s}\n", .{ path, loc.line, loc.col, d.msg }); if (d.note_anchor) |na| { const nloc = sema.locate(src, na); try err_out.print("flashc: {s}:{d}:{d}: note: {s}\n", .{ path, nloc.line, nloc.col, d.note_msg.? }); } } try err_out.flush(); std.process.exit(1); } const zig_src = try lower.emit(arena, program); try out.writeAll(zig_src); } // Order diagnostics by the byte offset of their anchor in the source, so they // print top-to-bottom regardless of the order the checker collected them. fn lessByAnchor(src: []const u8, a: sema.Diag, b: sema.Diag) bool { _ = src; return @intFromPtr(a.anchor.ptr) < @intFromPtr(b.anchor.ptr); } fn readFile(io: Io, arena: std.mem.Allocator, path: []const u8) ![]u8 { return std.Io.Dir.cwd().readFileAlloc(io, path, arena, .limited(1 << 20)); } fn dumpTokens(out: *Io.Writer, src: []const u8) !void { var lx = Lexer.init(src); while (true) { const t = lx.next(); try out.print("{d:>4} {s:<12} {s}\n", .{ t.line, @tagName(t.kind), t.lexeme(src) }); if (t.kind == .eof) break; } }