Skip to main content

Crate tulisp

Crate tulisp 

Source
Expand description

§Tulisp

docs.rs Crates.io

Tulisp is an embeddable Lisp interpreter for Rust with Emacs Lisp-compatible syntax. It is designed as a configuration and scripting layer for Rust applications — zero external dependencies, low startup cost, and a clean API for exposing Rust functions to Lisp code.

§Quick start

Requires Rust 1.88 or higher.

use std::process;
use tulisp::{TulispContext, Error};

fn run(ctx: &mut TulispContext) -> Result<(), Error> {
    ctx.defun("add-round", |a: f64, b: f64| -> i64 {
        (a + b).round() as i64
    });

    let result: i64 = ctx.eval_string("(add-round 10.2 20.0)")?.try_into()?;
    assert_eq!(result, 30);
    Ok(())
}

fn main() {
    let mut ctx = TulispContext::new();
    if let Err(e) = run(&mut ctx) {
        println!("{}", e.format(&ctx));
        process::exit(-1);
    }
}

§Exposing Rust functions

TulispContext::defun is the primary way to register Rust functions. Argument evaluation, arity checking, and type conversion are handled automatically.

use tulisp::{TulispContext, Rest};

let mut ctx = TulispContext::new();

// Fixed arguments
ctx.defun("add", |a: i64, b: i64| a + b);

// Optional arguments (Lisp &optional)
ctx.defun("greet", |name: String, greeting: Option<String>| {
    format!("{}, {}!", greeting.unwrap_or("Hello".into()), name)
});

// Variadic arguments (Lisp &rest)
ctx.defun("sum", |items: Rest<f64>| -> f64 { items.into_iter().sum() });

// Fallible function
ctx.defun("safe-div", |a: i64, b: i64| -> Result<i64, tulisp::Error> {
    if b == 0 {
        Err(tulisp::Error::invalid_argument("Division by zero"))
    } else {
        Ok(a / b)
    }
});

Supported argument and return types include i64, f64, bool, String, Number, Vec<T>, and TulispObject. Add &mut TulispContext as the first parameter to access the interpreter. Any type can be made passable by implementing TulispConvertible.

For advanced use cases — custom evaluation order, implementing control flow — use defspecial (raw argument list) or defmacro (code transformation before evaluation).

§Keyword-argument functions with AsPlist!

When a function accepts many optional parameters, use a plist as the argument list. The AsPlist! macro derives the required Plistable trait for a struct, and Plist<T> as a parameter type wires it up automatically.

use tulisp::{TulispContext, Plist, AsPlist};

AsPlist! {
    struct ServerConfig {
        host: String,
        port: i64 {= 8080},
    }
}

let mut ctx = TulispContext::new();
ctx.defun("connect", |cfg: Plist<ServerConfig>| -> String {
    format!("{}:{}", cfg.host, cfg.port)
});
// (connect :host "localhost")          => "localhost:8080"
// (connect :host "example.com" :port 443)  => "example.com:443"

§Opaque Rust values

Any Clone + Display type can be stored in a TulispObject and passed between Rust and Lisp transparently via Shared::new and TulispObject::as_any.

use std::fmt;
use tulisp::{TulispContext, TulispConvertible, TulispObject, Shared, Error};

#[derive(Clone)]
struct Point { x: i64, y: i64 }

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "(Point {} {})", self.x, self.y)
    }
}

impl TulispConvertible for Point {
    fn from_tulisp(value: &TulispObject) -> Result<Self, Error> {
        value.as_any().ok()
            .and_then(|v| v.downcast_ref::<Point>().cloned())
            .ok_or_else(|| Error::type_mismatch("Expected Point"))
    }
    fn into_tulisp(self) -> TulispObject { Shared::new(self).into() }
}

let mut ctx = TulispContext::new();
ctx.defun("make-point", |x: i64, y: i64| Point { x, y });
ctx.defun("point-x", |p: Point| p.x);
// (point-x (make-point 3 4)) => 3

§Built-in Lisp features

  • Control flow: if, cond, when, unless, while, progn
  • Binding: let, let*, setq, set
  • Functions and macros: defun, defmacro, lambda, funcall, eval, macroexpand
  • Lists: cons, list, append, nth, nthcdr, last, length, mapcar, dolist, dotimes
  • Alists and plists: assoc, alist-get, plist-get
  • Strings: concat, format, prin1-to-string, princ, print
  • Arithmetic: +, -, *, /, mod, 1+, 1-, abs, max, min, sqrt, expt, fround, ftruncate
  • Comparison: =, /=, <, <=, >, >= (numbers); string<, string>, string= (strings); eq, equal
  • Logic: and, or, not, xor
  • Conditionals: if-let, if-let*, when-let, while-let
  • Symbols: intern, make-symbol, gensym
  • Hash tables: make-hash-table, gethash, puthash
  • Error handling: error, catch, throw
  • Threading macros: -> / thread-first, ->> / thread-last
  • Time: current-time, time-add, time-subtract, time-less-p, time-equal-p, format-seconds
  • Quoting: ', `, ,, ,@ (backquote/unquote/splice)
  • Tail-call optimisation (TCO) for recursive functions
  • Lexical scoping and lexical binding

A full reference is available in the builtin module docs.

§Cargo features

FeatureDescription
syncMakes the interpreter thread-safe (Arc/RwLock instead of Rc/RefCell)
big_functionsIncreases the maximum number of defun parameters from 5 to 10
etagsEnables TAGS file generation for Lisp source files

§Next steps

§Projects using Tulisp

  • slippy — a configuration tool for the Sway window manager
  • microsim — a microgrid simulator

Modules§

builtin
A list of currently implemented functions and macros:
lists

Macros§

AsPlist
Derive Plistable for a struct, enabling it to be used as a Plist<T> argument in defun-registered functions.
destruct_bind
Destructures lists and binds the components to separate symbols.
destruct_eval_bind
intern
Creates a struct that holds interned symbols.
list
Provides a lisp-like syntax for constructing lists.

Structs§

BaseIter
Error
Represents an error that occurred during Tulisp evaluation.
Iter
Plist
A typed wrapper around a Lisp plist, for use as a defun argument.
Rest
A variadic tail argument in a defun function.
Shared
TulispContext
Represents an instance of the Tulisp interpreter.
TulispObject
A type for representing tulisp objects.

Enums§

ErrorKind
The kind of error that occurred.
Number

Traits§

Plistable
Conversion between a Rust struct and a Lisp plist.
TulispAny
TulispConvertible
Bidirectional conversion between Rust types and TulispObject.