Step 1 of 3 · Reading · ~3 min
Object-Oriented Programming
Closures & Classes
Classes & Instances
Classes reuse almost everything you've already built — callables, environments, property-style lookup — combined into two new runtime concepts: a LoxClass (the blueprint) and a LoxInstance (an object with its own mutable state).
Parsing a class declaration
class Dog {
init(name) { this.name = name; }
bark() { print this.name + " barks!"; }
}
A class body is just a sequence of method declarations — reuse your existing function-declaration parsing logic for each one (no fun keyword needed inside a class body, but the parameter list + block body shape is identical). Store the class as a statement node: a name token and a list of method AST nodes (plus, next lesson, an optional superclass reference).
LoxClass is itself callable
Instantiating with Dog("Rex") looks exactly like a function call syntactically, so the cleanest implementation makes LoxClass implement the same call/arity interface as LoxFunction:
Calling a class creates a fresh LoxInstance, runs init on it if one is defined (passing along the constructor arguments), and returns the instance itself — not whatever init's body returns, and even if init has no explicit return. This is a special case worth calling out: unlike ordinary functions, calling init always evaluates to the new object, by design.
LoxInstance and property access
An instance is just a class reference plus a mutable field table:
Note the precedence: fields are checked before methods. instance.name first looks in the instance's own field dictionary; only if it's absent does it fall back to methods on the class. This is what lets you shadow a method name with an instance field if you really want to (and matches how the reference implementation behaves).
Binding this
A method looked up via .bark() needs this inside its body to refer to the specific instance it was called on — but a LoxFunction for a method is shared by every instance of the class. The trick is bind: wrap the method's existing closure in one more environment that defines this, and return a new LoxFunction using that as its closure:
Every time you access instance.bark, you get a new bound method whose closure has this pinned to instance — this is the same closure mechanism from the previous lesson, just capturing an implicit variable instead of a user-written one.
Edge cases to watch
- Setting a field on a non-instance (e.g.
5.x = 1) is a runtime error, not silently ignored. thisused outside any method should be caught statically — extend your resolver so it treatsthislike a special local variable scoped only within method bodies.initcalled directly asinstance.init()re-runs the constructor logic on the same object; that's expected, but returning a different instance always returns the original one for consistency, sinceinit's return value is discarded byLoxClass.call.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…