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}