lark
lark copied to clipboard
The Lark programming language.
Lark
----
Lark is an experiment in designing a homoiconic language with a syntax inspired by SmallTalk. It's very early, so there isn't much to see yet, but you can play with it.
Lark Syntax
Core syntax
Internally, Lark has a very simple syntax. It's more complex than Scheme, but just barely. It has three core expression types:
- Atoms are things like names, numbers and other literals.
- Lists are an ordered set of other expressions.
- Calls are a pair of expressions, the function and the argument.
That's it. Scheme has the first two, and Lark just adds one more. Every expression in Lark can be desugared to these core elements. The rest of this section explains the syntactic sugar added on top of that, but that's handled only by the parser. To the interpreter, the above is the only syntax.
This should mean that it'll be as easy to make powerful macros in Lark as it is in Scheme.
Names
The simplest syntactical element in Lark are names, identifiers. There are three kinds of identifiers. Regular ones start with a letter and don't contain any colons. Like:
a, abs, do-something, what?!, abc123
They are used for regular prefix function calls and don't trigger any special parsing, so you can just stick them anywhere.
Operators start with a punctuation character, like:
+, -, !can-have-letters-too!, !@#%#$$&$
An operator name will cause the parser to parse an infix expression. If you don't want that, and just want to use the name of an operator for something (for example, to pass it to a function), you can do so by surrounding it in (). This:
(+) (1, 2)
is the same as:
1 + 2
Keywords start with a letter and contain at least one colon. A colon by itself is also a keyword. Ex:
if:, :, multiple:colons:in:one:
Like operators, keywords will trigger special parsing, so must be put in parentheses if you just want to treat it like a name.
Prefix functions
The basic Lark syntax looks much like C. A regular name followed by an expression will call that named function and pass it the given argument. Ex:
square 123
Dotted functions
Lark also allows functions to be called using the dot operator. It's functionally equivalent to a regular function call, but has two minor differences: the function and argument are swapped, and the precedence is higher. For example:
a.b c.d
is desugared to:
(b(a))(d(c))
Swapping the argument and function may sound a bit arbitrary, but it allows you to write in an "object.method" style familiar to many OOP programmers. For example, instead of:
length list
you can use:
list.length
Operators
Operators are parsed like SmallTalk. All operators have the same precedence (no Dear Aunt Sally), and are parsed from left to right. The expression:
a + b * c @#%! d
is equivalent to:
@#%!(*(+(a, b), c), d)
Keywords
Lark has keywords that work similar to SmallTalk. The parser will translate a series of keywords separated by arguments into a single keyword name followed by the list of arguments. This:
if: a then: b else: c
is parsed as:
if:then:else:(a, b, c)
Lists
A list can be created by separating expressions with commas, like so:
1, 2, 3
Parentheses are not necessary, but are often useful since commas have low precedence. For example:
this: means:
difference 1, 2 ===> (difference(1), 2)
difference (1, 2) ===> difference(1, 2)
Braces
A series of expressions separated by semicolons and surrounded by curly braces is syntactical sugar for a "do" function call. This gives us a simple syntax for expressing a sequence of things that executed in order (which is what the "do" special form is for). So this:
{ print 1, 2; print 3, 4 }
is parsed as:
do (print (1, 2), print (3, 4))
Note that the semicolons are separators, not terminators. The last expression does not have one after it.
Precedence
The precedence rules follow SmallTalk. From highest to lowest, it is prefix functions, operators, keywords, then ",". So, the following:
if: a < b then: c, d
is parsed as:
(if:then:(<(a, b), c), d)
That's the basic syntax. The Lark parser reads that and immediately translates it to the much simpler core syntax.
Running Lark
Lark is a Java application stored in a single jar, you can run it by doing either:
$ java -jar lark.jar
Or, if you have bash installed, just:
$ ./lark
This starts up the Lark REPL, like this:
lark v0.0.0
Type 'q' and press Enter to quit.
From there you can enter expressions, which the interpreter will evaluate and print the result.
You can also tell lark to load and interpret a script stored in a separate file by passing in the path to the file to lark:
$./lark path/to/my/file.lark
Built-in functions
So what can you actually do with it? Not much, yet. Only a few built-in functions are implemented:
'
The quote function simply returns its argument in unevaluated
form. Ex:
> ` print 123
: print 123
(note: does not call print function)
do
If the expression is anything but a list, it simply evaluates it
and returns. If it's a list, it evaluates each item in the list,
in order, and then returns the evaluated value of the last item.
In other words, it's Lark's version of a { } block in C. Ex:
> do (print 1, print 2, print 3)
1
2
3
: ()
print
Simply evaluates the argument and prints it to the console.
Creates an anonymous function (a lambda). <params> should either
be a single name or a list of parameter names. <body> is an
expression that forms the body of the function. Ex:
> (a => do (print a, print a)) 123
123
123
: ()
def:
Binds a value to a variable in the current scope. <name> should
be a single name. <expr> will be evaluated and the result bound
to the name. Ex:
> def: a is: 123
: ()
> a
: 123
if:
Evaluates the condition. If it evaluates to true, then it
evaluates and returns <expr>. If it evaluates to false, it will
either return () or, if an else: clause is given, evaluate and
return that. Ex:
> if: true then: print 1 else: print 2
2
: ()
bool?
Evaluates <expr> then returns true if it is the given type. Note
that ().list? returns *true* because unit is the empty list. Ex:
> 123.int?
: true
> ('foo).name?
: true
> 123.list?
: false
Evaluates <expr>, expecting it to be a list. Returns the item
at the <int> position in the list (zero-based). Basically, this
means an int is a function you can pass a list to to get an item
from it. Ex:
> 0 (1, 2, 3)
: 1
> (4, 5, 6, 7).3
: 7
count
Evaluates <expr>, expecting it to be a list. Returns the number of
items in the list. Ex:
> (1, 2, 3).count
: 3
That's it so far. Like I said, very early...