1 unstable release
0.1.0 | Mar 20, 2022 |
---|
#464 in Programming languages
48KB
1.5K
SLoC
Osyris
Osyris is a pure Rust programming language with no dependencies. It's a LISP, and is intended to be easily embeddable. It's not extremely fast to execute, but it's very fast to parse and code starts executing in no time.
API
The main concepts you need to know about when using Osyris as a library are
the Reader, the Scope, and the eval
function. The Reader parses an input file
into expressions, the Scope is the map from names to variables, and the
eval function takes an expression and a scope and produces a value.
Here's a simple sample program:
use osyris::{eval, parse, stdlib};
use std::cell::RefCell;
use std::rc::Rc;
// The code we want to execute
static CODE: &'static str = r#"
(def 'fib {bind args 'num
{if (<= num 1)
1
{+ (fib (- num 1))
(fib (- num 2))}}})
(print "fib of 20 is" (fib 20))
"#;
fn main() -> Result<(), String> {
// Create a reader which will give us expressions
let mut reader = parse::Reader::new(CODE.as_bytes());
// Create a scope, and populate it with the stdlib
let scope = Rc::new(RefCell::new(eval::Scope::new()));
stdlib::init(&scope);
// Read and eval expressions
loop {
// We get a ParseError if the syntax is wrong
let expr = match parse::parse(&mut reader) {
Ok(expr) => expr,
Err(err) => {
println!("Parse error: {}:{}: {}", err.line, err.col, err.msg);
break;
}
};
// If the returned expression is None, we reached EOF
let expr = match expr {
Some(expr) => expr,
None => break,
};
// Evaluate the expression
match eval::eval(&expr, &scope) {
Ok(_value) => (), // Ignore the return value
Err(err) => {
println!("Eval error: {}", err);
break;
}
};
}
Ok(())
}
Syntax
Like most LISPs, the grammar is extremely simple. There are really only strings, numbers, identifiers, function calls, and quoted lists.
The basics are:
- Number literals:
10
,20
- String literals:
"hello world"
,"with \"escapes\""
,'identifier-strings
- Identifiers:
look-like-this
,
Function calls are an opening brace, followed by expressions, followed by a closing brace,
like this: (print 10 "hello" name)
. The value of the first expression is expected to be
a quoted list (or a built-in function).
Quoted lists are like function calls but preceded by a quote: '(print 10 "hello" name)
.
When evaluated, a quoted list becomes a list of expressions. It's common for functions
to take a quoted list as an argument as a kind of callback function. Because they're
so common, Osyris lets you write them with braces instead: {print 10 "hello" name}
.
Examples
if
is a function which takes a condition and one or two quotes:
(if (> 10 20)
{print "10 is greater than 20"}
{print "10 is not greater than 20"})
def
introduces a value in the current scope:
(def 'age 32)
(print "Your age is" age)
You can define functions by defining variables whose bodies are quotes:
(def 'say-hello {print "Hello!"})
(say-hello)
And functions get an implicit args
value which contains function arguments:
(def 'say-hello {print "Hello," (args 0)})
(say-hello "Bob")
You can use the bind
function to give arguments names:
(def 'say-hello {bind 'name 'age {print "Hello," age "year old" name}})
(say-hello "Annie" 41)
The standard library
These values are populated when you call stdlib::init
:
none
: A variable of type None.(print [values...])
: A function to print stuff to stdout.(+ [numbers...])
: Add together numbers.(- [head] [tail...])
: Subtract the numbers in the tail from the head.(* [numbers...])
: Multiply together numbers.(/ [head] [tail...])
: Divide the number in the head by the numbers in the tail.(== [values...])
: Return 1 if all values are equal, 0 otherwise.(!= [values...])
: Opposite of==
.(<= [values...])
: Return 1 if each value is smaller than or equal to the next, 0 otherwise.(< [values...])
: Return 1 if each value is smaller than the next, 0 otherwise.(>= [values...])
: Return 1 if each value is greater than or equal to the next, 0 otherwise.(> [values...])
: Return 1 if each value is greater than the next, 0 otherwise.(|| [values...])
: Return the first truthy value, or the last value if all are falsy.(&& [values...])
: Return the first falsy value, or the last value if all are truthy.(def <name> <value>)
: Create a new variable calledname
in the current scope.(set <name> <value>)
: Replace the existing variable calledname
.(if <condition> <body> [else-body])
: Executebody
ifcondition
is truthy, otherwise executeelse-body
if it exists.(match [pairs...] [default])
: Execute one out of a set of options, based on predicates. Example:
(def x 55)
(match {> x 10} {print "It's greater than 10"}
{< x 10} {print "It's smaller than 10"}
{print "It's neither greater nor smaller than 10"})
(while <condition> <body>)
: Executebody
whilecondition
executes to something truthy.(do [values...])
: Return the last value.(bind <array> [names...] <body>)
: Bind values in thearray
to names, then executebody
.(with [pairs...] <body>)
: Bind values to names, then executebody
. Example:
(with 'x 10
'y 20
{print "x + y is" (+ x y)})
(list [values...])
: Create a list.(dict [pairs...])
: Create a dictionary. Example:
(dict 'name "Bob"
'age 34
'profession "Programmer")
(lazy <quoted list>)
: Create a lazy variable.(read <port> [size])
: Read fromport
. Ifsize
is provided, read a chunk.(write <port> <data>)
: Writedata
toport
.(seek <port> <offset> [whence])
: Seek tooffset
. By default,offset
is relative to the start, butwhence
can be one of:'set
,'end
or'current
to change that.
The IO library
These values are populated when you call iolib::init
.
(open <path>)
: Open the file atpath
in read-only mode.(create <path>)
: Create or truncate the file atpath
, open it in write-only mode.(exec <argv>)
: Execute system command.