13 releases
0.5.0 | Dec 22, 2022 |
---|---|
0.4.5 | Jun 12, 2021 |
0.4.4 | Feb 22, 2020 |
0.4.3 | May 10, 2019 |
0.1.0 | Apr 17, 2019 |
#227 in Programming languages
Used in 2 crates
(via gain-lep)
46KB
1K
SLoC
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.
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
>> ()
>> cons 1 2
(1 . 2)
>> 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
>> env
(($2 . false) (z . false) (y . 12) (x . 6) ($1 . 6))
The statement syntax is optimized for two-step usage:
- Evaluate an expression
- 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.
When the first term is a variable or a literal and there are multiple terms, the expression is quoted:
>> 1 2 3
(1 2 3)
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
Built-in functions:
(and arg1 arg2 ...)
(or arg1 arg2 ...)
(not arg)
(apply function arguments)
(+ arg1 arg2 ...)
(- arg1 arg2 ...)
(* arg1 arg2 ...)
(/ arg1 arg2 ...)
(car arg)
(cdr arg)
(cons arg1 arg2)
(list arg1 arg2 ...)
(drop list count)
(take list count)
(env)
Custom extension functions have signature
fn(args: &Rc<dyn Any>) -> Result<Rc<dyn Any>, String>
or implement the lep::Fun
or the lep::FunMut
trait.
Types
Lep has a dynamic and open-ended type system. Type Rc<dyn Any>
(aliased as
lep::Obj
) 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::Name
(symbol)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 mut domain = lep::Domain::new();
lep::builtin::register(&mut domain);
domain.register("nop", |args: &lep::Obj| Ok(lep::obj::nil()));
An evaluation iteration takes an existing lep::State
and replaces it with a
new one:
let mut state = lep::State::new();
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);
}
}
}