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 resolve;
23mod env;
24mod eval;
25mod interp;
26mod arena;
27mod rust_jit;
28mod graph_ir;
29mod checkpoint;
30mod trace;
31mod type_check;
32mod effect_check;
33mod kg;
34
35use eval::Evaluator;
36use interp::{make_env, run_code, print_repr};
37
38// ── Stateless interpreter ─────────────────────────────────────────────────
39
40/// Stateless Rusty interpreter. Each eval() call gets a fresh environment.
41/// Fast for one-shot tool calls and expression evaluation.
42#[pyclass(name = "Rusty")]
43pub struct RustyInterp;
44
45#[pymethods]
46impl RustyInterp {
47    #[new]
48    fn new() -> Self { RustyInterp }
49
50    /// Evaluate Rusty/Lisp code. Returns result as string (unquoted).
51    fn eval(&self, code: &str) -> PyResult<String> {
52        let env = make_env();
53        match run_code(code, &env, &Evaluator::new()) {
54            Ok(v)  => Ok(print_repr(&v)),
55            Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(e)),
56        }
57    }
58
59    /// Evaluate and return the Lisp display form (strings stay quoted).
60    fn eval_repr(&self, code: &str) -> PyResult<String> {
61        let env = make_env();
62        match run_code(code, &env, &Evaluator::new()) {
63            Ok(v)  => Ok(format!("{}", v)),
64            Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(e)),
65        }
66    }
67
68    /// Returns True if the code is syntactically valid.
69    fn check(&self, code: &str) -> bool {
70        let tokens = lexer::Lexer::new(code).tokenize();
71        // It says "syntactically valid", so it has to mean it: unbalanced
72        // parens used to pass here.
73        match parser::Parser::new(tokens).parse_checked() {
74            Ok(ast) => !ast.is_empty(),
75            Err(_) => false,
76        }
77    }
78
79    fn __repr__(&self) -> &str { "<Rusty interpreter>" }
80}
81
82// ── Stateful session ──────────────────────────────────────────────────────
83
84/// Stateful session. Definitions persist across eval() calls by replaying
85/// history into a fresh env each time (safe, no Rc across threads).
86#[pyclass]
87pub struct RustySession {
88    history: Vec<String>,
89}
90
91#[pymethods]
92impl RustySession {
93    #[new]
94    fn new() -> Self { RustySession { history: Vec::new() } }
95
96    /// Eval code, preserving all previous definitions.
97    fn eval(&mut self, code: &str) -> PyResult<String> {
98        let env  = make_env();
99        let eval = Evaluator::new();
100        for prev in &self.history {
101            let _ = run_code(prev, &env, &eval);
102        }
103        match run_code(code, &env, &eval) {
104            Ok(v) => {
105                self.history.push(code.to_string());
106                Ok(print_repr(&v))
107            }
108            Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(e)),
109        }
110    }
111
112    /// Clear all session state.
113    fn reset(&mut self) { self.history.clear(); }
114
115    /// Number of expressions in history.
116    fn history_len(&self) -> usize { self.history.len() }
117
118    fn __repr__(&self) -> String {
119        format!("<RustySession history={}>", self.history.len())
120    }
121}
122
123// ── Module ────────────────────────────────────────────────────────────────
124
125#[pymodule]
126fn rusty(m: &Bound<'_, PyModule>) -> PyResult<()> {
127    m.add_class::<RustyInterp>()?;
128    m.add_class::<RustySession>()?;
129    m.add_function(wrap_pyfunction!(eval_fn, m)?)?;
130    Ok(())
131}
132
133/// Top-level rusty.eval("code") convenience function.
134#[pyfunction]
135#[pyo3(name = "eval")]
136fn eval_fn(code: &str) -> PyResult<String> {
137    let env = make_env();
138    match run_code(code, &env, &Evaluator::new()) {
139        Ok(v)  => Ok(print_repr(&v)),
140        Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(e)),
141    }
142}