A toy interpreter for a subset of the JavaScript programming language. It is written in Haskell, and uses Alex and Happy for lexer and parser generation.
To calculate the nth Fibonacci number, you can use the following code:
var fibonacci = function (n) {
if (n < 2) {
n;
} else {
fibonacci(n - 1) + fibonacci(n - 2);
}
};
fibonacci(10); // 55
Similarly, to calculate the sum of an array of numbers of length n, you can use the following code:
var sum = function (arr, n) {
if (n <= 0) {
0;
} else {
arr[n - 1] + sum(arr, n - 1);
}
};
sum([1, 2, 3, 4], 4); // 10
- Arithmetic operations
- Boolean operations
- Variable declarations
- Conditional expressions
- First class functions
- Recursive functions
- Multiple argument functions
- Multi-dimensional arrays
- Lexical scoping
- Lazy (call-by-need) evaluation
- Read-Eval-Print Loop (REPL)
Below are some primitive code examples that the interpreter accepts and evaluates correctly, along with brief notes on certain design choices and language quirks.
Basic mathematical and boolean operations are supported. Operator precedence is identical to that of JavaScript.
- (1 + 2) * 3; // -9
4 ** 3 % 9; // 1
true && !true; // false
false || (2 + 2) != 5; // true
1 <= 1; // true
-1 > 2; // false
Immutable variables can be declared and used throughout the program using the var
keyword.
var x = 1 + 2;
x ** 3; // 27
Variables are immutable, but can be shadowed by re-declaring them in the same scope.
var a = 1;
var b = 2;
var a = b;
a; // 2
Note: this language uses call-by-need evaluation, where both expressions and their values are stored in the execution environment. Upon encountering an expression for the first time it is stored directly, thereafter it is evaluated and stored as a concrete value. This is a hybrid approach between call-by-value and call-by-name evaluation to get the best of both strategies; prevent repeated evaluation of a declaration (as in call-by-value) and avoid wasting computational resources where declared values are never used (as in call-by-name).
Expressions can be conditionally evaluated using if
and else
. Conditions must evaluate to a boolean value.
var x = 2;
if (x == 2) {
x / 2;
} else {
-1;
} // 1
These expressions can be nested to create more complex conditional logic.
var x = 2;
if (x == 1) {
1;
} else {
if (x == 2) {
2;
} else {
3;
}
} // 2
Multi-dimensional arrays can be created using square brackets. However, similar to JavaScript, there is no enforcement of type continuity, meaning multiple types can coexist in the same array. Accessing elements from an array is done using square brackets and 0-based indexing.
var a = [1, [10, 11], true];
a[1][0]; // 10
Functions in this language are first-class by nature, allowing them to be passed and returned from other functions. The syntax may seem familiar to early JavaScript users where function expressions are defined using the function
keyword and assigned to variables. Such functions, like JavaScript, are not hoisted and therefore cannot be used before they are defined.
var add = function (x) {
function (y) {
x + y;
};
};
var addOne = add(1);
addOne(9); // 10
Recursive functions are also possible.
var add = function (x, y) {
if (x == 0) {
y;
} else {
add(x - 1, y + 1);
}
};
add(4, 2); // 6
To build the project, you will need Stack. To evaluate a file or start the REPL using Stack, use the following commands in the project directory:
# Build the project
$ stack build
# Read and evaluate file
$ stack exec mjs example.js
# REPL
$ stack exec mjs
An executable produced by Stack can just as easily be used directly in place of stack exec mjs
. To create the executable, simply use:
# Create executable
$ stack install
The executable file will be copied to some directory on your machine. Stack will helpfully print the path to your terminal for you to navigate to or to add to your PATH
. You can then run the interpreter by executing it using:
# Read and evaluate file
./mjs [example.js]
# REPL
./mjs
Alternatively, you can download a pre-built executable from the releases page.
To enter the REPL, simply use:
# REPL
./mjs
Whilst in the REPL, you can load and evaluate files using:
# Read and evaluate file in REPL
:load example.js
To exit the REPL, use:
# Exit REPL
:quit