From 7514b71286a8659c2917b78acf7f36b5da7e9657 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 23 Jul 2024 04:35:26 -0700 Subject: [PATCH] Implement Arrays (#67) To make it easier to create unbounded temporary allocations, such as in the case of creating a []Value for an array value, a new abstraction for `s.scratch` is added: `core.Stack` and `core.U32Stack`. This provides a safer interface for these operations. --- CHANGELOG.md | 2 + build.zig | 12 +- src/core/Air.Dump.zig | 45 ++++- src/core/Air.zig | 19 +++ src/core/Ast.Dump.zig | 22 ++- src/core/Ast.zig | 46 +++++- src/core/InternPool.zig | 167 ++++++++++++++----- src/core/Parse.zig | 92 +++++++++-- src/core/Report.zig | 24 ++- src/core/Sema.zig | 353 +++++++++++++++++++++++++--------------- src/core/Teg.Dump.zig | 67 ++++++-- src/core/Teg.zig | 62 ++++--- src/core/Transform.zig | 55 ++++--- src/core/array.zig | 9 + src/core/js/Link.zig | 37 ++++- src/core/js/NodeGen.zig | 6 +- src/core/js/Tree.zig | 4 + src/core/main.zig | 14 +- src/core/mod.zig | 36 +++- src/core/stack.zig | 156 ++++++++++++++++++ src/core/util.zig | 49 ------ 21 files changed, 945 insertions(+), 332 deletions(-) create mode 100644 src/core/stack.zig delete mode 100644 src/core/util.zig diff --git a/CHANGELOG.md b/CHANGELOG.md index 078597fe175ba66eb41bb8f117258af5117ee87b..1385b355b836d05ea471ce9a62cda2797578a651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.1.4 - feat: implement basic struct types (#42) +- feat: implement basic array types (#67) - feat: implement string identifiers (#54) - feat: include a `README.html` alongside compiler with documentation and code samples. - feat(transform): include a warning for leading zero in numbers @@ -9,6 +10,7 @@ - fix(sema): handle recursive function calls - fix(sema): quit block analysis when hitting `noreturn` - fix(sema): binary type resolution with opaque and numeric literals no longer crash +- fix(sema): false positive warning for numeric int ambiguity when it was always float ## 0.1.3 diff --git a/build.zig b/build.zig index 7c5e8a2baf4491cea83bfe526666369a27f0ef39..517d5d22517572c1750f5a7c4568e2f869ef3e86 100644 --- a/build.zig +++ b/build.zig @@ -117,12 +117,12 @@ pub fn build(b: *std.Build) void { const win_version: OsVersion = .{ .windows = .win7 }; inline for (.{ - b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version }), - b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version }), - b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .macos, .os_version_min = mac_version }), - b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .macos, .os_version_min = mac_version }), - b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu }), - b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu }), + b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version, .cpu_model = .baseline }), + b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version, .cpu_model = .baseline }), + b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .macos, .os_version_min = mac_version, .cpu_model = .baseline }), + b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .macos, .os_version_min = mac_version, .cpu_model = .baseline }), + b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu, .cpu_model = .baseline }), + b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu, .cpu_model = .baseline }), }) |cross_target| { const name = std.fmt.allocPrint( b.allocator, diff --git a/src/core/Air.Dump.zig b/src/core/Air.Dump.zig index 1823157e86248fd0f45d05a3ae95e20bf84059c5..ef0df4fdcdf60a66b665fdf3e18c38024b3e0643 100644 --- a/src/core/Air.Dump.zig +++ b/src/core/Air.Dump.zig @@ -92,6 +92,7 @@ pub const State = struct { .lt, .gt, .eq, + .index, => |comptime_tag| { const bin: Air.Bin = air.instData(comptime_tag, inst); try w.writeAll("("); @@ -185,6 +186,15 @@ pub const State = struct { try s.newline(w); try w.writeAll("})"); }, + .array_init => { + const array_init: Air.ArrayInit = air.instData(.array_init, inst); + const array_type = array_init.ty.toArrayType(s.ip).?; + + try w.writeAll("("); + try s.dumpValue(w, array_type.item_type.value()); + try s.dumpRefList(w, array_init.list.slice(air), '[', ']'); + try w.writeAll(")"); + }, } } @@ -337,6 +347,31 @@ pub const State = struct { try w.writeAll(">"); try tty.setColor(w, .reset); }, + .array_type => |array_type| { + try w.writeAll("[]"); + try s.dumpValue(w, array_type.item_type.value()); + }, + .array_value => |array_value| { + try tty.setColor(w, .bright_black); + try w.writeAll("<"); + try tty.setColor(w, .bright_magenta); + try s.dumpValue(w, array_value.type.value()); + try tty.setColor(w, .bright_black); + try w.writeAll(", "); + try tty.setColor(w, .reset); + try w.writeAll("["); + + for (array_value.items, 0..) |value, i| { + if (i != 0) + try w.writeAll(", "); + try s.dumpValue(w, value); + } + + try w.writeAll("]"); + try tty.setColor(w, .bright_black); + try w.writeAll(">"); + try tty.setColor(w, .reset); + }, } } @@ -388,7 +423,7 @@ pub fn format( var state = State{ .air = &air, .ip = dump.ip, - .tty = detectTTYFromWriter(writer), + .tty = core.detectTTYFromWriter(writer), .arena = arena.allocator(), }; try state.dumpInst(writer, .main_block); @@ -399,10 +434,8 @@ pub fn format( const std = @import("std"); const assert = std.debug.assert; -const Air = @import("Air.zig"); +const core = @import("mod.zig"); +const Air = core.Air; const Inst = Air.Inst; - -const InternPool = @import("InternPool.zig"); +const InternPool = core.InternPool; const Value = InternPool.Value; - -const detectTTYFromWriter = @import("util.zig").detectTTYFromWriter; diff --git a/src/core/Air.zig b/src/core/Air.zig index ac000eecad53e091fb348022e3657e17282b537c..7705135ad1eb129d2722dad7e7f55f4fcba4378a 100644 --- a/src/core/Air.zig +++ b/src/core/Air.zig @@ -25,10 +25,13 @@ pub const Inst = struct { store, struct_init, + array_init, field_opaque, field_struct, + index, + // bin add, sub, @@ -55,8 +58,10 @@ pub const Inst = struct { .memory => Memory, .store => Store, .struct_init => StructInit, + .array_init => ArrayInit, .field_opaque => FieldOpaque, .field_struct => FieldStruct, + .index, .add, .sub, .mul, @@ -272,6 +277,11 @@ pub const StructInit = struct { } }; +pub const ArrayInit = struct { + ty: Type, + list: RefList, +}; + pub const FieldStruct = struct { base: Inst.Index, field_index: u32, @@ -326,6 +336,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In .store => air.instData(.memory, air.instData(.store, index).memory).ty, .@"if" => air.instData(.@"if", index).data.get(air).union_ty, .struct_init => air.instData(.struct_init, index).ty, + .array_init => air.instData(.array_init, index).ty, .arg => air.instData(.arg, index).type, // A handful of binary operators are lowered to a form where both sides @@ -367,6 +378,14 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In .toStructType(ip) orelse unreachable; // not a struct return base_type.field_types[inst.field_index]; }, + + // Array accesses use the array they are accessing into. + .index => { + const inst = air.instData(.index, index); + const base_type = air.typeOf(inst.lhs, ip) + .toArrayType(ip) orelse unreachable; // not an array + return base_type.item_type; + }, }; } diff --git a/src/core/Ast.Dump.zig b/src/core/Ast.Dump.zig index 22bfacf4e744225ba2538eca3cb58468971310b9..093833a6f7908c3c7c27e46796a4a367d905015e 100644 --- a/src/core/Ast.Dump.zig +++ b/src/core/Ast.Dump.zig @@ -30,7 +30,7 @@ pub fn format( .node_tags = ast.nodes.items(.tag), .node_data = ast.nodes.items(.data), .node_tokens = ast.nodes.items(.main_token), - .tty = detectTTYFromWriter(writer), + .tty = core.detectTTYFromWriter(writer), }; try dumpNode(writer, &state, 0); try writer.writeAll("\n"); @@ -127,6 +127,8 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err => try dumpNodeList(writer, state, list, '{', '}'), .builtin_call, => try dumpNodeList(writer, state, list, '(', ')'), + .array_init, + => try dumpNodeList(writer, state, list, '[', ']'), else => unreachable, } }, @@ -145,14 +147,18 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err try tty.setColor(writer, .reset); var indent = false; if (var_decl.type) |type_node| { + try writer.writeAll(" {"); state.indent += 1; indent = true; try state.newline(writer); - try writer.writeAll(": "); + try writer.writeAll("type: "); try dumpNode(writer, state, type_node); - try writer.writeAll(", "); + try writer.writeAll(","); + try state.newline(writer); + try writer.writeAll("init: "); + } else { + try writer.writeAll(" = "); } - try writer.writeAll(" = "); try dumpNode(writer, state, var_decl.init); if (indent) { state.indent -= 1; @@ -312,7 +318,8 @@ fn dumpNodeList(writer: anytype, state: *State, list: []const NodeIndex, l: u8, try dumpNode(writer, state, node); } } - try state.newline(writer); + if (list.len > 0) + try state.newline(writer); try writer.print("{c}", .{r}); } @@ -321,10 +328,9 @@ const Dump = @This(); const std = @import("std"); const assert = std.debug.assert; -const Ast = @import("Ast.zig"); +const core = @import("mod.zig"); +const Ast = core.Ast; const NodeIndex = Ast.NodeIndex; const Node = Ast.Node; const TokenIndex = Ast.TokenIndex; const Call = Ast.Call; - -const detectTTYFromWriter = @import("util.zig").detectTTYFromWriter; diff --git a/src/core/Ast.zig b/src/core/Ast.zig index 248c0a8b2cf9e36e67ac1f6c1b3e67ca64d46e61..a80f54645797091cf27f3f3f8f3456835a367b40 100644 --- a/src/core/Ast.zig +++ b/src/core/Ast.zig @@ -218,6 +218,10 @@ pub const Node = struct { struct_field, /// Struct Init struct_init, + /// Array type + array_type, + /// Array initialization + array_init, pub fn isAssignBin(op: Tag) bool { return switch (op) { @@ -333,6 +337,8 @@ pub const Node = struct { .shr_assign = .bin, .string = .none, .struct_decl = .list, + .array_type = .un, + .array_init = .list, .struct_field = .un, .struct_init = .struct_init, .sub = .bin, @@ -784,12 +790,44 @@ const Ast = @This(); const std = @import("std"); const assert = std.debug.assert; -const Tokenizer = @import("Tokenizer.zig"); +const core = @import("mod.zig"); + +const Tokenizer = core.Tokenizer; const Token = Tokenizer.Token; const TokenList = Tokenizer.Token.List; const ByteOffset = Tokenizer.ByteOffset; -const Report = @import("Report.zig"); +const Report = core.Report; -const util = @import("util.zig"); -const Ordinal = util.Ordinal; +/// For numbers that are used both as user-visible one based numbers and zero +/// based offsets, use this type to reduce mistakes. A common usage of this +/// would be line and column numbers for `Report.Item`s +pub fn Ordinal(Int: type) type { + comptime assert(@typeInfo(Int).Int.signedness == .unsigned); + return enum(Int) { + start = 0, + _, + + pub fn fromZeroBased(int: Int) @This() { + assert(int != std.math.maxInt(Int)); + return @enumFromInt(int); + } + + pub fn fromOneBased(int: Int) @This() { + assert(int > 0); + return @enumFromInt(int - 1); + } + + pub fn zeroBased(ord: @This()) Int { + return @intFromEnum(ord); + } + + pub fn oneBased(ord: @This()) Int { + return @intFromEnum(ord) + 1; + } + + pub fn add(ord: @This(), inc: Int) @This() { + return fromZeroBased(ord.zeroBased() + inc); + } + }; +} diff --git a/src/core/InternPool.zig b/src/core/InternPool.zig index a62f1afb2c37477e300ab052b72e83d7dc6eada6..fd5f15febebc8024746fe6462c059af8958b30f6 100644 --- a/src/core/InternPool.zig +++ b/src/core/InternPool.zig @@ -69,6 +69,8 @@ pub const Tag = enum(u8) { function_type, /// A struct type struct_type, + /// An array type + array_type, /// A type that can be represented with only an enum tag. simple_type, @@ -92,6 +94,8 @@ pub const Tag = enum(u8) { struct_value, + array_value, + pub fn Data(comptime tag: Tag) type { return switch (tag) { .removed, @@ -118,7 +122,9 @@ pub const Tag = enum(u8) { .function_type => TypeFunction, .string => String.WithLen, .struct_value => StructValue, - .struct_type => TypeStruct, + .struct_type => StructType, + .array_type => ArrayType, + .array_value => ArrayValue, }; } @@ -140,10 +146,23 @@ pub const Tag = enum(u8) { /// Trailing: /// 0. field_key: NullTerminatedString. Repeated per field_len /// 1. field_type: Type. Repeated per field_len - pub const TypeStruct = struct { + pub const StructType = struct { fields_len: u32, }; + /// Trailing: + pub const ArrayType = struct { + item_type: Type, + }; + + /// Trailing: + /// 1. item: Type. Repeated per items_len + pub const ArrayValue = struct { + /// This is the array type, not the item type + type: Type, + items_len: u32, + }; + /// Trailing: /// 0. value: Value. Repeated ty.structInfo().field_len pub const StructValue = struct { @@ -155,9 +174,9 @@ pub const Tag = enum(u8) { /// This is an index into .items pub const Value = enum(u32) { pub const first_type: Value = .opaque_type; - pub const last_type: Value = .noreturn_type; + pub const last_type: Value = .numeric_literal_array_type; pub const first_value: Value = .void; - pub const last_value: Value = .empty_string; + pub const last_value: Value = .empty_array; opaque_type, bool_type, @@ -167,6 +186,8 @@ pub const Value = enum(u32) { noreturn_type, type_type, numeric_literal_type, + numeric_literal_array_type, + noreturn_array_type, void, bool_true, @@ -175,7 +196,7 @@ pub const Value = enum(u32) { float_zero, nan, empty_string, - standard_library, + empty_array, none = std.math.maxInt(u32), _, @@ -183,7 +204,7 @@ pub const Value = enum(u32) { /// An array of `Value` existing within the `extra` array. /// This type exists to provide a struct with lifetime that is /// not invalidated when items are added to the `InternPool`. - /// TODO: Replace with ExtraStore.Slice(Value) + // TODO: Replace with ExtraStore.Slice(Value) pub const Slice = struct { start: u32, len: u32, @@ -237,7 +258,7 @@ pub const Value = enum(u32) { // TODO: move the formatter out of Air.Dump var state: Air.Dump.State = undefined; state.ip = ctx.ip; - state.tty = @import("util.zig").detectTTYFromWriter(writer); + state.tty = core.detectTTYFromWriter(writer); try Air.Dump.State.dumpValue(&state, writer, ctx.val); } @@ -245,9 +266,11 @@ pub const Value = enum(u32) { return switch (value.valueTag(ip)) { .removed => unreachable, // UAF - .function_type => .type, - .simple_type => .type, - .struct_type => .type, + .function_type, + .simple_type, + .struct_type, + .array_type, + => .type, .simple_value => switch (value.valueData(ip, .simple_value)) { .true => .bool, @@ -261,6 +284,7 @@ pub const Value = enum(u32) { .string => .string, .func => value.toKey(ip).func.ty, .struct_value => value.toKey(ip).struct_value.ty, + .array_value => value.toKey(ip).array_value.type, }; } @@ -273,6 +297,7 @@ pub const Value = enum(u32) { .function_type, .struct_type, .simple_type, + .array_type, => @enumFromInt(@intFromEnum(value)), // exception: void is a value AND a type @@ -321,6 +346,8 @@ pub const Type = enum(u32) { noreturn = @intFromEnum(Value.noreturn_type), type = @intFromEnum(Value.type_type), numeric_literal = @intFromEnum(Value.numeric_literal_type), + numeric_literal_array = @intFromEnum(Value.numeric_literal_array_type), + noreturn_array = @intFromEnum(Value.noreturn_array_type), void = @intFromEnum(Value.void), _, @@ -328,6 +355,7 @@ pub const Type = enum(u32) { pub const Tag = enum(u32) { function_type = @intFromEnum(InternPool.Tag.function_type), struct_type = @intFromEnum(InternPool.Tag.struct_type), + array_type = @intFromEnum(InternPool.Tag.array_type), simple_type = @intFromEnum(InternPool.Tag.simple_type), }; @@ -362,6 +390,11 @@ pub const Type = enum(u32) { return ip.valueToKey(ty.value()).struct_type; } + pub fn toArrayType(ty: Type, ip: *const InternPool) ?Key.ArrayType { + if (ty.tag(ip) != .array_type) return null; + return ip.valueToKey(ty.value()).array_type; + } + pub fn hasRuntimeRepresentation(ty: Type, ip: *const InternPool) bool { return switch (ty) { .bool, @@ -377,8 +410,11 @@ pub const Type = enum(u32) { => false, else => switch (ty.tag(ip)) { .simple_type => unreachable, // all simple types are already printed - .function_type => true, - .struct_type => true, + .struct_type, + => false, + .function_type, + => true, + .array_type => ty.toArrayType(ip).?.item_type.hasRuntimeRepresentation(ip), }, }; } @@ -428,6 +464,11 @@ pub const Type = enum(u32) { .struct_type => { try writer.writeAll("[struct]"); }, + .array_type => { + try writer.writeAll("[]"); + const array_type = ty.toArrayType(ip).?; + try writer.print("{}", .{array_type.item_type.displayName(ip)}); + }, } } }; @@ -464,6 +505,8 @@ pub const static_keys = [_]Key{ SimpleType.noreturn.key(), SimpleType.type.key(), SimpleType.numeric_literal.key(), + .{ .array_type = .{ .item_type = Type.numeric_literal } }, + .{ .array_type = .{ .item_type = Type.noreturn } }, SimpleValue.void.key(), SimpleValue.true.key(), @@ -471,6 +514,10 @@ pub const static_keys = [_]Key{ .{ .int32 = 0 }, .{ .float = 0.0 }, .{ .float = Float.pure_nan.decode() }, + .{ .array_value = .{ + .type = Type.noreturn_array, + .items = &.{}, + } }, .{ .string = "" }, }; @@ -782,10 +829,12 @@ pub const Key = union(enum) { simple_value: SimpleValue, struct_value: StructValue, func: Func, + array_value: ArrayValue, // Types simple_type: SimpleType, function_type: FunctionType, struct_type: StructType, + array_type: ArrayType, const Adapter = struct { intern_pool: *const InternPool, @@ -827,6 +876,7 @@ pub const Key = union(enum) { pub fn hash(self: StructType, hasher: *Hash) void { // TODO: remove this function. structural typing is not used + // this should be hashing it's location / decl, exactly like `Func` std.hash.autoHashStrat(hasher, self, .DeepRecursive); } }; @@ -841,6 +891,25 @@ pub const Key = union(enum) { } }; + pub const ArrayType = struct { + item_type: Type, + + pub fn hash(self: ArrayType, hasher: *Hash) void { + std.hash.autoHash(hasher, self.item_type); + } + }; + + pub const ArrayValue = struct { + /// This is the array type, not the item type + type: Type, + items: []const Value, + + pub fn hash(self: ArrayValue, hasher: *Hash) void { + std.hash.autoHash(hasher, self.type); + std.hash.autoHashStrat(hasher, self.items, .Deep); + } + }; + pub const Func = struct { ty: Type, owner_decl: DeclIndex, @@ -874,22 +943,13 @@ pub const Key = union(enum) { .string, => |x| Hash.hash(seed, x), .float, .comptime_float => |x| Hash.hash(seed, asBytes(&Float.assertPure(x))), - .function_type => |x| { - var hasher = Hash.init(seed); - x.hash(&hasher); - return hasher.final(); - }, - .func => |x| { - var hasher = Hash.init(seed); - x.hash(&hasher); - return hasher.final(); - }, - .struct_type => |x| { - var hasher = Hash.init(seed); - x.hash(&hasher); - return hasher.final(); - }, - .struct_value => |x| { + inline .function_type, + .func, + .struct_type, + .struct_value, + .array_value, + .array_type, + => |x| { var hasher = Hash.init(seed); x.hash(&hasher); return hasher.final(); @@ -934,6 +994,14 @@ pub const Key = union(enum) { return a_info.ty == b_info.ty and std.mem.eql(Value, a_info.field_values, b_info.field_values); }, + .array_type => |a_info| { + return a_info.item_type == b.array_type.item_type; + }, + .array_value => |a_info| { + const b_info = b.array_value; + return a_info.type == b_info.type and + std.mem.eql(Value, a_info.items, b_info.items); + }, }; } }; @@ -1066,7 +1134,7 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key { .function_type = ip.extraTypeFunction(data.decode(.function_type, item.tag)), }, .struct_type => { - const payload: Tag.TypeStruct, var trail = + const payload: Tag.StructType, var trail = data.decode(.struct_type, item.tag).getTrail(ip); return .{ .struct_type = .{ @@ -1085,6 +1153,19 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key { ).get(ip), } }; }, + .array_type => { + const payload: Tag.ArrayType = data.decode(.array_type, item.tag).get(ip); + return .{ .array_type = .{ + .item_type = payload.item_type, + } }; + }, + .array_value => { + const payload: Tag.ArrayValue, var trail = data.decode(.array_value, item.tag).getTrail(ip); + return .{ .array_value = .{ + .type = payload.type, + .items = trail.readValueSlice(payload.items_len).get(ip), + } }; + }, }; } @@ -1147,7 +1228,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { }, .struct_type => |s| brk: { assert(s.field_keys.len == s.field_types.len); - const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TypeStruct, .{ + const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.StructType, .{ .fields_len = @intCast(s.field_keys.len), }, s.field_keys.len * 2); trail.writeSlice(s.field_keys); @@ -1163,6 +1244,19 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { trail.writeSlice(s.field_values); break :brk Item.encode(.struct_value, extra); }, + .array_type => |s| brk: { + break :brk Item.encode(.array_type, try ip.extra.append(gpa, Tag.ArrayType, .{ + .item_type = s.item_type, + })); + }, + .array_value => |s| brk: { + const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.ArrayValue, .{ + .type = s.type, + .items_len = @intCast(s.items.len), + }, s.items.len); + trail.writeSlice(s.items); + break :brk Item.encode(.array_value, extra); + }, }; ip.items.appendAssumeCapacity(item); const index: Value = @enumFromInt(ip.items.len - 1); @@ -1332,13 +1426,12 @@ const Allocator = std.mem.Allocator; const Hash = std.hash.Wyhash; const assert = std.debug.assert; -const Comp = @import("Comp.zig"); -const Cache = @import("Cache.zig"); - -const Teg = @import("Teg.zig"); -const Air = @import("Air.zig"); - -const dod = @import("array.zig"); +const core = @import("mod.zig"); +const Comp = core.Comp; +const Cache = core.Cache; +const Teg = core.Teg; +const Air = core.Air; +const dod = core.array; const Float = dod.Float; const tracer = @import("tracer"); diff --git a/src/core/Parse.zig b/src/core/Parse.zig index 8cd2d2fc08b3ffd4c3eecb3c7990bb0df94c4fb3..cea204e7dbcd9ae887bd7a7b9faee639d418d3f1 100644 --- a/src/core/Parse.zig +++ b/src/core/Parse.zig @@ -141,6 +141,8 @@ pub const AstError = union(enum) { /// Went past depth maximum depth_limit: Token, + array_type_flipped_hint: Token, + pub fn toReport(err: AstError, comp: *Comp, file: *Comp.File, trace: Report.DebugStoredTrace) !Report { return (Report.Err{ .ast = err }).toReport(comp, file, trace); } @@ -176,6 +178,13 @@ pub const AstError = union(enum) { data.got.tag.description(), }); }, + .array_type_flipped_hint => |token| { + try ctx.msg(.{ .token_stored = token }, "expected {s}, but found {s}", .{ + ExpectedContext.expression.description(), + token.tag.description(), + }); + try ctx.msgAlone("the syntax for array types is \"[]T\", not \"T[]\"", .{}); + }, .depth_limit => |data| { try ctx.msg(.{ .token_stored = data }, "syntax tree is nested too deeply (limit: {d})", .{depth_limit}); }, @@ -237,7 +246,7 @@ fn parseModuleMembers(p: *Parse) Error!SubList { => try p.scratch.append(p.gpa, try p.parseModuleDecl()), .kw_struct, - => try p.scratch.append(p.gpa, try p.parseStructDecl(.declaration)), + => try p.scratch.append(p.gpa, try p.parseStructDecl()), .kw_enum, .kw_trait, @@ -383,13 +392,12 @@ fn parseParamDecl(p: *Parse, types: TypePresence) Error!NodeIndex { }); } -fn parseStructDecl(p: *Parse, ctx: ExprParseContext) Error!NodeIndex { +fn parseStructDecl(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); const main_token = p.assertToken(.kw_struct); - _ = ctx; // TODO: _ = try p.expect(.id); _ = try p.expect(.start_brace); @@ -586,6 +594,7 @@ fn isExpressionStart(p: *Parse) bool { .number_float, .number_int, .start_brace, + .start_bracket, .start_paren, .string, .tilde, @@ -840,13 +849,14 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { // Complex expressions .builtin_id => try p.parseBuiltinCall(), - .kw_return => try p.parseReturn(), + .dot => try p.parseAnonInstantiation(), .kw_break => try p.parseBreak(), + .kw_fn => try p.parseAnonFunction(), .kw_if => try p.parseIf(), - .start_paren => try p.parseGroup(), + .kw_return => try p.parseReturn(), .start_brace => try p.parseBlock(), - .dot => try p.parseAnonInstantiation(), - .kw_fn => try p.parseAnonFunction(), + .start_bracket => try p.parseArrayValueOrType(mode), + .start_paren => try p.parseGroup(), // Unary operators inline .bang, @@ -900,7 +910,7 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { }, .start_paren => { const main_token = p.nextToken(); - const args = try p.parseCallArgs(); + const args = try p.parseExpressionList(.end_paren, .argument); const extra = try p.addExtra(Ast.Call, .{ .args = args, @@ -917,7 +927,12 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { }, .start_bracket => { const main_token = p.nextToken(); - const index = try p.parseExpression(.expression); + + if (p.currentTag() == .end_bracket) { + return p.fail(.{ .array_type_flipped_hint = p.fullToken(p.pos) }); + } + + const index = try p.tryParseExpression(.expression); expr = try p.addNode(.{ .tag = .index, @@ -1034,7 +1049,7 @@ fn parseBuiltinCall(p: *Parse) Error!NodeIndex { _ = try p.expect(.start_paren); - const args = try p.parseCallArgs(); + const args = try p.parseExpressionList(.end_paren, .argument); return try p.addNode(.{ .tag = .builtin_call, @@ -1043,7 +1058,11 @@ fn parseBuiltinCall(p: *Parse) Error!NodeIndex { }); } -fn parseCallArgs(p: *Parse) Error!SubList { +fn parseExpressionList( + p: *Parse, + comptime end_token: Token.Tag, + comptime fail_context: ExpectedContext, +) Error!SubList { const trace = t.trace(@src()); defer trace.end(); @@ -1054,19 +1073,19 @@ fn parseCallArgs(p: *Parse) Error!SubList { try p.scratch.append(p.gpa, try p.parseExpression(.expression)); switch (p.currentTag()) { - .end_paren => { + end_token => { break; }, .comma => { _ = p.nextToken(); - if (p.currentTag() == .end_paren) + if (p.currentTag() == end_token) break; }, - else => return p.failUnexpectedToken(.argument), + else => return p.failUnexpectedToken(fail_context), } } - _ = try p.expect(.end_paren); + _ = try p.expect(end_token); return p.scratchToSubList(scratch_top); } @@ -1204,6 +1223,49 @@ fn parseAnonFunction(p: *Parse) Error!NodeIndex { }); } +fn parseArrayValueOrType(p: *Parse, mode: ParseExprMode) Error!NodeIndex { + const main_token = p.assertToken(.start_bracket); + + if (p.eatToken(.end_bracket) != null) { + // Either array type or an empty array. These are technically ambiguous forms: + // + // [] & x; + // + // This is either a binary expression with an empty array, or an array + // of references. Since the latter makes more sense, we will defer to + // that. The same applies to the other operators that are both prefix + // and infix, however those will all lead to semantic analysis errors. + if (!p.isExpressionStart() or + (mode == .preceding_block and p.currentTag() == .start_brace)) + { + // [] + return p.addNode(.{ + .tag = .array_init, + .main_token = main_token, + .data = .{ .list = SubList.empty }, + }); + } else { + // []T + const operand = try p.parseExpressionPart(mode); + return p.addNode(.{ + .tag = .array_type, + .main_token = main_token, + .data = .{ .un = .{ + .expr = operand, + } }, + }); + } + } + + // [x, y, z] + const list = try p.parseExpressionList(.end_bracket, .expression); + return p.addNode(.{ + .tag = .array_init, + .main_token = main_token, + .data = .{ .list = list }, + }); +} + // helpers /// Append a node and return its index. diff --git a/src/core/Report.zig b/src/core/Report.zig index 317ee696d37a439c83265b4c73c23757d2fa3c71..cac8f4db1023d84c544d5969dba22e81ae2e0b25 100644 --- a/src/core/Report.zig +++ b/src/core/Report.zig @@ -490,10 +490,25 @@ pub const Warn = union(enum) { fn build(data: @This(), ctx: *MsgContext) !void { const node = try data.src.get(ctx); - try ctx.msgNode(node, "constant integer in a \"var\" has an unknown type, assuming \"float\"", .{}); + try ctx.msgNode(node, "constant integer cannot be represented as a runtime value, implicitly casting to \"float\"", .{}); try ctx.msgAlone("add \": float\" or \": int\" after the variable name to set the type", .{}); } }, + numeric_int_array_var_ambiguity: struct { + src: LazyNode, + + fn build(data: @This(), ctx: *MsgContext) !void { + const node = try data.src.get(ctx); + try ctx.msgNode( + node, + "\"{}\" cannot be represented as a runtime value, implicitly casting to \"[]float\"", + .{ + Type.numeric_literal_array.displayName(ctx.ip()), + }, + ); + try ctx.msgAlone("add \": []float\" or \": []int\" after the variable name to set the type", .{}); + } + }, }; /// A variant of std.builtin.StackTrace that stores its data within itself @@ -1065,6 +1080,11 @@ pub const LazyNode = struct { }, } }, + .array_init => { + assert(node_tag == .array_init); + const items = data.list.slice(ctx.ast); + return items[lazy.operand.getArgId()]; + }, else => {}, } @@ -1318,5 +1338,3 @@ const File = Comp.File; const InternPool = @import("InternPool.zig"); const Value = InternPool.Value; const Type = InternPool.Type; - -const util = @import("util.zig"); diff --git a/src/core/Sema.zig b/src/core/Sema.zig index 7c1afb15eccec51a1da66e4a658b372cac5c0dae..ba2455adb3d259529e70c416a0815b7198086ee6 100644 --- a/src/core/Sema.zig +++ b/src/core/Sema.zig @@ -36,9 +36,9 @@ namespace_index: Namespace.Index, // temp data: inst_map: InstMap = undefined, -/// scratch memory. can be used in any place you might temporarily allocate an arraylist -/// this is most notably used to store block contents before the block is finalized -scratch: std.ArrayListUnmanaged(u32) = .{}, +/// Scratch memory for temporary allocations. +/// Block contents are appended for marks. +scratch: core.U32Stack = .{}, /// This is only set for function declarations params: []Type = undefined, @@ -134,7 +134,7 @@ pub fn deinit(s: *Sema) void { defer s.* = undefined; s.inst_map.deinit(s.arena); - s.scratch.deinit(s.arena); + s.scratch.list.deinit(s.arena); s.air_extra.deinit(s.gpa); s.air_instructions.deinit(s.gpa); s.temp_result_map.deinit(s.arena); @@ -205,7 +205,7 @@ pub fn analyzeFunctionDeclaration(s: *Sema) Error!Value { defer s.gpa.free(s.params); for (fn_decl.params.slice(&s.code), 0..) |param, i| { - const src = s.inst_map.start.op(Report.Operand.argument(@intCast(i))); + const src = s.inst_map.start.op(operandArgument(@intCast(i))); const ty = try s.resolveType(src, param); if (!ty.hasRuntimeRepresentation(s.ip)) { return s.fail(.param_comptime_only, .{ @@ -268,8 +268,8 @@ pub fn analyzeBlock(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { const t = tracer.trace(@src()); defer t.end(); - const scratch_top = s.scratch.items.len; - defer s.scratch.shrinkRetainingCapacity(scratch_top); + const scratch = s.scratch.mark(); + defer scratch.deinit(); const block = try s.addBlock(); try s.inst_map.add(in, block.block_index.toRef()); @@ -284,10 +284,14 @@ pub fn analyzeBlock(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { }; } - if (scratch_top + 1 == s.scratch.items.len) { - const single_instruction = s.air_instructions.items(.tag)[@intFromEnum(block.block_index) + 1]; + if (scratch.len() == 1) { + const air_instructions = s.air_instructions.slice(); + const single_inst_id = @intFromEnum(block.block_index) + 1; + assert(@intFromEnum(scratch.slice(Air.Inst.Index)[0]) == single_inst_id); + + const single_instruction = air_instructions.items(.tag)[single_inst_id]; if (single_instruction == .@"break") { - const data: Air.Break = s.air_instructions.items(.data)[@intFromEnum(block.block_index) + 1] + const data: Air.Break = air_instructions.items(.data)[single_inst_id] .decode(.@"break", single_instruction); if (data.block_ref == block.block_index) { // This is a block that resolved to just a single value ref with @@ -303,7 +307,7 @@ pub fn analyzeBlock(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { } } - return (try block.finish(s, scratch_top)).toRef(); + return (try block.finish(s, scratch)).toRef(); } fn analyzeCode(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { @@ -534,7 +538,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { for (proto.param_types, call.args.slice(&s.code), 0..) |param, arg, i| { const ref = try s.resolveRef(arg); const coerced = try s.coerceRef( - in.op(Report.Operand.argument(@intCast(i))), + in.op(operandArgument(@intCast(i))), ref, param, ); @@ -543,7 +547,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { } else { for (call.args.slice(&s.code), 0..) |arg, i| { const ref = try s.ensureValueRuntime( - in.op(Report.Operand.argument(@intCast(i))), + in.op(operandArgument(@intCast(i))), try s.resolveRef(arg), ); scratch[i] = ref; @@ -571,7 +575,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { defer s.arena.free(scratch); for (call.args.slice(&s.code), 0..) |arg, i| { const ref = try s.ensureValueRuntime( - in.op(Report.Operand.argument(@intCast(i))), + in.op(operandArgument(@intCast(i))), try s.resolveRef(arg), ); scratch[i] = ref; @@ -635,7 +639,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { const block = try s.analyzeBlock(in); switch (block.decode()) { .index => |t| if (s.tempAir().instTag(t) == .block) { - try s.scratch.append(s.gpa, @intFromEnum(t)); + try s.scratch.add(s.gpa, Air.Inst.Index, t); }, else => {}, } @@ -692,7 +696,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { .compile_error => { const node: Teg.Inst.RefNode = s.code.instData(.compile_error, in); const value = try s.resolveCoerceValue( - in.op(Report.Operand.argument(0)), + in.op(operandArgument(0)), node.operand, .string, ); @@ -705,42 +709,26 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { .struct_decl => { const node: Teg.Inst.StructDecl = s.code.instPayload(.struct_decl, in); - const scratch_top = s.scratch.items.len; - try s.scratch.ensureUnusedCapacity(s.arena, node.field_len * 2); - s.scratch.items.len += node.field_len * 2; - defer s.scratch.shrinkRetainingCapacity(scratch_top); + const scratch = try s.scratch.reserveMulti( + s.arena, + .{ .keys = NullTerminatedString, .types = Type }, + .{ node.field_len, node.field_len }, + ); + defer scratch.deinit(); - for ( - node.keys(&s.code), - // Keeping the slice pointer is fine for this loop, since scratch is not resized - s.scratch.items[scratch_top..][0..node.field_len], - ) |key, *dest| { - dest.* = @intFromEnum(try s.ip.getOrPutString( - s.gpa, - key.slice(s.code.string_bytes), - .no_embedded_nulls, - )); + // Fill the first half with keys + for (scratch.slice(.keys), node.keys(&s.code)) |*dest, src| { + dest.* = try s.ip.getOrPutString(s.gpa, src.slice(s.code.string_bytes), .no_embedded_nulls); } - for ( - node.types(&s.code), - // We cannot reuse the pointer from scratch between calls to sema - scratch_top + node.field_len.., - 0.., - ) |ty, i, arg_i| { - s.scratch.items[i] = @intFromEnum(try s.resolveCoerceValue( - in.op(Report.Operand.argument(@intCast(arg_i))), - ty, - .type, - )); + // Fill the second half with coerced values. + for (node.types(&s.code), 0..) |ty, i| { + scratch.at(.types, i).* = try s.resolveType(in.op(operandArgument(@intCast(i))), ty); } - const keys = s.scratch.items[scratch_top..][0..node.field_len]; - const types = s.scratch.items[scratch_top + node.field_len ..][0..node.field_len]; - return try s.makeValueAir(.{ .struct_type = .{ - .field_keys = @ptrCast(keys), - .field_types = @ptrCast(types), + .field_keys = scratch.slice(.keys), + .field_types = scratch.slice(.types), } }); }, .struct_init => { @@ -760,22 +748,13 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { var is_constant = true; // Allocate scratch space to store the values as they are being prepared. - // TODO: make this an abstraction - const scratch_top = s.scratch.items.len; - - try s.scratch.ensureUnusedCapacity(s.arena, struct_type.field_types.len); - const storage: []Air.Inst.Ref.Optional = - @ptrCast(s.scratch.unusedCapacitySlice()[0..struct_type.field_types.len]); - s.scratch.items.len += storage.len; - @memset(storage, .none); - - const scratch_end = s.scratch.items.len; - defer { - const from = s.scratch.items[scratch_end..]; - const dest = s.scratch.items[scratch_top..][0..from.len]; - std.mem.copyForwards(u32, dest, from); - s.scratch.shrinkRetainingCapacity(scratch_top + from.len); - } + const scratch = try s.scratch.reserve( + s.arena, + Air.Inst.Ref.Optional, + struct_type.field_types.len, + ); + defer scratch.deinit(); + @memset(scratch.slice(), .none); for (node.keys(&s.code), node.inits(&s.code), 0..) |init_key, init_value, i| { // The order of `storage` is as ordered by the type, but this @@ -789,21 +768,20 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { has_errors = true; try s.addError(.struct_init_unknown_field, .{ .ty = ty, - .src = in.op(Report.Operand.argument(@intCast(i))).lazyNode(s.decl_index), + .src = in.op(operandArgument(@intCast(i))).lazyNode(s.decl_index), }); continue; }; - if (storage[found_index] != .none) { + if (scratch.at(found_index).* != .none) { has_errors = true; try s.addError(.struct_init_duplicate_field, .{ .ty = ty, - .src = in.op(Report.Operand.argument(@intCast(i))).lazyNode(s.decl_index), + .src = in.op(operandArgument(@intCast(i))).lazyNode(s.decl_index), }); continue; } - // TODO: calling coerceRef / resolveRefRuntime will clobber the `storage` pointer const coerced = s.resolveCoerceRef( - in.op(Report.Operand.argument(@intCast(i))), + in.op(operandArgument(@intCast(i))), init_value, struct_type.field_types[found_index], ) catch |e| switch (e) { @@ -811,17 +789,18 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { // Instead of bailing out with `try`, we will continue the loop. has_errors = true; // Mark something non-null so that duplicate field errors are emitted. - storage[found_index] = comptime Value.void.toAir().toOptional(); + scratch.at(found_index).* = comptime Value.void.toAir().toOptional(); continue; }, else => return e, }; if (coerced.kind == .index) is_constant = false; - storage[found_index] = coerced.toOptional(); + scratch.at(found_index).* = coerced.toOptional(); } // Check for missing fields { + const storage = scratch.slice(); var i: usize = 0; while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| { try s.addError(.struct_init_missing_field, .{ @@ -839,8 +818,8 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { if (is_constant) { // Convert all items in storage into a Value type - const result_storage: []Value = @ptrCast(storage); - for (storage, result_storage) |ref, *value| { + const result_storage = scratch.sliceReinterpret(Value); + for (scratch.slice(), scratch.sliceReinterpret(Value)) |ref, *value| { value.* = ref.unwrap().?.decode().value; } return try s.makeValueAir(.{ .struct_value = .{ @@ -848,16 +827,80 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { .field_values = result_storage, } }); } else { - // TODO: make this less error prone - const result_storage: []u32 = @ptrCast(storage); - const value_index = try s.air_extra.alloc(s.gpa, result_storage.len); - @memcpy(s.air_extra.list.items[value_index..].ptr, result_storage); + // Create a runtime struct instantiation + const value_index = try s.air_extra.appendSlice(s.gpa, scratch.slice()); return try s.addInstRef(.struct_init, .{ .ty = ty, .field_values = value_index, }); } }, + + .array_init => { + const node: Teg.Inst.ArrayInit = s.code.instPayload(.array_init, in); + + assert(node.list.len > 0); + + const scratch = try s.scratch.reserve(s.arena, Air.Inst.Ref, node.list.len); + defer scratch.deinit(); + + var item_type: ?Type = if (node.type_hint.unwrap()) |ty| + try s.resolveType(in.op(.operand), ty) + else + null; + + var is_constant = true; + + for (node.list.slice(&s.code), 0..) |item_teg_ref, i| { + const item = try s.resolveRef(item_teg_ref); + const coerced = if (item_type) |ty| + try s.coerceRef( + in.op(operandArgument(@intCast(i))), + item, + ty, + ) + else brk: { + item_type = s.typeOfRef(item); + break :brk item; + }; + + if (coerced.kind == .index) is_constant = false; + + scratch.at(i).* = coerced; + } + + const ty = try s.makeType(.{ .array_type = .{ + .item_type = item_type orelse unreachable, + } }); + + if (is_constant) { + // Convert all items in storage into a Value type + const result_storage = scratch.sliceReinterpret(Value); + for (scratch.slice(), scratch.sliceReinterpret(Value)) |ref, *value| { + value.* = ref.decode().value; + } + return try s.makeValueAir(.{ .array_value = .{ + .type = ty, + .items = result_storage, + } }); + } else { + // Create a runtime array instantiation + return try s.addInstRef(.array_init, .{ + .ty = ty, + .list = .{ + .start = try s.air_extra.appendSlice(s.gpa, scratch.slice()), + .len = node.list.len, + }, + }); + } + }, + + .array_type => { + const un: Teg.Inst.RefNode = s.code.instData(.array_type, in); + const operand = try s.resolveType(in.op(.operand), un.operand); + return s.makeValueAir(.{ .array_type = .{ .item_type = operand } }); + }, + else => |tag| std.debug.panic("TODO: analyze of .{s}", .{@tagName(tag)}), } } @@ -871,15 +914,15 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref { const t = tracer.trace(@src()); defer t.end(); - const scratch_top = s.scratch.items.len; - defer s.scratch.shrinkRetainingCapacity(scratch_top); + const scratch = s.scratch.mark(); + defer scratch.deinit(); const visited = s.resolveRef(in) catch |err| switch (err) { error.HitNoReturn => Value.void.toAir(), else => return err, }; - if (scratch_top == s.scratch.items.len and visited.kind == .value) { + if (scratch.len() == 0 and visited.kind == .value) { return visited; } @@ -894,25 +937,27 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref { .block_ref = block.block_index, .value = visited, }); - return (try block.finish(s, scratch_top)).toRef(); + return (try block.finish(s, scratch)).toRef(); } fn resolveRefExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Inst.Ref { - const index = ref.toIndex() orelse { - return ref.toValue().?.toAir(); - }; + return switch (ref.decode()) { + .value => |value| value.toAir(), + .index => |index| { + if (s.inst_map.get(index)) |air| { + if (mark) { + switch (air.decode()) { + .index => |i| s.markInst(i), + else => {}, + } + } - if (s.inst_map.get(index)) |air| { - if (mark) { - switch (air.decode()) { - .index => |i| s.markInst(i), - else => {}, + return air; } - } - return air; - } - return s.analyzeCode(index); + return s.analyzeCode(index); + }, + }; } fn resolveRef(s: *Sema, ref: Teg.Inst.Ref) Error!Air.Inst.Ref { @@ -997,21 +1042,21 @@ fn resolveType(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref) !Type { } /// Coerce `value` runtime ref to `coerce_type`, and emit an error at `src`'s location -fn coerceRef(s: *Sema, src: InstOp, value: Air.Inst.Ref, to: Type) !Air.Inst.Ref { - const t = tracer.traceFmt(@src(), "{} to {}", .{ value.dump(s.ip), to.dump(s.ip) }); +fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref { + const t = tracer.traceFmt(@src(), "{} to {}", .{ ref.dump(s.ip), to.dump(s.ip) }); defer t.end(); - const from = s.typeOfRef(value); + const from = s.typeOfRef(ref); if (from == to) { - return value; + return ref; } if (from == .noreturn) { return error.HitNoReturn; } if (from == .@"opaque") { - return value; + return ref; } - const index = switch (value.decode()) { + const index = switch (ref.decode()) { .value => |v| return (try s.coerceValue(src, v, to)).toAir(), .index => |index| index, }; @@ -1053,7 +1098,7 @@ fn coerceRef(s: *Sema, src: InstOp, value: Air.Inst.Ref, to: Type) !Air.Inst.Ref } /// Same as `coerceRef` but on a compile-time known value -fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value { +fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value { const from = value.typeOf(s.ip); if (from == to) { return value; @@ -1069,11 +1114,7 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value { .int32 => unreachable, // same type handled .numeric_literal, .float => switch (value.toKey(s.ip)) { .comptime_float, .float => |val| { - if (!math.isFinite(val) or - val < math.minInt(i32) or - val > math.maxInt(i32) or - (@rem(val, 1) != 0)) - { + if (!core.inInt32Range(val)) { return s.fail(.comptime_float_to_int, .{ .val = val, .src = s.lazyNode(src), @@ -1115,24 +1156,49 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value { // non-types do not coerce to types }, .void => if (value == .void) return .void, - else => {}, + else => { + if (to.toArrayType(s.ip)) |to_array_type| { + if (value.valueTag(s.ip) == .array_value) { + return s.coerceArrayValue(src, value, to_array_type.item_type); + } + } + }, } return s.coercionError(src, from, to); } -fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) !Air.Inst.Ref { +fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.Ref { switch (ref.decode()) { .value => |v| { const ty = v.typeOf(s.ip); switch (ty) { .numeric_literal => { - try s.report.warn(.numeric_int_var_ambiguity, .{ - .src = s.lazyNode(src), - }); + const float = v.toFloat(s.ip) orelse unreachable; + + if (!core.inInt32Range(float)) { + try s.report.warn(.numeric_int_var_ambiguity, .{ + .src = s.lazyNode(src), + }); + } + return (try s.makeValue(.{ - .float = v.toFloat(s.ip) orelse unreachable, + .float = float, })).toAir(); }, + .numeric_literal_array => { + try s.report.warn(.numeric_int_array_var_ambiguity, .{ + .src = s.lazyNode(src), + }); + return (try s.coerceArrayValue(src, v, .float)).toAir(); + }, + .noreturn_array => { + if (v.valueData(s.ip, .array_value).get(s.ip).items_len != 0) { + return s.fail(.runtime_incompatible_type, .{ + .src = s.lazyNode(src), + .type = ty, + }); + } + }, .type, .noreturn, => return s.fail(.runtime_incompatible_type, .{ @@ -1147,12 +1213,11 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) !Air.Inst.Ref { .string, .void, => {}, - _ => switch (ty.tag(s.ip)) { - .simple_type => unreachable, // handled above - - // Allowed at runtime - .function_type => {}, - .struct_type => {}, + _ => if (!ty.hasRuntimeRepresentation(s.ip)) { + return s.fail(.runtime_incompatible_type, .{ + .src = s.lazyNode(src), + .type = ty, + }); }, } }, @@ -1161,6 +1226,24 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) !Air.Inst.Ref { return ref; } +fn coerceArrayValue(s: *Sema, src: InstOp, value: Value, item_type: Type) !Value { + const array: InternPool.Tag.ArrayValue, var trail = value.valueData(s.ip, .array_value).getTrail(s.ip); + + const scratch = try s.scratch.reserve(s.arena, Value, array.items_len); + defer scratch.deinit(); + + const from = trail.readValueSlice(array.items_len); + + for (0..from.len) |i| { + scratch.at(i).* = try s.coerceValue(src, from.get(s.ip)[i], item_type); + } + + return try s.makeValue(.{ .array_value = .{ + .type = try s.makeType(.{ .array_type = .{ .item_type = item_type } }), + .items = scratch.slice(), + } }); +} + fn resolveDecl(s: *Sema, src: InstOp, decl_index: Decl.Index) Error!Value { const decl = s.ip.declPtr(decl_index); switch (decl.analysis) { @@ -1382,8 +1465,8 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value { const IncompleteBlock = struct { block_index: Air.Inst.Index, - pub fn finish(block: IncompleteBlock, s: *Sema, scratch_top: usize) !Air.Inst.Index { - s.setInstData(.block, block.block_index, try s.scratchToIndexList(scratch_top)); + pub fn finish(block: IncompleteBlock, s: *Sema, scratch_mark: core.U32Stack.Mark) !Air.Inst.Index { + s.setInstData(.block, block.block_index, try s.scratchMarkToIndexList(scratch_mark)); return block.block_index; } }; @@ -1404,7 +1487,7 @@ fn addInstRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst. .flags = .{}, }); const index: Air.Inst.Index = @enumFromInt(s.air_instructions.len - 1); - try s.scratch.append(s.arena, @intFromEnum(index)); + try s.scratch.add(s.arena, Air.Inst.Index, index); return index.toRef(); } @@ -1423,27 +1506,30 @@ fn makeValue(s: *Sema, key: InternPool.Key) !Value { return try s.ip.get(s.gpa, key); } +fn makeType(s: *Sema, key: InternPool.Key) !Type { + return (try s.makeValue(key)).toTypeUnsafe(s.ip); +} + fn makeValueAir(s: *Sema, key: InternPool.Key) !Air.Inst.Ref { return (try s.makeValue(key)).toAir(); } fn mapTeg(s: *Sema, inst: Teg.Inst.Ref) Air.Inst.Ref { - if (inst.toValue()) |interned| { - return interned.toAir(); - } - const index = inst.toIndex().?; - return s.inst_map.get(index) orelse { - if (std.debug.runtime_safety) { - std.debug.panic("Unmapped operand %{d}", .{@intFromEnum(index)}); - } - unreachable; + return switch (inst.decode()) { + .value => |value| value.toAir(), + .index => |index| s.inst_map.get(index) orelse { + if (std.debug.runtime_safety) { + std.debug.panic("Unmapped operand %{d}", .{@intFromEnum(index)}); + } + unreachable; + }, }; } -fn scratchToIndexList(s: *Sema, scratch_top: usize) !Air.IndexList { - const items = s.scratch.items[scratch_top..]; +fn scratchMarkToIndexList(s: *Sema, mark: core.U32Stack.Mark) !Air.IndexList { + const items = mark.slice(u32); const extra_top = s.air_extra.list.items.len; - try s.air_extra.list.appendSlice(s.gpa, s.scratch.items[scratch_top..]); + try s.air_extra.list.appendSlice(s.gpa, items); return .{ .start = @intCast(extra_top), .len = @intCast(items.len), @@ -1524,12 +1610,15 @@ const Teg = core.Teg; const InstOp = Teg.InstOp; const Air = core.Air; -const Report = core.Report; const Ast = core.Ast; const store = core.array; -const tracer = @import("tracer"); + +const Report = core.Report; +const operandArgument = Report.Operand.argument; const InternPool = core.InternPool; const Value = InternPool.Value; const Type = InternPool.Type; const NullTerminatedString = InternPool.NullTerminatedString; + +const tracer = @import("tracer"); diff --git a/src/core/Teg.Dump.zig b/src/core/Teg.Dump.zig index 0c77e875872cb4bdc0e51e5ae66b1b086017c277..ebb02f0270df35a178c69589d84eba5f02d5a6f2 100644 --- a/src/core/Teg.Dump.zig +++ b/src/core/Teg.Dump.zig @@ -101,6 +101,7 @@ const State = struct { try tty.setColor(writer, .reset); }, inline .field, + .index, .add, .sub, .mul, @@ -172,6 +173,7 @@ const State = struct { .fetch, .compile_error, .type_of, + .array_type, => |comptime_tag| { const data = teg.instData(comptime_tag, inst); try writer.writeAll("("); @@ -255,28 +257,59 @@ const State = struct { try s.newline(writer); try writer.writeAll("}"); }, + .array_init => { + const data: Teg.Inst.ArrayInit = teg.instPayload(.array_init, inst); + + if (data.type_hint.unwrap()) |type_hint| { + try writer.writeAll("("); + try s.dumpRef(writer, type_hint); + try writer.writeAll(", "); + } + + try writer.writeAll("["); + + s.indent += 1; + + for (data.list.slice(teg)) |item| { + try s.newline(writer); + try s.dumpRef(writer, item); + try writer.writeAll(","); + } + + s.indent -= 1; + try s.newline(writer); + + try writer.writeAll("]"); + + if (data.type_hint != .none) + try writer.writeAll(")"); + }, } } fn dumpRef(s: *State, writer: anytype, ref: Teg.Inst.Ref) !void { - if (ref.toIndex()) |index| { - try s.dumpInst(writer, index); - } else { - try s.tty.setColor(writer, .blue); - try writer.print("%{s}", .{@tagName(ref)}); - try s.tty.setColor(writer, .reset); + switch (ref.decode()) { + .index => |index| try s.dumpInst(writer, index), + .value => { + try s.tty.setColor(writer, .blue); + try writer.print("%{s}", .{@tagName(ref)}); + try s.tty.setColor(writer, .reset); + }, } } fn dumpRefMarker(s: *State, writer: anytype, ref: Teg.Inst.Ref) !void { - if (ref.toIndex()) |index| { - try s.tty.setColor(writer, .blue); - try writer.print("%{d}", .{@intFromEnum(index)}); - try s.tty.setColor(writer, .reset); - } else { - try s.tty.setColor(writer, .blue); - try writer.print("%{s}", .{@tagName(ref)}); - try s.tty.setColor(writer, .reset); + switch (ref.decode()) { + .index => |index| { + try s.tty.setColor(writer, .blue); + try writer.print("%{d}", .{@intFromEnum(index)}); + try s.tty.setColor(writer, .reset); + }, + .value => { + try s.tty.setColor(writer, .blue); + try writer.print("%{s}", .{@tagName(ref)}); + try s.tty.setColor(writer, .reset); + }, } } @@ -356,6 +389,6 @@ pub fn format( const std = @import("std"); const assert = std.debug.assert; -const Teg = @import("Teg.zig"); - -const detectTTYFromWriter = @import("util.zig").detectTTYFromWriter; +const core = @import("mod.zig"); +const Teg = core.Teg; +const detectTTYFromWriter = core.detectTTYFromWriter; diff --git a/src/core/Teg.zig b/src/core/Teg.zig index d67c89ab0e6ab7f8c795cc3f8b56ce1e8353d820..d194ba986f8ba8a409e4c41bbd0640e40f4d8ca8 100644 --- a/src/core/Teg.zig +++ b/src/core/Teg.zig @@ -46,6 +46,9 @@ pub const Inst = struct { struct_decl, struct_init, + array_type, + array_init, + add, sub, mul, @@ -70,6 +73,7 @@ pub const Inst = struct { bool_not, bit_not, field, + index, call, @"comptime", @@ -103,6 +107,7 @@ pub const Inst = struct { .js_new => PlNode(JsNew), .struct_decl => PlNode(StructDecl), .struct_init => PlNode(StructInit), + .array_init => PlNode(ArrayInit), // Single instruction operand .memory, @@ -118,10 +123,12 @@ pub const Inst = struct { .compile_error, .@"comptime", .type_of, + .array_type, => RefNode, // Two instruction operands .field, + .index, .add, .sub, .mul, @@ -150,7 +157,7 @@ pub const Inst = struct { } }; - /// TODO: make enum? idk + // TODO: make this an enum const NodeOffset = i32; pub fn PlNode(comptime Payload: type) type { @@ -184,7 +191,7 @@ pub const Inst = struct { main_module_inst = 0, _, - pub const static_len = @intFromEnum(Ref.empty_string) + 1; + pub const static_len = @intFromEnum(Ref.empty_array) + 1; pub fn toRef(i: Index) Inst.Ref { return @enumFromInt(static_len + @intFromEnum(i)); @@ -229,6 +236,8 @@ pub const Inst = struct { noreturn_type, type_type, numeric_literal_type, + numeric_literal_array_type, + noreturn_array_type, void, bool_true, @@ -237,11 +246,7 @@ pub const Inst = struct { float_zero, nan, empty_string, - standard_library, - - /// This Ref does not correspond to any instruction or constant - /// value and may instead be used as a sentinel to indicate null. - none = std.math.maxInt(u32), + empty_array, _, @@ -258,8 +263,7 @@ pub const Inst = struct { .{ .value = @enumFromInt(ref_int) }; } - pub fn toIndex(ref: Ref) ?Index { - assert(ref != .none); + pub fn index(ref: Ref) ?Index { const ref_int = @intFromEnum(ref); if (ref_int >= Index.static_len) { return @enumFromInt(ref_int - Index.static_len); @@ -268,12 +272,7 @@ pub const Inst = struct { } } - pub fn toIndexAllowNone(inst: Ref) ?Index { - if (inst == .none) return null; - return toIndex(inst); - } - - pub fn toValue(inst: Ref) ?InternPool.Value { + pub fn value(inst: Ref) ?InternPool.Value { const ref_int = @intFromEnum(inst); // TODO: is this > or >= @@ -282,6 +281,15 @@ pub const Inst = struct { return @enumFromInt(ref_int); } + + pub const Optional = enum(u32) { + none = std.math.maxInt(u32), + _, + + pub fn unwrap(opt: Optional) ?Ref { + return if (opt != .none) @enumFromInt(@intFromEnum(opt)) else null; + } + }; }; pub const Bin = struct { @@ -359,6 +367,15 @@ pub const Inst = struct { } }; + pub const ArrayInit = struct { + /// Optionally provide a result type for more reliable type coercion. + /// If specified, this must be an array type. Otherwise, the first + /// element's type is used. + type_hint: Ref.Optional, + /// The items in the array + list: RefList, + }; + /// This will contain trailing data depending on the .kind pub const Decl = struct { name: NullTerminatedString, @@ -562,9 +579,10 @@ const std = @import("std"); const mem = std.mem; const assert = std.debug.assert; -const Cache = @import("Cache.zig"); +const core = @import("mod.zig"); +const Cache = core.Cache; -const Ast = @import("Ast.zig"); +const Ast = core.Ast; const Unused = Ast.Unused; const NodeIndex = Ast.NodeIndex; const TokenIndex = Ast.TokenIndex; @@ -575,14 +593,14 @@ const optionalTokenIndex = OptionalTokenIndex.init; const optionalNodeIndex = OptionalNodeIndex.init; const optionalExtraIndex = OptionalExtraIndex.init; -const dod = @import("array.zig"); +const dod = core.array; const Float = dod.Float; -const InternPool = @import("InternPool.zig"); +const InternPool = core.InternPool; -const Air = @import("Air.zig"); +const Air = core.Air; -const Comp = @import("Comp.zig"); +const Comp = core.Comp; -const Report = @import("Report.zig"); +const Report = core.Report; const LazyNode = Report.LazyNode; diff --git a/src/core/Transform.zig b/src/core/Transform.zig index 2cf3d708e317aba9ee69c2d1b3811421b3e0cb00..a1dcf9f122e3610bdef97ba6603103f2a586eea0 100644 --- a/src/core/Transform.zig +++ b/src/core/Transform.zig @@ -235,7 +235,7 @@ const BuiltInIds = std.StaticStringMap(Inst.Ref).initComptime(.{ .{ "bool", .bool_type }, .{ "noreturn", .noreturn_type }, .{ "type", .type_type }, - .{ "std", .standard_library }, + // .{ "std", .standard_library }, }); fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.Index { @@ -559,14 +559,14 @@ fn transformExpressionToBlock( // TODO: evaluate if `if` blocks would be fine in sema with instruction refs // with this extra if check here, we filter out non-blocks. - if (result.toValue() != null) { + if (result.value() != null) { assert(block == null); return result; } } if (expr) |result| { - if (result.toIndex()) |index| { + if (result.index()) |index| { if (t.insts.items(.tag)[@intFromEnum(index)] == .block) { return result; } @@ -685,7 +685,7 @@ pub const Builtin = enum { } const base = try t.transformExpression(decl, args[0]) orelse return null; - const args_list = try t.transformCallArgs(decl, args[1..]) orelse return null; + const args_list = try t.transformExpressionList(decl, args[1..]) orelse return null; return try t.addInstRef(.{ .tag = .js_new, @@ -923,6 +923,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc inline .negate, .bool_not, .bit_not, + .array_type, => |tag| { const ast_un = t.ast.nodeData(node).un; const teg_tag = @field(Teg.Inst.Tag, @tagName(tag)); @@ -1048,7 +1049,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc const args = t.ast.extraData(call.extra, Ast.Call).args.slice(&t.ast); const base = try t.transformExpression(decl, call.base) orelse return null; - const args_list = try t.transformCallArgs(decl, args) orelse return null; + const args_list = try t.transformExpressionList(decl, args) orelse return null; const call_inst = try t.addInstRef(.{ .tag = .call, @@ -1227,11 +1228,9 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc ) |field_node, i, j| { const field = t.ast.fullStructField(field_node); - if (mem.startsWith(u8, field.id, "@")) { - @panic("TODO: string identifiers"); - } + const id = try t.decodeIdentifier(field.id); - t.extra.setU32(i, @intFromEnum(try t.addNullTerminatedString(field.id))); + t.extra.setU32(i, @intFromEnum(try t.addNullTerminatedString(id))); t.extra.setU32(j, @intFromEnum(try t.transformExpressionToBlock(decl, field.type, false))); } @@ -1264,11 +1263,9 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc ) |field_node, i, j| { const field = t.ast.fullStructField(field_node); - if (mem.startsWith(u8, field.id, "@")) { - @panic("TODO: string identifiers"); - } + const id = try t.decodeIdentifier(field.id); - t.extra.setU32(i, @intFromEnum(try t.addNullTerminatedString(field.id))); + t.extra.setU32(i, @intFromEnum(try t.addNullTerminatedString(id))); t.extra.setU32(j, @intFromEnum((try t.transformExpression(decl, field.type)) orelse @panic("TODO: print better error"))); } @@ -1286,11 +1283,29 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc }); }, + .array_init => { + const array_init = t.ast.nodeData(node).list; + + const items = try t.transformExpressionList(decl, array_init.slice(&t.ast)) orelse + return null; + + return try t.addInstRef(.{ + .tag = .array_init, + .data = EncodedData.encode(.array_init, .{ + .src_node = decl.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Inst.ArrayInit, .{ + .type_hint = .none, + .list = items, + }), + }), + }); + }, + else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}), } } -fn transformCallArgs(t: *Transform, decl: BlockContext, args: []const NodeIndex) !?RefList { +fn transformExpressionList(t: *Transform, decl: BlockContext, args: []const NodeIndex) !?RefList { const extra_top = try t.extra.alloc(t.gpa, args.len); for (args, extra_top..) |arg, i| { @@ -1492,7 +1507,7 @@ fn transformBlockStatement( .node = node, .data = switch (var_decl.is_const) { true => .{ .immutable = mem_ref }, - false => .{ .mutable = mem_ref.toIndex().? }, + false => .{ .mutable = mem_ref.index().? }, }, }; @@ -1696,16 +1711,16 @@ fn addDecodedSlicedString(t: *Transform, escaped: []const u8) !SlicedString { const decoded = if (Tokenizer.stringHasEscapes(escaped)) brk: { const len = Tokenizer.decodeStringCount(escaped); - // use the scratch arraylist as a temporary storage for - // decoding the string. this is done so that duplicate - // strings will get de-duplicated in string_bytes - // - // TODO: see if this is a useless optimization + // Use the scratch arraylist as a temporary storage for decoding the + // string. this is done so that duplicate strings will get de-duplicated + // in string_bytes. It is more likely that there will be remaining bytes + // in `.scratch` vs `.id_decode_buf` try t.scratch.ensureUnusedCapacity(t.gpa, @divFloor(len, @sizeOf(u32)) + 1); const buf = mem.sliceAsBytes(t.scratch.unusedCapacitySlice()); break :brk Tokenizer.decodeStringBuf(escaped, buf); } else escaped; + return t.addSlicedString(decoded); } diff --git a/src/core/array.zig b/src/core/array.zig index 1a8286399d1643c2443a627deb066b4e2d9b50aa..a9bad97d30df002bf493ea02aeac1e6570ff50f5 100644 --- a/src/core/array.zig +++ b/src/core/array.zig @@ -372,6 +372,15 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil store.list.items.len += len; return @intCast(idx); } + + pub fn appendSlice(store: *Store, gpa: Allocator, data: anytype) !u32 { + const idx = store.list.items.len; + comptime assert(@sizeOf(@TypeOf(data[0])) == @sizeOf(u32)); + comptime assert(@alignOf(@TypeOf(data[0])) == @alignOf(u32)); + try store.list.appendSlice(gpa, @ptrCast(data)); + store.list.items.len += data.len; + return @intCast(idx); + } }; } diff --git a/src/core/js/Link.zig b/src/core/js/Link.zig index 05c02bcf04e742c33168e5e87396688a539534c6..05a565491a6436eeab9e4c67060e66f8a0e2d81a 100644 --- a/src/core/js/Link.zig +++ b/src/core/js/Link.zig @@ -289,6 +289,19 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp try writer.writeAll(")"); }, + .array_value => |array_value| { + try writer.writeAll("["); + + for (array_value.items, 0..) |inner_value, i| { + if (i != 0) + try writer.writeAll(","); + + // TODO: handle special identifiers + try link.printValue(inner_value, .assignment, writer); + } + try writer.writeAll("]"); + }, + else => unreachable, // this value is not a plain_value } } @@ -410,6 +423,18 @@ fn printExprLevel( try writer.writeAll(")"); }, + .index => { + const bin: Tree.Bin = tree.nodeData(.index, node); + if (Level.access_and_call.needsParens(level)) + try writer.writeAll("("); + try link.printExprLevel(tree, bin.lhs, level, writer); + try writer.writeAll("["); + try link.printExprLevel(tree, bin.rhs, level, writer); + try writer.writeAll("]"); + if (Level.access_and_call.needsParens(level)) + try writer.writeAll(")"); + }, + inline .negate, .bool_not, .pre_dec, @@ -505,7 +530,7 @@ fn printExprLevel( try writer.writeAll(":"); - try link.printExpr(tree, value, writer); + try link.printExprLevel(tree, value, .assignment, writer); } try writer.writeAll("}"); @@ -513,6 +538,16 @@ fn printExprLevel( if (level == .statement) try writer.writeAll(")"); }, + + .array => { + const items: Tree.IndexList = tree.nodeData(.array, node); + try writer.writeAll("["); + for (items.slice(tree), 0..) |item, i| { + if (i != 0) try writer.writeAll(", "); + try link.printExprLevel(tree, item, .assignment, writer); + } + try writer.writeAll("]"); + }, } } diff --git a/src/core/js/NodeGen.zig b/src/core/js/NodeGen.zig index f1c2f1d6bad461d7bca3be6a0cdc622ed75fd303..5a49dc016c7f0d23d5ab2194e599b8c011855c25 100644 --- a/src/core/js/NodeGen.zig +++ b/src/core/js/NodeGen.zig @@ -422,7 +422,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { try c.markExpression(inst, node); }, - // else => |tag| std.debug.panic("TODO JS codeGenInst for .{s}", .{@tagName(tag)}), + else => |tag| std.debug.panic("TODO JS codeGenInst for .{s}", .{@tagName(tag)}), } } @@ -495,6 +495,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { .function_type, .simple_type, .struct_type, + .array_type, => unreachable, // Type values are not allowed here .int32, @@ -503,6 +504,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { .string, .simple_value, .struct_value, + .array_value, => return try c.add(.plain_value, .{ .value = v }), // Hoist functions as top level values @@ -666,6 +668,8 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { .bool_not, .store, .struct_init, + .array_init, + .index, => true, // JavaScript variable declarations require statements. diff --git a/src/core/js/Tree.zig b/src/core/js/Tree.zig index cd43c1d8dcb8bc5398664fe286ccc02f3b725cd2..1ca7092263a8e84d42004f21940fbcb60265feae 100644 --- a/src/core/js/Tree.zig +++ b/src/core/js/Tree.zig @@ -45,6 +45,8 @@ pub const Tag = enum { field, ternary, object_literal, + array, + index, // Prefix ops negate, bool_not, @@ -81,6 +83,7 @@ pub const Tag = enum { .eq, .lt, .gt, + .index, => Bin, .ternary => Ternary, .negate, @@ -89,6 +92,7 @@ pub const Tag = enum { .pre_dec, => Un, .object_literal => ObjectLiteral, + .array => IndexList, .call, .new => Call, .field => Field, .stmt_if => StmtIf, diff --git a/src/core/main.zig b/src/core/main.zig index 0597ba074b93bad26537b41259942dfb6ad94aeb..dbe1cb55392467ffb1024f22a35fcc03aed2a3f7 100644 --- a/src/core/main.zig +++ b/src/core/main.zig @@ -28,14 +28,14 @@ pub fn main() !void { } const use_gpa = builtin.mode == .Debug; - var gpa: if (use_gpa) std.heap.GeneralPurposeAllocator(.{ - .stack_trace_frames = 100, - }) else struct {} = .{}; - defer _ = if (use_gpa) gpa.deinit(); - const alloc = if (use_gpa) - gpa.allocator() + var gpa = if (use_gpa) + std.heap.GeneralPurposeAllocator(.{ + .stack_trace_frames = 100, + }){} else - std.heap.c_allocator; + std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer _ = if (use_gpa) gpa.deinit(); + const alloc = gpa.allocator(); var maybe_file_path: ?[]const u8 = null; defer if (maybe_file_path) |path| alloc.free(path); diff --git a/src/core/mod.zig b/src/core/mod.zig index 0b57cd8206e6d313dad4757c9324a559e67cbe62..5dc23f406d46cd95b8b865846bd53e1bec40c756 100644 --- a/src/core/mod.zig +++ b/src/core/mod.zig @@ -29,6 +29,9 @@ pub const Report = @import("Report.zig"); /// Semantic Analysis visits a `Teg` declaration and produces `Air` pub const Sema = @import("Sema.zig"); +pub const Stack = @import("stack.zig").Stack; +pub const U32Stack = Stack(u32); + /// Untyped Intermediate Representation. Flat instruction list pub const Teg = @import("Teg.zig"); @@ -40,8 +43,6 @@ pub const Transform = @import("Transform.zig"); pub const unicode_props = @import("unicode_props.zig"); -pub const util = @import("util.zig"); - // sort-lines: end // The rest of this file is utility functions, which are not specific to any of @@ -64,8 +65,35 @@ pub inline fn assertFailed(cond: bool) bool { } } -pub const detectTTYFromWriter = util.detectTTYFromWriter; -pub const Ordinal = util.Ordinal; +/// If `value` can be cast to `i32` +pub fn inInt32Range(value: anytype) bool { + return switch (@typeInfo(@TypeOf(value))) { + .Int, + .ComptimeInt, + => value >= std.math.minInt(i32) and + value <= std.math.maxInt(i32), + + .Float, + .ComptimeFloat, + => std.math.isFinite(value) and + value >= std.math.minInt(i32) and + value <= std.math.maxInt(i32) and + (@rem(value, 1) == 0), + + else => @compileError(unreachable), + }; +} + +pub fn detectTTYFromWriter(writer: std.io.AnyWriter) std.io.tty.Config { + const fileWriteFn = comptime (std.fs.File{ .handle = undefined }) + .writer().any().writeFn; + + if (writer.writeFn == fileWriteFn) { + const file: *const std.fs.File = @alignCast(@ptrCast(writer.context)); + return std.io.tty.detectConfig(file.*); + } + return .no_color; +} const std = @import("std"); const assert = std.debug.assert; diff --git a/src/core/stack.zig b/src/core/stack.zig new file mode 100644 index 0000000000000000000000000000000000000000..302c643502b4a15dd05cd4bfb27f77ba10a4ae42 --- /dev/null +++ b/src/core/stack.zig @@ -0,0 +1,156 @@ +//! This is an abstraction on top of ArrayListUnmanaged primarily intended for +//! unbounded temporary memory. Allocations must be freed last-in-first-out, but +//! the ability for memory reuse is high. +//! +//! This structure allows for a mark -> append -> gather pattern, which is +//! typically used with a block expression and it's contents, but it also allows +//! arbitrary temporary allocations that re-use this memory space. +pub fn Stack(comptime Base: type) type { + return struct { + const Self = @This(); + + list: std.ArrayListUnmanaged(Base) = .{}, + + pub const Mark = struct { + stack: *Self, + index: usize, + + pub fn deinit(m: Mark) void { + if (std.debug.runtime_safety) + @memset(m.slice(Base), undefined); + m.stack.list.shrinkRetainingCapacity(m.index); + } + + pub fn len(m: Mark) usize { + return m.stack.list.items.len - m.index; + } + + pub fn slice(m: Mark, comptime T: type) []T { + comptime assert(@alignOf(T) == @alignOf(Base)); + comptime assert(@sizeOf(T) == @sizeOf(Base)); + return @ptrCast(m.stack.list.items[m.index..]); + } + }; + + /// Mark this point of the stack, where all future appends are owned by + /// the mark. You can then use `slice()` to collect the appends. + pub fn mark(stack: *Self) Mark { + return .{ + .stack = stack, + .index = stack.list.items.len, + }; + } + + pub fn add(stack: *Self, allocator: Allocator, comptime T: type, item: T) !void { + comptime assert(@alignOf(T) == @alignOf(Base)); + comptime assert(@sizeOf(T) == @sizeOf(Base)); + try stack.list.append(allocator, @as(*const Base, @ptrCast(&item)).*); + } + + pub fn Reserve(comptime T: type) type { + assert(@alignOf(T) == @alignOf(Base)); + assert(@sizeOf(T) == @sizeOf(Base)); + + return struct { + stack: *Self, + start: usize, + len: usize, + + pub fn deinit(r: @This()) void { + const from = r.stack.list.items[r.start + r.len ..]; + const dest = r.stack.list.items[r.start..][0..from.len]; + std.mem.copyForwards(u32, dest, from); + r.stack.list.shrinkRetainingCapacity(r.start + from.len); + } + + pub fn at(r: @This(), i: usize) *T { + assert(i < r.len); + return @ptrCast(&r.stack.list.items[r.start + i]); + } + + pub fn slice(r: @This()) []T { + return @ptrCast(r.stack.list.items[r.start..][0..r.len]); + } + + pub fn sliceReinterpret(r: @This(), comptime Reinterpret: type) []Reinterpret { + assert(@alignOf(Reinterpret) == @alignOf(Base)); + assert(@sizeOf(Reinterpret) == @sizeOf(Base)); + return @ptrCast(r.slice()); + } + }; + } + + pub fn reserve( + stack: *Self, + allocator: Allocator, + comptime T: type, + len: usize, + ) !Reserve(T) { + try stack.list.ensureUnusedCapacity(allocator, len); + const start = stack.list.items.len; + stack.list.items.len += len; + return .{ .stack = stack, .start = start, .len = len }; + } + + pub fn ReserveMulti(comptime types: anytype) type { + const Types = @TypeOf(types); + const fields = std.meta.fields(Types); + const FieldEnum = std.meta.FieldEnum(Types); + + return struct { + stack: *Self, + starts: [fields.len]usize, + lengths: [fields.len]usize, + + fn Type(comptime field: FieldEnum) type { + return @field(types, @tagName(field)); + } + + pub fn slice(r: @This(), comptime field: FieldEnum) []Type(field) { + const f = comptime std.meta.fieldIndex(Types, @tagName(field)).?; + return @ptrCast(r.stack.list.items[r.starts[f]..][0..r.lengths[f]]); + } + + pub fn at(r: @This(), comptime field: FieldEnum, i: usize) *Type(field) { + const f = comptime std.meta.fieldIndex(Types, @tagName(field)).?; + assert(i < r.lengths[f]); + return @ptrCast(&r.stack.list.items[r.starts[f] + i]); + } + + pub fn deinit(r: @This()) void { + const start = r.starts[0]; + const len = r.starts[fields.len - 1] - r.starts[0] + r.lengths[fields.len - 1]; + + const from = r.stack.list.items[start + len ..]; + const dest = r.stack.list.items[start..][0..from.len]; + std.mem.copyForwards(u32, dest, from); + r.stack.list.shrinkRetainingCapacity(start + from.len); + } + }; + } + + /// Same as `reserve` but able to group multiple allocations of different types + /// into a singular push and pop. + pub fn reserveMulti( + stack: *Self, + allocator: Allocator, + comptime types: anytype, + lengths: [std.meta.fields(@TypeOf(types)).len]usize, + ) !ReserveMulti(types) { + var all_lengths: usize = 0; + var starts: [std.meta.fields(@TypeOf(types)).len]usize = undefined; + const start = stack.list.items.len; + inline for (lengths, &starts) |len, *slice_start| { + slice_start.* = start + all_lengths; + all_lengths += len; + } + try stack.list.ensureUnusedCapacity(allocator, all_lengths); + stack.list.items.len += all_lengths; + return .{ .stack = stack, .starts = starts, .lengths = lengths }; + } + }; +} + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; diff --git a/src/core/util.zig b/src/core/util.zig deleted file mode 100644 index 2062d3eceb0f4931f825fb56ffdb0b95392db1ac..0000000000000000000000000000000000000000 --- a/src/core/util.zig +++ /dev/null @@ -1,49 +0,0 @@ -// TODO: move all decls in this file into mod.zig - -pub fn detectTTYFromWriter(writer: anytype) std.io.tty.Config { - if (@hasField(@TypeOf(writer), "unbuffered_writer")) { - return detectTTYFromWriter(writer.unbuffered_writer); - } - if (@TypeOf(writer) == std.fs.File.Writer) { - return std.io.tty.detectConfig(writer.context); - } - if (@TypeOf(writer) == std.io.AnyWriter) { - const fileWriteFn = comptime (std.fs.File{ .handle = undefined }) - .writer().any().writeFn; - if (writer.writeFn == fileWriteFn) { - const file: *const std.fs.File = @alignCast(@ptrCast(writer.context)); - return std.io.tty.detectConfig(file.*); - } - } - return .no_color; -} - -pub fn Ordinal(Int: type) type { - comptime assert(@typeInfo(Int).Int.signedness == .unsigned); - return enum(Int) { - start = 0, - _, - - pub fn fromZeroBased(int: Int) @This() { - assert(int != std.math.maxInt(Int)); - return @enumFromInt(int); - } - pub fn fromOneBased(int: Int) @This() { - assert(int > 0); - return @enumFromInt(int - 1); - } - pub fn zeroBased(ord: @This()) Int { - return @intFromEnum(ord); - } - pub fn oneBased(ord: @This()) Int { - return @intFromEnum(ord) + 1; - } - - pub fn add(ord: @This(), inc: Int) @This() { - return fromZeroBased(ord.zeroBased() + inc); - } - }; -} - -const std = @import("std"); -const assert = std.debug.assert; -- 2.54.0