Implement Bytecode Executor for Kym #19
|
@ -7,10 +7,10 @@ const list = @import("./list.zig");
|
|||
const math = @import("./math.zig");
|
||||
|
||||
pub const Stacking = struct {
|
||||
base_allocator: io.Allocator,
|
||||
page_allocator: io.Allocator,
|
||||
min_page_size: usize,
|
||||
allocations: list.Stack(usize) = .{},
|
||||
pages: list.Stack(Page) = .{},
|
||||
allocations: list.Stack(usize),
|
||||
pages: list.Stack(Page),
|
||||
|
||||
const Page = struct {
|
||||
buffer: []u8,
|
||||
|
@ -49,11 +49,11 @@ pub const Stacking = struct {
|
|||
}
|
||||
|
||||
fn allocate_page(self: *Stacking, page_size: usize) io.AllocationError!*Page {
|
||||
var buffer = try io.allocate_many(self.base_allocator, page_size, u8);
|
||||
var buffer = try io.allocate_many(self.page_allocator, page_size, u8);
|
||||
|
||||
errdefer io.deallocate(self.base_allocator, buffer);
|
||||
errdefer io.deallocate(self.page_allocator, buffer);
|
||||
|
||||
try self.pages.push_one(self.base_allocator, .{
|
||||
try self.pages.push_one(.{
|
||||
.buffer = buffer,
|
||||
.used = 0,
|
||||
});
|
||||
|
@ -83,15 +83,6 @@ pub const Stacking = struct {
|
|||
}.reallocate);
|
||||
}
|
||||
|
||||
pub fn clear_allocations(self: *Stacking) void {
|
||||
for (self.pages.values) |page| {
|
||||
io.deallocate(self.base_allocator, page.buffer);
|
||||
}
|
||||
|
||||
self.pages.deinit(self.base_allocator);
|
||||
self.allocations.deinit(self.base_allocator);
|
||||
}
|
||||
|
||||
fn current_page(self: Stacking) ?*Page {
|
||||
if (self.pages.values.len == 0) {
|
||||
return null;
|
||||
|
@ -99,4 +90,22 @@ pub const Stacking = struct {
|
|||
|
||||
return &self.pages.values[self.pages.values.len - 1];
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Stacking) void {
|
||||
for (self.pages.values) |page| {
|
||||
io.deallocate(self.page_allocator, page.buffer);
|
||||
}
|
||||
|
||||
self.pages.deinit();
|
||||
self.allocations.deinit();
|
||||
}
|
||||
|
||||
pub fn init(allocator: io.Allocator, min_page_size: usize) io.AllocationError!Stacking {
|
||||
return Stacking{
|
||||
.page_allocator = allocator,
|
||||
.allocations = .{.allocator = allocator},
|
||||
.pages = .{.allocator = allocator},
|
||||
.min_page_size = min_page_size,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,6 +14,11 @@ pub const AllocationOptions = struct {
|
|||
|
||||
pub const Allocator = Generator(?[]u8, AllocationOptions);
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
pub const Byte = u8;
|
||||
|
||||
///
|
||||
/// Function pointer coupled with an immutable state context for providing dynamic dispatch over a given `Input` and
|
||||
/// `Output`.
|
||||
|
@ -134,7 +139,7 @@ pub const FixedBuffer = struct {
|
|||
}
|
||||
};
|
||||
|
||||
pub const Writer = Generator(?usize, []const u8);
|
||||
pub const Writer = Generator(?usize, []const Byte);
|
||||
|
||||
pub fn allocate_many(allocator: Allocator, amount: usize, comptime Type: type) AllocationError![]Type {
|
||||
if (@sizeOf(Type) == 0) {
|
||||
|
|
|
@ -9,6 +9,7 @@ const math = @import("./math.zig");
|
|||
///
|
||||
pub fn Stack(comptime Value: type) type {
|
||||
return struct {
|
||||
allocator: io.Allocator,
|
||||
capacity: usize = 0,
|
||||
values: []Value = &.{},
|
||||
|
||||
|
@ -34,12 +35,12 @@ pub fn Stack(comptime Value: type) type {
|
|||
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
|
||||
/// strategy as the one originally used to allocate the current internal buffer.
|
||||
///
|
||||
pub fn deinit(self: *Self, allocator: io.Allocator) void {
|
||||
pub fn deinit(self: *Self) void {
|
||||
if (self.capacity == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
io.deallocate(allocator, self.values.ptr[0 .. self.capacity]);
|
||||
io.deallocate(self.allocator, self.values.ptr[0 .. self.capacity]);
|
||||
|
||||
self.values = &.{};
|
||||
self.capacity = 0;
|
||||
|
@ -69,21 +70,18 @@ pub fn Stack(comptime Value: type) type {
|
|||
/// Growing ahead of multiple push operations is useful when the upper bound of pushes is well-understood, as it
|
||||
/// can reduce the number of allocations required per push.
|
||||
///
|
||||
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
|
||||
/// strategy as the one originally used to allocate the current internal buffer.
|
||||
///
|
||||
pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void {
|
||||
pub fn grow(self: *Self, growth_amount: usize) io.AllocationError!void {
|
||||
const grown_capacity = self.capacity + growth_amount;
|
||||
const values = (try io.allocate_many(allocator, grown_capacity, Value))[0 .. self.values.len];
|
||||
const values = (try io.allocate_many(self.allocator, grown_capacity, Value))[0 .. self.values.len];
|
||||
|
||||
errdefer io.deallocate(allocator, values);
|
||||
errdefer io.deallocate(self.allocator, values);
|
||||
|
||||
if (self.capacity != 0) {
|
||||
for (0 .. self.values.len) |index| {
|
||||
values[index] = self.values[index];
|
||||
}
|
||||
|
||||
io.deallocate(allocator, self.values.ptr[0 .. self.capacity]);
|
||||
io.deallocate(self.allocator, self.values.ptr[0 .. self.capacity]);
|
||||
}
|
||||
|
||||
self.values = values;
|
||||
|
@ -113,14 +111,11 @@ pub fn Stack(comptime Value: type) type {
|
|||
/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the
|
||||
/// internal buffer of `self` when necessary.
|
||||
///
|
||||
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
|
||||
/// strategy as the one originally used to allocate the current internal buffer.
|
||||
///
|
||||
pub fn push_all(self: *Self, allocator: io.Allocator, values: []const Value) io.AllocationError!void {
|
||||
pub fn push_all(self: *Self, values: []const Value) io.AllocationError!void {
|
||||
const new_length = self.values.len + values.len;
|
||||
|
||||
if (new_length > self.capacity) {
|
||||
try self.grow(allocator, values.len + values.len);
|
||||
try self.grow(values.len + values.len);
|
||||
}
|
||||
|
||||
const offset_index = self.values.len;
|
||||
|
@ -139,14 +134,11 @@ pub fn Stack(comptime Value: type) type {
|
|||
/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the
|
||||
/// internal buffer of `self` when necessary.
|
||||
///
|
||||
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
|
||||
/// strategy as the one originally used to allocate the current internal buffer.
|
||||
///
|
||||
pub fn push_many(self: *Self, allocator: io.Allocator, value: Value, amount: usize) io.AllocationError!void {
|
||||
pub fn push_many(self: *Self, value: Value, amount: usize) io.AllocationError!void {
|
||||
const new_length = self.values.len + amount;
|
||||
|
||||
if (new_length >= self.capacity) {
|
||||
try self.grow(allocator, amount + amount);
|
||||
try self.grow(amount + amount);
|
||||
}
|
||||
|
||||
const offset_index = self.values.len;
|
||||
|
@ -165,12 +157,9 @@ pub fn Stack(comptime Value: type) type {
|
|||
/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the
|
||||
/// internal buffer of `self` when necessary.
|
||||
///
|
||||
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
|
||||
/// strategy as the one originally used to allocate the current internal buffer.
|
||||
///
|
||||
pub fn push_one(self: *Self, allocator: io.Allocator, value: Value) io.AllocationError!void {
|
||||
pub fn push_one(self: *Self, value: Value) io.AllocationError!void {
|
||||
if (self.values.len == self.capacity) {
|
||||
try self.grow(allocator, math.max(1, self.capacity));
|
||||
try self.grow(math.max(1, self.capacity));
|
||||
}
|
||||
|
||||
const offset_index = self.values.len;
|
||||
|
@ -183,47 +172,19 @@ pub fn Stack(comptime Value: type) type {
|
|||
}
|
||||
|
||||
///
|
||||
/// Bridge context between a list type implement as part of the list module and an allocator, allowing the list resource
|
||||
/// referenced by the [Writable] instance to be written to directly or virtually via the [io.Writer] interface.
|
||||
///
|
||||
/// *Note* if the given list contains an existing allocation, the provided [io.Allocator] instance must reference the
|
||||
/// same allocation strategy as the one originally used to allocate the list type memory.
|
||||
///
|
||||
pub const Writable = struct {
|
||||
allocator: io.Allocator,
|
||||
pub const ByteStack = Stack(io.Byte);
|
||||
|
||||
list: union (enum) {
|
||||
stack: *ByteStack,
|
||||
},
|
||||
///
|
||||
/// Returns a [io.Writer] instance that binds a reference of `self` to the [write] operation.
|
||||
///
|
||||
pub fn stack_as_writer(self: *ByteStack) io.Writer {
|
||||
return io.Writer.bind(ByteStack, self, struct {
|
||||
fn write(stack: *ByteStack, bytes: []const io.Byte) ?usize {
|
||||
stack.push_all(bytes) catch return null;
|
||||
|
||||
///
|
||||
/// Stack of bytes.
|
||||
///
|
||||
const ByteStack = Stack(u8);
|
||||
|
||||
///
|
||||
/// Returns a [io.Writer] instance that binds a reference of `self` to the [write] operation.
|
||||
///
|
||||
pub fn as_writer(self: *Writable) io.Writer {
|
||||
return io.Writer.bind(Writable, self, struct {
|
||||
fn write(writable: *Writable, bytes: []const u8) ?usize {
|
||||
writable.write(bytes) catch return null;
|
||||
|
||||
return bytes.len;
|
||||
}
|
||||
}.write);
|
||||
}
|
||||
|
||||
///
|
||||
/// Attempts to call the appropriate multi-element writing function for the current list referenced by `self`,
|
||||
/// passing `bytes` along.
|
||||
///
|
||||
/// The function returns [io.AllocationError] if `allocator` could not commit the memory by the list implementation
|
||||
/// referenced by `self`. See the specific implementation details of the respective list type for more information.
|
||||
///
|
||||
pub fn write(self: *Writable, bytes: []const u8) io.AllocationError!void {
|
||||
return switch (self.list) {
|
||||
.stack => |stack| stack.push_all(self.allocator, bytes),
|
||||
};
|
||||
}
|
||||
};
|
||||
return bytes.len;
|
||||
}
|
||||
}.write);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ const std = @import("std");
|
|||
///
|
||||
pub fn Map(comptime index_int: std.builtin.Type.Int, comptime Value: type) type {
|
||||
return struct {
|
||||
allocator: io.Allocator,
|
||||
free_index: Index = 0,
|
||||
count: Index = 0,
|
||||
table: []Entry = &.{},
|
||||
|
@ -73,12 +74,12 @@ pub fn Map(comptime index_int: std.builtin.Type.Int, comptime Value: type) type
|
|||
///
|
||||
/// Fetches the value referenced by `index` in `self`, returning it.
|
||||
///
|
||||
pub fn fetch(self: *Self, index: Index) Value {
|
||||
pub fn fetch(self: *Self, index: Index) *Value {
|
||||
const entry = &self.table[index];
|
||||
|
||||
debug.assert(entry.* == .value);
|
||||
|
||||
return entry.value;
|
||||
return &entry.value;
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -125,9 +126,9 @@ pub fn Map(comptime index_int: std.builtin.Type.Int, comptime Value: type) type
|
|||
/// *Note* if the `table` field of `self` is an allocated slice, `allocator` must reference the same allocation
|
||||
/// strategy as the one originally used to allocate the current table.
|
||||
///
|
||||
pub fn insert(self: *Self, allocator: io.Allocator, value: Value) io.AllocationError!Index {
|
||||
pub fn insert(self: *Self, value: Value) io.AllocationError!Index {
|
||||
if (self.count == self.table.len) {
|
||||
try self.grow(allocator, math.max(1, self.count));
|
||||
try self.grow(self.allocator, math.max(1, self.count));
|
||||
}
|
||||
|
||||
if (self.free_index == self.count) {
|
||||
|
|
|
@ -22,6 +22,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
|
|||
const growth_factor = 0.6;
|
||||
|
||||
return struct {
|
||||
allocator: io.Allocator,
|
||||
count: usize = 0,
|
||||
table: []?Entry = &.{},
|
||||
|
||||
|
@ -101,10 +102,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
|
|||
/// The function returns [AllocationError] instead if `allocator` cannot commit the memory required to grow the
|
||||
/// entry table of `self` when necessary.
|
||||
///
|
||||
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
|
||||
/// `self`.
|
||||
///
|
||||
pub fn assign(self: *Self, allocator: io.Allocator, key: Key, value: Value) io.AllocationError!?Entry {
|
||||
pub fn assign(self: *Self, key: Key, value: Value) io.AllocationError!?Entry {
|
||||
if (self.calculate_load_factor() >= load_max) {
|
||||
const growth_size = @intToFloat(f64, math.max(1, self.table.len)) * growth_factor;
|
||||
|
||||
|
@ -112,7 +110,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
|
|||
return error.OutOfMemory;
|
||||
}
|
||||
|
||||
try self.rehash(allocator, @floatToInt(usize, growth_size));
|
||||
try self.rehash(@floatToInt(usize, growth_size));
|
||||
}
|
||||
|
||||
debug.assert(self.table.len > self.count);
|
||||
|
@ -174,15 +172,12 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
|
|||
///
|
||||
/// To clear all items from the table while preserving the current capacity, see [clear] instead.
|
||||
///
|
||||
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
|
||||
/// `self`.
|
||||
///
|
||||
pub fn deinit(self: *Self, allocator: io.Allocator) void {
|
||||
pub fn deinit(self: *Self) void {
|
||||
if (self.table.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
io.deallocate(allocator, self.table);
|
||||
io.deallocate(self.allocator, self.table);
|
||||
|
||||
self.table = &.{};
|
||||
self.count = 0;
|
||||
|
@ -195,15 +190,12 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
|
|||
/// The function returns [AllocationError] instead if `allocator` cannot commit the memory required to grow the
|
||||
/// entry table of `self` when necessary.
|
||||
///
|
||||
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
|
||||
/// `self`.
|
||||
///
|
||||
pub fn insert(self: *Self, allocator: io.Allocator, key: Key, value: Value) io.AllocationError!bool {
|
||||
pub fn insert(self: *Self, key: Key, value: Value) io.AllocationError!bool {
|
||||
if (self.calculate_load_factor() >= load_max) {
|
||||
const growth_amount = @intToFloat(f64, self.table.len) * growth_factor;
|
||||
const min_size = 1;
|
||||
|
||||
try self.rehash(allocator, self.table.len + math.max(min_size, @floatToInt(usize, growth_amount)));
|
||||
try self.rehash(self.table.len + math.max(min_size, @floatToInt(usize, growth_amount)));
|
||||
}
|
||||
|
||||
debug.assert(self.table.len > self.count);
|
||||
|
@ -246,16 +238,13 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
|
|||
/// greater than `requested_range`, returning [io.AllocationError] if `allocator` cannot commit the memory
|
||||
/// required for the table capacity size.
|
||||
///
|
||||
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
|
||||
/// `self`.
|
||||
///
|
||||
pub fn rehash(self: *Self, allocator: io.Allocator, requested_range: usize) io.AllocationError!void {
|
||||
pub fn rehash(self: *Self, requested_range: usize) io.AllocationError!void {
|
||||
const old_table = self.table;
|
||||
|
||||
self.table = try io.allocate_many(allocator, math.max(requested_range, self.count), ?Entry);
|
||||
self.table = try io.allocate_many(self.allocator, math.max(requested_range, self.count), ?Entry);
|
||||
|
||||
errdefer {
|
||||
io.deallocate(allocator, self.table);
|
||||
io.deallocate(self.allocator, self.table);
|
||||
|
||||
self.table = old_table;
|
||||
}
|
||||
|
@ -272,7 +261,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
|
|||
}
|
||||
}
|
||||
|
||||
io.deallocate(allocator, old_table);
|
||||
io.deallocate(self.allocator, old_table);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,541 @@
|
|||
pub const Environment = @import("./kym/Environment.zig");
|
||||
const Ast = @import("./kym/Ast.zig");
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const types = @import("./kym/types.zig");
|
||||
const file = @import("./file.zig");
|
||||
|
||||
const tokens = @import("./kym/tokens.zig");
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
pub const CallContext = struct {
|
||||
env: *RuntimeEnv,
|
||||
obj: Value,
|
||||
args: []const Value = &.{},
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
pub fn arg_at(self: CallContext, index: Int) RuntimeError!Value {
|
||||
if (!coral.math.is_clamped(index, 0, self.args.len - 1)) {
|
||||
return self.env.check_fail("argument out of bounds");
|
||||
}
|
||||
|
||||
return self.args[@intCast(usize, index)];
|
||||
}
|
||||
};
|
||||
|
||||
const Chunk = struct {
|
||||
env: *RuntimeEnv,
|
||||
opcodes: coral.list.Stack(Opcode),
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
const Opcode = union (enum) {
|
||||
push_nil,
|
||||
push_true,
|
||||
push_false,
|
||||
push_zero,
|
||||
push_number: Float,
|
||||
push_array: i32,
|
||||
push_table: i32,
|
||||
|
||||
not,
|
||||
neg,
|
||||
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
div,
|
||||
|
||||
eql,
|
||||
cgt,
|
||||
clt,
|
||||
cge,
|
||||
cle,
|
||||
};
|
||||
|
||||
pub fn compile_ast(self: *Chunk, ast: Ast) void {
|
||||
for (ast.list_statements()) |statement| {
|
||||
switch (statement) {
|
||||
.return_expression => |return_expression| {
|
||||
try self.compile_expression(return_expression);
|
||||
},
|
||||
|
||||
.return_nothing => {
|
||||
try self.emit(.push_nil);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_expression(self: *Chunk, expression: Ast.Expression) void {
|
||||
switch (expression) {
|
||||
.nil_literal => try self.emit(.push_nil),
|
||||
.true_literal => try self.emit(.push_true),
|
||||
.false_literal => try self.emit(.push_false),
|
||||
.integer_literal => |literal| try self.emit(if (literal == 0) .push_zero else .{.push_integer = literal}),
|
||||
.float_literal => |literal| try self.emit(if (literal == 0) .push_zero else .{.push_float = literal}),
|
||||
.string_literal => |literal| try self.emit(.{.push_object = try self.intern(literal)}),
|
||||
|
||||
.array_literal => |elements| {
|
||||
if (elements.values.len > coral.math.max_int(@typeInfo(Int).Int)) {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
|
||||
for (elements.values) |element_expression| {
|
||||
try self.compile_expression(element_expression);
|
||||
}
|
||||
|
||||
try self.emit(.{.push_array = @intCast(Int, elements.values.len)});
|
||||
},
|
||||
|
||||
.table_literal => |fields| {
|
||||
if (fields.values.len > coral.math.max_int(@typeInfo(Int).Int)) {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
|
||||
for (fields.values) |field| {
|
||||
try self.compile_expression(field.expression);
|
||||
try self.emit(.{.push_object = try self.intern(field.identifier)});
|
||||
}
|
||||
|
||||
try self.emit(.{.push_table = @intCast(Int, fields.values.len)});
|
||||
},
|
||||
|
||||
.binary_operation => |operation| {
|
||||
try self.compile_expression(operation.lhs_expression.*);
|
||||
try self.compile_expression(operation.rhs_expression.*);
|
||||
|
||||
try self.emit(switch (operation.operator) {
|
||||
.addition => .add,
|
||||
.subtraction => .sub,
|
||||
.multiplication => .mul,
|
||||
.divsion => .div,
|
||||
.greater_equals_comparison => .compare_eq,
|
||||
.greater_than_comparison => .compare_gt,
|
||||
.equals_comparison => .compare_ge,
|
||||
.less_than_comparison => .compare_lt,
|
||||
.less_equals_comparison => .compare_le,
|
||||
});
|
||||
},
|
||||
|
||||
.unary_operation => |operation| {
|
||||
try self.compile_expression(operation.expression.*);
|
||||
|
||||
try self.emit(switch (operation.operator) {
|
||||
.boolean_negation => .not,
|
||||
.numeric_negation => .neg,
|
||||
});
|
||||
},
|
||||
|
||||
.grouped_expression => |grouped_expression| {
|
||||
try self.compile_expression(grouped_expression.*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Chunk) void {
|
||||
self.opcodes.deinit(self.env.allocator);
|
||||
}
|
||||
|
||||
pub fn execute(self: *Chunk) RuntimeError!Value {
|
||||
_ = self;
|
||||
|
||||
return Value.nil;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Float = f64;
|
||||
|
||||
pub const Int = i32;
|
||||
|
||||
pub const Objectid = u32;
|
||||
|
||||
pub const RuntimeEnv = struct {
|
||||
output: coral.io.Writer,
|
||||
stack: coral.list.Stack(u64),
|
||||
interned: coral.table.Hashed([]const u8, Objectid, coral.table.string_keyer),
|
||||
objects: coral.slab.Map(@typeInfo(u32).Int, Object),
|
||||
user_allocator: coral.io.Allocator,
|
||||
|
||||
pub const DataSource = struct {
|
||||
name: []const u8,
|
||||
data: []const u8,
|
||||
};
|
||||
|
||||
pub const Limits = struct {
|
||||
stack_max: u32,
|
||||
calls_max: u32,
|
||||
};
|
||||
|
||||
const Object = struct {
|
||||
ref_count: usize,
|
||||
|
||||
state: struct {
|
||||
info: ObjectInfo,
|
||||
userdata: []u8,
|
||||
|
||||
fields: coral.table.Hashed(*Object, *Value, .{
|
||||
.hasher = struct {
|
||||
fn hash(object: *Object) coral.table.Hash {
|
||||
coral.debug.assert(object.state.info.identity == null);
|
||||
|
||||
return coral.table.hash_string(object.state.userdata);
|
||||
}
|
||||
}.hash,
|
||||
|
||||
.comparer = struct {
|
||||
fn compare(object_a: *Object, object_b: *Object) isize {
|
||||
coral.debug.assert(object_a.state.info.identity == null);
|
||||
coral.debug.assert(object_b.state.info.identity == null);
|
||||
|
||||
return coral.io.compare(object_a.state.userdata, object_b.state.userdata);
|
||||
}
|
||||
}.compare,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
pub const ObjectInfo = struct {
|
||||
caller: *const fn (caller: Value, context: CallContext) RuntimeError!Value = default_call,
|
||||
cleaner: *const fn (userdata: []u8) void = default_clean,
|
||||
getter: *const fn (context: CallContext) RuntimeError!Value = default_get,
|
||||
identity: ?*const anyopaque = null,
|
||||
setter: *const fn (context: CallContext) RuntimeError!void = default_set,
|
||||
|
||||
fn default_call(_: Value, context: CallContext) RuntimeError!Value {
|
||||
return context.env.fail(error.BadOperation, "attempt to call non-callable");
|
||||
}
|
||||
|
||||
fn default_clean(_: []u8) void {
|
||||
// Nothing to clean up by default.
|
||||
}
|
||||
|
||||
fn default_get(context: CallContext) RuntimeError!Value {
|
||||
return context.env.fail(error.BadOperation, "attempt to get non-indexable");
|
||||
}
|
||||
|
||||
fn default_set(context: CallContext) RuntimeError!void {
|
||||
return context.env.fail(error.BadOperation, "attempt to set non-indexable");
|
||||
}
|
||||
};
|
||||
|
||||
pub fn call(self: *RuntimeEnv, caller: Value, maybe_index: ?Value, args: []const Value) RuntimeError!RuntimeVar {
|
||||
if (maybe_index) |index| {
|
||||
const callable = try self.get(caller, index);
|
||||
|
||||
defer callable.deinit();
|
||||
|
||||
return switch (callable.value.unpack()) {
|
||||
.objectid => |callable_id| .{
|
||||
.env = self,
|
||||
|
||||
.value = try self.objects.fetch(callable_id).state.info.caller(.{
|
||||
.env = self,
|
||||
.callable = callable.value,
|
||||
.obj = caller,
|
||||
.args = args,
|
||||
}),
|
||||
},
|
||||
|
||||
else => self.fail(error.BadOperation, "attempt to call non-object type"),
|
||||
};
|
||||
}
|
||||
|
||||
return self.bind(try self.objects.fetch(try self.to_objectid(caller)).state.info.caller(.{
|
||||
.env = self,
|
||||
.obj = caller,
|
||||
// .caller = .{.object = self.global_object},
|
||||
.args = args,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn check(
|
||||
self: *RuntimeEnv,
|
||||
condition: bool,
|
||||
runtime_error: RuntimeError,
|
||||
failure_message: []const u8) RuntimeError!void {
|
||||
|
||||
if (condition) {
|
||||
return;
|
||||
}
|
||||
|
||||
return self.fail(runtime_error, failure_message);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *RuntimeEnv) void {
|
||||
self.stack.deinit();
|
||||
}
|
||||
|
||||
pub fn execute_data(self: *RuntimeEnv, allocator: coral.io.Allocator, source: DataSource) RuntimeError!RuntimeVar {
|
||||
var ast = try Ast.init(allocator);
|
||||
|
||||
defer ast.deinit();
|
||||
|
||||
{
|
||||
var tokenizer = tokens.Tokenizer{.source = source.data};
|
||||
|
||||
try ast.parse(&tokenizer);
|
||||
}
|
||||
|
||||
var chunk = Chunk{
|
||||
.env = self,
|
||||
.opcodes = .{.allocator = allocator},
|
||||
};
|
||||
|
||||
const typeid = "<chunk>";
|
||||
|
||||
const script = try self.new_object(allocator, coral.io.bytes_of(&chunk), .{
|
||||
.identity = typeid,
|
||||
|
||||
.cleaner = struct {
|
||||
fn clean(userdata: []const u8) void {
|
||||
@ptrCast(*Chunk, @alignCast(@alignOf(Chunk), userdata)).deinit();
|
||||
}
|
||||
}.clean,
|
||||
|
||||
.caller = struct {
|
||||
fn call(caller: Value, context: CallContext) RuntimeError!Value {
|
||||
_ = caller;
|
||||
|
||||
return (context.env.native_cast(context.obj, typeid, Chunk) catch unreachable).execute();
|
||||
}
|
||||
}.call,
|
||||
});
|
||||
|
||||
defer script.deinit();
|
||||
|
||||
return try self.call(script.value, null, &.{});
|
||||
}
|
||||
|
||||
pub fn execute_file(
|
||||
self: *RuntimeEnv,
|
||||
allocator: coral.io.Allocator,
|
||||
file_system: file.System,
|
||||
file_path: file.Path) RuntimeError!RuntimeVar {
|
||||
|
||||
const readable_file = file_system.open_readable(file_path) catch return error.SystemFailure;
|
||||
|
||||
defer readable_file.close();
|
||||
|
||||
var file_data = coral.list.ByteStack{.allocator = allocator};
|
||||
const file_size = (file_system.query_info(file_path) catch return error.SystemFailure).size;
|
||||
|
||||
try file_data.grow(file_size);
|
||||
|
||||
defer file_data.deinit();
|
||||
|
||||
{
|
||||
var stream_buffer = [_]u8{0} ** 4096;
|
||||
|
||||
if ((coral.io.stream(coral.list.stack_as_writer(&file_data), readable_file.as_reader(), &stream_buffer) catch {
|
||||
return error.SystemFailure;
|
||||
}) != file_size) {
|
||||
return error.SystemFailure;
|
||||
}
|
||||
}
|
||||
|
||||
return try self.execute_data(allocator, .{
|
||||
.name = file_path.to_string() catch return error.SystemFailure,
|
||||
.data = file_data.values,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn fail(self: *RuntimeEnv, runtime_error: RuntimeError, failure_message: []const u8) RuntimeError {
|
||||
// TODO: Call stack and line numbers.
|
||||
coral.utf8.print_formatted(self.output, "{name}@({line}): {message}\n", .{
|
||||
.name = ".ona",
|
||||
.line = @as(u64, 0),
|
||||
.message = failure_message,
|
||||
}) catch return error.SystemFailure;
|
||||
|
||||
return runtime_error;
|
||||
}
|
||||
|
||||
pub fn get(self: *RuntimeEnv, indexable: Value, index: Value) RuntimeError!RuntimeVar {
|
||||
const indexable_object = self.objects.fetch(switch (indexable.unpack()) {
|
||||
.objectid => |indexable_id| indexable_id,
|
||||
else => return self.fail(error.BadOperation, "attempt to index non-indexable type"),
|
||||
});
|
||||
|
||||
return .{
|
||||
.env = self,
|
||||
|
||||
.value = try indexable_object.state.info.getter(.{
|
||||
.env = self,
|
||||
.obj = indexable,
|
||||
.args = &.{index},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(allocator: coral.io.Allocator, output: coral.io.Writer, limits: Limits) RuntimeError!RuntimeEnv {
|
||||
var env = RuntimeEnv{
|
||||
.output = output,
|
||||
.stack = .{.allocator = allocator},
|
||||
.objects = .{.allocator = allocator},
|
||||
.interned = .{.allocator = allocator},
|
||||
.user_allocator = allocator,
|
||||
};
|
||||
|
||||
try env.stack.grow(limits.stack_max * limits.calls_max);
|
||||
|
||||
errdefer env.stack.deinit();
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
pub fn intern(self: *RuntimeEnv, string: []const u8) RuntimeError!Value {
|
||||
return Value.pack_objectid(self.interned.lookup(string) orelse {
|
||||
const interned_value = (try self.new_string(string)).value;
|
||||
|
||||
switch (interned_value.unpack()) {
|
||||
.objectid => |id| coral.debug.assert(try self.interned.insert(string, id)),
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
return interned_value;
|
||||
});
|
||||
}
|
||||
|
||||
pub fn native_cast(self: *RuntimeEnv, castable: Value, id: *const anyopaque, comptime Type: type) RuntimeError!*Type {
|
||||
const object = self.objects.fetch(castable.to_objectid() orelse {
|
||||
return self.fail(error.BadOperation, "attempt to cast non-castable type");
|
||||
});
|
||||
|
||||
const is_expected_type = (object.state.info.identity == id) and (object.state.userdata.len == @sizeOf(Type));
|
||||
|
||||
try self.check(is_expected_type, "invalid object cast: native type");
|
||||
|
||||
return @ptrCast(*Type, @alignCast(@alignOf(Type), object.state.userdata));
|
||||
}
|
||||
|
||||
pub fn new_object(self: *RuntimeEnv, allocator: coral.io.Allocator, userdata: []const u8, info: ObjectInfo) RuntimeError!RuntimeVar {
|
||||
const allocation = try coral.io.allocate_many(allocator, userdata.len, u8);
|
||||
|
||||
errdefer coral.io.deallocate(allocator, allocation);
|
||||
|
||||
coral.io.copy(allocation, userdata);
|
||||
|
||||
const objectid = try self.objects.insert(.{
|
||||
.ref_count = 1,
|
||||
|
||||
.state = .{
|
||||
.info = info,
|
||||
.userdata = allocation,
|
||||
.fields = .{.allocator = allocator},
|
||||
},
|
||||
});
|
||||
|
||||
return .{
|
||||
.env = self,
|
||||
.value = .{.data = @as(usize, 0x7FF8000000000002) | (@as(usize, objectid) << 32)},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn new_string(self: *RuntimeEnv, data: []const u8) RuntimeError!RuntimeVar {
|
||||
return try self.new_object(data, .{
|
||||
.getter = struct {
|
||||
fn get_byte(context: CallContext) RuntimeError!*Value {
|
||||
const string = context.env.string_cast(context.obj) catch unreachable;
|
||||
const index = try context.env.to_int(try context.arg_at(0));
|
||||
|
||||
try context.env.check(coral.math.is_clamped(index, 0, string.len), "index out of string bounds");
|
||||
|
||||
return context.env.new_int(string[@intCast(usize, index)]);
|
||||
}
|
||||
}.get_byte,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
pub const RuntimeError = coral.io.AllocationError || Ast.ParseError || error {
|
||||
BadOperation,
|
||||
BadArgument,
|
||||
SystemFailure,
|
||||
};
|
||||
|
||||
pub const RuntimeVar = struct {
|
||||
value: Value,
|
||||
env: *RuntimeEnv,
|
||||
|
||||
pub fn deinit(self: RuntimeVar) void {
|
||||
switch (self.value.unpack()) {
|
||||
.objectid => |id| {
|
||||
const object = self.env.objects.fetch(id);
|
||||
|
||||
coral.debug.assert(object.ref_count != 0);
|
||||
|
||||
object.ref_count -= 1;
|
||||
|
||||
if (object.ref_count == 0) {
|
||||
object.state.info.cleaner(object.state.userdata);
|
||||
// TODO: Free individual key-value pairs of fields
|
||||
object.state.fields.deinit();
|
||||
coral.io.deallocate(self.env.user_allocator, object.state.userdata);
|
||||
self.env.objects.remove(id);
|
||||
}
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Value = struct {
|
||||
data: u64,
|
||||
|
||||
pub const Unpacked = union (enum) {
|
||||
nil,
|
||||
false,
|
||||
true,
|
||||
number: Float,
|
||||
objectid: Objectid,
|
||||
};
|
||||
|
||||
fn pack_number(float: Float) Value {
|
||||
return @bitCast(Value, float);
|
||||
}
|
||||
|
||||
fn pack_objectid(id: Objectid) Value {
|
||||
return signature.objectid | id;
|
||||
}
|
||||
|
||||
pub const @"false" = @as(Value, nan | 0x0001000000000000);
|
||||
|
||||
const mask = .{
|
||||
.sign = @as(u64, 0x8000000000000000),
|
||||
.exponent = @as(u64, 0x7ff0000000000000),
|
||||
.quiet = @as(u64, 0x0008000000000000),
|
||||
.type = @as(u64, 0x0007000000000000),
|
||||
.signature = @as(u64, 0xffff000000000000),
|
||||
.object = @as(u64, 0x00000000ffffffff),
|
||||
};
|
||||
|
||||
pub const nan = @as(Value, mask.exponent | mask.quiet);
|
||||
|
||||
pub const nil = @as(Value, nan | 0x0003000000000000);
|
||||
|
||||
const signature = .{
|
||||
|
||||
};
|
||||
|
||||
pub const @"true" = @as(Value, nan | 0x0002000000000000);
|
||||
|
||||
pub fn unpack(self: Value) Unpacked {
|
||||
if ((~self.data & mask.exponent) != 0) {
|
||||
return .{.number = @bitCast(Float, self.data)};
|
||||
}
|
||||
|
||||
return switch ((self.data & mask.signature) != 0) {
|
||||
.signature_nan => .{.number = @bitCast(Float, self.data)},
|
||||
.signature_false => .false,
|
||||
.signature_true => .true,
|
||||
.signature_object => @intCast(Objectid, self.data & mask.object),
|
||||
else => return .nil,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,8 +2,6 @@ const coral = @import("coral");
|
|||
|
||||
const tokens = @import("./tokens.zig");
|
||||
|
||||
const types = @import("./types.zig");
|
||||
|
||||
allocator: coral.io.Allocator,
|
||||
arena: coral.arena.Stacking,
|
||||
statements: StatementList,
|
||||
|
@ -39,8 +37,8 @@ pub const Expression = union (enum) {
|
|||
nil_literal,
|
||||
true_literal,
|
||||
false_literal,
|
||||
integer_literal: types.Integer,
|
||||
float_literal: types.Float,
|
||||
integer_literal: []const u8,
|
||||
float_literal: []const u8,
|
||||
string_literal: []const u8,
|
||||
array_literal: coral.list.Stack(Expression),
|
||||
|
||||
|
@ -63,7 +61,15 @@ pub const Expression = union (enum) {
|
|||
},
|
||||
};
|
||||
|
||||
const ExpressionParser = fn (self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression;
|
||||
const ExpressionParser = fn (self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression;
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
pub const ParseError = error {
|
||||
OutOfMemory,
|
||||
BadSyntax,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
|
@ -81,7 +87,7 @@ const UnaryOperator = enum {
|
|||
|
||||
fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime operators: []const BinaryOperator) ExpressionParser {
|
||||
return struct {
|
||||
fn parse(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression {
|
||||
fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
|
||||
var expression = try parse_next(self, tokenizer);
|
||||
|
||||
{
|
||||
|
@ -111,7 +117,7 @@ fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime opera
|
|||
}.parse;
|
||||
}
|
||||
|
||||
fn check_syntax(self: *Self, condition: bool, error_message: []const u8) types.ParseError!void {
|
||||
fn check_syntax(self: *Self, condition: bool, error_message: []const u8) ParseError!void {
|
||||
if (condition) {
|
||||
return;
|
||||
}
|
||||
|
@ -120,11 +126,11 @@ fn check_syntax(self: *Self, condition: bool, error_message: []const u8) types.P
|
|||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.arena.clear_allocations();
|
||||
self.statements.deinit(self.allocator);
|
||||
self.arena.deinit();
|
||||
self.statements.deinit();
|
||||
}
|
||||
|
||||
fn fail_syntax(self: *Self, error_message: []const u8) types.ParseError {
|
||||
fn fail_syntax(self: *Self, error_message: []const u8) ParseError {
|
||||
self.error_message = error_message;
|
||||
|
||||
return error.BadSyntax;
|
||||
|
@ -132,13 +138,9 @@ fn fail_syntax(self: *Self, error_message: []const u8) types.ParseError {
|
|||
|
||||
pub fn init(allocator: coral.io.Allocator) coral.io.AllocationError!Self {
|
||||
return Self{
|
||||
.arena = .{
|
||||
.base_allocator = allocator,
|
||||
.min_page_size = 4096,
|
||||
},
|
||||
|
||||
.arena = try coral.arena.Stacking.init(allocator, 4096),
|
||||
.allocator = allocator,
|
||||
.statements = .{},
|
||||
.statements = .{.allocator = allocator},
|
||||
.error_message = "",
|
||||
};
|
||||
}
|
||||
|
@ -147,7 +149,7 @@ pub fn list_statements(self: Self) []const Statement {
|
|||
return self.statements.values;
|
||||
}
|
||||
|
||||
pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!void {
|
||||
pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
|
||||
self.reset();
|
||||
|
||||
errdefer self.reset();
|
||||
|
@ -159,7 +161,7 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!void {
|
|||
.keyword_return => {
|
||||
try self.check_syntax(has_not_returned_yet, "cannot return more than once per function scope");
|
||||
|
||||
try self.statements.push_one(self.allocator, get_statement: {
|
||||
try self.statements.push_one(get_statement: {
|
||||
if (tokenizer.step(.{.include_newlines = true})) {
|
||||
if (tokenizer.current_token != .newline) {
|
||||
break: get_statement .{.return_expression = try self.parse_expression(tokenizer)};
|
||||
|
@ -199,7 +201,7 @@ const parse_expression = binary_operation_parser(parse_equality, &.{
|
|||
.subtraction,
|
||||
});
|
||||
|
||||
fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression {
|
||||
fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
|
||||
switch (tokenizer.current_token) {
|
||||
.symbol_paren_left => {
|
||||
try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "expected an expression after `(`");
|
||||
|
@ -216,26 +218,13 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expr
|
|||
.integer => |value| {
|
||||
_ = tokenizer.step(.{.include_newlines = false});
|
||||
|
||||
return Expression{
|
||||
.integer_literal = coral.utf8.parse_decimal(types.Integer, value, .{}) catch |parse_error| {
|
||||
return self.fail_syntax(switch (parse_error) {
|
||||
error.BadSyntax => "invalid integer literal",
|
||||
error.IntOverflow => "integer literal is too big",
|
||||
});
|
||||
},
|
||||
};
|
||||
return Expression{.integer_literal = value};
|
||||
},
|
||||
|
||||
.real => |value| {
|
||||
_ = tokenizer.step(.{.include_newlines = false});
|
||||
|
||||
return Expression{
|
||||
.float_literal = coral.utf8.parse_decimal(types.Float, value, .{}) catch |parse_error| {
|
||||
return self.fail_syntax(switch (parse_error) {
|
||||
error.BadSyntax => "invalid float literal",
|
||||
});
|
||||
},
|
||||
};
|
||||
return Expression{.float_literal = value};
|
||||
},
|
||||
|
||||
.string => |value| {
|
||||
|
@ -247,14 +236,17 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expr
|
|||
.symbol_bracket_left => {
|
||||
try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of array literal");
|
||||
|
||||
var expression = Expression{.array_literal = .{}};
|
||||
var expression = Expression{
|
||||
.array_literal = .{
|
||||
.allocator = self.arena.as_allocator(),
|
||||
},
|
||||
};
|
||||
|
||||
coral.debug.assert(expression == .array_literal);
|
||||
|
||||
const allocator = self.arena.as_allocator();
|
||||
const array_average_maximum = 32;
|
||||
|
||||
try expression.array_literal.grow(allocator, array_average_maximum);
|
||||
try expression.array_literal.grow(array_average_maximum);
|
||||
|
||||
while (true) {
|
||||
switch (tokenizer.current_token) {
|
||||
|
@ -269,7 +261,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expr
|
|||
tokenizer.step(.{.include_newlines = false}),
|
||||
"expected `]` or expression after `[`");
|
||||
|
||||
try expression.array_literal.push_one(allocator, try self.parse_expression(tokenizer));
|
||||
try expression.array_literal.push_one(try self.parse_expression(tokenizer));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -278,12 +270,14 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expr
|
|||
.symbol_brace_left => {
|
||||
try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of table literal");
|
||||
|
||||
var expression = Expression{.table_literal = .{}};
|
||||
var expression = Expression{
|
||||
.table_literal = .{
|
||||
.allocator = self.arena.as_allocator(),
|
||||
},
|
||||
};
|
||||
|
||||
coral.debug.assert(expression == .table_literal);
|
||||
|
||||
const allocator = self.arena.as_allocator();
|
||||
|
||||
while (true) {
|
||||
switch (tokenizer.current_token) {
|
||||
.symbol_brace_right => {
|
||||
|
@ -299,7 +293,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expr
|
|||
|
||||
try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end after `=`");
|
||||
|
||||
try expression.table_literal.push_one(allocator, .{
|
||||
try expression.table_literal.push_one(.{
|
||||
.identifier = identifier,
|
||||
.expression = try self.parse_expression(tokenizer),
|
||||
});
|
||||
|
@ -365,5 +359,5 @@ const parse_term = binary_operation_parser(parse_factor, &.{
|
|||
|
||||
pub fn reset(self: *Self) void {
|
||||
self.statements.clear();
|
||||
self.arena.clear_allocations();
|
||||
self.arena.deinit();
|
||||
}
|
||||
|
|
|
@ -1,247 +0,0 @@
|
|||
const Ast = @import("./Ast.zig");
|
||||
|
||||
const Environment = @import("./Environment.zig");
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const types = @import("./types.zig");
|
||||
|
||||
const tokens = @import("./tokens.zig");
|
||||
|
||||
env: *Environment,
|
||||
message_name_len: usize,
|
||||
message_data: Buffer,
|
||||
bytecode_buffer: Buffer,
|
||||
|
||||
const Buffer = coral.list.Stack(u8);
|
||||
|
||||
const Opcode = enum (u8) {
|
||||
ret,
|
||||
|
||||
push_nil,
|
||||
push_true,
|
||||
push_false,
|
||||
push_zero,
|
||||
push_integer,
|
||||
push_float,
|
||||
push_object,
|
||||
push_array,
|
||||
push_table,
|
||||
|
||||
not,
|
||||
neg,
|
||||
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
div,
|
||||
|
||||
compare_eq,
|
||||
compare_gt,
|
||||
compare_lt,
|
||||
compare_ge,
|
||||
compare_le,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
fn clear_error_details(self: *Self) void {
|
||||
coral.debug.assert(self.message_data.values.len >= self.message_name_len);
|
||||
coral.debug.assert(self.message_data.drop(self.message_data.values.len - self.message_name_len));
|
||||
}
|
||||
|
||||
pub fn compile(self: *Self, data: []const u8) types.RuntimeError!void {
|
||||
var ast = try Ast.init(self.env.allocator);
|
||||
|
||||
defer ast.deinit();
|
||||
|
||||
{
|
||||
var tokenizer = tokens.Tokenizer{.source = data};
|
||||
|
||||
ast.parse(&tokenizer) catch |init_error| {
|
||||
if (init_error == error.BadSyntax) {
|
||||
self.clear_error_details();
|
||||
|
||||
var writable_data = coral.list.Writable{
|
||||
.allocator = self.env.allocator,
|
||||
.list = .{.stack = &self.message_data},
|
||||
};
|
||||
|
||||
coral.utf8.print_formatted(writable_data.as_writer(), "@({line}): {name}", .{
|
||||
.line = tokenizer.lines_stepped,
|
||||
.name = ast.error_message,
|
||||
}) catch return error.OutOfMemory;
|
||||
}
|
||||
|
||||
return init_error;
|
||||
};
|
||||
}
|
||||
|
||||
for (ast.list_statements()) |statement| {
|
||||
switch (statement) {
|
||||
.return_expression => |return_expression| {
|
||||
try self.compile_expression(return_expression);
|
||||
try self.emit_opcode(.ret);
|
||||
},
|
||||
|
||||
.return_nothing => {
|
||||
try self.emit_opcode(.push_nil);
|
||||
try self.emit_opcode(.ret);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_expression(self: *Self, expression: Ast.Expression) types.RuntimeError!void {
|
||||
switch (expression) {
|
||||
.nil_literal => try self.emit_opcode(.push_nil),
|
||||
.true_literal => try self.emit_opcode(.push_true),
|
||||
.false_literal => try self.emit_opcode(.push_false),
|
||||
|
||||
.integer_literal => |literal| {
|
||||
if (literal == 0) {
|
||||
try self.emit_opcode(.push_zero);
|
||||
} else {
|
||||
try self.emit_opcode(.push_integer);
|
||||
try self.emit_float(0);
|
||||
}
|
||||
},
|
||||
|
||||
.float_literal => |literal| {
|
||||
if (literal == 0) {
|
||||
try self.emit_opcode(.push_zero);
|
||||
} else {
|
||||
try self.emit_opcode(.push_float);
|
||||
try self.emit_float(literal);
|
||||
}
|
||||
},
|
||||
|
||||
.string_literal => |literal| {
|
||||
try self.emit_opcode(.push_object);
|
||||
try self.emit_object(try self.intern(literal));
|
||||
},
|
||||
|
||||
.array_literal => |elements| {
|
||||
if (elements.values.len > coral.math.max_int(@typeInfo(types.Integer).Int)) {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
|
||||
for (elements.values) |element_expression| {
|
||||
try self.compile_expression(element_expression);
|
||||
}
|
||||
|
||||
try self.emit_opcode(.push_array);
|
||||
try self.emit_integer(@intCast(types.Integer, elements.values.len));
|
||||
},
|
||||
|
||||
.table_literal => |fields| {
|
||||
if (fields.values.len > coral.math.max_int(@typeInfo(types.Integer).Int)) {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
|
||||
for (fields.values) |field| {
|
||||
try self.compile_expression(field.expression);
|
||||
try self.emit_opcode(.push_object);
|
||||
try self.emit_object(try self.intern(field.identifier));
|
||||
}
|
||||
|
||||
try self.emit_opcode(.push_table);
|
||||
try self.emit_integer(@intCast(types.Integer, fields.values.len));
|
||||
},
|
||||
|
||||
.binary_operation => |operation| {
|
||||
try self.compile_expression(operation.lhs_expression.*);
|
||||
try self.compile_expression(operation.rhs_expression.*);
|
||||
|
||||
try self.emit_opcode(switch (operation.operator) {
|
||||
.addition => .add,
|
||||
.subtraction => .sub,
|
||||
.multiplication => .mul,
|
||||
.divsion => .div,
|
||||
.greater_equals_comparison => .compare_eq,
|
||||
.greater_than_comparison => .compare_gt,
|
||||
.equals_comparison => .compare_ge,
|
||||
.less_than_comparison => .compare_lt,
|
||||
.less_equals_comparison => .compare_le,
|
||||
});
|
||||
},
|
||||
|
||||
.unary_operation => |operation| {
|
||||
try self.compile_expression(operation.expression.*);
|
||||
|
||||
try self.emit_opcode(switch (operation.operator) {
|
||||
.boolean_negation => .not,
|
||||
.numeric_negation => .neg,
|
||||
});
|
||||
},
|
||||
|
||||
.grouped_expression => |grouped_expression| {
|
||||
try self.compile_expression(grouped_expression.*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.bytecode_buffer.deinit(self.env.allocator);
|
||||
self.message_data.deinit(self.env.allocator);
|
||||
|
||||
self.message_name_len = 0;
|
||||
}
|
||||
|
||||
pub fn emit_float(self: *Self, float: types.Float) coral.io.AllocationError!void {
|
||||
try self.bytecode_buffer.push_all(self.env.allocator, coral.io.bytes_of(&float));
|
||||
}
|
||||
|
||||
pub fn emit_integer(self: *Self, integer: types.Integer) coral.io.AllocationError!void {
|
||||
try self.bytecode_buffer.push_all(self.env.allocator, coral.io.bytes_of(&integer));
|
||||
}
|
||||
|
||||
pub fn emit_object(self: *Self, object: types.Object) coral.io.AllocationError!void {
|
||||
try self.bytecode_buffer.push_all(self.env.allocator, coral.io.bytes_of(&object));
|
||||
}
|
||||
|
||||
pub fn emit_opcode(self: *Self, opcode: Opcode) coral.io.AllocationError!void {
|
||||
try self.bytecode_buffer.push_one(self.env.allocator, @enumToInt(opcode));
|
||||
}
|
||||
|
||||
pub fn error_details(self: Self) []const u8 {
|
||||
coral.debug.assert(self.message_data.values.len >= self.message_name_len);
|
||||
|
||||
return self.message_data.values;
|
||||
}
|
||||
|
||||
pub fn execute(self: *Self) types.RuntimeError!types.Val {
|
||||
_ = self;
|
||||
// TODO: Implement.
|
||||
|
||||
return .nil;
|
||||
}
|
||||
|
||||
pub fn init(env: *Environment, chunk_name: []const u8) coral.io.AllocationError!Self {
|
||||
var message_data = Buffer{};
|
||||
|
||||
try message_data.push_all(env.allocator, chunk_name);
|
||||
|
||||
errdefer message_data.deinit(env.allocator);
|
||||
|
||||
return Self{
|
||||
.env = env,
|
||||
.message_data = message_data,
|
||||
.bytecode_buffer = .{},
|
||||
.message_name_len = chunk_name.len,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn intern(self: *Self, string: []const u8) coral.io.AllocationError!types.Object {
|
||||
const interned_string = try self.env.intern(string);
|
||||
|
||||
coral.debug.assert(interned_string == .object);
|
||||
|
||||
return interned_string.object;
|
||||
}
|
||||
|
||||
pub fn name(self: Self) []const u8 {
|
||||
coral.debug.assert(self.message_data.values.len >= self.message_name_len);
|
||||
|
||||
return self.message_data.values[0 .. self.message_name_len];
|
||||
}
|
|
@ -1,466 +0,0 @@
|
|||
const Chunk = @import("./Chunk.zig");
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const file = @import("../file.zig");
|
||||
|
||||
const types = @import("./types.zig");
|
||||
|
||||
const tokens = @import("./tokens.zig");
|
||||
|
||||
allocator: coral.io.Allocator,
|
||||
heap: ObjectSlab,
|
||||
global_object: types.Object,
|
||||
interned: InternTable,
|
||||
reporter: Reporter,
|
||||
values: ValueStack,
|
||||
calls: CallStack,
|
||||
|
||||
const CallStack = coral.list.Stack(struct {
|
||||
ip: usize,
|
||||
slots: []types.Val,
|
||||
});
|
||||
|
||||
pub const DataSource = struct {
|
||||
name: []const u8,
|
||||
data: []const u8,
|
||||
};
|
||||
|
||||
pub const ExecuteFileError = file.System.OpenError || coral.io.StreamError || file.ReadError || types.RuntimeError;
|
||||
|
||||
pub const InitOptions = struct {
|
||||
values_max: u32,
|
||||
calls_max: u32,
|
||||
reporter: Reporter,
|
||||
};
|
||||
|
||||
const InternTable = coral.table.Hashed([]const u8, types.Object, coral.table.string_keyer);
|
||||
|
||||
const Object = struct {
|
||||
ref_count: usize,
|
||||
|
||||
state: struct {
|
||||
info: ObjectInfo,
|
||||
userdata: []u8,
|
||||
fields: Fields,
|
||||
},
|
||||
|
||||
const Fields = coral.table.Hashed(*Object, types.Val, .{
|
||||
.hasher = struct {
|
||||
fn hash(object: *Object) coral.table.Hash {
|
||||
coral.debug.assert(object.state.info.identity == null);
|
||||
|
||||
return coral.table.hash_string(object.state.userdata);
|
||||
}
|
||||
}.hash,
|
||||
|
||||
.comparer = struct {
|
||||
fn compare(object_a: *Object, object_b: *Object) isize {
|
||||
coral.debug.assert(object_a.state.info.identity == null);
|
||||
coral.debug.assert(object_b.state.info.identity == null);
|
||||
|
||||
return coral.io.compare(object_a.state.userdata, object_b.state.userdata);
|
||||
}
|
||||
}.compare,
|
||||
});
|
||||
|
||||
pub fn acquire(self: *Object) void {
|
||||
coral.debug.assert(self.ref_count != 0);
|
||||
|
||||
self.ref_count += 1;
|
||||
}
|
||||
};
|
||||
|
||||
pub const ObjectInfo = struct {
|
||||
caller: *const Caller = default_call,
|
||||
deinitializer: *const Deinitializer = default_deinitialize,
|
||||
getter: *const Getter = default_get,
|
||||
identity: ?*const anyopaque = null,
|
||||
setter: *const Setter = default_set,
|
||||
|
||||
pub const CallContext = struct {
|
||||
env: *Self,
|
||||
caller: types.Ref,
|
||||
callable: types.Ref,
|
||||
args: []const types.Ref,
|
||||
};
|
||||
|
||||
pub const Caller = fn (context: CallContext) types.RuntimeError!types.Val;
|
||||
|
||||
pub const DeinitializeContext = struct {
|
||||
env: *Self,
|
||||
obj: types.Ref,
|
||||
};
|
||||
|
||||
pub const Deinitializer = fn (context: DeinitializeContext) void;
|
||||
|
||||
pub const GetContext = struct {
|
||||
env: *Self,
|
||||
indexable: types.Ref,
|
||||
index: types.Ref,
|
||||
};
|
||||
|
||||
pub const Getter = fn (context: GetContext) types.RuntimeError!types.Val;
|
||||
|
||||
pub const SetContext = struct {
|
||||
env: *Self,
|
||||
indexable: types.Ref,
|
||||
index: types.Ref,
|
||||
value: types.Ref,
|
||||
};
|
||||
|
||||
pub const Setter = fn (context: SetContext) types.RuntimeError!void;
|
||||
|
||||
fn default_call(context: CallContext) types.RuntimeError!types.Val {
|
||||
return context.env.fail("attempt to call non-callable");
|
||||
}
|
||||
|
||||
fn default_deinitialize(_: DeinitializeContext) void {
|
||||
// Nothing to deinitialize by default.
|
||||
}
|
||||
|
||||
fn default_get(context: GetContext) types.RuntimeError!types.Val {
|
||||
return context.env.get_field(context.indexable, context.index);
|
||||
}
|
||||
|
||||
fn default_set(context: SetContext) types.RuntimeError!void {
|
||||
return context.env.fail("attempt to set non-indexable");
|
||||
}
|
||||
};
|
||||
|
||||
const ObjectSlab = coral.slab.Map(@typeInfo(u32).Int, Object);
|
||||
|
||||
pub const Reporter = coral.io.Functor(void, []const u8);
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const ValueStack = coral.list.Stack(types.Ref);
|
||||
|
||||
pub fn call(self: *Self, caller: types.Ref, maybe_index: ?types.Ref, args: []const types.Ref) types.RuntimeError!types.Val {
|
||||
if (maybe_index) |index| {
|
||||
try self.check(caller == .object, "invalid type conversion: object");
|
||||
|
||||
const callable = try self.get_object(caller, index);
|
||||
|
||||
defer self.discard(callable);
|
||||
try self.check(callable == .object, "invalid type conversion: object");
|
||||
|
||||
return self.heap.fetch(callable.object).state.info.caller(.{
|
||||
.env = self,
|
||||
.callable = callable.as_ref(),
|
||||
.caller = caller,
|
||||
.args = args,
|
||||
});
|
||||
}
|
||||
|
||||
return self.heap.fetch(caller.object).state.info.caller(.{
|
||||
.env = self,
|
||||
.callable = caller,
|
||||
.caller = .{.object = self.global_object},
|
||||
.args = args,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn check(self: *Self, condition: bool, failure_message: []const u8) !void {
|
||||
if (condition) {
|
||||
return;
|
||||
}
|
||||
|
||||
return self.fail(failure_message);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.object_release(self.global_object);
|
||||
|
||||
{
|
||||
var interned_iterable = InternTable.Iterable{.hashed_map = &self.interned};
|
||||
|
||||
while (interned_iterable.next()) |entry| {
|
||||
self.object_release(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
self.interned.deinit(self.allocator);
|
||||
self.values.deinit(self.allocator);
|
||||
self.calls.deinit(self.allocator);
|
||||
coral.debug.assert(self.heap.is_empty());
|
||||
self.heap.deinit(self.allocator);
|
||||
}
|
||||
|
||||
pub fn discard(self: *Self, val: types.Val) void {
|
||||
switch (val) {
|
||||
.object => |object| self.object_release(object),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_data(self: *Self, source: DataSource) types.RuntimeError!types.Val {
|
||||
const typeid = "<chunk>";
|
||||
|
||||
const Behaviors = struct {
|
||||
fn call(context: ObjectInfo.CallContext) types.RuntimeError!types.Val {
|
||||
return (context.env.native_cast(context.callable, typeid, Chunk) catch unreachable).execute();
|
||||
}
|
||||
|
||||
fn deinitialize(context: ObjectInfo.DeinitializeContext) void {
|
||||
(context.env.native_cast(context.obj, typeid, Chunk) catch unreachable).deinit();
|
||||
}
|
||||
};
|
||||
|
||||
var compiled_chunk = init_compiled_chunk: {
|
||||
var chunk = try Chunk.init(self, source.name);
|
||||
|
||||
errdefer chunk.deinit();
|
||||
|
||||
chunk.compile(source.data) catch |compile_error| {
|
||||
self.reporter.invoke(chunk.error_details());
|
||||
|
||||
return compile_error;
|
||||
};
|
||||
|
||||
break: init_compiled_chunk chunk;
|
||||
};
|
||||
|
||||
const script = try self.new_object(coral.io.bytes_of(&compiled_chunk), .{
|
||||
.identity = typeid,
|
||||
.deinitializer = Behaviors.deinitialize,
|
||||
.caller = Behaviors.call,
|
||||
});
|
||||
|
||||
defer self.discard(script);
|
||||
|
||||
return try self.call(script.as_ref(), null, &.{});
|
||||
}
|
||||
|
||||
pub fn execute_file(self: *Self, fs: file.System, file_path: file.Path) ExecuteFileError!types.Val {
|
||||
const readable_file = try fs.open_readable(file_path);
|
||||
|
||||
defer readable_file.close();
|
||||
|
||||
var file_data = coral.list.Stack(u8){};
|
||||
const file_size = (try fs.query_info(file_path)).size;
|
||||
|
||||
try file_data.grow(self.allocator, file_size);
|
||||
|
||||
defer file_data.deinit(self.allocator);
|
||||
|
||||
{
|
||||
var writable_data = coral.list.Writable{
|
||||
.allocator = self.allocator,
|
||||
.list = .{.stack = &file_data},
|
||||
};
|
||||
|
||||
var stream_buffer = [_]u8{0} ** 4096;
|
||||
|
||||
if ((try coral.io.stream(writable_data.as_writer(), readable_file.as_reader(), &stream_buffer)) != file_size) {
|
||||
return error.ReadFailure;
|
||||
}
|
||||
}
|
||||
|
||||
return try self.execute_data(.{
|
||||
.name = try file_path.to_string(),
|
||||
.data = file_data.values,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn fail(self: *Self, failure_message: []const u8) types.CheckError {
|
||||
self.reporter.invoke(failure_message);
|
||||
|
||||
return error.CheckFailed;
|
||||
}
|
||||
|
||||
pub fn get_field(self: *Self, indexable: types.Ref, field: types.Ref) !types.Val {
|
||||
try self.check(indexable == .object, "invalid type conversion: object");
|
||||
try self.check(field == .object, "invalid type conversion: object");
|
||||
|
||||
const value = get_value: {
|
||||
var field_data = self.heap.fetch(field.object);
|
||||
|
||||
break: get_value self.heap.fetch(indexable.object).state.fields.lookup(&field_data) orelse {
|
||||
return .nil;
|
||||
};
|
||||
};
|
||||
|
||||
if (value == .object) {
|
||||
var value_data = self.heap.fetch(value.object);
|
||||
|
||||
value_data.acquire();
|
||||
self.heap.assign(value.object, value_data);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
pub fn get_object(self: *Self, indexable: types.Ref, index: types.Ref) types.RuntimeError!types.Val {
|
||||
try self.check(indexable == .object, "invalid type conversion: object");
|
||||
|
||||
return self.heap.fetch(indexable.object).state.info.getter(.{
|
||||
.env = self,
|
||||
.indexable = indexable,
|
||||
.index = index,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn init(allocator: coral.io.Allocator, options: InitOptions) !Self {
|
||||
var env = Self{
|
||||
.global_object = 0,
|
||||
.allocator = allocator,
|
||||
.reporter = options.reporter,
|
||||
.interned = .{},
|
||||
.values = .{},
|
||||
.calls = .{},
|
||||
.heap = .{},
|
||||
};
|
||||
|
||||
errdefer {
|
||||
env.values.deinit(allocator);
|
||||
env.calls.deinit(allocator);
|
||||
}
|
||||
|
||||
try env.values.grow(allocator, options.values_max * options.calls_max);
|
||||
try env.calls.grow(allocator, options.calls_max);
|
||||
|
||||
{
|
||||
const globals = try env.new_object(&.{}, .{
|
||||
.identity = "KYM GLOBAL OBJECT OC DO NOT STEAL",
|
||||
});
|
||||
|
||||
coral.debug.assert(globals == .object);
|
||||
|
||||
env.global_object = globals.object;
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
pub fn intern(self: *Self, string: []const u8) coral.io.AllocationError!types.Ref {
|
||||
return .{.object = self.interned.lookup(string) orelse {
|
||||
const reference = try self.new_string(string);
|
||||
|
||||
coral.debug.assert(reference == .object);
|
||||
coral.debug.assert(try self.interned.insert(self.allocator, string, reference.object));
|
||||
|
||||
return .{.object = reference.object};
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn native_cast(self: *Self, castable: types.Ref, id: *const anyopaque, comptime Type: type) types.RuntimeError!*Type {
|
||||
try self.check(castable == .object, "invalid type conversion: object");
|
||||
|
||||
const object = self.heap.fetch(castable.object);
|
||||
const is_expected_type = (object.state.info.identity == id) and (object.state.userdata.len == @sizeOf(Type));
|
||||
|
||||
try self.check(is_expected_type, "invalid object cast: native type");
|
||||
|
||||
return @ptrCast(*Type, @alignCast(@alignOf(Type), object.state.userdata));
|
||||
}
|
||||
|
||||
pub fn new_array(self: *Self) coral.io.AllocationError!types.Val {
|
||||
return try self.new_object(.{
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
pub fn new_object(self: *Self, userdata: []const u8, info: ObjectInfo) coral.io.AllocationError!types.Val {
|
||||
const allocation = try coral.io.allocate_many(self.allocator, userdata.len, u8);
|
||||
|
||||
errdefer coral.io.deallocate(self.allocator, allocation);
|
||||
|
||||
coral.io.copy(allocation, userdata);
|
||||
|
||||
return .{.object = try self.heap.insert(self.allocator, .{
|
||||
.ref_count = 1,
|
||||
|
||||
.state = .{
|
||||
.info = info,
|
||||
.userdata = allocation,
|
||||
.fields = .{},
|
||||
},
|
||||
})};
|
||||
}
|
||||
|
||||
pub fn new_string(self: *Self, data: []const u8) coral.io.AllocationError!types.Val {
|
||||
const Behavior = struct {
|
||||
fn get_byte(context: ObjectInfo.GetContext) types.RuntimeError!types.Val {
|
||||
switch (context.index) {
|
||||
.integer => |integer| {
|
||||
const string = context.env.string_cast(context.indexable) catch unreachable;
|
||||
|
||||
try context.env.check(coral.math.is_clamped(integer, 0, string.len), "index out of string bounds");
|
||||
|
||||
return types.Val{.integer = string[@intCast(usize, integer)]};
|
||||
},
|
||||
|
||||
else => return context.env.fail("attempt to index string with non-integer value"),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return try self.new_object(data, .{
|
||||
.getter = Behavior.get_byte,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn object_release(self: *Self, object: types.Object) void {
|
||||
var data = self.heap.fetch(object);
|
||||
|
||||
coral.debug.assert(data.ref_count != 0);
|
||||
|
||||
data.ref_count -= 1;
|
||||
|
||||
if (data.ref_count == 0) {
|
||||
data.state.info.deinitializer(.{
|
||||
.env = self,
|
||||
.obj = .{.object = object},
|
||||
});
|
||||
|
||||
// TODO: Free individual key-value pairs of fields
|
||||
data.state.fields.deinit(self.allocator);
|
||||
coral.io.deallocate(self.allocator, data.state.userdata);
|
||||
self.heap.remove(object);
|
||||
} else {
|
||||
self.heap.assign(object, data);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_global(self: *Self, global_name: []const u8, value: types.Ref) coral.io.AllocationError!void {
|
||||
try self.globals.assign(self.allocator, global_name, value);
|
||||
}
|
||||
|
||||
pub fn set_object(self: *Self, obj: *Object, index: types.Ref, value: types.Ref) types.RuntimeError!void {
|
||||
return obj.behavior.setter(.{
|
||||
.env = self,
|
||||
.obj = obj,
|
||||
.index = index,
|
||||
.value = value,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn string_cast(self: *Self, value: types.Ref) ![]const u8 {
|
||||
try self.check(value == .object, "invalid type conversion: object");
|
||||
|
||||
const object = self.heap.fetch(value.object);
|
||||
|
||||
try self.check(object.state.info.identity == null, "invalid object cast: string");
|
||||
|
||||
return object.state.userdata;
|
||||
}
|
||||
|
||||
pub fn to_integer(self: *Self, value: types.Ref) !types.Integer {
|
||||
const fail_message = "invalid type conversion: integer";
|
||||
|
||||
switch (value) {
|
||||
.float => |float| {
|
||||
const int = @typeInfo(types.Integer).Int;
|
||||
|
||||
if (coral.math.is_clamped(float, coral.math.min_int(int), coral.math.max_int(int))) {
|
||||
return @floatToInt(types.Integer, float);
|
||||
}
|
||||
},
|
||||
|
||||
.integer => |integer| return integer,
|
||||
else => {},
|
||||
}
|
||||
|
||||
return self.fail(fail_message);
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
const coral = @import("coral");
|
||||
|
||||
pub const CheckError = error {
|
||||
CheckFailed
|
||||
};
|
||||
|
||||
pub const Float = f32;
|
||||
|
||||
pub const Integer = i32;
|
||||
|
||||
pub const Object = u32;
|
||||
|
||||
pub const Primitive = enum {
|
||||
nil,
|
||||
false,
|
||||
true,
|
||||
float,
|
||||
integer,
|
||||
object,
|
||||
};
|
||||
|
||||
pub const Ref = union (Primitive) {
|
||||
nil,
|
||||
false,
|
||||
true,
|
||||
float: Float,
|
||||
integer: Integer,
|
||||
object: Object,
|
||||
};
|
||||
|
||||
pub const ParseError = error {
|
||||
OutOfMemory,
|
||||
BadSyntax,
|
||||
};
|
||||
|
||||
pub const RuntimeError = CheckError || ParseError;
|
||||
|
||||
pub const Val = union (Primitive) {
|
||||
nil,
|
||||
false,
|
||||
true,
|
||||
float: Float,
|
||||
integer: Integer,
|
||||
object: Object,
|
||||
|
||||
pub fn as_ref(self: *const Val) Ref {
|
||||
return switch (self.*) {
|
||||
.nil => .nil,
|
||||
.false => .false,
|
||||
.true => .true,
|
||||
.float => .{.float = self.float},
|
||||
.integer => .{.integer = self.integer},
|
||||
.object => .{.object = self.object},
|
||||
};
|
||||
}
|
||||
};
|
|
@ -17,69 +17,99 @@ const AppManifest = struct {
|
|||
width: u16 = 640,
|
||||
height: u16 = 480,
|
||||
|
||||
pub fn load_script(self: *AppManifest, env: *kym.Environment, fs: file.System, file_path: []const u8) !void {
|
||||
const manifest = try env.execute_file(fs, file.Path.from(&.{file_path}));
|
||||
pub fn load_script(self: *AppManifest, env: *kym.RuntimeEnv, fs: file.System, file_path: []const u8) !void {
|
||||
var manifest = try env.execute_file(heap.allocator, fs, file.Path.from(&.{file_path}));
|
||||
|
||||
defer env.discard(manifest);
|
||||
|
||||
const manifest_ref = manifest.as_ref();
|
||||
defer manifest.deinit();
|
||||
|
||||
{
|
||||
const title = try env.get_field(manifest_ref, try env.intern("title"));
|
||||
var title = try env.get(manifest.value, try env.intern("title"));
|
||||
|
||||
defer env.discard(title);
|
||||
defer title.deinit();
|
||||
|
||||
const title_string = try env.string_cast(title.as_ref());
|
||||
const title_string = try env.string_cast(title.value);
|
||||
|
||||
try env.check(title_string.len <= self.title.len, "`title` cannot exceed 255 bytes in length");
|
||||
coral.io.copy(&self.title, title_string);
|
||||
}
|
||||
|
||||
const u16_int = @typeInfo(u16).Int;
|
||||
const u16_max = coral.math.max_int(@typeInfo(u16).Int);
|
||||
|
||||
{
|
||||
const width = try env.get_field(manifest_ref, try env.intern("width"));
|
||||
const width = try env.get(manifest.value, try env.intern("width"));
|
||||
|
||||
errdefer env.discard(width);
|
||||
errdefer width.deinit();
|
||||
|
||||
self.width = try coral.math.checked_cast(u16_int, try env.to_integer(width.as_ref()));
|
||||
if (width.value.as_number()) |value| {
|
||||
if (value < u16_max) {
|
||||
self.width = @floatToInt(u16, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const height = try env.get_field(manifest_ref, try env.intern("height"));
|
||||
const height = try env.get(manifest.value, try env.intern("height"));
|
||||
|
||||
errdefer env.discard(height);
|
||||
errdefer height.deinit();
|
||||
|
||||
self.width = try coral.math.checked_cast(u16_int, try env.to_integer(height.as_ref()));
|
||||
if (height.value.as_number()) |value| {
|
||||
if (value < u16_max) {
|
||||
self.height = @floatToInt(u16, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn stack_as_log_writer(self: *coral.list.ByteStack) coral.io.Writer {
|
||||
return coral.io.Writer.bind(coral.list.ByteStack, self, struct {
|
||||
fn write(stack: *coral.list.ByteStack, bytes: []const coral.io.Byte) ?usize {
|
||||
var line_written = @as(usize, 0);
|
||||
|
||||
for (bytes) |byte| {
|
||||
if (byte == '\n') {
|
||||
ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", stack.values.len, stack.values.ptr);
|
||||
stack.clear();
|
||||
|
||||
line_written = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
stack.push_one(byte) catch {
|
||||
coral.debug.assert(stack.drop(line_written));
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
line_written += 1;
|
||||
}
|
||||
|
||||
return bytes.len;
|
||||
}
|
||||
}.write);
|
||||
}
|
||||
|
||||
pub fn run_app(base_file_system: file.System) void {
|
||||
defer heap.trace_leaks();
|
||||
|
||||
const Logger = struct {
|
||||
const Self = @This();
|
||||
var log_buffer = coral.list.ByteStack{.allocator = heap.allocator};
|
||||
|
||||
fn log(_: *const Self, message: []const u8) void {
|
||||
ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", message.len, message.ptr);
|
||||
}
|
||||
};
|
||||
defer log_buffer.deinit();
|
||||
|
||||
var script_environment = kym.Environment.init(heap.allocator, .{
|
||||
.values_max = 512,
|
||||
var script_env = kym.RuntimeEnv.init(heap.allocator, stack_as_log_writer(&log_buffer), .{
|
||||
.stack_max = 512,
|
||||
.calls_max = 512,
|
||||
.reporter = kym.Environment.Reporter.bind(Logger, &.{}, Logger.log),
|
||||
}) catch {
|
||||
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to initialize Kym vm\n");
|
||||
};
|
||||
|
||||
defer script_environment.deinit();
|
||||
defer script_env.deinit();
|
||||
|
||||
const app_file_name = "app.ona";
|
||||
var app_manifest = AppManifest{};
|
||||
|
||||
app_manifest.load_script(&script_environment, base_file_system, app_file_name) catch {
|
||||
app_manifest.load_script(&script_env, base_file_system, app_file_name) catch {
|
||||
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to load %s\n", app_file_name);
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue