fix: semantic restrictions for global variables, constant folding
This commit is contained in:
parent
cbcc796f1e
commit
9b5cd4038f
13 changed files with 569 additions and 375 deletions
|
|
@ -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,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,6 +230,7 @@ pub const Inst = struct {
|
|||
pub const Declaration = struct {
|
||||
name: NullTerminatedString,
|
||||
value: Index,
|
||||
flags: u32,
|
||||
};
|
||||
|
||||
pub const Var = struct {
|
||||
|
|
|
|||
760
src/Sema.zig
760
src/Sema.zig
File diff suppressed because it is too large
Load diff
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
0
src/Story/testdata/constant-folding/input.txt
vendored
Normal file
0
src/Story/testdata/constant-folding/input.txt
vendored
Normal file
4
src/Story/testdata/constant-folding/story.ink
vendored
Normal file
4
src/Story/testdata/constant-folding/story.ink
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
VAR a = b + 1
|
||||
CONST b = c
|
||||
CONST c = 1
|
||||
{a}
|
||||
1
src/Story/testdata/constant-folding/transcript.txt
vendored
Normal file
1
src/Story/testdata/constant-folding/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
2
|
||||
0
src/Story/testdata/variable-arithmetic/input.txt
vendored
Normal file
0
src/Story/testdata/variable-arithmetic/input.txt
vendored
Normal file
3
src/Story/testdata/variable-arithmetic/story.ink
vendored
Normal file
3
src/Story/testdata/variable-arithmetic/story.ink
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
VAR a = 1
|
||||
VAR b = 2
|
||||
{a + b}
|
||||
1
src/Story/testdata/variable-arithmetic/transcript.txt
vendored
Normal file
1
src/Story/testdata/variable-arithmetic/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
3
|
||||
123
src/compile.zig
123
src/compile.zig
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(", ");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue