Array and Table Literal Expressions for Kym #11
|
@ -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];
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,13 +1,13 @@
|
||||||
|
///
|
||||||
|
/// Arena-based memory allocation strategies.
|
||||||
|
///
|
||||||
|
pub const arena = @import("./arena.zig");
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Debug build-only utilities and sanity-checkers.
|
/// Debug build-only utilities and sanity-checkers.
|
||||||
///
|
///
|
||||||
pub const debug = @import("./debug.zig");
|
pub const debug = @import("./debug.zig");
|
||||||
|
|
||||||
///
|
|
||||||
/// Heap memory allocation strategies.
|
|
||||||
///
|
|
||||||
pub const heap = @import("./heap.zig");
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Platform-agnostic data input and output operations.
|
/// Platform-agnostic data input and output operations.
|
||||||
///
|
///
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -11,31 +11,33 @@ pub const AllocationOptions = struct {
|
||||||
size: usize,
|
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 {
|
pub fn Functor(comptime Output: type, comptime Input: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
context: *anyopaque,
|
context: *const anyopaque,
|
||||||
invoker: *const fn (capture: *anyopaque, input: Input) Output,
|
invoker: *const fn (capture: *const anyopaque, input: Input) Output,
|
||||||
|
|
||||||
const Self = @This();
|
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 .{
|
return .{
|
||||||
.context = state,
|
.context = if (is_zero_aligned) state else @ptrCast(*const anyopaque, state),
|
||||||
|
|
||||||
.invoker = struct {
|
.invoker = struct {
|
||||||
fn invoke_opaque(context: *anyopaque, input: Input) Output {
|
fn invoke_opaque(context: *const anyopaque, input: Input) Output {
|
||||||
const state_alignment = @alignOf(State);
|
if (is_zero_aligned) {
|
||||||
|
return invoker(@ptrCast(*const State, context), input);
|
||||||
if (state_alignment == 0) {
|
|
||||||
return invoker(@ptrCast(*State, context), input);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return invoker(@ptrCast(*State, @alignCast(state_alignment, context)), input);
|
return invoker(@ptrCast(*const State, @alignCast(alignment, context)), input);
|
||||||
}
|
}
|
||||||
}.invoke_opaque,
|
}.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 {
|
pub const StreamError = error {
|
||||||
ReadFailure,
|
ReadFailure,
|
||||||
|
@ -66,7 +104,7 @@ pub const FixedBuffer = struct {
|
||||||
slice: []u8,
|
slice: []u8,
|
||||||
|
|
||||||
pub fn as_writer(self: *FixedBuffer) Writer {
|
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 {
|
fn write(writable_memory: *FixedBuffer, data: []const u8) ?usize {
|
||||||
return writable_memory.write(data);
|
return writable_memory.write(data);
|
||||||
}
|
}
|
||||||
|
@ -104,7 +142,7 @@ pub const GrowingBuffer = struct {
|
||||||
bytes: []const u8,
|
bytes: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Appender = Functor(AllocationError!void, AppendOptions);
|
const Appender = Generator(AllocationError!void, AppendOptions);
|
||||||
|
|
||||||
pub fn as_writer(self: *GrowingBuffer) Writer {
|
pub fn as_writer(self: *GrowingBuffer) Writer {
|
||||||
return Writer.bind(GrowingBuffer, self, struct {
|
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 {
|
pub fn allocate_many(comptime Type: type, amount: usize, allocator: Allocator) AllocationError![]Type {
|
||||||
if (@sizeOf(Type) == 0) {
|
if (@sizeOf(Type) == 0) {
|
||||||
@compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type));
|
@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 @ptrCast([*]Type, @alignCast(@alignOf(Type), allocator.invoke(.{.size = @sizeOf(Type) * amount}) orelse {
|
||||||
return error.OutOfMemory;
|
return error.OutOfMemory;
|
||||||
}))[0 .. amount];
|
}))[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);
|
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 {
|
pub fn copy(target: []u8, source: []const u8) void {
|
||||||
var index: usize = 0;
|
var index: usize = 0;
|
||||||
|
|
||||||
while (index < source.len) : (index += 1) target[index] = source[index];
|
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 {
|
pub fn ends_with(target: []const u8, match: []const u8) bool {
|
||||||
if (target.len < match.len) return false;
|
if (target.len < match.len) return false;
|
||||||
|
|
||||||
|
@ -274,10 +294,6 @@ pub const null_writer = Writer.bind(&null_context, struct {
|
||||||
}
|
}
|
||||||
}.write);
|
}.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 {
|
pub fn reallocate(allocator: Allocator, allocation: anytype, amount: usize) AllocationError![]@typeInfo(@TypeOf(allocation)).Pointer.child {
|
||||||
const pointer_info = @typeInfo(@TypeOf(allocation)).Pointer;
|
const pointer_info = @typeInfo(@TypeOf(allocation)).Pointer;
|
||||||
const Element = pointer_info.child;
|
const Element = pointer_info.child;
|
||||||
|
@ -319,6 +335,10 @@ pub fn swap(comptime Element: type, this: *Element, that: *Element) void {
|
||||||
that.* = temp;
|
that.* = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tag_of(comptime value: anytype) Tag(@TypeOf(value)) {
|
||||||
|
return @as(Tag(@TypeOf(value)), value);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn zero(target: []u8) void {
|
pub fn zero(target: []u8) void {
|
||||||
for (target) |*t| t.* = 0;
|
for (target) |*t| t.* = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ const math = @import("./math.zig");
|
||||||
///
|
///
|
||||||
pub fn Stack(comptime Value: type) type {
|
pub fn Stack(comptime Value: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
capacity: usize,
|
capacity: usize = 0,
|
||||||
values: []Value,
|
values: []Value = &.{},
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Stack type.
|
/// 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
|
/// 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.
|
/// 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
|
/// *Note* if `capacity` is a non-zero value, `allocator` must reference the same allocation strategy as the one
|
||||||
/// `self`.
|
/// originally used to allocate the current internal buffer.
|
||||||
///
|
///
|
||||||
pub fn as_buffer(self: *Self, allocator: io.Allocator) io.GrowingBuffer {
|
pub fn as_buffer(self: *Self, allocator: io.Allocator) io.GrowingBuffer {
|
||||||
return io.GrowingBuffer.bind(Self, allocator, self, push_all);
|
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.
|
/// 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
|
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
|
||||||
/// `self`.
|
/// strategy as the one originally used to allocate the current internal buffer.
|
||||||
///
|
///
|
||||||
pub fn deinit(self: *Self, allocator: io.Allocator) void {
|
pub fn deinit(self: *Self, allocator: io.Allocator) void {
|
||||||
|
if (self.capacity == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
io.deallocate(allocator, self.values);
|
io.deallocate(allocator, self.values);
|
||||||
|
|
||||||
|
self.values = &.{};
|
||||||
self.capacity = 0;
|
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
|
/// 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.
|
/// reduce the number of allocations required per push.
|
||||||
///
|
///
|
||||||
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
|
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
|
||||||
/// `self`.
|
/// 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 {
|
pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void {
|
||||||
const grown_capacity = self.capacity + growth_amount;
|
const grown_capacity = self.capacity + growth_amount;
|
||||||
|
@ -86,34 +91,18 @@ pub fn Stack(comptime Value: type) type {
|
||||||
|
|
||||||
errdefer io.deallocate(allocator, values);
|
errdefer io.deallocate(allocator, values);
|
||||||
|
|
||||||
for (0 .. self.values.len) |index| {
|
if (self.capacity != 0) {
|
||||||
values[index] = self.values[index];
|
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.values = values;
|
||||||
self.capacity = grown_capacity;
|
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
|
/// Attempts to remove the last element of `self` that was inserted, if one exists, returning it or `null` if
|
||||||
/// `self` is empty.
|
/// `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
|
/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the
|
||||||
/// internal buffer of `self` when necessary.
|
/// internal buffer of `self` when necessary.
|
||||||
///
|
///
|
||||||
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
|
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
|
||||||
/// `self`.
|
/// 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 {
|
pub fn push_all(self: *Self, allocator: io.Allocator, values: []const Value) io.AllocationError!void {
|
||||||
const new_length = self.values.len + values.len;
|
const new_length = self.values.len + values.len;
|
||||||
|
|
||||||
if (new_length >= self.capacity) {
|
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;
|
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
|
/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the
|
||||||
/// internal buffer of `self` when necessary.
|
/// internal buffer of `self` when necessary.
|
||||||
///
|
///
|
||||||
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
|
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
|
||||||
/// `self`.
|
/// 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 {
|
pub fn push_many(self: *Self, allocator: io.Allocator, value: Value, amount: usize) io.AllocationError!void {
|
||||||
const new_length = self.values.len + amount;
|
const new_length = self.values.len + amount;
|
||||||
|
|
||||||
if (new_length >= self.capacity) {
|
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;
|
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
|
/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the
|
||||||
/// internal buffer of `self` when necessary.
|
/// internal buffer of `self` when necessary.
|
||||||
///
|
///
|
||||||
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
|
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
|
||||||
/// `self`.
|
/// 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 {
|
pub fn push_one(self: *Self, allocator: io.Allocator, value: Value) io.AllocationError!void {
|
||||||
if (self.values.len == self.capacity) {
|
if (self.values.len == self.capacity) {
|
||||||
|
|
|
@ -6,8 +6,8 @@ const io = @import("./io.zig");
|
||||||
|
|
||||||
pub fn Map(comptime Index: type, comptime Element: type) type {
|
pub fn Map(comptime Index: type, comptime Element: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
free_index: Index,
|
free_index: Index = 0,
|
||||||
entries: []Entry,
|
entries: []Entry = &.{},
|
||||||
|
|
||||||
const Entry = union (enum) {
|
const Entry = union (enum) {
|
||||||
free_index: usize,
|
free_index: usize,
|
||||||
|
@ -28,17 +28,6 @@ pub fn Map(comptime Index: type, comptime Element: type) type {
|
||||||
io.deallocate(allocator, self.entries);
|
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 {
|
pub fn insert(self: *Self, allocator: io.Allocator, value: Element) io.AllocationError!Index {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = allocator;
|
_ = allocator;
|
||||||
|
|
|
@ -22,8 +22,8 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke
|
||||||
const growth_factor = 0.6;
|
const growth_factor = 0.6;
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
count: usize,
|
count: usize = 0,
|
||||||
table: []?Entry,
|
table: []?Entry = &.{},
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Key-value pair bundling.
|
/// 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 {
|
pub fn assign(self: *Self, allocator: io.Allocator, key: Key, value: Value) io.AllocationError!?Entry {
|
||||||
if (self.calculate_load_factor() >= load_max) {
|
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)) {
|
if (growth_size > math.max_int(@typeInfo(usize).Int)) {
|
||||||
return error.OutOfMemory;
|
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.
|
/// Returns the calculated load factor of `self` at the moment.
|
||||||
///
|
///
|
||||||
pub fn calculate_load_factor(self: Self) f32 {
|
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;
|
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,
|
/// 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`.
|
/// 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 {
|
pub fn insert(self: *Self, allocator: io.Allocator, key: Key, value: Value) io.AllocationError!bool {
|
||||||
if (self.calculate_load_factor() >= load_max) {
|
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)) {
|
try self.rehash(allocator, self.table.len + math.max(min_size, @floatToInt(usize, growth_amount)));
|
||||||
return error.OutOfMemory;
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.rehash(allocator, @floatToInt(usize, growth_size));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.assert(self.table.len > self.count);
|
debug.assert(self.table.len > self.count);
|
||||||
|
|
||||||
|
defer self.count += 1;
|
||||||
|
|
||||||
return (Entry{
|
return (Entry{
|
||||||
.key = key,
|
.key = key,
|
||||||
.value = value,
|
.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);
|
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 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);
|
const entry = &(self.table[hashed_key] orelse return null);
|
||||||
|
|
||||||
if (keyer.comparer(entry.key, key) == 0) {
|
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;
|
entry.* = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (old_table) |maybe_entry| {
|
if (old_table.len != 0)
|
||||||
if (maybe_entry) |entry| {
|
{
|
||||||
debug.assert(entry.write_into(self.table));
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
const std = @import("std");
|
const debug = @import("./debug.zig");
|
||||||
|
|
||||||
const io = @import("./io.zig");
|
const io = @import("./io.zig");
|
||||||
|
|
||||||
const math = @import("./math.zig");
|
const math = @import("./math.zig");
|
||||||
|
|
||||||
///
|
const std = @import("std");
|
||||||
/// Errors that may occur during utf8-encoded int parsing.
|
|
||||||
///
|
|
||||||
pub const IntParseError = math.CheckedArithmeticError || ParseError;
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Optional rules for int parsing logic to consider during parsing.
|
|
||||||
///
|
///
|
||||||
pub const IntParseOptions = struct {
|
///
|
||||||
|
pub const DecimalFormat = struct {
|
||||||
delimiter: []const u8 = "",
|
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) {
|
if (utf8.len == 0) {
|
||||||
return error.BadSyntax;
|
return error.BadSyntax;
|
||||||
}
|
}
|
||||||
|
|
||||||
const is_negative = utf8[0] == '-';
|
switch (@typeInfo(Decimal)) {
|
||||||
|
.Int => |int| {
|
||||||
|
var has_sign = switch (utf8[0]) {
|
||||||
|
'-', '+', ' ' => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
|
||||||
// "-"
|
var result = @as(Decimal, 0);
|
||||||
if (is_negative and (utf8.len == 1)) {
|
|
||||||
return error.BadSyntax;
|
|
||||||
}
|
|
||||||
|
|
||||||
const negative_offset = @boolToInt(is_negative);
|
for (@boolToInt(has_sign) .. utf8.len) |index| {
|
||||||
var has_decimal = utf8[negative_offset] == '.';
|
const radix = 10;
|
||||||
|
const code = utf8[index];
|
||||||
|
|
||||||
// "-."
|
switch (code) {
|
||||||
if (has_decimal and (utf8.len == 2)) {
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
||||||
return error.BadSyntax;
|
result = try math.checked_add(
|
||||||
}
|
try math.checked_mul(result, radix),
|
||||||
|
try math.checked_sub(code, '0'));
|
||||||
|
},
|
||||||
|
|
||||||
const Float = math.Float(float);
|
else => {
|
||||||
var result: Float = 0;
|
if (format.delimiter.len == 0 or !io.equals(format.delimiter, utf8[index ..])) {
|
||||||
var factor: Float = 1;
|
return error.BadSyntax;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (utf8[0 .. negative_offset + @boolToInt(has_decimal)]) |code| switch (code) {
|
switch (int.signedness) {
|
||||||
'.' => {
|
.signed => {
|
||||||
if (has_decimal) return error.BadSyntax;
|
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' => {
|
.Float => {
|
||||||
if (has_decimal) factor /= 10.0;
|
// ""
|
||||||
|
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,
|
else => @compileError("`" ++ @typeName(Decimal) ++ "` cannot be formatted as a decimal string"),
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
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 {
|
pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments: anytype) PrintError!void {
|
||||||
if (value == 0) {
|
switch (@typeInfo(@TypeOf(arguments))) {
|
||||||
return try print(writer, "0");
|
.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;
|
///
|
||||||
|
pub fn print_decimal(writer: io.Writer, value: anytype, format: DecimalFormat) PrintError!void {
|
||||||
if ((int.signedness == .unsigned) and (value < 0)) {
|
if (value == 0) {
|
||||||
buffer[0] = '-';
|
return print(writer, switch (format.positive_prefix) {
|
||||||
buffer_count += 1;
|
.none => "0",
|
||||||
}
|
.plus => "+0",
|
||||||
|
.space => " 0",
|
||||||
while (split_value != 0) : (buffer_count += 1) {
|
});
|
||||||
const radix = 10;
|
}
|
||||||
|
|
||||||
buffer[buffer_count] = @intCast(u8, (split_value % radix) + '0');
|
switch (@typeInfo(@TypeOf(value))) {
|
||||||
split_value = (split_value / radix);
|
.Int => |int| {
|
||||||
}
|
const radix = 10;
|
||||||
|
var buffer = [_]u8{0} ** (1 + math.max(int.bits, 1));
|
||||||
{
|
var buffer_start = buffer.len - 1;
|
||||||
const half_buffer_count = buffer_count / 2;
|
|
||||||
var index: usize = 0;
|
{
|
||||||
|
var decomposable_value = value;
|
||||||
while (index < half_buffer_count) : (index += 1) {
|
|
||||||
io.swap(u8, &buffer[index], &buffer[buffer_count - index - 1]);
|
while (decomposable_value != 0) : (buffer_start -= 1) {
|
||||||
}
|
buffer[buffer_start] = @intCast(u8, (decomposable_value % radix) + '0');
|
||||||
}
|
decomposable_value = (decomposable_value / radix);
|
||||||
|
}
|
||||||
try print(writer, buffer[0 .. buffer_count]);
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,15 +7,23 @@ const Context = struct {
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
const empty_allocation = [0]u8{};
|
||||||
|
|
||||||
fn reallocate(self: *Self, options: coral.io.AllocationOptions) ?[]u8 {
|
fn reallocate(self: *Self, options: coral.io.AllocationOptions) ?[]u8 {
|
||||||
if (options.size == 0) {
|
if (options.size == 0) {
|
||||||
if (options.allocation) |allocation| {
|
if (options.allocation) |allocation| {
|
||||||
ext.SDL_free(allocation.ptr);
|
if (allocation.ptr != &empty_allocation) {
|
||||||
|
ext.SDL_free(allocation.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
self.live_allocations -= 1;
|
self.live_allocations -= 1;
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
self.live_allocations += 1;
|
||||||
|
|
||||||
|
return &empty_allocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.allocation) |allocation| {
|
if (options.allocation) |allocation| {
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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");
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ const Opcode = enum (u8) {
|
||||||
push_integer,
|
push_integer,
|
||||||
push_float,
|
push_float,
|
||||||
push_object,
|
push_object,
|
||||||
|
push_array,
|
||||||
push_table,
|
push_table,
|
||||||
|
|
||||||
not,
|
not,
|
||||||
|
@ -50,54 +51,45 @@ fn clear_error_details(self: *Self) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile(self: *Self, data: []const u8) types.RuntimeError!void {
|
pub fn compile(self: *Self, data: []const u8) types.RuntimeError!void {
|
||||||
var tokenizer = tokens.Tokenizer{.source = data};
|
var ast = try Ast.init(self.env.allocator);
|
||||||
var parsed_statements = try ast.ParsedStatements.init(self.env.allocator, &tokenizer);
|
|
||||||
|
|
||||||
switch (parsed_statements) {
|
errdefer ast.deinit();
|
||||||
.valid => |*statements| {
|
|
||||||
defer statements.deinit(self.env.allocator);
|
|
||||||
|
|
||||||
for (statements.list.values) |statement| {
|
{
|
||||||
switch (statement) {
|
var tokenizer = tokens.Tokenizer{.source = data};
|
||||||
.return_expression => |return_expression| {
|
|
||||||
try self.compile_expression(return_expression);
|
|
||||||
try self.emit_opcode(.ret);
|
|
||||||
},
|
|
||||||
|
|
||||||
.return_nothing => {
|
ast.parse(&tokenizer) catch |init_error| {
|
||||||
try self.emit_opcode(.push_nil);
|
if (init_error == error.BadSyntax) {
|
||||||
try self.emit_opcode(.ret);
|
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| {
|
return init_error;
|
||||||
self.clear_error_details();
|
};
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
.return_nothing => {
|
||||||
const message_writer = message_buffer.as_writer();
|
try self.emit_opcode(.push_nil);
|
||||||
|
try self.emit_opcode(.ret);
|
||||||
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;
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
switch (expression) {
|
||||||
.nil_literal => try self.emit_opcode(.push_nil),
|
.nil_literal => try self.emit_opcode(.push_nil),
|
||||||
.true_literal => try self.emit_opcode(.push_true),
|
.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));
|
try self.emit_object(try self.intern(literal));
|
||||||
},
|
},
|
||||||
|
|
||||||
.table_literal => |literal| {
|
.array_literal => |elements| {
|
||||||
if (literal.values.len > coral.math.max_int(@typeInfo(types.Integer).Int)) {
|
if (elements.values.len > coral.math.max_int(@typeInfo(types.Integer).Int)) {
|
||||||
return error.OutOfMemory;
|
return error.OutOfMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (literal.values) |field| {
|
for (elements.values) |element_expression| {
|
||||||
try self.compile_expression(field.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_opcode(.push_object);
|
||||||
try self.emit_object(try self.intern(field.identifier));
|
try self.emit_object(try self.intern(field.identifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.emit_opcode(.push_table);
|
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| {
|
.binary_operation => |operation| {
|
||||||
try self.compile_expression(operation.lhs_expression.*);
|
try self.compile_expression(operation.lhs_expression.*);
|
||||||
try self.compile_expression(operation.rhs_expression.*);
|
try self.compile_expression(operation.rhs_expression.*);
|
||||||
|
|
||||||
try self.emit_opcode(switch (operation.kind) {
|
try self.emit_opcode(switch (operation.operator) {
|
||||||
.addition => .add,
|
.addition => .add,
|
||||||
.subtraction => .sub,
|
.subtraction => .sub,
|
||||||
.multiplication => .mul,
|
.multiplication => .mul,
|
||||||
.division => .div,
|
.divsion => .div,
|
||||||
.equality_comparison => .compare_eq,
|
.greater_equals_comparison => .compare_eq,
|
||||||
.greater_than_comparison => .compare_gt,
|
.greater_than_comparison => .compare_gt,
|
||||||
.greater_equals_comparison => .compare_ge,
|
.equals_comparison => .compare_ge,
|
||||||
.less_than_comparison => .compare_lt,
|
.less_than_comparison => .compare_lt,
|
||||||
.less_equals_comparison => .compare_le,
|
.less_equals_comparison => .compare_le,
|
||||||
});
|
});
|
||||||
|
@ -161,7 +166,7 @@ pub fn compile_expression(self: *Self, expression: ast.Expression) types.Runtime
|
||||||
.unary_operation => |operation| {
|
.unary_operation => |operation| {
|
||||||
try self.compile_expression(operation.expression.*);
|
try self.compile_expression(operation.expression.*);
|
||||||
|
|
||||||
try self.emit_opcode(switch (operation.kind) {
|
try self.emit_opcode(switch (operation.operator) {
|
||||||
.boolean_negation => .not,
|
.boolean_negation => .not,
|
||||||
.numeric_negation => .neg,
|
.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 {
|
pub fn error_details(self: Self) []const u8 {
|
||||||
coral.debug.assert(self.message_data.values.len >= self.message_name_len);
|
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 {
|
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);
|
try message_data.push_all(env.allocator, chunk_name);
|
||||||
|
|
||||||
var message_data = try Buffer.init(env.allocator, chunk_name.len);
|
|
||||||
|
|
||||||
errdefer message_data.deinit(env.allocator);
|
errdefer message_data.deinit(env.allocator);
|
||||||
|
|
||||||
message_data.push_all(env.allocator, chunk_name) catch unreachable;
|
|
||||||
|
|
||||||
return Self{
|
return Self{
|
||||||
.env = env,
|
.env = env,
|
||||||
.message_data = message_data,
|
.message_data = message_data,
|
||||||
.bytecode_buffer = bytecode_buffer,
|
.bytecode_buffer = .{},
|
||||||
.message_name_len = chunk_name.len,
|
.message_name_len = chunk_name.len,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,8 +235,10 @@ pub fn execute_file(self: *Self, fs: file.System, file_path: file.Path) ExecuteF
|
||||||
|
|
||||||
defer readable_file.close();
|
defer readable_file.close();
|
||||||
|
|
||||||
|
var file_source = coral.list.Stack(u8){};
|
||||||
const file_size = (try fs.query_info(file_path)).size;
|
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);
|
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 {
|
pub fn init(allocator: coral.io.Allocator, options: InitOptions) !Self {
|
||||||
var values = try ValueStack.init(allocator, options.values_max * options.calls_max);
|
var env = Self{
|
||||||
|
|
||||||
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{
|
|
||||||
.global_object = 0,
|
.global_object = 0,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.reporter = options.reporter,
|
.reporter = options.reporter,
|
||||||
.interned = interned,
|
.interned = .{},
|
||||||
.values = values,
|
.values = .{},
|
||||||
.calls = calls,
|
.calls = .{},
|
||||||
.heap = heap,
|
.heap = .{},
|
||||||
};
|
};
|
||||||
|
|
||||||
const globals = try environment.new_object(&.{}, .{
|
errdefer {
|
||||||
.identity = "KYM GLOBAL OBJECT OC DO NOT STEAL",
|
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 {
|
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);
|
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, .{
|
return .{.object = try self.heap.insert(self.allocator, .{
|
||||||
.ref_count = 1,
|
.ref_count = 1,
|
||||||
|
|
||||||
.state = .{
|
.state = .{
|
||||||
.info = info,
|
.info = info,
|
||||||
.userdata = allocation,
|
.userdata = allocation,
|
||||||
.fields = fields,
|
.fields = .{},
|
||||||
},
|
},
|
||||||
})};
|
})};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
|
||||||
};
|
|
|
@ -7,7 +7,6 @@ pub const Token = union(enum) {
|
||||||
global: []const u8,
|
global: []const u8,
|
||||||
local: []const u8,
|
local: []const u8,
|
||||||
|
|
||||||
symbol_assign,
|
|
||||||
symbol_plus,
|
symbol_plus,
|
||||||
symbol_minus,
|
symbol_minus,
|
||||||
symbol_asterisk,
|
symbol_asterisk,
|
||||||
|
@ -44,25 +43,31 @@ pub const Token = union(enum) {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.unknown => |unknown| @ptrCast([*]const u8, &unknown)[0 .. 1],
|
.unknown => |unknown| @ptrCast([*]const u8, &unknown)[0 .. 1],
|
||||||
.newline => "newline",
|
.newline => "newline",
|
||||||
.identifier_global => |identifier| identifier,
|
|
||||||
.identifier_local => |identifier| identifier,
|
|
||||||
|
|
||||||
.assign => "=",
|
.global => |identifier| identifier,
|
||||||
.plus => "+",
|
.local => |identifier| identifier,
|
||||||
.minus => "-",
|
|
||||||
.asterisk => "*",
|
.symbol_plus => "+",
|
||||||
.forward_slash => "/",
|
.symbol_minus => "-",
|
||||||
.paren_left => "(",
|
.symbol_asterisk => "*",
|
||||||
.paren_right => ")",
|
.symbol_forward_slash => "/",
|
||||||
.bang => "!",
|
.symbol_paren_left => "(",
|
||||||
.comma => ",",
|
.symbol_paren_right => ")",
|
||||||
.at => "@",
|
.symbol_bang => "!",
|
||||||
.brace_left => "{",
|
.symbol_comma => ",",
|
||||||
.brace_right => "}",
|
.symbol_at => "@",
|
||||||
.bracket_left => "[",
|
.symbol_brace_left => "{",
|
||||||
.bracket_right => "]",
|
.symbol_brace_right => "}",
|
||||||
.period => ".",
|
.symbol_bracket_left => "[",
|
||||||
.arrow => "=>",
|
.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,
|
.integer => |literal| literal,
|
||||||
.real => |literal| literal,
|
.real => |literal| literal,
|
||||||
|
@ -80,314 +85,329 @@ pub const Token = union(enum) {
|
||||||
pub const Tokenizer = struct {
|
pub const Tokenizer = struct {
|
||||||
source: []const u8,
|
source: []const u8,
|
||||||
lines_stepped: usize = 1,
|
lines_stepped: usize = 1,
|
||||||
previous_token: Token = .newline,
|
current_token: Token = .{.unknown = 0},
|
||||||
current_token: Token = .newline,
|
|
||||||
|
|
||||||
pub fn has_next(self: Tokenizer) bool {
|
const StepOptions = struct {
|
||||||
return self.source.len != 0;
|
include_newlines: bool,
|
||||||
}
|
};
|
||||||
|
|
||||||
pub fn step(self: *Tokenizer) bool {
|
|
||||||
self.previous_token = self.current_token;
|
|
||||||
|
|
||||||
|
pub fn step(self: *Tokenizer, options: StepOptions) bool {
|
||||||
var cursor = @as(usize, 0);
|
var cursor = @as(usize, 0);
|
||||||
|
|
||||||
defer self.source = self.source[cursor ..];
|
defer self.source = self.source[cursor ..];
|
||||||
|
|
||||||
while (self.has_next()) switch (self.source[cursor]) {
|
defer @import("std").debug.print("{s}\n", .{self.current_token.text()});
|
||||||
'#' => {
|
|
||||||
cursor += 1;
|
|
||||||
|
|
||||||
while (self.has_next() and (self.source[cursor] == '\n')) {
|
while (cursor < self.source.len) {
|
||||||
|
switch (self.source[cursor]) {
|
||||||
|
'#' => {
|
||||||
cursor += 1;
|
cursor += 1;
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
' ', '\t' => cursor += 1,
|
while (cursor < self.source.len and self.source[cursor] == '\n') {
|
||||||
|
|
||||||
'\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,
|
|
||||||
|
|
||||||
'.' => {
|
|
||||||
cursor += 1;
|
cursor += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
while (self.has_next()) switch (self.source[cursor]) {
|
' ', '\t' => cursor += 1,
|
||||||
'0' ... '9' => cursor += 1,
|
|
||||||
else => break,
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
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;
|
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;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
'>' => {
|
else => break,
|
||||||
cursor += 1;
|
};
|
||||||
self.current_token = .symbol_lambda;
|
|
||||||
|
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;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.current_token = .symbol_equals;
|
self.current_token = .{.local = identifier};
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
'<' => {
|
|
||||||
cursor += 1;
|
|
||||||
|
|
||||||
if (self.has_next() and (self.source[cursor] == '=')) {
|
|
||||||
cursor += 1;
|
|
||||||
self.current_token = .symbol_less_equals;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
|
|
||||||
self.current_token = .symbol_less_than;
|
'@' => {
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
'>' => {
|
|
||||||
cursor += 1;
|
|
||||||
|
|
||||||
if (self.has_next() and (self.source[cursor] == '=')) {
|
|
||||||
cursor += 1;
|
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;
|
return true;
|
||||||
}
|
},
|
||||||
|
|
||||||
self.current_token = .symbol_greater_than;
|
'"' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
return true;
|
const begin = cursor;
|
||||||
},
|
|
||||||
|
|
||||||
'.' => {
|
cursor += 1;
|
||||||
self.current_token = .symbol_period;
|
|
||||||
cursor += 1;
|
|
||||||
|
|
||||||
return true;
|
while (cursor < self.source.len) switch (self.source[cursor]) {
|
||||||
},
|
'"' => break,
|
||||||
|
else => cursor += 1,
|
||||||
|
};
|
||||||
|
|
||||||
else => {
|
self.current_token = .{.string = self.source[begin .. cursor]};
|
||||||
self.current_token = .{.unknown = self.source[cursor]};
|
cursor += 1;
|
||||||
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;
|
self.current_token = .newline;
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,13 @@ pub const Ref = union (Primitive) {
|
||||||
object: Object,
|
object: Object,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const RuntimeError = coral.io.AllocationError || CheckError || error {
|
pub const ParseError = error {
|
||||||
|
OutOfMemory,
|
||||||
BadSyntax,
|
BadSyntax,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const RuntimeError = CheckError || ParseError;
|
||||||
|
|
||||||
pub const Val = union (Primitive) {
|
pub const Val = union (Primitive) {
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
|
|
@ -59,17 +59,15 @@ pub fn run_app(base_file_system: file.System) void {
|
||||||
const Logger = struct {
|
const Logger = struct {
|
||||||
const Self = @This();
|
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);
|
ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", message.len, message.ptr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var logger = Logger{};
|
|
||||||
|
|
||||||
var script_environment = kym.Environment.init(heap.allocator, .{
|
var script_environment = kym.Environment.init(heap.allocator, .{
|
||||||
.values_max = 512,
|
.values_max = 512,
|
||||||
.calls_max = 512,
|
.calls_max = 512,
|
||||||
.reporter = kym.Environment.Reporter.bind(Logger, &logger, Logger.log),
|
.reporter = kym.Environment.Reporter.bind(Logger, &.{}, Logger.log),
|
||||||
}) catch {
|
}) catch {
|
||||||
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to initialize Kym vm\n");
|
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to initialize Kym vm\n");
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
const ona = @import("ona");
|
const ona = @import("ona");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
|
Loading…
Reference in New Issue