Merge pull request 'Add Function Literal Syntax to Ona Script' (#39) from kym-functions into main
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			Reviewed-on: #39
This commit is contained in:
		
						commit
						a0554d0d2e
					
				| @ -1,18 +1,25 @@ | ||||
| 
 | ||||
| var i = 0 | ||||
| 
 | ||||
| let pr = lambda (str): | ||||
| 	@print("This is a func call") | ||||
| 	@print(str) | ||||
| end | ||||
| 
 | ||||
| pr("") | ||||
| 
 | ||||
| while i < 5: | ||||
| 	@print("hello, world") | ||||
| 	pr("hello, world") | ||||
| 
 | ||||
| 	i = i + 1 | ||||
| end | ||||
| 
 | ||||
| if i > 6: | ||||
| 	@print("`i` greater than `6`") | ||||
| 	pr("`i` greater than `6`") | ||||
| elif i == 4: | ||||
| 	@print("`i` is equal to `4`") | ||||
| 	pr("`i` is equal to `4`") | ||||
| else: | ||||
| 	@print("i'unno") | ||||
| 	pr("i'unno") | ||||
| end | ||||
| 
 | ||||
| return { | ||||
|  | ||||
| @ -13,7 +13,7 @@ pub const Manifest = struct { | ||||
| 	tick_rate: f32 = 60.0, | ||||
| 
 | ||||
| 	pub fn load(self: *Manifest, env: *kym.RuntimeEnv) kym.RuntimeError!void { | ||||
| 		const manifest = try env.expect(try env.import(file.Path.from(&.{"app.ona"}))); | ||||
| 		const manifest = try env.import(file.Path.from(&.{"app.ona"})) orelse return; | ||||
| 
 | ||||
| 		defer env.discard(manifest); | ||||
| 
 | ||||
|  | ||||
| @ -4,6 +4,24 @@ const coral = @import("coral"); | ||||
| 
 | ||||
| const file = @import("./file.zig"); | ||||
| 
 | ||||
| pub const Frame = struct { | ||||
| 	name: []const coral.io.Byte = "", | ||||
| 	arg_count: u8, | ||||
| 	locals_top: usize, | ||||
| 
 | ||||
| 	pub fn args(self: *const Frame, env: *RuntimeEnv) []const ?*const RuntimeRef { | ||||
| 		return env.locals.values[self.locals_top .. (self.locals_top + self.arg_count)]; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn get_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) RuntimeError!*const RuntimeRef { | ||||
| 		return self.has_arg(env, arg_index) orelse env.raise(error.BadOperation, "nil reference"); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn has_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) ?*const RuntimeRef { | ||||
| 		return if (arg_index >= self.arg_count) null else env.locals.values[self.locals_top + arg_index]; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| pub const Fixed = i32; | ||||
| 
 | ||||
| pub const Float = f64; | ||||
| @ -18,6 +36,7 @@ pub const RuntimeEnv = struct { | ||||
| 	const Chunk = struct { | ||||
| 		env: *RuntimeEnv, | ||||
| 		name: []coral.io.Byte, | ||||
| 		arity: u8, | ||||
| 		opcodes: OpcodeList, | ||||
| 		constants: ConstList, | ||||
| 
 | ||||
| @ -33,6 +52,7 @@ pub const RuntimeEnv = struct { | ||||
| 			float: Float, | ||||
| 			string: []const coral.io.Byte, | ||||
| 			symbol: []const coral.io.Byte, | ||||
| 			chunk: Chunk, | ||||
| 		}; | ||||
| 
 | ||||
| 		const Opcode = union (enum) { | ||||
| @ -124,17 +144,39 @@ pub const RuntimeEnv = struct { | ||||
| 						}); | ||||
| 					}, | ||||
| 
 | ||||
| 					.table_literal => |fields| { | ||||
| 						if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) { | ||||
| 					.table_literal => |literal| { | ||||
| 						if (literal.values.len > coral.math.max_int(@typeInfo(u32).Int)) { | ||||
| 							return error.OutOfMemory; | ||||
| 						} | ||||
| 
 | ||||
| 						for (fields.values) |field| { | ||||
| 						for (literal.values) |field| { | ||||
| 							try self.compile_expression(chunk, field.value_expression); | ||||
| 							try self.compile_expression(chunk, field.key_expression); | ||||
| 						} | ||||
| 
 | ||||
| 						try chunk.opcodes.push_one(.{.push_table = @intCast(fields.values.len)}); | ||||
| 						try chunk.opcodes.push_one(.{.push_table = @intCast(literal.values.len)}); | ||||
| 					}, | ||||
| 
 | ||||
| 					.lambda_literal => |literal| { | ||||
| 						if (literal.argument_identifiers.values.len > coral.math.max_int(@typeInfo(u8).Int)) { | ||||
| 							return error.OutOfMemory; | ||||
| 						} | ||||
| 
 | ||||
| 						var lambda_chunk = try Chunk.make( | ||||
| 							chunk.env, | ||||
| 							"<lambda>", | ||||
| 							@intCast(literal.argument_identifiers.values.len), | ||||
| 						); | ||||
| 
 | ||||
| 						errdefer lambda_chunk.free(); | ||||
| 
 | ||||
| 						try lambda_chunk.compile(literal.block_statements.values, literal.argument_identifiers.values); | ||||
| 
 | ||||
| 						try chunk.opcodes.push_one(.{ | ||||
| 							.push_const = try chunk.declare_constant(.{ | ||||
| 								.chunk = lambda_chunk, | ||||
| 							}), | ||||
| 						}); | ||||
| 					}, | ||||
| 
 | ||||
| 					.binary_operation => |operation| { | ||||
| @ -213,11 +255,11 @@ pub const RuntimeEnv = struct { | ||||
| 					}, | ||||
| 
 | ||||
| 					.local_get => |local_get| { | ||||
| 						try chunk.opcodes.push_one(.{ | ||||
| 							.push_local = (self.resolve_local(local_get.identifier) orelse { | ||||
| 								return chunk.env.raise(error.OutOfMemory, "undefined local"); | ||||
| 							}).index, | ||||
| 						}); | ||||
| 						if (self.resolve_local(local_get.identifier)) |local| { | ||||
| 							return chunk.opcodes.push_one(.{.push_local = local.index}); | ||||
| 						} | ||||
| 
 | ||||
| 						return chunk.env.raise(error.OutOfMemory, "undefined local"); | ||||
| 					}, | ||||
| 
 | ||||
| 					.local_set => |local_set| { | ||||
| @ -287,16 +329,26 @@ pub const RuntimeEnv = struct { | ||||
| 
 | ||||
| 						try self.compile_expression(chunk, declare.assigned_expression); | ||||
| 
 | ||||
| 						if (self.locals_count == self.locals_buffer.len) { | ||||
| 							return chunk.env.raise(error.BadSyntax, "chunks may have a maximum of 255 locals"); | ||||
| 						switch (declare.storage) { | ||||
| 							.@"var" => { | ||||
| 								if (!self.declare_local(.{ | ||||
| 									.identifier = declare.identifier, | ||||
| 									.is_readonly = false, | ||||
| 								})) { | ||||
| 									return chunk.env.raise(error.BadSyntax, "too many locals"); | ||||
| 								} | ||||
| 							}, | ||||
| 
 | ||||
| 							.let => { | ||||
| 								// TODO: investigate constant folding. | ||||
| 								if (!self.declare_local(.{ | ||||
| 									.identifier = declare.identifier, | ||||
| 									.is_readonly = false, | ||||
| 								})) { | ||||
| 									return chunk.env.raise(error.BadSyntax, "too many locals"); | ||||
| 								} | ||||
| 							}, | ||||
| 						} | ||||
| 
 | ||||
| 						self.locals_buffer[self.locals_count] = .{ | ||||
| 							.identifier = declare.identifier, | ||||
| 							.is_readonly = declare.storage != .variant, | ||||
| 						}; | ||||
| 
 | ||||
| 						self.locals_count += 1; | ||||
| 					}, | ||||
| 
 | ||||
| 					.block => |block| { | ||||
| @ -348,6 +400,17 @@ pub const RuntimeEnv = struct { | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			fn declare_local(self: *CompilationUnit, local: Local) bool { | ||||
| 				if (self.locals_count == self.locals_buffer.len) { | ||||
| 					return false; | ||||
| 				} | ||||
| 
 | ||||
| 				self.locals_buffer[self.locals_count] = local; | ||||
| 				self.locals_count += 1; | ||||
| 
 | ||||
| 				return true; | ||||
| 			} | ||||
| 
 | ||||
| 			fn resolve_local(self: *CompilationUnit, local_identifier: []const coral.io.Byte) ?ResolvedLocal { | ||||
| 				if (self.locals_count == 0) { | ||||
| 					return null; | ||||
| @ -372,11 +435,29 @@ pub const RuntimeEnv = struct { | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		fn compile(self: *Chunk, statements: []const ast.Statement) RuntimeError!void { | ||||
| 		fn compile(self: *Chunk, statements: []const ast.Statement, args: []const []const coral.io.Byte) RuntimeError!void { | ||||
| 			var unit = CompilationUnit{}; | ||||
| 			var has_returned = false; | ||||
| 
 | ||||
| 			for (args) |arg| { | ||||
| 				if (!unit.declare_local(.{ | ||||
| 					.is_readonly = true, | ||||
| 					.identifier = arg, | ||||
| 				})) { | ||||
| 					return self.env.raise(error.BadSyntax, "too many arguments"); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			for (statements) |statement| { | ||||
| 				try unit.compile_statement(self, statement); | ||||
| 
 | ||||
| 				if (statement == .@"return") { | ||||
| 					has_returned = true; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (!has_returned) { | ||||
| 				try self.opcodes.push_one(.push_nil); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| @ -385,27 +466,27 @@ pub const RuntimeEnv = struct { | ||||
| 				return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); | ||||
| 			} | ||||
| 
 | ||||
| 			const constant_index = self.constants.values.len; | ||||
| 
 | ||||
| 			try self.constants.push_one(try switch (constant) { | ||||
| 			const constant_object = try switch (constant) { | ||||
| 				.fixed => |fixed| self.env.new_fixed(fixed), | ||||
| 				.float => |float| self.env.new_float(float), | ||||
| 				.string => |string| self.env.new_string(string), | ||||
| 				.symbol => |symbol| self.env.new_symbol(symbol), | ||||
| 			}); | ||||
| 
 | ||||
| 			return @intCast(constant_index); | ||||
| 				.chunk => |chunk| self.env.new_dynamic(coral.io.bytes_of(&chunk).ptr, &.{ | ||||
| 					.size = @sizeOf(Chunk), | ||||
| 					.name = "lambda", | ||||
| 					.destruct = typeinfo_destruct, | ||||
| 					.call = typeinfo_call, | ||||
| 				}), | ||||
| 			}; | ||||
| 
 | ||||
| 			errdefer self.env.discard(constant_object); | ||||
| 			try self.constants.push_one(constant_object); | ||||
| 
 | ||||
| 			return @intCast(self.constants.values.len - 1); | ||||
| 		} | ||||
| 
 | ||||
| 		fn execute(self: *Chunk) RuntimeError!?*RuntimeRef { | ||||
| 			try self.env.frames.push_one(.{ | ||||
| 				.arg_count = 0, | ||||
| 				.locals_top = self.env.locals.values.len, | ||||
| 				.name = self.name, | ||||
| 			}); | ||||
| 
 | ||||
| 			defer coral.debug.assert(self.env.frames.pop() != null); | ||||
| 
 | ||||
| 		fn execute(self: *Chunk, frame: Frame) RuntimeError!?*RuntimeRef { | ||||
| 			var opcode_cursor = @as(u32, 0); | ||||
| 
 | ||||
| 			while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) { | ||||
| @ -433,7 +514,7 @@ pub const RuntimeEnv = struct { | ||||
| 							return self.env.raise(error.IllegalState, "invalid local"); | ||||
| 						} | ||||
| 
 | ||||
| 						if (self.env.locals.values[push_local]) |local| { | ||||
| 						if (self.env.locals.values[frame.locals_top + push_local]) |local| { | ||||
| 							try self.env.locals.push_one(try self.env.acquire(local)); | ||||
| 						} else { | ||||
| 							try self.env.locals.push_one(null); | ||||
| @ -448,25 +529,19 @@ pub const RuntimeEnv = struct { | ||||
| 						{ | ||||
| 							const dynamic = table.object().payload.dynamic; | ||||
| 							const userdata = dynamic.userdata(); | ||||
| 							const table_set = dynamic.typeinfo().set; | ||||
| 							var popped = @as(usize, 0); | ||||
| 
 | ||||
| 							while (popped < push_table) : (popped += 1) { | ||||
| 								const index = try self.env.expect(try self.pop_local()); | ||||
| 								const index = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 								defer self.env.discard(index); | ||||
| 
 | ||||
| 								const maybe_value = try self.pop_local(); | ||||
| 								if (try self.pop_local()) |value| { | ||||
| 									defer self.env.discard(value); | ||||
| 
 | ||||
| 								defer { | ||||
| 									if (maybe_value) |value| { | ||||
| 										self.env.discard(value); | ||||
| 									} | ||||
| 									try table_set(self.env, userdata, index, value); | ||||
| 								} | ||||
| 
 | ||||
| 								try dynamic.typeinfo().set(.{ | ||||
| 									.userdata = userdata, | ||||
| 									.env = self.env, | ||||
| 								}, index, maybe_value); | ||||
| 							} | ||||
| 						} | ||||
| 
 | ||||
| @ -487,7 +562,7 @@ pub const RuntimeEnv = struct { | ||||
| 					}, | ||||
| 
 | ||||
| 					.local_set => |local_set| { | ||||
| 						const local = &self.env.locals.values[local_set]; | ||||
| 						const local = &self.env.locals.values[frame.locals_top + local_set]; | ||||
| 
 | ||||
| 						if (local.*) |previous_local| { | ||||
| 							self.env.discard(previous_local); | ||||
| @ -497,11 +572,11 @@ pub const RuntimeEnv = struct { | ||||
| 					}, | ||||
| 
 | ||||
| 					.object_get => { | ||||
| 						const index = try self.env.expect(try self.pop_local()); | ||||
| 						const index = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(index); | ||||
| 
 | ||||
| 						const indexable = try self.env.expect(try self.pop_local()); | ||||
| 						const indexable = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(indexable); | ||||
| 
 | ||||
| @ -542,36 +617,21 @@ pub const RuntimeEnv = struct { | ||||
| 
 | ||||
| 					.object_call => |object_call| { | ||||
| 						const result = call: { | ||||
| 							const callable = try self.env.expect(try self.pop_local()); | ||||
| 							const callable = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 							defer self.env.discard(callable); | ||||
| 
 | ||||
| 							try self.env.frames.push_one(.{ | ||||
| 								.name = "<lambda>", | ||||
| 								.arg_count = object_call, | ||||
| 								.locals_top = self.env.locals.values.len, | ||||
| 							}); | ||||
| 							const call_frame = try self.env.push_frame(object_call); | ||||
| 
 | ||||
| 							defer coral.debug.assert(self.env.frames.pop() != null); | ||||
| 							defer self.env.pop_frame(); | ||||
| 
 | ||||
| 							break: call try switch (callable.object().payload) { | ||||
| 								.syscall => |syscall| syscall(self.env), | ||||
| 
 | ||||
| 								.dynamic => |dynamic| dynamic.typeinfo().call(.{ | ||||
| 									.userdata = dynamic.userdata(), | ||||
| 									.env = self.env, | ||||
| 								}), | ||||
| 
 | ||||
| 								.syscall => |syscall| syscall(self.env, call_frame), | ||||
| 								.dynamic => |dynamic| dynamic.typeinfo().call(self.env, dynamic.userdata(), call_frame), | ||||
| 								else => self.env.raise(error.TypeMismatch, "object is not callable"), | ||||
| 							}; | ||||
| 						}; | ||||
| 
 | ||||
| 						for (0 .. object_call) |_| { | ||||
| 							if (try self.pop_local()) |popped_arg| { | ||||
| 								self.env.discard(popped_arg); | ||||
| 							} | ||||
| 						} | ||||
| 
 | ||||
| 						errdefer { | ||||
| 							if (result) |ref| { | ||||
| 								self.env.discard(ref); | ||||
| @ -592,7 +652,7 @@ pub const RuntimeEnv = struct { | ||||
| 					}, | ||||
| 
 | ||||
| 					.neg => { | ||||
| 						const value = try self.env.expect(try self.pop_local()); | ||||
| 						const value = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(value); | ||||
| 
 | ||||
| @ -604,11 +664,11 @@ pub const RuntimeEnv = struct { | ||||
| 					}, | ||||
| 
 | ||||
| 					.add => { | ||||
| 						const rhs = try self.env.expect(try self.pop_local()); | ||||
| 						const rhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(rhs); | ||||
| 
 | ||||
| 						const lhs = try self.env.expect(try self.pop_local()); | ||||
| 						const lhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(lhs); | ||||
| 
 | ||||
| @ -644,11 +704,11 @@ pub const RuntimeEnv = struct { | ||||
| 					}, | ||||
| 
 | ||||
| 					.sub => { | ||||
| 						const rhs = try self.env.expect(try self.pop_local()); | ||||
| 						const rhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(rhs); | ||||
| 
 | ||||
| 						const lhs = try self.env.expect(try self.pop_local()); | ||||
| 						const lhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(lhs); | ||||
| 
 | ||||
| @ -684,11 +744,11 @@ pub const RuntimeEnv = struct { | ||||
| 					}, | ||||
| 
 | ||||
| 					.mul => { | ||||
| 						const rhs = try self.env.expect(try self.pop_local()); | ||||
| 						const rhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(rhs); | ||||
| 
 | ||||
| 						const lhs = try self.env.expect(try self.pop_local()); | ||||
| 						const lhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(lhs); | ||||
| 
 | ||||
| @ -724,11 +784,11 @@ pub const RuntimeEnv = struct { | ||||
| 					}, | ||||
| 
 | ||||
| 					.div => { | ||||
| 						const rhs = try self.env.expect(try self.pop_local()); | ||||
| 						const rhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(rhs); | ||||
| 
 | ||||
| 						const lhs = try self.env.expect(try self.pop_local()); | ||||
| 						const lhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(lhs); | ||||
| 
 | ||||
| @ -780,11 +840,11 @@ pub const RuntimeEnv = struct { | ||||
| 					}, | ||||
| 
 | ||||
| 					.cgt => { | ||||
| 						const rhs = try self.env.expect(try self.pop_local()); | ||||
| 						const rhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(rhs); | ||||
| 
 | ||||
| 						const lhs = try self.env.expect(try self.pop_local()); | ||||
| 						const lhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(lhs); | ||||
| 
 | ||||
| @ -806,11 +866,11 @@ pub const RuntimeEnv = struct { | ||||
| 					}, | ||||
| 
 | ||||
| 					.clt => { | ||||
| 						const rhs = try self.env.expect(try self.pop_local()); | ||||
| 						const rhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(rhs); | ||||
| 
 | ||||
| 						const lhs = try self.env.expect(try self.pop_local()); | ||||
| 						const lhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(lhs); | ||||
| 
 | ||||
| @ -832,11 +892,11 @@ pub const RuntimeEnv = struct { | ||||
| 					}, | ||||
| 
 | ||||
| 					.cge => { | ||||
| 						const rhs = try self.env.expect(try self.pop_local()); | ||||
| 						const rhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(rhs); | ||||
| 
 | ||||
| 						const lhs = try self.env.expect(try self.pop_local()); | ||||
| 						const lhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(lhs); | ||||
| 
 | ||||
| @ -858,11 +918,11 @@ pub const RuntimeEnv = struct { | ||||
| 					}, | ||||
| 
 | ||||
| 					.cle => { | ||||
| 						const rhs = try self.env.expect(try self.pop_local()); | ||||
| 						const rhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(rhs); | ||||
| 
 | ||||
| 						const lhs = try self.env.expect(try self.pop_local()); | ||||
| 						const lhs = try self.expect(try self.pop_local()); | ||||
| 
 | ||||
| 						defer self.env.discard(lhs); | ||||
| 
 | ||||
| @ -912,6 +972,10 @@ pub const RuntimeEnv = struct { | ||||
| 			return self.pop_local(); | ||||
| 		} | ||||
| 
 | ||||
| 		fn expect(self: *Chunk, value: ?*RuntimeRef) RuntimeError!*RuntimeRef { | ||||
| 			return value orelse self.env.raise(error.TypeMismatch, "nil reference"); | ||||
| 		} | ||||
| 
 | ||||
| 		fn free(self: *Chunk) void { | ||||
| 			while (self.constants.pop()) |constant| { | ||||
| 				self.env.discard(constant); | ||||
| @ -922,11 +986,12 @@ pub const RuntimeEnv = struct { | ||||
| 			self.env.allocator.deallocate(self.name); | ||||
| 		} | ||||
| 
 | ||||
| 		fn make(env: *RuntimeEnv, name: []const coral.io.Byte) coral.io.AllocationError!Chunk { | ||||
| 		fn make(env: *RuntimeEnv, name: []const coral.io.Byte, arity: u8) coral.io.AllocationError!Chunk { | ||||
| 			return .{ | ||||
| 				.name = try coral.io.allocate_copy(env.allocator, name), | ||||
| 				.opcodes = OpcodeList.make(env.allocator), | ||||
| 				.constants = ConstList.make(env.allocator), | ||||
| 				.arity = arity, | ||||
| 				.env = env, | ||||
| 			}; | ||||
| 		} | ||||
| @ -934,15 +999,27 @@ pub const RuntimeEnv = struct { | ||||
| 		fn pop_local(self: *Chunk) RuntimeError!?*RuntimeRef { | ||||
| 			return self.env.locals.pop() orelse self.env.raise(error.IllegalState, "stack underflow"); | ||||
| 		} | ||||
| 
 | ||||
| 		fn typeinfo_call(env: *RuntimeEnv, userdata: []coral.io.Byte, frame: Frame) RuntimeError!?*RuntimeRef { | ||||
| 			const chunk = @as(*Chunk, @ptrCast(@alignCast(userdata))); | ||||
| 
 | ||||
| 			if (frame.arg_count < chunk.arity) { | ||||
| 				return env.raise(error.BadOperation, "expected more arguments"); | ||||
| 			} | ||||
| 
 | ||||
| 			return chunk.execute(frame); | ||||
| 		} | ||||
| 
 | ||||
| 		fn typeinfo_destruct(env: *RuntimeEnv, userdata: []coral.io.Byte) void { | ||||
| 			_ = env; | ||||
| 
 | ||||
| 			@as(*Chunk, @ptrCast(@alignCast(userdata))).free(); | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	const ConstList = coral.list.Stack(*RuntimeRef); | ||||
| 
 | ||||
| 	const FrameStack = coral.list.Stack(struct { | ||||
| 		name: []const coral.io.Byte, | ||||
| 		arg_count: u8, | ||||
| 		locals_top: usize, | ||||
| 	}); | ||||
| 	const FrameStack = coral.list.Stack(Frame); | ||||
| 
 | ||||
| 	pub const Options = struct { | ||||
| 		import_access: file.Access = .null, | ||||
| @ -966,15 +1043,15 @@ pub const RuntimeEnv = struct { | ||||
| 		associative: RefTable, | ||||
| 		contiguous: RefList, | ||||
| 
 | ||||
| 		fn typeinfo_destruct(method: Typeinfo.Method) void { | ||||
| 			const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); | ||||
| 		fn typeinfo_destruct(env: *RuntimeEnv, userdata: []coral.io.Byte) void { | ||||
| 			const table = @as(*Table, @ptrCast(@alignCast(userdata))); | ||||
| 
 | ||||
| 			{ | ||||
| 				var field_iterable = table.associative.as_iterable(); | ||||
| 
 | ||||
| 				while (field_iterable.next()) |entry| { | ||||
| 					method.env.discard(entry.key); | ||||
| 					method.env.discard(entry.value); | ||||
| 					env.discard(entry.key); | ||||
| 					env.discard(entry.value); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| @ -982,18 +1059,18 @@ pub const RuntimeEnv = struct { | ||||
| 
 | ||||
| 			while (table.contiguous.pop()) |value| { | ||||
| 				if (value) |ref| { | ||||
| 					method.env.discard(ref); | ||||
| 					env.discard(ref); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			table.contiguous.free(); | ||||
| 		} | ||||
| 
 | ||||
| 		fn typeinfo_get(method: Typeinfo.Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { | ||||
| 			const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); | ||||
| 			const acquired_index = try method.env.acquire(index); | ||||
| 		fn typeinfo_get(env: *RuntimeEnv, userdata: []coral.io.Byte, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { | ||||
| 			const table = @as(*Table, @ptrCast(@alignCast(userdata))); | ||||
| 			const acquired_index = try env.acquire(index); | ||||
| 
 | ||||
| 			defer method.env.discard(acquired_index); | ||||
| 			defer env.discard(acquired_index); | ||||
| 
 | ||||
| 			if (acquired_index.is_fixed()) |fixed| { | ||||
| 				if (fixed < 0) { | ||||
| @ -1002,22 +1079,22 @@ pub const RuntimeEnv = struct { | ||||
| 				} | ||||
| 
 | ||||
| 				if (fixed < table.contiguous.values.len) { | ||||
| 					return method.env.acquire(table.contiguous.values[@intCast(fixed)] orelse return null); | ||||
| 					return env.acquire(table.contiguous.values[@intCast(fixed)] orelse return null); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (table.associative.lookup(acquired_index)) |value_ref| { | ||||
| 				return method.env.acquire(value_ref); | ||||
| 				return env.acquire(value_ref); | ||||
| 			} | ||||
| 
 | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 		fn typeinfo_set(method: Typeinfo.Method, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { | ||||
| 			const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); | ||||
| 			const acquired_index = try method.env.acquire(index); | ||||
| 		fn typeinfo_set(env: *RuntimeEnv, userdata: []coral.io.Byte, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { | ||||
| 			const table = @as(*Table, @ptrCast(@alignCast(userdata))); | ||||
| 			const acquired_index = try env.acquire(index); | ||||
| 
 | ||||
| 			errdefer method.env.discard(acquired_index); | ||||
| 			errdefer env.discard(acquired_index); | ||||
| 
 | ||||
| 			if (acquired_index.is_fixed()) |fixed| { | ||||
| 				if (fixed < 0) { | ||||
| @ -1029,32 +1106,31 @@ pub const RuntimeEnv = struct { | ||||
| 					const maybe_replacing = &table.contiguous.values[@intCast(fixed)]; | ||||
| 
 | ||||
| 					if (maybe_replacing.*) |replacing| { | ||||
| 						method.env.discard(replacing); | ||||
| 						env.discard(replacing); | ||||
| 					} | ||||
| 
 | ||||
| 					maybe_replacing.* = if (value) |ref| try method.env.acquire(ref) else null; | ||||
| 					maybe_replacing.* = if (value) |ref| try env.acquire(ref) else null; | ||||
| 
 | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			const acquired_value = try method.env.acquire(value orelse { | ||||
| 			const acquired_value = try env.acquire(value orelse { | ||||
| 				if (table.associative.remove(acquired_index)) |removed| { | ||||
| 					method.env.discard(removed.key); | ||||
| 					method.env.discard(removed.value); | ||||
| 					env.discard(removed.key); | ||||
| 					env.discard(removed.value); | ||||
| 				} | ||||
| 
 | ||||
| 				return; | ||||
| 			}); | ||||
| 
 | ||||
| 			errdefer method.env.discard(acquired_value); | ||||
| 			errdefer env.discard(acquired_value); | ||||
| 
 | ||||
| 			if (try table.associative.replace(acquired_index, acquired_value)) |replaced| { | ||||
| 				method.env.discard(replaced.key); | ||||
| 				method.env.discard(replaced.value); | ||||
| 				env.discard(replaced.key); | ||||
| 				env.discard(replaced.value); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 	}; | ||||
| 
 | ||||
| 	pub fn acquire(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!*RuntimeRef { | ||||
| @ -1067,45 +1143,15 @@ pub const RuntimeEnv = struct { | ||||
| 		return @ptrCast(object); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn acquire_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*RuntimeRef { | ||||
| 		const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow"); | ||||
| 
 | ||||
| 		if (index < frame.arg_count) { | ||||
| 			if (self.locals.values[(frame.locals_top - frame.arg_count) + index]) |local| { | ||||
| 				return self.acquire(local); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return null; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn bind_system(self: *RuntimeEnv, name: []const coral.io.Byte, value: *const RuntimeRef) RuntimeError!void { | ||||
| 		const name_symbol = try self.new_symbol(name); | ||||
| 
 | ||||
| 		errdefer self.discard(name_symbol); | ||||
| 
 | ||||
| 		const acquired_value = try self.acquire(value); | ||||
| 
 | ||||
| 		errdefer self.discard(acquired_value); | ||||
| 
 | ||||
| 		if (try self.system_bindings.replace(name_symbol, acquired_value)) |replaced| { | ||||
| 			self.discard(replaced.key); | ||||
| 			self.discard(replaced.value); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn call(self: *RuntimeEnv, callable: *RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef { | ||||
| 		try self.locals.push_all(args); | ||||
| 		// TODO: Handle errors. | ||||
| 		for (args) |arg| { | ||||
| 			try self.locals.push_one(try self.acquire(arg)); | ||||
| 		} | ||||
| 
 | ||||
| 		defer coral.io.assert(self.locals.drop(args.len)); | ||||
| 		try self.push_frame(args.len); | ||||
| 
 | ||||
| 		try self.frames.push_one(.{ | ||||
| 			.name = "<native>", | ||||
| 			.arg_count = args.len, | ||||
| 			.locals_top = self.locals.values.len, | ||||
| 		}); | ||||
| 
 | ||||
| 		defer coral.io.assert(self.frames.pop() != null); | ||||
| 		defer self.pop_frame(); | ||||
| 
 | ||||
| 		return switch (callable.object().payload) { | ||||
| 			.syscall => |syscall| syscall(self.env), | ||||
| @ -1137,10 +1183,7 @@ pub const RuntimeEnv = struct { | ||||
| 
 | ||||
| 				.dynamic => |dynamic| { | ||||
| 					if (dynamic.typeinfo().destruct) |destruct| { | ||||
| 						destruct(.{ | ||||
| 							.userdata = dynamic.userdata(), | ||||
| 							.env = self, | ||||
| 						}); | ||||
| 						destruct(self, dynamic.userdata()); | ||||
| 					} | ||||
| 
 | ||||
| 					self.allocator.deallocate(dynamic.unpack()); | ||||
| @ -1159,7 +1202,7 @@ pub const RuntimeEnv = struct { | ||||
| 		defer self.allocator.deallocate(file_data); | ||||
| 
 | ||||
| 		const file_name = file_path.to_string() orelse "<script>"; | ||||
| 		var chunk = try Chunk.make(self, file_name); | ||||
| 		var chunk = try Chunk.make(self, file_name, 0); | ||||
| 
 | ||||
| 		defer chunk.free(); | ||||
| 
 | ||||
| @ -1170,13 +1213,13 @@ pub const RuntimeEnv = struct { | ||||
| 		try chunk.compile(ast_tree.parse(file_data) catch |parse_error| return switch (parse_error) { | ||||
| 			error.BadSyntax => self.raise(error.BadSyntax, ast_tree.error_message()), | ||||
| 			error.OutOfMemory => error.OutOfMemory, | ||||
| 		}); | ||||
| 		}, &.{}); | ||||
| 
 | ||||
| 		return chunk.execute(); | ||||
| 	} | ||||
| 		const frame = try self.push_frame(0); | ||||
| 
 | ||||
| 	pub fn expect(self: *RuntimeEnv, value: ?*RuntimeRef) RuntimeError!*RuntimeRef { | ||||
| 		return value orelse self.raise(error.TypeMismatch, "nil reference"); | ||||
| 		defer self.pop_frame(); | ||||
| 
 | ||||
| 		return chunk.execute(frame); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn free(self: *RuntimeEnv) void { | ||||
| @ -1201,6 +1244,14 @@ pub const RuntimeEnv = struct { | ||||
| 
 | ||||
| 	pub fn get(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { | ||||
| 		return switch (indexable.object().payload) { | ||||
| 			.false => self.raise(error.TypeMismatch, "false is not get-indexable"), | ||||
| 			.true => self.raise(error.TypeMismatch, "true is not get-indexable"), | ||||
| 			.fixed => self.raise(error.TypeMismatch, "fixed is not get-indexable"), | ||||
| 			.float => self.raise(error.TypeMismatch, "float is not get-indexable"), | ||||
| 			.string => self.raise(error.TypeMismatch, "string is not get-indexable"), | ||||
| 			.symbol => self.raise(error.TypeMismatch, "symbol is not get-indexable"), | ||||
| 			.syscall => self.raise(error.TypeMismatch, "syscall is not get-indexable"), | ||||
| 
 | ||||
| 			.vector2 => |vector2| swizzle: { | ||||
| 				const swizzle_symbol = try self.unbox_symbol(index); | ||||
| 				var swizzle_buffer = [_]f32{0} ** 3; | ||||
| @ -1252,12 +1303,7 @@ pub const RuntimeEnv = struct { | ||||
| 				} | ||||
| 			}, | ||||
| 
 | ||||
| 			.dynamic => |dynamic| dynamic.typeinfo().get(.{ | ||||
| 				.userdata = dynamic.userdata(), | ||||
| 				.env = self, | ||||
| 			}, index), | ||||
| 
 | ||||
| 			else => self.raise(error.TypeMismatch, "object is not get-indexable"), | ||||
| 			.dynamic => |dynamic| dynamic.typeinfo().get(self, dynamic.userdata(), index), | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| @ -1402,6 +1448,29 @@ pub const RuntimeEnv = struct { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn pop_frame(self: *RuntimeEnv) void { | ||||
| 		var to_pop = self.locals.values.len - (self.frames.pop() orelse unreachable).locals_top; | ||||
| 
 | ||||
| 		while (to_pop != 0) { | ||||
| 			if (self.locals.pop() orelse unreachable) |local| { | ||||
| 				self.discard(local); | ||||
| 			} | ||||
| 
 | ||||
| 			to_pop -= 1; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn push_frame(self: *RuntimeEnv, arg_count: u8) RuntimeError!Frame { | ||||
| 		const frame = Frame{ | ||||
| 			.arg_count = arg_count, | ||||
| 			.locals_top = self.locals.values.len - arg_count, | ||||
| 		}; | ||||
| 
 | ||||
| 		try self.frames.push_one(frame); | ||||
| 
 | ||||
| 		return frame; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError { | ||||
| 		self.print_error(message); | ||||
| 
 | ||||
| @ -1422,11 +1491,7 @@ pub const RuntimeEnv = struct { | ||||
| 
 | ||||
| 	pub fn set(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { | ||||
| 		return switch (indexable.object().payload) { | ||||
| 			.dynamic => |dynamic| dynamic.typeinfo().set(.{ | ||||
| 				.userdata = dynamic.userdata(), | ||||
| 				.env = self, | ||||
| 			}, index, value), | ||||
| 
 | ||||
| 			.dynamic => |dynamic| dynamic.typeinfo().set(self, dynamic.userdata(), index, value), | ||||
| 			else => self.raise(error.TypeMismatch, "object is not set-indexable"), | ||||
| 		}; | ||||
| 	} | ||||
| @ -1642,31 +1707,26 @@ pub const RuntimeRef = opaque { | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| pub const Syscall = fn (env: *RuntimeEnv) RuntimeError!?*RuntimeRef; | ||||
| pub const Syscall = fn (env: *RuntimeEnv, frame: Frame) RuntimeError!?*RuntimeRef; | ||||
| 
 | ||||
| pub const Typeinfo = struct { | ||||
| 	name: []const coral.io.Byte, | ||||
| 	size: usize, | ||||
| 	destruct: ?*const fn (method: Method) void = null, | ||||
| 	call: *const fn (method: Method) RuntimeError!?*RuntimeRef = default_call, | ||||
| 	get: *const fn (method: Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get, | ||||
| 	set: *const fn (method: Method, value: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set, | ||||
| 	destruct: ?*const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) void = null, | ||||
| 	call: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, frame: Frame) RuntimeError!?*RuntimeRef = default_call, | ||||
| 	get: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get, | ||||
| 	set: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, value: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set, | ||||
| 
 | ||||
| 	pub const Method = struct { | ||||
| 		env: *RuntimeEnv, | ||||
| 		userdata: []coral.io.Byte, | ||||
| 	}; | ||||
| 
 | ||||
| 	fn default_call(method: Method) RuntimeError!?*RuntimeRef { | ||||
| 		return method.env.raise(error.TypeMismatch, "object is not callable"); | ||||
| 	fn default_call(env: *RuntimeEnv, _: []coral.io.Byte, _: Frame) RuntimeError!?*RuntimeRef { | ||||
| 		return env.raise(error.BadOperation, "object is not callable"); | ||||
| 	} | ||||
| 
 | ||||
| 	fn default_get(method: Method, _: *const RuntimeRef) RuntimeError!?*RuntimeRef { | ||||
| 		return method.env.raise(error.TypeMismatch, "object is not index-gettable"); | ||||
| 	fn default_get(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef) RuntimeError!?*RuntimeRef { | ||||
| 		return env.raise(error.BadOperation, "object is not get-indexable"); | ||||
| 	} | ||||
| 
 | ||||
| 	fn default_set(method: Method, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void { | ||||
| 		return method.env.raise(error.TypeMismatch, "object is not index-settable"); | ||||
| 	fn default_set(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void { | ||||
| 		return env.raise(error.BadOperation, "object is not set-indexable"); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| @ -1694,59 +1754,35 @@ pub fn get_key(env: *RuntimeEnv, indexable: *RuntimeRef, key: []const coral.io.B | ||||
| 	return env.get(indexable, key_string); | ||||
| } | ||||
| 
 | ||||
| fn syscall_import(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { | ||||
| 	const arg = try env.expect(try env.acquire_arg(0)); | ||||
| 
 | ||||
| 	defer env.discard(arg); | ||||
| 
 | ||||
| 	return env.import(file.Path.from(&.{try env.unbox_string(arg)})); | ||||
| fn syscall_import(env: *RuntimeEnv, frame: Frame) RuntimeError!?*RuntimeRef { | ||||
| 	return env.import(file.Path.from(&.{try env.unbox_string(try frame.get_arg(env, 0))})); | ||||
| } | ||||
| 
 | ||||
| fn syscall_print(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { | ||||
| 	const arg = try env.expect(try env.acquire_arg(0)); | ||||
| 
 | ||||
| 	defer env.discard(arg); | ||||
| 
 | ||||
| 	env.print(try env.unbox_string(arg)); | ||||
| fn syscall_print(env: *RuntimeEnv, frame: Frame) RuntimeError!?*RuntimeRef { | ||||
| 	env.print(try env.unbox_string(try frame.get_arg(env, 0))); | ||||
| 
 | ||||
| 	return null; | ||||
| } | ||||
| 
 | ||||
| fn syscall_vec2(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { | ||||
| 	const x = decode: { | ||||
| 		const value = try env.expect(try env.acquire_arg(0)); | ||||
| 
 | ||||
| 		defer env.discard(value); | ||||
| 
 | ||||
| 		break: decode @as(f32, @floatCast(try env.unbox_float(value))); | ||||
| 	}; | ||||
| 
 | ||||
| 	if (try env.acquire_arg(1)) |y| { | ||||
| 		defer env.discard(y); | ||||
| fn syscall_vec2(env: *RuntimeEnv, frame: Frame) RuntimeError!?*RuntimeRef { | ||||
| 	const x = @as(f32, @floatCast(try env.unbox_float(try frame.get_arg(env, 0)))); | ||||
| 
 | ||||
| 	if (frame.has_arg(env, 1)) |y| { | ||||
| 		return env.new_vector2(x, @floatCast(try env.unbox_float(y))); | ||||
| 	} | ||||
| 
 | ||||
| 	return env.new_vector2(x, x); | ||||
| } | ||||
| 
 | ||||
| fn syscall_vec3(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { | ||||
| 	const x = decode: { | ||||
| 		const value = try env.expect(try env.acquire_arg(0)); | ||||
| fn syscall_vec3(env: *RuntimeEnv, frame: Frame) RuntimeError!?*RuntimeRef { | ||||
| 	const x = @as(f32, @floatCast(try env.unbox_float(try frame.get_arg(env, 0)))); | ||||
| 
 | ||||
| 		defer env.discard(value); | ||||
| 
 | ||||
| 		break: decode @as(f32, @floatCast(try env.unbox_float(value))); | ||||
| 	}; | ||||
| 
 | ||||
| 	if (try env.acquire_arg(1)) |y| { | ||||
| 		defer env.discard(y); | ||||
| 
 | ||||
| 		const z = try env.expect(try env.acquire_arg(2)); | ||||
| 
 | ||||
| 		defer env.discard(z); | ||||
| 
 | ||||
| 		return env.new_vector3(x, @floatCast(try env.unbox_float(y)), @floatCast(try env.unbox_float(z))); | ||||
| 	if (frame.has_arg(env, 1)) |y| { | ||||
| 		return env.new_vector3( | ||||
| 			x, | ||||
| 			@floatCast(try env.unbox_float(y)), | ||||
| 			@floatCast(try env.unbox_float(try frame.get_arg(env, 2))), | ||||
| 		); | ||||
| 	} | ||||
| 
 | ||||
| 	return env.new_vector3(x, x, x); | ||||
|  | ||||
| @ -74,6 +74,11 @@ pub const Expression = union (enum) { | ||||
| 	table_literal: TableLiteral, | ||||
| 	grouped_expression: *Expression, | ||||
| 
 | ||||
| 	lambda_literal: struct { | ||||
| 		argument_identifiers: IdentifierList, | ||||
| 		block_statements: Statement.List, | ||||
| 	}, | ||||
| 
 | ||||
| 	local_get: struct { | ||||
| 		identifier: []const coral.io.Byte, | ||||
| 	}, | ||||
| @ -129,13 +134,10 @@ pub const Expression = union (enum) { | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| pub const DeclarationStorage = enum { | ||||
| 	variant, | ||||
| 	readonly, | ||||
| }; | ||||
| 
 | ||||
| const ExpressionBuilder = fn (self: *Tree) ParseError!Expression; | ||||
| 
 | ||||
| const IdentifierList = coral.list.Stack([]const coral.io.Byte); | ||||
| 
 | ||||
| pub const ParseError = error { | ||||
| 	OutOfMemory, | ||||
| 	BadSyntax, | ||||
| @ -164,6 +166,11 @@ pub const Statement = union (enum) { | ||||
| 	block: List, | ||||
| 	expression: Expression, | ||||
| 
 | ||||
| 	pub const DeclarationStorage = enum { | ||||
| 		@"var", | ||||
| 		let, | ||||
| 	}; | ||||
| 
 | ||||
| 	const List = coral.list.Stack(Statement); | ||||
| }; | ||||
| 
 | ||||
| @ -221,8 +228,6 @@ pub const Tree = struct { | ||||
| 	fn parse_branch(self: *Tree) ParseError!Statement { | ||||
| 		const allocator = self.arena.as_allocator(); | ||||
| 
 | ||||
| 		defer self.tokenizer.skip_newlines(); | ||||
| 
 | ||||
| 		self.tokenizer.step(); | ||||
| 
 | ||||
| 		const condition_expression = try self.parse_expression(); | ||||
| @ -238,6 +243,8 @@ pub const Tree = struct { | ||||
| 		while (true) { | ||||
| 			switch (self.tokenizer.token) { | ||||
| 				.keyword_end => { | ||||
| 					self.tokenizer.skip_newlines(); | ||||
| 
 | ||||
| 					return .{ | ||||
| 						.@"if" = .{ | ||||
| 							.condition_expression = condition_expression, | ||||
| @ -262,6 +269,8 @@ pub const Tree = struct { | ||||
| 						try else_statements.push_one(try self.parse_statement()); | ||||
| 					} | ||||
| 
 | ||||
| 					self.tokenizer.skip_newlines(); | ||||
| 
 | ||||
| 					return .{ | ||||
| 						.@"if" = .{ | ||||
| 							.else_statement = try coral.io.allocate_one(allocator, Statement{.block = else_statements}), | ||||
| @ -404,6 +413,57 @@ pub const Tree = struct { | ||||
| 					break: parse .{.builtin = builtin}; | ||||
| 				}, | ||||
| 
 | ||||
| 				.keyword_lambda => { | ||||
| 					self.tokenizer.skip_newlines(); | ||||
| 
 | ||||
| 					if (self.tokenizer.token != .symbol_paren_left) { | ||||
| 						return self.report("expected `(` after opening lambda block"); | ||||
| 					} | ||||
| 
 | ||||
| 					var argument_identifiers = IdentifierList.make(allocator); | ||||
| 
 | ||||
| 					while (true) { | ||||
| 						self.tokenizer.skip_newlines(); | ||||
| 
 | ||||
| 						switch (self.tokenizer.token) { | ||||
| 							.identifier => |identifier| try argument_identifiers.push_one(identifier), | ||||
| 							.symbol_paren_right => break, | ||||
| 							else => return self.report("expected identifier or closing `)` in argument list"), | ||||
| 						} | ||||
| 
 | ||||
| 						self.tokenizer.skip_newlines(); | ||||
| 
 | ||||
| 						switch (self.tokenizer.token) { | ||||
| 							.symbol_comma => continue, | ||||
| 							.symbol_paren_right => break, | ||||
| 							else => return self.report("expected `,` or closing `)` after identifier in argument list"), | ||||
| 						} | ||||
| 					} | ||||
| 
 | ||||
| 					self.tokenizer.skip_newlines(); | ||||
| 
 | ||||
| 					if (self.tokenizer.token != .symbol_colon) { | ||||
| 						return self.report("expected `:` after closing `)` of lambda block argument list"); | ||||
| 					} | ||||
| 
 | ||||
| 					self.tokenizer.skip_newlines(); | ||||
| 
 | ||||
| 					var block_statements = Statement.List.make(allocator); | ||||
| 
 | ||||
| 					while (self.tokenizer.token != .keyword_end) { | ||||
| 						try block_statements.push_one(try self.parse_statement()); | ||||
| 					} | ||||
| 
 | ||||
| 					self.tokenizer.skip_newlines(); | ||||
| 
 | ||||
| 					break: parse .{ | ||||
| 						.lambda_literal = .{ | ||||
| 							.argument_identifiers = argument_identifiers, | ||||
| 							.block_statements = block_statements, | ||||
| 						}, | ||||
| 					}; | ||||
| 				}, | ||||
| 
 | ||||
| 				.symbol_brace_left => { | ||||
| 					var table_literal = Expression.TableLiteral.make(allocator); | ||||
| 
 | ||||
| @ -680,7 +740,7 @@ pub const Tree = struct { | ||||
| 				return .{ | ||||
| 					.declare = .{ | ||||
| 						.assigned_expression = try self.parse_expression(), | ||||
| 						.storage = .variant, | ||||
| 						.storage = .@"var", | ||||
| 						.identifier = identifier, | ||||
| 					}, | ||||
| 				}; | ||||
| @ -705,7 +765,7 @@ pub const Tree = struct { | ||||
| 				return .{ | ||||
| 					.declare = .{ | ||||
| 						.assigned_expression = try self.parse_expression(), | ||||
| 						.storage = .readonly, | ||||
| 						.storage = .let, | ||||
| 						.identifier = identifier, | ||||
| 					}, | ||||
| 				}; | ||||
|  | ||||
| @ -46,6 +46,7 @@ pub const Token = union(enum) { | ||||
| 	keyword_elif, | ||||
| 	keyword_var, | ||||
| 	keyword_let, | ||||
| 	keyword_lambda, | ||||
| 
 | ||||
| 	pub fn text(self: Token) []const coral.io.Byte { | ||||
| 		return switch (self) { | ||||
| @ -95,6 +96,7 @@ pub const Token = union(enum) { | ||||
| 			.keyword_else => "else", | ||||
| 			.keyword_var => "var", | ||||
| 			.keyword_let => "let", | ||||
| 			.keyword_lambda => "lambda", | ||||
| 		}; | ||||
| 	} | ||||
| }; | ||||
| @ -234,6 +236,12 @@ pub const Tokenizer = struct { | ||||
| 						}, | ||||
| 
 | ||||
| 						'l' => { | ||||
| 							if (coral.io.ends_with(identifier, "ambda")) { | ||||
| 								self.token = .keyword_lambda; | ||||
| 
 | ||||
| 								return; | ||||
| 							} | ||||
| 
 | ||||
| 							if (coral.io.ends_with(identifier, "et")) { | ||||
| 								self.token = .keyword_let; | ||||
| 
 | ||||
| @ -317,8 +325,6 @@ pub const Tokenizer = struct { | ||||
| 
 | ||||
| 					const begin = cursor; | ||||
| 
 | ||||
| 					cursor += 1; | ||||
| 
 | ||||
| 					while (cursor < self.source.len) switch (self.source[cursor]) { | ||||
| 						'"' => break, | ||||
| 						else => cursor += 1, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user