Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/munificent/lark

The Lark programming language.
https://github.com/munificent/lark

Last synced: 5 days ago
JSON representation

The Lark programming language.

Awesome Lists containing this project

README

        

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). should either
be a single name or a list of parameter names. is an
expression that forms the body of the function. Ex:

> (a => do (print a, print a)) 123
123
123
: ()

def: is:

Binds a value to a variable in the current scope. should
be a single name. will be evaluated and the result bound
to the name. Ex:

> def: a is: 123
: ()
> a
: 123

if: then:
if: then: else:

Evaluates the condition. If it evaluates to true, then it
evaluates and returns . 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?
int?
list?
name?
unit?

Evaluates 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 , expecting it to be a list. Returns the item
at the 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 , 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...