Welcome to Lever’s documentation

0.6.0 will be the next release of the Lever programming language. This documentation is incomplete.

Stable releases will come with complete documentation. For most things not described here, I advice you to join the irc channel set up for the community.

Contents:

Installing Lever

There are some things that may need explicit platform support and I’ll be busy coding the first programs in the language. Therefore I only promise that Linux version runs.

At maximum there will be a release for the SteamOS, so you have to compile this yourself. If you happen to be a person doing packaging for a distribution, please provide your contact info so that I can assist you out and help keep the packaging updated effortlessly.

Obtain the source code

Easiest way to obtain Lever source code is to use git. Git also helps tremendously if you later decide to contribute to lever. To download yourself a repository you can contribute to, do:

git clone https://github.com/cheery/lever

At any time, you may also download an archive snapshot of the repository and extract:

wget https://github.com/cheery/lever/archive/master.zip
unzip master.zip

Compiling instructions

Fortunately the compiling will only take few minutes and it’s easy. I have set up a script to compile lever. Here’s how to invoke it from a terminal:

cd lever
python setup.py compile

If you’re on debian based system, it prompts to install what you need to compile it. Otherwise you are adviced to provide the missing dependencies yourself. The script won’t attempt to proceed if it cannot find the needed libraries.

The compiling results with the executable. For now the run.py -script can be used to run any of the examples. Here’s how to run one of the samples:

python run.py samples/ticktock.lc

Languages present in Lever runtime

Lever currently uses python for scripts and for compiling from source code to bytecode. Though it is its own language.

The interpreter is written in RPython. From there it is translated into native machine code and a matching JIT compiler is generated.

Lever syntax & grammar

Before lever runs a source listing, it compiles that into instructions so that virtual machine can load in the program. Instead of having a grammar in itself, the compiler loads it as an input from the lever.grammar -file.

The lever.grammar is a listing of context-free rules the compiler recognizes. If you happen to prefer different syntax for some reason, all you need to do is change the lever.grammar and provide it along your project.

There is a thin layer of tokenizing before the grammar is applied to the input source listing.

If you have used dynamically typed languages before, and you know what a context free grammar is, most of this grammar should make sense.

The functions processing each of these rules are in compile.py, and they are prefixed with post_.

Blocks

Lever files are sequences of statements separated by newline. Each statement can be evaluated and when done so return a value, these statements are called blocks, whenever appropriate, increased indentation level can form a block inside expressions:

file =>
    empty_list:
    statements

block => pass(statements): indent statements dedent

statements =>
    first:           block_statement
    append(lhs rhs): lhs=statements newline rhs=block_statement

Different statements

The statement is divided into block-level and ordinary statements. Right now, expressions are the only ordinary statements:

block_statement =>
    pass: statement
    return(statement):
        kw_return:"return" statement
    if(statement block otherwise):
        kw_if:"if" statement block otherwise
    while(statement block):
        kw_while:"while" statement block
    local_assign(symbol block_statement):
        symbol assign_op:"=" block_statement
    upvalue_assign(symbol block_statement):
        symbol upvalue_assign_op:":=" block_statement
    setitem(expr idx block_statement):
        expr lb:"[" idx=expr rb:"]" assign_op:"=" block_statement
    setattr(expr symbol block_statement):
        expr dot:"." symbol assign_op:"=" block_statement
    for(symbol statement block):
        kw_for:"for" symbol kw_in:"in" statement block
    import(symbols_list):
        kw_import:"import" symbols_list

otherwise =>
    done:
    elif(statement block otherwise):
        newline kw_elif:"elif" statement block otherwise
    else(block):
        newline kw_else:"else" block

statement =>
    pass: expr

Note that on topmost level function, the local_assign assigns a value into module namespace rather than the local scope.

The upvalue assign -rule can be used to store values into already bound variables.

I have also not entirely decided how variable lookup should happen. Right now, lookup happens from local scope, if there is a preceding dominating control flow block that does local_assign into that variable. This is a cornel case and the behavior may change, though.

Expressions

Expressions are some of the few things that might be easier to describe with precedence table. For now, you can take a thumb of rule that the rule appearing lower in this subsequent listing will take higher precedence:

expr =>
    expr3
    or(expr3 expr): expr3 kw_or:"or" expr

expr3 =>
    expr5
    and(expr5 expr3): expr5 kw_and:"and" expr3

expr5 =>
    expr8
    not(expr8): kw_not:"not" expr8

expr8 =>
    expr10
    in(l r): l=expr10 kw_in:"in" r=expr10
    not_in(l r): l=expr10 kw_not:"not" kw_in:"in" r=expr10
    binary: expr10 lt:"<" expr10
    binary: expr10 gt:">" expr10
    binary: expr10 eq:"==" expr10
    binary: expr10 ne:"!=" expr10
    binary: expr10 le:"<=" expr10
    binary: expr10 ge:">=" expr10

expr10 =>
    expr20
    binary: expr10 bitwise_or:"|" expr20

expr20 =>
    expr30
    binary: expr20 bitwise_xor:"^" expr30

expr30 =>
    expr50
    binary: expr30 bitwise_and:"&" expr50

expr50 =>
    expr100
    binary: expr50 bitwise_shl:"<<" expr100
    binary: expr50 bitwise_shr:">>" expr100

expr100 =>
    expr200
    binary: expr100 concat:"++" expr200
    binary: expr100 plus:"+" expr200
    binary: expr100 minus:"-" expr200

expr200 =>
    prefix
    binary: expr200 star:"*" prefix
    binary: expr200 slash:"/" prefix
    binary: expr200 percent:"%" prefix

prefix =>
    postfix
    prefix: plus:"+" postfix
    prefix: minus:"-" postfix

postfix =>
    term
    call(postfix arguments):
        postfix lp:"(" arguments rp:")"
    getitem(postfix expr):
        postfix lb:"[" expr rb:"]"
    getattr(postfix symbol):
        postfix dot:"." symbol

arguments =>
    empty_list:
    arguments1
    pass(arguments1): arguments1 comma:","

arguments1 =>
    first: expr
    append(lst expr): lst=arguments1 comma:"," expr

Terms

These are the current terms understood by the language:

term =>
    lookup:          symbol
    int:             int
    hex:             hex
    float:           float
    string:          string
    pass(expr):      lp:"(" expr rp:")"
    list(arguments): lb:"[" arguments rb:"]"
    function(bindings block):
        lp:"(" bindings rp:")" colon:":" block
    dict(pairs): lc:"{" pairs rc:"}"
    lookup(escaped_keyword): lc:"{" escaped_keyword rc:"}"
    lookup(string): percent:"%" string

bindings =>
    empty_list:
    bindings1
    pass(bindings1): bindings1 comma:","

bindings1 =>
    first: symbol
    append(lst symbol): lst=bindings1 comma:"," symbol

pairs =>
    empty_list:
    pairs1
    pass(pairs1): pairs1 comma:","

pairs1 =>
    first: pair
    append(lst pair): lst=pairs1 comma:"," pair

pair => tuple(k v): k=expr colon:":" v=expr

escaped_keyword =>
    pass: kw_import:"import"
    pass: kw_and:"and"
    pass: kw_or:"or"
    pass: kw_not:"not"

symbols_list =>
    first: symbol
    append(lst symbol): lst=symbols_list comma:"," symbol

Lever base module

class dict([iterable])

Dictionary is a hash table that maps keys to values.

class module

Modules are hierarchical name->value -tables. They can be reset and reloaded.

class exnihilo

Produces ‘from scratch’ -objects that can be filled with anything you wish.

class object

Base interface for all interfaces.

class list

Lists represent sequences of values.

class multimethod(arity)

Multimethods represent bundles of functions of fixed arity. Programmer can insert more of functions into a multimethod at any time.

During invocation multimethods calls interface() for every argument they get. The result is used to determine the function to call. If the value is missing in the table, the default -method is called. If there’s no default method, then an error is raised.

class greenlet(arguments...)

Conceptually greenlets represent a body of work you can stop for a moment while you do a different task. Note that you are always in a greenlet. You can always put the current task to sleep, or switch to the eventloop to run a different task.

Greenlet represents a call frame that can be suspended. The arguments describe a function to call and it can be left blank. In that case the switching has to pass a function that is called inside the greenlet.

The greenlet.parent describes where the call returns once it finishes.

greenlet.switch(arguments...) suspends the current greenlet and switches to the targeted greenlet. If the greenlet hasn’t started yet, the given arguments are concatenated to the initial arguments. If the greenlet is suspended, the arguments are compacted into a value and returned in the target greenlet.

getcurrent()

Returns the currently running greenlet.

schedule(arguments...)

Schedule is similar to the greenlet -command, except that it queues the greenlet and sets it to return into the eventloop when it finishes. It returns the newly created greenlet.

sleep(duration[, function])

The sleep performs two functions. It can be used to suspend the current greenlet, queue it after duration of seconds pass.

If you pass it a function or greenlet, it will convert it into a greenlet and adds it to the event queue after the duration passes.

class int

An integer. Merely used to identify integers. No other interests.

class bool

Boolean. Merely used to identify booleans and to convert values into booleans.

class str

Represents strings. Strings are immutable.

null

Represents nonexistence of value.

true

Represents a true value.

false

Represents a false value.

{import}(name)

Imports a function.

interface(object)

Retrieve the interface of an object.

iter(object)

Retrieve an iterator. Invokes the +iter from the interface table.

getitem(object, index)

Equivalent to object[index]. Presented as a convenience. Invokes the +getitem from the interface table.

setitem(object, index, value)

Equivalent to object[index] = value. Presented as a convenience. Invokes the +setitem from the interface table.

getattr(object, name)

Retrieves attribute from an object.

setattr(object, name, value)

Sets an attribute from an object.

ord(character)

Returns integer representing the unicode point of the character.

chr(integer)

Returns a character that corresponds to the integer.

print(values...)

Prints the given values, with space between them.

{and}(a, b)

A convenience function that does a and b without conditional chaining rules.

{or}(a, b)

A convenience function that does a or b without conditional chaining rules.

{not}(a)

A convenience function that inverses a truth value.

coerce(a, b)

A Base multimethod for converting two value into values that pair arithmetically. Coercion is used when the values cannot be found from the usual multimethod table and when the exact pairing cannot be found.

{+}, {-}, {*}, {/}, {|}, {%}, {&}, {^}, {<<}, {>>}, min, max

Standard arithmetic methods.

{<}, {>}, {<=}, {>=}, {!=}, {==}

Stardard comparative methods.

{-expr}, {+expr}

Multimethods for negative and positive prefix.

{++}

Concatenation multimethod. Implemented on lists and strings.

Contributing to Lever

Contributing to Documentation

The Lever documentation sits in doc/ subdirectory. The following commands will update the documentation so you can preview your changes:

cd doc/
make publish

Indices and tables