come relax on the coast, stay for a while
The other day I was working in some Python code, and I wanted
to use a match
like form, without upgrading to Python 3.10. My thought process was:
-
Can I use carML? no, I’d rather not dive into that codebase
-
Can I use Coconut? no, I didn’t like the resulting Python code
-
Can I generate some of these cases? yes, but it would be nicer to just write something
I’ve been lamenting that I don’t really like working on carML: the codebase is unwieldy because of the change in direction I had; so I started looking at Yeti again, and thought that I could write something like Yeti that generated a few different languages, like Python3, and not have to write those again.
Previously, this was just eXperimental Language No. 30 (XL/30); I’ve written a few languages since carML, but none were meant to be a linguistic experiment. However, based on internal polling (of myself, my older son, and my partner) I’ve renamed this to coastML soon enough, with logo and nomenclature (come to the coast, stay a while).
Please see cur.coast
for the current set of examples (and our parser/compiler’s test file), but the simplest example is:
foo = fn x y { # (1)
# functions are just named bindings
# there's no other real need for
# syntax
case x # (2)
| 10 { print_endline "ok, x is 10" } # (3)
| 11 { print_endline "ok, x is 11" }
| (x >= y) { print_endline "ok, x is >= y" } # (4)
| _ { print_endline "oh no, x is none of the above" } # (5)
esac # (6)
}
foo 10 5;
foo 20 5;
-
Functions are just bound variables, no special syntax
-
lighter weight
case
form -
Pattern matches are just a simple value followed by a block (both required)
-
Guards are just function calls; will check to decompose types
-
The default case is actually just a catch all with
_
-
closing a form is just the name of the form backwards, Algol-style
One other item to note is that I finally decided to actually handle operators
There are only a handful of forms in coastML:
-
fn
: a lambda -
gn
: a generic lambda (for multi-methods) -
fc
: a case-lambda -
type
: for introducing records & ADTs -
case
: a combination ofcond
andcase
ormatch
-
mod
: modules -
type classes in the form of
sig
andimpl
-
assignments, operators, and function calls
-
one note about operators: like Project Verona and carML, coastML has no operator precedence, but does support inline operators
-
This means that you cannot have two operators in a form that are of different precedence without parentheses
-
for example:
1 + 2 + 3
is fine, but1 + 2 * 3
requires parentheses:1 + (2 * 3)
-
function calls work similarly:
print_endline (→string foo)
-
The top-level interfaces that most people would be interested in are:
-
carpet.Lex
, which can be used to iteratively tokenize all source code -
carpet.CoastalParser
, which returns AST objects and -
carpet.CarpetPython
, which actually generates Python code -
python3 -m carpet
, which just runs the module’smain
code -
coastml
, which is just a shell script wrapping the same
import carpet
src = """case x
| 10 { print "x is 10" }
| 11 { print "x is 11" }
| (x > y) { print "x is greater than y" }
| _ { print "none of the above" }
esac"""
ll = carpet.Lex(src)
for l in ll: # (1)
somefn(l)
coastparser = carpet.CoastalParser(src)
coastparser.load() # (2)
srccaseast = coastparser.sub_parse() # (3)
carpy = carpet.CarpetPython(src)
carpy.load()
carpy.generate() # (4)
-
The lexical analyzer also includes a
next
method, you needn’t use it via iterators -
Both the parser and the python generator can be reset with their respective
load
methods -
We use
sub_parse
here, but there is aparse
method to return all ASTs -
This currently just prints to screen, but I’ll refactor it to generate a string