Native "Syscall" Interface for Kym #21

Merged
kayomn merged 6 commits from kym-native-function into main 2023-07-22 15:03:22 +02:00
17 changed files with 1476 additions and 946 deletions

View File

@ -1,8 +1,8 @@
title = "Afterglow" @log_info("game is loading")
return { return {
title = title, title = "Afterglow",
width = 1280, width = 1280,
height = 800, height = 800,
tick_rate = 60, tick_rate = 60,

View File

@ -74,7 +74,7 @@ pub const Stacking = struct {
const aligned_size = (size + alignment - 1) & ~(alignment - 1); const aligned_size = (size + alignment - 1) & ~(alignment - 1);
if (self.pages.values.len == 0) { if (self.pages.values.len == 0) {
const page = try self.allocate_page(math.max(self.min_page_size, aligned_size)); const page = try self.allocate_page(@max(self.min_page_size, aligned_size));
page.used = size; page.used = size;
@ -84,7 +84,7 @@ pub const Stacking = struct {
var page = self.current_page() orelse unreachable; var page = self.current_page() orelse unreachable;
if (page.available() <= aligned_size) { if (page.available() <= aligned_size) {
page = try self.allocate_page(math.max(self.min_page_size, aligned_size)); page = try self.allocate_page(@max(self.min_page_size, aligned_size));
} }
debug.assert(page.available() >= size); debug.assert(page.available() >= size);

View File

@ -96,7 +96,7 @@ pub const FixedBuffer = struct {
} }
pub fn write(self: *FixedBuffer, bytes: []const Byte) usize { pub fn write(self: *FixedBuffer, bytes: []const Byte) usize {
const writable = math.min(self.bytes.len, bytes.len); const writable = @min(self.bytes.len, bytes.len);
copy(self.bytes, bytes); copy(self.bytes, bytes);
@ -116,18 +116,32 @@ pub fn Functor(comptime Output: type, comptime Input: type) type {
pub fn bind(comptime State: type, state: *const State, comptime invoker: fn (capture: *const State, input: Input) Output) Self { pub fn bind(comptime State: type, state: *const State, comptime invoker: fn (capture: *const State, input: Input) Output) Self {
const is_zero_aligned = @alignOf(State) == 0; const is_zero_aligned = @alignOf(State) == 0;
const Invoker = struct {
fn invoke(context: *const anyopaque, input: Input) Output {
if (is_zero_aligned) {
return invoker(@ptrCast(context), input);
}
return invoker(@ptrCast(@alignCast(context)), input);
}
};
return .{ return .{
.context = if (is_zero_aligned) state else @ptrCast(state), .context = if (is_zero_aligned) state else @ptrCast(state),
.invoker = Invoker.invoke,
};
}
.invoker = struct { pub fn from(comptime invoker: fn (input: Input) Output) Self {
fn invoke(context: *const anyopaque, input: Input) Output { const Invoker = struct {
if (is_zero_aligned) { fn invoke(_: *const anyopaque, input: Input) Output {
return invoker(@ptrCast(context), input); return invoker(input);
} }
};
return invoker(@ptrCast(@alignCast(context)), input); return .{
} .context = &.{},
}.invoke, .invoker = Invoker.invoke,
}; };
} }
@ -162,6 +176,19 @@ pub fn Generator(comptime Output: type, comptime Input: type) type {
}; };
} }
pub fn from(comptime invoker: fn (input: Input) Output) Self {
const Invoker = struct {
fn invoke(_: *const anyopaque, input: Input) Output {
return invoker(input);
}
};
return .{
.context = &.{},
.invoker = Invoker.invoke,
};
}
pub fn invoke(self: Self, input: Input) Output { pub fn invoke(self: Self, input: Input) Output {
return self.invoker(self.context, input); return self.invoker(self.context, input);
} }
@ -223,6 +250,21 @@ pub fn copy(target: []Byte, source: []const Byte) void {
} }
} }
pub fn compare(this: []const Byte, that: []const Byte) isize {
const range = @min(this.len, that.len);
var index: usize = 0;
while (index < range) : (index += 1) {
const difference = @as(isize, @intCast(this[index])) - @as(isize, @intCast(that[index]));
if (difference != 0) {
return difference;
}
}
return @as(isize, @intCast(this.len)) - @as(isize, @intCast(that.len));
}
pub fn ends_with(target: []const Byte, match: []const Byte) bool { pub fn ends_with(target: []const Byte, match: []const Byte) bool {
if (target.len < match.len) { if (target.len < match.len) {
return false; return false;

View File

@ -49,6 +49,28 @@ pub fn Stack(comptime Value: type) type {
}; };
} }
pub fn pack(self: *Self) io.AllocationError!void {
const packed_size = self.values.len;
const buffer = try self.allocator.reallocate(null, @sizeOf(Value) * self.values.len);
io.copy(buffer, io.bytes_of(self.values));
if (self.capacity != 0) {
self.allocator.deallocate(self.values.ptr[0 .. self.capacity]);
}
self.values = @as([*]Value, @ptrCast(@alignCast(buffer)))[0 .. packed_size];
self.capacity = packed_size;
}
pub fn peek(self: Self) ?Value {
if (self.values.len == 0) {
return null;
}
return self.values[self.values.len - 1];
}
pub fn pop(self: *Self) ?Value { pub fn pop(self: *Self) ?Value {
if (self.values.len == 0) { if (self.values.len == 0) {
return null; return null;
@ -61,9 +83,25 @@ pub fn Stack(comptime Value: type) type {
return self.values[last_index]; return self.values[last_index];
} }
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(values.len + values.len);
}
const offset_index = self.values.len;
self.values = self.values.ptr[0 .. new_length];
for (0 .. values.len) |index| {
self.values[offset_index + index] = values[index];
}
}
pub fn push_one(self: *Self, value: Value) io.AllocationError!void { pub fn push_one(self: *Self, value: Value) io.AllocationError!void {
if (self.values.len == self.capacity) { if (self.values.len == self.capacity) {
try self.grow(math.max(1, self.capacity)); try self.grow(@max(1, self.capacity));
} }
const offset_index = self.values.len; const offset_index = self.values.len;
@ -74,3 +112,11 @@ pub fn Stack(comptime Value: type) type {
} }
}; };
} }
pub fn stack_as_writer(self: *ByteStack) io.Writer {
return io.Writer.bind(ByteStack, self, write_stack);
}
fn write_stack(stack: *ByteStack, bytes: []const io.Byte) ?usize {
return stack.push_all(bytes) catch null;
}

View File

@ -55,6 +55,23 @@ pub fn Slab(comptime Value: type) type {
} }
} }
pub fn insert_at(self: *Self, key: usize, value: Value) bool {
if (self.entries.values.len < key) {
return false;
}
const entry = &self.entries.values[key - 1];
if (entry.* == .value) {
return false;
}
self.next_index = entry.next_index;
entry.* = .{.value = value};
return true;
}
pub fn make(allocator: io.Allocator) Self { pub fn make(allocator: io.Allocator) Self {
return .{ return .{
.next_index = 0, .next_index = 0,
@ -86,8 +103,18 @@ pub fn Slab(comptime Value: type) type {
}; };
} }
pub fn StringTable(comptime Value: type) type {
return Table([]const io.Byte, Value, .{
.hash = hash_string,
.match = io.equals,
});
}
pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTraits(Key)) type { pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTraits(Key)) type {
kayomn marked this conversation as resolved
Review

A lot of duplication with @typeInfo(usize).Int and getting min / max limits of int types in general. These could be reduced into a constants declared in function body to reduce repetition.

A lot of duplication with `@typeInfo(usize).Int` and getting min / max limits of int types in general. These could be reduced into a constants declared in function body to reduce repetition.
const load_max = 0.75; const load_max = 0.75;
const hash_int = @typeInfo(usize).Int;
const max_int = math.max_int(hash_int);
const min_int = math.min_int(hash_int);
return struct { return struct {
allocator: io.Allocator, allocator: io.Allocator,
@ -99,8 +126,8 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
value: Value, value: Value,
fn write_into(self: Entry, entry_table: []?Entry) bool { fn write_into(self: Entry, entry_table: []?Entry) bool {
const hash_max = math.min(math.max_int(@typeInfo(usize).Int), entry_table.len); const hash_max = @min(max_int, entry_table.len);
var hashed_key = math.wrap(traits.hash(self.key), math.min_int(@typeInfo(usize).Int), hash_max); var hashed_key = math.wrap(traits.hash(self.key), min_int, hash_max);
var iterations = @as(usize, 0); var iterations = @as(usize, 0);
while (true) : (iterations += 1) { while (true) : (iterations += 1) {
@ -126,7 +153,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
pub const Iterable = struct { pub const Iterable = struct {
table: *Self, table: *Self,
iterations: usize = 0, iterations: usize,
pub fn next(self: *Iterable) ?Entry { pub fn next(self: *Iterable) ?Entry {
while (self.iterations < self.table.entries.len) { while (self.iterations < self.table.entries.len) {
@ -143,14 +170,40 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
const Self = @This(); const Self = @This();
pub fn as_iterable(self: *Self) Iterable {
return .{
.table = self,
.iterations = 0,
};
}
pub fn remove(self: *Self, key: Key) ?Entry {
const hash_max = @min(max_int, self.entries.len);
var hashed_key = math.wrap(traits.hash(key), min_int, hash_max);
while (true) {
const entry = &(self.entries[hashed_key] orelse continue);
if (traits.match(entry.key, key)) {
const original_entry = entry.*;
self.entries[hashed_key] = null;
return original_entry;
}
hashed_key = (hashed_key +% 1) % hash_max;
}
}
pub fn replace(self: *Self, key: Key, value: Value) io.AllocationError!?Entry { pub fn replace(self: *Self, key: Key, value: Value) io.AllocationError!?Entry {
try self.rehash(load_max); try self.rehash(load_max);
debug.assert(self.entries.len > self.count); debug.assert(self.entries.len > self.count);
{ {
const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len); const hash_max = @min(max_int, self.entries.len);
var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max); var hashed_key = math.wrap(traits.hash(key), min_int, hash_max);
while (true) { while (true) {
const entry = &(self.entries[hashed_key] orelse { const entry = &(self.entries[hashed_key] orelse {
@ -223,8 +276,8 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
return null; return null;
} }
const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len); const hash_max = @min(max_int, self.entries.len);
var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max); var hashed_key = math.wrap(traits.hash(key), min_int, hash_max);
var iterations = @as(usize, 0); var iterations = @as(usize, 0);
while (iterations < self.count) : (iterations += 1) { while (iterations < self.count) : (iterations += 1) {
@ -253,7 +306,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
return; return;
} }
const min_count = math.max(1, self.count); const min_count = @max(1, self.count);
const table_size = min_count * 2; const table_size = min_count * 2;
const allocation = @as([*]?Entry, @ptrCast(@alignCast(try self.allocator.reallocate(null, @sizeOf(?Entry) * table_size))))[0 .. table_size]; const allocation = @as([*]?Entry, @ptrCast(@alignCast(try self.allocator.reallocate(null, @sizeOf(?Entry) * table_size))))[0 .. table_size];
@ -296,8 +349,3 @@ fn hash_string(key: []const io.Byte) usize {
return hash_code; return hash_code;
} }
pub const string_table_traits = TableTraits([]const io.Byte){
.hash = hash_string,
.match = io.equals,
};

View File

@ -1,7 +1,24 @@
const std = @import("std"); const std = @import("std");
pub fn max(a: anytype, b: anytype) @TypeOf(a, b) { pub fn Int(comptime int: std.builtin.Type.Int) type {
return @max(a, b); return @Type(.{.Int = int});
}
pub fn clamp(value: anytype, lower: anytype, upper: anytype) @TypeOf(value, lower, upper) {
return @max(lower, @min(upper, value));
}
pub fn clamped_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) Int(dest_int) {
const Value = @TypeOf(value);
return switch (@typeInfo(Value)) {
.Int => |int| switch (int.signedness) {
.signed => @intCast(clamp(value, min_int(dest_int), max_int(dest_int))),
.unsigned => @intCast(@min(value, max_int(dest_int))),
},
else => @compileError("`" ++ @typeName(Value) ++ "` cannot be cast to an int"),
};
} }
pub fn max_int(comptime int: std.builtin.Type.Int) comptime_int { pub fn max_int(comptime int: std.builtin.Type.Int) comptime_int {
@ -12,10 +29,6 @@ pub fn max_int(comptime int: std.builtin.Type.Int) comptime_int {
return (1 << (bit_count - @intFromBool(int.signedness == .signed))) - 1; return (1 << (bit_count - @intFromBool(int.signedness == .signed))) - 1;
} }
pub fn min(a: anytype, b: anytype) @TypeOf(a, b) {
return @min(a, b);
}
pub fn min_int(comptime int: std.builtin.Type.Int) comptime_int { pub fn min_int(comptime int: std.builtin.Type.Int) comptime_int {
if (int.signedness == .unsigned) return 0; if (int.signedness == .unsigned) return 0;

View File

@ -126,7 +126,7 @@ pub const DecimalFormat = struct {
switch (@typeInfo(ValueType)) { switch (@typeInfo(ValueType)) {
.Int => |int| { .Int => |int| {
const radix = 10; const radix = 10;
var buffer = [_]u8{0} ** (1 + math.max(int.bits, 1)); var buffer = [_]u8{0} ** (1 + @max(int.bits, 1));
var buffer_start = buffer.len - 1; var buffer_start = buffer.len - 1;
{ {

View File

@ -13,42 +13,57 @@ pub const Manifest = struct {
tick_rate: f32 = 60.0, tick_rate: f32 = 60.0,
pub fn load(self: *Manifest, env: *kym.RuntimeEnv, file_access: file.Access) kym.RuntimeError!void { pub fn load(self: *Manifest, env: *kym.RuntimeEnv, file_access: file.Access) kym.RuntimeError!void {
kayomn marked this conversation as resolved
Review

Resolve TODOs in the function body.

Resolve TODOs in the function body.
const manifest = try env.execute_file(file_access, file.Path.from(&.{"app.ona"})); const manifest_ref = try env.execute_file(file_access, file.Path.from(&.{"app.ona"}));
defer env.discard(manifest); defer env.discard(manifest_ref);
const title = try env.get_field(manifest, "title");
defer env.discard(title);
const title_string = try env.get_string(title);
const width = @as(u16, get: { const width = @as(u16, get: {
const ref = try env.get_field(manifest, "width"); const ref = try kym.get_field(env, manifest_ref, "width");
defer env.discard(ref); defer env.discard(ref);
break: get @intFromFloat(env.get_float(ref) catch @as(f64, @floatFromInt(self.width))); if (env.unbox(ref).expect_number()) |number| {
if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) {
break: get @intFromFloat(number);
}
}
break: get self.width;
}); });
const height = @as(u16, get: { const height = @as(u16, get: {
const ref = try env.get_field(manifest, "height"); const ref = try kym.get_field(env, manifest_ref, "height");
defer env.discard(ref); defer env.discard(ref);
break: get @intFromFloat(env.get_float(ref) catch @as(f64, @floatFromInt(self.height))); if (env.unbox(ref).expect_number()) |number| {
if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) {
break: get @intFromFloat(number);
}
}
break: get self.height;
}); });
const tick_rate = @as(f32, get: { const tick_rate = @as(f32, get: {
const ref = try env.get_field(manifest, "tick_rate"); const ref = try kym.get_field(env, manifest_ref, "tick_rate");
defer env.discard(ref); defer env.discard(ref);
break: get @floatCast(env.get_float(ref) catch self.tick_rate); if (env.unbox(ref).expect_number()) |number| {
break: get @floatCast(number);
}
break: get self.tick_rate;
}); });
{ {
const limited_title_len = coral.math.min(title_string.len, self.title.len); const title_ref = try kym.get_field(env, manifest_ref, "title");
defer env.discard(title_ref);
const title_string = env.unbox(title_ref).expect_string() orelse "";
const limited_title_len = @min(title_string.len, self.title.len);
coral.io.copy(&self.title, title_string[0 .. limited_title_len]); coral.io.copy(&self.title, title_string[0 .. limited_title_len]);
coral.io.zero(self.title[limited_title_len .. self.title.len]); coral.io.zero(self.title[limited_title_len .. self.title.len]);
@ -60,69 +75,29 @@ pub const Manifest = struct {
} }
}; };
pub const LogSeverity = enum { pub fn log_info(message: []const coral.io.Byte) void {
kayomn marked this conversation as resolved
Review

Potentially (but very unlikely to ever trigger) unsafe length cast.

Could do with clamping the length to the max buffer to be safe.

Potentially (but very unlikely to ever trigger) unsafe length cast. Could do with clamping the length to the max buffer to be safe.
info, ext.SDL_LogInfo(
warn, ext.SDL_LOG_CATEGORY_APPLICATION,
fail, "%.*s",
}; coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
kayomn marked this conversation as resolved Outdated

Potentially (but very unlikely to ever trigger) unsafe length cast.

Could do with clamping the length to the max buffer to be safe.

Potentially (but very unlikely to ever trigger) unsafe length cast. Could do with clamping the length to the max buffer to be safe.
message.ptr,
);
}
kayomn marked this conversation as resolved Outdated

Potentially (but very unlikely to ever trigger) unsafe length cast.

Could do with clamping the length to the max buffer to be safe.

Potentially (but very unlikely to ever trigger) unsafe length cast. Could do with clamping the length to the max buffer to be safe.
pub const WritableLog = struct { pub fn log_warn(message: []const coral.io.Byte) void {
severity: LogSeverity, ext.SDL_LogWarn(
write_buffer: coral.list.ByteStack, ext.SDL_LOG_CATEGORY_APPLICATION,
"%.*s",
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
message.ptr,
);
}
pub fn as_writer(self: *WritableLog) coral.io.Writer { pub fn log_fail(message: []const coral.io.Byte) void {
return coral.io.Writer.bind(WritableLog, self, struct { ext.SDL_LogError(
fn write(writable_log: *WritableLog, bytes: []const coral.io.Byte) ?usize { ext.SDL_LOG_CATEGORY_APPLICATION,
writable_log.write(bytes) catch return null; "%.*s",
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
return bytes.len; message.ptr,
} );
}.write); }
}
pub fn free(self: *WritableLog) void {
self.write_buffer.free();
}
pub fn make(log_severity: LogSeverity, allocator: coral.io.Allocator) WritableLog {
return .{
.severity = log_severity,
.write_buffer = coral.list.ByteStack.make(allocator),
};
}
pub fn write(self: *WritableLog, bytes: []const coral.io.Byte) coral.io.AllocationError!void {
const format_string = "%.*s";
var line_written = @as(usize, 0);
for (bytes) |byte| {
if (byte == '\n') {
ext.SDL_LogError(
ext.SDL_LOG_CATEGORY_APPLICATION,
format_string,
self.write_buffer.values.len,
self.write_buffer.values.ptr);
self.write_buffer.clear();
line_written = 0;
continue;
}
try self.write_buffer.push_one(byte);
line_written += 1;
}
if (self.write_buffer.values.len == 0) {
ext.SDL_LogError(
ext.SDL_LOG_CATEGORY_APPLICATION,
format_string,
self.write_buffer.values.len,
self.write_buffer.values.ptr);
self.write_buffer.clear();
}
}
};

View File

@ -156,15 +156,15 @@ pub const allocator = coral.io.Allocator.bind(Context, &context, .{
pub fn trace_leaks() void { pub fn trace_leaks() void {
switch (builtin.mode) { switch (builtin.mode) {
.Debug, .ReleaseSafe => { .Debug, .ReleaseSafe => {
var current_allocation_info = context.allocation_info_head; var current_node = context.head;
while (current_allocation_info) |allocation_info| : (current_allocation_info = allocation_info.next_info) { while (current_node) |node| : (current_node = node.next) {
std.debug.print("{d} byte leak at 0x{x} detected:\n", .{ std.debug.print("{d} byte leak at 0x{x} detected:\n", .{
allocation_info.size, node.size,
@as(usize, allocation_info) + @sizeOf(AllocationNode), @intFromPtr(node) + @sizeOf(AllocationNode),
}); });
allocation_info.trace.dump(); node.trace.dump();
} }
}, },

File diff suppressed because it is too large Load Diff

View File

@ -5,17 +5,22 @@ const tokens = @import("./tokens.zig");
allocator: coral.io.Allocator, allocator: coral.io.Allocator,
arena: coral.arena.Stacking, arena: coral.arena.Stacking,
statements: Statement.List, statements: Statement.List,
error_message: []const u8, error_message: []const coral.io.Byte,
pub const Expression = union (enum) { pub const Expression = union (enum) {
nil_literal, nil_literal,
true_literal, true_literal,
false_literal, false_literal,
number_literal: []const u8, number_literal: []const coral.io.Byte,
string_literal: []const u8, string_literal: []const coral.io.Byte,
table_literal: NamedList, table_literal: NamedList,
grouped_expression: *Expression, grouped_expression: *Expression,
get_local: []const u8, get_local: []const coral.io.Byte,
call_system: struct {
identifier: []const coral.io.Byte,
argument_expressions: List,
},
binary_operation: struct { binary_operation: struct {
operator: BinaryOperator, operator: BinaryOperator,
@ -55,7 +60,7 @@ pub const Expression = union (enum) {
}; };
pub const NamedList = coral.list.Stack(struct { pub const NamedList = coral.list.Stack(struct {
identifier: []const u8, identifier: []const coral.io.Byte,
expression: Expression, expression: Expression,
}); });
@ -80,6 +85,11 @@ pub const Statement = union (enum) {
expression: Expression, expression: Expression,
}, },
call_system: struct {
identifier: []const coral.io.Byte,
argument_expressions: Expression.List,
},
const List = coral.list.Stack(Statement); const List = coral.list.Stack(Statement);
}; };
@ -88,39 +98,40 @@ const UnaryOperator = enum {
numeric_negation, numeric_negation,
}; };
fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime operators: []const Expression.BinaryOperator) ExpressionParser { fn binary_operation_parser(
return struct { comptime parse_next: ExpressionParser,
comptime operators: []const Expression.BinaryOperator) ExpressionParser {
const BinaryOperationParser = struct {
fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression { fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
const allocator = self.arena.as_allocator();
var expression = try parse_next(self, tokenizer); var expression = try parse_next(self, tokenizer);
{ inline for (operators) |operator| {
const allocator = self.arena.as_allocator(); const token = comptime operator.token();
inline for (operators) |operator| { if (tokenizer.is_token(coral.io.tag_of(token))) {
const token = comptime operator.token(); tokenizer.step();
if (tokenizer.is_token(coral.io.tag_of(token))) { if (tokenizer.token == null) {
tokenizer.step(); return self.raise("expected other half of expression after `" ++ comptime token.text() ++ "`");
if (tokenizer.token == null) {
return self.raise(
"expected right-hand side of expression after `" ++ comptime token.text() ++ "`");
}
expression = .{
.binary_operation = .{
.operator = operator,
.lhs_expression = try coral.io.allocate_one(allocator, expression),
.rhs_expression = try coral.io.allocate_one(allocator, try parse_next(self, tokenizer)),
},
};
} }
expression = .{
.binary_operation = .{
.operator = operator,
.lhs_expression = try coral.io.allocate_one(allocator, expression),
.rhs_expression = try coral.io.allocate_one(allocator, try parse_next(self, tokenizer)),
},
};
} }
} }
return expression; return expression;
} }
}.parse; };
return BinaryOperationParser.parse;
} }
pub fn free(self: *Self) void { pub fn free(self: *Self) void {
@ -150,9 +161,12 @@ pub fn list_statements(self: Self) []const Statement {
pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void { pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
self.free(); self.free();
const allocator = self.arena.as_allocator();
var has_returned = false; var has_returned = false;
while (true) { while (true) {
const no_effect_message = "statement has no effect";
tokenizer.skip(.newline); tokenizer.skip(.newline);
switch (tokenizer.token orelse return) { switch (tokenizer.token orelse return) {
@ -178,11 +192,9 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
has_returned = true; has_returned = true;
}, },
.local => |identifier| { .identifier => |identifier| {
tokenizer.step(); tokenizer.step();
const no_effect_message = "statement has no effect";
switch (tokenizer.token orelse return self.raise(no_effect_message)) { switch (tokenizer.token orelse return self.raise(no_effect_message)) {
.newline => return self.raise(no_effect_message), .newline => return self.raise(no_effect_message),
@ -209,6 +221,45 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
} }
}, },
.special_identifier => |identifier| {
kayomn marked this conversation as resolved Outdated

Inaccurate name for syscall.

Inaccurate name for syscall.
tokenizer.step();
switch (tokenizer.token orelse return self.raise(no_effect_message)) {
.newline => return self.raise(no_effect_message),
.symbol_paren_left => {
tokenizer.step();
var expressions_list = Expression.List.make(allocator);
while (true) {
if (tokenizer.is_token(.symbol_paren_right)) {
break;
}
try expressions_list.push_one(try self.parse_expression(tokenizer));
switch (tokenizer.token orelse return self.raise("unexpected end after after `(`")) {
.symbol_comma => continue,
.symbol_paren_right => break,
else => return self.raise("expected `)` or argument after `(`"),
}
}
tokenizer.step();
try self.statements.push_one(.{
.call_system = .{
.argument_expressions = expressions_list,
.identifier = identifier,
},
});
},
else => return self.raise("expected `=` after local"),
}
},
else => return self.raise("invalid statement"), else => return self.raise("invalid statement"),
} }
} }
@ -252,6 +303,24 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
return Expression{.grouped_expression = try coral.io.allocate_one(allocator, expression)}; return Expression{.grouped_expression = try coral.io.allocate_one(allocator, expression)};
}, },
.keyword_nil => {
tokenizer.step();
return .nil_literal;
},
.keyword_true => {
tokenizer.step();
return .true_literal;
},
.keyword_false => {
tokenizer.step();
return .false_literal;
},
.number => |value| { .number => |value| {
tokenizer.step(); tokenizer.step();
@ -264,7 +333,49 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
return Expression{.string_literal = value}; return Expression{.string_literal = value};
}, },
.local => |identifier| { .special_identifier => |identifier| {
kayomn marked this conversation as resolved Outdated

Inaccurate name for syscall.

Inaccurate name for syscall.
tokenizer.skip(.newline);
var expression_list = Expression.List.make(allocator);
while (true) {
switch (tokenizer.token orelse return self.raise("expected expression or `)` after `(`")) {
.symbol_paren_right => {
tokenizer.step();
return Expression{
.call_system = .{
.identifier = identifier,
.argument_expressions = expression_list,
},
};
},
else => {
try expression_list.push_one(try self.parse_expression(tokenizer));
switch (tokenizer.token orelse return self.raise("expected `,` or `)` after argument")) {
.symbol_comma => continue,
.symbol_paren_right => {
tokenizer.step();
return Expression{
.call_system = .{
.identifier = identifier,
.argument_expressions = expression_list,
},
};
},
else => return self.raise("expected `,` or `)` after argument"),
}
},
}
}
},
.identifier => |identifier| {
tokenizer.step(); tokenizer.step();
return Expression{.get_local = identifier}; return Expression{.get_local = identifier};
@ -283,7 +394,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
return Expression{.table_literal = table_fields}; return Expression{.table_literal = table_fields};
}, },
.local => |identifier| { .identifier => |identifier| {
tokenizer.skip(.newline); tokenizer.skip(.newline);
if (!tokenizer.is_token(.symbol_equals)) { if (!tokenizer.is_token(.symbol_equals)) {

512
source/ona/kym/Chunk.zig Normal file
View File

@ -0,0 +1,512 @@
const Ast = @import("./Ast.zig");
const coral = @import("coral");
const kym = @import("../kym.zig");
env: *kym.RuntimeEnv,
constant_refs: RefList,
opcodes: OpcodeList,
const AstCompiler = struct {
chunk: *Self,
local_identifiers_buffer: [255][]const coral.io.Byte = [_][]const coral.io.Byte{""} ** 255,
local_identifiers_count: u8 = 0,
fn compile_expression(self: *AstCompiler, expression: Ast.Expression) kym.RuntimeError!void {
const number_format = coral.utf8.DecimalFormat{
.delimiter = "_",
.positive_prefix = .none,
};
switch (expression) {
.nil_literal => try self.chunk.append_opcode(.push_nil),
.true_literal => try self.chunk.append_opcode(.push_true),
.false_literal => try self.chunk.append_opcode(.push_false),
.number_literal => |literal| {
const parsed_number = number_format.parse(literal, kym.Float);
coral.debug.assert(parsed_number != null);
try self.chunk.append_opcode(.{
.push_const = try self.chunk.declare_constant_number(parsed_number.?),
});
},
.string_literal => |literal| {
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(literal)});
},
.table_literal => |fields| {
if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) {
return error.OutOfMemory;
}
for (fields.values) |field| {
try self.compile_expression(field.expression);
try self.chunk.append_opcode(.{
.push_const = try self.chunk.declare_constant_string(field.identifier),
});
}
try self.chunk.append_opcode(.{.push_table = @intCast(fields.values.len)});
},
.binary_operation => |operation| {
try self.compile_expression(operation.lhs_expression.*);
try self.compile_expression(operation.rhs_expression.*);
try self.chunk.append_opcode(switch (operation.operator) {
.addition => .add,
.subtraction => .sub,
.multiplication => .mul,
.divsion => .div,
.greater_equals_comparison => .eql,
.greater_than_comparison => .cgt,
.equals_comparison => .cge,
.less_than_comparison => .clt,
.less_equals_comparison => .cle,
});
},
.unary_operation => |operation| {
try self.compile_expression(operation.expression.*);
try self.chunk.append_opcode(switch (operation.operator) {
.boolean_negation => .not,
.numeric_negation => .neg,
});
},
.grouped_expression => |grouped_expression| {
try self.compile_expression(grouped_expression.*);
},
.get_local => |local| {
try self.chunk.append_opcode(.{
.push_local = self.resolve_local(local) orelse return self.chunk.env.raise(error.OutOfMemory, "undefined local"),
});
},
.call_system => |call| {
if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) {
return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals");
}
for (call.argument_expressions.values) |argument_expression| {
try self.compile_expression(argument_expression);
}
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)});
try self.chunk.append_opcode(.{.syscall = @intCast(call.argument_expressions.values.len)});
try self.chunk.append_opcode(.pop);
},
}
}
fn compile_statement(self: *AstCompiler, statement: Ast.Statement) kym.RuntimeError!void {
switch (statement) {
.return_expression => |return_expression| try self.compile_expression(return_expression),
.return_nothing => try self.chunk.append_opcode(.push_nil),
.set_local => |local| {
try self.compile_expression(local.expression);
if (self.resolve_local(local.identifier)) |index| {
try self.chunk.append_opcode(.{.set_local = index});
} else {
try self.declare_local(local.identifier);
}
},
.call_system => |call| {
if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) {
return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals");
}
for (call.argument_expressions.values) |argument_expression| {
try self.compile_expression(argument_expression);
}
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)});
try self.chunk.append_opcode(.{.syscall = @intCast(call.argument_expressions.values.len)});
try self.chunk.append_opcode(.pop);
}
}
}
fn declare_local(self: *AstCompiler, identifier: []const u8) kym.RuntimeError!void {
if (self.local_identifiers_count == self.local_identifiers_buffer.len) {
return self.chunk.env.raise(error.OutOfMemory, "functions may contain a maximum of 255 locals");
}
self.local_identifiers_buffer[self.local_identifiers_count] = identifier;
self.local_identifiers_count += 1;
}
fn resolve_local(self: *AstCompiler, local_identifier: []const coral.io.Byte) ?u8 {
if (self.local_identifiers_count == 0) {
return null;
}
var index = @as(u8, self.local_identifiers_count - 1);
while (true) : (index -= 1) {
if (coral.io.equals(local_identifier, self.local_identifiers_buffer[index])) {
return index;
}
if (index == 0) {
return null;
}
}
}
};
pub const Constant = u16;
const RefList = coral.list.Stack(?*kym.RuntimeRef);
const LocalsList = coral.list.Stack([]const u8);
pub const Opcode = union (enum) {
pop,
push_nil,
push_true,
push_false,
push_const: Constant,
push_local: u8,
push_table: u32,
set_local: u8,
call: u8,
syscall: u8,
not,
neg,
add,
sub,
mul,
div,
eql,
cgt,
clt,
cge,
cle,
};
const OpcodeList = coral.list.Stack(Opcode);
const Self = @This();
pub fn append_opcode(self: *Self, opcode: Opcode) kym.RuntimeError!void {
return self.opcodes.push_one(opcode);
}
pub fn as_caller(self: *Self) kym.Caller {
return kym.Caller.bind(Self, self, execute);
}
pub fn compile_ast(self: *Self, ast: Ast) kym.RuntimeError!void {
try self.constant_refs.grow(coral.math.max_int(@typeInfo(u16).Int));
var compiler = AstCompiler{.chunk = self};
for (ast.list_statements()) |statement| {
try compiler.compile_statement(statement);
}
try self.constant_refs.pack();
try self.opcodes.pack();
}
pub fn declare_constant_number(self: *Self, constant: kym.Float) kym.RuntimeError!Constant {
const tail = self.constant_refs.values.len;
if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants");
}
const constant_ref = try self.env.new_number(constant);
errdefer self.env.discard(constant_ref);
try self.constant_refs.push_one(constant_ref);
return @intCast(tail);
}
pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym.RuntimeError!Constant {
const tail = self.constant_refs.values.len;
if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants");
}
const constant_ref = try self.env.new_string(constant);
errdefer self.env.discard(constant_ref);
try self.constant_refs.push_one(constant_ref);
return @intCast(tail);
}
fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
for (self.opcodes.values) |opcode| {
switch (opcode) {
.pop => env.discard(try env.pop_local()),
.push_nil => try env.push_ref(null),
.push_true => try env.push_boolean(true),
.push_false => try env.push_boolean(false),
.push_const => |constant| try env.push_ref(self.constant_refs.values[constant]),
.push_table => |field_count| {
const table_ref = try kym.new_table(env);
defer env.discard(table_ref);
{
const dynamic = try kym.unbox_dynamic(env, table_ref);
var popped = @as(usize, 0);
while (popped < field_count) : (popped += 1) {
const index_ref = try env.pop_local();
defer env.discard(index_ref);
const value_ref = try env.pop_local();
defer env.discard(value_ref);
try dynamic.typeinfo.set(.{
.userdata = dynamic.userdata,
.env = env,
.index_ref = index_ref,
}, value_ref);
}
}
try env.push_ref(table_ref);
},
.push_local => |local| {
const ref = try env.get_local(local);
defer env.discard(ref);
try env.push_ref(ref);
},
.set_local => |local| {
const ref = try env.pop_local();
defer env.discard(ref);
try env.set_local(local, ref);
},
.call => |arg_count| {
const result_ref = call: {
const callable_ref = try env.pop_local();
defer env.discard(callable_ref);
break: call try env.call("", arg_count, (try kym.unbox_dynamic(env, callable_ref)).as_caller());
};
defer env.discard(result_ref);
try env.push_ref(result_ref);
},
.syscall => |arg_count| {
const result_ref = call: {
const identifier_ref = try env.pop_local();
defer env.discard(identifier_ref);
const identifier = try kym.unbox_string(env, identifier_ref);
break: call try env.call(identifier, arg_count, try env.syscaller(identifier));
};
defer env.discard(result_ref);
try env.push_ref(result_ref);
},
.neg => try env.push_number(switch (env.unbox(try env.pop_local())) {
.number => |number| -number,
else => return env.raise(error.TypeMismatch, "object is not scalar negatable"),
}),
.not => try env.push_boolean(switch (env.unbox(try env.pop_local())) {
.boolean => |boolean| !boolean,
else => return env.raise(error.TypeMismatch, "object is not boolean negatable"),
}),
.add => {
const rhs_ref = try env.pop_local();
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
defer env.discard(lhs_ref);
try env.push_ref(try switch (env.unbox(lhs_ref)) {
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
.number => |rhs_number| env.new_number(lhs_number + rhs_number),
else => return env.raise(error.TypeMismatch, "right-hand object is not addable"),
},
else => return env.raise(error.TypeMismatch, "left-hand object is not addable"),
});
},
.sub => {
const rhs_ref = try env.pop_local();
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
defer env.discard(lhs_ref);
try env.push_ref(try switch (env.unbox(lhs_ref)) {
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
.number => |rhs_number| env.new_number(lhs_number - rhs_number),
else => return env.raise(error.TypeMismatch, "right-hand object is not subtractable"),
},
else => return env.raise(error.TypeMismatch, "left-hand object is not subtractable"),
});
},
.mul => {
const rhs_ref = try env.pop_local();
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
defer env.discard(lhs_ref);
try env.push_ref(try switch (env.unbox(lhs_ref)) {
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
.number => |rhs_number| env.new_number(lhs_number * rhs_number),
else => return env.raise(error.TypeMismatch, "right-hand object is not multiplyable"),
},
else => return env.raise(error.TypeMismatch, "left-hand object is not multiplyable"),
});
},
.div => {
const rhs_ref = try env.pop_local();
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
defer env.discard(lhs_ref);
try env.push_ref(try switch (env.unbox(lhs_ref)) {
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
.number => |rhs_number| env.new_number(lhs_number / rhs_number),
else => return env.raise(error.TypeMismatch, "right-hand object is not divisable"),
},
else => return env.raise(error.TypeMismatch, "left-hand object is not divisable"),
});
},
.eql => {
const rhs_ref = try env.pop_local();
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
defer env.discard(lhs_ref);
try env.push_boolean(try kym.test_equality(env, lhs_ref, rhs_ref));
},
.cgt => {
const rhs_ref = try env.pop_local();
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
defer env.discard(lhs_ref);
try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) > 0);
},
.clt => {
const rhs_ref = try env.pop_local();
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
defer env.discard(lhs_ref);
try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) < 0);
},
.cge => {
const rhs_ref = try env.pop_local();
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
defer env.discard(lhs_ref);
try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) >= 0);
},
.cle => {
const rhs_ref = try env.pop_local();
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
defer env.discard(lhs_ref);
try env.push_boolean(try kym.test_difference(env, lhs_ref, rhs_ref) <= 0);
},
}
}
return env.pop_local();
}
pub fn free(self: *Self) void {
for (self.constant_refs.values) |constant| {
self.env.discard(constant);
}
self.opcodes.free();
self.constant_refs.free();
}
fn is_zero(utf8: []const u8) bool {
return coral.io.equals(utf8, "0") or coral.io.equals(utf8, "0.0");
}
pub fn make(env: *kym.RuntimeEnv) Self {
return Self{
.opcodes = OpcodeList.make(env.allocator),
.constant_refs = RefList.make(env.allocator),
.env = env,
};
}

View File

@ -1,148 +0,0 @@
const coral = @import("coral");
allocator: coral.io.Allocator,
interned: SymbolTable,
globals: Object,
values: DataStack,
frames: CallStack,
pub const Float = f64;
const CallStack = coral.list.Stack(struct {
callable: *Object,
opcode_index: usize,
stack_index: usize,
});
const DataStack = coral.list.Stack(Variant);
pub const Object = struct {
ref_count: usize,
userdata: []coral.io.Byte,
userinfo: *const anyopaque,
};
pub const PopError = error {
StackOverflow,
};
const Self = @This();
const SymbolTable = coral.map.Table([]const coral.io.Byte, *Object, coral.map.string_table_traits);
pub const Variant = union (enum) {
nil,
true,
false,
number: Float,
object: *Object,
pub fn equals(self: Variant, other: Variant) bool {
return switch (self) {
.nil => other == .nil,
.true => other == .true,
.false => other == .false,
.number => |number| switch (other) {
.number => |other_number| number == other_number,
else => false,
},
.object => |object| switch (other) {
.object => |other_object| object == other_object,
else => false,
},
};
}
};
pub fn acquire_instance(_: *Self, object: *Object) *Object {
// TODO: safety-check object belongs to state.
object.ref_count += 1;
return object;
}
pub fn acquire_interned(self: *Self, userdata: []const u8, userinfo: *const anyopaque) coral.io.AllocationError!*Object {
// TODO: Include userinfo in matching lookup.
if (self.interned.lookup(userdata)) |object| {
return self.acquire_instance(object);
} else {
const data_object = try self.acquire_new(userdata, userinfo);
errdefer self.release(data_object);
coral.debug.assert(try self.interned.insert(data_object.userdata, data_object));
return data_object;
}
}
pub fn acquire_new(self: *Self, userdata: []const u8, userinfo: *const anyopaque) coral.io.AllocationError!*Object {
const allocated_userdata = try coral.io.allocate_copy(self.allocator, userdata);
errdefer self.allocator.deallocate(allocated_userdata);
const allocated_object = try coral.io.allocate_one(self.allocator, Object{
.ref_count = 1,
.userdata = allocated_userdata,
.userinfo = userinfo,
});
errdefer self.allocator.deallocate(allocated_object);
return allocated_object;
}
pub fn free(self: *Self) void {
self.values.free();
self.frames.free();
self.interned.free();
}
pub fn get_value(self: *Self, index: u8) Variant {
return if (index < self.values.values.len) self.values.values[index] else .nil;
}
pub fn make(allocator: coral.io.Allocator) Self {
return .{
.values = DataStack.make(allocator),
.frames = CallStack.make(allocator),
.interned = SymbolTable.make(allocator),
.allocator = allocator,
.globals = .{
.ref_count = 0,
.userdata = &.{},
.userinfo = &.{},
},
};
}
pub fn pop_value(self: *Self) PopError!Variant {
return self.values.pop() orelse error.StackOverflow;
}
pub fn push_value(self: *Self, value: Variant) coral.io.AllocationError!void {
return self.values.push_one(value);
}
pub fn release(self: *Self, object: *Object) void {
coral.debug.assert(object.ref_count != 0);
object.ref_count -= 1;
if (object.ref_count == 0) {
self.allocator.deallocate(object);
}
}
pub fn set_value(self: *Self, index: u8, value: Variant) bool {
if (index >= self.values.values.len) {
return false;
}
self.values.values[index] = value;
return true;
}

View File

@ -1,76 +1,91 @@
const State = @import("./State.zig");
const coral = @import("coral"); const coral = @import("coral");
state: *State, const kym = @import("../kym.zig");
fields: FieldTable, fields: FieldTable,
array: ArrayList,
const ArrayList = coral.list.Stack(State.Variant); const FieldTable = coral.map.StringTable(struct {
key_ref: ?*kym.RuntimeRef,
const Field = struct { value_ref: ?*kym.RuntimeRef,
field: *State.Object, });
value: State.Variant,
fn release_objects(self: Field, state: *State) void {
state.release(self.field);
if (self.value == .object) {
state.release(self.value.object);
}
}
};
const FieldTable = coral.map.Table([]const coral.io.Byte, Field, coral.map.string_table_traits);
const Self = @This(); const Self = @This();
pub fn free(self: *Self) void { pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
{ var self = Self{
var field_iterator = FieldTable.Iterable{.table = &self.fields}; .fields = FieldTable.make(env.allocator),
};
while (field_iterator.next()) |entry| { errdefer {
entry.value.release_objects(self.state); self.fields.free();
}
return try env.new_dynamic(coral.io.bytes_of(&self), &typeinfo);
}
const typeinfo = kym.Typeinfo{
kayomn marked this conversation as resolved Outdated

Does this need to be public?

Does this need to be public?
.name = "table",
.clean = typeinfo_clean,
.get = typeinfo_get,
.set = typeinfo_set,
};
fn typeinfo_clean(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void {
const table = @as(*Self, @ptrCast(@alignCast(userdata.ptr)));
{
var field_iterable = table.fields.as_iterable();
while (field_iterable.next()) |entry| {
env.discard(entry.value.key_ref);
env.discard(entry.value.value_ref);
} }
} }
self.fields.free(); table.fields.free();
self.array.free();
} }
pub fn get_field(self: *Self, field_name: *State.Object) State.Variant { fn typeinfo_get(context: kym.IndexContext) kym.RuntimeError!?*kym.RuntimeRef {
const field = self.fields.lookup(field_name.userdata) orelse return .nil; const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
if (field.value == .object) { return switch (context.env.unbox(context.index_ref)) {
return .{.object = self.state.acquire_instance(field.value.object)}; .string => |string| context.env.acquire((table.fields.lookup(string) orelse return null).value_ref),
} // TODO: Implement number indices in tables.
.number => |_| unreachable,
return field.value; else => context.env.raise(error.TypeMismatch, "table objects may only be indexed with strings or numbers"),
}
pub fn get_index(self: *Self, index: usize) State.Variant {
return self.array.values[index];
}
pub fn make(allocator: coral.io.Allocator, state: *State) Self {
return .{
.state = state,
.fields = FieldTable.make(allocator),
.array = ArrayList.make(allocator),
}; };
} }
pub fn set_field(self: *Self, field_name: *State.Object, value: State.Variant) coral.io.AllocationError!void { fn typeinfo_set(context: kym.IndexContext, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void {
const previous_entry = try self.fields.replace(field_name.userdata, .{ const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
.field = field_name, const acquired_value_ref = context.env.acquire(value_ref);
.value = value,
});
if (previous_entry) |entry| { errdefer context.env.discard(acquired_value_ref);
entry.value.release_objects(self.state);
switch (context.env.unbox(context.index_ref)) {
.string => |string| {
const acquired_index_ref = context.env.acquire(context.index_ref);
errdefer context.env.discard(acquired_index_ref);
var displaced_table_entry = if (acquired_value_ref) |ref| try table.fields.replace(string, .{
.key_ref = acquired_index_ref,
.value_ref = ref,
}) else table.fields.remove(string);
if (displaced_table_entry) |*entry| {
context.env.discard(entry.value.key_ref);
context.env.discard(entry.value.value_ref);
}
},
.number => |_| {
// TODO: Implement number indices in tables.
unreachable;
},
else => {
return context.env.raise(error.TypeMismatch, "table objects may only be indexed with strings or numbers");
},
} }
} }
pub fn set_index(self: *Self, index: usize, value: State.Variant) coral.io.AllocationError!void {
self.array.values[index] = value;
}

View File

@ -1,11 +1,11 @@
const coral = @import("coral"); const coral = @import("coral");
pub const Token = union(enum) { pub const Token = union(enum) {
unknown: u8, unknown: coral.io.Byte,
newline, newline,
global: []const u8, special_identifier: []const coral.io.Byte,
local: []const u8, identifier: []const coral.io.Byte,
symbol_plus, symbol_plus,
symbol_minus, symbol_minus,
@ -29,8 +29,8 @@ pub const Token = union(enum) {
symbol_equals, symbol_equals,
symbol_double_equals, symbol_double_equals,
number: []const u8, number: []const coral.io.Byte,
string: []const u8, string: []const coral.io.Byte,
keyword_nil, keyword_nil,
keyword_false, keyword_false,
@ -39,13 +39,13 @@ pub const Token = union(enum) {
keyword_self, keyword_self,
keyword_const, keyword_const,
pub fn text(self: Token) []const u8 { pub fn text(self: Token) []const coral.io.Byte {
return switch (self) { return switch (self) {
.unknown => |unknown| @as([*]const u8, @ptrCast(&unknown))[0 .. 1], .unknown => |unknown| @as([*]const coral.io.Byte, @ptrCast(&unknown))[0 .. 1],
.newline => "newline", .newline => "newline",
.global => |identifier| identifier, .special_identifier => |identifier| identifier,
.local => |identifier| identifier, .identifier => |identifier| identifier,
.symbol_plus => "+", .symbol_plus => "+",
.symbol_minus => "-", .symbol_minus => "-",
@ -83,7 +83,7 @@ pub const Token = union(enum) {
}; };
pub const Tokenizer = struct { pub const Tokenizer = struct {
source: []const u8, source: []const coral.io.Byte,
lines_stepped: usize = 1, lines_stepped: usize = 1,
token: ?Token = null, token: ?Token = null,
@ -217,7 +217,7 @@ pub const Tokenizer = struct {
else => {}, else => {},
} }
self.token = .{.local = identifier}; self.token = .{.identifier = identifier};
return; return;
}, },
@ -236,7 +236,7 @@ pub const Tokenizer = struct {
else => break, else => break,
}; };
self.token = .{.global = self.source[begin .. cursor]}; self.token = .{.special_identifier = self.source[begin .. cursor]};
return; return;
}, },
@ -253,7 +253,7 @@ pub const Tokenizer = struct {
else => cursor += 1, else => cursor += 1,
}; };
self.token = .{.global = self.source[begin .. cursor]}; self.token = .{.special_identifier = self.source[begin .. cursor]};
cursor += 1; cursor += 1;
return; return;

View File

@ -10,73 +10,87 @@ const heap = @import("./heap.zig");
const kym = @import("./kym.zig"); const kym = @import("./kym.zig");
pub const RuntimeError = error { fn kym_handle_errors(info: kym.ErrorInfo) void {
OutOfMemory, var remaining_frames = info.frames.len;
InitFailure,
BadManifest, while (remaining_frames != 0) {
}; remaining_frames -= 1;
app.log_fail(info.frames[remaining_frames].name);
}
}
fn kym_log_info(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
app.log_info(try kym.unbox_string(env, try env.view_arg(0)));
return null;
}
fn kym_log_warn(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
app.log_warn(try kym.unbox_string(env, try env.view_arg(0)));
return null;
}
fn kym_log_fail(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
app.log_fail(try kym.unbox_string(env, try env.view_arg(0)));
return null;
}
fn last_sdl_error() [:0]const u8 { fn last_sdl_error() [:0]const u8 {
return coral.io.slice_sentineled(@as(u8, 0), @as([*:0]const u8, @ptrCast(ext.SDL_GetError()))); return coral.io.slice_sentineled(@as(u8, 0), @as([*:0]const u8, @ptrCast(ext.SDL_GetError())));
} }
pub fn run_app(file_access: file.Access) RuntimeError!void { pub fn run_app(file_access: file.Access) void {
var info_log = app.WritableLog.make(.info, heap.allocator); defer heap.trace_leaks();
defer info_log.free();
var fail_log = app.WritableLog.make(.fail, heap.allocator);
defer fail_log.free();
if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) {
try fail_log.write(last_sdl_error()); return app.log_fail(last_sdl_error());
return error.InitFailure;
} }
defer ext.SDL_Quit(); defer ext.SDL_Quit();
var script_env = kym.RuntimeEnv.make(heap.allocator, .{ var script_env = kym.RuntimeEnv.make(heap.allocator, kym.ErrorHandler.from(kym_handle_errors)) catch {
.out_writer = info_log.as_writer(), return app.log_fail("failed to initialize script runtime");
.err_writer = fail_log.as_writer(),
}) catch {
try fail_log.write("failed to initialize script runtime");
return error.InitFailure;
}; };
defer script_env.free(); defer script_env.free();
script_env.bind_syscalls(&.{
.{
.name = "log_info",
.caller = kym.Caller.from(kym_log_info),
},
.{
.name = "log_fail",
.caller = kym.Caller.from(kym_log_fail),
},
}) catch {
return app.log_fail("failed to bind syscalls to script runtime");
kayomn marked this conversation as resolved Outdated

Inaccurate log message.

Inaccurate log message.
};
var manifest = app.Manifest{}; var manifest = app.Manifest{};
manifest.load(&script_env, file_access) catch { manifest.load(&script_env, file_access) catch return;
fail_log.write("failed to load / execute app.ona manifest") catch {};
return error.BadManifest;
};
const window = create: { const window = create: {
const pos = ext.SDL_WINDOWPOS_CENTERED; const pos = ext.SDL_WINDOWPOS_CENTERED;
const flags = 0; const flags = 0;
break: create ext.SDL_CreateWindow(&manifest.title, pos, pos, manifest.width, manifest.height, flags) orelse { break: create ext.SDL_CreateWindow(&manifest.title, pos, pos, manifest.width, manifest.height, flags) orelse {
fail_log.write(last_sdl_error()) catch {}; return app.log_fail(last_sdl_error());
return error.InitFailure;
}; };
}; };
defer ext.SDL_DestroyWindow(window); defer ext.SDL_DestroyWindow(window);
const renderer = create: { const renderer = create: {
const defaultDriverIndex = -1; const default_driver_index = -1;
const flags = ext.SDL_RENDERER_ACCELERATED; const flags = ext.SDL_RENDERER_ACCELERATED;
break: create ext.SDL_CreateRenderer(window, defaultDriverIndex, flags) orelse { break: create ext.SDL_CreateRenderer(window, default_driver_index, flags) orelse {
fail_log.write(last_sdl_error()) catch {}; return app.log_fail(last_sdl_error());
return error.InitFailure;
}; };
}; };

View File

@ -1,5 +1,5 @@
const ona = @import("ona"); const ona = @import("ona");
pub fn main() ona.RuntimeError!void { pub fn main() void {
kayomn marked this conversation as resolved Outdated

Error union is unused.

Error union is unused.
try ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd}); ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd});
} }