Slang

I wrote my first programming language in March 2020. It was the second semester of my senior year in high school, and we had just gone on break because of COVID. Supposedly, we were supposed to get back to school after a week or two. I'd been interested in interpreters lately, so I decided to try and write one. As a challenge, I decided to give myself only a week, and to not reference any existing code or tutorials.

The result of this was slang. It worked, but not very well. Here's a snippet:

fn make_table(n: Int) -> String {
   let ret_str = ""

   let row = 1
   while row <= n {
    let col = 1
    while col <= n {
     ret_str = ret_str + "\t" + (row * col) as String
     col = col + 1
    }

    ret_str = ret_str + "\n"
    row = row + 1
   }

   return ret_str
}

print(make_table(10))

The syntax is very similar to Rust's, but its functionality is very limited. Maybe surprisingly, the main limitiation was in the parser; I hadn't written one before so what I made was basically a glorified tokenizer. My AST looked like this:

pub enum SubExpression {
  Val(Variable),
  Function(Rc<(HashMap<String, Expression>, Vec<BlockSection>)>),
  Operator(OperatorType),
  Name(Rc<String>),
}

pub type Expression = Vec<SubExpression>;

// the base "Tree"
pub enum BlockSection {
  Line(Vec<Token>),
  InnerBlock(Vec<BlockSection>),
}

It's barely a tree to be honest, and explains a lot of the issues I had. I'm still pretty proud of it though; I think I got pretty far for six days without any learning resources.


Slang V2

The next language I wrote is called slang-v2. Very creative. It's a huge improvement over the first version though, supporting basically every thing you would expect except for any sort of record type. The improvements come from loosely following Crafting Interpreters, and a lot more time spent on it. From the GitHub history, it looks like I took a few breaks, but I spent around a month actively working on it.

Here's a snippet:

let arr = [1, 1]

let n = 40

for (let i = 2; i < n; i += 1) {
  push(arr, arr[i - 1] + arr[i - 2])
}

for (let i = 0; i < len(arr); i += 1) {
   print(arr[i])
}

While it doesn't do anything creative and certainly isn't very productive or fun to write, it can get the job done. For example, I wrote an AI pong game that runs using stdg for display. I also did a few Euler problems.


CalcuLaTeX

The next language I wrote was CalcuLaTeX. I'm not sure if it counts though; it's an engineering/math DSL and probably not Turing complete. I thought it was worth mentioning since I learned a lot from writing it, and it's one of my favorite projects.

CalcuLaTeX demo


RustScript

Soon after CalcuLaTeX, I wrote RustScript. RustScript started as a bit of a joke; I was writing it for a bonus section of a school project, whose goal was to write an async messenger app in Java. I needed a lot of bonus because I'd missed big deadline earlier, so I decided to go overkill and write a language good for oneliners and treat it as a bot within the messenger. The idea was that if you were ever chatting with friends and needed to do some quick math, you could send something like ![x * x for x in [0..15]] and everyone in the group would see the result. It's definitely a bit contrived.

When I was coming up with the name, a friend suggested "Java Script", since it's written in Java, so I decided to call it RustScript because it's not written in Rust. The tagline is that it's as similar to Rust as JavaScript is to Java.

It ended up working a bit too well. Turns out expression based languages are both easier to write an interpreter for and better to write in. Here's a snippet:

> let gcd = fn (a, b) => if (b == 0)  then (a) else (gcd(b, (a % b)))
> let lcm = fn (a, b) => (a * b) / (gcd(a, b))
> fold(lcm, 1, [1..20])
232792560

After I'd completed it and hadn't planned to work on it any further, someone started submitting pretty impactful PRs, and, at my suggestion, he forked it and made a much more complete version. You can find his repo here: https://github.com/WilliamRagstad/RustScript. He added features such as actual code blocks, limited pattern matching, and modules, making the language work more than just oneliners. It was actually pretty nice to write in.


JavaScripth

A few weeks after writing RustScript, I wrote JavaScripth. JavaScripth is a joke and it really sucks. I wrote about it a bit more here: https://blog.mikail-khan.com/post/json-interpreter. You can get the gist of it from this snippet:

[
  {"def": {"fib": {"fn": [["n"],
  {"if": {"cond": {"lt": ["n", 2]},
    "then": 1,
    "else": {"+": [
    {"fib": [{"-": ["n", 1]}]},
    {"fib": [{"-": ["n", 2]}]}
    ]}
  }}
  ]}}},
  {"print": {"fib": [10]}}
]

RustScript 2

Last semester, I wanted to learn OCaml, so I decided to rewrite RustScript in it, with a few additional features such as proper pattern matching and tail call optimization. It turned out pretty well, so I added maps, closures, and atoms. I also wrote some AST optimizations such as identifier interning and a few others. Another interesting aspect is that it compiles to closures for some additional speed. It's still pretty slow by virtue of being a dynamically typed treewalk interpreter though.

I liked writing in it a lot, and I was inspired by Ink, so I decided to rewrite my website in it. I started by adding some builtin bindings to OCaml CoHTTP, and then I wrote a TOML parser, an HTML templating library, and a HTTP server/routing library. The finished website is hosted at https://rustscript.mikail-khan.com, and once it reaches 6 months of continuous runtime I'll promote it to the apex domain. It works pretty nicely:


# each endpoint is a route that uses a generator function
# to generate the page
let endpoints = %{
  "" => fn(gen_state, server_state) => {
	template_file_string("templates/index.html", gen_state)
  },
  ...
  "portfolio.html" => fn(gen_state, %{projects}) => {
	let state = %{"projects" => projects | gen_state}
	template_file_string("templates/portfolio.html", state)
  },
  "portfolio/details/{{project_name}}.html" => fn(gen_state, %{projects}) => {
	# route arguments are added to gen_state
	let project_name = gen_state("project_name")
	let project = find(fn(p) => p("projectName") == project_name, projects)
	match project
	  | () -> "project not found"
	  | _ -> {
		let state = merge_maps(project, gen_state)
		template_file_string("templates/portfolio_details.html", state)
	  }
  },
  ...
  (:any, "*") => fn(_, server_state) => ("404", server_state, %{}, 404)
}

serve_endpoints(:tls, 8000, global_state, %{projects: read_projects(), resume: read_resume()}, endpoints)

That's all the languages I've written so far, but I'll write another one eventually.