Functions
Source: Dev.to
Screenshots
What I built
Commits 3da669d, 930014e, 5adb148, and 9d534f3
What I understood
1️⃣ Updating the Grammar
- Precedence – Function calls now have the highest precedence, above the unary rule.
- Callable – Introduced a
Callableclass (actuallyLoxCallable) to determine whether an entity can be invoked.- Example: a string isn’t callable, while classes and functions (user‑defined or native) are.
- If the callee doesn’t implement
LoxCallable, the interpreter raises a runtime error.
- Currying / Chained Calls – The parser now allows constructs like
(( "(" arguments? ")" )*, enabling calls such asfunction(arg)(arg)(arg).

2️⃣ Arguments
- Arity checking – The interpreter verifies that the number of arguments supplied matches the number of parameters defined.

3️⃣ Native Functions
- Implemented a
clock()native function in Java. - It’s an anonymous class that implements
LoxCallableand is bound to the identifierclockin the interpreter’s global environment. - Returns the current system time in seconds, useful for benchmarking.
4️⃣ Function Declaration
- Encountering
funcreates a callable object (LoxCallable) and binds it to the function’s name in a new environment. - The function’s parameters are bound in that local environment.
- Each function call also creates a fresh environment, enabling recursion.
- After the function body finishes, the local environment is discarded and execution returns to the caller’s environment.
5️⃣ Return
- The interpreter looks for a
returnstatement optionally followed by an expression. - If no expression is provided,
nilis returned implicitly. - When
returnis executed, the interpreter must unwind the current call stack immediately. - This is achieved with a simple exception class
Returnthat carries the return value. - The call site wraps the function execution in a
try‑catchblock that catches this exception; if none is thrown, the function completes normally and returnsnil.
6️⃣ Scoping
- Previously Lox used dynamic scoping: a called function’s scope was always the global environment, not the environment where the function was defined.
- This prevented functions from accessing variables captured at definition time once the defining environment had ended.
- The fix is to capture the lexical environment (the environment that existed when the function was created) rather than deferring name resolution to the call site.
Example illustrating the problem (and the fix)
fun getName(name) {
var person = name;
fun greet() {
print "Hello, " + person + "!";
}
return greet;
}
var momo = getName("Momo");
var chutney = getName("Chutney");
momo(); // Should print "Hello, Momo!"
chutney(); // Should print "Hello, Chutney!"
- Before the fix this would raise an error because
personwas looked up in the global scope. - After implementing lexical scoping each returned
greetclosure correctly captures its ownpersonvariable, producing the expected output.
Closures, Scoping, and the Resolver
1. Example of a Closure
function getName(name) {
var person = "Hello, " + name + "!";
return function () {
console.log(person);
};
}
var momo = getName("Momo"); // Returns a function that closes over `person`
momo(); // → “Hello, Momo!”
function chutney() {
console.log("Hello, Chutney!");
}
chutney(); // → “Hello, Chutney!”
2. Why the First Example Fails
- When
getName("Momo")finishes, the variablepersonis destroyed because the function’s scope ends. - When
momo()is later called, it tries to findpersonin its parent scope, which is now the global scope (which doesn’t containperson). → Error!
3. Lexical (Static) Scoping
- We solve this problem by using lexical scoping.
- When a function is declared, it closes over (captures) the environment surrounding it.
- The inner function holds a live reference to the outer environment, preventing the garbage collector from destroying it even after the outer function returns.
- When the inner function is later called, the captured environment becomes its parent scope instead of the global scope.
4. Resolver
Implementing closures through lexical scoping introduced another issue:
- A closure holds a live reference to a mutable map representing the current scope.
- Its captured view of the scope can change based on later code in the same block, which should not happen.
Because Lox has lexical/static scope, a variable’s usage must always resolve to the same declaration throughout program execution, even if the variable is later redeclared or redefined.
Example
var a = "one";
{
fun showA() {
print a;
}
showA(); // → "one"
var a = "two";
showA(); // Should still print "one", but would print "two" without a resolver
}
abelongs to the global scope with the value"one".showAcreates a closure that captures the block/environment where it is declared.- When
showA()is first called,ais not found in the block, so the lookup proceeds to the global scope → prints"one". - After
var a = "two"redeclaresain the same block, the mutable scope causes the second call toshowA()to print"two"—incorrect behavior.
How the Resolver Fixes This
The resolver (added after the parser and before the interpreter) uses persistent data structures:
- The original data structure cannot be tampered with directly.
- Any change creates a new structure that contains the original information plus the modification.
This is analogous to a blockchain, where each new block references the previous block’s hash while containing the new change.
When the resolver processes a block and sees a variable being used (after its declaration), it performs a static pass:
Scope 0 (function's scope): a is not found
Scope 1 (block scope): a is not found (var a = "two" hasn't been defined yet)
Scope 2 (global scope): a is found
- Because the variable is found 2 hops away from the innermost scope, the resolver annotates that use with the number 2.
- At runtime, the interpreter uses this hop count to directly jump to the correct declaration, avoiding mutable‑scope surprises.
5. What’s Next?
We’re going to keep it Classy! 💅
(Classes and inheritance are up next.)
6. Musings
I’ve had the opportunity to witness some of the most wonderful and bewildering things these past few months. It feels like I’ve seen the full spectrum of life, much like Shakespeare’s seven stages of man. In a way, it really humbled me.
For a long time, I was afraid of letting go—of my beliefs, interests, and even some dreams—because I thought I’d lose my very identity. Now I’ve learned that, much like functions, it’s okay to have a template of non‑negotiables, upon which we can let life keep adding its own “implementations.” (I absolutely HAD to use that analogy—please excuse me.)
Change and unpredictability don’t seem so daunting anymore, and maybe (like 0.05 %) they’re good too. I suppose c’est la vie.

