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:

```rust 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:

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:

osyris (if (> 10 20) {print "10 is greater than 20"} {print "10 is not greater than 20"})


def introduces a value in the current scope:

osyris (def 'age 32) (print "Your age is" age)


You can define functions by defining variables whose bodies are quotes:

osyris (def 'say-hello {print "Hello!"}) (say-hello)


And functions get an implicit args value which contains function arguments:

osyris (def 'say-hello {print "Hello," (args 0)}) (say-hello "Bob")


You can use the bind function to give arguments names:

osyris (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:

The IO library

These values are populated when you call iolib::init.