This article will take about 4 minutes to read.
See the code for this project here
fn main = {| echo hello world; }
do main
Squibbish
was one of my hobby projects from 2017. I write a lot of system scripts, and at one point I had gotten frustrated over the boilerplate and weak typing that comes with writing in bash. I decided that I’d need a new scripting language that had slightly more structure. In general I don’t advocate for reinventing the wheel (and eventually I did land on using python), but I thought that this would be a good opportunity to try out some of the compiler techniques that I had been reading about at that time.
Around 2017, I was still working on figuring out some of the more advanced language features that kotlin had to offer. In Jetbrain’s YouTrack board, there was a lot of discussion being done on how improvements could be made, and it got me interested on what actually went into writing a brand new language. To hone my kotlin scripts, I decided to write the transpiler in Kotlin - and to make the project easier, I decided to output the code directly as bash.
For those that are interested, I recommend the purple dragon book for learning compilation techniques.
I went down a rabbit hole almost immediately with grammar parsers. Bison/Yacc/ANTLR and similar parsers did not fit my needs - they required C/ASM code and they had a lot of legacy considerations that made their API hard to use. I would consider them for an enterprise grade language, but not for a toy language which was intended to improve my kotlin skills!
One of the first lessons I learned when starting again from scratch was that test driven development (TDD) is a must when writing compilers. When starting out, it is a good idea to start with the basics like comments and print statements, but introducing parsing of control flow statements can cause chaos in your output. Being able to run tests consistently after small changes meant that I was able to make incremental progress while ensuring that I was not breaking previously stable functionality.
One of the nice perks of this was that I was able to write the source code that I wanted first, and use the test to generate some transpiled code as an execution candidate. When I ran the snippet, I would be able to validate the output, and if it was completely correct, it would become the “expect” case for the assertions in my tests.
This is a simple, runnable program that shows some of the features of the language, as currently implemented.
// fahrenheit_to_celsius
fn convertToC = { input |
let ret = math { ( $input - 32 ) / 1.8 }
echo $ret
}
fn convertToF = { input |
let ret = math { ( $input * 1.8 ) + 32}
echo $ret
}
fn main = { input type |
br type {
"F" { do convertToC $input }
"C" { do convertToF $input }
}
}
do main $input $type
Functions can be defined with the fn
keyword. To make the syntax easier to represent, functions are always assigned with =
. The parameters of a function are always strings, and they end at the first instance of a |
.
Calling a function is as easy as saying do functionName $argument1
, followed by the arguments that you would like to give it. It borrows from bash syntax in this regard.
There are no if
, switch
or when
keywords in the language. They are all replaced with br {}
, which stands for branch
. br
can take an optional argument as well, similar to how when
is used in kotlin.
Math is done in a math wrapper, which interprets variables as numbers instead of strings. This makes it easy to generate bash code, where arithmetic needs to be done in a different context, like $((1+1))
Ultimately, I think I got a lot out of the project. I have a deeper understanding of what it takes to write a language, I improved quite a bit in my kotlin skills, and I have a cool language project that fits my own aesthetic for code.
There are several things that I would like to change about the project, like:
but I am happy with these results for my first attempt!