Skip to main content

rusty/
lib.rs

1// Copyright (c) 2026 Nicholas Vermeulen
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Rusty Python bindings via PyO3
5//!
6//! Usage:
7//!   import rusty
8//!   print(rusty.eval("(+ 1 2)"))          # => "3"
9//!   r = rusty.Rusty()
10//!   r.eval("(define x 42)")
11//!   r.eval("(* x 2)")                     # => "84" (stateless: x not persisted)
12//!
13//!   s = rusty.RustySession()
14//!   s.eval("(define x 42)")
15//!   s.eval("(* x 2)")                     # => "84" (stateful: x persisted)
16
17use pyo3::prelude::*;
18use pyo3::wrap_pyfunction;
19
20mod lexer;
21mod parser;
22mod env;
23mod eval;
24mod interp;
25mod arena;
26mod rust_jit;
27mod graph_ir;
28mod checkpoint;
29mod trace;
30mod type_check;
31mod effect_check;
32mod kg;
33
34use eval::Evaluator;
35use interp::{make_env, run_code, print_repr};
36
37// ── Stateless interpreter ─────────────────────────────────────────────────
38
39/// Stateless Rusty interpreter. Each eval() call gets a fresh environment.
40/// Fast for one-shot tool calls and expression evaluation.
41#[pyclass(name = "Rusty")]
42pub struct RustyInterp;
43
44#[pymethods]
45impl RustyInterp {
46    #[new]
47    fn new() -> Self { RustyInterp }
48
49    /// Evaluate Rusty/Lisp code. Returns result as string (unquoted).
50    fn eval(&self, code: &str) -> PyResult<String> {
51        let env = make_env();
52        match run_code(code, &env, &Evaluator::new()) {
53            Ok(v)  => Ok(print_repr(&v)),
54            Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(e)),
55        }
56    }
57
58    /// Evaluate and return the Lisp display form (strings stay quoted).
59    fn eval_repr(&self, code: &str) -> PyResult<String> {
60        let env = make_env();
61        match run_code(code, &env, &Evaluator::new()) {
62            Ok(v)  => Ok(format!("{}", v)),
63            Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(e)),
64        }
65    }
66
67    /// Returns True if the code is syntactically valid.
68    fn check(&self, code: &str) -> bool {
69        let tokens = lexer::Lexer::new(code).tokenize();
70        !parser::Parser::new(tokens).parse().is_empty()
71    }
72
73    fn __repr__(&self) -> &str { "<Rusty interpreter>" }
74}
75
76// ── Stateful session ──────────────────────────────────────────────────────
77
78/// Stateful session. Definitions persist across eval() calls by replaying
79/// history into a fresh env each time (safe, no Rc across threads).
80#[pyclass]
81pub struct RustySession {
82    history: Vec<String>,
83}
84
85#[pymethods]
86impl RustySession {
87    #[new]
88    fn new() -> Self { RustySession { history: Vec::new() } }
89
90    /// Eval code, preserving all previous definitions.
91    fn eval(&mut self, code: &str) -> PyResult<String> {
92        let env  = make_env();
93        let eval = Evaluator::new();
94        for prev in &self.history {
95            let _ = run_code(prev, &env, &eval);
96        }
97        match run_code(code, &env, &eval) {
98            Ok(v) => {
99                self.history.push(code.to_string());
100                Ok(print_repr(&v))
101            }
102            Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(e)),
103        }
104    }
105
106    /// Clear all session state.
107    fn reset(&mut self) { self.history.clear(); }
108
109    /// Number of expressions in history.
110    fn history_len(&self) -> usize { self.history.len() }
111
112    fn __repr__(&self) -> String {
113        format!("<RustySession history={}>", self.history.len())
114    }
115}
116
117// ── Module ────────────────────────────────────────────────────────────────
118
119#[pymodule]
120fn rusty(m: &Bound<'_, PyModule>) -> PyResult<()> {
121    m.add_class::<RustyInterp>()?;
122    m.add_class::<RustySession>()?;
123    m.add_function(wrap_pyfunction!(eval_fn, m)?)?;
124    Ok(())
125}
126
127/// Top-level rusty.eval("code") convenience function.
128#[pyfunction]
129#[pyo3(name = "eval")]
130fn eval_fn(code: &str) -> PyResult<String> {
131    let env = make_env();
132    match run_code(code, &env, &Evaluator::new()) {
133        Ok(v)  => Ok(print_repr(&v)),
134        Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(e)),
135    }
136}