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}