feat: code generation for diverts, wip
This commit is contained in:
parent
ee26be6254
commit
20292bcc6a
5 changed files with 699 additions and 149 deletions
111
src/Story.zig
111
src/Story.zig
|
|
@ -17,11 +17,14 @@ choice_index: usize = 0,
|
|||
current_choices: std.ArrayListUnmanaged(Choice) = .empty,
|
||||
constants_pool: std.ArrayListUnmanaged(*Object) = .empty,
|
||||
globals: std.StringHashMapUnmanaged(?*Object) = .empty,
|
||||
paths: std.ArrayListUnmanaged(*Object) = .empty,
|
||||
stack: std.ArrayListUnmanaged(?*Object) = .empty,
|
||||
call_stack: std.ArrayListUnmanaged(CallFrame) = .empty,
|
||||
stack_max: usize = 128,
|
||||
gc_objects: std.SinglyLinkedList = .{},
|
||||
// FIXME: This was a hack to keep string bytes alive.
|
||||
string_bytes: []const u8 = &.{},
|
||||
|
||||
pub const default_knot_name: [:0]const u8 = "$__main__$";
|
||||
|
||||
pub const CallFrame = struct {
|
||||
ip: usize,
|
||||
|
|
@ -104,9 +107,9 @@ pub fn deinit(story: *Story) void {
|
|||
story.current_choices.deinit(gpa);
|
||||
story.constants_pool.deinit(gpa);
|
||||
story.globals.deinit(gpa);
|
||||
story.paths.deinit(gpa);
|
||||
story.stack.deinit(gpa);
|
||||
story.call_stack.deinit(gpa);
|
||||
gpa.free(story.string_bytes);
|
||||
}
|
||||
|
||||
pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
||||
|
|
@ -129,8 +132,14 @@ pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
|||
try writer.writeAll("\n");
|
||||
try writer.writeAll("=== Knots ===\n");
|
||||
|
||||
for (story.paths.items) |path_object| {
|
||||
try story_dumper.dump(@ptrCast(path_object));
|
||||
var knots_iter = story.globals.iterator();
|
||||
while (knots_iter.next()) |entry| {
|
||||
if (entry.value_ptr.*) |global| {
|
||||
switch (global.tag) {
|
||||
.content_path => try story_dumper.dump(@ptrCast(global)),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,16 +150,18 @@ pub fn trace(story: *Story, writer: *std.Io.Writer, frame: *CallFrame) !void {
|
|||
const stack = &story.stack;
|
||||
const stack_top = story.stack.items.len;
|
||||
if (stack_top > 0) {
|
||||
const last_slot = stack.items[stack.items.len - 1];
|
||||
for (stack.items[frame.sp .. stack.items.len - 1]) |slot| {
|
||||
if (slot) |object| {
|
||||
try story_dumper.dumpObject(object);
|
||||
} else {
|
||||
try writer.writeAll("null");
|
||||
// FIXME: There has to be a better way to do this.
|
||||
if (stack_top > 1) {
|
||||
for (stack.items[frame.sp .. stack.items.len - 1]) |slot| {
|
||||
if (slot) |object| {
|
||||
try story_dumper.dumpObject(object);
|
||||
} else {
|
||||
try writer.writeAll("null");
|
||||
}
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
if (last_slot) |object| {
|
||||
if (stack.items[stack.items.len - 1]) |object| {
|
||||
try story_dumper.dumpObject(object);
|
||||
} else {
|
||||
try writer.writeAll("null");
|
||||
|
|
@ -198,7 +209,7 @@ fn getConstant(story: *Story, frame: *CallFrame, offset: u8) !*Object {
|
|||
}
|
||||
|
||||
fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?*Object {
|
||||
const stack_top = vm.stack.capacity;
|
||||
const stack_top = vm.stack.items.len;
|
||||
const stack_offset = frame.sp + offset;
|
||||
assert(stack_top > stack_offset);
|
||||
|
||||
|
|
@ -206,19 +217,21 @@ fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?*Object {
|
|||
}
|
||||
|
||||
fn setLocal(vm: *Story, frame: *CallFrame, offset: u8, value: *Object) void {
|
||||
const stack_top = vm.stack.capacity;
|
||||
const stack_top = vm.stack.items.len;
|
||||
const stack_offset = frame.sp + offset;
|
||||
assert(stack_top > stack_offset);
|
||||
|
||||
vm.stack.items[stack_offset] = value;
|
||||
}
|
||||
|
||||
// TODO: This should probably check the constants table first.
|
||||
fn getGlobal(vm: *Story, key: *const Object.String) !*Object {
|
||||
const key_bytes = key.bytes[0..key.length];
|
||||
const val = vm.globals.get(key_bytes) orelse return error.InvalidVariable;
|
||||
return val orelse unreachable;
|
||||
}
|
||||
|
||||
// TODO: This should probably check the constants table first.
|
||||
fn setGlobal(vm: *Story, key: *const Object.String, value: *Object) !void {
|
||||
const key_bytes = key.bytes[0..key.length];
|
||||
return vm.globals.putAssumeCapacity(key_bytes, value);
|
||||
|
|
@ -231,12 +244,12 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
}
|
||||
if (vm.isCallStackEmpty()) return .empty;
|
||||
|
||||
const frame = vm.currentFrame();
|
||||
const code = std.mem.bytesAsSlice(Opcode, frame.callee.bytes);
|
||||
var stream_writer = std.Io.Writer.Allocating.init(gpa);
|
||||
defer stream_writer.deinit();
|
||||
|
||||
while (true) {
|
||||
const frame = vm.currentFrame();
|
||||
const code = std.mem.bytesAsSlice(Opcode, frame.callee.bytes);
|
||||
if (vm.dump_writer) |w| {
|
||||
vm.trace(w, frame) catch {};
|
||||
}
|
||||
|
|
@ -447,6 +460,16 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
|
||||
frame.ip = branch_dispatch.dest_offset;
|
||||
},
|
||||
.divert => {
|
||||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
frame.ip += 2;
|
||||
|
||||
if (peekStack(vm, arg_offset)) |knot| {
|
||||
try divertToKnot(vm, @ptrCast(knot));
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
},
|
||||
else => return error.InvalidInstruction,
|
||||
}
|
||||
}
|
||||
|
|
@ -462,27 +485,37 @@ pub fn advance(story: *Story, gpa: std.mem.Allocator) ![]const u8 {
|
|||
return content.toOwnedSlice(gpa);
|
||||
}
|
||||
|
||||
fn divert(vm: *Story, path_name: []const u8) !void {
|
||||
const gpa = vm.allocator;
|
||||
const path_object: ?*Object.ContentPath = blk: {
|
||||
for (vm.paths.items) |object| {
|
||||
const current_path: *Object.ContentPath = @ptrCast(object);
|
||||
const current_name = current_path.name;
|
||||
// TODO(Brett): We probably should create a method for doing this.
|
||||
const name_bytes = current_name.bytes[0..current_name.length];
|
||||
if (std.mem.eql(u8, name_bytes, path_name)) break :blk current_path;
|
||||
pub fn getKnot(vm: *Story, name: []const u8) ?*Object.ContentPath {
|
||||
const knot: ?*Object.ContentPath = blk: {
|
||||
if (vm.globals.get(name)) |object| {
|
||||
break :blk @ptrCast(object);
|
||||
}
|
||||
break :blk null;
|
||||
};
|
||||
if (path_object) |path| {
|
||||
// TODO(Brett): Add arguments?
|
||||
const stack_needed = path.arity + path.locals_count;
|
||||
const stack_ptr = vm.stack.items.len;
|
||||
try vm.stack.ensureUnusedCapacity(gpa, stack_needed);
|
||||
try vm.call_stack.ensureUnusedCapacity(gpa, 1);
|
||||
return knot;
|
||||
}
|
||||
|
||||
vm.stack.appendNTimesAssumeCapacity(null, stack_needed);
|
||||
vm.call_stack.appendAssumeCapacity(.{ .ip = 0, .sp = stack_ptr, .callee = path });
|
||||
// TODO(Brett): Add arguments?
|
||||
fn divertToKnot(vm: *Story, knot: *Object.ContentPath) !void {
|
||||
const gpa = vm.allocator;
|
||||
const stack_ptr = vm.stack.items.len - knot.arity;
|
||||
const stack_needed = knot.locals_count;
|
||||
|
||||
try vm.stack.ensureUnusedCapacity(gpa, stack_needed);
|
||||
try vm.call_stack.ensureUnusedCapacity(gpa, 1);
|
||||
|
||||
vm.call_stack.appendAssumeCapacity(.{
|
||||
.callee = knot,
|
||||
.ip = 0,
|
||||
.sp = stack_ptr,
|
||||
});
|
||||
vm.stack.appendNTimesAssumeCapacity(null, stack_needed);
|
||||
vm.can_advance = true;
|
||||
}
|
||||
|
||||
fn divert(vm: *Story, knot_name: []const u8) !void {
|
||||
return if (getKnot(vm, knot_name)) |knot| {
|
||||
return divertToKnot(vm, knot);
|
||||
} else return error.InvalidPath;
|
||||
}
|
||||
|
||||
|
|
@ -535,7 +568,6 @@ pub fn loadFromString(
|
|||
try options.stderr_writer.flush();
|
||||
return error.CompilationFailed;
|
||||
}
|
||||
|
||||
if (options.dump_ir) {
|
||||
if (options.dump_writer) |w| {
|
||||
try w.writeAll("=== Semantic IR ===\n");
|
||||
|
|
@ -552,8 +584,13 @@ pub fn loadFromString(
|
|||
.can_advance = false,
|
||||
.dump_writer = options.dump_writer,
|
||||
};
|
||||
try compiled.buildRuntime(gpa, sem_ir, &story);
|
||||
try story.divert("$__main__$");
|
||||
story.can_advance = true;
|
||||
errdefer story.deinit();
|
||||
|
||||
try compiled.buildRuntime(gpa, &sem_ir, &story);
|
||||
|
||||
if (story.getKnot(Story.default_knot_name)) |knot| {
|
||||
try story.divertToKnot(knot);
|
||||
story.can_advance = true;
|
||||
}
|
||||
return story;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue