Native "Syscall" Interface for Kym #21
|
@ -1,8 +1,8 @@
|
|||
|
||||
title = "Afterglow"
|
||||
@log_info("game is loading")
|
||||
|
||||
return {
|
||||
title = title,
|
||||
title = "Afterglow",
|
||||
width = 1280,
|
||||
height = 800,
|
||||
tick_rate = 60,
|
||||
|
|
|
@ -74,7 +74,7 @@ pub const Stacking = struct {
|
|||
const aligned_size = (size + alignment - 1) & ~(alignment - 1);
|
||||
|
||||
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;
|
||||
|
||||
|
@ -84,7 +84,7 @@ pub const Stacking = struct {
|
|||
var page = self.current_page() orelse unreachable;
|
||||
|
||||
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);
|
||||
|
|
|
@ -96,7 +96,7 @@ pub const FixedBuffer = struct {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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 {
|
||||
const is_zero_aligned = @alignOf(State) == 0;
|
||||
|
||||
return .{
|
||||
.context = if (is_zero_aligned) state else @ptrCast(state),
|
||||
|
||||
.invoker = struct {
|
||||
const Invoker = struct {
|
||||
fn invoke(context: *const anyopaque, input: Input) Output {
|
||||
if (is_zero_aligned) {
|
||||
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);
|
||||
}
|
||||
}.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 {
|
||||
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 {
|
||||
if (target.len < match.len) {
|
||||
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 {
|
||||
if (self.values.len == 0) {
|
||||
return null;
|
||||
|
@ -61,9 +83,25 @@ pub fn Stack(comptime Value: type) type {
|
|||
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 {
|
||||
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;
|
||||
|
@ -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 {
|
||||
return .{
|
||||
.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 {
|
||||
kayomn marked this conversation as resolved
|
||||
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 {
|
||||
allocator: io.Allocator,
|
||||
|
@ -99,8 +126,8 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
value: Value,
|
||||
|
||||
fn write_into(self: Entry, entry_table: []?Entry) bool {
|
||||
const hash_max = math.min(math.max_int(@typeInfo(usize).Int), entry_table.len);
|
||||
var hashed_key = math.wrap(traits.hash(self.key), math.min_int(@typeInfo(usize).Int), hash_max);
|
||||
const hash_max = @min(max_int, entry_table.len);
|
||||
var hashed_key = math.wrap(traits.hash(self.key), min_int, hash_max);
|
||||
var iterations = @as(usize, 0);
|
||||
|
||||
while (true) : (iterations += 1) {
|
||||
|
@ -126,7 +153,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
|
||||
pub const Iterable = struct {
|
||||
table: *Self,
|
||||
iterations: usize = 0,
|
||||
iterations: usize,
|
||||
|
||||
pub fn next(self: *Iterable) ?Entry {
|
||||
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();
|
||||
|
||||
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 {
|
||||
try self.rehash(load_max);
|
||||
|
||||
debug.assert(self.entries.len > self.count);
|
||||
|
||||
{
|
||||
const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len);
|
||||
var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max);
|
||||
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 {
|
||||
|
@ -223,8 +276,8 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
return null;
|
||||
}
|
||||
|
||||
const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len);
|
||||
var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max);
|
||||
const hash_max = @min(max_int, self.entries.len);
|
||||
var hashed_key = math.wrap(traits.hash(key), min_int, hash_max);
|
||||
var iterations = @as(usize, 0);
|
||||
|
||||
while (iterations < self.count) : (iterations += 1) {
|
||||
|
@ -253,7 +306,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
return;
|
||||
}
|
||||
|
||||
const min_count = math.max(1, self.count);
|
||||
const min_count = @max(1, self.count);
|
||||
const table_size = min_count * 2;
|
||||
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;
|
||||
}
|
||||
|
||||
pub const string_table_traits = TableTraits([]const io.Byte){
|
||||
.hash = hash_string,
|
||||
.match = io.equals,
|
||||
};
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn max(a: anytype, b: anytype) @TypeOf(a, b) {
|
||||
return @max(a, b);
|
||||
pub fn Int(comptime int: std.builtin.Type.Int) type {
|
||||
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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
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 {
|
||||
if (int.signedness == .unsigned) return 0;
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ pub const DecimalFormat = struct {
|
|||
switch (@typeInfo(ValueType)) {
|
||||
.Int => |int| {
|
||||
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;
|
||||
|
||||
{
|
||||
|
|
|
@ -13,42 +13,57 @@ pub const Manifest = struct {
|
|||
tick_rate: f32 = 60.0,
|
||||
|
||||
pub fn load(self: *Manifest, env: *kym.RuntimeEnv, file_access: file.Access) kym.RuntimeError!void {
|
||||
kayomn marked this conversation as resolved
kayomn
commented
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);
|
||||
|
||||
const title = try env.get_field(manifest, "title");
|
||||
|
||||
defer env.discard(title);
|
||||
|
||||
const title_string = try env.get_string(title);
|
||||
defer env.discard(manifest_ref);
|
||||
|
||||
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);
|
||||
|
||||
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 ref = try env.get_field(manifest, "height");
|
||||
const ref = try kym.get_field(env, manifest_ref, "height");
|
||||
|
||||
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 ref = try env.get_field(manifest, "tick_rate");
|
||||
const ref = try kym.get_field(env, manifest_ref, "tick_rate");
|
||||
|
||||
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.zero(self.title[limited_title_len .. self.title.len]);
|
||||
|
@ -60,69 +75,29 @@ pub const Manifest = struct {
|
|||
}
|
||||
};
|
||||
|
||||
pub const LogSeverity = enum {
|
||||
info,
|
||||
warn,
|
||||
fail,
|
||||
};
|
||||
pub fn log_info(message: []const coral.io.Byte) void {
|
||||
kayomn marked this conversation as resolved
kayomn
commented
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.
|
||||
ext.SDL_LogInfo(
|
||||
ext.SDL_LOG_CATEGORY_APPLICATION,
|
||||
"%.*s",
|
||||
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
|
||||
kayomn marked this conversation as resolved
Outdated
kayomn
commented
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
kayomn
commented
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 {
|
||||
severity: LogSeverity,
|
||||
write_buffer: coral.list.ByteStack,
|
||||
pub fn log_warn(message: []const coral.io.Byte) void {
|
||||
ext.SDL_LogWarn(
|
||||
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 {
|
||||
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 {
|
||||
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') {
|
||||
pub fn log_fail(message: []const coral.io.Byte) void {
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
"%.*s",
|
||||
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
|
||||
message.ptr,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -156,15 +156,15 @@ pub const allocator = coral.io.Allocator.bind(Context, &context, .{
|
|||
pub fn trace_leaks() void {
|
||||
switch (builtin.mode) {
|
||||
.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", .{
|
||||
allocation_info.size,
|
||||
@as(usize, allocation_info) + @sizeOf(AllocationNode),
|
||||
node.size,
|
||||
@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,
|
||||
arena: coral.arena.Stacking,
|
||||
statements: Statement.List,
|
||||
error_message: []const u8,
|
||||
error_message: []const coral.io.Byte,
|
||||
|
||||
pub const Expression = union (enum) {
|
||||
nil_literal,
|
||||
true_literal,
|
||||
false_literal,
|
||||
number_literal: []const u8,
|
||||
string_literal: []const u8,
|
||||
number_literal: []const coral.io.Byte,
|
||||
string_literal: []const coral.io.Byte,
|
||||
table_literal: NamedList,
|
||||
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 {
|
||||
operator: BinaryOperator,
|
||||
|
@ -55,7 +60,7 @@ pub const Expression = union (enum) {
|
|||
};
|
||||
|
||||
pub const NamedList = coral.list.Stack(struct {
|
||||
identifier: []const u8,
|
||||
identifier: []const coral.io.Byte,
|
||||
expression: Expression,
|
||||
});
|
||||
|
||||
|
@ -80,6 +85,11 @@ pub const Statement = union (enum) {
|
|||
expression: Expression,
|
||||
},
|
||||
|
||||
call_system: struct {
|
||||
identifier: []const coral.io.Byte,
|
||||
argument_expressions: Expression.List,
|
||||
},
|
||||
|
||||
const List = coral.list.Stack(Statement);
|
||||
};
|
||||
|
||||
|
@ -88,13 +98,14 @@ const UnaryOperator = enum {
|
|||
numeric_negation,
|
||||
};
|
||||
|
||||
fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime operators: []const Expression.BinaryOperator) ExpressionParser {
|
||||
return struct {
|
||||
fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
|
||||
var expression = try parse_next(self, tokenizer);
|
||||
fn binary_operation_parser(
|
||||
comptime parse_next: ExpressionParser,
|
||||
comptime operators: []const Expression.BinaryOperator) ExpressionParser {
|
||||
|
||||
{
|
||||
const BinaryOperationParser = struct {
|
||||
fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
|
||||
const allocator = self.arena.as_allocator();
|
||||
var expression = try parse_next(self, tokenizer);
|
||||
|
||||
inline for (operators) |operator| {
|
||||
const token = comptime operator.token();
|
||||
|
@ -103,8 +114,7 @@ fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime opera
|
|||
tokenizer.step();
|
||||
|
||||
if (tokenizer.token == null) {
|
||||
return self.raise(
|
||||
"expected right-hand side of expression after `" ++ comptime token.text() ++ "`");
|
||||
return self.raise("expected other half of expression after `" ++ comptime token.text() ++ "`");
|
||||
}
|
||||
|
||||
expression = .{
|
||||
|
@ -116,11 +126,12 @@ fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime opera
|
|||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return expression;
|
||||
}
|
||||
}.parse;
|
||||
};
|
||||
|
||||
return BinaryOperationParser.parse;
|
||||
}
|
||||
|
||||
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 {
|
||||
self.free();
|
||||
|
||||
const allocator = self.arena.as_allocator();
|
||||
var has_returned = false;
|
||||
|
||||
while (true) {
|
||||
const no_effect_message = "statement has no effect";
|
||||
|
||||
tokenizer.skip(.newline);
|
||||
|
||||
switch (tokenizer.token orelse return) {
|
||||
|
@ -178,11 +192,9 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
|
|||
has_returned = true;
|
||||
},
|
||||
|
||||
.local => |identifier| {
|
||||
.identifier => |identifier| {
|
||||
tokenizer.step();
|
||||
|
||||
const no_effect_message = "statement has no effect";
|
||||
|
||||
switch (tokenizer.token orelse 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
kayomn
commented
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"),
|
||||
}
|
||||
}
|
||||
|
@ -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)};
|
||||
},
|
||||
|
||||
.keyword_nil => {
|
||||
tokenizer.step();
|
||||
|
||||
return .nil_literal;
|
||||
},
|
||||
|
||||
.keyword_true => {
|
||||
tokenizer.step();
|
||||
|
||||
return .true_literal;
|
||||
},
|
||||
|
||||
.keyword_false => {
|
||||
tokenizer.step();
|
||||
|
||||
return .false_literal;
|
||||
},
|
||||
|
||||
.number => |value| {
|
||||
tokenizer.step();
|
||||
|
||||
|
@ -264,7 +333,49 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
|
|||
return Expression{.string_literal = value};
|
||||
},
|
||||
|
||||
.local => |identifier| {
|
||||
.special_identifier => |identifier| {
|
||||
kayomn marked this conversation as resolved
Outdated
kayomn
commented
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();
|
||||
|
||||
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};
|
||||
},
|
||||
|
||||
.local => |identifier| {
|
||||
.identifier => |identifier| {
|
||||
tokenizer.skip(.newline);
|
||||
|
||||
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");
|
||||
|
||||
state: *State,
|
||||
const kym = @import("../kym.zig");
|
||||
|
||||
fields: FieldTable,
|
||||
array: ArrayList,
|
||||
|
||||
const ArrayList = coral.list.Stack(State.Variant);
|
||||
|
||||
const Field = struct {
|
||||
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 FieldTable = coral.map.StringTable(struct {
|
||||
key_ref: ?*kym.RuntimeRef,
|
||||
value_ref: ?*kym.RuntimeRef,
|
||||
});
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn free(self: *Self) void {
|
||||
{
|
||||
var field_iterator = FieldTable.Iterable{.table = &self.fields};
|
||||
|
||||
while (field_iterator.next()) |entry| {
|
||||
entry.value.release_objects(self.state);
|
||||
}
|
||||
}
|
||||
pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
|
||||
var self = Self{
|
||||
.fields = FieldTable.make(env.allocator),
|
||||
};
|
||||
|
||||
errdefer {
|
||||
self.fields.free();
|
||||
self.array.free();
|
||||
}
|
||||
|
||||
pub fn get_field(self: *Self, field_name: *State.Object) State.Variant {
|
||||
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;
|
||||
return try env.new_dynamic(coral.io.bytes_of(&self), &typeinfo);
|
||||
}
|
||||
|
||||
pub fn get_index(self: *Self, index: usize) State.Variant {
|
||||
return self.array.values[index];
|
||||
const typeinfo = kym.Typeinfo{
|
||||
kayomn marked this conversation as resolved
Outdated
kayomn
commented
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);
|
||||
}
|
||||
}
|
||||
|
||||
table.fields.free();
|
||||
}
|
||||
|
||||
pub fn make(allocator: coral.io.Allocator, state: *State) Self {
|
||||
return .{
|
||||
.state = state,
|
||||
.fields = FieldTable.make(allocator),
|
||||
.array = ArrayList.make(allocator),
|
||||
fn typeinfo_get(context: kym.IndexContext) kym.RuntimeError!?*kym.RuntimeRef {
|
||||
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
|
||||
|
||||
return switch (context.env.unbox(context.index_ref)) {
|
||||
.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 {
|
||||
const previous_entry = try self.fields.replace(field_name.userdata, .{
|
||||
.field = field_name,
|
||||
.value = value,
|
||||
});
|
||||
fn typeinfo_set(context: kym.IndexContext, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void {
|
||||
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
|
||||
const acquired_value_ref = context.env.acquire(value_ref);
|
||||
|
||||
if (previous_entry) |entry| {
|
||||
entry.value.release_objects(self.state);
|
||||
errdefer context.env.discard(acquired_value_ref);
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
const coral = @import("coral");
|
||||
|
||||
pub const Token = union(enum) {
|
||||
unknown: u8,
|
||||
unknown: coral.io.Byte,
|
||||
newline,
|
||||
|
||||
global: []const u8,
|
||||
local: []const u8,
|
||||
special_identifier: []const coral.io.Byte,
|
||||
identifier: []const coral.io.Byte,
|
||||
|
||||
symbol_plus,
|
||||
symbol_minus,
|
||||
|
@ -29,8 +29,8 @@ pub const Token = union(enum) {
|
|||
symbol_equals,
|
||||
symbol_double_equals,
|
||||
|
||||
number: []const u8,
|
||||
string: []const u8,
|
||||
number: []const coral.io.Byte,
|
||||
string: []const coral.io.Byte,
|
||||
|
||||
keyword_nil,
|
||||
keyword_false,
|
||||
|
@ -39,13 +39,13 @@ pub const Token = union(enum) {
|
|||
keyword_self,
|
||||
keyword_const,
|
||||
|
||||
pub fn text(self: Token) []const u8 {
|
||||
pub fn text(self: Token) []const coral.io.Byte {
|
||||
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",
|
||||
|
||||
.global => |identifier| identifier,
|
||||
.local => |identifier| identifier,
|
||||
.special_identifier => |identifier| identifier,
|
||||
.identifier => |identifier| identifier,
|
||||
|
||||
.symbol_plus => "+",
|
||||
.symbol_minus => "-",
|
||||
|
@ -83,7 +83,7 @@ pub const Token = union(enum) {
|
|||
};
|
||||
|
||||
pub const Tokenizer = struct {
|
||||
source: []const u8,
|
||||
source: []const coral.io.Byte,
|
||||
lines_stepped: usize = 1,
|
||||
token: ?Token = null,
|
||||
|
||||
|
@ -217,7 +217,7 @@ pub const Tokenizer = struct {
|
|||
else => {},
|
||||
}
|
||||
|
||||
self.token = .{.local = identifier};
|
||||
self.token = .{.identifier = identifier};
|
||||
|
||||
return;
|
||||
},
|
||||
|
@ -236,7 +236,7 @@ pub const Tokenizer = struct {
|
|||
else => break,
|
||||
};
|
||||
|
||||
self.token = .{.global = self.source[begin .. cursor]};
|
||||
self.token = .{.special_identifier = self.source[begin .. cursor]};
|
||||
|
||||
return;
|
||||
},
|
||||
|
@ -253,7 +253,7 @@ pub const Tokenizer = struct {
|
|||
else => cursor += 1,
|
||||
};
|
||||
|
||||
self.token = .{.global = self.source[begin .. cursor]};
|
||||
self.token = .{.special_identifier = self.source[begin .. cursor]};
|
||||
cursor += 1;
|
||||
|
||||
return;
|
||||
|
|
|
@ -10,73 +10,87 @@ const heap = @import("./heap.zig");
|
|||
|
||||
const kym = @import("./kym.zig");
|
||||
|
||||
pub const RuntimeError = error {
|
||||
OutOfMemory,
|
||||
InitFailure,
|
||||
BadManifest,
|
||||
};
|
||||
fn kym_handle_errors(info: kym.ErrorInfo) void {
|
||||
var remaining_frames = info.frames.len;
|
||||
|
||||
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 {
|
||||
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 {
|
||||
var info_log = app.WritableLog.make(.info, heap.allocator);
|
||||
|
||||
defer info_log.free();
|
||||
|
||||
var fail_log = app.WritableLog.make(.fail, heap.allocator);
|
||||
|
||||
defer fail_log.free();
|
||||
pub fn run_app(file_access: file.Access) void {
|
||||
defer heap.trace_leaks();
|
||||
|
||||
if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) {
|
||||
try fail_log.write(last_sdl_error());
|
||||
|
||||
return error.InitFailure;
|
||||
return app.log_fail(last_sdl_error());
|
||||
}
|
||||
|
||||
defer ext.SDL_Quit();
|
||||
|
||||
var script_env = kym.RuntimeEnv.make(heap.allocator, .{
|
||||
.out_writer = info_log.as_writer(),
|
||||
.err_writer = fail_log.as_writer(),
|
||||
}) catch {
|
||||
try fail_log.write("failed to initialize script runtime");
|
||||
|
||||
return error.InitFailure;
|
||||
var script_env = kym.RuntimeEnv.make(heap.allocator, kym.ErrorHandler.from(kym_handle_errors)) catch {
|
||||
return app.log_fail("failed to initialize script runtime");
|
||||
};
|
||||
|
||||
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
kayomn
commented
Inaccurate log message. Inaccurate log message.
|
||||
};
|
||||
|
||||
var manifest = app.Manifest{};
|
||||
|
||||
manifest.load(&script_env, file_access) catch {
|
||||
fail_log.write("failed to load / execute app.ona manifest") catch {};
|
||||
|
||||
return error.BadManifest;
|
||||
};
|
||||
manifest.load(&script_env, file_access) catch return;
|
||||
|
||||
const window = create: {
|
||||
const pos = ext.SDL_WINDOWPOS_CENTERED;
|
||||
const flags = 0;
|
||||
|
||||
break: create ext.SDL_CreateWindow(&manifest.title, pos, pos, manifest.width, manifest.height, flags) orelse {
|
||||
fail_log.write(last_sdl_error()) catch {};
|
||||
|
||||
return error.InitFailure;
|
||||
return app.log_fail(last_sdl_error());
|
||||
};
|
||||
};
|
||||
|
||||
defer ext.SDL_DestroyWindow(window);
|
||||
|
||||
const renderer = create: {
|
||||
const defaultDriverIndex = -1;
|
||||
const default_driver_index = -1;
|
||||
const flags = ext.SDL_RENDERER_ACCELERATED;
|
||||
|
||||
break: create ext.SDL_CreateRenderer(window, defaultDriverIndex, flags) orelse {
|
||||
fail_log.write(last_sdl_error()) catch {};
|
||||
|
||||
return error.InitFailure;
|
||||
break: create ext.SDL_CreateRenderer(window, default_driver_index, flags) orelse {
|
||||
return app.log_fail(last_sdl_error());
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const ona = @import("ona");
|
||||
|
||||
pub fn main() ona.RuntimeError!void {
|
||||
try ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd});
|
||||
pub fn main() void {
|
||||
kayomn marked this conversation as resolved
Outdated
kayomn
commented
Error union is unused. Error union is unused.
|
||||
ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
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.