diff --git a/CHANGELOG.md b/CHANGELOG.md index 152375646f0bf0faf1ca457a13467471a7f8bd49..d22d8243f42922400d7aea953d7e99af67086007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.1.3 + +- feat(sema): coersion: numeric_literal -> float +- feat(transform): lhs of assignments now validated +- feat(transform): mark useless discard +- fix(sema): properly coerce assignments + ## 0.1.2 - feat: semantic analysis can return formatted errors to the user diff --git a/src/compiler/Air.zig b/src/compiler/Air.zig index 0811efcf84df67ea59921320092586c9ca6eafcb..4252ffa290e9295ce7318681d943d70aad6298ed 100644 --- a/src/compiler/Air.zig +++ b/src/compiler/Air.zig @@ -315,6 +315,6 @@ const Value = InternPool.Value; const Type = InternPool.Type; const Teg = @import("Teg.zig"); -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const Unused = enum(u32) { zero = 0 }; diff --git a/src/compiler/Ast.zig b/src/compiler/Ast.zig index f1eb29fcf62758e5e113d3a4e1573cf1b7d18c57..e11c6d8fa3ed6be8c5d0d1be924fcfe2707e062f 100644 --- a/src/compiler/Ast.zig +++ b/src/compiler/Ast.zig @@ -381,11 +381,12 @@ pub const Node = struct { pub const Error = error{ OutOfMemory, ParseFail }; pub const Location = struct { - // TODO: add an ordinal number type, replace with Ordinal(usize) - line: usize, - column: usize, - line_start: usize, - line_end: usize, + line: Ordinal(u32), + column: Ordinal(u32), + // Byte offset of the start of the line + line_start: ByteOffset, + // Byte offset of the end of the line + line_end: ByteOffset, }; pub const Span = struct { @@ -672,3 +673,6 @@ const TokenList = Tokenizer.Token.List; const ByteOffset = Tokenizer.ByteOffset; const Report = @import("Report.zig"); + +const util = @import("util.zig"); +const Ordinal = util.Ordinal; diff --git a/src/compiler/InternPool.zig b/src/compiler/InternPool.zig index cac26a5554ec06c64b09c90e9d1c1c4e0c55c321..7863bf6debc6688cc2765fa2bd99bfef73f3294d 100644 --- a/src/compiler/InternPool.zig +++ b/src/compiler/InternPool.zig @@ -171,6 +171,7 @@ pub const Value = enum(u32) { float_zero, nan, empty_string, + standard_library, none = std.math.maxInt(u32), _, @@ -1170,7 +1171,7 @@ const Cache = @import("Cache.zig"); const Teg = @import("Teg.zig"); const Air = @import("Air.zig"); -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const Float = dod.Float; const tracer = @import("tracer"); diff --git a/src/compiler/JsNodeGen.zig b/src/compiler/JsNodeGen.zig index 8b61b9ea17ba66eea2e1b1c74b9dadcf564adc2d..f3b801e05e8e7b37b38e3903f81bcc39a5e1381e 100644 --- a/src/compiler/JsNodeGen.zig +++ b/src/compiler/JsNodeGen.zig @@ -696,5 +696,5 @@ const LocalIndex = JsTree.LocalIndex; const Air = @import("Air.zig"); -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const tracer = @import("tracer"); diff --git a/src/compiler/JsTree.zig b/src/compiler/JsTree.zig index 81186431c72fbcea50d645d3526f830e2586d07c..291b621802fe1957910128273fd77b223064ee14 100644 --- a/src/compiler/JsTree.zig +++ b/src/compiler/JsTree.zig @@ -252,6 +252,6 @@ const String = InternPool.String; const Comp = @import("Comp.zig"); const Decl = Comp.Decl; -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const Unused = @import("Ast.zig").Unused; diff --git a/src/compiler/Report.zig b/src/compiler/Report.zig index f0191aa5086807fae8458779e512965d82acfb1a..814a0f3510f5d342912441a6b39b4a66df3cd735 100644 --- a/src/compiler/Report.zig +++ b/src/compiler/Report.zig @@ -169,6 +169,48 @@ pub const Err = union(enum) { }); } }, + assign_builtin: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgNode( + data.node, + "cannot re-assign built-in \"{s}\"", + .{ctx.ast.basicNodeSlice(data.node)}, + ); + } + }, + invalid_assignment_symbol: struct { + node: NodeIndex, + decl: NodeIndex, + kind: UnusedKind, + + fn build(data: @This(), ctx: *MsgContext) !void { + const id = switch (ctx.ast.nodeTag(data.decl)) { + .param_decl => ctx.ast.fullParamDecl(data.decl).id, + .fn_decl => ctx.ast.fullFnDecl(data.decl).id, + .var_decl => ctx.ast.fullVarDecl(data.decl).id, + else => unreachable, // not a valid symbol node + }; + + try ctx.msgToken(ctx.ast.idToken(data.node), "cannot assign to {s} \"{s}\"", .{ + @tagName(data.kind), + id, + }); + try ctx.msgToken(ctx.ast.idToken(data.decl), "declared here", .{}); + } + }, + invalid_assignment_target: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgNode( + data.node, + "invalid assignment target", + .{}, + ); + } + }, }; pub const Warn = union(enum) { @@ -177,7 +219,6 @@ pub const Warn = union(enum) { unused: struct { node: NodeIndex, kind: UnusedKind, - // shadowed: ?NodeIndex = null, fn build(data: @This(), ctx: *MsgContext) !void { const id = switch (ctx.ast.nodeTag(data.node)) { @@ -192,15 +233,23 @@ pub const Warn = union(enum) { id, }); - // if (data.shadowed) |node| { - // try ctx.msgToken(ctx.ast.idToken(node), "{s} was re-declared here", .{ - // @tagName(data.kind), - // }); - // } - try ctx.msgAlone("if this was intentional, discard it with \"_ = {s};\"", .{id}); } }, + not_mutated_var: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + const id = switch (ctx.ast.nodeTag(data.node)) { + .var_decl => ctx.ast.fullVarDecl(data.node).id, + else => unreachable, // not a valid variable symbol node + }; + + try ctx.msgToken(ctx.ast.idToken(data.node), "variable \"{s}\" not mutated", .{ + id, + }); + } + }, invalid_discard: struct { node: NodeIndex, @@ -208,6 +257,20 @@ pub const Warn = union(enum) { try ctx.msgToken(ctx.ast.nodeToken(data.node), "\"let\" is not required for a discard", .{}); } }, + discard_builtin: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgToken(ctx.ast.nodeToken(data.node), "discarding a built-in symbol is meaningless", .{}); + } + }, + unnecessary_discard: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgToken(ctx.ast.nodeToken(data.node), "useless discard", .{}); + } + }, dangling_identifier: struct { node: NodeIndex, @@ -219,9 +282,8 @@ pub const Warn = union(enum) { node: LazyNode, fn build(data: @This(), ctx: *MsgContext) !void { - const var_decl_node = try data.node.get(ctx); - const init_node = ctx.ast.nodeData(var_decl_node).var_decl.init; - try ctx.msgNode(init_node, "constant integer in a \"var\" has an unknown type, assuming \"float\"", .{}); + const node = try data.node.get(ctx); + try ctx.msgNode(node, "constant integer in a \"var\" has an unknown type, assuming \"float\"", .{}); try ctx.msgAlone("add \": float\" or \": int\" after the variable name to set the type", .{}); } }, @@ -346,7 +408,6 @@ pub fn formatItem( const need_load_ast = !(item_type == .@"error" and item.data.@"error" == .ast); const ast_data = if (need_load_ast) try file.getAst(comp) else undefined; - const ast = if (need_load_ast) &ast_data else null; var ctx: MsgContext = .{ .arena = alloc, @@ -392,92 +453,32 @@ pub fn formatItem( try writer.writeAll(component.message_utf8); try writer.writeAll("\n"); - const SliceLoc = struct { - loc: Location, - byte: ByteOffset, - slice: []const u8, - }; - const utf8_source = ctx.utf8_source; - const at: SliceLoc = switch (component.highlight) { - .alone => { - if (is_last) { - try tty.setColor(writer, .dim); - try writer.print(" {s}", .{"┴"}); - try writer.writeAll("\n"); - try tty.setColor(writer, .reset); - } - continue; - }, - inline .node, - .token, - .two_tokens, - => |payload, tag| loc: { - const token_index: TokenIndex = switch (tag) { - .node => ast.?.nodeToken(payload), - .token, .two_tokens => payload, - else => comptime unreachable, - }; - - const location = ast.?.tokenLocation(0, token_index); - const token = ast.?.tokens.get(token_index); - - break :loc .{ - .loc = location, - .slice = token.slice(ast.?.utf8_source), - .byte = token.offset, - }; - }, - - .token_stored => |token| loc: { - const location = Tokenizer.tokenLocation(ctx.utf8_source, 0, token.offset); - - break :loc .{ - .loc = location, - .slice = token.slice(utf8_source), - .byte = token.offset, - }; - }, - - inline .codepoint, - .codepoint_eol, - => |b, tag| loc: { - const len = switch (tag) { - .codepoint => std.unicode.utf8ByteSequenceLength(utf8_source[b]) catch 1, - .codepoint_eol => if (std.mem.indexOfScalar(u8, utf8_source[b..], '\n')) |n| - n - else - (utf8_source.len - b), - else => comptime unreachable, - }; - break :loc .{ - .byte = b, - .slice = utf8_source[b..][0..len], - .loc = .{ - .column = 0, - .line = 0, - .line_start = 0, - .line_end = utf8_source.len, - }, - }; - }, + const at = component.location orelse { + if (is_last) { + try tty.setColor(writer, .dim); + try writer.print(" {s}", .{"┴"}); + try writer.writeAll("\n"); + try tty.setColor(writer, .reset); + } + continue; }; const line_before = utf8_source[at.loc.line_start..at.byte]; const trimmed_before = std.mem.trimLeft(u8, line_before, " \t\r"); const line_after = utf8_source[at.byte + at.slice.len .. at.loc.line_end + 1]; - const width = std.fmt.count("{d}", .{at.loc.line}); + const width = std.fmt.count("{d}", .{at.loc.line.oneBased()}); try tty.setColor(writer, .dim); try writer.writeByteNTimes(' ', width); try writer.print("{s}{s}:{d}:{d}\n", .{ if (i == 1) "┬ " else "├ ", file.file_path, - at.loc.line, - at.loc.column, + at.loc.line.oneBased(), + at.loc.column.oneBased(), }); - try writer.print("{d}", .{at.loc.line}); + try writer.print("{d}", .{at.loc.line.oneBased()}); try writer.print("{s} ", .{"│"}); try tty.setColor(writer, .reset); try writer.writeAll(trimmed_before); @@ -593,6 +594,17 @@ pub const Builder = struct { } }; +pub const SliceLoc = struct { + loc: Location, + byte: ByteOffset, + slice: []const u8, +}; + +const Component = struct { + message_utf8: []const u8, + location: ?SliceLoc, +}; + pub const Highlight = union(enum) { // No source view alone, @@ -608,11 +620,64 @@ pub const Highlight = union(enum) { codepoint: ByteOffset, // Highlight from the start of a codepoint to '\n' codepoint_eol: ByteOffset, -}; -const Component = struct { - message_utf8: []const u8, - highlight: Highlight, + pub fn loc(highlight: Highlight, ast: *const Ast) ?SliceLoc { + return switch (highlight) { + .alone => null, + inline .node, + .token, + .two_tokens, + => |payload, tag| loc: { + const token_index: TokenIndex = switch (tag) { + .node => ast.nodeToken(payload), + .token, .two_tokens => payload, + else => comptime unreachable, + }; + + const location = ast.tokenLocation(0, token_index); + const token = ast.tokens.get(token_index); + + break :loc .{ + .loc = location, + .slice = token.slice(ast.utf8_source), + .byte = token.offset, + }; + }, + + .token_stored => |token| loc: { + const location = Tokenizer.tokenLocation(ast.utf8_source, 0, token.offset); + + break :loc .{ + .loc = location, + .slice = token.slice(ast.utf8_source), + .byte = token.offset, + }; + }, + inline .codepoint, + .codepoint_eol, + => |b, tag| loc: { + const len = switch (tag) { + .codepoint => std.unicode.utf8ByteSequenceLength(ast.utf8_source[b]) catch 1, + .codepoint_eol => if (std.mem.indexOfScalar(u8, ast.utf8_source[b..], '\n')) |n| + n + else + (ast.utf8_source.len - b), + else => comptime unreachable, + }; + break :loc .{ + .byte = b, + .slice = ast.utf8_source[b..][0..len], + .loc = .{ + // TODO: + .column = .start, + .line = .start, + .line_start = 0, + .line_end = @intCast(ast.utf8_source.len), + }, + }; + }, + }; + } }; /// This serves as a small API to make creating messages easier. @@ -636,7 +701,7 @@ pub const MsgContext = struct { const text = try std.fmt.allocPrint(m.arena, fmt, args); try m.components.append(m.arena, .{ .message_utf8 = text, - .highlight = highlight, + .location = highlight.loc(m.ast), }); } @@ -719,6 +784,21 @@ pub const LazyNode = struct { }, else => return base_node, }, + .memory => switch (node_tag) { + .var_decl => switch (lazy.operand) { + .operand => return data.var_decl.init, + else => {}, + }, + else => {}, + }, + .store => { + assert(node_tag.isAssignBin()); + switch (lazy.operand) { + .lhs => return data.bin.lhs, + .rhs => return data.bin.rhs, + else => unreachable, + } + }, else => {}, } diff --git a/src/compiler/Sema.zig b/src/compiler/Sema.zig index f23e3daf92670c25e204e3d58f630508d43ea234..cae7f9f6fd9a8663acf6d0f2c55f06954ec7e15f 100644 --- a/src/compiler/Sema.zig +++ b/src/compiler/Sema.zig @@ -500,16 +500,16 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { assert(s.code.instTag(data.memory) == .memory); // corrupt store instruction const location = s.mapTeg(data.memory.toRef()).decode().index; + s.markInst(location); const operand = try s.resolveRefRuntime(data.operand); - - // try s.validateType( - // s.typeOfRef(operand).toValue(), - // s.tempAir().instData(.memory, location).ty.ip_index, - // ); - + const operand_coerced = try s.coerceRef( + in.op(.rhs), + operand, + s.typeOfRef(location.toRef()), + ); return try s.addInstRef(.store, .{ .memory = location, - .operand = operand, + .operand = operand_coerced, }); }, .as => { @@ -690,8 +690,13 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value { }, else => return s.coersionError(src, from, to), }, - .float => { - // + .float => switch (from) { + .float => unreachable, // same type handled + .numeric_literal => if (value.toFloat(s.ip)) |val| + return try s.makeValue(.{ + .float = val, + }), + else => {}, }, .string => { // @@ -985,7 +990,7 @@ const InstOp = Teg.InstOp; const Air = @import("Air.zig"); const Report = @import("Report.zig"); const Ast = @import("Ast.zig"); -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const tracer = @import("tracer"); const InternPool = @import("InternPool.zig"); diff --git a/src/compiler/Teg.zig b/src/compiler/Teg.zig index 31f2b06ecf5a24eb902fb14b57a4c0024cb96a6e..5b76ced3658ab57f233eca18e6396439a6a21573 100644 --- a/src/compiler/Teg.zig +++ b/src/compiler/Teg.zig @@ -224,6 +224,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. @@ -472,7 +473,7 @@ const optionalTokenIndex = OptionalTokenIndex.init; const optionalNodeIndex = OptionalNodeIndex.init; const optionalExtraIndex = OptionalExtraIndex.init; -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const Float = dod.Float; const InternPool = @import("InternPool.zig"); diff --git a/src/compiler/Tokenizer.zig b/src/compiler/Tokenizer.zig index 687307b791a79fcf5b1c4e89c692e174ec963bb2..9afadd8b6918d36cab142ccf359eb25584502c2a 100644 --- a/src/compiler/Tokenizer.zig +++ b/src/compiler/Tokenizer.zig @@ -949,10 +949,10 @@ pub fn tokenLocation( token_start: ByteOffset, ) Location { var loc: Location = .{ - .line = 1, - .column = 1, + .line = .start, + .column = .start, .line_start = start_offset, - .line_end = utf8_source.len, + .line_end = @intCast(utf8_source.len), }; // Scan to by line until we go past the token start @@ -960,25 +960,25 @@ pub fn tokenLocation( if (i >= token_start) { break; // Went past } - loc.line += 1; - loc.line_start = i + 1; + loc.line = loc.line.add(1); + loc.line_start = @intCast(i + 1); } const offset = loc.line_start; for (utf8_source[offset..], 0..) |c, i| { if (i + offset == token_start) { - loc.line_end = i + offset; + loc.line_end = @intCast(i + offset); while (loc.line_end < utf8_source.len and utf8_source[loc.line_end] != '\n') { loc.line_end += 1; } return loc; } if (c == '\n') { - loc.line += 1; - loc.column = 1; - loc.line_start = i + 1; + loc.line = loc.line.add(1); + loc.column = .start; + loc.line_start = @intCast(i + 1); } else { - loc.column += 1; + loc.column = loc.column.add(1); } } diff --git a/src/compiler/Transform.zig b/src/compiler/Transform.zig index 3e054977f01cd3ab95fb9e589fe0f61a579e1c8d..9652b3edc67059de5b4f81652cff4a753db1f0fb 100644 --- a/src/compiler/Transform.zig +++ b/src/compiler/Transform.zig @@ -79,34 +79,49 @@ const Scope = struct { const Symbol = struct { node: NodeIndex, data: Symbol.Data, + // TODO: optimize this structure used: bool = false, mutated: bool = false, + discarded: ?NodeIndex = null, const Data = union(enum) { declaration, param, - mutable: Inst.Ref, + mutable: Inst.Index, immutable: Inst.Ref, }; - }; - fn addUnusedWarnings(s: Scope, t: *Transform) !void { - for (s.symbols.values()) |value| { - if (!value.used) { + pub fn addWarnings(symbol: Symbol, t: *Transform) !void { + if (!symbol.mutated and symbol.data == .mutable) { + try t.report.warn(.not_mutated_var, .{ + .node = symbol.node, + }); + } + if (!symbol.used) { try t.report.warn(.unused, .{ - .node = value.node, - .kind = switch (value.data) { + .node = symbol.node, + .kind = switch (symbol.data) { .declaration => .function, .param => .param, .mutable => .variable, - .immutable => switch (t.ast.nodeTag(value.node)) { + .immutable => switch (t.ast.nodeTag(symbol.node)) { .param_decl => .param, else => .constant, }, }, }); + } else if (symbol.discarded) |discard| { + try t.report.warn(.unnecessary_discard, .{ + .node = discard, + }); } } + }; + + fn addWarnings(s: Scope, t: *Transform) !void { + for (s.symbols.values()) |value| { + try value.addWarnings(t); + } } fn deinit(s: *Scope, gpa: Allocator) void { @@ -212,7 +227,7 @@ const BuiltInIds = std.ComptimeStringMap(Inst.Ref, .{ .{ "bool", .bool_type }, .{ "noreturn", .noreturn_type }, .{ "type", .type_type }, - // todo: nan/infinity? + .{ "std", .standard_library }, }); fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.Index { @@ -303,7 +318,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I .extra_index = try t.extra.append(t.gpa, IndexList, declarations), }); - try t.scope().addUnusedWarnings(t); + try t.scope().addWarnings(t); return module; } @@ -400,7 +415,7 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A .extra_index = decl_extra, }); - try t.scope().addUnusedWarnings(t); + try t.scope().addWarnings(t); return func; } @@ -613,9 +628,8 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .string = try t.addNullTerminatedString(id), }), }), - .mutable, - .immutable, - => |ref| ref, + .mutable => |idx| idx.toRef(), + .immutable => |ref| ref, .param => unreachable, //unresolved param }; }, @@ -650,14 +664,16 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .assign => { const ast_bin = t.ast.nodeData(node).bin; - const lhs = try t.transformExpression(decl, ast_bin.lhs) orelse return null; - const rhs = try t.transformExpression(decl, ast_bin.rhs) orelse return null; + const lhs = try t.transformAssignmentTarget(decl, ast_bin.lhs) orelse + return .void; + const rhs = try t.transformExpression(decl, ast_bin.rhs) orelse + return null; const assign_inst = try t.addInstRef(.{ .tag = .store, .data = EncodedData.encode(.store, .{ .src_node = decl.nodeOffset(node), .extra_index = try t.extra.append(t.gpa, Inst.Store, .{ - .memory = lhs.toIndex() orelse @panic("TODO: convert this into Ref"), + .memory = lhs, .operand = rhs, }), }), @@ -709,7 +725,12 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc @tagName(tag), ); const ast_bin = t.ast.nodeData(node).bin; - const lhs = try t.transformExpression(decl, ast_bin.lhs) orelse return null; + const lhs = if (is_assign) + try t.transformAssignmentTarget(decl, ast_bin.lhs) orelse + return .void + else + try t.transformExpression(decl, ast_bin.lhs) orelse + return null; const rhs = try t.transformExpression(decl, ast_bin.rhs) orelse return null; const compute_inst = try t.addInstRef(.{ @@ -717,7 +738,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .data = EncodedData.encode(teg_tag, .{ .src_node = decl.nodeOffset(node), .extra_index = try t.extra.append(t.gpa, Inst.Bin, .{ - .lhs = lhs, + .lhs = if (is_assign) lhs.toRef() else lhs, .rhs = rhs, }), }), @@ -729,7 +750,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .data = EncodedData.encode(.store, .{ .src_node = decl.nodeOffset(node), .extra_index = try t.extra.append(t.gpa, Inst.Store, .{ - .memory = lhs.toIndex() orelse @panic("TODO: convert this into Ref"), + .memory = lhs, .operand = compute_inst, }), }), @@ -1042,6 +1063,75 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc } } +fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex) Allocator.Error!?Inst.Index { + const node_tag = t.ast.nodes.items(.tag)[node]; + + const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)}); + defer trace.end(); + + switch (node_tag) { + .root, + .param_decl, + .var_decl, + .fn_decl, + => unreachable, // not an expression + + .id => { + const id = t.ast.basicNodeSlice(node); + assert(!mem.eql(u8, id, "_")); // discards are not assignment targets + + if (BuiltInIds.has(id)) { + try t.report.err(.assign_builtin, .{ .node = node }); + return null; + } + + if (mem.startsWith(u8, id, "@")) { + @panic("TODO: string identifiers"); + } + + const lookup = t.lookupId(id) orelse { + try t.report.err(.unknown_identifier, .{ .node = node }); + return null; + }; + + return switch (lookup.data) { + .mutable => |inst| { + lookup.mutated = true; + return inst; + }, + else => { + lookup.used = true; + + try t.report.err(.invalid_assignment_symbol, .{ + .node = node, + .decl = lookup.node, + .kind = switch (lookup.data) { + .mutable => unreachable, + .declaration => .function, + .param => .param, + .immutable => switch (t.ast.nodeTag(lookup.node)) { + .param_decl => .param, + else => .constant, + }, + }, + }); + return null; + }, + }; + }, + + // TODO: allow field expressions, but these need to emit a different store node + + else => { + _ = try t.transformExpression(decl, node); + try t.report.err(.invalid_assignment_target, .{ + .node = node, + }); + return null; + }, + } +} + fn transformBlockStatement( t: *Transform, decl: BlockContext, @@ -1063,8 +1153,30 @@ fn transformBlockStatement( if (mem.eql(u8, var_decl.id, "_")) { try t.report.warn(.invalid_discard, .{ .node = node }); - const ref = try t.transformExpression(decl, var_decl.init) orelse - return null; + const ref = switch (t.ast.nodeTag(var_decl.init)) { + .id => { + const id = t.ast.basicNodeSlice(node); + if (BuiltInIds.has(id)) { + try t.report.warn(.discard_builtin, .{ .node = node }); + return .void; + } + + if (mem.startsWith(u8, id, "@")) { + @panic("TODO: string identifiers"); + } + + const lookup = t.lookupId(id) orelse { + try t.report.err(.unknown_identifier, .{ .node = node }); + return .void; + }; + + lookup.discarded = node; + + return .void; + }, + else => try t.transformExpression(decl, var_decl.init) orelse + return null, + }; return try t.addInstRef(.{ .tag = .discard, @@ -1115,29 +1227,14 @@ fn transformBlockStatement( // if re-declaring, apply unused warnings now if (gop.found_existing) { - const value = gop.value_ptr.*; - if (!value.used) { - try t.report.warn(.unused, .{ - .node = value.node, - .kind = switch (value.data) { - .declaration => .function, - .param => .param, - .mutable => .variable, - .immutable => switch (t.ast.nodeTag(value.node)) { - .param_decl => .param, - else => .constant, - }, - }, - // .shadowed = node, - }); - } + try gop.value_ptr.addWarnings(t); } gop.value_ptr.* = .{ .node = node, .data = switch (var_decl.is_const) { true => .{ .immutable = mem_ref }, - false => .{ .mutable = mem_ref }, + false => .{ .mutable = mem_ref.toIndex().? }, }, }; @@ -1352,7 +1449,7 @@ const IndexList = Teg.IndexList; const EncodedData = Teg.EncodedData; const ExtraIndex = Teg.ExtraIndex; -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const ExtraStore = dod.ExtraStore(.teg, .@"var"); const Float = dod.Float; diff --git a/src/compiler/array.zig b/src/compiler/array.zig new file mode 100644 index 0000000000000000000000000000000000000000..57ccac606608a1b511c3f0f54871592143edb8a1 --- /dev/null +++ b/src/compiler/array.zig @@ -0,0 +1,390 @@ +const extra = @This(); + +pub const Mutability = enum { @"const", @"var" }; + +fn setAsU32(items: []u32, comptime T: type, index: usize, payload: T) void { + const fields = std.meta.fields(@TypeOf(payload)); + var i = index; + inline for (fields) |field| { + const value = @field(payload, field.name); + switch (field.type) { + u32 => items[i] = value, + i32 => items[i] = @bitCast(value), + + [16]u8 => { + const u32s: [4]u32 = @bitCast(value); + items[i] = u32s[0]; + items[i + 1] = u32s[1]; + items[i + 2] = u32s[2]; + items[i + 3] = u32s[3]; + }, + + else => brk: { + const info = @typeInfo(field.type); + if (info == .Enum) { + items[i] = @intFromEnum(value); + break :brk; + } + + const struct_data = info.Struct; + if (struct_data.layout == .@"packed") { + items[i] = @bitCast(value); + break :brk; + } + + @call(.always_inline, setAsU32, .{ items, field.type, i, value }); + }, + } + i += extraSizeOf(field.type); + } +} + +fn getAsU32(items: []const u32, comptime T: type, index: usize) T { + const fields = std.meta.fields(T); + var i = index; + var result: T = undefined; + inline for (fields) |field| { + @field(result, field.name) = switch (field.type) { + u32 => items[i], + i32 => @bitCast(items[i]), + + [16]u8 => @bitCast(items[i..][0..4].*), + + else => brk: { + const info = @typeInfo(field.type); + if (info == .Enum) { + break :brk @enumFromInt(items[i]); + } + + const struct_data = info.Struct; + if (struct_data.layout == .@"packed") { + break :brk @bitCast(items[i]); + } + + break :brk @call( + .always_inline, + getAsU32, + .{ items, field.type, i }, + ); + }, + }; + i += extraSizeOf(field.type); + } + return result; +} + +pub const StoreKind = enum { + intern_pool, + teg, + air, + tfr, + + pub fn resolveStoreConst( + comptime kind: StoreKind, + owner: anytype, + ) *const kind.ResolvedStore(@TypeOf(owner)) { + return &owner.extra; + } + + pub fn resolveStoreMut( + comptime kind: StoreKind, + owner: anytype, + ) *kind.ResolvedStore(@TypeOf(owner)) { + return &owner.extra; + } + + pub fn ResolvedStore( + comptime kind: StoreKind, + comptime Owner: type, + ) type { + const ptr = @typeInfo(Owner).Pointer; + assert(ptr.size == .One); + const mutability = brk: for (@typeInfo(ptr.child).Struct.fields) |field| { + if (std.mem.eql(u8, field.name, "extra")) { + break :brk field.type.mutability; + } + } else unreachable; + return ExtraStore(kind, mutability); + } +}; + +fn ExtraWrap(comptime kind: StoreKind) type { + return struct { + pub fn Index(comptime T: type) type { + return enum(u32) { + _, + + // Reads the data from `store.extra` and returns it. + pub fn get(index: @This(), owner: anytype) T { + return kind.resolveStoreConst(owner).get(T, @intFromEnum(index)); + } + + /// Returns data with a trailing reader to read data stored immediately after + /// the main data structure. What is stored should be documented on `T` + pub fn getTrail(index: @This(), owner: anytype) struct { + T, + kind.ResolvedStore(@TypeOf(owner)).TrailReader, + } { + const advance = comptime extraSizeOf(T); + return .{ + index.get(owner), + .{ .index = @intFromEnum(index) + advance }, + }; + } + }; + } + }; +} + +pub fn EncodedData(comptime Tag: type, backing_type: type) type { + return enum(backing_type) { + _, + + pub fn decode(data: @This(), comptime tag: Tag, proof: Tag) tag.Data() { + assert(proof == tag); // incorrect memory access + const Data = tag.Data(); + const info = @typeInfo(Data); + if (info == .Enum) { + return @enumFromInt(@intFromEnum(data)); + } + if (info == .Struct) { + assert(backing_type == u64); + var fields: [2]u32 = @bitCast(@intFromEnum(data)); + return getAsU32(&fields, Data, 0); + } + return @bitCast(@intFromEnum(data)); + } + + pub fn encode(comptime tag: Tag, data: tag.Data()) @This() { + const Data = tag.Data(); + const info = @typeInfo(Data); + if (info == .Enum) { + return @enumFromInt(@intFromEnum(data)); + } + if (info == .Struct) { + assert(backing_type == u64); + var fields: [2]u32 = undefined; + setAsU32(&fields, Data, 0, data); + return @enumFromInt(@as(backing_type, @bitCast(fields))); + } + return @enumFromInt(@as(backing_type, @bitCast(data))); + } + }; +} + +/// The number of 'u32's this type consumes in `ip.extra`. +pub fn extraSizeOf(comptime T: type) usize { + return switch (T) { + u32, + i32, + => return 1, + + [16]u8 => return 4, + + else => { + const info = @typeInfo(T); + if (info == .Enum) { + comptime assert(@sizeOf(info.Enum.tag_type) <= 32); + return 1; + } + + const struct_data = info.Struct; + if (struct_data.layout == .@"packed") { + comptime assert(struct_data.backing_integer == u32); + return 1; + } + + const fields = std.meta.fields(T); + var size: usize = 0; + inline for (fields) |field| { + size += extraSizeOf(field.type); + } + return size; + }, + }; +} + +pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutability) type { + return struct { + list: List = .{}, + + pub const kind = set_kind; + pub const mutability = set_mutability; + + const Store = @This(); + + const index_holder = ExtraWrap(kind); + pub const Index = index_holder.Index; + + pub const List = switch (mutability) { + .@"const" => struct { + items: []const u32 = &.{}, + + pub fn deinit(slice: *@This(), gpa: Allocator) void { + gpa.free(slice.items); + } + }, + .@"var" => std.ArrayListUnmanaged(u32), + }; + + pub fn deinit(store: *Store, gpa: Allocator) void { + store.list.deinit(gpa); + store.* = undefined; + } + + pub const TrailReader = struct { + index: usize, + + pub fn read(trail: *TrailReader, ip: *const Store, comptime T: type) T { + defer trail.index += comptime extraSizeOf(T); + return ip.get(T, trail.index); + } + + pub fn readValueSlice(trail: *TrailReader, len: u32) @import("InternPool.zig").Value.Slice { + defer trail.index += len; + return .{ + .start = @intCast(trail.index), + .len = len, + }; + } + + pub fn readTypeSlice(trail: *TrailReader, len: u32) @import("InternPool.zig").Type.Slice { + defer trail.index += len; + return .{ + .start = @intCast(trail.index), + .len = len, + }; + } + }; + + pub const TrailWriter = struct { + store: *Store, + leftover: usize, + + pub fn init(store: *Store, len: usize) TrailWriter { + return .{ .store = store, .leftover = len }; + } + + pub fn end(w: *TrailWriter) void { + assert(w.leftover == 0); + } + + pub fn writeU32(w: *TrailWriter, value: u32) void { + assert(w.leftover > 0); + w.ip.extra.appendAssumeCapacity(value); + w.leftover -= 1; + } + + pub fn writeSlice(w: *TrailWriter, slice: anytype) void { + const Child = std.meta.Child(@TypeOf(slice)); + const child = @typeInfo(Child); + comptime assert(Child == u32 or (child == .Enum and child.Enum.tag_type == u32)); + assert(w.leftover >= slice.len); + w.store.list.appendSliceAssumeCapacity(@ptrCast(slice)); + w.leftover -= slice.len; + } + + pub fn writeStruct(w: *TrailWriter, comptime T: type, data: T) void { + _ = appendAssumeCapacity(w.store, T, data); + } + }; + + pub fn append(store: *Store, gpa: Allocator, comptime T: type, payload: T) Allocator.Error!Index(T) { + comptime assert(mutability == .@"var"); + try store.list.ensureUnusedCapacity(gpa, extraSizeOf(@TypeOf(payload))); + return store.appendAssumeCapacity(T, payload); + } + + pub fn appendAssumeCapacity(ip: *Store, comptime T: type, payload: T) Index(T) { + comptime assert(mutability == .@"var"); + const extra_index: u32 = @intCast(ip.list.items.len); + ip.list.items.len += extraSizeOf(@TypeOf(payload)); + ip.set(T, extra_index, payload); + return @enumFromInt(extra_index); + } + + pub fn appendTrailing( + store: *Store, + gpa: Allocator, + comptime T: type, + payload: T, + trailing_len: usize, + ) Allocator.Error!struct { Index(T), TrailWriter } { + comptime assert(mutability == .@"var"); + try store.list.ensureUnusedCapacity(gpa, extraSizeOf(@TypeOf(payload)) + trailing_len); + const extra_index: u32 = @intCast(store.list.items.len); + store.list.items.len += extraSizeOf(@TypeOf(payload)); + store.set(T, extra_index, payload); + return .{ + @enumFromInt(extra_index), + TrailWriter.init(store, trailing_len), + }; + } + + fn set(store: *Store, comptime T: type, index: usize, payload: T) void { + comptime assert(mutability == .@"var"); + setAsU32(store.list.items, T, index, payload); + } + + fn get(store: *const Store, comptime T: type, index: usize) T { + return getAsU32(store.list.items, T, index); + } + + pub fn toOwnedStore(store: *Store, gpa: Allocator) !ExtraStore(set_kind, .@"const") { + comptime assert(mutability == .@"var"); + return .{ .list = .{ .items = try store.list.toOwnedSlice(gpa) } }; + } + + pub fn constant(store: *const Store) ExtraStore(set_kind, .@"const") { + return .{ .list = .{ .items = store.list.items } }; + } + }; +} + +pub const Float = struct { + low: u32, + high: u32, + + pub const pure_nan = Float{ .low = 0x7f800000, .high = 0 }; + + pub fn purifyNaN(f: f64) f64 { + if (std.math.isNan(f)) { + return comptime Float.pure_nan.decode(); + } + return f; + } + + pub fn assertPure(f: f64) f64 { + if (std.math.isNan(f)) { + assert(Float.pure_nan.eql(encode(f))); + } + return f; + } + + pub fn encode(decoded: f64) Float { + if (std.math.isNan(decoded)) { + return pure_nan; + } + const as_bytes: [2]u32 = @bitCast(decoded); + return .{ + .low = as_bytes[0], + .high = as_bytes[1], + }; + } + + pub fn decode(encoded: Float) f64 { + const as_bytes: [2]u32 = .{ + encoded.low, + encoded.high, + }; + return @bitCast(as_bytes); + } + + pub fn eql(a: Float, b: Float) bool { + return a.low == b.low and a.high == b.high; + } +}; + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; diff --git a/src/compiler/dod.zig b/src/compiler/dod.zig deleted file mode 100644 index 57ccac606608a1b511c3f0f54871592143edb8a1..0000000000000000000000000000000000000000 --- a/src/compiler/dod.zig +++ /dev/null @@ -1,390 +0,0 @@ -const extra = @This(); - -pub const Mutability = enum { @"const", @"var" }; - -fn setAsU32(items: []u32, comptime T: type, index: usize, payload: T) void { - const fields = std.meta.fields(@TypeOf(payload)); - var i = index; - inline for (fields) |field| { - const value = @field(payload, field.name); - switch (field.type) { - u32 => items[i] = value, - i32 => items[i] = @bitCast(value), - - [16]u8 => { - const u32s: [4]u32 = @bitCast(value); - items[i] = u32s[0]; - items[i + 1] = u32s[1]; - items[i + 2] = u32s[2]; - items[i + 3] = u32s[3]; - }, - - else => brk: { - const info = @typeInfo(field.type); - if (info == .Enum) { - items[i] = @intFromEnum(value); - break :brk; - } - - const struct_data = info.Struct; - if (struct_data.layout == .@"packed") { - items[i] = @bitCast(value); - break :brk; - } - - @call(.always_inline, setAsU32, .{ items, field.type, i, value }); - }, - } - i += extraSizeOf(field.type); - } -} - -fn getAsU32(items: []const u32, comptime T: type, index: usize) T { - const fields = std.meta.fields(T); - var i = index; - var result: T = undefined; - inline for (fields) |field| { - @field(result, field.name) = switch (field.type) { - u32 => items[i], - i32 => @bitCast(items[i]), - - [16]u8 => @bitCast(items[i..][0..4].*), - - else => brk: { - const info = @typeInfo(field.type); - if (info == .Enum) { - break :brk @enumFromInt(items[i]); - } - - const struct_data = info.Struct; - if (struct_data.layout == .@"packed") { - break :brk @bitCast(items[i]); - } - - break :brk @call( - .always_inline, - getAsU32, - .{ items, field.type, i }, - ); - }, - }; - i += extraSizeOf(field.type); - } - return result; -} - -pub const StoreKind = enum { - intern_pool, - teg, - air, - tfr, - - pub fn resolveStoreConst( - comptime kind: StoreKind, - owner: anytype, - ) *const kind.ResolvedStore(@TypeOf(owner)) { - return &owner.extra; - } - - pub fn resolveStoreMut( - comptime kind: StoreKind, - owner: anytype, - ) *kind.ResolvedStore(@TypeOf(owner)) { - return &owner.extra; - } - - pub fn ResolvedStore( - comptime kind: StoreKind, - comptime Owner: type, - ) type { - const ptr = @typeInfo(Owner).Pointer; - assert(ptr.size == .One); - const mutability = brk: for (@typeInfo(ptr.child).Struct.fields) |field| { - if (std.mem.eql(u8, field.name, "extra")) { - break :brk field.type.mutability; - } - } else unreachable; - return ExtraStore(kind, mutability); - } -}; - -fn ExtraWrap(comptime kind: StoreKind) type { - return struct { - pub fn Index(comptime T: type) type { - return enum(u32) { - _, - - // Reads the data from `store.extra` and returns it. - pub fn get(index: @This(), owner: anytype) T { - return kind.resolveStoreConst(owner).get(T, @intFromEnum(index)); - } - - /// Returns data with a trailing reader to read data stored immediately after - /// the main data structure. What is stored should be documented on `T` - pub fn getTrail(index: @This(), owner: anytype) struct { - T, - kind.ResolvedStore(@TypeOf(owner)).TrailReader, - } { - const advance = comptime extraSizeOf(T); - return .{ - index.get(owner), - .{ .index = @intFromEnum(index) + advance }, - }; - } - }; - } - }; -} - -pub fn EncodedData(comptime Tag: type, backing_type: type) type { - return enum(backing_type) { - _, - - pub fn decode(data: @This(), comptime tag: Tag, proof: Tag) tag.Data() { - assert(proof == tag); // incorrect memory access - const Data = tag.Data(); - const info = @typeInfo(Data); - if (info == .Enum) { - return @enumFromInt(@intFromEnum(data)); - } - if (info == .Struct) { - assert(backing_type == u64); - var fields: [2]u32 = @bitCast(@intFromEnum(data)); - return getAsU32(&fields, Data, 0); - } - return @bitCast(@intFromEnum(data)); - } - - pub fn encode(comptime tag: Tag, data: tag.Data()) @This() { - const Data = tag.Data(); - const info = @typeInfo(Data); - if (info == .Enum) { - return @enumFromInt(@intFromEnum(data)); - } - if (info == .Struct) { - assert(backing_type == u64); - var fields: [2]u32 = undefined; - setAsU32(&fields, Data, 0, data); - return @enumFromInt(@as(backing_type, @bitCast(fields))); - } - return @enumFromInt(@as(backing_type, @bitCast(data))); - } - }; -} - -/// The number of 'u32's this type consumes in `ip.extra`. -pub fn extraSizeOf(comptime T: type) usize { - return switch (T) { - u32, - i32, - => return 1, - - [16]u8 => return 4, - - else => { - const info = @typeInfo(T); - if (info == .Enum) { - comptime assert(@sizeOf(info.Enum.tag_type) <= 32); - return 1; - } - - const struct_data = info.Struct; - if (struct_data.layout == .@"packed") { - comptime assert(struct_data.backing_integer == u32); - return 1; - } - - const fields = std.meta.fields(T); - var size: usize = 0; - inline for (fields) |field| { - size += extraSizeOf(field.type); - } - return size; - }, - }; -} - -pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutability) type { - return struct { - list: List = .{}, - - pub const kind = set_kind; - pub const mutability = set_mutability; - - const Store = @This(); - - const index_holder = ExtraWrap(kind); - pub const Index = index_holder.Index; - - pub const List = switch (mutability) { - .@"const" => struct { - items: []const u32 = &.{}, - - pub fn deinit(slice: *@This(), gpa: Allocator) void { - gpa.free(slice.items); - } - }, - .@"var" => std.ArrayListUnmanaged(u32), - }; - - pub fn deinit(store: *Store, gpa: Allocator) void { - store.list.deinit(gpa); - store.* = undefined; - } - - pub const TrailReader = struct { - index: usize, - - pub fn read(trail: *TrailReader, ip: *const Store, comptime T: type) T { - defer trail.index += comptime extraSizeOf(T); - return ip.get(T, trail.index); - } - - pub fn readValueSlice(trail: *TrailReader, len: u32) @import("InternPool.zig").Value.Slice { - defer trail.index += len; - return .{ - .start = @intCast(trail.index), - .len = len, - }; - } - - pub fn readTypeSlice(trail: *TrailReader, len: u32) @import("InternPool.zig").Type.Slice { - defer trail.index += len; - return .{ - .start = @intCast(trail.index), - .len = len, - }; - } - }; - - pub const TrailWriter = struct { - store: *Store, - leftover: usize, - - pub fn init(store: *Store, len: usize) TrailWriter { - return .{ .store = store, .leftover = len }; - } - - pub fn end(w: *TrailWriter) void { - assert(w.leftover == 0); - } - - pub fn writeU32(w: *TrailWriter, value: u32) void { - assert(w.leftover > 0); - w.ip.extra.appendAssumeCapacity(value); - w.leftover -= 1; - } - - pub fn writeSlice(w: *TrailWriter, slice: anytype) void { - const Child = std.meta.Child(@TypeOf(slice)); - const child = @typeInfo(Child); - comptime assert(Child == u32 or (child == .Enum and child.Enum.tag_type == u32)); - assert(w.leftover >= slice.len); - w.store.list.appendSliceAssumeCapacity(@ptrCast(slice)); - w.leftover -= slice.len; - } - - pub fn writeStruct(w: *TrailWriter, comptime T: type, data: T) void { - _ = appendAssumeCapacity(w.store, T, data); - } - }; - - pub fn append(store: *Store, gpa: Allocator, comptime T: type, payload: T) Allocator.Error!Index(T) { - comptime assert(mutability == .@"var"); - try store.list.ensureUnusedCapacity(gpa, extraSizeOf(@TypeOf(payload))); - return store.appendAssumeCapacity(T, payload); - } - - pub fn appendAssumeCapacity(ip: *Store, comptime T: type, payload: T) Index(T) { - comptime assert(mutability == .@"var"); - const extra_index: u32 = @intCast(ip.list.items.len); - ip.list.items.len += extraSizeOf(@TypeOf(payload)); - ip.set(T, extra_index, payload); - return @enumFromInt(extra_index); - } - - pub fn appendTrailing( - store: *Store, - gpa: Allocator, - comptime T: type, - payload: T, - trailing_len: usize, - ) Allocator.Error!struct { Index(T), TrailWriter } { - comptime assert(mutability == .@"var"); - try store.list.ensureUnusedCapacity(gpa, extraSizeOf(@TypeOf(payload)) + trailing_len); - const extra_index: u32 = @intCast(store.list.items.len); - store.list.items.len += extraSizeOf(@TypeOf(payload)); - store.set(T, extra_index, payload); - return .{ - @enumFromInt(extra_index), - TrailWriter.init(store, trailing_len), - }; - } - - fn set(store: *Store, comptime T: type, index: usize, payload: T) void { - comptime assert(mutability == .@"var"); - setAsU32(store.list.items, T, index, payload); - } - - fn get(store: *const Store, comptime T: type, index: usize) T { - return getAsU32(store.list.items, T, index); - } - - pub fn toOwnedStore(store: *Store, gpa: Allocator) !ExtraStore(set_kind, .@"const") { - comptime assert(mutability == .@"var"); - return .{ .list = .{ .items = try store.list.toOwnedSlice(gpa) } }; - } - - pub fn constant(store: *const Store) ExtraStore(set_kind, .@"const") { - return .{ .list = .{ .items = store.list.items } }; - } - }; -} - -pub const Float = struct { - low: u32, - high: u32, - - pub const pure_nan = Float{ .low = 0x7f800000, .high = 0 }; - - pub fn purifyNaN(f: f64) f64 { - if (std.math.isNan(f)) { - return comptime Float.pure_nan.decode(); - } - return f; - } - - pub fn assertPure(f: f64) f64 { - if (std.math.isNan(f)) { - assert(Float.pure_nan.eql(encode(f))); - } - return f; - } - - pub fn encode(decoded: f64) Float { - if (std.math.isNan(decoded)) { - return pure_nan; - } - const as_bytes: [2]u32 = @bitCast(decoded); - return .{ - .low = as_bytes[0], - .high = as_bytes[1], - }; - } - - pub fn decode(encoded: Float) f64 { - const as_bytes: [2]u32 = .{ - encoded.low, - encoded.high, - }; - return @bitCast(as_bytes); - } - - pub fn eql(a: Float, b: Float) bool { - return a.low == b.low and a.high == b.high; - } -}; - -const std = @import("std"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; diff --git a/src/compiler/util.zig b/src/compiler/util.zig index f4c51ab4cc6eb35fdcd80465008b9e12672344fb..6e244c52b8da94449206f1cf1ce0fcedd0fc369b 100644 --- a/src/compiler/util.zig +++ b/src/compiler/util.zig @@ -19,7 +19,9 @@ pub fn detectTTYFromWriter(writer: anytype) std.io.tty.Config { 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); @@ -34,6 +36,19 @@ pub fn Ordinal(Int: type) type { pub fn oneBased(ord: @This()) Int { return @intFromEnum(ord) + 1; } + + pub fn add(ord: @This(), inc: Int) @This() { + return fromZeroBased(ord.zeroBased() + inc); + } + + pub fn format( + _: @This(), + comptime _: []const u8, + _: std.fmt.FormatOptions, + _: anytype, + ) !void { + @compileError("Do not format `Ordinal` directly. Call oneBased or zeroBased to specify."); + } }; }