feat: ir for declarations and semantic analyzer start
This commit is contained in:
parent
f16162b5bb
commit
197a37ebe7
4 changed files with 453 additions and 145 deletions
261
src/Sema.zig
Normal file
261
src/Sema.zig
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
const std = @import("std");
|
||||
const Ir = @import("Ir.zig");
|
||||
const Story = @import("Story.zig");
|
||||
const Object = Story.Object;
|
||||
const Sema = @This();
|
||||
|
||||
gpa: std.mem.Allocator,
|
||||
ir: *const Ir,
|
||||
bytecode: std.ArrayListUnmanaged(u8) = .empty,
|
||||
constants: std.ArrayListUnmanaged(CompiledStory.Constant) = .empty,
|
||||
knots: std.ArrayListUnmanaged(CompiledStory.Knot) = .empty,
|
||||
|
||||
const InnerError = error{
|
||||
OutOfMemory,
|
||||
TooManyConstants,
|
||||
};
|
||||
|
||||
fn deinit(sema: *Sema) void {
|
||||
const gpa = sema.gpa;
|
||||
sema.bytecode.deinit(gpa);
|
||||
sema.constants.deinit(gpa);
|
||||
sema.knots.deinit(gpa);
|
||||
sema.* = undefined;
|
||||
}
|
||||
|
||||
fn resolveIndex(sema: *Sema, index: Ir.Inst.Index) Ir.Inst {
|
||||
return sema.ir.instructions[@intFromEnum(index)];
|
||||
}
|
||||
|
||||
fn emitByte(sema: *Sema, byte: u8) !void {
|
||||
const gpa = sema.gpa;
|
||||
return sema.bytecode.append(gpa, byte);
|
||||
}
|
||||
|
||||
fn emitByteOp(sema: *Sema, op: Story.Opcode) !void {
|
||||
return emitByte(sema, @intFromEnum(op));
|
||||
}
|
||||
|
||||
fn emitConstOp(sema: *Sema, op: Story.Opcode, arg: usize) !void {
|
||||
const gpa = sema.gpa;
|
||||
if (arg >= std.math.maxInt(u8)) return error.TooManyConstants;
|
||||
try sema.bytecode.ensureUnusedCapacity(gpa, 2);
|
||||
sema.bytecode.appendAssumeCapacity(@intFromEnum(op));
|
||||
sema.bytecode.appendAssumeCapacity(@intCast(arg));
|
||||
}
|
||||
|
||||
fn emitJumpOp(sema: *Sema, op: Story.Opcode) error{OutOfMemory}!usize {
|
||||
const gpa = sema.gpa;
|
||||
try sema.bytecode.ensureUnusedCapacity(gpa, 3);
|
||||
sema.bytecode.appendAssumeCapacity(@intFromEnum(op));
|
||||
sema.bytecode.appendAssumeCapacity(0xff);
|
||||
sema.bytecode.appendAssumeCapacity(0xff);
|
||||
return sema.bytecode.items.len - 2;
|
||||
}
|
||||
|
||||
fn makeConstant(sema: *Sema, data: CompiledStory.Constant) !usize {
|
||||
const gpa = sema.gpa;
|
||||
const const_index = sema.constants.items.len;
|
||||
try sema.constants.append(gpa, data);
|
||||
return const_index;
|
||||
}
|
||||
|
||||
fn unaryInst(sema: *Sema, _: Ir.Inst, op: Story.Opcode) InnerError!void {
|
||||
return emitByteOp(sema, op);
|
||||
}
|
||||
|
||||
fn binaryInst(sema: *Sema, _: Ir.Inst, op: Story.Opcode) InnerError!void {
|
||||
return emitByteOp(sema, op);
|
||||
}
|
||||
|
||||
fn contentInst(sema: *Sema, _: Ir.Inst) InnerError!void {
|
||||
return emitByteOp(sema, .stream_flush);
|
||||
}
|
||||
|
||||
fn blockInst(sema: *Sema, block_inst: Ir.Inst) InnerError!void {
|
||||
const ir = sema.ir;
|
||||
const extra = ir.extraData(Ir.Inst.Block, block_inst.data.payload.payload_index);
|
||||
const body = ir.bodySlice(extra.end, extra.data.body_len);
|
||||
|
||||
for (body) |inst_index| {
|
||||
const body_inst = resolveIndex(sema, inst_index);
|
||||
try compileInst(sema, body_inst);
|
||||
}
|
||||
}
|
||||
|
||||
fn integerInst(sema: *Sema, inst: Ir.Inst) InnerError!void {
|
||||
const int_const = try sema.makeConstant(.{
|
||||
.integer = inst.data.integer.value,
|
||||
});
|
||||
return emitConstOp(sema, .load_const, int_const);
|
||||
}
|
||||
|
||||
fn stringInst(sema: *Sema, inst: Ir.Inst) InnerError!void {
|
||||
const str_const = try sema.makeConstant(.{
|
||||
.string = inst.data.string.start,
|
||||
});
|
||||
return emitConstOp(sema, .load_const, str_const);
|
||||
}
|
||||
|
||||
fn compileInst(sema: *Sema, inst: Ir.Inst) InnerError!void {
|
||||
switch (inst.tag) {
|
||||
.block => try blockInst(sema, inst),
|
||||
.add => try binaryInst(sema, inst, .add),
|
||||
.sub => try binaryInst(sema, inst, .sub),
|
||||
.mul => try binaryInst(sema, inst, .mul),
|
||||
.div => try binaryInst(sema, inst, .div),
|
||||
.mod => try binaryInst(sema, inst, .mod),
|
||||
.neg => try unaryInst(sema, inst, .neg),
|
||||
.content => try contentInst(sema, inst),
|
||||
.string => try stringInst(sema, inst),
|
||||
.integer => try integerInst(sema, inst),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn innerDecl(sema: *Sema, inst: Ir.Inst) !void {
|
||||
const ir = sema.ir;
|
||||
const extra = ir.extraData(Ir.Inst.Knot, inst.data.payload.payload_index);
|
||||
const body_slice = ir.bodySlice(extra.end, extra.data.body_len);
|
||||
for (body_slice) |inst_index| {
|
||||
const body_inst = resolveIndex(sema, inst_index);
|
||||
try compileInst(sema, body_inst);
|
||||
}
|
||||
}
|
||||
|
||||
fn declaration(sema: *Sema, inst: Ir.Inst) !void {
|
||||
const gpa = sema.gpa;
|
||||
const ir = sema.ir;
|
||||
const byte_index = sema.bytecode.items.len;
|
||||
const const_index = sema.constants.items.len;
|
||||
const extra = ir.extraData(Ir.Inst.Declaration, inst.data.payload.payload_index);
|
||||
const value_inst = sema.resolveIndex(extra.data.value);
|
||||
switch (value_inst.tag) {
|
||||
.decl_var => try innerDecl(sema, value_inst),
|
||||
.decl_knot => try innerDecl(sema, value_inst),
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
const name_ref = extra.data.name;
|
||||
try sema.knots.append(gpa, .{
|
||||
.name_ref = name_ref,
|
||||
.arity = 0,
|
||||
.stack_size = 0,
|
||||
.bytecode = .{
|
||||
.start = @intCast(byte_index),
|
||||
.len = @intCast(sema.bytecode.items.len - byte_index),
|
||||
},
|
||||
.constants = .{
|
||||
.start = @intCast(const_index),
|
||||
.len = @intCast(sema.constants.items.len - const_index),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
fn file(sema: *Sema, inst: Ir.Inst) !void {
|
||||
const extra = sema.ir.extraData(Ir.Inst.Block, inst.data.payload.payload_index);
|
||||
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
||||
for (body) |inst_index| {
|
||||
const body_inst = sema.resolveIndex(inst_index);
|
||||
switch (body_inst.tag) {
|
||||
.declaration => try declaration(sema, body_inst),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const CompiledStory = struct {
|
||||
knots: []Knot,
|
||||
constants: []Constant,
|
||||
bytecode: []u8,
|
||||
|
||||
pub const Knot = struct {
|
||||
name_ref: Ir.NullTerminatedString,
|
||||
arity: u32,
|
||||
stack_size: u32,
|
||||
constants: struct {
|
||||
start: u32,
|
||||
len: u32,
|
||||
},
|
||||
bytecode: struct {
|
||||
start: u32,
|
||||
len: u32,
|
||||
},
|
||||
};
|
||||
|
||||
pub const Constant = union(enum) {
|
||||
integer: u64,
|
||||
string: Ir.NullTerminatedString,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *CompiledStory, gpa: std.mem.Allocator) void {
|
||||
gpa.free(self.knots);
|
||||
gpa.free(self.bytecode);
|
||||
gpa.free(self.constants);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn bytecodeSlice(self: CompiledStory, knot: *const Knot) []const u8 {
|
||||
return self.bytecode[knot.bytecode.start..][0..knot.bytecode.len];
|
||||
}
|
||||
|
||||
fn constantsSlice(self: CompiledStory, knot: *const Knot) []const Constant {
|
||||
return self.constants[knot.constants.start..][0..knot.constants.len];
|
||||
}
|
||||
|
||||
pub fn buildRuntime(
|
||||
self: *CompiledStory,
|
||||
gpa: std.mem.Allocator,
|
||||
ir: Ir,
|
||||
story: *Story,
|
||||
) !void {
|
||||
for (self.knots) |compiled_knot| {
|
||||
var constant_pool: std.ArrayListUnmanaged(*Object) = .empty;
|
||||
try constant_pool.ensureUnusedCapacity(gpa, compiled_knot.constants.len);
|
||||
defer constant_pool.deinit(gpa);
|
||||
|
||||
const constants_slice = self.constantsSlice(&compiled_knot);
|
||||
for (constants_slice) |constant| {
|
||||
switch (constant) {
|
||||
.integer => |value| {
|
||||
const object: *Object.Number = try .create(story, .{
|
||||
.integer = @intCast(value),
|
||||
});
|
||||
constant_pool.appendAssumeCapacity(&object.base);
|
||||
},
|
||||
.string => |ref| {
|
||||
const bytes = ir.nullTerminatedString(ref);
|
||||
const object: *Object.String = try .create(story, bytes);
|
||||
constant_pool.appendAssumeCapacity(&object.base);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const bytecode_slice = self.bytecodeSlice(&compiled_knot);
|
||||
const chunk_name = ir.nullTerminatedString(compiled_knot.name_ref);
|
||||
const runtime_chunk: *Object.ContentPath = try .create(story, .{
|
||||
.name = try .create(story, chunk_name),
|
||||
.arity = @intCast(compiled_knot.arity),
|
||||
.locals_count = @intCast(compiled_knot.stack_size - compiled_knot.arity),
|
||||
.const_pool = try constant_pool.toOwnedSlice(gpa),
|
||||
.bytes = try gpa.dupe(u8, bytecode_slice),
|
||||
});
|
||||
try story.paths.append(gpa, &runtime_chunk.base);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn compile(gpa: std.mem.Allocator, ir: *const Ir) !CompiledStory {
|
||||
var sema: Sema = .{
|
||||
.gpa = gpa,
|
||||
.ir = ir,
|
||||
};
|
||||
defer sema.deinit();
|
||||
|
||||
try file(&sema, ir.instructions[0]);
|
||||
return .{
|
||||
.bytecode = try sema.bytecode.toOwnedSlice(gpa),
|
||||
.constants = try sema.constants.toOwnedSlice(gpa),
|
||||
.knots = try sema.knots.toOwnedSlice(gpa),
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue