Merge pull request 'Native "Syscall" Interface for Kym' (#21) from kym-native-function into main
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #21
This commit is contained in:
commit
5d06e61564
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,10 +116,7 @@ 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;
|
||||||
|
|
||||||
return .{
|
const Invoker = struct {
|
||||||
.context = if (is_zero_aligned) state else @ptrCast(state),
|
|
||||||
|
|
||||||
.invoker = struct {
|
|
||||||
fn invoke(context: *const anyopaque, input: Input) Output {
|
fn invoke(context: *const anyopaque, input: Input) Output {
|
||||||
if (is_zero_aligned) {
|
if (is_zero_aligned) {
|
||||||
return invoker(@ptrCast(context), input);
|
return invoker(@ptrCast(context), input);
|
||||||
|
@ -127,7 +124,24 @@ pub fn Functor(comptime Output: type, comptime Input: type) type {
|
||||||
|
|
||||||
return invoker(@ptrCast(@alignCast(context)), input);
|
return invoker(@ptrCast(@alignCast(context)), input);
|
||||||
}
|
}
|
||||||
}.invoke,
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.context = if (is_zero_aligned) state else @ptrCast(state),
|
||||||
|
.invoker = Invoker.invoke,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 {
|
||||||
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 {
|
||||||
info,
|
ext.SDL_LogInfo(
|
||||||
warn,
|
ext.SDL_LOG_CATEGORY_APPLICATION,
|
||||||
fail,
|
"%.*s",
|
||||||
};
|
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
|
||||||
|
message.ptr,
|
||||||
pub const WritableLog = struct {
|
);
|
||||||
severity: LogSeverity,
|
|
||||||
write_buffer: coral.list.ByteStack,
|
|
||||||
|
|
||||||
pub fn as_writer(self: *WritableLog) coral.io.Writer {
|
|
||||||
return coral.io.Writer.bind(WritableLog, self, struct {
|
|
||||||
fn write(writable_log: *WritableLog, bytes: []const coral.io.Byte) ?usize {
|
|
||||||
writable_log.write(bytes) catch return null;
|
|
||||||
|
|
||||||
return bytes.len;
|
|
||||||
}
|
|
||||||
}.write);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free(self: *WritableLog) void {
|
pub fn log_warn(message: []const coral.io.Byte) void {
|
||||||
self.write_buffer.free();
|
ext.SDL_LogWarn(
|
||||||
|
ext.SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"%.*s",
|
||||||
|
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
|
||||||
|
message.ptr,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make(log_severity: LogSeverity, allocator: coral.io.Allocator) WritableLog {
|
pub fn log_fail(message: []const coral.io.Byte) void {
|
||||||
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_LogError(
|
||||||
ext.SDL_LOG_CATEGORY_APPLICATION,
|
ext.SDL_LOG_CATEGORY_APPLICATION,
|
||||||
format_string,
|
"%.*s",
|
||||||
self.write_buffer.values.len,
|
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
|
||||||
self.write_buffer.values.ptr);
|
message.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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -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
|
@ -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,13 +98,14 @@ 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,
|
||||||
fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
|
comptime operators: []const Expression.BinaryOperator) ExpressionParser {
|
||||||
var expression = try parse_next(self, tokenizer);
|
|
||||||
|
|
||||||
{
|
const BinaryOperationParser = struct {
|
||||||
|
fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
|
||||||
const allocator = self.arena.as_allocator();
|
const allocator = self.arena.as_allocator();
|
||||||
|
var expression = try parse_next(self, tokenizer);
|
||||||
|
|
||||||
inline for (operators) |operator| {
|
inline for (operators) |operator| {
|
||||||
const token = comptime operator.token();
|
const token = comptime operator.token();
|
||||||
|
@ -103,8 +114,7 @@ fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime opera
|
||||||
tokenizer.step();
|
tokenizer.step();
|
||||||
|
|
||||||
if (tokenizer.token == null) {
|
if (tokenizer.token == null) {
|
||||||
return self.raise(
|
return self.raise("expected other half of expression after `" ++ comptime token.text() ++ "`");
|
||||||
"expected right-hand side of expression after `" ++ comptime token.text() ++ "`");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expression = .{
|
expression = .{
|
||||||
|
@ -116,11 +126,12 @@ fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime opera
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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| {
|
||||||
|
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| {
|
||||||
|
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)) {
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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| {
|
|
||||||
entry.value.release_objects(self.state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
errdefer {
|
||||||
self.fields.free();
|
self.fields.free();
|
||||||
self.array.free();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_field(self: *Self, field_name: *State.Object) State.Variant {
|
return try env.new_dynamic(coral.io.bytes_of(&self), &typeinfo);
|
||||||
const field = self.fields.lookup(field_name.userdata) orelse return .nil;
|
|
||||||
|
|
||||||
if (field.value == .object) {
|
|
||||||
return .{.object = self.state.acquire_instance(field.value.object)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return field.value;
|
const typeinfo = kym.Typeinfo{
|
||||||
|
.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_index(self: *Self, index: usize) State.Variant {
|
table.fields.free();
|
||||||
return self.array.values[index];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make(allocator: coral.io.Allocator, state: *State) Self {
|
fn typeinfo_get(context: kym.IndexContext) kym.RuntimeError!?*kym.RuntimeRef {
|
||||||
return .{
|
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
|
||||||
.state = state,
|
|
||||||
.fields = FieldTable.make(allocator),
|
return switch (context.env.unbox(context.index_ref)) {
|
||||||
.array = ArrayList.make(allocator),
|
.string => |string| context.env.acquire((table.fields.lookup(string) orelse return null).value_ref),
|
||||||
|
// TODO: Implement number indices in tables.
|
||||||
|
.number => |_| unreachable,
|
||||||
|
else => context.env.raise(error.TypeMismatch, "table objects may only be indexed with strings or numbers"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_index(self: *Self, index: usize, value: State.Variant) coral.io.AllocationError!void {
|
switch (context.env.unbox(context.index_ref)) {
|
||||||
self.array.values[index] = value;
|
.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");
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
|
};
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const ona = @import("ona");
|
const ona = @import("ona");
|
||||||
|
|
||||||
pub fn main() ona.RuntimeError!void {
|
pub fn main() void {
|
||||||
try ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd});
|
ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue