Skip to content

veera-sivarajan/lang0

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

lang0

A tree walk interpreter written in modern C++ following Bob Nystrom’s Crafting Interpreters. In this project, my goal was to learn and understand how the scanner, parser, interpreter and environment all work together to form a programming language. In the end, I have gained a better understanding of all the concepts involved in the making of an interpreter and also better ways to reason about programs.

Checkout the blog post which explains some of the features I have added to the interpreter. I also made a video to demo the features listed below.

Additional Features

I have also added some features that would make the language more usable.

Lists

Implementing Lists in lang0 was the most exciting part of the project. Internally, Lists are stored as std::vector<std::any> and uses its methods to add and remove elements from the list. To iterate through all the elements in a list and terminate at the end, the interpreter returns a nil when indexing out of bound.

var list = [1, 2, 3, 4, 5];
print list[0]; // 1

var result = [];
for (var i = 0; list[i] != nil; i = i + 1) {
    result[i] = list[i] * 10;
}
print result; // [10, 20, 30, 40, 50]

Anonymous Functions

It felt odd to not have lambdas when functions are first class citizens. To evaluate lambda expressions, the interpreter casts the parsed function into runtime’s representation of a function (class LambdaFunction in /include/DloxFunction.hpp) and returns the value. Here, unlike executing function declarations, it is not defined in the current environment.

var list = [1, 2, 3, 4, 5];
print map(lambda(num) { return num * num; }, list); // [1, 4, 9, 16, 25]

Unused Variable Warnings

The interpreter throws warnings when variables in a local scope are not used. While the Resolver scans through the code, it pushes and pops a map when entering and exiting a local scope. When a new variable is declared in the local scope, it gets added to the top most map with zero as value and whenver it gets resolved in the scope, the value is incremented by one. So by the end of the scope, calling checkUnusedVariables() will loop through all the pairs in the map and throw a warning if any value is zero.

fun badBoy(a, b) {
    var c = 10;
    var d = 20;
    return c * d;
}
print badBoy(1, 2);
// Output
// [line 1] Warning at 'b': Unused local variable.
// [line 1] Warning at 'a': Unused local variable.

Acknowledgement

I would not have finished this project without the inspiration I got from the below mentioned links.

About

lox tree-walk interpreter with lists and lambdas

Resources

Stars

Watchers

Forks