Skip to main content

sema/
lib.rs

1//! Sema — a Lisp with LLM primitives.
2//!
3//! This module provides a clean embedding API for the Sema interpreter.
4//!
5//! # Quick Start
6//!
7//! ```no_run
8//! use sema::{Interpreter, InterpreterBuilder, Value};
9//!
10//! let interp = InterpreterBuilder::new().build();
11//! let result = interp.eval_str("(+ 1 2)").unwrap();
12//! assert_eq!(result, Value::int(3));
13//! ```
14
15use std::rc::Rc;
16
17// Re-export core types.
18pub use sema_core::{intern, resolve, with_resolved, Caps, Env, Sandbox, SemaError, Value};
19/// Result of evaluating a Sema expression.
20pub type EvalResult = Result<Value>;
21
22pub type Result<T> = std::result::Result<T, SemaError>;
23
24/// Builder for configuring and constructing an [`Interpreter`].
25///
26/// By default, both the standard library and LLM builtins are enabled.
27pub struct InterpreterBuilder {
28    stdlib: bool,
29    llm: bool,
30    sandbox: Sandbox,
31}
32
33impl Default for InterpreterBuilder {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl InterpreterBuilder {
40    /// Create a new builder.
41    pub fn new() -> Self {
42        Self {
43            stdlib: true,
44            llm: true,
45            sandbox: Sandbox::allow_all(),
46        }
47    }
48
49    /// Enable or disable the standard library (default: `true`).
50    pub fn with_stdlib(mut self, enable: bool) -> Self {
51        self.stdlib = enable;
52        self
53    }
54
55    /// Enable or disable the LLM builtins (default: `true`).
56    pub fn with_llm(mut self, enable: bool) -> Self {
57        self.llm = enable;
58        self
59    }
60
61    /// Set the sandbox configuration to restrict dangerous operations.
62    pub fn with_sandbox(mut self, sandbox: Sandbox) -> Self {
63        self.sandbox = sandbox;
64        self
65    }
66
67    /// Disable the standard library.
68    pub fn without_stdlib(self) -> Self {
69        self.with_stdlib(false)
70    }
71
72    /// Disable the LLM builtins.
73    pub fn without_llm(self) -> Self {
74        self.with_llm(false)
75    }
76
77    /// Build the [`Interpreter`] with the configured options.
78    pub fn build(self) -> Interpreter {
79        sema_llm::builtins::reset_runtime_state();
80
81        let env = Env::new();
82        let ctx = sema_eval::EvalContext::new();
83
84        sema_core::set_eval_callback(&ctx, sema_eval::eval_value);
85        sema_core::set_call_callback(&ctx, sema_eval::call_value);
86
87        if self.stdlib {
88            sema_stdlib::register_stdlib(&env, &self.sandbox);
89        }
90
91        if self.llm {
92            sema_llm::builtins::register_llm_builtins(&env, &self.sandbox);
93            sema_llm::builtins::set_eval_callback(sema_eval::eval_value);
94        }
95
96        Interpreter {
97            inner: sema_eval::Interpreter {
98                global_env: Rc::new(env),
99                ctx,
100            },
101        }
102    }
103}
104
105/// A Sema Lisp interpreter instance.
106///
107/// Use [`InterpreterBuilder`] for fine-grained control, or call
108/// [`Interpreter::new`] for a default interpreter with stdlib enabled.
109pub struct Interpreter {
110    inner: sema_eval::Interpreter,
111}
112
113impl Default for Interpreter {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119impl Interpreter {
120    pub fn new() -> Self {
121        InterpreterBuilder::new().build()
122    }
123
124    /// Create an [`InterpreterBuilder`] for fine-grained configuration.
125    pub fn builder() -> InterpreterBuilder {
126        InterpreterBuilder::new()
127    }
128
129    /// Evaluate a single parsed [`Value`] expression.
130    ///
131    /// Definitions (`define`) persist across calls.
132    pub fn eval(&self, expr: &Value) -> EvalResult {
133        self.inner.eval_in_global(expr)
134    }
135
136    /// Parse and evaluate a string containing one or more Sema expressions.
137    ///
138    /// Definitions (`define`) persist across calls, so you can define a
139    /// function in one call and use it in the next.
140    pub fn eval_str(&self, input: &str) -> EvalResult {
141        self.inner.eval_str_in_global(input)
142    }
143
144    /// Register a native function that can be called from Sema code.
145    ///
146    /// # Example
147    ///
148    /// ```no_run
149    /// use sema::{Interpreter, Value, SemaError};
150    ///
151    /// let interp = Interpreter::new();
152    /// interp.register_fn("square", |args: &[Value]| {
153    ///     if let Some(n) = args[0].as_int() {
154    ///         Ok(Value::int(n * n))
155    ///     } else {
156    ///         Err(SemaError::type_error("integer", args[0].type_name()))
157    ///     }
158    /// });
159    /// ```
160    pub fn register_fn<F>(&self, name: &str, f: F)
161    where
162        F: Fn(&[Value]) -> Result<Value> + 'static,
163    {
164        use sema_core::NativeFn;
165
166        let native = NativeFn::simple(name, f);
167        self.inner
168            .global_env
169            .set_str(name, Value::native_fn(native));
170    }
171
172    /// Return a reference to the global environment.
173    pub fn global_env(&self) -> &Rc<Env> {
174        &self.inner.global_env
175    }
176
177    pub fn env(&self) -> &Rc<Env> {
178        self.global_env()
179    }
180}