narju/repl/
mod.rs

1//! Top-level runner: parse → lower → evaluate.
2//!
3//! `TopLevel` is a thin wrapper that accumulates an environment and staging
4//! context. There is no `define`, no macro system, no prelude - the language
5//! is exactly λ↑↓ from the paper. All recursion goes through the lambda
6//! self-ref slot; all naming goes through `let`.
7
8pub mod commands;
9pub mod highlight;
10pub mod runner;
11
12// Re-export the runner entry points used by main.rs
13pub use runner::{eval_str, load_file, run_repl};
14
15use smol_str::SmolStr;
16
17use crate::core::{env_new, Env, Val};
18use crate::error::NarjuError;
19use crate::eval::{display, evalmsg, EvalCtx};
20use crate::io::terminal::TerminalIo;
21use crate::io::NarjuIo;
22use crate::parse::{lower, sug, Sexp};
23
24// ── TopLevel ──────────────────────────────────────────────────────────────────
25
26/// Accumulated evaluator state: environment vector and staging context.
27/// Exists purely as a convenience for multi-expression REPL sessions.
28pub struct TopLevel {
29    /// The base runtime environment. The floor has no top-level
30    /// `define`, so ordinary evaluation never extends this — every form
31    /// is closed, and only `ctx` carries state between forms. It exists
32    /// so an embedding can pre-seed bindings: slot `i` holds the value
33    /// of `names[i]`, and lowering resolves free names against `names`
34    /// positionally.
35    pub env: Env,
36    /// Surface names for the slots of `env`, in lockstep.
37    pub names: Vec<SmolStr>,
38    /// The staging context: fresh-variable counter, ANF block stack,
39    /// closure memo table, tower level, and the I/O implementation.
40    pub ctx: EvalCtx,
41}
42
43impl TopLevel {
44    /// Construct over a real terminal ([`TerminalIo`]).
45    ///
46    /// Panics when no terminal is available, so tests and other
47    /// non-interactive embeddings must use [`with_io`](Self::with_io)
48    /// with a [`HeadlessIo`](crate::io::headless::HeadlessIo) instead.
49    pub fn new() -> Self {
50        let io = TerminalIo::new().unwrap_or_else(|e| panic!("failed to init terminal: {e}"));
51        Self::with_io(Box::new(io))
52    }
53
54    /// Construct with an explicit I/O implementation.
55    ///
56    /// Use this in tests to avoid depending on a real terminal:
57    /// ```rust
58    /// use narju::io::headless::HeadlessIo;
59    /// use narju::repl::TopLevel;
60    ///
61    /// let mut top = TopLevel::with_io(Box::new(HeadlessIo::empty()));
62    /// assert_eq!(top.eval_int("(+ 1 2)"), 3);
63    /// ```
64    pub fn with_io(io: Box<dyn NarjuIo>) -> Self {
65        Self {
66            env: env_new(),
67            names: Vec::new(),
68            ctx: EvalCtx::new(io),
69        }
70    }
71
72    /// Evaluate a single expression in the current environment.
73    pub fn eval_exp(&mut self, sexp: &Sexp) -> Result<Val, NarjuError> {
74        let sexp = sug(sexp);
75        let exp = lower(&sexp, &mut self.names).map_err(NarjuError::Parse)?;
76        evalmsg(&self.env, exp, &mut self.ctx)
77    }
78
79    /// Parse and evaluate all forms in `src`, returning each result.
80    pub fn run_str(&mut self, src: &str) -> Result<Vec<RunResult>, NarjuError> {
81        let mut results = Vec::new();
82        for form in parse_all(src)? {
83            if let Some(r) = run_form(self, &form)? {
84                results.push(r);
85            }
86        }
87        Ok(results)
88    }
89
90    /// Parse and evaluate a file.
91    pub fn run_file(&mut self, path: &str) -> Result<Vec<RunResult>, NarjuError> {
92        let src = crate::io::read_source(path).map_err(NarjuError::Io)?;
93        self.run_str(&src)
94    }
95
96    // ── Test helpers ──────────────────────────────────────────────────────────
97
98    /// Evaluate a source string, panicking on error. For tests only.
99    pub fn eval(&mut self, src: &str) -> Val {
100        let forms = parse_all(src).expect("parse error");
101        let form = forms.into_iter().next().expect("empty input");
102        self.eval_exp(&form).expect("eval error")
103    }
104
105    /// Evaluate and unwrap as `i64`, panicking on type mismatch. For tests only.
106    pub fn eval_int(&mut self, src: &str) -> i64 {
107        match self.eval(src) {
108            Val::Cst(n) => n,
109            v => panic!("expected Cst, got: {}", display(&v)),
110        }
111    }
112}
113
114impl Default for TopLevel {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120// ── RunResult ─────────────────────────────────────────────────────────────────
121
122/// The outcome of evaluating one top-level form.
123#[derive(Debug)]
124pub struct RunResult {
125    /// The value the form evaluated to.
126    pub val: Val,
127}
128
129impl RunResult {
130    /// Render the value the way the REPL prints it.
131    pub fn display(&self) -> String {
132        display(&self.val)
133    }
134}
135
136// ── Parsing helpers ───────────────────────────────────────────────────────────
137
138/// Read every form in `src`, wrapping reader failures in
139/// [`NarjuError::Parse`]. A thin shim over [`crate::parse::parse_all`],
140/// whose error type is a bare `String`.
141pub fn parse_all(src: &str) -> Result<Vec<Sexp>, NarjuError> {
142    crate::parse::parse_all(src).map_err(NarjuError::Parse)
143}
144
145/// Execute a single pre-parsed form, evaluating it in `top`'s current env.
146/// Every form is an expression — there are no top-level special forms.
147pub fn run_form(top: &mut TopLevel, sexp: &Sexp) -> Result<Option<RunResult>, NarjuError> {
148    let val = top.eval_exp(sexp)?;
149    Ok(Some(RunResult { val }))
150}