Add support for basic lambda syntax
This commit is contained in:
		
							parent
							
								
									0e26b8acde
								
							
						
					
					
						commit
						f5ed4bbcad
					
				| @ -1,18 +1,22 @@ | |||||||
| 
 | 
 | ||||||
| var i = 0 | var i = 0 | ||||||
| 
 | 
 | ||||||
|  | let pr = lambda (): | ||||||
|  | 	@print("foo") | ||||||
|  | end | ||||||
|  | 
 | ||||||
| while i < 5: | while i < 5: | ||||||
| 	@print("hello, world") | 	pr("hello, world") | ||||||
| 
 | 
 | ||||||
| 	i = i + 1 | 	i = i + 1 | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| if i > 6: | if i > 6: | ||||||
| 	@print("`i` greater than `6`") | 	pr("`i` greater than `6`") | ||||||
| elif i == 4: | elif i == 4: | ||||||
| 	@print("`i` is equal to `4`") | 	pr("`i` is equal to `4`") | ||||||
| else: | else: | ||||||
| 	@print("i'unno") | 	pr("i'unno") | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| return { | return { | ||||||
|  | |||||||
| @ -33,6 +33,7 @@ pub const RuntimeEnv = struct { | |||||||
| 			float: Float, | 			float: Float, | ||||||
| 			string: []const coral.io.Byte, | 			string: []const coral.io.Byte, | ||||||
| 			symbol: []const coral.io.Byte, | 			symbol: []const coral.io.Byte, | ||||||
|  | 			chunk: Chunk, | ||||||
| 		}; | 		}; | ||||||
| 
 | 
 | ||||||
| 		const Opcode = union (enum) { | 		const Opcode = union (enum) { | ||||||
| @ -124,17 +125,35 @@ pub const RuntimeEnv = struct { | |||||||
| 						}); | 						}); | ||||||
| 					}, | 					}, | ||||||
| 
 | 
 | ||||||
| 					.table_literal => |fields| { | 					.table_literal => |literal| { | ||||||
| 						if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) { | 						if (literal.values.len > coral.math.max_int(@typeInfo(u32).Int)) { | ||||||
| 							return error.OutOfMemory; | 							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.value_expression); | ||||||
| 							try self.compile_expression(chunk, field.key_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(u32).Int)) { | ||||||
|  | 							return error.OutOfMemory; | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						var lambda_chunk = try Chunk.make(chunk.env, "<lambda>"); | ||||||
|  | 
 | ||||||
|  | 						errdefer lambda_chunk.free(); | ||||||
|  | 
 | ||||||
|  | 						try lambda_chunk.compile(literal.block_statements.values); | ||||||
|  | 
 | ||||||
|  | 						try chunk.opcodes.push_one(.{ | ||||||
|  | 							.push_const = try chunk.declare_constant(.{ | ||||||
|  | 								.chunk = lambda_chunk, | ||||||
|  | 							}), | ||||||
|  | 						}); | ||||||
| 					}, | 					}, | ||||||
| 
 | 
 | ||||||
| 					.binary_operation => |operation| { | 					.binary_operation => |operation| { | ||||||
| @ -374,9 +393,18 @@ pub const RuntimeEnv = struct { | |||||||
| 
 | 
 | ||||||
| 		fn compile(self: *Chunk, statements: []const ast.Statement) RuntimeError!void { | 		fn compile(self: *Chunk, statements: []const ast.Statement) RuntimeError!void { | ||||||
| 			var unit = CompilationUnit{}; | 			var unit = CompilationUnit{}; | ||||||
|  | 			var has_returned = false; | ||||||
| 
 | 
 | ||||||
| 			for (statements) |statement| { | 			for (statements) |statement| { | ||||||
| 				try unit.compile_statement(self, 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 +413,27 @@ pub const RuntimeEnv = struct { | |||||||
| 				return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); | 				return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			const constant_index = self.constants.values.len; | 			const constant_object = try switch (constant) { | ||||||
| 
 |  | ||||||
| 			try self.constants.push_one(try switch (constant) { |  | ||||||
| 				.fixed => |fixed| self.env.new_fixed(fixed), | 				.fixed => |fixed| self.env.new_fixed(fixed), | ||||||
| 				.float => |float| self.env.new_float(float), | 				.float => |float| self.env.new_float(float), | ||||||
| 				.string => |string| self.env.new_string(string), | 				.string => |string| self.env.new_string(string), | ||||||
| 				.symbol => |symbol| self.env.new_symbol(symbol), | 				.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 { | 		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); |  | ||||||
| 
 |  | ||||||
| 			var opcode_cursor = @as(u32, 0); | 			var opcode_cursor = @as(u32, 0); | ||||||
| 
 | 
 | ||||||
| 			while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) { | 			while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) { | ||||||
| @ -547,14 +575,16 @@ pub const RuntimeEnv = struct { | |||||||
| 							defer self.env.discard(callable); | 							defer self.env.discard(callable); | ||||||
| 
 | 
 | ||||||
| 							try self.env.frames.push_one(.{ | 							try self.env.frames.push_one(.{ | ||||||
| 								.name = "<lambda>", | 								.name = "<chunk>", | ||||||
| 								.arg_count = object_call, | 								.arg_count = object_call, | ||||||
| 								.locals_top = self.env.locals.values.len, | 								.locals_top = self.env.locals.values.len, | ||||||
| 							}); | 							}); | ||||||
| 
 | 
 | ||||||
| 							defer coral.debug.assert(self.env.frames.pop() != null); | 							defer coral.debug.assert(self.env.frames.pop() != null); | ||||||
| 
 | 
 | ||||||
| 							break: call try switch (callable.object().payload) { | 							const payload = callable.object().payload; | ||||||
|  | 
 | ||||||
|  | 							break: call try switch (payload) { | ||||||
| 								.syscall => |syscall| syscall(self.env), | 								.syscall => |syscall| syscall(self.env), | ||||||
| 
 | 
 | ||||||
| 								.dynamic => |dynamic| dynamic.typeinfo().call(.{ | 								.dynamic => |dynamic| dynamic.typeinfo().call(.{ | ||||||
| @ -566,18 +596,18 @@ pub const RuntimeEnv = struct { | |||||||
| 							}; | 							}; | ||||||
| 						}; | 						}; | ||||||
| 
 | 
 | ||||||
| 						for (0 .. object_call) |_| { |  | ||||||
| 							if (try self.pop_local()) |popped_arg| { |  | ||||||
| 								self.env.discard(popped_arg); |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 
 |  | ||||||
| 						errdefer { | 						errdefer { | ||||||
| 							if (result) |ref| { | 							if (result) |ref| { | ||||||
| 								self.env.discard(ref); | 								self.env.discard(ref); | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 
 | 
 | ||||||
|  | 						for (0 .. object_call) |_| { | ||||||
|  | 							if (try self.pop_local()) |popped_arg| { | ||||||
|  | 								self.env.discard(popped_arg); | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
| 						try self.env.locals.push_one(result); | 						try self.env.locals.push_one(result); | ||||||
| 					}, | 					}, | ||||||
| 
 | 
 | ||||||
| @ -934,6 +964,14 @@ pub const RuntimeEnv = struct { | |||||||
| 		fn pop_local(self: *Chunk) RuntimeError!?*RuntimeRef { | 		fn pop_local(self: *Chunk) RuntimeError!?*RuntimeRef { | ||||||
| 			return self.env.locals.pop() orelse self.env.raise(error.IllegalState, "stack underflow"); | 			return self.env.locals.pop() orelse self.env.raise(error.IllegalState, "stack underflow"); | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
|  | 		fn typeinfo_call(method: Typeinfo.Method) RuntimeError!?*RuntimeRef { | ||||||
|  | 			return @as(*Chunk, @ptrCast(@alignCast(method.userdata))).execute(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fn typeinfo_destruct(method: Typeinfo.Method) void { | ||||||
|  | 			@as(*Chunk, @ptrCast(@alignCast(method.userdata))).free(); | ||||||
|  | 		} | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	const ConstList = coral.list.Stack(*RuntimeRef); | 	const ConstList = coral.list.Stack(*RuntimeRef); | ||||||
| @ -967,7 +1005,7 @@ pub const RuntimeEnv = struct { | |||||||
| 		contiguous: RefList, | 		contiguous: RefList, | ||||||
| 
 | 
 | ||||||
| 		fn typeinfo_destruct(method: Typeinfo.Method) void { | 		fn typeinfo_destruct(method: Typeinfo.Method) void { | ||||||
| 			const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); | 			const table = @as(*Table, @ptrCast(@alignCast(method.userdata))); | ||||||
| 
 | 
 | ||||||
| 			{ | 			{ | ||||||
| 				var field_iterable = table.associative.as_iterable(); | 				var field_iterable = table.associative.as_iterable(); | ||||||
| @ -990,7 +1028,7 @@ pub const RuntimeEnv = struct { | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		fn typeinfo_get(method: Typeinfo.Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { | 		fn typeinfo_get(method: Typeinfo.Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { | ||||||
| 			const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); | 			const table = @as(*Table, @ptrCast(@alignCast(method.userdata))); | ||||||
| 			const acquired_index = try method.env.acquire(index); | 			const acquired_index = try method.env.acquire(index); | ||||||
| 
 | 
 | ||||||
| 			defer method.env.discard(acquired_index); | 			defer method.env.discard(acquired_index); | ||||||
| @ -1014,7 +1052,7 @@ pub const RuntimeEnv = struct { | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		fn typeinfo_set(method: Typeinfo.Method, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { | 		fn typeinfo_set(method: Typeinfo.Method, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { | ||||||
| 			const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); | 			const table = @as(*Table, @ptrCast(@alignCast(method.userdata))); | ||||||
| 			const acquired_index = try method.env.acquire(index); | 			const acquired_index = try method.env.acquire(index); | ||||||
| 
 | 
 | ||||||
| 			errdefer method.env.discard(acquired_index); | 			errdefer method.env.discard(acquired_index); | ||||||
| @ -1079,19 +1117,14 @@ pub const RuntimeEnv = struct { | |||||||
| 		return null; | 		return null; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pub fn bind_system(self: *RuntimeEnv, name: []const coral.io.Byte, value: *const RuntimeRef) RuntimeError!void { | 	pub fn acquire_args(self: *RuntimeEnv) RuntimeError!*RuntimeRef { | ||||||
| 		const name_symbol = try self.new_symbol(name); | 		const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow"); | ||||||
| 
 | 
 | ||||||
| 		errdefer self.discard(name_symbol); | 		_ = frame; | ||||||
| 
 | 
 | ||||||
| 		const acquired_value = try self.acquire(value); | 		const args_table = self.new_table(); | ||||||
| 
 | 
 | ||||||
| 		errdefer self.discard(acquired_value); | 		return args_table; | ||||||
| 
 |  | ||||||
| 		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 { | 	pub fn call(self: *RuntimeEnv, callable: *RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef { | ||||||
| @ -1172,6 +1205,14 @@ pub const RuntimeEnv = struct { | |||||||
| 			error.OutOfMemory => error.OutOfMemory, | 			error.OutOfMemory => error.OutOfMemory, | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
|  | 		try self.frames.push_one(.{ | ||||||
|  | 			.name = file_name, | ||||||
|  | 			.arg_count = 0, | ||||||
|  | 			.locals_top = self.locals.values.len, | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		defer coral.debug.assert(self.frames.pop() != null); | ||||||
|  | 
 | ||||||
| 		return chunk.execute(); | 		return chunk.execute(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -74,6 +74,11 @@ pub const Expression = union (enum) { | |||||||
| 	table_literal: TableLiteral, | 	table_literal: TableLiteral, | ||||||
| 	grouped_expression: *Expression, | 	grouped_expression: *Expression, | ||||||
| 
 | 
 | ||||||
|  | 	lambda_literal: struct { | ||||||
|  | 		argument_identifiers: IdentifierList, | ||||||
|  | 		block_statements: Statement.List, | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
| 	local_get: struct { | 	local_get: struct { | ||||||
| 		identifier: []const coral.io.Byte, | 		identifier: []const coral.io.Byte, | ||||||
| 	}, | 	}, | ||||||
| @ -136,6 +141,8 @@ pub const DeclarationStorage = enum { | |||||||
| 
 | 
 | ||||||
| const ExpressionBuilder = fn (self: *Tree) ParseError!Expression; | const ExpressionBuilder = fn (self: *Tree) ParseError!Expression; | ||||||
| 
 | 
 | ||||||
|  | const IdentifierList = coral.list.Stack([]const coral.io.Byte); | ||||||
|  | 
 | ||||||
| pub const ParseError = error { | pub const ParseError = error { | ||||||
| 	OutOfMemory, | 	OutOfMemory, | ||||||
| 	BadSyntax, | 	BadSyntax, | ||||||
| @ -221,8 +228,6 @@ pub const Tree = struct { | |||||||
| 	fn parse_branch(self: *Tree) ParseError!Statement { | 	fn parse_branch(self: *Tree) ParseError!Statement { | ||||||
| 		const allocator = self.arena.as_allocator(); | 		const allocator = self.arena.as_allocator(); | ||||||
| 
 | 
 | ||||||
| 		defer self.tokenizer.skip_newlines(); |  | ||||||
| 
 |  | ||||||
| 		self.tokenizer.step(); | 		self.tokenizer.step(); | ||||||
| 
 | 
 | ||||||
| 		const condition_expression = try self.parse_expression(); | 		const condition_expression = try self.parse_expression(); | ||||||
| @ -238,6 +243,8 @@ pub const Tree = struct { | |||||||
| 		while (true) { | 		while (true) { | ||||||
| 			switch (self.tokenizer.token) { | 			switch (self.tokenizer.token) { | ||||||
| 				.keyword_end => { | 				.keyword_end => { | ||||||
|  | 					self.tokenizer.skip_newlines(); | ||||||
|  | 
 | ||||||
| 					return .{ | 					return .{ | ||||||
| 						.@"if" = .{ | 						.@"if" = .{ | ||||||
| 							.condition_expression = condition_expression, | 							.condition_expression = condition_expression, | ||||||
| @ -262,6 +269,8 @@ pub const Tree = struct { | |||||||
| 						try else_statements.push_one(try self.parse_statement()); | 						try else_statements.push_one(try self.parse_statement()); | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
|  | 					self.tokenizer.skip_newlines(); | ||||||
|  | 
 | ||||||
| 					return .{ | 					return .{ | ||||||
| 						.@"if" = .{ | 						.@"if" = .{ | ||||||
| 							.else_statement = try coral.io.allocate_one(allocator, Statement{.block = else_statements}), | 							.else_statement = try coral.io.allocate_one(allocator, Statement{.block = else_statements}), | ||||||
| @ -404,6 +413,57 @@ pub const Tree = struct { | |||||||
| 					break: parse .{.builtin = builtin}; | 					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 => { | 				.symbol_brace_left => { | ||||||
| 					var table_literal = Expression.TableLiteral.make(allocator); | 					var table_literal = Expression.TableLiteral.make(allocator); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -46,6 +46,7 @@ pub const Token = union(enum) { | |||||||
| 	keyword_elif, | 	keyword_elif, | ||||||
| 	keyword_var, | 	keyword_var, | ||||||
| 	keyword_let, | 	keyword_let, | ||||||
|  | 	keyword_lambda, | ||||||
| 
 | 
 | ||||||
| 	pub fn text(self: Token) []const coral.io.Byte { | 	pub fn text(self: Token) []const coral.io.Byte { | ||||||
| 		return switch (self) { | 		return switch (self) { | ||||||
| @ -95,6 +96,7 @@ pub const Token = union(enum) { | |||||||
| 			.keyword_else => "else", | 			.keyword_else => "else", | ||||||
| 			.keyword_var => "var", | 			.keyword_var => "var", | ||||||
| 			.keyword_let => "let", | 			.keyword_let => "let", | ||||||
|  | 			.keyword_lambda => "lambda", | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
| @ -234,6 +236,12 @@ pub const Tokenizer = struct { | |||||||
| 						}, | 						}, | ||||||
| 
 | 
 | ||||||
| 						'l' => { | 						'l' => { | ||||||
|  | 							if (coral.io.ends_with(identifier, "ambda")) { | ||||||
|  | 								self.token = .keyword_lambda; | ||||||
|  | 
 | ||||||
|  | 								return; | ||||||
|  | 							} | ||||||
|  | 
 | ||||||
| 							if (coral.io.ends_with(identifier, "et")) { | 							if (coral.io.ends_with(identifier, "et")) { | ||||||
| 								self.token = .keyword_let; | 								self.token = .keyword_let; | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user