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        // It says "syntactically valid", so it has to mean it: unbalanced
71        // parens used to pass here.
72        match parser::Parser::new(tokens).parse_checked() {
73            Ok(ast) => !ast.is_empty(),
74            Err(_) => false,
75        }
76    }
77
78    fn __repr__(&self) -> &str { "<Rusty interpreter>" }
79}
80
81// ── Stateful session ──────────────────────────────────────────────────────
82
83/// Stateful session. Definitions persist across eval() calls by replaying
84/// history into a fresh env each time (safe, no Rc across threads).
85#[pyclass]
86pub struct RustySession {
87    history: Vec<String>,
88}
89
90#[pymethods]
91impl RustySession {
92    #[new]
93    fn new() -> Self { RustySession { history: Vec::new() } }
94
95    /// Eval code, preserving all previous definitions.
96    fn eval(&mut self, code: &str) -> PyResult<String> {
97        let env  = make_env();
98        let eval = Evaluator::new();
99        for prev in &self.history {
100            let _ = run_code(prev, &env, &eval);
101        }
102        match run_code(code, &env, &eval) {
103            Ok(v) => {
104                self.history.push(code.to_string());
105                Ok(print_repr(&v))
106            }
107            Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(e)),
108        }
109    }
110
111    /// Clear all session state.
112    fn reset(&mut self) { self.history.clear(); }
113
114    /// Number of expressions in history.
115    fn history_len(&self) -> usize { self.history.len() }
116
117    fn __repr__(&self) -> String {
118        format!("<RustySession history={}>", self.history.len())
119    }
120}
121
122// ── Module ────────────────────────────────────────────────────────────────
123
124#[pymodule]
125fn rusty(m: &Bound<'_, PyModule>) -> PyResult<()> {
126    m.add_class::<RustyInterp>()?;
127    m.add_class::<RustySession>()?;
128    m.add_function(wrap_pyfunction!(eval_fn, m)?)?;
129    Ok(())
130}
131
132/// Top-level rusty.eval("code") convenience function.
133#[pyfunction]
134#[pyo3(name = "eval")]
135fn eval_fn(code: &str) -> PyResult<String> {
136    let env = make_env();
137    match run_code(code, &env, &Evaluator::new()) {
138        Ok(v)  => Ok(print_repr(&v)),
139        Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(e)),
140    }
141}