/Maml

Primary LanguageOCaml

Forks Stargazers Issues MIT License LinkedIn


Logo

Maml Programming Language

An Awesome scripting language implemented in Ocaml
Report Bug · Request Feature

Table of Contents
  1. About The Project
  2. Getting Started
  3. Usage
  4. Roadmap
  5. Contributing
  6. License
  7. Contact
  8. Acknowledgments

About The Project

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.

(back to top)

Built With

  • Ocaml

Built On

  • Nixos

(back to top)

Getting Started

Right now the easiest way to use this language is to use the nix package manager

Prerequisites

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!!

Installation from source

  1. Clone the repo

    git clone https://github.com/k-kahora/writing-an-interpreter-in-ocaml
  2. Install opam packages

    # Make sure opam is installed before running these commands
    opam install xxhash
    opam install cmdliner
    opam install alcotest
    opam install dune
  3. Run tests

    dune runtest
  4. build the language

    dune build
  5. Run the language

    dune exec -- Maml --help

(back to top)

Usage

feautures

Feature Interpreter Compiler
Bindings
Conditionals
Strings
Integers
arithmetic +-/*
Arrays
Indexing
Dictionarys
Functions
First class functions
Higher order functions
Closures
Recursion
BuiltInFunctions
Loops
Floats
Macros

Builtin functions

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

small examples

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]

Known bugs

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).

(back to top)

TODO

  • 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

(back to top)

Contributing

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!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

License

Distributed under the MIT License. See LICENSE.txt for more information.

(back to top)

Contact

Malcolm Kahora - malcolmkahora@gmail.com

Project Link: https://github.com/k-kahora/writing-an-interpreter-in-ocaml

(back to top)

Acknowledgments

(back to top)