fix: semantic restrictions for global variables, constant folding

This commit is contained in:
Brett Broadhurst 2026-03-28 11:00:07 -06:00
parent cbcc796f1e
commit 9b5cd4038f
Failed to generate hash of commit
13 changed files with 569 additions and 375 deletions

View file

@ -504,6 +504,7 @@ fn setDeclaration(
.extra_index = astgen.addExtraAssumeCapacity(Ir.Inst.Declaration{
.name = args.name,
.value = args.value,
.flags = if (args.node.tag == .const_decl) 0x01 else 0x00,
}),
};
}

View file

@ -230,6 +230,7 @@ pub const Inst = struct {
pub const Declaration = struct {
name: NullTerminatedString,
value: Index,
flags: u32,
};
pub const Var = struct {

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,22 @@ const std = @import("std");
const fatal = std.process.fatal;
const ink = @import("../root.zig");
test "fixture - hello world" {
try testRuntimeFixture("hello-world");
}
test "fixture - monsieur-fogg" {
try testRuntimeFixture("monsieur-fogg");
}
test "fixture - variable arithmetic" {
try testRuntimeFixture("variable-arithmetic");
}
test "fixture - constant folding" {
try testRuntimeFixture("constant-folding");
}
const Options = struct {
input_reader: *std.Io.Reader,
error_writer: *std.Io.Writer,
@ -63,11 +79,3 @@ fn testRuntimeFixture(comptime fixture: []const u8) !void {
});
return std.testing.expectEqualSlices(u8, transcript_bytes, io_w.written());
}
test "fixture - hello world" {
try testRuntimeFixture("hello-world");
}
test "fixture - monsieur-fogg" {
try testRuntimeFixture("monsieur-fogg");
}

View file

View file

@ -0,0 +1,4 @@
VAR a = b + 1
CONST b = c
CONST c = 1
{a}

View file

@ -0,0 +1 @@
2

View file

View file

@ -0,0 +1,3 @@
VAR a = 1
VAR b = 2
{a + b}

View file

@ -0,0 +1 @@
3

View file

@ -10,7 +10,6 @@ const assert = std.debug.assert;
pub fn IntrusiveQueue(comptime T: type) type {
return struct {
const Self = @This();
head: ?*T = null,
tail: ?*T = null,
@ -80,32 +79,35 @@ test IntrusiveQueue {
}
pub const InternPool = struct {
constants: std.ArrayListUnmanaged(Constant.Key) = .empty,
constants_map: std.AutoHashMapUnmanaged(Constant.Key, Constant.Index) = .empty,
values: std.ArrayListUnmanaged(Key) = .empty,
values_map: std.AutoHashMapUnmanaged(Key, Index) = .empty,
code_chunks: std.ArrayListUnmanaged(*Module.CodeChunk) = .empty,
pub const Constant = struct {
pub const Key = union(enum) {
int: u64,
str: Ir.NullTerminatedString,
};
pub const Index = enum(u32) {
_,
};
pub const Index = enum(u32) {
none,
_,
};
pub fn getOrPutConstant(
pub const Key = union(enum) {
int: u64,
str: Ir.NullTerminatedString,
};
pub fn getOrPutValue(
ip: *InternPool,
gpa: std.mem.Allocator,
key: Constant.Key,
) error{OutOfMemory}!Constant.Index {
if (ip.constants_map.get(key)) |index| {
key: Key,
) error{OutOfMemory}!Index {
if (ip.values_map.get(key)) |index| {
return index;
} else {
const new_index: Constant.Index = @enumFromInt(ip.constants.items.len);
try ip.constants.append(gpa, key);
try ip.constants_map.put(gpa, key, new_index);
const new_index: Index = @enumFromInt(ip.values.items.len);
const new_value: Key = switch (key) {
.int => |int| .{ .int = int },
.str => |str| .{ .str = str },
};
try ip.values.append(gpa, new_value);
try ip.values_map.put(gpa, key, new_index);
return new_index;
}
}
@ -114,8 +116,8 @@ pub const InternPool = struct {
ip: *InternPool,
gpa: std.mem.Allocator,
value: u64,
) error{OutOfMemory}!Constant.Index {
return ip.getOrPutConstant(gpa, .{
) error{OutOfMemory}!Index {
return ip.getOrPutValue(gpa, .{
.int = value,
});
}
@ -123,16 +125,16 @@ pub const InternPool = struct {
pub fn getOrPutStr(
ip: *InternPool,
gpa: std.mem.Allocator,
start: Ir.NullTerminatedString,
) error{OutOfMemory}!Constant.Index {
return ip.getOrPutConstant(gpa, .{
.str = start,
value: Ir.NullTerminatedString,
) error{OutOfMemory}!Index {
return ip.getOrPutValue(gpa, .{
.str = value,
});
}
pub fn getStrBytes(ip: *InternPool, ir: Ir, index: Constant.Index) []const u8 {
assert(ip.constants.items.len > @intFromEnum(index));
const c = ip.constants.items[@intFromEnum(index)];
pub fn getStrBytes(ip: *InternPool, ir: Ir, index: Index) []const u8 {
assert(ip.values.items.len > @intFromEnum(index));
const c = ip.values.items[@intFromEnum(index)];
return ir.nullTerminatedString(c.str);
}
@ -142,8 +144,8 @@ pub const InternPool = struct {
}
pub fn deinit(ip: *InternPool, gpa: std.mem.Allocator) void {
ip.constants.deinit(gpa);
ip.constants_map.deinit(gpa);
ip.values.deinit(gpa);
ip.values_map.deinit(gpa);
ip.code_chunks.deinit(gpa);
}
};
@ -151,7 +153,7 @@ pub const InternPool = struct {
pub const WorkItem = struct {
tag: Tag,
next: ?*WorkItem = null,
decl_name: InternPool.Constant.Index,
decl_name: InternPool.Index,
inst_index: Ir.Inst.Index,
namespace: *Module.Namespace,
@ -169,13 +171,19 @@ pub const Module = struct {
tree: Ast,
ir: Ir,
intern_pool: InternPool = .{},
globals: std.ArrayListUnmanaged(Global) = .empty,
knots: std.ArrayListUnmanaged(Knot) = .empty,
stitches: std.ArrayListUnmanaged(Stitch) = .empty,
errors: std.ArrayListUnmanaged(Error) = .empty,
work_queue: WorkQueue = .{},
pub const Global = struct {
key: InternPool.Index,
value: InternPool.Index,
};
pub const Knot = struct {
name_index: InternPool.Constant.Index,
name_index: InternPool.Index,
code_index: CodeChunk.Index,
pub const Index = enum(u32) {
@ -186,7 +194,7 @@ pub const Module = struct {
pub const Stitch = struct {
knot_index: ?Knot.Index,
code_index: CodeChunk.Index,
name_index: InternPool.Constant.Index,
name_index: InternPool.Index,
pub const Index = enum(u32) {
_,
@ -195,18 +203,26 @@ pub const Module = struct {
pub const Namespace = struct {
parent: ?*Namespace,
decls: std.AutoHashMapUnmanaged(InternPool.Constant.Index, Decl),
decls: std.AutoHashMapUnmanaged(InternPool.Index, Decl),
pub const Decl = struct {
tag: Tag,
namespace: ?*Namespace,
decl_inst: Ir.Inst.Index,
args_count: u32,
resolution: Resolution = .unresolved,
pub const Resolution = union(enum) {
unresolved,
in_progress,
resolved: Sema.ValueInfo,
};
pub const Tag = enum {
knot,
stitch,
variable,
var_mut,
var_const,
};
};
};
@ -237,6 +253,7 @@ pub const Module = struct {
const extra = mod.ir.extraData(Ir.Inst.Block, data.extra_index);
const top_level_decls = mod.ir.bodySlice(extra.end, extra.data.body_len);
var knot_index: ?Knot.Index = null;
var sema: Sema = .{
.module = mod,
.gpa = gpa,
@ -247,39 +264,36 @@ pub const Module = struct {
defer sema.deinit();
const file_scope = try mod.createNamespace(null);
for (top_level_decls) |decl_index| {
try sema.analyzeTopLevelDecl(file_scope, decl_index);
}
try sema.scanTopLevelDecls(file_scope, top_level_decls);
var knot_index: ?Knot.Index = null;
while (mod.work_queue.pop()) |work_unit| {
const chunk_index = mod.intern_pool.code_chunks.items.len;
var chunk: Sema.Chunk = .{
var builder: Sema.Builder = .{
.sema = &sema,
.code = try mod.createCodeChunk(),
.namespace = work_unit.namespace,
};
defer chunk.deinit(gpa);
defer builder.deinit(gpa);
const debug_name_str = mod.intern_pool.getStrBytes(mod.ir, work_unit.decl_name);
std.debug.print("Analyzing {s}\n", .{debug_name_str});
switch (work_unit.tag) {
.knot => {
try sema.analyzeKnot(&chunk, work_unit.inst_index);
try chunk.finalize();
try sema.analyzeKnot(&builder, work_unit.inst_index);
try builder.finalize();
knot_index = @enumFromInt(mod.knots.items.len);
try mod.intern_pool.code_chunks.append(gpa, chunk.code);
try mod.intern_pool.code_chunks.append(gpa, builder.code);
try mod.knots.append(gpa, .{
.name_index = work_unit.decl_name,
.code_index = @enumFromInt(chunk_index),
});
},
.stitch => {
try sema.analyzeStitch(&chunk, work_unit.inst_index);
try chunk.finalize();
try sema.analyzeStitch(&builder, work_unit.inst_index);
try builder.finalize();
try mod.intern_pool.code_chunks.append(gpa, chunk.code);
try mod.intern_pool.code_chunks.append(gpa, builder.code);
try mod.stitches.append(gpa, .{
.knot_index = knot_index,
.name_index = work_unit.decl_name,
@ -360,10 +374,10 @@ pub const Module = struct {
pub fn setupStoryRuntime(mod: *Module, gpa: std.mem.Allocator, story: *Story) !void {
assert(mod.errors.items.len == 0);
const constants_len = mod.intern_pool.constants.items.len;
const constants_len = mod.intern_pool.values.items.len;
try story.constants_pool.ensureUnusedCapacity(gpa, constants_len);
for (mod.intern_pool.constants.items) |constant| {
for (mod.intern_pool.values.items) |constant| {
switch (constant) {
.int => |value| {
const obj = try Object.Number.create(story, .{
@ -380,6 +394,14 @@ pub const Module = struct {
},
}
}
for (mod.globals.items) |global| {
const key_bytes = mod.intern_pool.getStrBytes(mod.ir, global.key);
const constant_data = mod.intern_pool.values.items[@intFromEnum(global.value)];
const value_obj = try Object.Number.create(story, .{
.integer = @intCast(constant_data.int),
});
try story.globals.put(gpa, key_bytes, @ptrCast(value_obj));
}
for (mod.knots.items) |knot| {
const name_bytes = mod.intern_pool.getStrBytes(mod.ir, knot.name_index);
const code_chunk = mod.intern_pool.getCodeChunk(knot.code_index);
@ -438,7 +460,7 @@ pub const Module = struct {
mod: *Module,
options: struct {
tag: WorkItem.Tag,
decl_name: InternPool.Constant.Index,
decl_name: InternPool.Index,
inst_index: Ir.Inst.Index,
namespace: *Namespace,
},
@ -476,6 +498,7 @@ pub const Module = struct {
const gpa = mod.gpa;
mod.ir.deinit(gpa);
mod.intern_pool.deinit(gpa);
mod.globals.deinit(gpa);
mod.knots.deinit(gpa);
mod.stitches.deinit(gpa);
mod.errors.deinit(gpa);

View file

@ -68,6 +68,29 @@ test "compiler: invalid divert target" {
);
}
test "compiler: global variable restrictions" {
try testEqual(
\\VAR a = b
\\VAR b = a
,
\\<STDIN>:1:9: error: global variable assignments cannot refer to other variables
\\1 | VAR a = b
\\ | ^
\\
);
}
test "compiler: constant cycle detection" {
try testEqual(
\\CONST a = a
,
\\<STDIN>:1:11: error: cycle detected in constant initializer
\\1 | CONST a = a
\\ | ^
\\
);
}
fn testEqual(source_bytes: [:0]const u8, expected_error: []const u8) !void {
const gpa = std.testing.allocator;
var arena_allocator = std.heap.ArenaAllocator.init(gpa);

View file

@ -234,6 +234,9 @@ pub const Writer = struct {
fn writeDeclarationInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void {
const data = self.code.instructions[@intFromEnum(inst)].data.payload;
const extra = self.code.extraData(Ir.Inst.Declaration, data.extra_index);
if (extra.data.flags == 0x01) {
try w.writeAll("const, ");
}
try w.writeAll("name=");
try self.writeStringRef(w, extra.data.name);
try w.writeAll(", ");