fix: content inconsistencies in the vm

This commit is contained in:
Brett Broadhurst 2026-04-02 19:31:42 -06:00
parent 236acc7d60
commit ecd6017673
Failed to generate hash of commit
6 changed files with 259 additions and 115 deletions

View file

@ -43,6 +43,7 @@ pub const Opcode = enum(u8) {
exit,
done,
ret,
nil,
/// Pop a value off the stack, discarding it.
pop,
/// Push an object representing the boolean value of "true" to the stack.
@ -105,8 +106,6 @@ pub const CallFrame = struct {
/// Instruction pointer.
ip: usize,
bp: usize,
/// Output stream base.
output_base: usize,
};
pub const Value = union(enum) {
@ -450,10 +449,9 @@ fn call(vm: *Story, knot: *Object.Knot, args_count: u8) !void {
.callee = knot,
.ip = 0,
.bp = vm.stack_top - args_count - 1,
.output_base = vm.output_buffer.items.len,
};
vm.call_stack_top += 1;
vm.stack_top += knot.code.locals_count;
vm.stack_top += knot.code.stack_size;
}
fn divertValue(vm: *Story, value: Value, args_count: u8) !void {
@ -477,7 +475,6 @@ fn divert(vm: *Story, knot: *Object.Knot, args_count: u8) !void {
.callee = knot,
.ip = 0,
.bp = vm.stack_top - args_count - 1,
.output_base = vm.output_buffer.items.len,
};
vm.stack_top += knot.code.locals_count;
@ -510,32 +507,18 @@ fn step(vm: *Story) !StepSignal {
switch (code[frame.ip]) {
.exit => return .exit,
.done => return .done,
.nil => {
try pushStack(vm, .nil);
frame.ip += 1;
},
.ret => {
if (vm.call_stack_top == 0) return error.UnexpectedReturn;
vm.call_stack_top -= 1;
const resolved_stream: ?Value = if (vm.output_buffer.items.len > frame.output_base) blk: {
const frame_output = vm.output_buffer.items[frame.output_base..];
defer vm.output_buffer.shrinkRetainingCapacity(frame.output_base);
const str_bytes = try resolveOutputStream(vm, arena, frame_output);
const str_object = try Object.String.create(vm, .{ .bytes = str_bytes });
break :blk .{ .object = &str_object.base };
} else blk: {
break :blk null;
};
const return_value = if (resolved_stream) |stream| blk: {
if (frame.bp + frame.callee.code.stack_size + 1 < vm.stack_top) {
try vm.output_buffer.append(gpa, .{ .value = stream });
break :blk popStack(vm).?;
}
break :blk stream;
} else if (frame.bp + frame.callee.code.stack_size + 1 < vm.stack_top) blk: {
break :blk popStack(vm).?;
} else .nil;
const value = popStack(vm).?;
vm.stack_top = frame.bp;
vm.stack[vm.stack_top] = return_value;
vm.stack[vm.stack_top] = value;
vm.stack_top += 1;
frame = &vm.call_stack[vm.call_stack_top - 1];
},
@ -726,7 +709,10 @@ fn step(vm: *Story) !StepSignal {
.stream_push => {
// TODO: Make this more strict.
if (popStack(vm)) |value| {
try vm.output_buffer.append(gpa, .{ .value = value });
switch (value) {
.nil => {},
else => try vm.output_buffer.append(gpa, .{ .value = value }),
}
}
frame.ip += 1;
},
@ -780,8 +766,8 @@ fn resolveOutputStream(
gpa: std.mem.Allocator,
stream: []const OutputCommand,
) ![]const u8 {
var glue_active = false;
var pending_newline = false;
var pending_glue = false;
var result: std.ArrayListUnmanaged(u8) = .empty;
defer result.deinit(gpa);
@ -792,16 +778,21 @@ fn resolveOutputStream(
try result.append(gpa, '\n');
pending_newline = false;
}
glue_active = false;
const str = try Object.String.fromValue(story, value);
try result.appendSlice(gpa, str.toSlice());
pending_glue = false;
switch (value) {
.nil => {},
else => {
const str = try Object.String.fromValue(story, value);
try result.appendSlice(gpa, str.toSlice());
},
}
},
.line => {
if (!glue_active) pending_newline = true;
if (!pending_glue) pending_newline = true;
},
.glue => {
pending_newline = false;
glue_active = true;
pending_glue = true;
},
}
}