Skip to content

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:

python

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:

python

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:

python

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.
  • this used outside any method should be caught statically — extend your resolver so it treats this like a special local variable scoped only within method bodies.
  • init called directly as instance.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, since init's return value is discarded by LoxClass.call.
Up nextInheritance & Method ResolutionClosures & Classes

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…