lep 0.3.0

Mini language for interactive consoles, featuring small code footprint.
Documentation

Lep

Mini language for interactive consoles, featuring small code footprint. It's essentially a sugarcoated Lisp subset, approximating shell syntax, influenced by Python.

The core language is functional/declarative. It's not Turing complete: function creation, looping and recursion are not supported. Variable binding is supported at the top-level (session scope). Extension functions may introduce side-effects.

The idea is that the Rust program (which embeds Lep) implements extension functions, effectively creating a domain-specific query or administration language. The host program is also responsible for setting up the REPL (read-eval-print-loop), thus choosing where the input is read from and what gets printed.

Syntax

Evaluation is line-oriented: one line corresponds to one statement. A statement yields an observable result value.

Statement syntax (both parts are optional):

!variable expression

The variable name is any character string excluding whitespace, ( and ). The expression is an otherwise usual S-expression, but the outermost parentheses may be omitted in some cases (when the first term is an atom).

The statement yields the value of the expression. If the ! prefix is present, the value is also assigned to a variable. If variable name is empty, an unused name is chosen (e.g. $1). If the ! prefix is omitted, the value is assigned to the _ variable. If the expression is omitted, the _ variable is evaluated.

Examples:

>> echo "foo"
foo
>> + 1 2 3
6
>> (+ _ 10)
16
>> * (/ (- 10 5) 2) 50
100
>> ()
>> list 1 2
(1 . (2 . ()))
>> ! + 1 2 3
$1 = 6
>> !x + 1 2 3
x = 6
>> !y (* x 2)
y = 12
>> and (or (and x y) (or)) true false
false
>> !z
z = false
>> !
$2 = false

The statement syntax is optimized for two-step usage:

  1. Evaluate an expression
  2. Decide whether the result is worth remembering!

An expression without outer parentheses evaluates a variable/literal or invokes a function depending on the type of the first term. Therefore it's not possible to directly get a reference to a function. However, the built-in identity function can be used to work around that:

>> identity echo
<function echo>
>> _ "bar"
bar

Some expressions cannot be written without outer parentheses:

>> (choose-operator) arg
error
>> ((choose-operator) arg)
ok

When the first term is a variable or a literal and there are multiple terms, the expression is quoted:

>> 1 2
(1 . (2 . ()))

A statement consisting only of whitespace yields () and leaves the variables unchanged. When the user inputs an empty line, the embedder program doesn't need to evaluate it, and may do something else instead.

Functions

Special forms are implemented internally:

(and arg1 arg2 ...)
(or arg1 arg2 ...)

Optional built-in functions:

(+ arg1 arg2 ...)
(- arg1 arg2 ...)
(* arg1 arg2 ...)
(/ arg1 arg2 ...)
(car arg)
(cdr arg)
(cons arg1 arg2)
(list arg1 arg2 ...)
(not arg)
(identity arg)

Custom extension functions implement the lep::Fun or the lep::FunMut trait:

struct Println;

impl lep::Fun for Println {
    fn invoke(&self, world: &World, args: Vec<Rc<dyn Any>>) -> Result<Rc<dyn Any>, String> {
        for x in args {
            print!("{}", lep::stringify(x));
        }
        println!("");
        Ok(world.nil())
    }
}

Types

Lep has a dynamic and open-ended type system. Type Rc<dyn Any> is used to pass immutable values to and from functions. Extension functions may return and accept custom types, but they cannot be used with built-in arithmetic functions, and logical functions will treat their values as truthful.

Fully supported types:

  • ()
  • bool
  • i64
  • String
  • lep::Ref (function)
  • lep::Pair (cons cell, e.g. a list node)

The following values are considered untrue:

  • ()
  • false
  • 0 (only i64)
  • "" (only String)

Evaluation

Extension functions are bound to a lep::Domain object:

let my_println = Println {};
let mut domain = lep::Domain::new();
lep::builtin::register_all(&mut domain);
domain.register("println", &my_println);

An evaluation iteration takes an existing lep::State and replaces it with a new one:

let mut state = lep::State::new(&domain);
loop {
    match lep::eval_stmt(&mut domain, state.clone(), read_line()) {
        Ok(new_state) => {
            // Print result here.
            state = new_state;
        }
        Err(msg) {
            println!("error: {}", msg);
        }
    }
}