diff --git a/source/coral/arena.zig b/source/coral/arena.zig new file mode 100755 index 0000000..a9719bf --- /dev/null +++ b/source/coral/arena.zig @@ -0,0 +1,102 @@ +const debug = @import("./debug.zig"); + +const io = @import("./io.zig"); + +const list = @import("./list.zig"); + +const math = @import("./math.zig"); + +pub const Stacking = struct { + base_allocator: io.Allocator, + min_page_size: usize, + allocations: list.Stack(usize) = .{}, + pages: list.Stack(Page) = .{}, + + const Page = struct { + buffer: []u8, + used: usize, + + const Self = @This(); + + fn available(self: Self) usize { + return self.buffer.len - self.used; + } + }; + + pub fn allocate(self: *Stacking, allocation_size: usize) io.AllocationError![]u8 { + const alignment = @as(usize, 4); + const aligned_allocation_size = (allocation_size + alignment - 1) & ~(alignment - 1); + + if (self.pages.values.len == 0) { + const page = try self.allocate_page(math.max(self.min_page_size, aligned_allocation_size)); + + page.used = allocation_size; + + return page.buffer[0 .. allocation_size]; + } + + var page = self.current_page() orelse unreachable; + + if (page.available() <= aligned_allocation_size) { + page = try self.allocate_page(math.max(self.min_page_size, aligned_allocation_size)); + } + + debug.assert(page.available() >= allocation_size); + + defer page.used += aligned_allocation_size; + + return page.buffer[page.used .. (page.used + allocation_size)]; + } + + fn allocate_page(self: *Stacking, page_size: usize) io.AllocationError!*Page { + var buffer = try io.allocate_many(u8, page_size, self.base_allocator); + + errdefer io.deallocate(self.base_allocator, buffer); + + try self.pages.push_one(self.base_allocator, .{ + .buffer = buffer, + .used = 0, + }); + + return (self.current_page() orelse unreachable); + } + + pub fn as_allocator(self: *Stacking) io.Allocator { + return io.Allocator.bind(Stacking, self, struct { + fn reallocate(stacking: *Stacking, options: io.AllocationOptions) ?[]u8 { + const allocation = options.allocation orelse { + return stacking.allocate(options.size) catch null; + }; + + if (allocation.len == 0) { + return null; + } + + const reallocation = stacking.allocate(allocation.len) catch { + return null; + }; + + io.copy(reallocation, allocation); + + return reallocation; + } + }.reallocate); + } + + pub fn clear_allocations(self: *Stacking) void { + for (self.pages.values) |page| { + io.deallocate(self.base_allocator, page.buffer); + } + + self.pages.deinit(self.base_allocator); + self.allocations.deinit(self.base_allocator); + } + + fn current_page(self: Stacking) ?*Page { + if (self.pages.values.len == 0) { + return null; + } + + return &self.pages.values[self.pages.values.len - 1]; + } +}; diff --git a/source/coral/coral.zig b/source/coral/coral.zig index 9bb6c92..8f6450f 100755 --- a/source/coral/coral.zig +++ b/source/coral/coral.zig @@ -1,13 +1,13 @@ +/// +/// Arena-based memory allocation strategies. +/// +pub const arena = @import("./arena.zig"); + /// /// Debug build-only utilities and sanity-checkers. /// pub const debug = @import("./debug.zig"); -/// -/// Heap memory allocation strategies. -/// -pub const heap = @import("./heap.zig"); - /// /// Platform-agnostic data input and output operations. /// diff --git a/source/coral/heap.zig b/source/coral/heap.zig deleted file mode 100644 index 4eced2b..0000000 --- a/source/coral/heap.zig +++ /dev/null @@ -1,96 +0,0 @@ -const debug = @import("./debug.zig"); - -const io = @import("./io.zig"); - -const math = @import("./math.zig"); - -const table = @import("./table.zig"); - -pub const Bucketed = struct { - base_allocator: io.Allocator, - slab_table: SlabTable, - - const Slab = struct { - count: usize = 0, - buffer: []u8 = &.{}, - erased: []usize = &.{}, - - fn create(self: *Slab, allocator: io.Allocator) ?[]u8 { - if (self.count == self.erased.len) { - const buffer = io.allocate_many(allocator, u8, math.max(1, self.buffer.len * 2)) orelse return null; - - errdefer io.deallocate(allocator, buffer); - - const erased = io.allocate_many(allocator, usize, math.max(1, self.erased.len * 2)) orelse return null; - - errdefer io.deallocate(allocator, erased); - - self.buffer = buffer; - self.erased = erased; - } - - return null; - } - - fn destroy(self: *Slab) void { - _ = self; - } - }; - - const SlabTable = table.Hashed(table.unsigned_key(@bitSizeOf(usize)), *Slab); - - fn acquire_slab(self: *Bucketed, slab_element_size: usize) ?*Slab { - if (slab_element_size == 0) return null; - - return self.slab_table.lookup(slab_element_size) orelse create_slab: { - const allocated_slab = io.allocate_one(self.base_allocator, Slab); - - errdefer io.deallocate(self.base_allocator, allocated_slab); - - allocated_slab.* = .{.size = slab_element_size}; - - debug.assert(self.size_buckets.insert(slab_element_size, allocated_slab) catch return null); - - break: create_slab allocated_slab; - }; - } - - pub fn as_allocator(self: *Bucketed) io.Allocator { - return io.Allocator.bind(self, reallocate); - } - - pub fn deinit(self: *Bucketed) void { - var slab_iterator = SlabTable.Iterator{.table = self.slab_table}; - - while (slab_iterator.next()) |slab| { - slab.free(self.base_allocator); - } - - self.size_buckets.free(self.base_allocator); - } - - pub fn init(base_allocator: io.Allocator) io.AllocationError!Bucketed { - return Bucketed{ - .base_allocator = base_allocator, - .size_buckets = &.{}, - }; - } - - pub fn owns(self: Bucketed, memory: []const u8) bool { - return io.overlaps(memory.ptr, (self.slab_table.lookup(memory.len) orelse return false).buffer); - } - - pub fn reallocate(self: *Bucketed, options: io.AllocationOptions) ?[]u8 { - const origin_slab = self.acquire_slab(options.size) orelse return null; - const existing_allocation = options.allocation orelse return origin_slab.create(self.base_allocator); - - defer origin_slab.destroy(existing_allocation); - - const target_slab = self.acquire_slab(existing_allocation.len) orelse return null; - const updated_allocation = target_slab.create(existing_allocation.len); - - io.copy(updated_allocation, existing_allocation); - - return updated_allocation; - } -}; diff --git a/source/coral/io.zig b/source/coral/io.zig index 9437ebf..90cbb23 100755 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -11,31 +11,33 @@ pub const AllocationOptions = struct { size: usize, }; -pub const Allocator = Functor(?[]u8, AllocationOptions); +pub const Allocator = Generator(?[]u8, AllocationOptions); /// -/// Function pointer coupled with a state context for providing dynamic dispatch over a given `Input` and `Output`. +/// Function pointer coupled with an immutable state context for providing dynamic dispatch over a given `Input` and +/// `Output`. /// pub fn Functor(comptime Output: type, comptime Input: type) type { return struct { - context: *anyopaque, - invoker: *const fn (capture: *anyopaque, input: Input) Output, + context: *const anyopaque, + invoker: *const fn (capture: *const anyopaque, input: Input) Output, const Self = @This(); - pub fn bind(comptime State: type, state: *State, comptime invoker: fn (capture: *State, input: Input) Output) Self { + pub fn bind(comptime State: type, state: *const State, comptime invoker: fn (capture: *const State, input: Input) Output) Self { + const alignment = @alignOf(State); + const is_zero_aligned = alignment == 0; + return .{ - .context = state, + .context = if (is_zero_aligned) state else @ptrCast(*const anyopaque, state), .invoker = struct { - fn invoke_opaque(context: *anyopaque, input: Input) Output { - const state_alignment = @alignOf(State); - - if (state_alignment == 0) { - return invoker(@ptrCast(*State, context), input); + fn invoke_opaque(context: *const anyopaque, input: Input) Output { + if (is_zero_aligned) { + return invoker(@ptrCast(*const State, context), input); } - return invoker(@ptrCast(*State, @alignCast(state_alignment, context)), input); + return invoker(@ptrCast(*const State, @alignCast(alignment, context)), input); } }.invoke_opaque, }; @@ -47,7 +49,43 @@ pub fn Functor(comptime Output: type, comptime Input: type) type { }; } -pub const Reader = Functor(?usize, []u8); +/// +/// Function pointer coupled with a mutable state context for providing dynamic dispatch over a given `Input` and +/// `Output`. +/// +pub fn Generator(comptime Output: type, comptime Input: type) type { + return struct { + context: *anyopaque, + invoker: *const fn (capture: *anyopaque, input: Input) Output, + + const Self = @This(); + + pub fn bind(comptime State: type, state: *State, comptime invoker: fn (capture: *State, input: Input) Output) Self { + const alignment = @alignOf(State); + const is_zero_aligned = alignment == 0; + + return .{ + .context = if (is_zero_aligned) state else @ptrCast(*anyopaque, state), + + .invoker = struct { + fn invoke_opaque(context: *anyopaque, input: Input) Output { + if (is_zero_aligned) { + return invoker(@ptrCast(*State, context), input); + } + + return invoker(@ptrCast(*State, @alignCast(alignment, context)), input); + } + }.invoke_opaque, + }; + } + + pub fn invoke(self: Self, input: Input) Output { + return self.invoker(self.context, input); + } + }; +} + +pub const Reader = Generator(?usize, []u8); pub const StreamError = error { ReadFailure, @@ -66,7 +104,7 @@ pub const FixedBuffer = struct { slice: []u8, pub fn as_writer(self: *FixedBuffer) Writer { - return Writer.bind(self, struct { + return Writer.bind(FixedBuffer, self, struct { fn write(writable_memory: *FixedBuffer, data: []const u8) ?usize { return writable_memory.write(data); } @@ -104,7 +142,7 @@ pub const GrowingBuffer = struct { bytes: []const u8, }; - const Appender = Functor(AllocationError!void, AppendOptions); + const Appender = Generator(AllocationError!void, AppendOptions); pub fn as_writer(self: *GrowingBuffer) Writer { return Writer.bind(GrowingBuffer, self, struct { @@ -136,17 +174,13 @@ pub const GrowingBuffer = struct { } }; -pub const Writer = Functor(?usize, []const u8); +pub const Writer = Generator(?usize, []const u8); pub fn allocate_many(comptime Type: type, amount: usize, allocator: Allocator) AllocationError![]Type { if (@sizeOf(Type) == 0) { @compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type)); } - if (amount == 0) { - return &.{}; - } - return @ptrCast([*]Type, @alignCast(@alignOf(Type), allocator.invoke(.{.size = @sizeOf(Type) * amount}) orelse { return error.OutOfMemory; }))[0 .. amount]; @@ -191,46 +225,32 @@ pub fn compare(this: []const u8, that: []const u8) isize { return @intCast(isize, this.len) - @intCast(isize, that.len); } -pub fn deallocate(allocator: Allocator, allocation: anytype) void { - const Element = @TypeOf(allocation); - - switch (@typeInfo(Element).Pointer.size) { - .One => { - debug.assert(allocator.invoke(.{ - .allocation = @ptrCast([*]u8, allocation)[0 .. @sizeOf(Element)], - .size = 0 - }) == null); - }, - - .Slice => { - debug.assert(allocator.invoke(.{ - .allocation = @ptrCast([*]u8, allocation.ptr)[0 .. (@sizeOf(Element) * allocation.len)], - .size = 0 - }) == null); - }, - - .Many, .C => @compileError("length of allocation must be known to deallocate"), - } -} - -pub fn bytes_to(comptime Type: type, source_bytes: []const u8) ?Type { - const type_size = @sizeOf(Type); - - if (source_bytes.len != type_size) return null; - - var target_bytes = @as([type_size]u8, undefined); - - copy(&target_bytes, source_bytes); - - return @bitCast(Type, target_bytes); -} - pub fn copy(target: []u8, source: []const u8) void { var index: usize = 0; while (index < source.len) : (index += 1) target[index] = source[index]; } +pub fn deallocate(allocator: Allocator, allocation: anytype) void { + const Allocation = @TypeOf(allocation); + + switch (@typeInfo(Allocation)) { + .Pointer => |allocation_pointer| { + _ = allocator.invoke(.{ + .allocation = switch (allocation_pointer.size) { + .One => @ptrCast([*]u8, allocation)[0 .. @sizeOf(Allocation)], + .Slice => @ptrCast([*]u8, allocation.ptr)[0 .. (@sizeOf(Allocation) * allocation.len)], + .Many, .C => @compileError("length of allocation must be known to deallocate"), + }, + + .size = 0, + }); + }, + + else => @compileError("cannot deallocate " ++ allocation), + } +} + pub fn ends_with(target: []const u8, match: []const u8) bool { if (target.len < match.len) return false; @@ -274,10 +294,6 @@ pub const null_writer = Writer.bind(&null_context, struct { } }.write); -pub fn overlaps(pointer: [*]u8, memory_range: []u8) bool { - return (pointer >= memory_range.ptr) and (pointer < (memory_range.ptr + memory_range.len)); -} - pub fn reallocate(allocator: Allocator, allocation: anytype, amount: usize) AllocationError![]@typeInfo(@TypeOf(allocation)).Pointer.child { const pointer_info = @typeInfo(@TypeOf(allocation)).Pointer; const Element = pointer_info.child; @@ -319,6 +335,10 @@ pub fn swap(comptime Element: type, this: *Element, that: *Element) void { that.* = temp; } +pub fn tag_of(comptime value: anytype) Tag(@TypeOf(value)) { + return @as(Tag(@TypeOf(value)), value); +} + pub fn zero(target: []u8) void { for (target) |*t| t.* = 0; } diff --git a/source/coral/list.zig b/source/coral/list.zig index 6cdcba2..4968526 100755 --- a/source/coral/list.zig +++ b/source/coral/list.zig @@ -9,8 +9,8 @@ const math = @import("./math.zig"); /// pub fn Stack(comptime Value: type) type { return struct { - capacity: usize, - values: []Value, + capacity: usize = 0, + values: []Value = &.{}, /// /// Stack type. @@ -23,8 +23,8 @@ pub fn Stack(comptime Value: type) type { /// The returned buffer may be used to write to the stack without needing to explicitly pass an allocator /// context, as well decay further into a generic [io.Writer] type. /// - /// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize - /// `self`. + /// *Note* if `capacity` is a non-zero value, `allocator` must reference the same allocation strategy as the one + /// originally used to allocate the current internal buffer. /// pub fn as_buffer(self: *Self, allocator: io.Allocator) io.GrowingBuffer { return io.GrowingBuffer.bind(Self, allocator, self, push_all); @@ -44,12 +44,17 @@ pub fn Stack(comptime Value: type) type { /// /// To clear all items from the stack while preserving the current internal buffer, see [clear] instead. /// - /// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize - /// `self`. + /// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation + /// strategy as the one originally used to allocate the current internal buffer. /// pub fn deinit(self: *Self, allocator: io.Allocator) void { + if (self.capacity == 0) { + return; + } + io.deallocate(allocator, self.values); + self.values = &.{}; self.capacity = 0; } @@ -77,8 +82,8 @@ pub fn Stack(comptime Value: type) type { /// Growing ahead of pushing operations is useful when the upper bound of pushes is well-understood, as it can /// reduce the number of allocations required per push. /// - /// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize - /// `self`. + /// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation + /// strategy as the one originally used to allocate the current internal buffer. /// pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void { const grown_capacity = self.capacity + growth_amount; @@ -86,34 +91,18 @@ pub fn Stack(comptime Value: type) type { errdefer io.deallocate(allocator, values); - for (0 .. self.values.len) |index| { - values[index] = self.values[index]; - } + if (self.capacity != 0) { + for (0 .. self.values.len) |index| { + values[index] = self.values[index]; + } - io.deallocate(allocator, self.values); + io.deallocate(allocator, self.values); + } self.values = values; self.capacity = grown_capacity; } - /// - /// Attempts to allocate and return an empty stack with an internal buffer of `initial_capacity` size using - /// `allocator` as the memory allocation strategy. - /// - /// The function returns [io.AllocationError] if `allocator` could not commit the memory required for an - /// internal buffer of `initial_capacity` size. - /// - pub fn init(allocator: io.Allocator, initial_capacity: usize) !Self { - const values = try io.allocate_many(Value, initial_capacity, allocator); - - errdefer io.deallocate(values); - - return Self{ - .capacity = initial_capacity, - .values = values[0 .. 0], - }; - } - /// /// Attempts to remove the last element of `self` that was inserted, if one exists, returning it or `null` if /// `self` is empty. @@ -137,14 +126,14 @@ pub fn Stack(comptime Value: type) type { /// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the /// internal buffer of `self` when necessary. /// - /// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize - /// `self`. + /// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation + /// strategy as the one originally used to allocate the current internal buffer. /// pub fn push_all(self: *Self, allocator: io.Allocator, values: []const Value) io.AllocationError!void { const new_length = self.values.len + values.len; if (new_length >= self.capacity) { - try self.grow(allocator, math.min(new_length, self.capacity)); + try self.grow(allocator, values.len + values.len); } const offset_index = self.values.len; @@ -163,14 +152,14 @@ pub fn Stack(comptime Value: type) type { /// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the /// internal buffer of `self` when necessary. /// - /// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize - /// `self`. + /// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation + /// strategy as the one originally used to allocate the current internal buffer. /// pub fn push_many(self: *Self, allocator: io.Allocator, value: Value, amount: usize) io.AllocationError!void { const new_length = self.values.len + amount; if (new_length >= self.capacity) { - try self.grow(allocator, math.max(usize, new_length, self.capacity)); + try self.grow(allocator, amount + amount); } const offset_index = self.values.len; @@ -189,8 +178,8 @@ pub fn Stack(comptime Value: type) type { /// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the /// internal buffer of `self` when necessary. /// - /// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize - /// `self`. + /// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation + /// strategy as the one originally used to allocate the current internal buffer. /// pub fn push_one(self: *Self, allocator: io.Allocator, value: Value) io.AllocationError!void { if (self.values.len == self.capacity) { diff --git a/source/coral/slab.zig b/source/coral/slab.zig index 09c994d..0bf4dee 100644 --- a/source/coral/slab.zig +++ b/source/coral/slab.zig @@ -6,8 +6,8 @@ const io = @import("./io.zig"); pub fn Map(comptime Index: type, comptime Element: type) type { return struct { - free_index: Index, - entries: []Entry, + free_index: Index = 0, + entries: []Entry = &.{}, const Entry = union (enum) { free_index: usize, @@ -28,17 +28,6 @@ pub fn Map(comptime Index: type, comptime Element: type) type { io.deallocate(allocator, self.entries); } - pub fn init(allocator: io.Allocator) io.AllocationError!Self { - const entries = try io.allocate_many(Entry, 4, allocator); - - errdefer io.deallocate(allocator, entries); - - return Self{ - .free_index = 0, - .entries = entries, - }; - } - pub fn insert(self: *Self, allocator: io.Allocator, value: Element) io.AllocationError!Index { _ = self; _ = allocator; diff --git a/source/coral/table.zig b/source/coral/table.zig index ae0a0ae..aba75bb 100755 --- a/source/coral/table.zig +++ b/source/coral/table.zig @@ -22,8 +22,8 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke const growth_factor = 0.6; return struct { - count: usize, - table: []?Entry, + count: usize = 0, + table: []?Entry = &.{}, /// /// Key-value pair bundling. @@ -82,7 +82,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke /// pub fn assign(self: *Self, allocator: io.Allocator, key: Key, value: Value) io.AllocationError!?Entry { if (self.calculate_load_factor() >= load_max) { - const growth_size = @intToFloat(f64, self.table.len) * growth_factor; + const growth_size = @intToFloat(f64, math.max(1, self.table.len)) * growth_factor; if (growth_size > math.max_int(@typeInfo(usize).Int)) { return error.OutOfMemory; @@ -129,7 +129,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke /// Returns the calculated load factor of `self` at the moment. /// pub fn calculate_load_factor(self: Self) f32 { - return @intToFloat(f32, self.count) / @intToFloat(f32, self.table.len); + return if (self.table.len == 0) 1 else @intToFloat(f32, self.count) / @intToFloat(f32, self.table.len); } /// @@ -160,28 +160,6 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke self.count = 0; } - /// - /// Attempts to allocate and return an empty table with an implementation-defined initial capacity using - /// `allocator` as the memory allocation strategy. - /// - /// The function returns [AllocationError] instead if `allocator` cannot commit the memory required for the - /// table capcity size. - /// - pub fn init(allocator: io.Allocator) io.AllocationError!Self { - const table = try io.allocate_many(?Entry, 4, allocator); - - errdefer io.deallocate(allocator, table); - - for (table) |*entry| { - entry.* = null; - } - - return Self{ - .table = table, - .count = 0, - }; - } - /// /// Attempts to write the `key`-`value` pair into `self`, using `allocator` as the memory allocation strategy, /// if no value already exists with a matching `key`, returning `true` if it was inserted, otherwise `false`. @@ -194,17 +172,16 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke /// pub fn insert(self: *Self, allocator: io.Allocator, key: Key, value: Value) io.AllocationError!bool { if (self.calculate_load_factor() >= load_max) { - const growth_size = @intToFloat(f64, self.table.len) * growth_factor; + const growth_amount = @intToFloat(f64, self.table.len) * growth_factor; + const min_size = 1; - if (growth_size > math.max_int(@typeInfo(usize).Int)) { - return error.OutOfMemory; - } - - try self.rehash(allocator, @floatToInt(usize, growth_size)); + try self.rehash(allocator, self.table.len + math.max(min_size, @floatToInt(usize, growth_amount))); } debug.assert(self.table.len > self.count); + defer self.count += 1; + return (Entry{ .key = key, .value = value, @@ -222,8 +199,9 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke { const hash_max = math.min(math.max_int(hash_info), self.table.len); var hashed_key = math.wrap(keyer.hasher(key), math.min_int(hash_info), hash_max); + var iterations = @as(usize, 0); - while (true) { + while (iterations < self.count) : (iterations += 1) { const entry = &(self.table[hashed_key] orelse return null); if (keyer.comparer(entry.key, key) == 0) { @@ -260,13 +238,16 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke entry.* = null; } - for (old_table) |maybe_entry| { - if (maybe_entry) |entry| { - debug.assert(entry.write_into(self.table)); + if (old_table.len != 0) + { + for (old_table) |maybe_entry| { + if (maybe_entry) |entry| { + debug.assert(entry.write_into(self.table)); + } } - } - io.deallocate(allocator, old_table); + io.deallocate(allocator, old_table); + } } }; } diff --git a/source/coral/utf8.zig b/source/coral/utf8.zig index f9b37ca..4b448ba 100755 --- a/source/coral/utf8.zig +++ b/source/coral/utf8.zig @@ -1,19 +1,26 @@ -const std = @import("std"); +const debug = @import("./debug.zig"); const io = @import("./io.zig"); const math = @import("./math.zig"); -/// -/// Errors that may occur during utf8-encoded int parsing. -/// -pub const IntParseError = math.CheckedArithmeticError || ParseError; +const std = @import("std"); /// -/// Optional rules for int parsing logic to consider during parsing. /// -pub const IntParseOptions = struct { +/// +pub const DecimalFormat = struct { delimiter: []const u8 = "", + positive_prefix: enum {none, plus, space} = .none, +}; + +/// +/// +/// +pub const HexadecimalFormat = struct { + delimiter: []const u8 = "", + positive_prefix: enum {none, plus, space} = .none, + casing: enum {lower, upper} = .lower, }; /// @@ -32,107 +39,104 @@ pub const PrintError = error { }; /// -/// Attempts to parse a float value of type described by `float` from `utf8`. /// -/// The function returns a [ParseError] if `utf8` does not conform to the syntax of a float. /// -pub fn parse_float(comptime float: std.builtin.Type.Float, utf8: []const u8) ParseError!math.Float(float) { - // "" +pub fn parse_decimal(comptime Decimal: type, utf8: []const u8, format: DecimalFormat) !Decimal { if (utf8.len == 0) { return error.BadSyntax; } - const is_negative = utf8[0] == '-'; + switch (@typeInfo(Decimal)) { + .Int => |int| { + var has_sign = switch (utf8[0]) { + '-', '+', ' ' => true, + else => false, + }; - // "-" - if (is_negative and (utf8.len == 1)) { - return error.BadSyntax; - } + var result = @as(Decimal, 0); - const negative_offset = @boolToInt(is_negative); - var has_decimal = utf8[negative_offset] == '.'; + for (@boolToInt(has_sign) .. utf8.len) |index| { + const radix = 10; + const code = utf8[index]; - // "-." - if (has_decimal and (utf8.len == 2)) { - return error.BadSyntax; - } + switch (code) { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { + result = try math.checked_add( + try math.checked_mul(result, radix), + try math.checked_sub(code, '0')); + }, - const Float = math.Float(float); - var result: Float = 0; - var factor: Float = 1; + else => { + if (format.delimiter.len == 0 or !io.equals(format.delimiter, utf8[index ..])) { + return error.BadSyntax; + } + }, + } + } - for (utf8[0 .. negative_offset + @boolToInt(has_decimal)]) |code| switch (code) { - '.' => { - if (has_decimal) return error.BadSyntax; + switch (int.signedness) { + .signed => { + return result * @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1); + }, - has_decimal = true; + .unsigned => { + if (has_sign and utf8[0] == '-') { + return error.OutOfMemory; + } + + return result; + }, + } }, - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { - if (has_decimal) factor /= 10.0; + .Float => { + // "" + if (utf8.len == 0) { + return error.BadSyntax; + } - result = ((result * 10.0) + @intToFloat(Float, code - '0')); + var has_sign = switch (utf8[0]) { + '-', '+', ' ' => true, + else => false, + }; + + // "-" + if (has_sign and utf8.len == 1) { + return error.BadSyntax; + } + + const sign_offset = @boolToInt(has_sign); + var has_decimal = utf8[sign_offset] == '.'; + + // "-." + if (has_decimal and (utf8.len == 2)) { + return error.BadSyntax; + } + + var result = @as(Decimal, 0); + var factor = @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1); + + for (utf8[0 .. (sign_offset + @boolToInt(has_decimal))]) |code| switch (code) { + '.' => { + if (has_decimal) return error.BadSyntax; + + has_decimal = true; + }, + + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { + if (has_decimal) factor /= 10.0; + + result = ((result * 10.0) + @intToFloat(Decimal, code - '0')); + }, + + else => return error.BadSyntax, + }; + + return result * factor; }, - else => return error.BadSyntax, - }; - - return result * factor; -} - -/// -/// Attempts to parse an int value of type described by `int` from `utf8`, with `options` as additional rules for the -/// parsing logic to consider. -/// -/// The function returns a [IntParseError] if `utf8` does not conform to the syntax of a float, does not match the rules -/// specified in `option`, or exceeds the maximum size of the int described by `int`. -/// -pub fn parse_int( - comptime int: std.builtin.Type.Int, - utf8: []const u8, - options: IntParseOptions) IntParseError!math.Int(int) { - - if (utf8.len == 0) { - return error.BadSyntax; + else => @compileError("`" ++ @typeName(Decimal) ++ "` cannot be formatted as a decimal string"), } - - { - const is_negative = utf8[0] == '-'; - - switch (int.signedness) { - .signed => { - if (is_negative and utf8.len == 1) { - return error.BadSyntax; - } - }, - - .unsigned => { - if (is_negative) { - return error.BadSyntax; - } - }, - } - } - - var result = @as(math.Int(int), 0); - - for (0 .. utf8.len) |index| { - const code = utf8[index]; - - switch (code) { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { - result = try math.checked_add(try math.checked_mul(result, 10), try math.checked_sub(code, '0')); - }, - - else => { - if (options.delimiter.len == 0 or !io.equals(options.delimiter, utf8[index ..])) { - return error.BadSyntax; - } - }, - } - } - - return result; } /// @@ -147,40 +151,148 @@ pub fn print(writer: io.Writer, utf8: []const u8) PrintError!void { } /// -/// Attempts to print the int `value` described by `int` to `writer`. /// -/// The function returns [PrintError] if the write failed to complete partially or entirely. /// -pub fn print_int(comptime int: std.builtin.Type.Int, writer: io.Writer, value: math.Int(int)) PrintError!void { - if (value == 0) { - return try print(writer, "0"); +pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments: anytype) PrintError!void { + switch (@typeInfo(@TypeOf(arguments))) { + .Struct => |arguments_struct| { + comptime var arg_index = 0; + comptime var head = 0; + comptime var tail = 0; + + inline while (tail < format.len) : (tail += 1) { + if (format[tail] == '{') { + if (tail > format.len) { + @compileError("expected an idenifier after opening `{`"); + } + + tail += 1; + + switch (format[tail]) { + '{' => { + try print(writer, format[head .. (tail - 1)]); + + tail += 1; + head = tail; + }, + + '}' => { + if (!arguments_struct.is_tuple) { + @compileError("all format specifiers must be named when using a named struct"); + } + + try print(writer, arguments[arg_index]); + + arg_index += 1; + tail += 1; + head = tail; + }, + + else => { + if (arguments_struct.is_tuple) { + @compileError("format specifiers cannot be named when using a tuple struct"); + } + + try print(writer, format[head .. (tail - 1)]); + + head = tail; + tail += 1; + + if (tail >= format.len) { + @compileError("expected closing `}` or another `{` after opening `{`"); + } + + debug.assert(tail < format.len); + + inline while (format[tail] != '}') { + tail += 1; + + debug.assert(tail < format.len); + } + + try print_value(writer, @field(arguments, format[head .. tail])); + + tail += 1; + head = tail; + } + } + } + } + }, + + else => @compileError("`arguments` must be a struct type"), } - - // TODO: Don't make this buffer arbitrarily size cause big int types WILL overflow. - var buffer = [_]u8{0} ** 40; - var buffer_count: usize = 0; - var split_value = value; - - if ((int.signedness == .unsigned) and (value < 0)) { - buffer[0] = '-'; - buffer_count += 1; - } - - while (split_value != 0) : (buffer_count += 1) { - const radix = 10; - - buffer[buffer_count] = @intCast(u8, (split_value % radix) + '0'); - split_value = (split_value / radix); - } - - { - const half_buffer_count = buffer_count / 2; - var index: usize = 0; - - while (index < half_buffer_count) : (index += 1) { - io.swap(u8, &buffer[index], &buffer[buffer_count - index - 1]); - } - } - - try print(writer, buffer[0 .. buffer_count]); +} + +/// +/// +/// +pub fn print_decimal(writer: io.Writer, value: anytype, format: DecimalFormat) PrintError!void { + if (value == 0) { + return print(writer, switch (format.positive_prefix) { + .none => "0", + .plus => "+0", + .space => " 0", + }); + } + + switch (@typeInfo(@TypeOf(value))) { + .Int => |int| { + const radix = 10; + var buffer = [_]u8{0} ** (1 + math.max(int.bits, 1)); + var buffer_start = buffer.len - 1; + + { + var decomposable_value = value; + + while (decomposable_value != 0) : (buffer_start -= 1) { + buffer[buffer_start] = @intCast(u8, (decomposable_value % radix) + '0'); + decomposable_value = (decomposable_value / radix); + } + } + + if (int.signedness == .unsigned and value < 0) { + buffer[buffer_start] = '-'; + } else { + switch (format.positive_prefix) { + .none => buffer_start += 1, + .plus => buffer[buffer_start] = '+', + .space => buffer[buffer_start] = ' ', + } + } + + try print(writer, buffer[buffer_start ..]); + }, + + else => @compileError("`arguments` must be a struct type"), + } +} + +pub fn print_hexadecimal(writer: io.Writer, value: anytype, format: HexadecimalFormat) PrintError!void { + // TODO: Implement. + _ = writer; + _ = value; + _ = format; + + unreachable; +} + +noinline fn print_value(writer: io.Writer, value: anytype) PrintError!void { + const Value = @TypeOf(value); + + return switch (@typeInfo(Value)) { + .Int => print_decimal(writer, value, .{}), + .Float => print_decimal(writer, value, .{}), + + .Pointer => |pointer| switch (pointer.size) { + .One, .Many, .C => print_hexadecimal(writer, @ptrToInt(value), .{}), + .Slice => if (pointer.child == u8) print(writer, value) else @compileError(unformattableMessage(Value)), + }, + + else => @compileError(unformattableMessage(Value)), + }; +} + +fn unformattableMessage(comptime Value: type) []const u8 { + return "`" ++ @typeName(Value) ++ "` are not formattable"; } diff --git a/source/ona/heap.zig b/source/ona/heap.zig index 14cfd1a..25923f0 100644 --- a/source/ona/heap.zig +++ b/source/ona/heap.zig @@ -7,15 +7,23 @@ const Context = struct { const Self = @This(); + const empty_allocation = [0]u8{}; + fn reallocate(self: *Self, options: coral.io.AllocationOptions) ?[]u8 { if (options.size == 0) { if (options.allocation) |allocation| { - ext.SDL_free(allocation.ptr); + if (allocation.ptr != &empty_allocation) { + ext.SDL_free(allocation.ptr); + } self.live_allocations -= 1; + + return null; } - return null; + self.live_allocations += 1; + + return &empty_allocation; } if (options.allocation) |allocation| { diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig new file mode 100755 index 0000000..c7ab731 --- /dev/null +++ b/source/ona/kym/Ast.zig @@ -0,0 +1,369 @@ +const coral = @import("coral"); + +const tokens = @import("./tokens.zig"); + +const types = @import("./types.zig"); + +allocator: coral.io.Allocator, +arena: coral.arena.Stacking, +statements: StatementList, +error_message: []const u8, + +pub const BinaryOperator = enum { + addition, + subtraction, + multiplication, + divsion, + equals_comparison, + greater_than_comparison, + greater_equals_comparison, + less_than_comparison, + less_equals_comparison, + + fn token(self: BinaryOperator) tokens.Token { + return switch (self) { + .addition => .symbol_plus, + .subtraction => .symbol_minus, + .multiplication => .symbol_asterisk, + .divsion => .symbol_forward_slash, + .equals_comparison => .symbol_double_equals, + .greater_than_comparison => .symbol_greater_than, + .greater_equals_comparison => .symbol_greater_equals, + .less_than_comparison => .symbol_less_than, + .less_equals_comparison => .symbol_less_equals, + }; + } +}; + +pub const Expression = union (enum) { + nil_literal, + true_literal, + false_literal, + integer_literal: types.Integer, + float_literal: types.Float, + string_literal: []const u8, + array_literal: coral.list.Stack(Expression), + + table_literal: coral.list.Stack(struct { + identifier: []const u8, + expression: Expression, + }), + + grouped_expression: *Expression, + + binary_operation: struct { + operator: BinaryOperator, + lhs_expression: *Expression, + rhs_expression: *Expression, + }, + + unary_operation: struct { + operator: UnaryOperator, + expression: *Expression, + }, +}; + +const ExpressionParser = fn (self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression; + +const Self = @This(); + +pub const Statement = union (enum) { + return_expression: Expression, + return_nothing, +}; + +const StatementList = coral.list.Stack(Statement); + +const UnaryOperator = enum { + boolean_negation, + numeric_negation, +}; + +fn binary_operation_parser(comptime parse_next: ExpressionParser, comptime operators: []const BinaryOperator) ExpressionParser { + return struct { + fn parse(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression { + var expression = try parse_next(self, tokenizer); + + { + const allocator = self.arena.as_allocator(); + + inline for (operators) |operator| { + const token = comptime operator.token(); + + if (tokenizer.current_token == coral.io.tag_of(token)) { + try self.check_syntax( + tokenizer.step(.{.include_newlines = true}), + "expected right-hand side of expression after `" ++ comptime token.text() ++ "`"); + + expression = .{ + .binary_operation = .{ + .operator = operator, + .lhs_expression = try coral.io.allocate_one(allocator, expression), + .rhs_expression = try coral.io.allocate_one(allocator, try parse_next(self, tokenizer)), + }, + }; + } + } + } + + return expression; + } + }.parse; +} + +fn check_syntax(self: *Self, condition: bool, error_message: []const u8) types.ParseError!void { + if (condition) { + return; + } + + return self.fail_syntax(error_message); +} + +pub fn deinit(self: *Self) void { + self.arena.clear_allocations(); + self.statements.deinit(self.allocator); +} + +fn fail_syntax(self: *Self, error_message: []const u8) types.ParseError { + self.error_message = error_message; + + return error.BadSyntax; +} + +pub fn init(allocator: coral.io.Allocator) coral.io.AllocationError!Self { + return Self{ + .arena = .{ + .base_allocator = allocator, + .min_page_size = 4096, + }, + + .allocator = allocator, + .statements = .{}, + .error_message = "", + }; +} + +pub fn list_statements(self: Self) []const Statement { + return self.statements.values; +} + +pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!void { + self.reset(); + + errdefer self.reset(); + + var has_not_returned_yet = true; + + while (tokenizer.step(.{.include_newlines = false})) { + switch (tokenizer.current_token) { + .keyword_return => { + try self.check_syntax(has_not_returned_yet, "cannot return more than once per function scope"); + + try self.statements.push_one(self.allocator, get_statement: { + if (tokenizer.step(.{.include_newlines = true})) { + if (tokenizer.current_token != .newline) { + break: get_statement .{.return_expression = try self.parse_expression(tokenizer)}; + } + + if (tokenizer.step(.{.include_newlines = true})) { + try self.check_syntax( + tokenizer.current_token == .newline, + "expected end of declaration after return expression"); + } + } + + break: get_statement .return_nothing; + }); + + has_not_returned_yet = false; + }, + + else => return self.fail_syntax("invalid statement"), + } + } +} + +const parse_comparison = binary_operation_parser(parse_term, &.{ + .greater_than_comparison, + .greater_equals_comparison, + .less_than_comparison, + .less_equals_comparison +}); + +const parse_equality = binary_operation_parser(parse_comparison, &.{ + .equals_comparison, +}); + +const parse_expression = binary_operation_parser(parse_equality, &.{ + .addition, + .subtraction, +}); + +fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) types.ParseError!Expression { + switch (tokenizer.current_token) { + .symbol_paren_left => { + try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "expected an expression after `(`"); + + const expression = try self.parse_expression(tokenizer); + + try self.check_syntax( + tokenizer.step(.{.include_newlines = false}) and tokenizer.current_token == .symbol_paren_right, + "expected a closing `)` after expression"); + + return Expression{.grouped_expression = try coral.io.allocate_one(self.arena.as_allocator(), expression)}; + }, + + .integer => |value| { + _ = tokenizer.step(.{.include_newlines = false}); + + return Expression{ + .integer_literal = coral.utf8.parse_decimal(types.Integer, value, .{}) catch |parse_error| { + return self.fail_syntax(switch (parse_error) { + error.BadSyntax => "invalid integer literal", + error.IntOverflow => "integer literal is too big", + }); + }, + }; + }, + + .real => |value| { + _ = tokenizer.step(.{.include_newlines = false}); + + return Expression{ + .float_literal = coral.utf8.parse_decimal(types.Float, value, .{}) catch |parse_error| { + return self.fail_syntax(switch (parse_error) { + error.BadSyntax => "invalid float literal", + }); + }, + }; + }, + + .string => |value| { + _ = tokenizer.step(.{.include_newlines = false}); + + return Expression{.string_literal = value}; + }, + + .symbol_bracket_left => { + try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of array literal"); + + var expression = Expression{.array_literal = .{}}; + + coral.debug.assert(expression == .array_literal); + + const allocator = self.arena.as_allocator(); + const array_average_maximum = 32; + + try expression.array_literal.grow(allocator, array_average_maximum); + + while (true) { + switch (tokenizer.current_token) { + .symbol_bracket_right => { + _ = tokenizer.step(.{.include_newlines = false}); + + return expression; + }, + + else => { + try self.check_syntax( + tokenizer.step(.{.include_newlines = false}), + "expected `]` or expression after `[`"); + + try expression.array_literal.push_one(allocator, try self.parse_expression(tokenizer)); + }, + } + } + }, + + .symbol_brace_left => { + try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end of table literal"); + + var expression = Expression{.table_literal = .{}}; + + coral.debug.assert(expression == .table_literal); + + const allocator = self.arena.as_allocator(); + + while (true) { + switch (tokenizer.current_token) { + .symbol_brace_right => { + _ = tokenizer.step(.{.include_newlines = false}); + + return expression; + }, + + .local => |identifier| { + try self.check_syntax( + tokenizer.step(.{.include_newlines = false}) and tokenizer.current_token == .symbol_equals, + "expected `=` after identifier"); + + try self.check_syntax(tokenizer.step(.{.include_newlines = false}), "unexpected end after `=`"); + + try expression.table_literal.push_one(allocator, .{ + .identifier = identifier, + .expression = try self.parse_expression(tokenizer), + }); + + switch (tokenizer.current_token) { + .symbol_comma => _ = tokenizer.step(.{.include_newlines = false}), + + .symbol_brace_right => { + _ = tokenizer.step(.{.include_newlines = false}); + + return expression; + }, + + else => return self.fail_syntax("expected `,` or `}` after expression"), + } + }, + + else => return self.fail_syntax("expected `}` or fields in table literal"), + } + } + }, + + .symbol_minus => { + try self.check_syntax( + tokenizer.step(.{.include_newlines = false}), + "expected expression after numeric negation (`-`)"); + + return Expression{ + .unary_operation = .{ + .expression = try coral.io.allocate_one( + self.arena.as_allocator(), + try self.parse_factor(tokenizer)), + + .operator = .numeric_negation, + }, + }; + }, + + .symbol_bang => { + try self.check_syntax( + tokenizer.step(.{.include_newlines = false}), + "expected expression after numeric negation (`!`)"); + + return Expression{ + .unary_operation = .{ + .expression = try coral.io.allocate_one( + self.arena.as_allocator(), + try self.parse_factor(tokenizer)), + + .operator = .boolean_negation, + }, + }; + }, + + else => return self.fail_syntax("unexpected token in expression"), + } +} + +const parse_term = binary_operation_parser(parse_factor, &.{ + .multiplication, + .divsion, +}); + +pub fn reset(self: *Self) void { + self.statements.clear(); + self.arena.clear_allocations(); +} diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index fad11ce..30add13 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -1,6 +1,6 @@ -const Environment = @import("./Environment.zig"); +const Ast = @import("./Ast.zig"); -const ast = @import("./ast.zig"); +const Environment = @import("./Environment.zig"); const coral = @import("coral"); @@ -25,6 +25,7 @@ const Opcode = enum (u8) { push_integer, push_float, push_object, + push_array, push_table, not, @@ -50,54 +51,45 @@ fn clear_error_details(self: *Self) void { } pub fn compile(self: *Self, data: []const u8) types.RuntimeError!void { - var tokenizer = tokens.Tokenizer{.source = data}; - var parsed_statements = try ast.ParsedStatements.init(self.env.allocator, &tokenizer); + var ast = try Ast.init(self.env.allocator); - switch (parsed_statements) { - .valid => |*statements| { - defer statements.deinit(self.env.allocator); + errdefer ast.deinit(); - for (statements.list.values) |statement| { - switch (statement) { - .return_expression => |return_expression| { - try self.compile_expression(return_expression); - try self.emit_opcode(.ret); - }, + { + var tokenizer = tokens.Tokenizer{.source = data}; - .return_nothing => { - try self.emit_opcode(.push_nil); - try self.emit_opcode(.ret); - }, - } + ast.parse(&tokenizer) catch |init_error| { + if (init_error == error.BadSyntax) { + self.clear_error_details(); + + var message_buffer = self.message_data.as_buffer(self.env.allocator); + + coral.utf8.print_formatted(message_buffer.as_writer(), "@({line}): {name}", .{ + .line = tokenizer.lines_stepped, + .name = ast.error_message, + }) catch return error.OutOfMemory; } - }, - .invalid => |invalid| { - self.clear_error_details(); + return init_error; + }; + } - try self.message_data.push_all(self.env.allocator, "@("); + for (ast.list_statements()) |statement| { + switch (statement) { + .return_expression => |return_expression| { + try self.compile_expression(return_expression); + try self.emit_opcode(.ret); + }, - var message_buffer = self.message_data.as_buffer(self.env.allocator); - const message_writer = message_buffer.as_writer(); - - coral.utf8.print_int(@typeInfo(usize).Int, message_writer, tokenizer.lines_stepped) catch { - return error.OutOfMemory; - }; - - coral.utf8.print(message_writer, "): ") catch { - return error.OutOfMemory; - }; - - coral.utf8.print(message_writer, invalid) catch { - return error.OutOfMemory; - }; - - return error.BadSyntax; - }, + .return_nothing => { + try self.emit_opcode(.push_nil); + try self.emit_opcode(.ret); + }, + } } } -pub fn compile_expression(self: *Self, expression: ast.Expression) types.RuntimeError!void { +pub fn compile_expression(self: *Self, expression: Ast.Expression) types.RuntimeError!void { switch (expression) { .nil_literal => try self.emit_opcode(.push_nil), .true_literal => try self.emit_opcode(.push_true), @@ -126,33 +118,46 @@ pub fn compile_expression(self: *Self, expression: ast.Expression) types.Runtime try self.emit_object(try self.intern(literal)); }, - .table_literal => |literal| { - if (literal.values.len > coral.math.max_int(@typeInfo(types.Integer).Int)) { + .array_literal => |elements| { + if (elements.values.len > coral.math.max_int(@typeInfo(types.Integer).Int)) { return error.OutOfMemory; } - for (literal.values) |field| { - try self.compile_expression(field.expression.*); + for (elements.values) |element_expression| { + try self.compile_expression(element_expression); + } + + try self.emit_opcode(.push_array); + try self.emit_integer(@intCast(types.Integer, elements.values.len)); + }, + + .table_literal => |fields| { + if (fields.values.len > coral.math.max_int(@typeInfo(types.Integer).Int)) { + return error.OutOfMemory; + } + + for (fields.values) |field| { + try self.compile_expression(field.expression); try self.emit_opcode(.push_object); try self.emit_object(try self.intern(field.identifier)); } try self.emit_opcode(.push_table); - try self.emit_integer(@intCast(types.Integer, literal.values.len)); + try self.emit_integer(@intCast(types.Integer, fields.values.len)); }, .binary_operation => |operation| { try self.compile_expression(operation.lhs_expression.*); try self.compile_expression(operation.rhs_expression.*); - try self.emit_opcode(switch (operation.kind) { + try self.emit_opcode(switch (operation.operator) { .addition => .add, .subtraction => .sub, .multiplication => .mul, - .division => .div, - .equality_comparison => .compare_eq, + .divsion => .div, + .greater_equals_comparison => .compare_eq, .greater_than_comparison => .compare_gt, - .greater_equals_comparison => .compare_ge, + .equals_comparison => .compare_ge, .less_than_comparison => .compare_lt, .less_equals_comparison => .compare_le, }); @@ -161,7 +166,7 @@ pub fn compile_expression(self: *Self, expression: ast.Expression) types.Runtime .unary_operation => |operation| { try self.compile_expression(operation.expression.*); - try self.emit_opcode(switch (operation.kind) { + try self.emit_opcode(switch (operation.operator) { .boolean_negation => .not, .numeric_negation => .neg, }); @@ -199,24 +204,20 @@ pub fn emit_opcode(self: *Self, opcode: Opcode) coral.io.AllocationError!void { pub fn error_details(self: Self) []const u8 { coral.debug.assert(self.message_data.values.len >= self.message_name_len); - return self.message_data.values[self.message_name_len .. ]; + return self.message_data.values; } pub fn init(env: *Environment, chunk_name: []const u8) coral.io.AllocationError!Self { - var bytecode_buffer = try Buffer.init(env.allocator, 0); + var message_data = Buffer{}; - errdefer bytecode_buffer.deinit(env.allocator); - - var message_data = try Buffer.init(env.allocator, chunk_name.len); + try message_data.push_all(env.allocator, chunk_name); errdefer message_data.deinit(env.allocator); - message_data.push_all(env.allocator, chunk_name) catch unreachable; - return Self{ .env = env, .message_data = message_data, - .bytecode_buffer = bytecode_buffer, + .bytecode_buffer = .{}, .message_name_len = chunk_name.len, }; } diff --git a/source/ona/kym/Environment.zig b/source/ona/kym/Environment.zig index 860ffb9..069c9d8 100644 --- a/source/ona/kym/Environment.zig +++ b/source/ona/kym/Environment.zig @@ -235,8 +235,10 @@ pub fn execute_file(self: *Self, fs: file.System, file_path: file.Path) ExecuteF defer readable_file.close(); + var file_source = coral.list.Stack(u8){}; const file_size = (try fs.query_info(file_path)).size; - var file_source = try coral.list.Stack(u8).init(self.allocator, file_size); + + try file_source.grow(self.allocator, file_size); defer file_source.deinit(self.allocator); @@ -287,41 +289,35 @@ pub fn get_object(self: *Self, indexable: types.Ref, index: types.Ref) types.Run } pub fn init(allocator: coral.io.Allocator, options: InitOptions) !Self { - var values = try ValueStack.init(allocator, options.values_max * options.calls_max); - - errdefer values.deinit(allocator); - - var calls = try CallStack.init(allocator, options.calls_max); - - errdefer calls.deinit(allocator); - - var interned = try InternTable.init(allocator); - - errdefer interned.deinit(allocator); - - var heap = try ObjectSlab.init(allocator); - - errdefer heap.deinit(allocator); - - var environment = Self{ + var env = Self{ .global_object = 0, .allocator = allocator, .reporter = options.reporter, - .interned = interned, - .values = values, - .calls = calls, - .heap = heap, + .interned = .{}, + .values = .{}, + .calls = .{}, + .heap = .{}, }; - const globals = try environment.new_object(&.{}, .{ - .identity = "KYM GLOBAL OBJECT OC DO NOT STEAL", - }); + errdefer { + env.values.deinit(allocator); + env.calls.deinit(allocator); + } - coral.debug.assert(globals == .object); + try env.values.grow(allocator, options.values_max * options.calls_max); + try env.calls.grow(allocator, options.calls_max); - environment.global_object = globals.object; + { + const globals = try env.new_object(&.{}, .{ + .identity = "KYM GLOBAL OBJECT OC DO NOT STEAL", + }); - return environment; + coral.debug.assert(globals == .object); + + env.global_object = globals.object; + } + + return env; } pub fn intern(self: *Self, string: []const u8) coral.io.AllocationError!types.Ref { @@ -360,17 +356,13 @@ pub fn new_object(self: *Self, userdata: []const u8, info: ObjectInfo) coral.io. coral.io.copy(allocation, userdata); - var fields = try Object.Fields.init(self.allocator); - - errdefer fields.deinit(self.allocator); - return .{.object = try self.heap.insert(self.allocator, .{ .ref_count = 1, .state = .{ .info = info, .userdata = allocation, - .fields = fields, + .fields = .{}, }, })}; } diff --git a/source/ona/kym/ast.zig b/source/ona/kym/ast.zig deleted file mode 100644 index e612a6b..0000000 --- a/source/ona/kym/ast.zig +++ /dev/null @@ -1,645 +0,0 @@ -const coral = @import("coral"); - -const tokens = @import("./tokens.zig"); - -const types = @import("./types.zig"); - -pub const BinaryOperation = enum { - addition, - subtraction, - multiplication, - division, - equality_comparison, - greater_than_comparison, - greater_equals_comparison, - less_than_comparison, - less_equals_comparison, -}; - -pub const ParsedExpression = union (enum) { - valid: Expression, - invalid: []const u8, - - pub fn init(allocator: coral.io.Allocator, tokenizer: *tokens.Tokenizer) coral.io.AllocationError!ParsedExpression { - var parsed_lhs_expression = try init_equality(allocator, tokenizer); - - switch (parsed_lhs_expression) { - .valid => |*lhs_expression| { - var expression = lhs_expression.*; - var is_invalid = true; - - defer if (is_invalid) { - expression.deinit(allocator); - }; - - if (tokenizer.current_token == .symbol_plus) { - if (!tokenizer.step()) { - return ParsedExpression{.invalid = "expected right-hand side of expression after `+`"}; - } - - var parsed_rhs_expression = try init_equality(allocator, tokenizer); - - switch (parsed_rhs_expression) { - .valid => |*rhs_expression| { - errdefer rhs_expression.deinit(allocator); - - expression = try Expression.init_binary_operation( - allocator, - .addition, - lhs_expression, - rhs_expression); - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } - - if (tokenizer.current_token == .symbol_minus) { - if (!tokenizer.step()) { - return ParsedExpression{.invalid = "expected right-hand side of expression after `-`"}; - } - - var parsed_rhs_expression = try init_equality(allocator, tokenizer); - - switch (parsed_rhs_expression) { - .valid => |*rhs_expression| { - errdefer rhs_expression.deinit(allocator); - - expression = try Expression.init_binary_operation( - allocator, - .subtraction, - lhs_expression, - rhs_expression); - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } - - is_invalid = false; - - return ParsedExpression{.valid = expression}; - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } - - pub fn init_comparison( - allocator: coral.io.Allocator, - tokenizer: *tokens.Tokenizer) coral.io.AllocationError!ParsedExpression { - - var parsed_lhs_expression = try init_term(allocator, tokenizer); - - switch (parsed_lhs_expression) { - .valid => |*lhs_expression| { - var expression = lhs_expression.*; - var is_invalid = true; - - defer if (is_invalid) { - expression.deinit(allocator); - }; - - if (tokenizer.current_token == .symbol_greater_than) { - if (!tokenizer.step()) { - return ParsedExpression{.invalid = "expected right-hand side of expression after `>`"}; - } - - var parsed_rhs_expression = try init_term(allocator, tokenizer); - - switch (parsed_rhs_expression) { - .valid => |*rhs_expression| { - errdefer rhs_expression.deinit(allocator); - - expression = try Expression.init_binary_operation( - allocator, - .greater_than_comparison, - lhs_expression, - rhs_expression); - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } - - if (tokenizer.current_token == .symbol_greater_equals) { - if (!tokenizer.step()) { - return ParsedExpression{.invalid = "expected right-hand side of expression after `>=`"}; - } - - var parsed_rhs_expression = try init_term(allocator, tokenizer); - - switch (parsed_rhs_expression) { - .valid => |*rhs_expression| { - errdefer rhs_expression.deinit(allocator); - - expression = try Expression.init_binary_operation( - allocator, - .greater_equals_comparison, - lhs_expression, - rhs_expression); - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } - - if (tokenizer.current_token == .symbol_less_than) { - if (!tokenizer.step()) { - return ParsedExpression{.invalid = "expected right-hand side of expression after `<`"}; - } - - var parsed_rhs_expression = try init_term(allocator, tokenizer); - - switch (parsed_rhs_expression) { - .valid => |*rhs_expression| { - errdefer rhs_expression.deinit(allocator); - - expression = try Expression.init_binary_operation( - allocator, - .less_than_comparison, - lhs_expression, - rhs_expression); - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } - - if (tokenizer.current_token == .symbol_less_equals) { - if (!tokenizer.step()) { - return ParsedExpression{.invalid = "expected right-hand side of expression after `<=`"}; - } - - var parsed_rhs_expression = try init_term(allocator, tokenizer); - - switch (parsed_rhs_expression) { - .valid => |*rhs_expression| { - errdefer rhs_expression.deinit(allocator); - - expression = try Expression.init_binary_operation( - allocator, - .less_equals_comparison, - lhs_expression, - rhs_expression); - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } - - is_invalid = false; - - return ParsedExpression{.valid = expression}; - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } - - fn init_equality( - allocator: coral.io.Allocator, - tokenizer: *tokens.Tokenizer) coral.io.AllocationError!ParsedExpression { - - var parsed_lhs_expression = try init_comparison(allocator, tokenizer); - - switch (parsed_lhs_expression) { - .valid => |*lhs_expression| { - var expression = lhs_expression.*; - var is_invalid = true; - - defer if (is_invalid) { - expression.deinit(allocator); - }; - - if (tokenizer.current_token == .symbol_double_equals) { - if (!tokenizer.step()) { - return ParsedExpression{.invalid = "expected right-hand side of expression after `==`"}; - } - - var parsed_rhs_expression = try init_comparison(allocator, tokenizer); - - switch (parsed_rhs_expression) { - .valid => |*rhs_expression| { - errdefer rhs_expression.deinit(allocator); - - expression = try Expression.init_binary_operation( - allocator, - .equality_comparison, - lhs_expression, - rhs_expression); - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } - - is_invalid = false; - - return ParsedExpression{.valid = expression}; - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } - - fn init_factor( - allocator: coral.io.Allocator, - tokenizer: *tokens.Tokenizer) coral.io.AllocationError!ParsedExpression { - - switch (tokenizer.current_token) { - .symbol_paren_left => { - if (!tokenizer.step()) { - return ParsedExpression{.invalid = "expected an expression after `(`"}; - } - - var parsed_expression = try ParsedExpression.init(allocator, tokenizer); - - switch (parsed_expression) { - .valid => |*expression| { - var is_invalid = true; - - defer if (is_invalid) { - expression.deinit(allocator); - }; - - if ((!tokenizer.step()) or (tokenizer.current_token != .symbol_paren_right)) { - return ParsedExpression{.invalid = "expected a closing `)` after expression"}; - } - - is_invalid = false; - - return ParsedExpression{ - .valid = .{.grouped_expression = try coral.io.allocate_one(allocator, expression.*)}, - }; - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - }, - - .integer => |value| { - defer _ = tokenizer.step(); - - return ParsedExpression{ - .valid = .{ - .integer_literal = coral.utf8.parse_int( - @typeInfo(types.Integer).Int, - value, .{}) catch |parse_error| { - - return ParsedExpression{ - .invalid = switch (parse_error) { - error.BadSyntax => "invalid integer literal", - error.IntOverflow => "integer literal is too big", - } - }; - }, - }, - }; - }, - - .real => |value| { - defer _ = tokenizer.step(); - - return ParsedExpression{ - .valid = .{ - .float_literal = coral.utf8.parse_float( - @typeInfo(types.Float).Float, - value) catch |parse_error| { - - return ParsedExpression{ - .invalid = switch (parse_error) { - error.BadSyntax => "invalid float literal", - }, - }; - }, - }, - }; - }, - - .string => |value| { - defer _ = tokenizer.step(); - - return ParsedExpression{ - .valid = .{ - .string_literal = value, - }, - }; - }, - - .symbol_minus => { - if (!tokenizer.step()) { - return ParsedExpression{.invalid = "expected expression after numeric negation (`-`)"}; - } - - var parsed_factor_expression = try init_factor(allocator, tokenizer); - - switch (parsed_factor_expression) { - .valid => |*factor_expression| { - errdefer factor_expression.deinit(allocator); - - return ParsedExpression{ - .valid = try Expression.init_unary_operation( - allocator, - .numeric_negation, - factor_expression), - }; - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - }, - - .symbol_bang => { - if (!tokenizer.step()) { - return ParsedExpression{.invalid = "expected expression after boolean negation (`!`)"}; - } - - var parsed_factor_expression = try init_factor(allocator, tokenizer); - - switch (parsed_factor_expression) { - .valid => |*factor_expression| { - errdefer factor_expression.deinit(allocator); - - return ParsedExpression{ - .valid = try Expression.init_unary_operation( - allocator, - .boolean_negation, - factor_expression), - }; - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - }, - - else => return ParsedExpression{.invalid = "unexpected token in expression"}, - } - } - - pub fn init_term(allocator: coral.io.Allocator, tokenizer: *tokens.Tokenizer) coral.io.AllocationError!ParsedExpression { - var parsed_lhs_expression = try init_factor(allocator, tokenizer); - - switch (parsed_lhs_expression) { - .valid => |*lhs_expression| { - var expression = lhs_expression.*; - var is_invalid = true; - - defer if (is_invalid) { - expression.deinit(allocator); - }; - - if (tokenizer.current_token == .symbol_asterisk) { - if (!tokenizer.step()) { - return ParsedExpression{.invalid = "expected right-hand side of expression after `*`"}; - } - - var parsed_rhs_expression = try init_factor(allocator, tokenizer); - - switch (parsed_rhs_expression) { - .valid => |*rhs_expression| { - errdefer rhs_expression.deinit(allocator); - - expression = try Expression.init_binary_operation( - allocator, - .multiplication, - lhs_expression, - rhs_expression); - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } - - if (tokenizer.current_token == .symbol_forward_slash) { - if (!tokenizer.step()) { - return ParsedExpression{.invalid = "expected right-hand side of expression after `/`"}; - } - - var parsed_rhs_expression = try init_equality(allocator, tokenizer); - - switch (parsed_rhs_expression) { - .valid => |*rhs_expression| { - errdefer rhs_expression.deinit(allocator); - - expression = try Expression.init_binary_operation( - allocator, - .division, - lhs_expression, - rhs_expression); - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } - - is_invalid = false; - - return ParsedExpression{.valid = expression}; - }, - - .invalid => |details| return ParsedExpression{.invalid = details}, - } - } -}; - -pub const ParsedStatements = union (enum) { - valid: Statements, - invalid: []const u8, - - pub fn init(allocator: coral.io.Allocator, tokenizer: *tokens.Tokenizer) coral.io.AllocationError!ParsedStatements { - var statements_list = try Statements.List.init(allocator, 0); - var has_returned = false; - var is_invalid = true; - - defer if (is_invalid) { - for (statements_list.values) |*statement| { - statement.deinit(allocator); - } - - statements_list.deinit(allocator); - }; - - while (tokenizer.step()) { - switch (tokenizer.current_token) { - .newline => {}, - - .keyword_return => { - if (has_returned) { - return ParsedStatements{.invalid = "cannot return more than once per function scope"}; - } - - if (tokenizer.step() and (tokenizer.current_token != .newline)) { - var parsed_expression = try ParsedExpression.init(allocator, tokenizer); - - switch (parsed_expression) { - .valid => |*expression| { - errdefer expression.deinit(allocator); - - try statements_list.push_one(allocator, .{ - .return_expression = expression.*, - }); - }, - - .invalid => |details| { - return ParsedStatements{.invalid = details}; - }, - } - } else { - try statements_list.push_one(allocator, .return_nothing); - } - - if (tokenizer.step() and tokenizer.current_token != .newline) { - return ParsedStatements{.invalid = "expected newline after expression"}; - } - - has_returned = true; - }, - - else => { - return ParsedStatements{.invalid = "invalid statement"}; - }, - } - } - - is_invalid = false; - - return ParsedStatements{ - .valid = .{ - .list = statements_list, - }, - }; - } -}; - -pub const Expression = union (enum) { - nil_literal, - true_literal, - false_literal, - integer_literal: types.Integer, - float_literal: types.Float, - string_literal: []const u8, - table_literal: TableLiteral, - grouped_expression: *Expression, - - binary_operation: struct { - kind: BinaryOperation, - lhs_expression: *Expression, - rhs_expression: *Expression, - }, - - unary_operation: struct { - kind: UnaryOperation, - expression: *Expression, - }, - - const TableLiteral = coral.list.Stack(struct { - identifier: []const u8, - expression: *Expression, - }); - - fn deinit(self: *Expression, allocator: coral.io.Allocator) void { - switch (self.*) { - .nil_literal, .true_literal, .false_literal, .integer_literal, .float_literal, .string_literal => {}, - - .table_literal => |*literal| { - for (literal.values) |field| { - field.expression.deinit(allocator); - coral.io.deallocate(allocator, field.expression); - } - - literal.deinit(allocator); - }, - - .grouped_expression => |expression| { - expression.deinit(allocator); - }, - - .binary_operation => |operation| { - operation.lhs_expression.deinit(allocator); - coral.io.deallocate(allocator, operation.lhs_expression); - operation.rhs_expression.deinit(allocator); - coral.io.deallocate(allocator, operation.rhs_expression); - }, - - .unary_operation => |operation| { - operation.expression.deinit(allocator); - coral.io.deallocate(allocator, operation.expression); - } - } - } - - fn init_binary_operation( - allocator: coral.io.Allocator, - kind: BinaryOperation, - lhs_expression: *const Expression, - rhs_operation: *const Expression) coral.io.AllocationError!Expression { - - const allocated_lhs_expression = try coral.io.allocate_one(allocator, lhs_expression.*); - - errdefer coral.io.deallocate(allocator, allocated_lhs_expression); - - const allocated_rhs_expression = try coral.io.allocate_one(allocator, rhs_operation.*); - - errdefer coral.io.deallocate(allocator, allocated_rhs_expression); - - return Expression{ - .binary_operation = .{ - .kind = kind, - .lhs_expression = allocated_lhs_expression, - .rhs_expression = allocated_rhs_expression, - }, - }; - } - - fn init_unary_operation( - allocator: coral.io.Allocator, - kind: UnaryOperation, - expression: *const Expression) coral.io.AllocationError!Expression { - - const allocated_expression = try coral.io.allocate_one(allocator, expression.*); - - errdefer coral.io.deallocate(allocator, allocated_expression); - - return Expression{ - .unary_operation = .{ - .kind = kind, - .expression = allocated_expression, - }, - }; - } -}; - -pub const Statements = struct { - list: List, - - const List = coral.list.Stack(union (enum) { - return_expression: Expression, - return_nothing, - - const Self = @This(); - - fn deinit(self: *Self, allocator: coral.io.Allocator) void { - switch (self.*) { - .return_expression => |*expression| { - expression.deinit(allocator); - }, - - .return_nothing => {}, - } - } - }); - - pub fn deinit(self: *Statements, allocator: coral.io.Allocator) void { - for (self.list.values) |*statement| { - statement.deinit(allocator); - } - - self.list.deinit(allocator); - } -}; - -pub const UnaryOperation = enum { - boolean_negation, - numeric_negation, -}; diff --git a/source/ona/kym/tokens.zig b/source/ona/kym/tokens.zig index 7baa988..3925817 100755 --- a/source/ona/kym/tokens.zig +++ b/source/ona/kym/tokens.zig @@ -7,7 +7,6 @@ pub const Token = union(enum) { global: []const u8, local: []const u8, - symbol_assign, symbol_plus, symbol_minus, symbol_asterisk, @@ -44,25 +43,31 @@ pub const Token = union(enum) { return switch (self) { .unknown => |unknown| @ptrCast([*]const u8, &unknown)[0 .. 1], .newline => "newline", - .identifier_global => |identifier| identifier, - .identifier_local => |identifier| identifier, - .assign => "=", - .plus => "+", - .minus => "-", - .asterisk => "*", - .forward_slash => "/", - .paren_left => "(", - .paren_right => ")", - .bang => "!", - .comma => ",", - .at => "@", - .brace_left => "{", - .brace_right => "}", - .bracket_left => "[", - .bracket_right => "]", - .period => ".", - .arrow => "=>", + .global => |identifier| identifier, + .local => |identifier| identifier, + + .symbol_plus => "+", + .symbol_minus => "-", + .symbol_asterisk => "*", + .symbol_forward_slash => "/", + .symbol_paren_left => "(", + .symbol_paren_right => ")", + .symbol_bang => "!", + .symbol_comma => ",", + .symbol_at => "@", + .symbol_brace_left => "{", + .symbol_brace_right => "}", + .symbol_bracket_left => "[", + .symbol_bracket_right => "]", + .symbol_period => ".", + .symbol_lambda => "=>", + .symbol_less_than => "<", + .symbol_less_equals => "<=", + .symbol_greater_than => ">", + .symbol_greater_equals => ">=", + .symbol_equals => "=", + .symbol_double_equals => "==", .integer => |literal| literal, .real => |literal| literal, @@ -80,314 +85,329 @@ pub const Token = union(enum) { pub const Tokenizer = struct { source: []const u8, lines_stepped: usize = 1, - previous_token: Token = .newline, - current_token: Token = .newline, + current_token: Token = .{.unknown = 0}, - pub fn has_next(self: Tokenizer) bool { - return self.source.len != 0; - } - - pub fn step(self: *Tokenizer) bool { - self.previous_token = self.current_token; + const StepOptions = struct { + include_newlines: bool, + }; + pub fn step(self: *Tokenizer, options: StepOptions) bool { var cursor = @as(usize, 0); defer self.source = self.source[cursor ..]; - while (self.has_next()) switch (self.source[cursor]) { - '#' => { - cursor += 1; + defer @import("std").debug.print("{s}\n", .{self.current_token.text()}); - while (self.has_next() and (self.source[cursor] == '\n')) { + while (cursor < self.source.len) { + switch (self.source[cursor]) { + '#' => { cursor += 1; - } - }, - ' ', '\t' => cursor += 1, - - '\n' => { - cursor += 1; - self.lines_stepped += 1; - self.current_token = .newline; - - return true; - }, - - '0' ... '9' => { - const begin = cursor; - - cursor += 1; - - while (self.has_next()) switch (self.source[cursor]) { - '0' ... '9' => cursor += 1, - - '.' => { + while (cursor < self.source.len and self.source[cursor] == '\n') { cursor += 1; + } + }, - while (self.has_next()) switch (self.source[cursor]) { - '0' ... '9' => cursor += 1, - else => break, - }; + ' ', '\t' => cursor += 1, - self.current_token = .{.real = self.source[begin .. cursor]}; + '\n' => { + cursor += 1; + self.current_token = .newline; + self.lines_stepped += 1; + if (options.include_newlines) { return true; - }, + } + }, - else => break, - }; + '0' ... '9' => { + const begin = cursor; - self.current_token = .{.integer = self.source[begin .. cursor]}; + cursor += 1; - return true; - }, + while (cursor < self.source.len) switch (self.source[cursor]) { + '0' ... '9' => cursor += 1, - 'A' ... 'Z', 'a' ... 'z', '_' => { - const begin = cursor; - - cursor += 1; - - while (cursor < self.source.len) switch (self.source[cursor]) { - '0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1, - else => break, - }; - - const identifier = self.source[begin .. cursor]; - - coral.debug.assert(identifier.len != 0); - - switch (identifier[0]) { - 'n' => if (coral.io.ends_with(identifier, "il")) { - self.current_token = .keyword_nil; - }, - - 'f' => if (coral.io.ends_with(identifier, "alse")) { - self.current_token = .keyword_false; - }, - - 't' => if (coral.io.ends_with(identifier, "rue")) { - self.current_token = .keyword_true; - }, - - 'r' => if (coral.io.ends_with(identifier, "eturn")) { - self.current_token = .keyword_return; - }, - - 's' => if (coral.io.ends_with(identifier, "elf")) { - self.current_token = .keyword_self; - }, - - else => self.current_token = .{.local = identifier}, - } - - return true; - }, - - '@' => { - cursor += 1; - - if (self.has_next()) switch (self.source[cursor]) { - 'A'...'Z', 'a'...'z', '_' => { - const begin = cursor; - - cursor += 1; - - while (self.has_next()) switch (self.source[cursor]) { - '0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1, - else => break, - }; - - self.current_token = .{.global = self.source[begin .. cursor]}; - - return true; - }, - - '"' => { - cursor += 1; - - const begin = cursor; - - cursor += 1; - - while (self.has_next()) switch (self.source[cursor]) { - '"' => break, - else => cursor += 1, - }; - - self.current_token = .{.global = self.source[begin .. cursor]}; - cursor += 1; - - return true; - }, - - else => {}, - }; - - self.current_token = .symbol_at; - - return true; - }, - - '"' => { - cursor += 1; - - const begin = cursor; - - cursor += 1; - - while (self.has_next()) switch (self.source[cursor]) { - '"' => break, - else => cursor += 1, - }; - - self.current_token = .{.string = self.source[begin .. cursor]}; - cursor += 1; - - return true; - }, - - '{' => { - self.current_token = .symbol_brace_left; - cursor += 1; - - return true; - }, - - '}' => { - self.current_token = .symbol_brace_right; - cursor += 1; - - return true; - }, - - ',' => { - self.current_token = .symbol_comma; - cursor += 1; - - return true; - }, - - '!' => { - self.current_token = .symbol_bang; - cursor += 1; - - return true; - }, - - ')' => { - self.current_token = .symbol_paren_right; - cursor += 1; - - return true; - }, - - '(' => { - self.current_token = .symbol_paren_left; - cursor += 1; - - return true; - }, - - '/' => { - self.current_token = .symbol_forward_slash; - cursor += 1; - - return true; - }, - - '*' => { - self.current_token = .symbol_asterisk; - cursor += 1; - - return true; - }, - - '-' => { - self.current_token = .symbol_minus; - cursor += 1; - - return true; - }, - - '+' => { - self.current_token = .symbol_plus; - cursor += 1; - - return true; - }, - - '=' => { - cursor += 1; - - if (self.has_next()) { - switch (self.source[cursor]) { - '=' => { + '.' => { cursor += 1; - self.current_token = .symbol_double_equals; + + while (cursor < self.source.len) switch (self.source[cursor]) { + '0' ... '9' => cursor += 1, + else => break, + }; + + self.current_token = .{.real = self.source[begin .. cursor]}; return true; }, - '>' => { - cursor += 1; - self.current_token = .symbol_lambda; + else => break, + }; + + self.current_token = .{.integer = self.source[begin .. cursor]}; + + return true; + }, + + 'A' ... 'Z', 'a' ... 'z', '_' => { + const begin = cursor; + + cursor += 1; + + while (cursor < self.source.len) switch (self.source[cursor]) { + '0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1, + else => break, + }; + + const identifier = self.source[begin .. cursor]; + + coral.debug.assert(identifier.len != 0); + + switch (identifier[0]) { + 'n' => if (coral.io.ends_with(identifier, "il")) { + self.current_token = .keyword_nil; + + return true; + }, + + 'f' => if (coral.io.ends_with(identifier, "alse")) { + self.current_token = .keyword_false; + + return true; + }, + + 't' => if (coral.io.ends_with(identifier, "rue")) { + self.current_token = .keyword_true; + + return true; + }, + + 'r' => if (coral.io.ends_with(identifier, "eturn")) { + self.current_token = .keyword_return; + + return true; + }, + + 's' => if (coral.io.ends_with(identifier, "elf")) { + self.current_token = .keyword_self; return true; }, else => {}, } - } - self.current_token = .symbol_equals; - - return true; - }, - - '<' => { - cursor += 1; - - if (self.has_next() and (self.source[cursor] == '=')) { - cursor += 1; - self.current_token = .symbol_less_equals; + self.current_token = .{.local = identifier}; return true; - } + }, - self.current_token = .symbol_less_than; - - return true; - }, - - '>' => { - cursor += 1; - - if (self.has_next() and (self.source[cursor] == '=')) { + '@' => { cursor += 1; - self.current_token = .symbol_greater_equals; + + if (cursor < self.source.len) switch (self.source[cursor]) { + 'A'...'Z', 'a'...'z', '_' => { + const begin = cursor; + + cursor += 1; + + while (cursor < self.source.len) switch (self.source[cursor]) { + '0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1, + else => break, + }; + + self.current_token = .{.global = self.source[begin .. cursor]}; + + return true; + }, + + '"' => { + cursor += 1; + + const begin = cursor; + + cursor += 1; + + while (cursor < self.source.len) switch (self.source[cursor]) { + '"' => break, + else => cursor += 1, + }; + + self.current_token = .{.global = self.source[begin .. cursor]}; + cursor += 1; + + return true; + }, + + else => {}, + }; + + self.current_token = .symbol_at; return true; - } + }, - self.current_token = .symbol_greater_than; + '"' => { + cursor += 1; - return true; - }, + const begin = cursor; - '.' => { - self.current_token = .symbol_period; - cursor += 1; + cursor += 1; - return true; - }, + while (cursor < self.source.len) switch (self.source[cursor]) { + '"' => break, + else => cursor += 1, + }; - else => { - self.current_token = .{.unknown = self.source[cursor]}; - cursor += 1; + self.current_token = .{.string = self.source[begin .. cursor]}; + cursor += 1; - return true; - }, - }; + return true; + }, + + '{' => { + self.current_token = .symbol_brace_left; + cursor += 1; + + return true; + }, + + '}' => { + self.current_token = .symbol_brace_right; + cursor += 1; + + return true; + }, + + ',' => { + self.current_token = .symbol_comma; + cursor += 1; + + return true; + }, + + '!' => { + self.current_token = .symbol_bang; + cursor += 1; + + return true; + }, + + ')' => { + self.current_token = .symbol_paren_right; + cursor += 1; + + return true; + }, + + '(' => { + self.current_token = .symbol_paren_left; + cursor += 1; + + return true; + }, + + '/' => { + self.current_token = .symbol_forward_slash; + cursor += 1; + + return true; + }, + + '*' => { + self.current_token = .symbol_asterisk; + cursor += 1; + + return true; + }, + + '-' => { + self.current_token = .symbol_minus; + cursor += 1; + + return true; + }, + + '+' => { + self.current_token = .symbol_plus; + cursor += 1; + + return true; + }, + + '=' => { + cursor += 1; + + if (cursor < self.source.len) { + switch (self.source[cursor]) { + '=' => { + cursor += 1; + self.current_token = .symbol_double_equals; + + return true; + }, + + '>' => { + cursor += 1; + self.current_token = .symbol_lambda; + + return true; + }, + + else => {}, + } + } + + self.current_token = .symbol_equals; + + return true; + }, + + '<' => { + cursor += 1; + + if (cursor < self.source.len and (self.source[cursor] == '=')) { + cursor += 1; + self.current_token = .symbol_less_equals; + + return true; + } + + self.current_token = .symbol_less_than; + + return true; + }, + + '>' => { + cursor += 1; + + if (cursor < self.source.len and (self.source[cursor] == '=')) { + cursor += 1; + self.current_token = .symbol_greater_equals; + + return true; + } + + self.current_token = .symbol_greater_than; + + return true; + }, + + '.' => { + self.current_token = .symbol_period; + cursor += 1; + + return true; + }, + + else => { + self.current_token = .{.unknown = self.source[cursor]}; + cursor += 1; + + return true; + }, + } + } self.current_token = .newline; diff --git a/source/ona/kym/types.zig b/source/ona/kym/types.zig index d8f742e..514b157 100644 --- a/source/ona/kym/types.zig +++ b/source/ona/kym/types.zig @@ -28,10 +28,13 @@ pub const Ref = union (Primitive) { object: Object, }; -pub const RuntimeError = coral.io.AllocationError || CheckError || error { +pub const ParseError = error { + OutOfMemory, BadSyntax, }; +pub const RuntimeError = CheckError || ParseError; + pub const Val = union (Primitive) { nil, false, diff --git a/source/ona/ona.zig b/source/ona/ona.zig index 82405fb..13e36c2 100755 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -59,17 +59,15 @@ pub fn run_app(base_file_system: file.System) void { const Logger = struct { const Self = @This(); - fn log(_: *Self, message: []const u8) void { + fn log(_: *const Self, message: []const u8) void { ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", message.len, message.ptr); } }; - var logger = Logger{}; - var script_environment = kym.Environment.init(heap.allocator, .{ .values_max = 512, .calls_max = 512, - .reporter = kym.Environment.Reporter.bind(Logger, &logger, Logger.log), + .reporter = kym.Environment.Reporter.bind(Logger, &.{}, Logger.log), }) catch { return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to initialize Kym vm\n"); }; diff --git a/source/runner.zig b/source/runner.zig index 98d9614..215069b 100755 --- a/source/runner.zig +++ b/source/runner.zig @@ -1,3 +1,5 @@ +const coral = @import("coral"); + const ona = @import("ona"); pub fn main() !void {