Compare commits
	
		
			2 Commits
		
	
	
		
			c8cd24afbf
			...
			b522f05c8c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b522f05c8c | |||
| 3c1c37799c | 
							
								
								
									
										9
									
								
								samples/ch28_calling_methods.lox
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								samples/ch28_calling_methods.lox
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| class Scone { | ||||
|     topping(first, second) { | ||||
|         return "scone with " + first + " and " + second; | ||||
|     } | ||||
| } | ||||
|  | ||||
| var scone = Scone(); | ||||
| var result = scone.topping("berries", "cream"); | ||||
| print result; | ||||
							
								
								
									
										8
									
								
								samples/ch28_methods_initializers.lox
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								samples/ch28_methods_initializers.lox
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| class Brunch { | ||||
|     bacon() {} | ||||
|     eggs() {} | ||||
| } | ||||
|  | ||||
| var brunch = Brunch(); | ||||
| print brunch; | ||||
| brunch.bacon(); | ||||
							
								
								
									
										11
									
								
								samples/ch28_say_name.lox
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								samples/ch28_say_name.lox
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| class Person { | ||||
|     sayName() { | ||||
|         print this.name; | ||||
|     } | ||||
| } | ||||
|  | ||||
| var jane = Person(); | ||||
| jane.name = "Jane"; | ||||
|  | ||||
| var method = jane.sayName; | ||||
| method(); | ||||
							
								
								
									
										5
									
								
								samples/ch28_this_outside.lox
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								samples/ch28_this_outside.lox
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| print this; | ||||
|  | ||||
| fun invalid() { | ||||
|     print this; | ||||
| } | ||||
| @@ -151,6 +151,7 @@ pub const Chunk = struct { | ||||
|             @intFromEnum(OpCode.OP_CLASS) => return utils.constant_instruction("OP_CLASS", self, offset), | ||||
|             @intFromEnum(OpCode.OP_GET_PROPERTY) => return utils.constant_instruction("OP_GET_PROPERTY", self, offset), | ||||
|             @intFromEnum(OpCode.OP_SET_PROPERTY) => return utils.constant_instruction("OP_SET_PROPERTY", self, offset), | ||||
|             @intFromEnum(OpCode.OP_METHOD) => return utils.constant_instruction("OP_METHOD", self, offset), | ||||
|             else => { | ||||
|                 debug.print("unknown opcode {d}\n", .{instruction}); | ||||
|                 return offset + 1; | ||||
|   | ||||
| @@ -35,6 +35,10 @@ const Precedence = enum { | ||||
|  | ||||
| const ParserFn = *const fn (*Parser, bool) ParsingError!void; | ||||
|  | ||||
| const ClassCompiler = struct { | ||||
|     enclosing: ?*ClassCompiler, | ||||
| }; | ||||
|  | ||||
| const ParserRule = struct { | ||||
|     prefix: ?ParserFn, | ||||
|     infix: ?ParserFn, | ||||
| @@ -49,6 +53,7 @@ pub const Parser = struct { | ||||
|     had_error: bool, | ||||
|     panic_mode: bool, | ||||
|     vm: *VM, | ||||
|     class_compiler: ?*ClassCompiler, | ||||
|  | ||||
|     fn new(vm: *VM, compiler: *Compiler, scanner: *Scanner) Parser { | ||||
|         return Parser{ | ||||
| @@ -59,6 +64,7 @@ pub const Parser = struct { | ||||
|             .had_error = false, | ||||
|             .panic_mode = false, | ||||
|             .vm = vm, | ||||
|             .class_compiler = null, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| @@ -66,6 +72,10 @@ pub const Parser = struct { | ||||
|         return self.compiler.function.chunk; | ||||
|     } | ||||
|  | ||||
|     inline fn current_class_compiler(self: *Parser) ?*ClassCompiler { | ||||
|         return self.class_compiler; | ||||
|     } | ||||
|  | ||||
|     fn advance(self: *Parser) void { | ||||
|         self.previous = self.current; | ||||
|  | ||||
| @@ -278,7 +288,7 @@ pub const Parser = struct { | ||||
|             TokenType.PRINT => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, | ||||
|             TokenType.RETURN => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, | ||||
|             TokenType.SUPER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, | ||||
|             TokenType.THIS => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, | ||||
|             TokenType.THIS => ParserRule{ .prefix = this_, .infix = null, .precedence = Precedence.None }, | ||||
|             TokenType.TRUE => ParserRule{ .prefix = literal, .infix = null, .precedence = Precedence.None }, | ||||
|             TokenType.VAR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, | ||||
|             TokenType.WHILE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, | ||||
| @@ -863,14 +873,29 @@ pub const Parser = struct { | ||||
|  | ||||
|     fn class_declaration(self: *Parser) ParsingError!void { | ||||
|         self.consume(TokenType.IDENTIFIER, "Expect class name."); | ||||
|         const class_name = self.previous.?; | ||||
|  | ||||
|         const name_constant = try self.identifier_constant(self.previous.?); | ||||
|         self.declare_variable(); | ||||
|  | ||||
|         try self.emit_bytes(@intFromEnum(OpCode.OP_CLASS), name_constant); | ||||
|         try self.define_variable(name_constant); | ||||
|  | ||||
|         var class_compiler = ClassCompiler{ | ||||
|             .enclosing = self.current_class_compiler(), | ||||
|         }; | ||||
|         self.class_compiler = &class_compiler; | ||||
|  | ||||
|         try self.named_variable(class_name, false); | ||||
|  | ||||
|         self.consume(TokenType.LEFT_BRACE, "Expect '{' before class body."); | ||||
|         while (!self.check(TokenType.RIGHT_BRACE) and !self.check(TokenType.EOF)) { | ||||
|             try self.method(); | ||||
|         } | ||||
|         self.consume(TokenType.RIGHT_BRACE, "Expect '}' after class body."); | ||||
|         try self.emit_byte(@intFromEnum(OpCode.OP_POP)); | ||||
|  | ||||
|         self.class_compiler = self.current_class_compiler().?.enclosing; | ||||
|     } | ||||
|  | ||||
|     fn dot(self: *Parser, can_assign: bool) ParsingError!void { | ||||
| @@ -884,11 +909,30 @@ pub const Parser = struct { | ||||
|             try self.emit_bytes(@intFromEnum(OpCode.OP_GET_PROPERTY), name); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn method(self: *Parser) ParsingError!void { | ||||
|         self.consume(TokenType.IDENTIFIER, "Expect method name."); | ||||
|         const constant = try self.identifier_constant(self.previous.?); | ||||
|  | ||||
|         try self.function(FunctionType.Method); | ||||
|         try self.emit_bytes(@intFromEnum(OpCode.OP_METHOD), constant); | ||||
|     } | ||||
|  | ||||
|     fn this_(self: *Parser, can_assign: bool) ParsingError!void { | ||||
|         if (self.current_class_compiler() == null) { | ||||
|             self.error_msg("Can't use 'this' outside of a class."); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         _ = can_assign; | ||||
|         try self.variable(false); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const FunctionType = enum { | ||||
|     Function, | ||||
|     Script, | ||||
|     Method, | ||||
| }; | ||||
|  | ||||
| pub const Compiler = struct { | ||||
| @@ -915,6 +959,8 @@ pub const Compiler = struct { | ||||
|             .enclosing = enclosing, | ||||
|         }; | ||||
|  | ||||
|         compiler.local_count += 1; | ||||
|  | ||||
|         compiler.locals[0].depth = 0; | ||||
|         compiler.locals[0].name = Token{ | ||||
|             .token_type = TokenType.EOF, | ||||
| @@ -924,7 +970,10 @@ pub const Compiler = struct { | ||||
|         }; | ||||
|         compiler.locals[0].is_captured = false; | ||||
|  | ||||
|         compiler.local_count += 1; | ||||
|         if (function_type != FunctionType.Function) { | ||||
|             compiler.locals[0].name.start = "this"; | ||||
|             compiler.locals[0].name.length = 4; | ||||
|         } | ||||
|  | ||||
|         return compiler; | ||||
|     } | ||||
|   | ||||
| @@ -237,12 +237,18 @@ pub const ZloxAllocator = struct { | ||||
|             ObjType.Class => { | ||||
|                 const class: *Obj.Class = obj.as_class(); | ||||
|                 self.mark_object(&class.name.obj); | ||||
|                 self.mark_table(&class.methods); | ||||
|             }, | ||||
|             ObjType.Instance => { | ||||
|                 const instance: *Obj.Instance = obj.as_instance(); | ||||
|                 self.mark_object(&instance.class.obj); | ||||
|                 self.mark_table(&instance.fields); | ||||
|             }, | ||||
|             ObjType.BoundMethod => { | ||||
|                 const bound_method: *Obj.BoundMethod = obj.as_bound_method(); | ||||
|                 self.mark_value(&bound_method.receiver); | ||||
|                 self.mark_object(&bound_method.method.obj); | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -17,6 +17,7 @@ pub const ObjType = enum { | ||||
|     Upvalue, | ||||
|     Class, | ||||
|     Instance, | ||||
|     BoundMethod, | ||||
| }; | ||||
|  | ||||
| pub const NativeFn = *const fn (vm: *VM, arg_count: usize, args: []Value) Value; | ||||
| @@ -51,6 +52,7 @@ pub const Obj = struct { | ||||
|             ObjType.Upvalue => self.as_upvalue().destroy(), | ||||
|             ObjType.Class => self.as_class().destroy(), | ||||
|             ObjType.Instance => self.as_instance().destroy(), | ||||
|             ObjType.BoundMethod => self.as_bound_method().destroy(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -166,16 +168,19 @@ pub const Obj = struct { | ||||
|     pub const Class = struct { | ||||
|         obj: Obj, | ||||
|         name: *Obj.String, | ||||
|         methods: Table, | ||||
|  | ||||
|         pub fn new(vm: *VM, name: *Obj.String) *Class { | ||||
|             const class_obj = Obj.new(Class, vm, ObjType.Class); | ||||
|  | ||||
|             class_obj.name = name; | ||||
|             class_obj.methods = Table.new(vm.allocator); | ||||
|  | ||||
|             return class_obj; | ||||
|         } | ||||
|  | ||||
|         pub fn destroy(self: *Class) void { | ||||
|             self.methods.destroy(); | ||||
|             self.obj.allocator.destroy(self); | ||||
|         } | ||||
|     }; | ||||
| @@ -200,6 +205,25 @@ pub const Obj = struct { | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     pub const BoundMethod = struct { | ||||
|         obj: Obj, | ||||
|         receiver: Value, | ||||
|         method: *Obj.Closure, | ||||
|  | ||||
|         pub fn new(vm: *VM, receiver: Value, method: *Obj.Closure) *BoundMethod { | ||||
|             const bound_method = Obj.new(BoundMethod, vm, ObjType.BoundMethod); | ||||
|  | ||||
|             bound_method.receiver = receiver; | ||||
|             bound_method.method = method; | ||||
|  | ||||
|             return bound_method; | ||||
|         } | ||||
|  | ||||
|         pub fn destroy(self: *BoundMethod) void { | ||||
|             self.obj.allocator.destroy(self); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     pub fn is_type(self: *Obj, kind: ObjType) bool { | ||||
|         return self.kind == kind; | ||||
|     } | ||||
| @@ -232,6 +256,10 @@ pub const Obj = struct { | ||||
|         return self.is_type(ObjType.Instance); | ||||
|     } | ||||
|  | ||||
|     pub fn is_bound_method(self: *Obj) bool { | ||||
|         return self.is_type(ObjType.BoundMethod); | ||||
|     } | ||||
|  | ||||
|     pub fn print(self: *Obj) void { | ||||
|         switch (self.kind) { | ||||
|             ObjType.String => { | ||||
| @@ -264,6 +292,10 @@ pub const Obj = struct { | ||||
|                 const obj = self.as_instance(); | ||||
|                 debug.print("{s} instance", .{obj.class.name.chars}); | ||||
|             }, | ||||
|             ObjType.BoundMethod => { | ||||
|                 const obj = self.as_bound_method(); | ||||
|                 obj.method.function.obj.print(); | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -301,4 +333,9 @@ pub const Obj = struct { | ||||
|         std.debug.assert(self.kind == ObjType.Instance); | ||||
|         return @fieldParentPtr("obj", self); | ||||
|     } | ||||
|  | ||||
|     pub fn as_bound_method(self: *Obj) *BoundMethod { | ||||
|         std.debug.assert(self.kind == ObjType.BoundMethod); | ||||
|         return @fieldParentPtr("obj", self); | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -31,4 +31,5 @@ pub const OpCode = enum(u8) { | ||||
|     OP_CLOSE_UPVALUE, | ||||
|     OP_RETURN, | ||||
|     OP_CLASS, | ||||
|     OP_METHOD, | ||||
| }; | ||||
|   | ||||
							
								
								
									
										36
									
								
								src/vm.zig
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								src/vm.zig
									
									
									
									
									
								
							| @@ -311,8 +311,9 @@ pub const VM = struct { | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     self.runtime_error("Undefined property"); // XXX to complete with name.chars | ||||
|                     return InterpretResult.RUNTIME_ERROR; | ||||
|                     if (!self.bind_method(instance.class, name)) { | ||||
|                         return InterpretResult.RUNTIME_ERROR; | ||||
|                     } | ||||
|                 }, | ||||
|                 @intFromEnum(OpCode.OP_SET_PROPERTY) => { | ||||
|                     if (!self.peek(1).is_obj() or !self.peek(1).as_obj().is_instance()) { | ||||
| @@ -326,6 +327,9 @@ pub const VM = struct { | ||||
|                     _ = self.pop(); | ||||
|                     _ = try self.push(value); | ||||
|                 }, | ||||
|                 @intFromEnum(OpCode.OP_METHOD) => { | ||||
|                     self.define_method(self.read_constant().as_string()); | ||||
|                 }, | ||||
|                 else => { | ||||
|                     debug.print("Invalid instruction: {d}\n", .{instruction}); | ||||
|                     return InterpretResult.RUNTIME_ERROR; | ||||
| @@ -498,6 +502,11 @@ pub const VM = struct { | ||||
|  | ||||
|                     return true; | ||||
|                 }, | ||||
|                 ObjType.BoundMethod => { | ||||
|                     const bound_method = callee.as_obj().as_bound_method(); | ||||
|                     self.stack[self.stack_top - arg_count - 1] = bound_method.receiver; | ||||
|                     return self.call(bound_method.method, arg_count); | ||||
|                 }, | ||||
|                 else => {}, | ||||
|             } | ||||
|         } | ||||
| @@ -505,6 +514,21 @@ pub const VM = struct { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     pub fn bind_method(self: *VM, class: *Obj.Class, name: *Obj.String) bool { | ||||
|         var method: Value = Value.nil_val(); | ||||
|  | ||||
|         if (!class.methods.get(name, &method)) { | ||||
|             self.runtime_error("Undefined property."); // XXX add name.chars as '%s'. | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         const bound: *Obj.BoundMethod = Obj.BoundMethod.new(self, self.peek(0), method.as_obj().as_closure()); | ||||
|         _ = self.pop(); | ||||
|         try self.push(Value.obj_val(&bound.obj)); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     pub fn call(self: *VM, closure: *Obj.Closure, arg_count: usize) bool { | ||||
|         if (arg_count != closure.function.arity) { | ||||
|             self.runtime_error("Invalid argument count."); | ||||
| @@ -571,4 +595,12 @@ pub const VM = struct { | ||||
|             self.open_upvalues = upvalue.next; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn define_method(self: *VM, name: *Obj.String) void { | ||||
|         const method = self.peek(0); | ||||
|         const class = self.peek(1).as_obj().as_class(); | ||||
|  | ||||
|         _ = class.methods.set(name, method); | ||||
|         _ = self.pop(); | ||||
|     } | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user