feat: minimal virtual machine / runtime port from ink-c
This commit is contained in:
parent
619eb3b338
commit
849908f251
5 changed files with 833 additions and 35 deletions
8
src/AstGen.zig
Normal file
8
src/AstGen.zig
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const Ast = @import("./Ast.zig");
|
||||||
|
const Story = @import("./Story.zig");
|
||||||
|
|
||||||
|
/// Perform code generation via tree-walk.
|
||||||
|
pub fn generate(_: std.mem.Allocator, _: *const Ast) !Story {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
544
src/Story.zig
Normal file
544
src/Story.zig
Normal file
|
|
@ -0,0 +1,544 @@
|
||||||
|
//! Virtual machine state for story execution.
|
||||||
|
const std = @import("std");
|
||||||
|
const tokenizer = @import("./tokenizer.zig");
|
||||||
|
const Ast = @import("./Ast.zig");
|
||||||
|
const AstGen = @import("./AstGen.zig");
|
||||||
|
pub const Object = @import("./object.zig").Object;
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Story = @This();
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
dump_writer: ?*std.Io.Writer = null,
|
||||||
|
is_exited: bool = false,
|
||||||
|
can_advance: bool = false,
|
||||||
|
gc_objects: std.SinglyLinkedList = .{},
|
||||||
|
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,
|
||||||
|
|
||||||
|
pub const CallFrame = struct {
|
||||||
|
ip: usize,
|
||||||
|
sp: usize,
|
||||||
|
callee: *Object.ContentPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Opcode = enum(u8) {
|
||||||
|
exit,
|
||||||
|
ret,
|
||||||
|
pop,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
add,
|
||||||
|
sub,
|
||||||
|
mul,
|
||||||
|
div,
|
||||||
|
mod,
|
||||||
|
neg,
|
||||||
|
not,
|
||||||
|
cmp_eq,
|
||||||
|
cmp_lt,
|
||||||
|
cmp_gt,
|
||||||
|
cmp_lte,
|
||||||
|
cmp_gte,
|
||||||
|
jmp,
|
||||||
|
jmp_t,
|
||||||
|
jmp_f,
|
||||||
|
call,
|
||||||
|
divert,
|
||||||
|
load_const,
|
||||||
|
load,
|
||||||
|
store,
|
||||||
|
load_global,
|
||||||
|
store_global,
|
||||||
|
load_choice_id,
|
||||||
|
content,
|
||||||
|
line,
|
||||||
|
glue,
|
||||||
|
choice,
|
||||||
|
flush,
|
||||||
|
_,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn getObjectType(object: *const Object) []const u8 {
|
||||||
|
switch (object.tag) {
|
||||||
|
.number => return "Number",
|
||||||
|
.string => return "String",
|
||||||
|
.content_path => return "ContentPath",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printObject(writer: *std.Io.Writer, object: *const Object) !void {
|
||||||
|
const type_string = getObjectType(object);
|
||||||
|
switch (object.tag) {
|
||||||
|
.number => {
|
||||||
|
const typed_object: *const Object.Number = @ptrCast(object);
|
||||||
|
switch (typed_object.data) {
|
||||||
|
.boolean => |value| {
|
||||||
|
try writer.print("<type={s} value={s}, address={*}>", .{
|
||||||
|
type_string,
|
||||||
|
if (value) "true" else "false",
|
||||||
|
object,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
.floating => |value| {
|
||||||
|
try writer.print("<type={s} value={d}, address={*}>", .{
|
||||||
|
type_string,
|
||||||
|
value,
|
||||||
|
object,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
.integer => |value| {
|
||||||
|
try writer.print("<type={s} value={d}, address={*}>", .{
|
||||||
|
type_string,
|
||||||
|
value,
|
||||||
|
object,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.string => {
|
||||||
|
const typed_object: *const Object.String = @ptrCast(object);
|
||||||
|
const string_bytes = typed_object.bytes[0..typed_object.length];
|
||||||
|
try writer.print("<type={s} value=\"{s}\", address={*}>", .{
|
||||||
|
type_string,
|
||||||
|
string_bytes,
|
||||||
|
object,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
.content_path => {
|
||||||
|
try writer.print("<type={s} address={*}>", .{ type_string, object });
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Dumper = struct {
|
||||||
|
story: *const Story,
|
||||||
|
|
||||||
|
fn dumpSimpleInst(
|
||||||
|
_: *const Dumper,
|
||||||
|
writer: *std.Io.Writer,
|
||||||
|
offset: usize,
|
||||||
|
op: Opcode,
|
||||||
|
) !usize {
|
||||||
|
try writer.print("{s}\n", .{@tagName(op)});
|
||||||
|
return offset + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dumpByteInst(
|
||||||
|
_: *const Dumper,
|
||||||
|
writer: *std.Io.Writer,
|
||||||
|
context: *const Object.ContentPath,
|
||||||
|
offset: usize,
|
||||||
|
op: Opcode,
|
||||||
|
) !usize {
|
||||||
|
const arg = context.bytes[offset + 1];
|
||||||
|
if (op == .load_const) {
|
||||||
|
try writer.print("{s} {d} (", .{ @tagName(op), arg });
|
||||||
|
try printObject(writer, context.const_pool[arg]);
|
||||||
|
try writer.print(")\n", .{});
|
||||||
|
} else {
|
||||||
|
try writer.print("{s} {x}\n", .{ @tagName(op), arg });
|
||||||
|
}
|
||||||
|
return offset + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dumpGlobalInst(
|
||||||
|
_: *const Dumper,
|
||||||
|
writer: *std.Io.Writer,
|
||||||
|
context: *const Object.ContentPath,
|
||||||
|
offset: usize,
|
||||||
|
op: Opcode,
|
||||||
|
) !usize {
|
||||||
|
const arg = context.bytes[offset + 1];
|
||||||
|
const global_name: *Object.String = @ptrCast(context.const_pool[arg]);
|
||||||
|
const name_bytes = global_name.bytes[0..global_name.length];
|
||||||
|
|
||||||
|
try writer.print("{s} {x} '{s}'\n", .{ @tagName(op), arg, name_bytes });
|
||||||
|
return offset + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dumpJumpInst(
|
||||||
|
_: *const Dumper,
|
||||||
|
writer: *std.Io.Writer,
|
||||||
|
context: *const Object.ContentPath,
|
||||||
|
offset: usize,
|
||||||
|
op: Opcode,
|
||||||
|
) !usize {
|
||||||
|
var jump: u16 = @as(u16, context.bytes[offset + 1]) << 8;
|
||||||
|
jump |= context.bytes[offset + 2];
|
||||||
|
|
||||||
|
try writer.print("{s} 0x{x:0>4} (0x{x:0>4} -> 0x{x:0>4})\n", .{
|
||||||
|
@tagName(op),
|
||||||
|
jump,
|
||||||
|
offset,
|
||||||
|
offset + 3 + jump,
|
||||||
|
});
|
||||||
|
return offset + 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dumpInst(
|
||||||
|
self: *const Dumper,
|
||||||
|
writer: *std.Io.Writer,
|
||||||
|
path: *const Object.ContentPath,
|
||||||
|
offset: usize,
|
||||||
|
should_prefix: bool,
|
||||||
|
) !usize {
|
||||||
|
const name_object = path.name;
|
||||||
|
const name_bytes = name_object.bytes[0..name_object.length];
|
||||||
|
const op: Opcode = @enumFromInt(path.bytes[offset]);
|
||||||
|
|
||||||
|
if (should_prefix) {
|
||||||
|
try writer.print("<{s}>:0x{x:0>4} | ", .{ name_bytes, offset });
|
||||||
|
} else {
|
||||||
|
try writer.print("0x{x:0>4} | ", .{offset});
|
||||||
|
}
|
||||||
|
switch (op) {
|
||||||
|
.exit,
|
||||||
|
.ret,
|
||||||
|
.pop,
|
||||||
|
.true,
|
||||||
|
.false,
|
||||||
|
.add,
|
||||||
|
.sub,
|
||||||
|
.mul,
|
||||||
|
.div,
|
||||||
|
.mod,
|
||||||
|
.neg,
|
||||||
|
.not,
|
||||||
|
.cmp_eq,
|
||||||
|
.cmp_lt,
|
||||||
|
.cmp_lte,
|
||||||
|
.cmp_gt,
|
||||||
|
.cmp_gte,
|
||||||
|
.flush,
|
||||||
|
.load_choice_id,
|
||||||
|
.content,
|
||||||
|
.choice,
|
||||||
|
.line,
|
||||||
|
.glue,
|
||||||
|
=> return self.dumpSimpleInst(writer, offset, op),
|
||||||
|
.load_const,
|
||||||
|
.load,
|
||||||
|
.store,
|
||||||
|
=> return self.dumpByteInst(writer, path, offset, op),
|
||||||
|
.load_global,
|
||||||
|
.store_global,
|
||||||
|
.call,
|
||||||
|
.divert,
|
||||||
|
=> return self.dumpGlobalInst(writer, path, offset, op),
|
||||||
|
.jmp,
|
||||||
|
.jmp_t,
|
||||||
|
.jmp_f,
|
||||||
|
=> return self.dumpJumpInst(writer, path, offset, op),
|
||||||
|
else => |code| {
|
||||||
|
try writer.print("Unknown opcode 0x{x:0>4}\n", .{@intFromEnum(code)});
|
||||||
|
return offset + 1;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump(
|
||||||
|
self: *const Dumper,
|
||||||
|
writer: *std.Io.Writer,
|
||||||
|
path: *const Object.ContentPath,
|
||||||
|
) !void {
|
||||||
|
const name_object = path.name;
|
||||||
|
const name_bytes = name_object.bytes[0..name_object.length];
|
||||||
|
|
||||||
|
try writer.print("=== {s}(args: {d}, locals: {d}) ===\n", .{
|
||||||
|
name_bytes,
|
||||||
|
path.arity,
|
||||||
|
path.locals_count,
|
||||||
|
});
|
||||||
|
|
||||||
|
var index: usize = 0;
|
||||||
|
while (index < path.bytes.len) {
|
||||||
|
index = try self.dumpInst(writer, path, index, false);
|
||||||
|
}
|
||||||
|
return writer.flush();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn deinit(story: *Story) void {
|
||||||
|
const gpa = story.allocator;
|
||||||
|
var next = story.gc_objects.first;
|
||||||
|
while (next) |node| {
|
||||||
|
const object: *Object = @alignCast(@fieldParentPtr("node", node));
|
||||||
|
next = node.next;
|
||||||
|
object.destroy(story);
|
||||||
|
}
|
||||||
|
|
||||||
|
story.globals.deinit(gpa);
|
||||||
|
story.paths.deinit(gpa);
|
||||||
|
story.stack.deinit(gpa);
|
||||||
|
story.call_stack.deinit(gpa);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trace(story: *Story, writer: *std.Io.Writer, frame: *CallFrame) !void {
|
||||||
|
try writer.print("\tStack => stack_pointer={d}, objects=[", .{frame.sp});
|
||||||
|
|
||||||
|
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 printObject(writer, object);
|
||||||
|
} else {
|
||||||
|
try writer.writeAll("NULL");
|
||||||
|
}
|
||||||
|
try writer.writeAll(", ");
|
||||||
|
}
|
||||||
|
if (last_slot) |object| {
|
||||||
|
try printObject(writer, object);
|
||||||
|
} else {
|
||||||
|
try writer.writeAll("NULL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll("]\n");
|
||||||
|
const dumper = Dumper{ .story = story };
|
||||||
|
_ = try dumper.dumpInst(writer, frame.callee, frame.ip, true);
|
||||||
|
return writer.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isCallStackEmpty(vm: *const Story) bool {
|
||||||
|
return vm.call_stack.items.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn currentFrame(vm: *Story) *CallFrame {
|
||||||
|
return &vm.call_stack.items[vm.call_stack.items.len - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peekStack(vm: *Story, offset: usize) ?*Object {
|
||||||
|
const stack_top = vm.stack.items.len;
|
||||||
|
assert(stack_top > offset);
|
||||||
|
assert(stack_top != 0);
|
||||||
|
|
||||||
|
return vm.stack.items[stack_top - offset - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pushStack(vm: *Story, object: *Object) !void {
|
||||||
|
const gpa = vm.allocator;
|
||||||
|
const stack_top = vm.stack.items.len;
|
||||||
|
const max_stack_top = vm.stack_max;
|
||||||
|
if (stack_top >= max_stack_top) return error.StackOverflow;
|
||||||
|
|
||||||
|
return vm.stack.append(gpa, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn popStack(vm: *Story) ?*Object {
|
||||||
|
return vm.stack.pop() orelse unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getConstant(_: *Story, frame: *CallFrame, offset: u8) !*Object {
|
||||||
|
const constant_pool = frame.callee.const_pool;
|
||||||
|
if (offset >= constant_pool.len) return error.InvalidArgument;
|
||||||
|
return constant_pool[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?*Object {
|
||||||
|
const stack_top = vm.stack.capacity;
|
||||||
|
const stack_offset = frame.sp + offset;
|
||||||
|
assert(stack_top > stack_offset);
|
||||||
|
|
||||||
|
return vm.stack.items[stack_offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setLocal(vm: *Story, frame: *CallFrame, offset: u8, value: *Object) void {
|
||||||
|
const stack_top = vm.stack.capacity;
|
||||||
|
const stack_offset = frame.sp + offset;
|
||||||
|
assert(stack_top > stack_offset);
|
||||||
|
|
||||||
|
vm.stack.items[stack_offset] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(vm: *Story, writer: *std.Io.Writer) !void {
|
||||||
|
defer {
|
||||||
|
vm.can_advance = false;
|
||||||
|
}
|
||||||
|
if (vm.isCallStackEmpty()) return;
|
||||||
|
|
||||||
|
const frame = vm.currentFrame();
|
||||||
|
const code = std.mem.bytesAsSlice(Opcode, frame.callee.bytes);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (vm.trace_writer) |w| {
|
||||||
|
vm.trace(w, frame) catch {};
|
||||||
|
}
|
||||||
|
switch (code[frame.ip]) {
|
||||||
|
.exit => {
|
||||||
|
vm.is_exited = true;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
.pop => {
|
||||||
|
const object_top = vm.popStack();
|
||||||
|
if (object_top == null) return error.InvalidArgument;
|
||||||
|
frame.ip += 1;
|
||||||
|
},
|
||||||
|
.add => {
|
||||||
|
const lhs = vm.peekStack(1) orelse return error.Bugged;
|
||||||
|
const rhs = vm.peekStack(0) orelse return error.Bugged;
|
||||||
|
const value = try Object.add(vm, lhs, rhs);
|
||||||
|
|
||||||
|
_ = vm.popStack();
|
||||||
|
_ = vm.popStack();
|
||||||
|
try vm.pushStack(value);
|
||||||
|
frame.ip += 1;
|
||||||
|
},
|
||||||
|
.sub, .mul, .div, .mod => |op| {
|
||||||
|
const lhs = vm.peekStack(1) orelse return error.Bugged;
|
||||||
|
const rhs = vm.peekStack(0) orelse return error.Bugged;
|
||||||
|
const value = try Object.Number.performArithmetic(vm, op, @ptrCast(lhs), @ptrCast(rhs));
|
||||||
|
|
||||||
|
_ = vm.popStack();
|
||||||
|
_ = vm.popStack();
|
||||||
|
try vm.pushStack(@ptrCast(value));
|
||||||
|
frame.ip += 1;
|
||||||
|
},
|
||||||
|
.neg => {
|
||||||
|
const arg_object = vm.peekStack(0);
|
||||||
|
if (arg_object) |arg| {
|
||||||
|
_ = Object.Number.negate(@ptrCast(arg));
|
||||||
|
}
|
||||||
|
frame.ip += 1;
|
||||||
|
},
|
||||||
|
.content => {
|
||||||
|
const arg_object = vm.popStack();
|
||||||
|
if (arg_object) |object| {
|
||||||
|
const string_object = try Object.String.fromObject(vm, object);
|
||||||
|
const string_bytes = string_object.bytes[0..string_object.length];
|
||||||
|
try writer.writeAll(string_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.ip += 1;
|
||||||
|
},
|
||||||
|
.load_const => {
|
||||||
|
const index: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||||
|
const value = try vm.getConstant(frame, index);
|
||||||
|
try vm.pushStack(value);
|
||||||
|
frame.ip += 2;
|
||||||
|
},
|
||||||
|
.load => {
|
||||||
|
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||||
|
const value = vm.getLocal(frame, arg_offset) orelse return error.Bugged;
|
||||||
|
try vm.pushStack(value);
|
||||||
|
frame.ip += 2;
|
||||||
|
},
|
||||||
|
.store => {
|
||||||
|
const value = vm.peekStack(0);
|
||||||
|
if (value) |arg| {
|
||||||
|
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||||
|
vm.setLocal(frame, arg_offset, arg);
|
||||||
|
}
|
||||||
|
frame.ip += 2;
|
||||||
|
},
|
||||||
|
.load_global => {
|
||||||
|
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||||
|
const global_name = try vm.getConstant(frame, arg_offset);
|
||||||
|
assert(global_name.tag == .string);
|
||||||
|
|
||||||
|
const global_value = try vm.getGlobal(@ptrCast(global_name));
|
||||||
|
try vm.pushStack(global_value);
|
||||||
|
frame.ip += 2;
|
||||||
|
},
|
||||||
|
.store_global => {
|
||||||
|
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||||
|
const global_name = try vm.getConstant(frame, arg_offset);
|
||||||
|
assert(global_name.tag == .string);
|
||||||
|
|
||||||
|
const value = vm.peekStack(0);
|
||||||
|
if (value) |arg| {
|
||||||
|
try vm.setGlobal(@ptrCast(global_name), arg);
|
||||||
|
_ = vm.popStack();
|
||||||
|
try vm.pushStack(arg);
|
||||||
|
}
|
||||||
|
frame.ip += 2;
|
||||||
|
},
|
||||||
|
else => return error.InvalidInstruction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(story: *Story) ![]const u8 {
|
||||||
|
const gpa = story.allocator;
|
||||||
|
var aw = std.Io.Writer.Allocating.init(gpa);
|
||||||
|
|
||||||
|
try story.execute(&aw.writer);
|
||||||
|
return aw.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
vm.stack.appendNTimesAssumeCapacity(null, stack_needed);
|
||||||
|
vm.call_stack.appendAssumeCapacity(.{ .ip = 0, .sp = stack_ptr, .callee = path });
|
||||||
|
} else return error.InvalidPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const LoadOptions = struct {
|
||||||
|
dump_writer: ?*std.Io.Writer = null,
|
||||||
|
stderr_writer: *std.Io.Writer,
|
||||||
|
use_color: bool = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn loadFromString(
|
||||||
|
gpa: std.mem.Allocator,
|
||||||
|
source_bytes: [:0]const u8,
|
||||||
|
options: LoadOptions,
|
||||||
|
) !Story {
|
||||||
|
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
||||||
|
defer arena_allocator.deinit();
|
||||||
|
|
||||||
|
const arena = arena_allocator.allocator();
|
||||||
|
const ast = try Ast.parse(gpa, arena, source_bytes, "<STDIN>", 0);
|
||||||
|
|
||||||
|
if (options.dump_writer) |w| {
|
||||||
|
try ast.render(gpa, w, .{
|
||||||
|
.use_color = options.use_color,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (ast.errors.len > 0) {
|
||||||
|
try ast.renderErrors(gpa, options.stderr_writer, .{
|
||||||
|
.use_color = options.use_color,
|
||||||
|
});
|
||||||
|
return error.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
var story = try AstGen.generate(gpa, &ast);
|
||||||
|
errdefer story.deinit();
|
||||||
|
|
||||||
|
try story.divert("@main@");
|
||||||
|
story.dump_writer = options.dump_writer;
|
||||||
|
story.can_advance = true;
|
||||||
|
return story;
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ const fatal = std.process.fatal;
|
||||||
|
|
||||||
var stdin_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
var stdin_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
||||||
var stdout_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
var stdout_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
||||||
|
var stderr_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
||||||
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
||||||
const max_src_size = std.math.maxInt(u32);
|
const max_src_size = std.math.maxInt(u32);
|
||||||
|
|
||||||
|
|
@ -75,9 +76,13 @@ fn mainArgs(
|
||||||
const stdout = std.fs.File.stdout();
|
const stdout = std.fs.File.stdout();
|
||||||
var stdout_writer = stdout.writer(&stdout_buffer);
|
var stdout_writer = stdout.writer(&stdout_buffer);
|
||||||
|
|
||||||
|
const stderr = std.fs.File.stderr();
|
||||||
|
var stderr_writer = stderr.writer(&stderr_buffer);
|
||||||
|
|
||||||
var story = try ink.Story.loadFromString(gpa, source_bytes, .{
|
var story = try ink.Story.loadFromString(gpa, source_bytes, .{
|
||||||
|
.stderr_writer = &stderr_writer.interface,
|
||||||
.dump_writer = &stdout_writer.interface,
|
.dump_writer = &stdout_writer.interface,
|
||||||
.dump_use_color = use_color,
|
.use_color = use_color,
|
||||||
});
|
});
|
||||||
defer story.deinit();
|
defer story.deinit();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
272
src/object.zig
Normal file
272
src/object.zig
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const Story = @import("./Story.zig");
|
||||||
|
|
||||||
|
/// Runtime Object, whose memory is managed through the virtual machine's
|
||||||
|
/// garbage collector.
|
||||||
|
///
|
||||||
|
/// This structure is the base object type that is embedded into subtypes.
|
||||||
|
/// Each subtype has a `.create` method to allocate a new object.
|
||||||
|
pub const Object = struct {
|
||||||
|
tag: Tag,
|
||||||
|
is_marked: bool,
|
||||||
|
node: std.SinglyLinkedList.Node,
|
||||||
|
|
||||||
|
pub const Tag = enum {
|
||||||
|
number,
|
||||||
|
string,
|
||||||
|
content_path,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn destroy(object: *Object, story: *Story) void {
|
||||||
|
switch (object.tag) {
|
||||||
|
.number => {
|
||||||
|
const obj: *Object.Number = @alignCast(@fieldParentPtr("base", object));
|
||||||
|
obj.destroy(story);
|
||||||
|
},
|
||||||
|
.string => {
|
||||||
|
const obj: *Object.String = @alignCast(@fieldParentPtr("base", object));
|
||||||
|
obj.destroy(story);
|
||||||
|
},
|
||||||
|
.content_path => {
|
||||||
|
const obj: *Object.ContentPath = @alignCast(@fieldParentPtr("base", object));
|
||||||
|
obj.destroy(story);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(story: *Story, lhs: *Object, rhs: *Object) !*Object {
|
||||||
|
if (lhs.tag == .string or rhs.tag == .string) {
|
||||||
|
const _lhs = try Object.String.fromObject(story, lhs);
|
||||||
|
const _rhs = try Object.String.fromObject(story, rhs);
|
||||||
|
const result = try Object.String.concat(story, _lhs, _rhs);
|
||||||
|
return @ptrCast(result);
|
||||||
|
} else if (lhs.tag == .number or rhs.tag == .number) {
|
||||||
|
const _lhs = try Object.Number.fromObject(story, lhs);
|
||||||
|
const _rhs = try Object.Number.fromObject(story, rhs);
|
||||||
|
const result = try Object.Number.performArithmetic(story, .add, _lhs, _rhs);
|
||||||
|
return @ptrCast(result);
|
||||||
|
} else {
|
||||||
|
return error.InvalidArgument;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Number = struct {
|
||||||
|
base: Object,
|
||||||
|
data: Data,
|
||||||
|
|
||||||
|
pub const Data = union(enum) {
|
||||||
|
boolean: bool,
|
||||||
|
integer: i64,
|
||||||
|
floating: f64,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create(story: *Story, data: Data) error{OutOfMemory}!*Object.Number {
|
||||||
|
const gpa = story.allocator;
|
||||||
|
const alloc_len = @sizeOf(Object.Number);
|
||||||
|
const raw = try gpa.alignedAlloc(u8, .of(Object.Number), alloc_len);
|
||||||
|
const object: *Object.Number = @ptrCast(raw);
|
||||||
|
|
||||||
|
object.* = .{
|
||||||
|
.base = .{
|
||||||
|
.tag = .number,
|
||||||
|
.is_marked = false,
|
||||||
|
.node = .{},
|
||||||
|
},
|
||||||
|
.data = data,
|
||||||
|
};
|
||||||
|
|
||||||
|
story.gc_objects.prepend(&object.base.node);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(obj: *Object.Number, story: *Story) void {
|
||||||
|
const gpa = story.allocator;
|
||||||
|
const alloc_len = @sizeOf(Object.Number);
|
||||||
|
const base: [*]align(@alignOf(Object.Number)) u8 = @ptrCast(obj);
|
||||||
|
gpa.free(base[0..alloc_len]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromObject(story: *Story, object: *Object) !*Object.Number {
|
||||||
|
const data: Object.Number.Data = v: switch (object.tag) {
|
||||||
|
.number => {
|
||||||
|
const number: *Object.Number = @ptrCast(object);
|
||||||
|
switch (number.data) {
|
||||||
|
.boolean => |value| break :v .{ .integer = @intFromBool(value) },
|
||||||
|
.integer => |value| break :v .{ .integer = value },
|
||||||
|
.floating => |value| break :v .{ .floating = value },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => break :v .{ .integer = 1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const obj = Object.Number.create(story, data);
|
||||||
|
// ink_gc_own(story, obj);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arithmeticOp(comptime T: type, op: Story.Opcode, lhs: T, rhs: T) T {
|
||||||
|
switch (op) {
|
||||||
|
.add => return lhs + rhs,
|
||||||
|
.sub => return lhs - rhs,
|
||||||
|
.mul => return lhs * rhs,
|
||||||
|
.div => return @divTrunc(lhs, rhs),
|
||||||
|
.mod => return @mod(lhs, rhs),
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn negate(object: *Object.Number) *Object.Number {
|
||||||
|
switch (object.data) {
|
||||||
|
.integer => |*value| value.* = -value.*,
|
||||||
|
.floating => |*value| value.* = -value.*,
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn performArithmetic(
|
||||||
|
story: *Story,
|
||||||
|
op: Story.Opcode,
|
||||||
|
lhs: *Object.Number,
|
||||||
|
rhs: *Object.Number,
|
||||||
|
) !*Object.Number {
|
||||||
|
if (lhs.data == .floating or rhs.data == .floating) {
|
||||||
|
const _lhs = try Object.Number.fromObject(story, @ptrCast(lhs));
|
||||||
|
const _rhs = try Object.Number.fromObject(story, @ptrCast(rhs));
|
||||||
|
return .create(story, .{
|
||||||
|
.floating = arithmeticOp(f64, op, _lhs.data.floating, _rhs.data.floating),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const _lhs = try Object.Number.fromObject(story, @ptrCast(lhs));
|
||||||
|
const _rhs = try Object.Number.fromObject(story, @ptrCast(rhs));
|
||||||
|
return .create(story, .{
|
||||||
|
.integer = arithmeticOp(i64, op, _lhs.data.integer, _rhs.data.integer),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const String = struct {
|
||||||
|
base: Object,
|
||||||
|
hash: u32, // TODO: Not implemented.
|
||||||
|
length: usize, // TODO: This could probably be u32.
|
||||||
|
bytes: [*]const u8,
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
story: *Story,
|
||||||
|
bytes: []const u8,
|
||||||
|
) error{OutOfMemory}!*Object.String {
|
||||||
|
const gpa = story.allocator;
|
||||||
|
const alloc_len = @sizeOf(String) + bytes.len + 1;
|
||||||
|
const raw = try gpa.alignedAlloc(u8, .of(String), alloc_len);
|
||||||
|
const object: *String = @ptrCast(raw);
|
||||||
|
|
||||||
|
object.* = .{
|
||||||
|
.base = .{
|
||||||
|
.tag = .string,
|
||||||
|
.is_marked = false,
|
||||||
|
.node = .{},
|
||||||
|
},
|
||||||
|
.hash = 0,
|
||||||
|
.length = bytes.len,
|
||||||
|
.bytes = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Point bytes slice to the memory *after* the struct
|
||||||
|
const buf = raw[@sizeOf(String)..][0 .. bytes.len + 1];
|
||||||
|
object.bytes = buf.ptr;
|
||||||
|
@memcpy(buf[0..bytes.len], bytes);
|
||||||
|
buf[bytes.len] = 0;
|
||||||
|
|
||||||
|
story.gc_objects.prepend(&object.base.node);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(object: *Object.String, story: *Story) void {
|
||||||
|
const gpa = story.allocator;
|
||||||
|
const alloc_len = @sizeOf(Object.String) + object.length + 1;
|
||||||
|
const base: [*]align(@alignOf(Object.String)) u8 = @ptrCast(object);
|
||||||
|
gpa.free(base[0..alloc_len]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromObject(story: *Story, object: *Object) !*Object.String {
|
||||||
|
// NOTE: 20 bytes should be enough.
|
||||||
|
const print_buffer_len = 20;
|
||||||
|
var print_buffer: [print_buffer_len]u8 = undefined;
|
||||||
|
|
||||||
|
switch (object.tag) {
|
||||||
|
.number => {
|
||||||
|
const number_object: *Object.Number = @ptrCast(object);
|
||||||
|
const number_bytes = try std.fmt.bufPrint(&print_buffer, "{}", .{
|
||||||
|
number_object.data.floating,
|
||||||
|
});
|
||||||
|
return Object.String.create(story, number_bytes);
|
||||||
|
},
|
||||||
|
.string => return @ptrCast(object),
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn concat(story: *Story, lhs: *String, rhs: *String) !*Object.String {
|
||||||
|
const gpa = story.allocator;
|
||||||
|
const length = lhs.length + rhs.length;
|
||||||
|
const bytes = try gpa.alloc(u8, length + 1);
|
||||||
|
defer gpa.free(bytes);
|
||||||
|
|
||||||
|
@memcpy(bytes[0..lhs.length], lhs.bytes[0..lhs.length]);
|
||||||
|
@memcpy(bytes[lhs.length..], rhs.bytes[0..rhs.length]);
|
||||||
|
//ink_gc_disown(story, INK_OBJ(lhs));
|
||||||
|
//ink_gc_disown(story, INK_OBJ(rhs));
|
||||||
|
return .create(story, bytes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ContentPath = struct {
|
||||||
|
base: Object,
|
||||||
|
name: *Object.String,
|
||||||
|
arity: usize,
|
||||||
|
locals_count: usize,
|
||||||
|
const_pool: []*Object,
|
||||||
|
bytes: []const u8,
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
story: *Story,
|
||||||
|
name: *Object.String,
|
||||||
|
arity: usize,
|
||||||
|
locals_count: usize,
|
||||||
|
const_pool: []*Object,
|
||||||
|
bytes: []const u8,
|
||||||
|
) error{OutOfMemory}!*ContentPath {
|
||||||
|
const gpa = story.allocator;
|
||||||
|
const alloc_len = @sizeOf(ContentPath);
|
||||||
|
const raw = try gpa.alignedAlloc(u8, .of(ContentPath), alloc_len);
|
||||||
|
const object: *ContentPath = @ptrCast(raw);
|
||||||
|
|
||||||
|
object.* = .{
|
||||||
|
.base = .{
|
||||||
|
.tag = .content_path,
|
||||||
|
.is_marked = false,
|
||||||
|
.node = .{},
|
||||||
|
},
|
||||||
|
.name = name,
|
||||||
|
.arity = arity,
|
||||||
|
.locals_count = locals_count,
|
||||||
|
.const_pool = const_pool,
|
||||||
|
.bytes = bytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
story.gc_objects.prepend(&object.base.node);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(obj: *ContentPath, story: *Story) void {
|
||||||
|
const gpa = story.allocator;
|
||||||
|
gpa.free(obj.const_pool);
|
||||||
|
gpa.free(obj.bytes);
|
||||||
|
|
||||||
|
const alloc_len = @sizeOf(ContentPath);
|
||||||
|
const base: [*]align(@alignOf(ContentPath)) u8 = @ptrCast(obj);
|
||||||
|
gpa.free(base[0..alloc_len]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
37
src/root.zig
37
src/root.zig
|
|
@ -1,40 +1,9 @@
|
||||||
const std = @import("std");
|
|
||||||
const tokenizer = @import("tokenizer.zig");
|
const tokenizer = @import("tokenizer.zig");
|
||||||
|
pub const Story = @import("Story.zig");
|
||||||
pub const Ast = @import("Ast.zig");
|
pub const Ast = @import("Ast.zig");
|
||||||
|
|
||||||
pub const Story = struct {
|
|
||||||
pub const LoadOptions = struct {
|
|
||||||
dump_writer: *std.Io.Writer,
|
|
||||||
dump_use_color: bool = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn loadFromString(
|
|
||||||
gpa: std.mem.Allocator,
|
|
||||||
source_bytes: [:0]const u8,
|
|
||||||
options: LoadOptions,
|
|
||||||
) !Story {
|
|
||||||
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
|
||||||
defer arena_allocator.deinit();
|
|
||||||
|
|
||||||
const arena = arena_allocator.allocator();
|
|
||||||
var ast = try Ast.parse(gpa, arena, source_bytes, "<STDIN>", 0);
|
|
||||||
defer ast.deinit(gpa);
|
|
||||||
|
|
||||||
try ast.render(gpa, options.dump_writer, .{
|
|
||||||
.use_color = options.dump_use_color,
|
|
||||||
});
|
|
||||||
if (ast.errors.len > 0) {
|
|
||||||
try ast.renderErrors(gpa, options.dump_writer, .{
|
|
||||||
.use_color = options.dump_use_color,
|
|
||||||
});
|
|
||||||
return error.Invalid;
|
|
||||||
}
|
|
||||||
return .{};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(_: *Story) void {}
|
|
||||||
};
|
|
||||||
|
|
||||||
test {
|
test {
|
||||||
_ = tokenizer;
|
_ = tokenizer;
|
||||||
|
_ = Ast;
|
||||||
|
_ = Story;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue