From 127db83041d793ad8c7e607ce82adbf3ebe8e721 Mon Sep 17 00:00:00 2001 From: Brett Broadhurst Date: Mon, 20 Apr 2026 23:02:32 -0600 Subject: [PATCH] feat: loading precompiled sections, strings/constants/knots --- src/InternPool.zig | 18 +- src/Story.zig | 10 +- src/Story/Dumper.zig | 36 +++- src/Story/Loader.zig | 480 +++++++++++++++++++++++++++++++++++++------ src/compile.zig | 22 +- src/main.zig | 129 +++++++----- 6 files changed, 553 insertions(+), 142 deletions(-) diff --git a/src/InternPool.zig b/src/InternPool.zig index 1ae52de..c2c5363 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -103,23 +103,23 @@ pub fn getOrPutString( gpa: std.mem.Allocator, bytes: []const u8, ) error{OutOfMemory}!NullTerminatedString { - const str_index: u32 = @intCast(ip.string_bytes.items.len); - try ip.string_bytes.ensureUnusedCapacity(gpa, bytes.len + 1); - ip.string_bytes.appendSliceAssumeCapacity(bytes); + const string_bytes = &ip.string_bytes; + const str_index: u32 = @intCast(string_bytes.items.len); + try string_bytes.appendSlice(gpa, bytes); - const key: []const u8 = ip.string_bytes.items[str_index..]; + const key: []const u8 = string_bytes.items[str_index..]; const gop = try ip.string_table.getOrPutContextAdapted(gpa, key, StringIndexAdapter{ - .bytes = &ip.string_bytes, + .bytes = string_bytes, }, StringIndexContext{ - .bytes = &ip.string_bytes, + .bytes = string_bytes, }); if (gop.found_existing) { - ip.string_bytes.shrinkRetainingCapacity(str_index); + string_bytes.shrinkRetainingCapacity(str_index); return @enumFromInt(gop.key_ptr.*); } else { gop.key_ptr.* = str_index; - try ip.string_bytes.append(gpa, 0); - return @enumFromInt(gop.key_ptr.*); + try string_bytes.append(gpa, 0); + return @enumFromInt(str_index); } } diff --git a/src/Story.zig b/src/Story.zig index b492933..ba41b6d 100644 --- a/src/Story.zig +++ b/src/Story.zig @@ -127,7 +127,15 @@ pub const CallFrame = struct { bp: usize, }; -pub const Value = union(enum) { +pub const ValueType = enum(u8) { + nil, + bool, + int, + float, + object, +}; + +pub const Value = union(ValueType) { nil, bool: bool, int: i64, diff --git a/src/Story/Dumper.zig b/src/Story/Dumper.zig index 64a8eb6..b30bdfe 100644 --- a/src/Story/Dumper.zig +++ b/src/Story/Dumper.zig @@ -9,6 +9,31 @@ const Dumper = @This(); story: *const Story, indent_level: usize = 0, +fn dumpStringBytes(dumper: *Dumper, writer: *std.Io.Writer) !void { + const bytes = dumper.story.string_bytes; + var start: usize = 0; + while (start < bytes.len) { + const end = std.mem.indexOfScalarPos(u8, bytes, start, 0) orelse break; + const str = bytes[start..end]; + + try writer.print("[{d:04}] ", .{start}); + for (str) |b| try writer.print("{x:02} ", .{b}); + try writer.print("00: {s}\n", .{str}); + start = end + 1; + } + return writer.flush(); +} + +fn dumpConstants(dumper: *Dumper, writer: *std.Io.Writer) !void { + const story = dumper.story; + var i: usize = 0; + while (i < story.constants_pool.len) : (i += 1) { + const global_constant = &story.constants_pool[i]; + try dumper.dumpValue(writer, global_constant); + try writer.writeAll("\n"); + } +} + fn dumpSimpleInst(_: *Dumper, w: *std.Io.Writer, offset: usize, op: Opcode) !usize { try w.print("{s}\n", .{@tagName(op)}); return offset + 1; @@ -312,14 +337,11 @@ pub fn dumpValue(_: Dumper, w: *std.Io.Writer, value: *const Value) !void { pub fn dump(story: *Story, writer: *std.Io.Writer) !void { var story_dumper: Dumper = .{ .story = story }; - try writer.writeAll("=== Constants ===\n"); + try writer.writeAll("=== Strings ===\n"); + try story_dumper.dumpStringBytes(writer); - var i: usize = 0; - while (i < story.constants_pool.len) : (i += 1) { - const global_constant = &story.constants_pool[i]; - try story_dumper.dumpValue(writer, global_constant); - try writer.writeAll("\n"); - } + try writer.writeAll("=== Constants ===\n"); + try story_dumper.dumpConstants(writer); try writer.writeAll("\n"); try writer.writeAll("=== Globals ===\n"); diff --git a/src/Story/Loader.zig b/src/Story/Loader.zig index cb357d9..7681187 100644 --- a/src/Story/Loader.zig +++ b/src/Story/Loader.zig @@ -2,63 +2,237 @@ const std = @import("std"); const InternPool = @import("../InternPool.zig"); const Compilation = @import("../compile.zig").Compilation; const Story = @import("../Story.zig"); -const Value = Story.Value; const Object = Story.Object; +const Loader = @This(); -fn makeValueFromInterned(story: *Story, value: InternPool.Key) !Story.Value { - return switch (value) { - .bool => |boolean| .{ .bool = boolean }, - .int => |int| .{ .int = @intCast(int) }, - .float => |float| .{ .float = @bitCast(float) }, - .str => |index| blk: { - const str_object = try Object.String.create(story, .{ - .bytes = stringBytes(story, index), - }); - break :blk .{ .object = &str_object.base }; - }, +gpa: std.mem.Allocator, +story: *Story, +endian: std.builtin.Endian = .big, + +pub const FileHeader = extern struct { + magic: [4]u8 = .{ 'I', 'N', 'K', 'C' }, + version: [2]u8 = .{ 0, 0 }, + __padding: u16 = 0, + sections_count: u32 = 0, +}; + +pub const SectionHeader = extern struct { + kind: Tag, + flags: u8 = 0, + __padding: u16 = 0, + size: u32, + + pub const Tag = enum(u8) { + strings, + constants, + globals, + knot, + _, }; -} +}; -fn makeKnotObject( - story: *Story, - name: []const u8, - code: *InternPool.CodeChunk, -) !*Object.Knot { - return Object.Knot.create(story, .{ - .name = name, - .code = try Object.Code.create(story, .{ - .args_count = @intCast(code.args_count), - .locals_count = @intCast(code.locals_count), - .stack_size = @intCast(code.stack_size), - .constants = try code.constants.toOwnedSlice(story.gpa), - .code_bytes = try code.bytecode.toOwnedSlice(story.gpa), - }), - }); -} +pub const CodeHeader = extern struct { + args_count: u8, + locals_count: u8, + stack_size: u16, + constants_len: u16, + bytecode_len: u16, +}; -fn makeConstantsPool(mod: *Compilation, story: *Story) ![]const Story.Value { - const ip = &mod.intern_pool; - const gpa = mod.gpa; - var constants_pool: std.ArrayListUnmanaged(Value) = .empty; - defer constants_pool.deinit(mod.gpa); - try constants_pool.ensureUnusedCapacity(mod.gpa, ip.values.items.len); - - for (ip.values.items) |value| { - const obj = try makeValueFromInterned(story, value); - constants_pool.appendAssumeCapacity(obj); - } - return constants_pool.toOwnedSlice(gpa); -} - -fn stringBytes(story: *Story, index: InternPool.NullTerminatedString) [:0]const u8 { - const slice = story.string_bytes[@intFromEnum(index)..]; - return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0]; -} +pub const Constant = enum(u8) { + bool, + int, + float, + str_index, + _, +}; pub const Options = struct { stack_size: usize, }; +pub const SectionIterator = struct { + bytes: []const u8, + index: usize, + + pub const Entry = struct { + header: SectionHeader, + bytes: []const u8, + }; + + pub fn next(iter: *SectionIterator) !?Entry { + if (iter.index >= iter.bytes.len) + return null; + if (iter.index + @sizeOf(SectionHeader) > iter.bytes.len) + return error.Corrupted; + + const header: SectionHeader = .{ + .kind = @enumFromInt(iter.bytes[iter.index]), + .flags = iter.bytes[iter.index + 1], + .__padding = 0, + .size = std.mem.readInt(u32, iter.bytes[iter.index + 4 ..][0..4], .big), + }; + const payload_start = iter.index + @sizeOf(SectionHeader); + const payload_end = payload_start + header.size; + + if (payload_end > iter.bytes.len) + return error.Corrupted; + + const entry: Entry = .{ + .header = header, + .bytes = iter.bytes[payload_start..payload_end], + }; + iter.index = payload_end; + return entry; + } +}; + +pub fn fromCachedCompilation( + gpa: std.mem.Allocator, + bytes: []const u8, + options: Options, +) !Story { + var story: Story = .{ + .gpa = gpa, + .arena = .init(gpa), + }; + errdefer story.deinit(); + + story.stack = try gpa.alloc(Story.Value, options.stack_size); + story.call_stack = try gpa.alloc(Story.CallFrame, options.stack_size); + + var loader: Loader = .{ + .gpa = gpa, + .story = &story, + }; + var section_iter: SectionIterator = .{ + .index = @sizeOf(FileHeader), + .bytes = bytes, + }; + + var string_bytes: std.ArrayList(u8) = .empty; + defer string_bytes.deinit(gpa); + + var constants_pool: std.ArrayList(Story.Value) = .empty; + defer constants_pool.deinit(gpa); + + if (try section_iter.next()) |section| { + try string_bytes.appendSlice(gpa, section.bytes); + story.string_bytes = try string_bytes.toOwnedSlice(gpa); + } + if (try section_iter.next()) |section| { + var remaining = section.bytes; + while (remaining.len > 0) { + const tag: Constant = @enumFromInt(remaining[0]); + remaining = remaining[1..]; + + const value: Story.Value = v: switch (tag) { + .bool => { + const v = remaining[0]; + remaining = remaining[1..]; + break :v .{ .bool = if (v == 1) true else false }; + }, + .int => { + const v = std.mem.readInt(i64, remaining[0..8], .big); + remaining = remaining[8..]; + break :v .{ .int = @bitCast(v) }; + }, + .float => { + const v = std.mem.readInt(i64, remaining[0..8], .big); + remaining = remaining[8..]; + break :v .{ .float = @bitCast(v) }; + }, + .str_index => { + const str_index = std.mem.readInt(u16, remaining[2..4], .big); + const slice = story.string_bytes[str_index..]; + const str_bytes = slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0]; + const str_object = try Object.String.create(loader.story, .{ + .bytes = str_bytes, + }); + remaining = remaining[@sizeOf(u16) + @sizeOf(u16) ..]; + break :v .{ .object = &str_object.base }; + }, + else => return error.MalformedValueTag, + }; + try constants_pool.append(gpa, value); + } + story.constants_pool = try constants_pool.toOwnedSlice(gpa); + } + while (try section_iter.next()) |section| { + switch (section.header.kind) { + .strings => {}, + .constants => {}, + .knot => { + const knot = try readKnot(&loader, section.bytes); + const knot_value: Story.Value = .{ .object = &knot.base }; + try story.globals.put(gpa, knot.name.toSlice(), knot_value); + }, + else => {}, + } + } + if (story.getKnot(Story.default_knot_name)) |knot| { + try story.pushStack(.{ .object = &knot.base }); + try story.divert(knot, 0); + } + return story; +} + +fn nullTerminatedString(loader: *const Loader, index: u32) []const u8 { + const slice = loader.story.string_bytes[index..]; + return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0]; +} + +fn readKnot(loader: *Loader, bytes: []const u8) !*Object.Knot { + const gpa = loader.gpa; + const name_index = std.mem.readInt(u32, bytes[0..4], loader.endian); + const code_header: CodeHeader = .{ + .args_count = bytes[4], + .locals_count = bytes[5], + .stack_size = std.mem.readInt(u16, bytes[6..8], loader.endian), + .constants_len = std.mem.readInt(u16, bytes[8..10], loader.endian), + .bytecode_len = std.mem.readInt(u16, bytes[10..12], loader.endian), + }; + const constants_start = 12; + const constants_end = constants_start + code_header.constants_len; + const bytecode_end = constants_end + code_header.bytecode_len; + + if (bytecode_end > bytes.len) + return error.InvalidKnotSection; + + var constants: std.ArrayList(u8) = .empty; + defer constants.deinit(gpa); + + var bytecode: std.ArrayList(u8) = .empty; + defer bytecode.deinit(gpa); + + try constants.appendSlice(gpa, bytes[constants_start..constants_end]); + try bytecode.appendSlice(gpa, bytes[constants_end..bytecode_end]); + + return .create(loader.story, .{ + .name = loader.nullTerminatedString(name_index), + .code = try Object.Code.create(loader.story, .{ + .args_count = code_header.args_count, + .locals_count = code_header.locals_count, + .stack_size = code_header.stack_size, + .constants = try constants.toOwnedSlice(gpa), + .code_bytes = try bytecode.toOwnedSlice(gpa), + }), + }); +} + +pub fn fromCompilation( + gpa: std.mem.Allocator, + mod: *Compilation, + options: Options, +) !Story { + var story: Story = .{ + .gpa = gpa, + .arena = .init(gpa), + }; + try fromCompilationCompat(gpa, mod, &story, options); + return story; +} + pub fn fromCompilationCompat( gpa: std.mem.Allocator, mod: *Compilation, @@ -111,26 +285,202 @@ pub fn fromCompilationCompat( } } -pub fn fromCompilation( +pub const Builder = struct { gpa: std.mem.Allocator, - mod: *Compilation, - options: Options, -) !Story { - var story: Story = .{ - .gpa = gpa, - .arena = .init(gpa), + intern_pool: *const InternPool, + endian: std.builtin.Endian = .big, + + pub const Section = struct { + builder: *Builder, + header: SectionHeader, + bytes: std.ArrayList(u8) = .empty, + + fn addConstant(self: *Section, value: InternPool.Key) !void { + const gpa = self.builder.gpa; + const bytes = &self.bytes; + switch (value) { + .bool => |v| { + const size: u32 = 1 + @sizeOf(bool); + self.header.size += size; + + try bytes.ensureUnusedCapacity(gpa, size); + bytes.appendAssumeCapacity(@intFromEnum(Constant.bool)); + bytes.appendAssumeCapacity(@intFromBool(v)); + }, + .int => |v| { + const size: u32 = 1 + @sizeOf(i64); + self.header.size += size; + + try bytes.ensureUnusedCapacity(gpa, size); + bytes.appendAssumeCapacity(@intFromEnum(Constant.int)); + bytes.appendSliceAssumeCapacity( + &std.mem.toBytes(std.mem.nativeToBig(i64, v)), + ); + }, + .float => |v| { + const size: u32 = 1 + @sizeOf(u64); + self.header.size += size; + + try bytes.ensureUnusedCapacity(gpa, size); + bytes.appendAssumeCapacity(@intFromEnum(Constant.float)); + bytes.appendSliceAssumeCapacity( + &std.mem.toBytes(std.mem.nativeToBig(u64, v)), + ); + }, + .str => |index| { + const slice = self.builder.intern_pool.string_bytes.items[@intFromEnum(index)..]; + const str_bytes = slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0]; + const str_len: u16 = @intCast(str_bytes.len); + const size: u32 = 1 + @sizeOf(u16) + @sizeOf(u16); + self.header.size += size; + + try bytes.ensureUnusedCapacity(gpa, size); + bytes.appendAssumeCapacity(@intFromEnum(Constant.str_index)); + bytes.appendSliceAssumeCapacity( + &std.mem.toBytes(std.mem.nativeToBig(u16, str_len)), + ); + bytes.appendSliceAssumeCapacity( + &std.mem.toBytes(std.mem.nativeToBig(u16, @intCast(@intFromEnum(index)))), + ); + }, + } + } + + fn addKnot(self: *Section, knot: InternPool.Knot) !void { + const builder = self.builder; + const gpa = builder.gpa; + const bytes = &self.bytes; + const section_size = @sizeOf(u32) + @sizeOf(CodeHeader) + + knot.code_chunk.constants.items.len + + knot.code_chunk.bytecode.items.len; + self.header.size += @intCast(section_size); + + const string_const = builder.intern_pool.values.items[@intFromEnum(knot.name_index)]; + const string_index = string_const.str; + try bytes.ensureUnusedCapacity(gpa, section_size); + + const code_header: CodeHeader = .{ + .args_count = @intCast(knot.code_chunk.args_count), + .locals_count = @intCast(knot.code_chunk.locals_count), + .stack_size = @intCast(knot.code_chunk.stack_size), + .constants_len = @intCast(knot.code_chunk.constants.items.len), + .bytecode_len = @intCast(knot.code_chunk.bytecode.items.len), + }; + bytes.appendSliceAssumeCapacity( + &std.mem.toBytes(std.mem.nativeToBig(u32, @intFromEnum(string_index))), + ); + bytes.appendAssumeCapacity(code_header.args_count); + bytes.appendAssumeCapacity(code_header.locals_count); + bytes.appendSliceAssumeCapacity( + &std.mem.toBytes(std.mem.nativeToBig(u16, code_header.stack_size)), + ); + bytes.appendSliceAssumeCapacity( + &std.mem.toBytes(std.mem.nativeToBig(u16, code_header.constants_len)), + ); + bytes.appendSliceAssumeCapacity( + &std.mem.toBytes(std.mem.nativeToBig(u16, code_header.bytecode_len)), + ); + bytes.appendSliceAssumeCapacity(knot.code_chunk.constants.items); + bytes.appendSliceAssumeCapacity(knot.code_chunk.bytecode.items); + } }; - try fromCompilationCompat(gpa, mod, &story, options); - return story; + + fn makeSection(self: *Builder, tag: SectionHeader.Tag) Section { + return .{ + .builder = self, + .header = .{ .kind = tag, .size = 0 }, + }; + } +}; + +pub fn writeFile(comp: *Compilation, writer: *std.Io.Writer) !void { + const endian: std.builtin.Endian = .big; + const gpa = comp.gpa; + const ip = &comp.intern_pool; + var bytes: std.ArrayList(u8) = .empty; + defer bytes.deinit(comp.gpa); + + var builder: Builder = .{ + .gpa = gpa, + .intern_pool = ip, + .endian = endian, + }; + const file_header: FileHeader = .{ + .sections_count = @intCast(2 + comp.knots.items.len), + }; + + try writer.writeStruct(file_header, endian); + + var strings_section = builder.makeSection(.strings); + defer strings_section.bytes.deinit(gpa); + try strings_section.bytes.appendSlice(gpa, ip.string_bytes.items); + strings_section.header.size += @intCast(ip.string_bytes.items.len); + try writer.writeStruct(strings_section.header, endian); + try writer.writeAll(strings_section.bytes.items); + + var const_section = builder.makeSection(.constants); + defer const_section.bytes.deinit(gpa); + for (ip.values.items) |value| { + try const_section.addConstant(value); + } + try writer.writeStruct(const_section.header, endian); + try writer.writeAll(const_section.bytes.items); + + for (comp.knots.items) |knot| { + var knot_section = builder.makeSection(.knot); + try knot_section.addKnot(knot); + try writer.writeStruct(knot_section.header, endian); + try writer.writeAll(knot_section.bytes.items); + } + try writer.flush(); } -pub fn fromCachedCompilation( - gpa: std.mem.Allocator, - bytes: []const u8, - options: Options, -) !Story { - _ = gpa; - _ = bytes; - _ = options; - return error.NotImplemented; +fn makeValueFromInterned(story: *Story, value: InternPool.Key) !Story.Value { + return switch (value) { + .bool => |boolean| .{ .bool = boolean }, + .int => |int| .{ .int = @intCast(int) }, + .float => |float| .{ .float = @bitCast(float) }, + .str => |index| blk: { + const str_object = try Object.String.create(story, .{ + .bytes = stringBytes(story, index), + }); + break :blk .{ .object = &str_object.base }; + }, + }; +} + +fn makeConstantsPool(mod: *Compilation, story: *Story) ![]const Story.Value { + const ip = &mod.intern_pool; + const gpa = mod.gpa; + var constants_pool: std.ArrayList(Story.Value) = .empty; + defer constants_pool.deinit(mod.gpa); + try constants_pool.ensureUnusedCapacity(mod.gpa, ip.values.items.len); + + for (ip.values.items) |value| { + const obj = try makeValueFromInterned(story, value); + constants_pool.appendAssumeCapacity(obj); + } + return constants_pool.toOwnedSlice(gpa); +} + +fn makeKnotObject( + story: *Story, + name: []const u8, + code: *InternPool.CodeChunk, +) !*Object.Knot { + return .create(story, .{ + .name = name, + .code = try .create(story, .{ + .args_count = @intCast(code.args_count), + .locals_count = @intCast(code.locals_count), + .stack_size = @intCast(code.stack_size), + .constants = try code.constants.toOwnedSlice(story.gpa), + .code_bytes = try code.bytecode.toOwnedSlice(story.gpa), + }), + }); +} + +fn stringBytes(story: *Story, index: InternPool.NullTerminatedString) [:0]const u8 { + const slice = story.string_bytes[@intFromEnum(index)..]; + return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0]; } diff --git a/src/compile.zig b/src/compile.zig index 30e159b..17eda1a 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -81,10 +81,11 @@ pub const Compilation = struct { arena: std.heap.ArenaAllocator, tree: Ast, ir: Ir, - globals: std.ArrayListUnmanaged(InternPool.Global) = .empty, - knots: std.ArrayListUnmanaged(InternPool.Knot) = .empty, - stitches: std.ArrayListUnmanaged(InternPool.Stitch) = .empty, - errors: *std.ArrayListUnmanaged(Error), + globals: std.ArrayList(InternPool.Global) = .empty, + knots: std.ArrayList(InternPool.Knot) = .empty, + stitches: std.ArrayList(InternPool.Stitch) = .empty, + code_chunks: std.ArrayList(*InternPool.CodeChunk) = .empty, + errors: *std.ArrayList(Error), intern_pool: InternPool = .{}, work_queue: WorkQueue = .{}, @@ -291,9 +292,9 @@ pub const Compilation = struct { cu: *Compilation, arena: std.mem.Allocator, ) error{OutOfMemory}!*InternPool.CodeChunk { - _ = cu; const chunk = try arena.create(InternPool.CodeChunk); chunk.* = .{}; + try cu.code_chunks.append(cu.gpa, chunk); return chunk; } @@ -338,11 +339,20 @@ pub const Compilation = struct { pub fn deinit(mod: *Compilation) void { const gpa = mod.gpa; - mod.arena.deinit(); + + var i: usize = 0; + while (i < mod.code_chunks.items.len) : (i += 1) { + const chunk = mod.code_chunks.items[i]; + chunk.bytecode.deinit(gpa); + chunk.constants.deinit(gpa); + } + mod.intern_pool.deinit(gpa); mod.globals.deinit(gpa); mod.knots.deinit(gpa); mod.stitches.deinit(gpa); + mod.code_chunks.deinit(gpa); + mod.arena.deinit(); mod.* = undefined; } }; diff --git a/src/main.zig b/src/main.zig index 9ef7156..cf17c88 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,7 +9,6 @@ const fatal = std.process.fatal; var stdin_buffer: [4096]u8 align(std.heap.page_size_min) = undefined; var stdout_buffer: [4096]u8 align(std.heap.page_size_min) = undefined; -var stderr_buffer: [4096]u8 align(std.heap.page_size_min) = undefined; var debug_allocator: std.heap.DebugAllocator(.{}) = .init; const ArgsIterator = struct { @@ -64,6 +63,7 @@ fn mainArgs( var dump_trace: bool = false; var use_stdin: bool = false; var use_color: bool = false; + const stack_size = 128; var args_iter: ArgsIterator = .{ .args = args_list[1..], @@ -98,34 +98,33 @@ fn mainArgs( } } + const stdout = std.Io.File.stdout(); + var stdout_writer = stdout.writer(io, &stdout_buffer); + const log_writer = &stdout_writer.interface; + const filename = source_path orelse ""; - const source_bytes: [:0]const u8 = s: { - var f = if (source_path) |p| file: { - break :file std.Io.Dir.cwd().openFile(io, p, .{}) catch |err| { - fatal("unable to open file '{s}': {s}", .{ p, @errorName(err) }); - }; - } else std.Io.File.stdin(); - defer if (source_path != null) f.close(io); - - var file_reader: std.Io.File.Reader = f.reader(io, &stdin_buffer); - break :s ink.readSourceFileToEndAlloc(arena, &file_reader) catch |err| { - fatal("unable to load file '{s}': {s}", .{ filename, @errorName(err) }); - }; - }; - - const stderr = std.Io.File.stderr(); - var stderr_writer = stderr.writer(io, &stderr_buffer); - const io_w = &stderr_writer.interface; - const stack_size = 128; - const file_ext: FileExtension = f: { const bytes = std.fs.path.extension(filename); if (std.mem.eql(u8, bytes, ".ink")) break :f .ink; if (std.mem.eql(u8, bytes, ".inkc")) break :f .inkc; return error.InvalidFileExtension; }; - switch (file_ext) { + var story: Story = story: switch (file_ext) { .ink => { + const source_bytes: [:0]const u8 = s: { + var f = if (source_path) |p| file: { + break :file std.Io.Dir.cwd().openFile(io, p, .{}) catch |err| { + fatal("unable to open file '{s}': {s}", .{ p, @errorName(err) }); + }; + } else std.Io.File.stdin(); + defer if (source_path != null) f.close(io); + + var file_reader: std.Io.File.Reader = f.reader(io, &stdin_buffer); + break :s ink.readSourceFileToEndAlloc(arena, &file_reader) catch |err| { + fatal("unable to load file '{s}': {s}", .{ filename, @errorName(err) }); + }; + }; + var tree = try Ast.parse(gpa, arena, source_bytes, filename, 0); defer tree.deinit(gpa); @@ -136,16 +135,17 @@ fn mainArgs( defer errors.deinit(gpa); if (dump_ast) { - try io_w.writeAll("=== AST ===\n"); - try tree.render(gpa, io_w, .{ + try log_writer.writeAll("=== AST ===\n"); + try tree.render(gpa, log_writer, .{ .use_color = use_color, }); - try io_w.flush(); + try log_writer.flush(); } if (dump_ir) { - try io_w.writeAll("=== IR ===\n"); - try ir.render(io_w); - try io_w.flush(); + try log_writer.writeAll("=== IR ===\n"); + try ir.dumpInfo(log_writer); + try ir.render(log_writer); + try log_writer.flush(); } var cu = Compilation.build(gpa, tree, ir, &errors) catch |err| switch (err) { @@ -155,41 +155,62 @@ fn mainArgs( if (cu.hasCompileErrors()) { for (cu.errors.items) |err| { - try cu.renderError(io_w, err); + try cu.renderError(log_writer, err); } - } else if (output_path) |_| { - return error.NotImplemented; - } else { - var story: Story = try .fromCompilation(gpa, &cu, .{ - .stack_size = stack_size, - }); - defer story.deinit(); - if (dump_story) { - try story.dump(io_w); - } - if (dump_trace) { - story.dump_writer = io_w; - } - return if (!compile_only) run(io, gpa, &story); } - }, - .inkc => { - var story: Story = try .fromCachedCompilation(gpa, source_bytes, .{ + if (output_path) |path| { + const file = try std.Io.Dir.cwd().createFile(io, path, .{}); + defer file.close(io); + var file_writer = file.writer(io, &.{}); + try Story.Loader.writeFile(&cu, &file_writer.interface); + try file_writer.interface.flush(); + return; + } + break :story try .fromCompilation(gpa, &cu, .{ .stack_size = stack_size, }); - defer story.deinit(); - return if (!compile_only) run(io, gpa, &story); }, - } -} + .inkc => { + const source_bytes: [:0]const u8 = s: { + var f = if (source_path) |p| file: { + break :file std.Io.Dir.cwd().openFile(io, p, .{}) catch |err| { + fatal("unable to open file '{s}': {s}", .{ p, @errorName(err) }); + }; + } else std.Io.File.stdin(); + defer if (source_path != null) f.close(io); + + var file_reader: std.Io.File.Reader = f.reader(io, &stdin_buffer); + break :s ink.readSourceFileToEndAlloc(arena, &file_reader) catch |err| { + fatal("unable to load file '{s}': {s}", .{ filename, @errorName(err) }); + }; + }; + + break :story try .fromCachedCompilation(gpa, source_bytes, .{ + .stack_size = stack_size, + }); + }, + }; + defer story.deinit(); + + if (dump_story) try story.dump(log_writer); + if (dump_trace) story.dump_writer = log_writer; -fn run(io: std.Io, _: std.mem.Allocator, story: *Story) !void { const stdin = std.Io.File.stdin(); var stdin_reader = stdin.reader(io, &stdin_buffer); - const stdout = std.Io.File.stdin(); - var stdout_writer = stdout.writer(io, &stdout_buffer); - const reader = &stdin_reader.interface; - const writer = &stdout_writer.interface; + return if (!compile_only) run(gpa, &story, .{ + .reader = &stdin_reader.interface, + .writer = log_writer, + }); +} + +pub const RunOptions = struct { + reader: *std.Io.Reader, + writer: *std.Io.Writer, +}; + +fn run(_: std.mem.Allocator, story: *Story, options: RunOptions) !void { + const reader = options.reader; + const writer = options.writer; while (!story.is_exited and story.can_advance) { while (try story.advance()) |content| {