Merge pull request 'kym-tables-overhaul' (#30) from kym-tables-overhaul into main
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #30
This commit is contained in:
commit
415dff46b7
|
@ -7,7 +7,8 @@
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"target": "${workspaceRoot}/zig-out/bin/runner",
|
"target": "${workspaceRoot}/zig-out/bin/runner",
|
||||||
"cwd": "${workspaceRoot}/debug/",
|
"cwd": "${workspaceRoot}/debug/",
|
||||||
"valuesFormatting": "parseText"
|
"valuesFormatting": "parseText",
|
||||||
|
"preLaunchTask": "Build All"
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
|
|
||||||
# Test comment.
|
# Test comment.
|
||||||
@log_info("game is loading")
|
|
||||||
|
|
||||||
return {
|
options = {
|
||||||
title = "Afterglow",
|
.title = "Afterglow",
|
||||||
width = 1280,
|
.width = 1280,
|
||||||
height = 800,
|
.height = 800,
|
||||||
tick_rate = 60,
|
.tick_rate = 60,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@log_info(options.title)
|
||||||
|
|
||||||
|
return options
|
||||||
|
|
|
@ -2,6 +2,8 @@ const debug = @import("./debug.zig");
|
||||||
|
|
||||||
const math = @import("./math.zig");
|
const math = @import("./math.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
pub const AllocationError = error {
|
pub const AllocationError = error {
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
};
|
};
|
||||||
|
@ -27,7 +29,7 @@ pub const Allocator = struct {
|
||||||
const ErasedActions = struct {
|
const ErasedActions = struct {
|
||||||
fn deallocate(context: *anyopaque, allocation: []Byte) void {
|
fn deallocate(context: *anyopaque, allocation: []Byte) void {
|
||||||
if (is_zero_aligned) {
|
if (is_zero_aligned) {
|
||||||
return actions.deallocator(@ptrCast(context), allocation);
|
return actions.deallocate(@ptrCast(context), allocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return actions.deallocate(@ptrCast(@alignCast(context)), allocation);
|
return actions.deallocate(@ptrCast(@alignCast(context)), allocation);
|
||||||
|
@ -35,7 +37,7 @@ pub const Allocator = struct {
|
||||||
|
|
||||||
fn reallocate(context: *anyopaque, return_address: usize, existing_allocation: ?[]Byte, size: usize) AllocationError![]Byte {
|
fn reallocate(context: *anyopaque, return_address: usize, existing_allocation: ?[]Byte, size: usize) AllocationError![]Byte {
|
||||||
if (is_zero_aligned) {
|
if (is_zero_aligned) {
|
||||||
return actions.reallocator(@ptrCast(context), return_address, existing_allocation, size);
|
return actions.reallocate(@ptrCast(context), return_address, existing_allocation, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return actions.reallocate(@ptrCast(@alignCast(context)), return_address, existing_allocation, size);
|
return actions.reallocate(@ptrCast(@alignCast(context)), return_address, existing_allocation, size);
|
||||||
|
@ -213,6 +215,27 @@ pub fn allocate_copy(allocator: Allocator, source: []const Byte) AllocationError
|
||||||
return allocation;
|
return allocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn allocate_many(allocator: Allocator, count: usize, value: anytype) AllocationError![]@TypeOf(value) {
|
||||||
|
const Type = @TypeOf(value);
|
||||||
|
const typeSize = @sizeOf(Type);
|
||||||
|
|
||||||
|
if (typeSize == 0) {
|
||||||
|
@compileError("Cannot allocate memory for 0-byte sized type " ++ @typeName(Type));
|
||||||
|
}
|
||||||
|
|
||||||
|
const allocations = @as([*]Type, @ptrCast(@alignCast(try allocator.actions.reallocate(
|
||||||
|
allocator.context,
|
||||||
|
@returnAddress(),
|
||||||
|
null,
|
||||||
|
typeSize * count))))[0 .. count];
|
||||||
|
|
||||||
|
for (allocations) |*allocation| {
|
||||||
|
allocation.* = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allocations;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@TypeOf(value) {
|
pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@TypeOf(value) {
|
||||||
const Type = @TypeOf(value);
|
const Type = @TypeOf(value);
|
||||||
const typeSize = @sizeOf(Type);
|
const typeSize = @sizeOf(Type);
|
||||||
|
@ -232,6 +255,16 @@ pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@Type
|
||||||
return allocation;
|
return allocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn allocate_string(allocator: Allocator, source: []const Byte) AllocationError![:0]Byte {
|
||||||
|
const allocation = try allocator.actions.reallocate(allocator.context, @returnAddress(), null, source.len + 1);
|
||||||
|
|
||||||
|
copy(allocation[0 .. source.len], source);
|
||||||
|
|
||||||
|
allocation[source.len] = 0;
|
||||||
|
|
||||||
|
return @ptrCast(allocation);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bytes_of(value: anytype) []const Byte {
|
pub fn bytes_of(value: anytype) []const Byte {
|
||||||
const pointer_info = @typeInfo(@TypeOf(value)).Pointer;
|
const pointer_info = @typeInfo(@TypeOf(value)).Pointer;
|
||||||
|
|
||||||
|
@ -265,6 +298,16 @@ pub fn compare(this: []const Byte, that: []const Byte) isize {
|
||||||
return @as(isize, @intCast(this.len)) - @as(isize, @intCast(that.len));
|
return @as(isize, @intCast(this.len)) - @as(isize, @intCast(that.len));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn djb2_hash(comptime int: std.builtin.Type.Int, target: []const Byte) math.Int(int) {
|
||||||
|
var hash_code = @as(math.Int(int), 5381);
|
||||||
|
|
||||||
|
for (target) |byte| {
|
||||||
|
hash_code = ((hash_code << 5) +% hash_code) +% byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash_code;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
@ -297,6 +340,16 @@ pub fn equals(target: []const Byte, match: []const Byte) bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_first(haystack: []const Byte, needle: Byte) ?usize {
|
||||||
|
for (0 .. haystack.len) |i| {
|
||||||
|
if (haystack[i] == needle) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var null_context = @as(usize, 0);
|
var null_context = @as(usize, 0);
|
||||||
|
|
||||||
pub const null_writer = Writer.bind(usize, &null_context, struct {
|
pub const null_writer = Writer.bind(usize, &null_context, struct {
|
||||||
|
|
|
@ -41,6 +41,10 @@ pub fn Stack(comptime Value: type) type {
|
||||||
self.capacity = grown_capacity;
|
self.capacity = grown_capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(self: Self) bool {
|
||||||
|
return self.values.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn make(allocator: io.Allocator) Self {
|
pub fn make(allocator: io.Allocator) Self {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
|
|
|
@ -6,118 +6,27 @@ const list = @import("./list.zig");
|
||||||
|
|
||||||
const math = @import("./math.zig");
|
const math = @import("./math.zig");
|
||||||
|
|
||||||
pub fn Slab(comptime Value: type) type {
|
pub fn StringTable(comptime Value: type) type {
|
||||||
return struct {
|
return Table([]const io.Byte, Value, struct {
|
||||||
next_index: usize,
|
|
||||||
entries: EntryList,
|
|
||||||
|
|
||||||
const EntryList = list.Stack(union (enum) {
|
|
||||||
value: Value,
|
|
||||||
next_index: usize,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn lookup(self: Self, key: usize) ?Value {
|
fn equals(key_a: []const io.Byte, key_b: []const io.Byte) bool {
|
||||||
if (key == 0 or key > self.entries.values.len) {
|
return io.equals(key_a, key_b);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return switch (self.entries.values[key - 1]) {
|
fn hash(key: []const io.Byte) usize {
|
||||||
.value => |value| value,
|
return io.djb2_hash(@typeInfo(usize).Int, key);
|
||||||
.next_index => null,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free(self: *Self) void {
|
|
||||||
self.entries.free();
|
|
||||||
|
|
||||||
self.next_index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(self: *Self, value: Value) io.AllocationError!usize {
|
|
||||||
if (self.next_index < self.entries.values.len) {
|
|
||||||
const index = self.next_index;
|
|
||||||
const entry = &self.entries.values[index];
|
|
||||||
|
|
||||||
debug.assert(entry.* == .next_index);
|
|
||||||
|
|
||||||
self.next_index = entry.next_index;
|
|
||||||
entry.* = .{.value = value};
|
|
||||||
|
|
||||||
return index + 1;
|
|
||||||
} else {
|
|
||||||
try self.entries.push_one(.{.value = value});
|
|
||||||
|
|
||||||
self.next_index += 1;
|
|
||||||
|
|
||||||
return self.next_index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
.entries = EntryList.make(allocator),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(self: *Self, key: usize) ?Value {
|
|
||||||
if (key == 0 or key > self.entries.values.len) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = key - 1;
|
|
||||||
const entry = &self.entries.values[index];
|
|
||||||
|
|
||||||
return switch (entry.*) {
|
|
||||||
.next_index => null,
|
|
||||||
|
|
||||||
.value => get_value: {
|
|
||||||
const value = entry.value;
|
|
||||||
|
|
||||||
entry.* = .{.next_index = self.next_index};
|
|
||||||
self.next_index = index;
|
|
||||||
|
|
||||||
break: get_value value;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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: type) type {
|
||||||
const load_max = 0.75;
|
const load_max = 0.75;
|
||||||
const hash_int = @typeInfo(usize).Int;
|
const max_int = math.max_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,
|
||||||
|
traits: Traits,
|
||||||
count: usize,
|
count: usize,
|
||||||
entries: []?Entry,
|
entries: []?Entry,
|
||||||
|
|
||||||
|
@ -125,24 +34,26 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
||||||
key: Key,
|
key: Key,
|
||||||
value: Value,
|
value: Value,
|
||||||
|
|
||||||
fn write_into(self: Entry, entry_table: []?Entry) bool {
|
fn write_into(self: Entry, table: *Self) bool {
|
||||||
const hash_max = @min(max_int, entry_table.len);
|
const hash_max = @min(max_int, table.entries.len);
|
||||||
var hashed_key = math.wrap(traits.hash(self.key), min_int, hash_max);
|
var hashed_key = table.hash_key(self.key) % hash_max;
|
||||||
var iterations = @as(usize, 0);
|
var iterations = @as(usize, 0);
|
||||||
|
|
||||||
while (true) : (iterations += 1) {
|
while (true) : (iterations += 1) {
|
||||||
debug.assert(iterations < entry_table.len);
|
debug.assert(iterations < table.entries.len);
|
||||||
|
|
||||||
const table_entry = &(entry_table[hashed_key] orelse {
|
const table_entry = &(table.entries[hashed_key] orelse {
|
||||||
entry_table[hashed_key] = .{
|
table.entries[hashed_key] = .{
|
||||||
.key = self.key,
|
.key = self.key,
|
||||||
.value = self.value,
|
.value = self.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
table.count += 1;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (traits.match(table_entry.key, self.key)) {
|
if (table.keys_equal(table_entry.key, self.key)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,14 +88,18 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hash_key(self: Self, key: Key) usize {
|
||||||
|
return if (@sizeOf(Traits) == 0) Traits.hash(key) else self.traits.hash(key);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove(self: *Self, key: Key) ?Entry {
|
pub fn remove(self: *Self, key: Key) ?Entry {
|
||||||
const hash_max = @min(max_int, self.entries.len);
|
const hash_max = @min(max_int, self.entries.len);
|
||||||
var hashed_key = math.wrap(traits.hash(key), min_int, hash_max);
|
var hashed_key = self.hash_key(key) % hash_max;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const entry = &(self.entries[hashed_key] orelse continue);
|
const entry = &(self.entries[hashed_key] orelse continue);
|
||||||
|
|
||||||
if (traits.match(entry.key, key)) {
|
if (self.keys_equal(entry.key, key)) {
|
||||||
const original_entry = entry.*;
|
const original_entry = entry.*;
|
||||||
|
|
||||||
self.entries[hashed_key] = null;
|
self.entries[hashed_key] = null;
|
||||||
|
@ -203,7 +118,8 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
||||||
|
|
||||||
{
|
{
|
||||||
const hash_max = @min(max_int, self.entries.len);
|
const hash_max = @min(max_int, self.entries.len);
|
||||||
var hashed_key = math.wrap(traits.hash(key), min_int, hash_max);
|
const has_context = @sizeOf(Traits) != 0;
|
||||||
|
var hashed_key = (if (has_context) self.traits.hash(key) else Traits.hash(key)) % hash_max;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const entry = &(self.entries[hashed_key] orelse {
|
const entry = &(self.entries[hashed_key] orelse {
|
||||||
|
@ -217,7 +133,8 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (traits.match(entry.key, key)) {
|
if (has_context) {
|
||||||
|
if (self.traits.equals(entry.key, key)) {
|
||||||
const original_entry = entry.*;
|
const original_entry = entry.*;
|
||||||
|
|
||||||
entry.* = .{
|
entry.* = .{
|
||||||
|
@ -227,6 +144,18 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
||||||
|
|
||||||
return original_entry;
|
return original_entry;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (Traits.equals(entry.key, key)) {
|
||||||
|
const original_entry = entry.*;
|
||||||
|
|
||||||
|
entry.* = .{
|
||||||
|
.key = key,
|
||||||
|
.value = value,
|
||||||
|
};
|
||||||
|
|
||||||
|
return original_entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hashed_key = (hashed_key +% 1) % hash_max;
|
hashed_key = (hashed_key +% 1) % hash_max;
|
||||||
}
|
}
|
||||||
|
@ -261,14 +190,20 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
||||||
|
|
||||||
debug.assert(self.entries.len > self.count);
|
debug.assert(self.entries.len > self.count);
|
||||||
|
|
||||||
defer self.count += 1;
|
|
||||||
|
|
||||||
const entry = Entry{
|
const entry = Entry{
|
||||||
.key = key,
|
.key = key,
|
||||||
.value = value,
|
.value = value,
|
||||||
};
|
};
|
||||||
|
|
||||||
return entry.write_into(self.entries);
|
return entry.write_into(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keys_equal(self: Self, key_a: Key, key_b: Key) bool {
|
||||||
|
if (@sizeOf(Traits) == 0) {
|
||||||
|
return Traits.equals(key_a, key_b);
|
||||||
|
} else {
|
||||||
|
return self.traits.equals(key_a, key_b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookup(self: Self, key: Key) ?Value {
|
pub fn lookup(self: Self, key: Key) ?Value {
|
||||||
|
@ -277,15 +212,22 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
||||||
}
|
}
|
||||||
|
|
||||||
const hash_max = @min(max_int, self.entries.len);
|
const hash_max = @min(max_int, self.entries.len);
|
||||||
var hashed_key = math.wrap(traits.hash(key), min_int, hash_max);
|
const has_context = @sizeOf(Traits) != 0;
|
||||||
|
var hashed_key = (if (has_context) self.traits.hash(key) else Traits.hash(key)) % hash_max;
|
||||||
var iterations = @as(usize, 0);
|
var iterations = @as(usize, 0);
|
||||||
|
|
||||||
while (iterations < self.count) : (iterations += 1) {
|
while (iterations < self.count) : (iterations += 1) {
|
||||||
const entry = &(self.entries[hashed_key] orelse return null);
|
const entry = &(self.entries[hashed_key] orelse return null);
|
||||||
|
|
||||||
if (traits.match(entry.key, key)) {
|
if (has_context) {
|
||||||
|
if (self.traits.equals(entry.key, key)) {
|
||||||
return entry.value;
|
return entry.value;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (Traits.equals(entry.key, key)) {
|
||||||
|
return entry.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hashed_key = (hashed_key +% 1) % hash_max;
|
hashed_key = (hashed_key +% 1) % hash_max;
|
||||||
}
|
}
|
||||||
|
@ -293,11 +235,12 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make(allocator: io.Allocator) Self {
|
pub fn make(allocator: io.Allocator, traits: Traits) Self {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.count = 0,
|
.count = 0,
|
||||||
.entries = &.{},
|
.entries = &.{},
|
||||||
|
.traits = traits,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,46 +249,26 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var table = make(self.allocator, self.traits);
|
||||||
|
|
||||||
|
errdefer table.free();
|
||||||
|
|
||||||
|
table.entries = allocate: {
|
||||||
const min_count = @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];
|
|
||||||
|
|
||||||
errdefer self.allocator.deallocate(allocation);
|
break: allocate try io.allocate_many(self.allocator, table_size, @as(?Entry, null));
|
||||||
|
};
|
||||||
|
|
||||||
self.entries = replace_table: {
|
|
||||||
for (allocation) |*entry| {
|
|
||||||
entry.* = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.entries.len != 0) {
|
|
||||||
for (self.entries) |maybe_entry| {
|
for (self.entries) |maybe_entry| {
|
||||||
if (maybe_entry) |entry| {
|
if (maybe_entry) |entry| {
|
||||||
debug.assert(entry.write_into(allocation));
|
debug.assert(entry.write_into(&table));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.allocator.deallocate(self.entries);
|
self.free();
|
||||||
}
|
|
||||||
|
|
||||||
break: replace_table allocation;
|
self.* = table;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn TableTraits(comptime Key: type) type {
|
|
||||||
return struct {
|
|
||||||
hash: fn (key: Key) usize,
|
|
||||||
match: fn (key: Key, key: Key) bool,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash_string(key: []const io.Byte) usize {
|
|
||||||
var hash_code = @as(usize, 5381);
|
|
||||||
|
|
||||||
for (key) |byte| {
|
|
||||||
hash_code = ((hash_code << 5) +% hash_code) +% byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash_code;
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,6 +8,24 @@ pub fn clamp(value: anytype, lower: anytype, upper: anytype) @TypeOf(value, lowe
|
||||||
return @max(lower, @min(upper, value));
|
return @max(lower, @min(upper, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn checked_add(a: anytype, b: anytype) ?@TypeOf(a + b) {
|
||||||
|
const result = @addWithOverflow(a, b);
|
||||||
|
|
||||||
|
return if (result.@"1" == 0) result.@"0" else null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checked_mul(a: anytype, b: anytype) ?@TypeOf(a * b) {
|
||||||
|
const result = @mulWithOverflow(a, b);
|
||||||
|
|
||||||
|
return if (result.@"1" == 0) result.@"0" else null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checked_sub(a: anytype, b: anytype) ?@TypeOf(a - b) {
|
||||||
|
const result = @subWithOverflow(a, b);
|
||||||
|
|
||||||
|
return if (result.@"1" == 0) result.@"0" else null;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clamped_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) Int(dest_int) {
|
pub fn clamped_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) Int(dest_int) {
|
||||||
const Value = @TypeOf(value);
|
const Value = @TypeOf(value);
|
||||||
|
|
||||||
|
@ -17,6 +35,8 @@ pub fn clamped_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) Int
|
||||||
.unsigned => @intCast(@min(value, max_int(dest_int))),
|
.unsigned => @intCast(@min(value, max_int(dest_int))),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.Float => @intFromFloat(clamp(value, min_int(dest_int), max_int(dest_int))),
|
||||||
|
|
||||||
else => @compileError("`" ++ @typeName(Value) ++ "` cannot be cast to an int"),
|
else => @compileError("`" ++ @typeName(Value) ++ "` cannot be cast to an int"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -40,9 +60,3 @@ pub fn min_int(comptime int: std.builtin.Type.Int) comptime_int {
|
||||||
|
|
||||||
return -(1 << (bit_count - 1));
|
return -(1 << (bit_count - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wrap(value: anytype, lower: anytype, upper: anytype) @TypeOf(value, lower, upper) {
|
|
||||||
const range = upper - lower;
|
|
||||||
|
|
||||||
return if (range == 0) lower else lower + @mod((@mod((value - lower), range) + range), range);
|
|
||||||
}
|
|
||||||
|
|
|
@ -33,9 +33,10 @@ pub const DecimalFormat = struct {
|
||||||
|
|
||||||
switch (code) {
|
switch (code) {
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
||||||
result = try math.checked_add(
|
const offset_code = math.checked_sub(code, '0') orelse return null;
|
||||||
try math.checked_mul(result, radix),
|
|
||||||
try math.checked_sub(code, '0'));
|
result = math.checked_mul(result, radix) orelse return null;
|
||||||
|
result = math.checked_add(result, offset_code) orelse return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
else => {
|
else => {
|
||||||
|
|
|
@ -13,18 +13,18 @@ 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_ref = try env.execute_file(file_access, file.Path.from(&.{"app.ona"}));
|
const manifest = try env.expect(try env.execute_file(file_access, file.Path.from(&.{"app.ona"})));
|
||||||
|
|
||||||
defer env.discard(manifest_ref);
|
defer env.discard(manifest);
|
||||||
|
|
||||||
const width = @as(u16, get: {
|
const width = @as(u16, get: {
|
||||||
const ref = try kym.get_field(env, manifest_ref, "width");
|
if (try env.get_field(manifest, "width")) |ref| {
|
||||||
|
|
||||||
defer env.discard(ref);
|
defer env.discard(ref);
|
||||||
|
|
||||||
if (env.unbox(ref).expect_number()) |number| {
|
const fixed = try env.unbox_fixed(ref);
|
||||||
if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) {
|
|
||||||
break: get @intFromFloat(number);
|
if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) {
|
||||||
|
break: get @intCast(fixed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,13 +32,13 @@ pub const Manifest = struct {
|
||||||
});
|
});
|
||||||
|
|
||||||
const height = @as(u16, get: {
|
const height = @as(u16, get: {
|
||||||
const ref = try kym.get_field(env, manifest_ref, "height");
|
if (try env.get_field(manifest, "height")) |ref| {
|
||||||
|
|
||||||
defer env.discard(ref);
|
defer env.discard(ref);
|
||||||
|
|
||||||
if (env.unbox(ref).expect_number()) |number| {
|
const fixed = try env.unbox_fixed(ref);
|
||||||
if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) {
|
|
||||||
break: get @intFromFloat(number);
|
if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) {
|
||||||
|
break: get @intCast(fixed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,27 +46,25 @@ pub const Manifest = struct {
|
||||||
});
|
});
|
||||||
|
|
||||||
const tick_rate = @as(f32, get: {
|
const tick_rate = @as(f32, get: {
|
||||||
const ref = try kym.get_field(env, manifest_ref, "tick_rate");
|
if (try env.get_field(manifest, "tick_rate")) |ref| {
|
||||||
|
|
||||||
defer env.discard(ref);
|
defer env.discard(ref);
|
||||||
|
|
||||||
if (env.unbox(ref).expect_number()) |number| {
|
break: get @floatCast(try env.unbox_float(ref));
|
||||||
break: get @floatCast(number);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break: get self.tick_rate;
|
break: get self.tick_rate;
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
if (try env.get_field(manifest, "title")) |ref| {
|
||||||
const title_ref = try kym.get_field(env, manifest_ref, "title");
|
defer env.discard(ref);
|
||||||
|
|
||||||
defer env.discard(title_ref);
|
const title_string = try env.unbox_string(ref);
|
||||||
|
|
||||||
const title_string = env.unbox(title_ref).expect_string() orelse "";
|
|
||||||
const limited_title_len = @min(title_string.len, self.title.len);
|
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]);
|
||||||
|
} else {
|
||||||
|
coral.io.zero(&self.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tick_rate = tick_rate;
|
self.tick_rate = tick_rate;
|
||||||
|
|
|
@ -159,7 +159,7 @@ pub fn trace_leaks() void {
|
||||||
var current_node = context.head;
|
var current_node = context.head;
|
||||||
|
|
||||||
while (current_node) |node| : (current_node = node.next) {
|
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", .{
|
||||||
node.size,
|
node.size,
|
||||||
@intFromPtr(node) + @sizeOf(AllocationNode),
|
@intFromPtr(node) + @sizeOf(AllocationNode),
|
||||||
});
|
});
|
||||||
|
|
1667
source/ona/kym.zig
1667
source/ona/kym.zig
File diff suppressed because it is too large
Load Diff
|
@ -15,13 +15,22 @@ pub const Expression = union (enum) {
|
||||||
false_literal,
|
false_literal,
|
||||||
number_literal: []const coral.io.Byte,
|
number_literal: []const coral.io.Byte,
|
||||||
string_literal: []const coral.io.Byte,
|
string_literal: []const coral.io.Byte,
|
||||||
table_literal: NamedList,
|
symbol_literal: []const coral.io.Byte,
|
||||||
|
table_literal: TableLiteral,
|
||||||
grouped_expression: *Expression,
|
grouped_expression: *Expression,
|
||||||
get_local: []const coral.io.Byte,
|
get_system: []const coral.io.Byte,
|
||||||
|
local_get: []const coral.io.Byte,
|
||||||
|
local_set: []const coral.io.Byte,
|
||||||
|
|
||||||
call_system: struct {
|
field_get: struct {
|
||||||
|
object_expression: *Expression,
|
||||||
identifier: []const coral.io.Byte,
|
identifier: []const coral.io.Byte,
|
||||||
argument_expressions: List,
|
},
|
||||||
|
|
||||||
|
field_set: struct {
|
||||||
|
object_expression: *Expression,
|
||||||
|
identifier: []const coral.io.Byte,
|
||||||
|
value_expression: *Expression,
|
||||||
},
|
},
|
||||||
|
|
||||||
binary_operation: struct {
|
binary_operation: struct {
|
||||||
|
@ -35,6 +44,11 @@ pub const Expression = union (enum) {
|
||||||
expression: *Expression,
|
expression: *Expression,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
invoke: struct {
|
||||||
|
object_expression: *Expression,
|
||||||
|
argument_expressions: List,
|
||||||
|
},
|
||||||
|
|
||||||
pub const BinaryOperator = enum {
|
pub const BinaryOperator = enum {
|
||||||
addition,
|
addition,
|
||||||
subtraction,
|
subtraction,
|
||||||
|
@ -61,9 +75,9 @@ pub const Expression = union (enum) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const NamedList = coral.list.Stack(struct {
|
pub const TableLiteral = coral.list.Stack(struct {
|
||||||
identifier: []const coral.io.Byte,
|
key_expression: Expression,
|
||||||
expression: Expression,
|
value_expression: Expression,
|
||||||
});
|
});
|
||||||
|
|
||||||
pub const List = coral.list.Stack(Expression);
|
pub const List = coral.list.Stack(Expression);
|
||||||
|
@ -79,18 +93,11 @@ pub const ParseError = error {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub const Statement = union (enum) {
|
pub const Statement = union (enum) {
|
||||||
return_expression: Expression,
|
@"return": struct {
|
||||||
return_nothing,
|
expression: ?Expression,
|
||||||
|
},
|
||||||
|
|
||||||
set_local: struct {
|
|
||||||
identifier: []const coral.io.Byte,
|
|
||||||
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);
|
||||||
};
|
};
|
||||||
|
@ -112,10 +119,10 @@ fn binary_operation_parser(
|
||||||
inline for (operators) |operator| {
|
inline for (operators) |operator| {
|
||||||
const token = comptime operator.token();
|
const token = comptime operator.token();
|
||||||
|
|
||||||
if (self.tokenizer.is_token(coral.io.tag_of(token))) {
|
if (self.tokenizer.token == coral.io.tag_of(token)) {
|
||||||
self.tokenizer.step();
|
self.tokenizer.step();
|
||||||
|
|
||||||
if (self.tokenizer.token == null) {
|
if (self.tokenizer.token == .end) {
|
||||||
return self.report("expected other half of expression after `" ++ comptime token.text() ++ "`");
|
return self.report("expected other half of expression after `" ++ comptime token.text() ++ "`");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,112 +181,58 @@ pub fn list_statements(self: Self) []const Statement {
|
||||||
pub fn parse(self: *Self, data: []const coral.io.Byte) ParseError!void {
|
pub fn parse(self: *Self, data: []const coral.io.Byte) ParseError!void {
|
||||||
self.tokenizer = .{.source = data};
|
self.tokenizer = .{.source = data};
|
||||||
|
|
||||||
const allocator = self.arena.as_allocator();
|
|
||||||
var has_returned = false;
|
var has_returned = false;
|
||||||
|
|
||||||
while (true) {
|
self.tokenizer.skip_newlines();
|
||||||
self.tokenizer.skip(.newline);
|
|
||||||
|
while (true) {
|
||||||
|
try self.statements.push_one(parse_statement: {
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.end => return,
|
||||||
|
|
||||||
switch (self.tokenizer.token orelse return) {
|
|
||||||
.keyword_return => {
|
.keyword_return => {
|
||||||
if (has_returned) {
|
if (has_returned) {
|
||||||
return self.report("multiple returns in function scope but expected only one");
|
return self.report("multiple returns in function scope but expected only one");
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.statements.push_one(get_statement: {
|
|
||||||
self.tokenizer.step();
|
self.tokenizer.step();
|
||||||
|
|
||||||
if (!self.tokenizer.is_token_null_or(.newline)) {
|
if (self.tokenizer.token != .end and self.tokenizer.token != .newline) {
|
||||||
break: get_statement .{.return_expression = try self.parse_expression()};
|
break: parse_statement .{
|
||||||
|
.@"return" = .{
|
||||||
|
.expression = try self.parse_expression(),
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!self.tokenizer.is_token_null_or(.newline)) {
|
if (self.tokenizer.token != .end and self.tokenizer.token != .newline) {
|
||||||
return self.report("unexpected token after return");
|
return self.report("expected end or newline after return statement");
|
||||||
}
|
}
|
||||||
|
|
||||||
break: get_statement .return_nothing;
|
|
||||||
});
|
|
||||||
|
|
||||||
has_returned = true;
|
has_returned = true;
|
||||||
|
|
||||||
|
break: parse_statement .{
|
||||||
|
.@"return" = .{
|
||||||
|
.expression = null,
|
||||||
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
.identifier => |identifier| {
|
else => {
|
||||||
self.tokenizer.step();
|
break: parse_statement .{
|
||||||
|
.expression = try self.parse_expression()
|
||||||
const no_effect_message = "statement has no effect";
|
};
|
||||||
|
},
|
||||||
switch (self.tokenizer.token orelse return self.report(no_effect_message)) {
|
|
||||||
.newline => return self.report(no_effect_message),
|
|
||||||
|
|
||||||
.symbol_equals => {
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == null) {
|
|
||||||
return self.report("expected expression after `=`");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.statements.push_one(.{
|
|
||||||
.set_local = .{
|
|
||||||
.expression = try self.parse_expression(),
|
|
||||||
.identifier = identifier,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!self.tokenizer.is_token_null_or(.newline)) {
|
|
||||||
return self.report("unexpected token after assignment");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return self.report("expected `=` after local"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.special_identifier => |identifier| {
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
const missing_arguments_message = "system call is missing arguments";
|
|
||||||
|
|
||||||
switch (self.tokenizer.token orelse return self.report(missing_arguments_message)) {
|
|
||||||
.newline => return self.report(missing_arguments_message),
|
|
||||||
|
|
||||||
.symbol_paren_left => {
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
var expressions_list = Expression.List.make(allocator);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (self.tokenizer.is_token(.symbol_paren_right)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try expressions_list.push_one(try self.parse_expression());
|
|
||||||
|
|
||||||
switch (self.tokenizer.token orelse return self.report("unexpected end after after `(`")) {
|
|
||||||
.symbol_comma => continue,
|
|
||||||
.symbol_paren_right => break,
|
|
||||||
else => return self.report("expected `)` or argument after `(`"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
try self.statements.push_one(.{
|
|
||||||
.call_system = .{
|
|
||||||
.argument_expressions = expressions_list,
|
|
||||||
.identifier = identifier,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return self.report("expected `=` after local"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return self.report("invalid statement"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parse_additive = binary_operation_parser(parse_equality, &.{
|
||||||
|
.addition,
|
||||||
|
.subtraction,
|
||||||
|
});
|
||||||
|
|
||||||
const parse_comparison = binary_operation_parser(parse_term, &.{
|
const parse_comparison = binary_operation_parser(parse_term, &.{
|
||||||
.greater_than_comparison,
|
.greater_than_comparison,
|
||||||
.greater_equals_comparison,
|
.greater_equals_comparison,
|
||||||
|
@ -291,149 +244,153 @@ const parse_equality = binary_operation_parser(parse_comparison, &.{
|
||||||
.equals_comparison,
|
.equals_comparison,
|
||||||
});
|
});
|
||||||
|
|
||||||
const parse_expression = binary_operation_parser(parse_equality, &.{
|
pub fn parse_expression(self: *Self) ParseError!Expression {
|
||||||
.addition,
|
const allocator = self.arena.as_allocator();
|
||||||
.subtraction,
|
const expression = try parse_additive(self);
|
||||||
});
|
|
||||||
|
if (self.tokenizer.token == .symbol_equals) {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
if (self.tokenizer.token == .end) {
|
||||||
|
return self.report("expected assignment after `=`");
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (expression) {
|
||||||
|
.local_get => |local_get| .{.local_set = local_get},
|
||||||
|
|
||||||
|
.field_get => |field_get| .{
|
||||||
|
.field_set = .{
|
||||||
|
.object_expression = field_get.object_expression,
|
||||||
|
.identifier = field_get.identifier,
|
||||||
|
.value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
else => self.report("expected local or field on left-hand side of expression"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_factor(self: *Self) ParseError!Expression {
|
fn parse_factor(self: *Self) ParseError!Expression {
|
||||||
const allocator = self.arena.as_allocator();
|
const allocator = self.arena.as_allocator();
|
||||||
|
|
||||||
switch (self.tokenizer.token orelse return self.report("expected operand after operator")) {
|
var expression = @as(Expression, parse: {
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
.symbol_paren_left => {
|
.symbol_paren_left => {
|
||||||
self.tokenizer.skip(.newline);
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
if (self.tokenizer.token == null) {
|
if (self.tokenizer.token == .end) {
|
||||||
return self.report("expected an expression after `(`");
|
return self.report("expected an expression after `(`");
|
||||||
}
|
}
|
||||||
|
|
||||||
const expression = try self.parse_expression();
|
const expression = try self.parse_expression();
|
||||||
|
|
||||||
if (!self.tokenizer.is_token(.symbol_paren_right)) {
|
if (self.tokenizer.token != .symbol_paren_right) {
|
||||||
return self.report("expected a closing `)` after expression");
|
return self.report("expected a closing `)` after expression");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tokenizer.step();
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
return Expression{.grouped_expression = try coral.io.allocate_one(allocator, expression)};
|
break: parse .{.grouped_expression = try coral.io.allocate_one(allocator, expression)};
|
||||||
},
|
},
|
||||||
|
|
||||||
.keyword_nil => {
|
.keyword_nil => {
|
||||||
self.tokenizer.step();
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
return .nil_literal;
|
break: parse .nil_literal;
|
||||||
},
|
},
|
||||||
|
|
||||||
.keyword_true => {
|
.keyword_true => {
|
||||||
self.tokenizer.step();
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
return .true_literal;
|
break: parse .true_literal;
|
||||||
},
|
},
|
||||||
|
|
||||||
.keyword_false => {
|
.keyword_false => {
|
||||||
self.tokenizer.step();
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
return .false_literal;
|
break: parse .false_literal;
|
||||||
},
|
},
|
||||||
|
|
||||||
.number => |value| {
|
.number => |value| {
|
||||||
self.tokenizer.step();
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
return Expression{.number_literal = value};
|
break: parse .{.number_literal = value};
|
||||||
},
|
},
|
||||||
|
|
||||||
.string => |value| {
|
.string => |value| {
|
||||||
self.tokenizer.step();
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
return Expression{.string_literal = value};
|
break: parse .{.string_literal = value};
|
||||||
},
|
|
||||||
|
|
||||||
.special_identifier => |identifier| {
|
|
||||||
self.tokenizer.skip(.newline);
|
|
||||||
|
|
||||||
var expression_list = Expression.List.make(allocator);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
switch (self.tokenizer.token orelse return self.report("expected expression or `)` after `(`")) {
|
|
||||||
.symbol_paren_right => {
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
return Expression{
|
|
||||||
.call_system = .{
|
|
||||||
.identifier = identifier,
|
|
||||||
.argument_expressions = expression_list,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {
|
|
||||||
try expression_list.push_one(try self.parse_expression());
|
|
||||||
|
|
||||||
switch (self.tokenizer.token orelse return self.report("expected `,` or `)` after argument")) {
|
|
||||||
.symbol_comma => continue,
|
|
||||||
|
|
||||||
.symbol_paren_right => {
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
return Expression{
|
|
||||||
.call_system = .{
|
|
||||||
.identifier = identifier,
|
|
||||||
.argument_expressions = expression_list,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return self.report("expected `,` or `)` after argument"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
.identifier => |identifier| {
|
.identifier => |identifier| {
|
||||||
self.tokenizer.step();
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
return Expression{.get_local = identifier};
|
break: parse .{.local_get = identifier};
|
||||||
|
},
|
||||||
|
|
||||||
|
.system_identifier => |system_identifier| {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
break: parse .{.get_system = system_identifier};
|
||||||
},
|
},
|
||||||
|
|
||||||
.symbol_brace_left => {
|
.symbol_brace_left => {
|
||||||
var table_fields = Expression.NamedList.make(allocator);
|
var table_literal = Expression.TableLiteral.make(allocator);
|
||||||
|
|
||||||
self.tokenizer.skip(.newline);
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
switch (self.tokenizer.token orelse return self.report("unexpected end of table literal")) {
|
switch (self.tokenizer.token) {
|
||||||
.symbol_brace_right => {
|
.symbol_brace_right => {
|
||||||
self.tokenizer.step();
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
return Expression{.table_literal = table_fields};
|
break: parse .{.table_literal = table_literal};
|
||||||
},
|
},
|
||||||
|
|
||||||
.identifier => |identifier| {
|
.symbol_bracket_left => {
|
||||||
self.tokenizer.skip(.newline);
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
if (!self.tokenizer.is_token(.symbol_equals)) {
|
if (self.tokenizer.token != .symbol_equals) {
|
||||||
return self.report("expected `=` after identifier");
|
return self.report("expected expression after identifier");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_period => {
|
||||||
|
self.tokenizer.step();
|
||||||
|
|
||||||
|
const identifier = switch (self.tokenizer.token) {
|
||||||
|
.identifier => |identifier| identifier,
|
||||||
|
else => return self.report("expected identifier after `.`"),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
if (self.tokenizer.token != .symbol_equals) {
|
||||||
|
return self.report("expected `=` after key");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tokenizer.skip(.newline);
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
if (self.tokenizer.token == null) {
|
if (self.tokenizer.token == .end) {
|
||||||
return self.report("unexpected end after `=`");
|
return self.report("unexpected end after `=`");
|
||||||
}
|
}
|
||||||
|
|
||||||
try table_fields.push_one(.{
|
try table_literal.push_one(.{
|
||||||
.expression = try self.parse_expression(),
|
.value_expression = try self.parse_expression(),
|
||||||
.identifier = identifier,
|
.key_expression = .{.symbol_literal = identifier},
|
||||||
});
|
});
|
||||||
|
|
||||||
switch (self.tokenizer.token orelse return self.report("unexpected end of table")) {
|
switch (self.tokenizer.token) {
|
||||||
.symbol_comma => self.tokenizer.skip(.newline),
|
.symbol_comma => self.tokenizer.skip_newlines(),
|
||||||
|
|
||||||
.symbol_brace_right => {
|
.symbol_brace_right => {
|
||||||
self.tokenizer.step();
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
return Expression{.table_literal = table_fields};
|
break: parse .{.table_literal = table_literal};
|
||||||
},
|
},
|
||||||
|
|
||||||
else => return self.report("expected `,` or `}` after expression"),
|
else => return self.report("expected `,` or `}` after expression"),
|
||||||
|
@ -446,13 +403,13 @@ fn parse_factor(self: *Self) ParseError!Expression {
|
||||||
},
|
},
|
||||||
|
|
||||||
.symbol_minus => {
|
.symbol_minus => {
|
||||||
self.tokenizer.skip(.newline);
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
if (self.tokenizer.token == null) {
|
if (self.tokenizer.token == .end) {
|
||||||
return self.report("expected expression after numeric negation (`-`)");
|
return self.report("expected expression after numeric negation (`-`)");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Expression{
|
break: parse .{
|
||||||
.unary_operation = .{
|
.unary_operation = .{
|
||||||
.expression = try coral.io.allocate_one(allocator, try self.parse_factor()),
|
.expression = try coral.io.allocate_one(allocator, try self.parse_factor()),
|
||||||
.operator = .numeric_negation,
|
.operator = .numeric_negation,
|
||||||
|
@ -461,13 +418,13 @@ fn parse_factor(self: *Self) ParseError!Expression {
|
||||||
},
|
},
|
||||||
|
|
||||||
.symbol_bang => {
|
.symbol_bang => {
|
||||||
self.tokenizer.skip(.newline);
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
if (self.tokenizer.token == null) {
|
if (self.tokenizer.token == .end) {
|
||||||
return self.report("expected expression after boolean negation (`!`)");
|
return self.report("expected expression after boolean negation (`!`)");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Expression{
|
break: parse .{
|
||||||
.unary_operation = .{
|
.unary_operation = .{
|
||||||
.expression = try coral.io.allocate_one(allocator, try self.parse_factor()),
|
.expression = try coral.io.allocate_one(allocator, try self.parse_factor()),
|
||||||
.operator = .boolean_negation,
|
.operator = .boolean_negation,
|
||||||
|
@ -477,6 +434,69 @@ fn parse_factor(self: *Self) ParseError!Expression {
|
||||||
|
|
||||||
else => return self.report("unexpected token in expression"),
|
else => return self.report("unexpected token in expression"),
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.symbol_period => {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
||||||
|
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
||||||
|
|
||||||
|
expression = .{
|
||||||
|
.field_get = .{
|
||||||
|
.identifier = switch (self.tokenizer.token) {
|
||||||
|
.identifier => |field_identifier| field_identifier,
|
||||||
|
else => return self.report("expected identifier after `.`"),
|
||||||
|
},
|
||||||
|
|
||||||
|
.object_expression = unnecessary_temp,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_paren_left => {
|
||||||
|
var argument_expressions = Expression.List.make(allocator);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.symbol_paren_right => break,
|
||||||
|
|
||||||
|
else => {
|
||||||
|
try argument_expressions.push_one(try self.parse_expression());
|
||||||
|
|
||||||
|
switch (self.tokenizer.token) {
|
||||||
|
.symbol_comma => continue,
|
||||||
|
.symbol_paren_right => break,
|
||||||
|
else => return self.report("expected `,` or `)` after function argument expression"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tokenizer.skip_newlines();
|
||||||
|
|
||||||
|
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
||||||
|
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
||||||
|
|
||||||
|
expression = .{
|
||||||
|
.invoke = .{
|
||||||
|
.argument_expressions = argument_expressions,
|
||||||
|
.object_expression = unnecessary_temp,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
else => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parse_term = binary_operation_parser(parse_factor, &.{
|
const parse_term = binary_operation_parser(parse_factor, &.{
|
||||||
|
|
|
@ -1,512 +0,0 @@
|
||||||
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,91 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const kym = @import("../kym.zig");
|
|
||||||
|
|
||||||
fields: FieldTable,
|
|
||||||
|
|
||||||
const FieldTable = coral.map.StringTable(struct {
|
|
||||||
key_ref: ?*kym.RuntimeRef,
|
|
||||||
value_ref: ?*kym.RuntimeRef,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
|
|
||||||
var self = Self{
|
|
||||||
.fields = FieldTable.make(env.allocator),
|
|
||||||
};
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
self.fields.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
return try env.new_dynamic(coral.io.bytes_of(&self), &typeinfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
table.fields.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
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"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,11 @@
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
pub const Token = union(enum) {
|
pub const Token = union(enum) {
|
||||||
|
end,
|
||||||
unknown: coral.io.Byte,
|
unknown: coral.io.Byte,
|
||||||
newline,
|
newline,
|
||||||
|
|
||||||
special_identifier: []const coral.io.Byte,
|
system_identifier: []const coral.io.Byte,
|
||||||
identifier: []const coral.io.Byte,
|
identifier: []const coral.io.Byte,
|
||||||
|
|
||||||
symbol_plus,
|
symbol_plus,
|
||||||
|
@ -41,10 +42,11 @@ pub const Token = union(enum) {
|
||||||
|
|
||||||
pub fn text(self: Token) []const coral.io.Byte {
|
pub fn text(self: Token) []const coral.io.Byte {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
|
.end => "end",
|
||||||
.unknown => |unknown| @as([*]const coral.io.Byte, @ptrCast(&unknown))[0 .. 1],
|
.unknown => |unknown| @as([*]const coral.io.Byte, @ptrCast(&unknown))[0 .. 1],
|
||||||
.newline => "newline",
|
.newline => "newline",
|
||||||
|
|
||||||
.special_identifier => |identifier| identifier,
|
.system_identifier => |identifier| identifier,
|
||||||
.identifier => |identifier| identifier,
|
.identifier => |identifier| identifier,
|
||||||
|
|
||||||
.symbol_plus => "+",
|
.symbol_plus => "+",
|
||||||
|
@ -85,26 +87,12 @@ pub const Token = union(enum) {
|
||||||
pub const Tokenizer = struct {
|
pub const Tokenizer = struct {
|
||||||
source: []const coral.io.Byte,
|
source: []const coral.io.Byte,
|
||||||
lines_stepped: usize = 1,
|
lines_stepped: usize = 1,
|
||||||
token: ?Token = null,
|
token: Token = .end,
|
||||||
|
|
||||||
const TokenTag = coral.io.Tag(Token);
|
pub fn skip_newlines(self: *Tokenizer) void {
|
||||||
|
|
||||||
pub fn is_token(self: *Tokenizer, token_tag: TokenTag) bool {
|
|
||||||
return if (self.token) |token| token == token_tag else false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_token_null_or(self: *Tokenizer, token_tag: TokenTag) bool {
|
|
||||||
return if (self.token) |token| token == token_tag else true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn skip(self: *Tokenizer, skip_token_tag: TokenTag) void {
|
|
||||||
self.step();
|
self.step();
|
||||||
|
|
||||||
while (self.token) |token| {
|
while (self.token == .newline) {
|
||||||
if (token != skip_token_tag) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.step();
|
self.step();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,7 +224,7 @@ pub const Tokenizer = struct {
|
||||||
else => break,
|
else => break,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.token = .{.special_identifier = self.source[begin .. cursor]};
|
self.token = .{.system_identifier = self.source[begin .. cursor]};
|
||||||
|
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
@ -253,7 +241,7 @@ pub const Tokenizer = struct {
|
||||||
else => cursor += 1,
|
else => cursor += 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.token = .{.special_identifier = self.source[begin .. cursor]};
|
self.token = .{.system_identifier = self.source[begin .. cursor]};
|
||||||
cursor += 1;
|
cursor += 1;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -429,7 +417,7 @@ pub const Tokenizer = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.token = null;
|
self.token = .end;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,19 +27,31 @@ fn kym_handle_errors(info: kym.ErrorInfo) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kym_log_info(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
|
fn kym_log_info(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
|
||||||
app.log_info(try kym.unbox_string(env, try env.view_arg(0)));
|
const argument = try env.expect(try env.arg(0));
|
||||||
|
|
||||||
|
defer env.discard(argument);
|
||||||
|
|
||||||
|
app.log_info(try env.unbox_string(argument));
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kym_log_warn(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
|
fn kym_log_warn(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
|
||||||
app.log_warn(try kym.unbox_string(env, try env.view_arg(0)));
|
const argument = try env.expect(try env.arg(0));
|
||||||
|
|
||||||
|
defer env.discard(argument);
|
||||||
|
|
||||||
|
app.log_warn(try env.unbox_string(argument));
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kym_log_fail(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
|
fn kym_log_fail(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
|
||||||
app.log_fail(try kym.unbox_string(env, try env.view_arg(0)));
|
const argument = try env.expect(try env.arg(0));
|
||||||
|
|
||||||
|
defer env.discard(argument);
|
||||||
|
|
||||||
|
app.log_fail(try env.unbox_string(argument));
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -63,21 +75,16 @@ pub fn run_app(file_access: file.Access) void {
|
||||||
|
|
||||||
defer script_env.free();
|
defer script_env.free();
|
||||||
|
|
||||||
script_env.bind_syscalls(&.{
|
kym.bind_syscaller(&script_env, "log_info", kym.Caller.from(kym_log_info)) catch {
|
||||||
.{
|
return app.log_fail("failed to bind `log_info` syscall");
|
||||||
.name = "log_info",
|
};
|
||||||
.caller = kym.Caller.from(kym_log_info),
|
|
||||||
},
|
kym.bind_syscaller(&script_env, "log_warn", kym.Caller.from(kym_log_warn)) catch {
|
||||||
.{
|
return app.log_fail("failed to bind `log_warn` syscall");
|
||||||
.name = "log_warn",
|
};
|
||||||
.caller = kym.Caller.from(kym_log_warn),
|
|
||||||
},
|
kym.bind_syscaller(&script_env, "log_fail", kym.Caller.from(kym_log_fail)) catch {
|
||||||
.{
|
return app.log_fail("failed to bind `log_fail` syscall");
|
||||||
.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{};
|
||||||
|
|
Loading…
Reference in New Issue