diff --git a/build.zig b/build.zig index be7e234..290774a 100644 --- a/build.zig +++ b/build.zig @@ -121,18 +121,20 @@ fn buildExamples( optimize: std.builtin.OptimizeMode, c_lib_: ?*std.Build.Step.Compile, ) ![]const *std.Build.Step.Compile { - const alloc = b.allocator; - var steps: std.ArrayList(*std.Build.Step.Compile) = .empty; - defer steps.deinit(alloc); + const gpa = b.allocator; + const io = b.graph.io; - var dir = try std.fs.cwd().openDir(try b.build_root.join( - b.allocator, + var steps: std.ArrayList(*std.Build.Step.Compile) = .empty; + defer steps.deinit(gpa); + + var dir = try std.Io.Dir.cwd().openDir(io, try b.build_root.join( + gpa, &.{"examples"}, ), .{ .iterate = true }); - defer dir.close(); + defer dir.close(io); var it = dir.iterate(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { const index = std.mem.lastIndexOfScalar(u8, entry.name, '.') orelse continue; if (index == 0) continue; @@ -159,11 +161,11 @@ fn buildExamples( .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), }); - exe.linkLibC(); - exe.addIncludePath(b.path("include")); - exe.addCSourceFile(.{ + exe.root_module.addIncludePath(b.path("include")); + exe.root_module.addCSourceFile(.{ .file = b.path(b.fmt( "examples/{s}", .{entry.name}, @@ -176,10 +178,10 @@ fn buildExamples( "-D_POSIX_C_SOURCE=199309L", }, }); - exe.linkLibrary(c_lib); + exe.root_module.linkLibrary(c_lib); break :exe exe; }; - try steps.append(alloc, exe); + try steps.append(gpa, exe); } - return steps.toOwnedSlice(alloc); + return steps.toOwnedSlice(gpa); } diff --git a/build.zig.zon b/build.zig.zon index dd8cc73..a23ddbc 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,7 +2,7 @@ .name = .ink_zig, .version = "0.0.0", .fingerprint = 0xf2bf28592b4cdc24, - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.16.0", .dependencies = .{}, .paths = .{ "build.zig", diff --git a/src/Ast/Render.zig b/src/Ast/Render.zig index 80b0d6d..dd2959f 100644 --- a/src/Ast/Render.zig +++ b/src/Ast/Render.zig @@ -4,8 +4,8 @@ const Ast = ink.Ast; const Render = @This(); gpa: std.mem.Allocator, -tty_config: std.Io.tty.Config, tree: *const Ast, +terminal: std.Io.Terminal, prefix: Prefix, lines: LineCache, @@ -147,43 +147,43 @@ const Prefix = struct { fn writeType(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void { const tag = nodeTagToString(node.tag); - try r.tty_config.setColor(writer, .magenta); - try r.tty_config.setColor(writer, .bold); + try r.terminal.setColor(.magenta); + try r.terminal.setColor(.bold); try writer.writeAll(tag); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); } fn writeLexeme(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void { - try r.tty_config.setColor(writer, .yellow); + try r.terminal.setColor(.yellow); try writer.print("`{s}`", .{r.tree.nodeSlice(node)}); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); } fn writeLineSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void { const line_start = r.lines.calculateLine(node.loc.start); const line_end = r.lines.calculateLine(node.loc.end); - try r.tty_config.setColor(writer, .white); + try r.terminal.setColor(.white); try writer.writeByte('<'); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); - try r.tty_config.setColor(writer, .yellow); + try r.terminal.setColor(.yellow); try writer.print("line:{d}", .{line_start + 1}); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); - try r.tty_config.setColor(writer, .white); + try r.terminal.setColor(.white); try writer.writeByte(','); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); try writer.writeByte(' '); - try r.tty_config.setColor(writer, .yellow); + try r.terminal.setColor(.yellow); try writer.print("line:{d}", .{line_end + 1}); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); - try r.tty_config.setColor(writer, .white); + try r.terminal.setColor(.white); try writer.writeByte('>'); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); } fn writeColumnSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void { @@ -192,27 +192,27 @@ fn writeColumnSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !v const column_start = (node.loc.start - line_range.start); const column_end = (node.loc.end - line_range.start); - try r.tty_config.setColor(writer, .white); + try r.terminal.setColor(.white); try writer.writeByte('<'); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); - try r.tty_config.setColor(writer, .yellow); + try r.terminal.setColor(.yellow); try writer.print("col:{d}", .{column_start + 1}); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); - try r.tty_config.setColor(writer, .white); + try r.terminal.setColor(.white); try writer.writeByte(','); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); try writer.writeByte(' '); - try r.tty_config.setColor(writer, .yellow); + try r.terminal.setColor(.yellow); try writer.print("col:{d}", .{column_end + 1}); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); - try r.tty_config.setColor(writer, .white); + try r.terminal.setColor(.white); try writer.writeByte('>'); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); } fn writeLineColumnSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void { @@ -221,26 +221,26 @@ fn writeLineColumnSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node const column_start = (node.loc.start - line_range.start); const column_end = (node.loc.end - line_range.start); - try r.tty_config.setColor(writer, .white); + try r.terminal.setColor(.white); try writer.writeByte('<'); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); - try r.tty_config.setColor(writer, .yellow); + try r.terminal.setColor(.yellow); try writer.print("line:{d}", .{line_start + 1}); - try r.tty_config.setColor(writer, .white); + try r.terminal.setColor(.white); try writer.writeByte(','); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); try writer.writeByte(' '); - try r.tty_config.setColor(writer, .yellow); + try r.terminal.setColor(.yellow); try writer.print("col:{d}:{d}", .{ column_start + 1, column_end + 1 }); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); - try r.tty_config.setColor(writer, .white); + try r.terminal.setColor(.white); try writer.writeByte('>'); - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); } const NodeOptions = struct { @@ -255,14 +255,14 @@ fn renderAstNode( options: NodeOptions, ) !void { if (!options.is_root) { - try r.tty_config.setColor(writer, .blue); + try r.terminal.setColor(.blue); try r.prefix.writeIndent(writer); if (options.is_last) { try writer.writeAll("`--"); } else { try writer.writeAll("|--"); } - try r.tty_config.setColor(writer, .reset); + try r.terminal.setColor(.reset); } try r.writeType(writer, node); @@ -473,7 +473,10 @@ pub fn renderTree( var r: Render = .{ .gpa = gpa, .tree = ast, - .tty_config = if (options.use_color) .escape_codes else .no_color, + .terminal = .{ + .writer = writer, + .mode = if (options.use_color) .escape_codes else .no_color, + }, .prefix = .{}, .lines = try LineCache.build(gpa, ast.source), }; diff --git a/src/AstGen.zig b/src/AstGen.zig index cbc2f25..348a971 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -748,7 +748,7 @@ fn stringExpr(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.In const result = switch (sub_node.tag) { .inline_logic_expr => try inlineLogicExpr(gi, scope, sub_node), .string_literal => try stringLiteral(gi, sub_node), - inline else => |_| unreachable, + inline else => unreachable, }; try gi.astgen.scratch.append(gpa, @intFromEnum(result)); } @@ -803,7 +803,7 @@ fn expr(gi: *GenIr, scope: *Scope, optional_node: ?*const Ast.Node) InnerError!I .logical_lesser_or_equal_expr => return binaryOp(gi, scope, node, .cmp_lte), .call_expr => return callExpr(gi, scope, node, .call), .selector_expr => return fieldAccess(gi, scope, node), - inline else => |_| unreachable, + inline else => unreachable, } } diff --git a/src/Sema.zig b/src/Sema.zig index ee5693c..9260e6e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -734,15 +734,15 @@ fn irStore(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void switch (lhs) { .none => unreachable, .stack => {}, - .value => |_| return sema.fail(src, "could not assign to constant value", .{}), + .value => return sema.fail(src, "could not assign to constant value", .{}), .variable => |index| { const local_index = try builder.getOrPutConstantIndex(index); try builder.addConstOp(.store_global, @intCast(local_index)); }, .temp => |temp| try builder.addConstOp(.store, @intCast(temp)), - .knot => |_| return sema.fail(src, "could not assign to knot", .{}), - .stitch => |_| return sema.fail(src, "could not assign to stitch", .{}), - .function => |_| return sema.fail(src, "could not assign to function", .{}), + .knot => return sema.fail(src, "could not assign to knot", .{}), + .stitch => return sema.fail(src, "could not assign to stitch", .{}), + .function => return sema.fail(src, "could not assign to function", .{}), } try builder.addByteOp(.pop); diff --git a/src/Story/Dumper.zig b/src/Story/Dumper.zig index ca39ad3..64a8eb6 100644 --- a/src/Story/Dumper.zig +++ b/src/Story/Dumper.zig @@ -282,15 +282,15 @@ pub fn dumpValue(_: Dumper, w: *std.Io.Writer, value: *const Value) !void { "<{s}>", .{value.tagBytes()}, ), - .bool => |_| try w.print( + .bool => try w.print( "<{s} value={f}>", .{ value.tagBytes(), value }, ), - .int => |_| try w.print( + .int => try w.print( "<{s} value={f}, address={*}>", .{ value.tagBytes(), value, value }, ), - .float => |_| try w.print( + .float => try w.print( "<{s} value={f}, address={*}>", .{ value.tagBytes(), value, value }, ), diff --git a/src/Story/Object.zig b/src/Story/Object.zig index 277bee8..3e7bb52 100644 --- a/src/Story/Object.zig +++ b/src/Story/Object.zig @@ -44,22 +44,31 @@ pub fn eql(lhs: *Object, rhs: *Object) bool { const rhs_object: *Object.String = @ptrCast(rhs); break :blk std.mem.eql(u8, lhs_object.toSlice(), rhs_object.toSlice()); }, - .code => |_| false, - .knot => |_| false, - .string_builder => |_| false, + .code => false, + .knot => false, + .string_builder => false, }; } pub fn destroy(obj: *Object, story: *Story) void { - inline for (comptime std.meta.fields(Tag)) |field| { - const tag = @field(Tag, field.name); - if (obj.tag == tag) { - const typed_obj: *Tag.ObjectType(tag) = @alignCast(@fieldParentPtr("base", obj)); + switch (obj.tag) { + .string => { + const typed_obj: *String = @alignCast(@fieldParentPtr("base", obj)); typed_obj.destroy(story); - return; - } + }, + .code => { + const typed_obj: *Code = @alignCast(@fieldParentPtr("base", obj)); + typed_obj.destroy(story); + }, + .knot => { + const typed_obj: *Knot = @alignCast(@fieldParentPtr("base", obj)); + typed_obj.destroy(story); + }, + .string_builder => { + const typed_obj: *StringBuilder = @alignCast(@fieldParentPtr("base", obj)); + typed_obj.destroy(story); + }, } - unreachable; } pub const String = struct { @@ -96,9 +105,12 @@ pub const String = struct { } pub fn destroy(obj: *String, story: *Story) void { - const alloc_len = @sizeOf(Type) + obj.length + 1; const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj); - story.gpa.free(base[0..alloc_len]); + story.gpa.free(base[0..allocLen(obj)]); + } + + fn allocLen(obj: *String) usize { + return @sizeOf(Type) + obj.length + 1; } pub fn toSlice(obj: *const Object.String) []const u8 { @@ -184,7 +196,11 @@ pub const Code = struct { gpa.free(obj.bytecode); const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj); - gpa.free(base[0..@sizeOf(Type)]); + gpa.free(base[0..allocLen(obj)]); + } + + fn allocLen(_: *Code) usize { + return @sizeOf(Type); } }; @@ -225,9 +241,12 @@ pub const Knot = struct { const gpa = story.gpa; obj.members.deinit(gpa); - const alloc_len = @sizeOf(Type); const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj); - gpa.free(base[0..alloc_len]); + gpa.free(base[0..allocLen(obj)]); + } + + fn allocLen(_: *Knot) usize { + return @sizeOf(Type); } }; @@ -251,9 +270,12 @@ pub const StringBuilder = struct { pub fn destroy(obj: *StringBuilder, story: *Story) void { obj.inner.deinit(); - const alloc_len = @sizeOf(Type); const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj); - story.gpa.free(base[0..alloc_len]); + story.gpa.free(base[0..allocLen(obj)]); + } + + fn allocLen(_: *StringBuilder) usize { + return @sizeOf(Type); } pub fn append(obj: *StringBuilder, value: Story.Value) error{OutOfMemory}!void { diff --git a/src/Story/runtime_tests.zig b/src/Story/runtime_tests.zig index 3c1aeb3..53c93ff 100644 --- a/src/Story/runtime_tests.zig +++ b/src/Story/runtime_tests.zig @@ -4,6 +4,8 @@ const ink = @import("../root.zig"); const Story = ink.Story; const Compilation = ink.Compilation; +var threaded_io: std.Io.Threaded = .init_single_threaded; + test "fixture - variable arithmetic" { try testRuntimeFixture("variable-arithmetic"); } @@ -343,12 +345,13 @@ fn testRuntimeFixture(comptime fixture: []const u8) !void { const input_bytes = @embedFile(test_root ++ fixture ++ "/input.txt"); var stderr_buffer: [1024]u8 = undefined; const gpa = std.testing.allocator; - const stderr = std.fs.File.stderr(); + const stderr = std.Io.File.stderr(); + const io = threaded_io.io(); var io_r = std.Io.Reader.fixed(input_bytes); var io_w = std.Io.Writer.Allocating.init(gpa); defer io_w.deinit(); - var stderr_writer = stderr.writer(&stderr_buffer); + var stderr_writer = stderr.writer(io, &stderr_buffer); try testRunner(gpa, source_bytes, .{ .input_reader = &io_r, diff --git a/src/c_api.zig b/src/c_api.zig index c535de2..6143cab 100644 --- a/src/c_api.zig +++ b/src/c_api.zig @@ -6,6 +6,7 @@ const Ast = ink.Ast; const AstGen = ink.AstGen; const Ir = ink.Ir; +var threaded_io: std.Io.Threaded = .init_single_threaded; var global_allocator: std.heap.DebugAllocator(.{}) = .init; var stdout_buffer: [4096]u8 align(std.heap.page_size_min) = undefined; @@ -75,12 +76,13 @@ fn loadStory(gpa: std.mem.Allocator, options: LoadStoryOptions) !*Story { pub export fn ink_load_story_options( options: *const InkLoadOpts, ) callconv(.c) ?*Story { + const io = threaded_io.io(); const gpa = global_allocator.allocator(); const source_bytes = options.source_bytes[0..options.source_length :0]; const filename = options.filename[0..options.filename_length :0]; const stack_size = 128; - const stderr = std.fs.File.stderr(); - var stderr_writer = stderr.writer(&stdout_buffer); + const stderr = std.Io.File.stderr(); + var stderr_writer = stderr.writer(io, &stdout_buffer); return loadStory(gpa, .{ .filename = filename, diff --git a/src/error_tests.zig b/src/error_tests.zig index 5dfd6cf..54b4ea2 100644 --- a/src/error_tests.zig +++ b/src/error_tests.zig @@ -168,7 +168,7 @@ fn testEqual(source_bytes: [:0]const u8, expected_error: []const u8) !void { var arena_allocator = std.heap.ArenaAllocator.init(gpa); defer arena_allocator.deinit(); - var allocating = std.io.Writer.Allocating.init(gpa); + var allocating = std.Io.Writer.Allocating.init(gpa); defer allocating.deinit(); const io_w = &allocating.writer; diff --git a/src/main.zig b/src/main.zig index ce70685..9ef7156 100644 --- a/src/main.zig +++ b/src/main.zig @@ -38,24 +38,19 @@ const FileExtension = enum { inkc, }; -pub fn main() !void { - const gpa = debug_allocator.allocator(); - defer _ = debug_allocator.deinit(); - - var arena_allocator = std.heap.ArenaAllocator.init(gpa); - defer arena_allocator.deinit(); - - const arena = arena_allocator.allocator(); - const args = try std.process.argsAlloc(gpa); - defer std.process.argsFree(gpa, args); - +pub fn main(init: std.process.Init) !void { + const gpa = init.gpa; + const io = init.io; + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); if (args.len < 2) { fatal("Not enough arguments!", .{}); } - return mainArgs(gpa, arena, args); + return mainArgs(io, gpa, arena, args); } fn mainArgs( + io: std.Io, gpa: std.mem.Allocator, arena: std.mem.Allocator, args_list: []const [:0]const u8, @@ -106,20 +101,20 @@ fn mainArgs( const filename = source_path orelse ""; const source_bytes: [:0]const u8 = s: { var f = if (source_path) |p| file: { - break :file std.fs.cwd().openFile(p, .{}) catch |err| { + break :file std.Io.Dir.cwd().openFile(io, p, .{}) catch |err| { fatal("unable to open file '{s}': {s}", .{ p, @errorName(err) }); }; - } else std.fs.File.stdin(); - defer if (source_path != null) f.close(); + } else std.Io.File.stdin(); + defer if (source_path != null) f.close(io); - var file_reader: std.fs.File.Reader = f.reader(&stdin_buffer); + 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.fs.File.stderr(); - var stderr_writer = stderr.writer(&stderr_buffer); + const stderr = std.Io.File.stderr(); + var stderr_writer = stderr.writer(io, &stderr_buffer); const io_w = &stderr_writer.interface; const stack_size = 128; @@ -175,7 +170,7 @@ fn mainArgs( if (dump_trace) { story.dump_writer = io_w; } - return if (!compile_only) run(gpa, &story); + return if (!compile_only) run(io, gpa, &story); } }, .inkc => { @@ -183,16 +178,16 @@ fn mainArgs( .stack_size = stack_size, }); defer story.deinit(); - return if (!compile_only) run(gpa, &story); + return if (!compile_only) run(io, gpa, &story); }, } } -fn run(_: std.mem.Allocator, story: *Story) !void { - const stdin = std.fs.File.stdin(); - var stdin_reader = stdin.reader(&stdin_buffer); - const stdout = std.fs.File.stdin(); - var stdout_writer = stdout.writer(&stdout_buffer); +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; diff --git a/src/parser_tests.zig b/src/parser_tests.zig index 9dce462..4af8a45 100644 --- a/src/parser_tests.zig +++ b/src/parser_tests.zig @@ -280,7 +280,7 @@ fn testEqual(source_bytes: [:0]const u8, expected_ast: []const u8) !void { defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - var allocating = std.io.Writer.Allocating.init(gpa); + var allocating = std.Io.Writer.Allocating.init(gpa); defer allocating.deinit(); const ast = try Ast.parse(gpa, arena, source_bytes, "", 0); diff --git a/src/root.zig b/src/root.zig index 0cb9478..253f707 100644 --- a/src/root.zig +++ b/src/root.zig @@ -8,13 +8,44 @@ pub const Compilation = @import("compile.zig").Compilation; pub const max_src_size = std.math.maxInt(u32); -// std.zig.readSourceFileToEndAlloc leaks in 0.15.2. -// Use an arena allocator for now. pub fn readSourceFileToEndAlloc( gpa: std.mem.Allocator, - file_reader: *std.fs.File.Reader, + file_reader: *std.Io.File.Reader, ) ![:0]u8 { - return std.zig.readSourceFileToEndAlloc(gpa, file_reader); + var buffer: std.ArrayList(u8) = .empty; + defer buffer.deinit(gpa); + + if (file_reader.getSize()) |size| { + const casted_size = std.math.cast(u32, size) orelse return error.StreamTooLong; + // +1 to avoid resizing for the null byte added in toOwnedSliceSentinel below. + try buffer.ensureTotalCapacityPrecise(gpa, casted_size + 1); + } else |_| {} + + try file_reader.interface.appendRemaining(gpa, &buffer, .limited(max_src_size)); + + // Detect unsupported file types with their Byte Order Mark + const unsupported_boms = [_][]const u8{ + "\xff\xfe\x00\x00", // UTF-32 little endian + "\xfe\xff\x00\x00", // UTF-32 big endian + "\xfe\xff", // UTF-16 big endian + }; + for (unsupported_boms) |bom| { + if (std.mem.startsWith(u8, buffer.items, bom)) { + return error.UnsupportedEncoding; + } + } + + // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8 + if (std.mem.startsWith(u8, buffer.items, "\xff\xfe")) { + if (buffer.items.len % 2 != 0) return error.InvalidEncoding; + return std.unicode.utf16LeToUtf8AllocZ(gpa, @ptrCast(@alignCast(buffer.items))) catch |err| switch (err) { + error.DanglingSurrogateHalf => error.UnsupportedEncoding, + error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding, + error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding, + else => |e| return e, + }; + } + return buffer.toOwnedSliceSentinel(gpa, 0); } test {