
An Awesome scripting language implemented in Ocaml
Report Bug
·
Request Feature
Table of Contents
This project is an interpreter and a compiler for the Monkey Programming Language. The interpreter features its own Lexer, Parser, and Evaluator. The compiler features its own 32-bit opcodes, a bytecode compiler to convert the AST into bytecode, and a virtual machine to execute the bytecode.
The books I followed, "Writing an Interpreter in Go" and "Writing a Compiler in Go," were very enjoyable and easy to follow due to the simple patterns the author created as well as the simplicity of Golang.
The goal of the projects was sevenfold and covered a lot of ground for things I have been wanting to learn. First and foremost, writing a compiled and interpreted language from the ground up. In addition, this is my first time writing OCaml, as well as using a functional language, so it was incredibly helpful in getting me really comfortable with the language. Additionally, unit testing was something I tended to avoid; however, having to convert all the Golang tests to OCaml tests was a whole project in and of itself. I do not regret implementing the tests as they give additional confidence in the robustness of the language.
Translating the Go code into OCaml code was never straightforward. Things such as loops, mutability, arrays, and early returns are not the best way to go about things in OCaml and are avoided. This involed using recursion, lists, and result monads to achieve the above.
Areas I have missed or lacked: I did not use the Jane Street Core library as I wanted to first learn the standard OCaml library. Also, print debugging was a nightmare in OCaml, having to write too many custom string functions for OCaml objects. In the future, I plan to look into PPX as I believe it is a solid solution to my printing nightmares.
As this is my first time writing OCaml code and a compiler/interpreter, please let me know of areas I may have missed the ball on or areas I could have done better on.
Right now the easiest way to use this language is to use the nix package manager
To test drive Maml and jump straight into the repl
-
Nix
nix run github:k-kahora/maml/#maml -- -r
-
To see what the Maml CLI can do
nix run github:k-kahora/maml/#maml -- --help
-
Evaluate maml code outside the repl pass the code as a string to the eval argument
nix run github:k-kahora/maml/#maml -- --eval="puts(\"Hello World!!\")"
# Hello World!!
-
Clone the repo
git clone https://github.com/k-kahora/writing-an-interpreter-in-ocaml
-
Install opam packages
# Make sure opam is installed before running these commands opam install xxhash opam install cmdliner opam install alcotest opam install dune
-
Run tests
dune runtest
-
build the language
dune build
-
Run the language
dune exec -- Maml --help
Feature | Interpreter | Compiler |
---|---|---|
Bindings | ✅ | ✅ |
Conditionals | ✅ | ✅ |
Strings | ✅ | ✅ |
Integers | ✅ | ✅ |
arithmetic +-/* | ✅ | ✅ |
Arrays | ✅ | ✅ |
Indexing | ✅ | ✅ |
Dictionarys | ✅ | ✅ |
Functions | ✅ | ✅ |
First class functions | ✅ | ✅ |
Higher order functions | ✅ | ✅ |
Closures | ✅ | ✅ |
Recursion | ✅ | ✅ |
BuiltInFunctions | ✅ | ✅ |
Loops | ❌ | ❌ |
Floats | ❌ | ❌ |
Macros | ❌ | ❌ |
Name | Description |
---|---|
len(x) | Returns length of string,array,dictionary |
first(x) | Returns the first item of the array |
last(x) | Returns the last item of the array |
rest(x) | Returns every item but the first in an array |
push(x,item) | Appends item to the end of an array returning a new array |
puts(x) | Print x |
These are small impractical examples curated to showcase the syntax of the language.
Bindings
// You can use = or <- to bind
let x <- 30
let x = 30
Arithmatic
let x <- 30
let y <- 20
x + y
// 50
Conditionals
let value <- 50
let result <- if (value < 60) {value / 5} else {value / 2}
// 10
Arrays
let value <- [1,"hello",3,"world",5,[2,4,6],7,5,9]
let result_one <- value[1]
let result_two <- value[1 + 4][0]
// "hello"
// 2
Dictionarys
let value <- {"monkey":{"see":{"monkey":{"do":"!"}}}}
let result = value["monkey"]["see"]["monkey"]["do"]
puts("monkey" + result)
// "monkey!"
Functions
let sum <- fn(x,y) {x + y}
let result = sum(10,50)
let subtract_thirty = fn(item) {let thirty = 30; return item - 30}
let nested_square = fn() { fn(y) { y * y } }
puts(nested_square()(subtract_thirty(result)))
// 900
Fibonacci sequence (Closures, and recursion)
let fibonacci = fn(x) {
if (x == 0) {
return 0;
} else {
if (x == 1) {
return 1;
} else {
fibonacci(x - 1) + fibonacci(x - 2);
}
}
};
puts(fibonacci(15));
// 610
Map
let map = fn(list,f) {
let iter = fn(list, acc) {
if (len(list) == 0) {
return acc
}
else {
let head = first(list)
let tail = rest(list)
let accumulated = push(acc, f(head))
iter(tail, accumulated)
}
}
iter(list,[])
}
let add_one = fn(x) { x + 1 };
puts(map([1,2,3],add_one))
// [2,3,4]
let array = []
let array = push(array,10)
// Error empty item
// Should be [10]
The CLI is not fully implemented right now, I would advise just sticking to -r and -e.
See the open issues for a full list of proposed features (and known issues).
- Allow CLI to either interpret or compile
- Implement closures from the last chapter of the compiler book
- Create a logo
- Implement Macros in the Interpreter and Compiler
- Refreactor the lexer to no longer throw exceptions and insted output Errors
- Add documentation to be generated with ocamldoc
- File extensions for the compiler to recognize .maml
- Syntax sugar
- <- instead of = (completely remove = so equality is not ==)
- % modulo
- ?? !! ternary conditionals
- support <= and >= conditionals
- ** powers
- ( +=/-=/%=/*= ) infix -:- and ( ++ and -- ) postfix
- Lexer should store token location to have errors point to the right spot
- Built in functions
- dump() take a string and output the bytecode
- map(list,f)
- fold(f(a,b),acc,list) fold function on arrays and dictionarys
- iter(list) iterative over lists and arrays
- Publish the website to run Maml in the browser
- Let the CLI read a file
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
Distributed under the MIT License. See LICENSE.txt
for more information.
Malcolm Kahora - malcolmkahora@gmail.com
Project Link: https://github.com/k-kahora/writing-an-interpreter-in-ocaml