bop/evaluator.rs
1#[cfg(feature = "no_std")]
2use alloc::{format, string::{String, ToString}, vec, vec::Vec};
3
4use alloc_import::collections::BTreeMap;
5
6#[cfg(not(feature = "no_std"))]
7use std as alloc_import;
8#[cfg(feature = "no_std")]
9use alloc as alloc_import;
10
11#[cfg(feature = "no_std")]
12use alloc::rc::Rc;
13
14#[cfg(not(feature = "no_std"))]
15use std::rc::Rc;
16
17use core::cell::RefCell;
18
19use crate::builtins::{
20 self, error, error_at, error_fatal_with_hint, error_with_hint, error_with_hint_at,
21};
22use crate::error::BopError;
23use crate::lexer::StringPart;
24use crate::methods;
25use crate::ops;
26use crate::parser::*;
27use crate::value::{BopFn, EnumPayload, FnBody, Value};
28use crate::{BopHost, BopLimits};
29
30/// What the tree-walker stores for each imported module once it
31/// has been loaded. Cached by dot-joined path so the same module
32/// imported twice in one `run` only evaluates once.
33///
34/// `Loading` is the in-progress sentinel; if an import request
35/// sees a module already in this state it's a circular import and
36/// halts with a clear error.
37enum ImportSlot {
38 Loading,
39 Loaded(ModuleBindings),
40}
41
42#[derive(Clone)]
43struct ModuleBindings {
44 /// `(name, value)` pairs for every top-level `let` in the
45 /// module (fns are handled separately — they also need to
46 /// land in `self.functions` so cross-fn calls within the
47 /// module resolve).
48 bindings: Vec<(String, Value)>,
49 /// Top-level `fn` declarations, keyed by name. The importer
50 /// registers each both in its `self.functions` table
51 /// (for nested call resolution) and as a scope binding
52 /// (so the fn is also usable as a first-class value).
53 fn_decls: Vec<(String, FnDef)>,
54 /// Struct type declarations the module introduces, already
55 /// qualified with their full identity `(module_path,
56 /// type_name)`. The importer copies these into its own
57 /// `struct_defs` without rewriting keys, so type identity
58 /// stays pinned to the declaring module across the import
59 /// boundary.
60 struct_defs: Vec<((String, String), Vec<String>)>,
61 /// Enum type declarations, qualified the same way as
62 /// `struct_defs` above.
63 enum_defs: Vec<((String, String), Vec<crate::parser::VariantDecl>)>,
64 /// User methods the module declared, keyed by the *full
65 /// type identity* of the receiver. `((module_path,
66 /// type_name), method_name, fn_def)` — the importer merges
67 /// these directly into its own `methods` table.
68 methods: Vec<((String, String), String, FnDef)>,
69}
70
71type ImportCache = Rc<RefCell<alloc_import::collections::BTreeMap<String, ImportSlot>>>;
72
73const MAX_CALL_DEPTH: usize = 64;
74
75// `try` unwinds via `BopError::try_return_signal` — a proper
76// sentinel with `is_try_return = true` rather than the older
77// magic-string approach. The value travels on
78// `Evaluator::pending_try_return` so the `BopError` itself
79// doesn't need to carry a `Value` (which would introduce a
80// module cycle between `bop::error` and `bop::value`).
81
82// ─── Control flow signals ──────────────────────────────────────────────────
83
84enum Signal {
85 None,
86 Break,
87 Continue,
88 Return(Value),
89}
90
91#[derive(Clone)]
92struct FnDef {
93 params: Vec<String>,
94 body: Vec<Stmt>,
95}
96
97// ─── Evaluator ─────────────────────────────────────────────────────────────
98
99pub struct Evaluator<'h, H: BopHost> {
100 scopes: Vec<BTreeMap<String, Value>>,
101 functions: BTreeMap<String, FnDef>,
102 /// Module this evaluator is running. Used to tag newly
103 /// declared types with the module they live in, so two
104 /// modules that declare `struct Color { ... }` produce
105 /// distinct types rather than colliding by name. `<root>`
106 /// for the top-level program, the dot-joined module path
107 /// for a sub-evaluator loading a `use`'d module.
108 current_module: String,
109 /// User-defined struct types, keyed by their full identity
110 /// `(module_path, type_name)` — the same pair the runtime
111 /// values carry. Two modules declaring the same struct name
112 /// coexist at different keys with independently-validated
113 /// field lists.
114 struct_defs: BTreeMap<(String, String), Vec<String>>,
115 /// User-defined enum types, same `(module_path, type_name)`
116 /// keying scheme as [`Self::struct_defs`].
117 enum_defs: BTreeMap<(String, String), Vec<VariantDecl>>,
118 /// User-defined methods. Outer key is the *full type
119 /// identity* `(module_path, type_name)`; inner key is the
120 /// method name. Methods receive the receiver as their first
121 /// parameter (conventionally `self`). Dispatch looks up the
122 /// receiver's own `(module_path, type_name)` so a method
123 /// declared for `paint.Color` isn't accidentally called on
124 /// `other.Color`.
125 methods: BTreeMap<(String, String), BTreeMap<String, FnDef>>,
126 /// Per-scope bare-name → module_path bindings for types
127 /// *and* module aliases. Parallels `scopes`: a declared
128 /// type or an aliased `use` binds `name → module_path` in
129 /// the current scope. Bare-name lookup (`Color::Red`,
130 /// `m.Color::Red`) walks this stack inside-out. `<builtin>`
131 /// types (`Result`, `RuntimeError`) are seeded into scope
132 /// 0 at evaluator construction so they're visible
133 /// everywhere.
134 ///
135 /// Unlike `scopes`, this stack is *preserved* across
136 /// function calls — type identity follows lexical scoping
137 /// at the module level, so a `fn` declared inside the root
138 /// program can still match patterns against types + aliases
139 /// that were visible at its declaration site. Function
140 /// bodies push a fresh frame on entry so any locally-
141 /// declared types are discarded on return; module-scope
142 /// entries below stay put.
143 type_bindings: Vec<BTreeMap<String, String>>,
144 /// Module-level aliases `m → Rc<BopModule>`, populated by
145 /// aliased `use` statements. Separate from `scopes` so
146 /// aliases remain reachable from inside function bodies
147 /// (where `self.scopes` is fresh per call). Field access /
148 /// method dispatch on `m.foo` falls back to this map when
149 /// `m` isn't a local binding.
150 module_aliases: BTreeMap<String, Rc<crate::value::BopModule>>,
151 host: &'h mut H,
152 steps: u64,
153 call_depth: usize,
154 limits: BopLimits,
155 rand_state: u64,
156 /// Shared across nested evaluators so recursive imports see
157 /// the same cache — every sub-evaluator inherits the parent's
158 /// `Rc` clone in `new_nested`.
159 imports: ImportCache,
160 /// Paths already injected into *this* evaluator's scope (not
161 /// shared with sub-evaluators). Re-importing the same path at
162 /// the same level is a no-op — matches Python's `import x;
163 /// import x` behaviour.
164 imported_here: alloc_import::collections::BTreeSet<String>,
165 /// Set by `try` when it sees an `Err(...)` variant and wants
166 /// the enclosing fn to early-return with that value. The
167 /// expression that raised stuffs the value here and returns
168 /// a sentinel `BopError`; the fn-call wrapper detects the
169 /// sentinel, takes the value, and converts it to
170 /// `Signal::Return`. Always `None` outside the narrow
171 /// unwinding window.
172 pending_try_return: Option<Value>,
173 /// Non-fatal runtime warnings accumulated during execution —
174 /// currently only `use`-time name-shadowing events (glob
175 /// imports bringing in a name that's already bound). Read
176 /// after `run()` returns via [`Self::take_warnings`].
177 runtime_warnings: Vec<crate::error::BopWarning>,
178}
179
180impl<'h, H: BopHost> Evaluator<'h, H> {
181 pub fn new(host: &'h mut H, limits: BopLimits) -> Self {
182 crate::memory::bop_memory_init(limits.max_memory);
183 let (struct_defs, enum_defs, builtin_bindings) = seed_builtin_types();
184 Self {
185 scopes: vec![BTreeMap::new()],
186 functions: BTreeMap::new(),
187 current_module: String::from(crate::value::ROOT_MODULE_PATH),
188 struct_defs,
189 enum_defs,
190 methods: BTreeMap::new(),
191 type_bindings: vec![builtin_bindings],
192 module_aliases: BTreeMap::new(),
193 host,
194 steps: 0,
195 call_depth: 0,
196 limits,
197 rand_state: 0,
198 imports: Rc::new(RefCell::new(alloc_import::collections::BTreeMap::new())),
199 imported_here: alloc_import::collections::BTreeSet::new(),
200 pending_try_return: None,
201 runtime_warnings: Vec::new(),
202 }
203 }
204
205 /// Build a sub-evaluator for loading a module — inherits the
206 /// parent's import cache, memory ceiling, and step budget, but
207 /// runs with a fresh scope stack so module code can't see the
208 /// importer's locals. `module_path` is the dot-joined name the
209 /// module is being loaded under; types it declares tag their
210 /// runtime values with this path.
211 fn new_for_module(
212 host: &'h mut H,
213 limits: BopLimits,
214 imports: ImportCache,
215 module_path: String,
216 ) -> Self {
217 let (struct_defs, enum_defs, builtin_bindings) = seed_builtin_types();
218 Self {
219 scopes: vec![BTreeMap::new()],
220 functions: BTreeMap::new(),
221 current_module: module_path,
222 struct_defs,
223 enum_defs,
224 methods: BTreeMap::new(),
225 type_bindings: vec![builtin_bindings],
226 module_aliases: BTreeMap::new(),
227 host,
228 steps: 0,
229 call_depth: 0,
230 limits,
231 rand_state: 0,
232 imports,
233 imported_here: alloc_import::collections::BTreeSet::new(),
234 pending_try_return: None,
235 runtime_warnings: Vec::new(),
236 }
237 }
238
239 pub fn run(mut self, stmts: &[Stmt]) -> Result<(), BopError> {
240 let result = self.exec_block(stmts);
241 #[cfg(not(feature = "no_std"))]
242 {
243 // Surface any runtime warnings accumulated during the
244 // run (currently: glob-import shadowing). They land on
245 // stderr with the standard `warning:` prefix so
246 // terminal users see them — no public API change
247 // needed. Embedders that want structured access can
248 // use `run_with_warnings` (future) or implement their
249 // own evaluation loop.
250 for w in &self.runtime_warnings {
251 eprintln!("warning: {}", w.message);
252 }
253 }
254 match result? {
255 Signal::Break => {
256 return Err(error(0, "break used outside of a loop"));
257 }
258 Signal::Continue => {
259 return Err(error(0, "continue used outside of a loop"));
260 }
261 _ => {}
262 }
263 Ok(())
264 }
265
266 fn tick(&mut self, line: u32) -> Result<(), BopError> {
267 self.steps += 1;
268 if self.steps > self.limits.max_steps {
269 // Fatal — `try_call` must not swallow this, or the
270 // sandbox invariant breaks.
271 return Err(error_fatal_with_hint(
272 line,
273 "Your code took too many steps (possible infinite loop)",
274 "Check your loops — make sure they have a condition that eventually stops them.",
275 ));
276 }
277 if crate::memory::bop_memory_exceeded() {
278 return Err(error_fatal_with_hint(
279 line,
280 "Memory limit exceeded",
281 "Your code is using too much memory. Check for large strings or arrays growing in loops.",
282 ));
283 }
284 self.host.on_tick()?;
285 Ok(())
286 }
287
288 // ─── Scope ─────────────────────────────────────────────────────
289
290 fn push_scope(&mut self) {
291 self.scopes.push(BTreeMap::new());
292 // Type bindings parallel the value scopes — same push /
293 // pop rhythm keeps `use`-injected type names stack-scoped.
294 self.type_bindings.push(BTreeMap::new());
295 }
296
297 fn pop_scope(&mut self) {
298 self.scopes.pop();
299 self.type_bindings.pop();
300 }
301
302 /// Resolve a type reference to the module it was declared
303 /// in. `namespace` is the explicit qualifier (from
304 /// `m.Color::Red`) or `None` for bare names. For bare names
305 /// the scope stack is walked inside-out; for namespaced
306 /// references the alias is resolved via
307 /// `validate_namespaced_type` and its backing module path
308 /// returned. Returns `None` when no matching type is visible.
309 fn resolve_type_ref(&self, namespace: Option<&str>, type_name: &str) -> Option<String> {
310 resolve_type_in(
311 &self.scopes,
312 &self.type_bindings,
313 &self.module_aliases,
314 namespace,
315 type_name,
316 )
317 }
318
319 /// Record a type declaration in the current module.
320 /// Registers `(current_module, name)` in the appropriate
321 /// table and binds the bare name in the current scope so
322 /// subsequent references resolve to *this* module's version.
323 /// Same-shape redeclarations in the same module are a no-op;
324 /// different-shape ones error.
325 fn bind_local_type(&mut self, name: &str) {
326 if let Some(top) = self.type_bindings.last_mut() {
327 top.insert(name.to_string(), self.current_module.clone());
328 }
329 }
330
331 fn define(&mut self, name: String, value: Value) {
332 if let Some(scope) = self.scopes.last_mut() {
333 scope.insert(name, value);
334 }
335 }
336
337 fn get_var(&self, name: &str) -> Option<&Value> {
338 for scope in self.scopes.iter().rev() {
339 if let Some(val) = scope.get(name) {
340 return Some(val);
341 }
342 }
343 None
344 }
345
346 fn set_var(&mut self, name: &str, value: Value) -> bool {
347 for scope in self.scopes.iter_mut().rev() {
348 if scope.contains_key(name) {
349 scope.insert(name.to_string(), value);
350 return true;
351 }
352 }
353 false
354 }
355
356 // ─── "Did you mean?" candidate collectors ─────────────────
357 //
358 // Every name the user could reasonably have meant, gathered
359 // into a single list so `bop::suggest::did_you_mean` picks
360 // the closest match. Separate methods for the "ident used
361 // as a value" and "ident called as a function" paths since
362 // the reachable sets differ (builtins are callable but
363 // aren't scope values).
364
365 /// Names reachable when an identifier is used as a value:
366 /// any local from the enclosing scopes plus any top-level
367 /// fn declaration (fns are first-class — `let g = some_fn`
368 /// works, so they count as value-like).
369 fn value_candidates_hint(&self, target: &str) -> Option<String> {
370 let mut candidates: Vec<String> = Vec::new();
371 for scope in &self.scopes {
372 for k in scope.keys() {
373 candidates.push(k.clone());
374 }
375 }
376 for name in self.functions.keys() {
377 candidates.push(name.clone());
378 }
379 crate::suggest::did_you_mean(target, candidates)
380 }
381
382 /// Names reachable in a call position: user fns, core
383 /// builtins, plus `try_call`. Host builtins stay with the
384 /// host's own `function_hint()` path — embedders often want
385 /// to list theirs differently.
386 fn callable_candidates_hint(&self, target: &str) -> Option<String> {
387 let mut candidates: Vec<String> =
388 self.functions.keys().cloned().collect();
389 for builtin in crate::suggest::CORE_CALLABLE_BUILTINS {
390 candidates.push((*builtin).to_string());
391 }
392 crate::suggest::did_you_mean(target, candidates)
393 }
394
395 // ─── Statements ────────────────────────────────────────────────
396
397 fn exec_block(&mut self, stmts: &[Stmt]) -> Result<Signal, BopError> {
398 for stmt in stmts {
399 let signal = self.exec_stmt(stmt)?;
400 match signal {
401 Signal::None => {}
402 other => return Ok(other),
403 }
404 }
405 Ok(Signal::None)
406 }
407
408 fn exec_stmt(&mut self, stmt: &Stmt) -> Result<Signal, BopError> {
409 self.tick(stmt.line)?;
410
411 match &stmt.kind {
412 StmtKind::Let { name, value, is_const: _ } => {
413 let val = self.eval_expr(value)?;
414 self.define(name.clone(), val);
415 Ok(Signal::None)
416 }
417
418 StmtKind::Assign { target, op, value } => {
419 let new_val = self.eval_expr(value)?;
420 self.exec_assign(target, op, new_val, stmt.line)?;
421 Ok(Signal::None)
422 }
423
424 StmtKind::If {
425 condition,
426 body,
427 else_ifs,
428 else_body,
429 } => {
430 if self.eval_expr(condition)?.is_truthy() {
431 self.push_scope();
432 let sig = self.exec_block(body)?;
433 self.pop_scope();
434 return Ok(sig);
435 }
436 for (elif_cond, elif_body) in else_ifs {
437 if self.eval_expr(elif_cond)?.is_truthy() {
438 self.push_scope();
439 let sig = self.exec_block(elif_body)?;
440 self.pop_scope();
441 return Ok(sig);
442 }
443 }
444 if let Some(else_body) = else_body {
445 self.push_scope();
446 let sig = self.exec_block(else_body)?;
447 self.pop_scope();
448 return Ok(sig);
449 }
450 Ok(Signal::None)
451 }
452
453 StmtKind::While { condition, body } => {
454 loop {
455 self.tick(stmt.line)?;
456 if !self.eval_expr(condition)?.is_truthy() {
457 break;
458 }
459 self.push_scope();
460 let sig = self.exec_block(body)?;
461 self.pop_scope();
462 match sig {
463 Signal::Break => break,
464 Signal::Continue => continue,
465 Signal::Return(v) => return Ok(Signal::Return(v)),
466 Signal::None => {}
467 }
468 }
469 Ok(Signal::None)
470 }
471
472 StmtKind::Repeat { count, body } => {
473 let n = match self.eval_expr(count)? {
474 Value::Int(n) => n,
475 Value::Number(n) => n as i64,
476 other => {
477 return Err(error(
478 stmt.line,
479 format!("repeat needs a number, but got {}", other.type_name()),
480 ));
481 }
482 };
483 for _ in 0..n.max(0) {
484 self.tick(stmt.line)?;
485 self.push_scope();
486 let sig = self.exec_block(body)?;
487 self.pop_scope();
488 match sig {
489 Signal::Break => break,
490 Signal::Continue => continue,
491 Signal::Return(v) => return Ok(Signal::Return(v)),
492 Signal::None => {}
493 }
494 }
495 Ok(Signal::None)
496 }
497
498 StmtKind::ForIn {
499 var,
500 iterable,
501 body,
502 } => {
503 let val = self.eval_expr(iterable)?;
504 // Fast path for the three built-in iterables:
505 // materialise up-front and loop over the Vec
506 // directly, skipping the method-dispatch cost of
507 // the iterator protocol. Semantically identical
508 // to `for x in v.iter()` for these types.
509 if matches!(
510 &val,
511 Value::Array(_) | Value::Str(_) | Value::Dict(_)
512 ) {
513 let mut val = val;
514 let items: Vec<Value> = match &mut val {
515 Value::Array(arr) => arr.take(),
516 Value::Str(s) => s
517 .chars()
518 .map(|c| Value::new_str(c.to_string()))
519 .collect(),
520 Value::Dict(d) => d
521 .iter()
522 .map(|(k, _)| Value::new_str(k.clone()))
523 .collect(),
524 _ => unreachable!(),
525 };
526 for item in items {
527 self.tick(stmt.line)?;
528 self.push_scope();
529 self.define(var.clone(), item);
530 let sig = self.exec_block(body)?;
531 self.pop_scope();
532 match sig {
533 Signal::Break => break,
534 Signal::Continue => continue,
535 Signal::Return(v) => return Ok(Signal::Return(v)),
536 Signal::None => {}
537 }
538 }
539 return Ok(Signal::None);
540 }
541 // Protocol path: anything else must either be an
542 // iterator already (Value::Iter, or a user value
543 // with a `.next()` method that returns
544 // `Iter::Next/Done`) or an iterable — a value
545 // whose `.iter()` method returns an iterator.
546 // Primitives and callables that don't fit get a
547 // clean "can't iterate over X" error instead of
548 // a raw "no such method" surface from the
549 // dispatcher below.
550 let iterator = match &val {
551 Value::Iter(_) | Value::Struct(_) | Value::EnumVariant(_) => {
552 // Ask the value for an iterator. User
553 // structs typically implement `iter` to
554 // return either a built-in iterator or
555 // themselves.
556 self.call_method_full(&val, "iter", Vec::new(), stmt.line)?
557 }
558 other => {
559 return Err(error(
560 stmt.line,
561 crate::error_messages::cant_iterate_over(
562 other.type_name(),
563 ),
564 ));
565 }
566 };
567 loop {
568 self.tick(stmt.line)?;
569 let next_val =
570 self.call_method_full(&iterator, "next", Vec::new(), stmt.line)?;
571 let item = match unwrap_iter_result(&next_val) {
572 Some(IterStep::Next(v)) => v,
573 Some(IterStep::Done) => break,
574 None => {
575 return Err(error(
576 stmt.line,
577 format!(
578 "`.next()` on a `for` iterator must return `Iter::Next(v)` or `Iter::Done`, got {}",
579 next_val.inspect()
580 ),
581 ));
582 }
583 };
584 self.push_scope();
585 self.define(var.clone(), item);
586 let sig = self.exec_block(body)?;
587 self.pop_scope();
588 match sig {
589 Signal::Break => break,
590 Signal::Continue => continue,
591 Signal::Return(v) => return Ok(Signal::Return(v)),
592 Signal::None => {}
593 }
594 }
595 Ok(Signal::None)
596 }
597
598 StmtKind::FnDecl { name, params, body } => {
599 self.functions.insert(
600 name.clone(),
601 FnDef {
602 params: params.clone(),
603 body: body.clone(),
604 },
605 );
606 Ok(Signal::None)
607 }
608
609 StmtKind::MethodDecl {
610 type_name,
611 method_name,
612 params,
613 body,
614 } => {
615 // Methods belong to a *specific* type identity —
616 // `fn Color.area(self)` inside module `paint`
617 // registers under `(paint, Color)` and doesn't
618 // leak to `other.Color`. If the named type isn't
619 // declared in this module, that's an error at
620 // parse/emit consistency — fall back to the
621 // current module as the owner.
622 let type_key = (self.current_module.clone(), type_name.clone());
623 self.methods
624 .entry(type_key)
625 .or_default()
626 .insert(
627 method_name.clone(),
628 FnDef {
629 params: params.clone(),
630 body: body.clone(),
631 },
632 );
633 Ok(Signal::None)
634 }
635
636 StmtKind::Return { value } => {
637 let val = match value {
638 Some(expr) => self.eval_expr(expr)?,
639 None => Value::None,
640 };
641 Ok(Signal::Return(val))
642 }
643
644 StmtKind::Break => Ok(Signal::Break),
645 StmtKind::Continue => Ok(Signal::Continue),
646
647 StmtKind::Use { path, items, alias } => {
648 self.exec_import(
649 path,
650 items.as_deref(),
651 alias.as_deref(),
652 stmt.line,
653 )?;
654 Ok(Signal::None)
655 }
656
657 StmtKind::StructDecl { name, fields } => {
658 // Reject duplicate field names at decl time so
659 // downstream code doesn't have to re-check. Walker,
660 // VM, and AOT all assume unique fields per struct.
661 let mut seen = alloc_import::collections::BTreeSet::new();
662 for f in fields {
663 if !seen.insert(f.clone()) {
664 return Err(error(
665 stmt.line,
666 format!("Struct `{}` has duplicate field `{}`", name, f),
667 ));
668 }
669 }
670 // Type identity is `(current_module, name)` —
671 // two different modules declaring the same name
672 // coexist because their keys differ. A redecl
673 // *in the same module* is a no-op when the shape
674 // matches (mirrors the same-module idempotency
675 // rule for re-imports) and a clash otherwise.
676 let key = (self.current_module.clone(), name.clone());
677 if let Some(existing) = self.struct_defs.get(&key) {
678 if existing == fields {
679 self.bind_local_type(name);
680 return Ok(Signal::None);
681 }
682 return Err(error(
683 stmt.line,
684 format!("Struct `{}` is already declared", name),
685 ));
686 }
687 self.struct_defs.insert(key, fields.clone());
688 self.bind_local_type(name);
689 Ok(Signal::None)
690 }
691
692 StmtKind::EnumDecl { name, variants } => {
693 let key = (self.current_module.clone(), name.clone());
694 if let Some(existing) = self.enum_defs.get(&key) {
695 // Same rule as `StructDecl` above — a
696 // same-module same-shape redeclaration is a
697 // no-op, anything else is a clash.
698 if variants_equivalent(existing, variants) {
699 self.bind_local_type(name);
700 return Ok(Signal::None);
701 }
702 return Err(error(
703 stmt.line,
704 format!("Enum `{}` is already declared", name),
705 ));
706 }
707 let mut seen_variants = alloc_import::collections::BTreeSet::new();
708 for v in variants {
709 if !seen_variants.insert(v.name.clone()) {
710 return Err(error(
711 stmt.line,
712 format!(
713 "Enum `{}` has duplicate variant `{}`",
714 name, v.name
715 ),
716 ));
717 }
718 if let VariantKind::Struct(fields) | VariantKind::Tuple(fields) =
719 &v.kind
720 {
721 let mut seen_fields = alloc_import::collections::BTreeSet::new();
722 for f in fields {
723 if !seen_fields.insert(f.clone()) {
724 return Err(error(
725 stmt.line,
726 format!(
727 "Enum variant `{}::{}` has duplicate field `{}`",
728 name, v.name, f
729 ),
730 ));
731 }
732 }
733 }
734 }
735 self.enum_defs.insert(key, variants.clone());
736 self.bind_local_type(name);
737 Ok(Signal::None)
738 }
739
740 StmtKind::ExprStmt(expr) => {
741 self.eval_expr(expr)?;
742 Ok(Signal::None)
743 }
744 }
745 }
746
747 /// Execute a `use` statement.
748 ///
749 /// Four shapes are dispatched from the parser:
750 ///
751 /// - `use foo` — glob: inject all public
752 /// (non-`_`-prefixed) exports into the caller's scope.
753 /// Name collisions emit a `BopWarning` and the first
754 /// binding wins (already-present beats newcomer).
755 /// - `use foo.{a, b}` — selective: inject only the
756 /// listed names. Missing names raise a clear error. Names
757 /// that start with `_` are accepted when listed
758 /// explicitly (the selective form is how you opt-in to
759 /// private bindings).
760 /// - `use foo as m` — aliased: every export
761 /// (including `_`-prefixed) hangs off a `Value::Module`
762 /// bound as `m`. Access via `m.binding` or
763 /// `m.Type { ... }` / `m.Type::Variant(...)`.
764 /// - `use foo.{a, b} as m` — selective + aliased: same
765 /// filtering, and the resulting module only exposes the
766 /// listed names.
767 ///
768 /// Struct / enum / method declarations from the imported
769 /// module still register globally on every form — types
770 /// are first-come-first-served across the engine. Conflicts
771 /// there produce the same "clashes with existing" error as
772 /// before. (Qualified type names that genuinely disambiguate
773 /// same-named types across modules are a future extension.)
774 fn exec_import(
775 &mut self,
776 path: &str,
777 items: Option<&[String]>,
778 alias: Option<&str>,
779 line: u32,
780 ) -> Result<(), BopError> {
781 // Idempotent at the glob injection site: re-importing a
782 // module already applied is a no-op. Aliased / selective
783 // forms don't enter this cache — they always run, because
784 // the same module imported with different shapes can
785 // legitimately produce different scope effects.
786 let is_plain_glob = items.is_none() && alias.is_none();
787 if is_plain_glob && self.imported_here.contains(path) {
788 return Ok(());
789 }
790
791 let bindings = self.load_module(path, line)?;
792
793 // Types always register under their *full identity*
794 // `(module_path, type_name)`. That means two modules
795 // declaring `struct Color { ... }` with different fields
796 // coexist at different registry keys — no clash. Same-
797 // identity reinsertion is a no-op; same key + different
798 // shape would mean the same module got loaded twice with
799 // a different source, which we treat as a hard error.
800 for (key, fields) in &bindings.struct_defs {
801 if let Some(existing) = self.struct_defs.get(key) {
802 if existing == fields {
803 continue;
804 }
805 return Err(error(
806 line,
807 format!(
808 "Type `{}` from `{}` reloaded with different fields",
809 key.1, key.0
810 ),
811 ));
812 }
813 self.struct_defs.insert(key.clone(), fields.clone());
814 }
815 for (key, variants) in &bindings.enum_defs {
816 if let Some(existing) = self.enum_defs.get(key) {
817 if variants_equivalent(existing, variants) {
818 continue;
819 }
820 return Err(error(
821 line,
822 format!(
823 "Type `{}` from `{}` reloaded with different variants",
824 key.1, key.0
825 ),
826 ));
827 }
828 self.enum_defs.insert(key.clone(), variants.clone());
829 }
830 for (type_key, method_name, fn_def) in &bindings.methods {
831 let slot = self.methods.entry(type_key.clone()).or_default();
832 slot.insert(method_name.clone(), fn_def.clone());
833 }
834
835 // Figure out which name-value pairs to surface based on
836 // the (items, alias) combination. Fn declarations and
837 // plain `let` bindings are threaded together so the
838 // caller-visible order matches the module's declaration
839 // order.
840 let mut exports: Vec<(String, Value)> =
841 Vec::with_capacity(bindings.fn_decls.len() + bindings.bindings.len());
842 let mut fn_entries: Vec<(String, FnDef)> =
843 Vec::with_capacity(bindings.fn_decls.len());
844 for (name, fn_def) in &bindings.fn_decls {
845 let value = Value::new_fn(
846 fn_def.params.clone(),
847 Vec::new(),
848 fn_def.body.clone(),
849 Some(name.clone()),
850 );
851 exports.push((name.clone(), value));
852 fn_entries.push((name.clone(), fn_def.clone()));
853 }
854 for (name, value) in &bindings.bindings {
855 exports.push((name.clone(), value.clone()));
856 }
857
858 // Selective filter: restrict to the listed names.
859 // Missing names error loudly — a silent skip would make
860 // typos hard to spot.
861 if let Some(list) = items {
862 let available: alloc_import::collections::BTreeSet<&str> =
863 exports.iter().map(|(k, _)| k.as_str()).collect();
864 for wanted in list {
865 if !available.contains(wanted.as_str())
866 && !bindings
867 .struct_defs
868 .iter()
869 .any(|((_, n), _)| n == wanted)
870 && !bindings
871 .enum_defs
872 .iter()
873 .any(|((_, n), _)| n == wanted)
874 {
875 return Err(error(
876 line,
877 format!(
878 "`{}` isn't exported from `{}` (selective import)",
879 wanted, path
880 ),
881 ));
882 }
883 }
884 let listed: alloc_import::collections::BTreeSet<String> =
885 list.iter().cloned().collect();
886 exports.retain(|(k, _)| listed.contains(k));
887 fn_entries.retain(|(k, _)| listed.contains(k));
888 }
889
890 // Figure out which of the module's types the caller
891 // should see *by bare name*. The selective form picks
892 // exactly the listed names; the glob form takes
893 // everything public; the aliased form never binds bare
894 // names (the alias is the only way in).
895 let module_type_names: Vec<String> = bindings
896 .struct_defs
897 .iter()
898 .map(|((_, n), _)| n.clone())
899 .chain(bindings.enum_defs.iter().map(|((_, n), _)| n.clone()))
900 .collect();
901 let exposed_types: Vec<String> = match items {
902 Some(list) => module_type_names
903 .into_iter()
904 .filter(|n| list.iter().any(|i| i == n))
905 .collect(),
906 None => module_type_names
907 .into_iter()
908 .filter(|n| !crate::naming::is_private(n))
909 .collect(),
910 };
911
912 if let Some(alias_name) = alias {
913 // Aliased form: build a `Value::Module` and bind it
914 // under the alias. The module carries its bindings
915 // (let + fn) and the names of declared types so
916 // namespaced constructors like `m.Entity { ... }`
917 // can verify they're reaching for something the
918 // module actually exports.
919 if self
920 .scopes
921 .last()
922 .map(|s| s.contains_key(alias_name))
923 .unwrap_or(false)
924 || self.functions.contains_key(alias_name)
925 {
926 return Err(error(
927 line,
928 format!(
929 "`{}` is already bound — can't use it as a module alias",
930 alias_name
931 ),
932 ));
933 }
934 // Fn entries imported via alias also register in
935 // `self.functions` so `m.foo()` (which lowers to
936 // `Value::Fn` lookup in the module) and `foo()`
937 // (bare call, never reaches the alias) stay
938 // consistent when the module itself has sibling fn
939 // calls inside its own body. Selective + alias:
940 // only the listed fns register.
941 for (name, fn_def) in fn_entries {
942 if !self.functions.contains_key(&name) {
943 self.functions.insert(name, fn_def);
944 }
945 }
946 let module_rc = Rc::new(crate::value::BopModule {
947 path: path.to_string(),
948 bindings: exports,
949 types: exposed_types,
950 });
951 // Bind the alias three ways:
952 // 1. as a Value::Module in the current value
953 // scope (for `m.helper(x)` style calls that
954 // happen directly at the callsite);
955 // 2. in `module_aliases` so it survives the
956 // fresh-scope reset at function boundaries and
957 // stays reachable for field access inside fns;
958 // 3. in `type_bindings` so patterns + construction
959 // can resolve `m.Type` inside function bodies
960 // without needing the Value::Module in value
961 // scope.
962 self.define(
963 alias_name.to_string(),
964 Value::Module(Rc::clone(&module_rc)),
965 );
966 self.module_aliases
967 .insert(alias_name.to_string(), Rc::clone(&module_rc));
968 if let Some(scope) = self.type_bindings.last_mut() {
969 scope.insert(alias_name.to_string(), path.to_string());
970 }
971 } else {
972 // Glob / selective without alias — flat injection.
973 // Privacy: glob-only drops `_`-prefixed names.
974 // Selective lets the user reach into private
975 // bindings explicitly.
976 let skip_private = items.is_none();
977 for (name, fn_def) in fn_entries {
978 if skip_private && crate::naming::is_private(&name) {
979 continue;
980 }
981 if self
982 .scopes
983 .last()
984 .map(|s| s.contains_key(&name))
985 .unwrap_or(false)
986 || self.functions.contains_key(&name)
987 {
988 self.runtime_warnings.push(crate::error::BopWarning::at(
989 format!(
990 "`{}` from `{}` shadowed by an existing binding — the first definition wins",
991 name, path
992 ),
993 line,
994 ));
995 continue;
996 }
997 self.functions.insert(name, fn_def);
998 }
999 for (name, value) in exports {
1000 if skip_private && crate::naming::is_private(&name) {
1001 continue;
1002 }
1003 if self
1004 .scopes
1005 .last()
1006 .map(|s| s.contains_key(&name))
1007 .unwrap_or(false)
1008 {
1009 self.runtime_warnings.push(crate::error::BopWarning::at(
1010 format!(
1011 "`{}` from `{}` shadowed by an existing binding — the first definition wins",
1012 name, path
1013 ),
1014 line,
1015 ));
1016 continue;
1017 }
1018 self.define(name, value);
1019 }
1020 // Bind the bare type names in the current scope's
1021 // type_bindings so subsequent `Color::Red` /
1022 // `Color { ... }` resolves to the module the name
1023 // came from. Shadowing here is silent (same as
1024 // values): first definition wins. Types without an
1025 // explicit bare binding remain reachable through
1026 // the alias form only.
1027 for tn in &exposed_types {
1028 let already_bound = self
1029 .type_bindings
1030 .last()
1031 .map(|s| s.contains_key(tn))
1032 .unwrap_or(false);
1033 if already_bound {
1034 continue;
1035 }
1036 if let Some(scope) = self.type_bindings.last_mut() {
1037 scope.insert(tn.clone(), path.to_string());
1038 }
1039 }
1040 }
1041
1042 if is_plain_glob {
1043 self.imported_here.insert(path.to_string());
1044 }
1045 Ok(())
1046 }
1047
1048 /// Resolve and evaluate a module, caching the result. Returns
1049 /// the module's exported bindings.
1050 fn load_module(&mut self, path: &str, line: u32) -> Result<ModuleBindings, BopError> {
1051 // Fast path: already loaded.
1052 {
1053 let cache = self.imports.borrow();
1054 if let Some(ImportSlot::Loaded(bindings)) = cache.get(path) {
1055 return Ok(bindings.clone());
1056 }
1057 if let Some(ImportSlot::Loading) = cache.get(path) {
1058 return Err(error(
1059 line,
1060 format!("Circular import: module `{}` is still loading", path),
1061 ));
1062 }
1063 }
1064
1065 // Ask the host for source.
1066 let source = match self.host.resolve_module(path) {
1067 Some(Ok(s)) => s,
1068 Some(Err(e)) => return Err(e),
1069 None => {
1070 return Err(error(
1071 line,
1072 format!("Module `{}` not found", path),
1073 ));
1074 }
1075 };
1076
1077 // Mark in-progress before we start evaluating so a
1078 // circular import surfaces as a clean error.
1079 self.imports
1080 .borrow_mut()
1081 .insert(path.to_string(), ImportSlot::Loading);
1082
1083 let result = self.evaluate_module(path, &source, line);
1084
1085 match result {
1086 Ok(bindings) => {
1087 self.imports
1088 .borrow_mut()
1089 .insert(path.to_string(), ImportSlot::Loaded(bindings.clone()));
1090 Ok(bindings)
1091 }
1092 Err(e) => {
1093 // Drop the Loading marker so a subsequent, non-
1094 // broken context could retry.
1095 self.imports.borrow_mut().remove(path);
1096 Err(e)
1097 }
1098 }
1099 }
1100
1101 /// Parse and walk a module source in a fresh scope, returning
1102 /// its top-level bindings as a `ModuleBindings`. Reuses the
1103 /// parent evaluator's host, limits, and import cache.
1104 /// `module_path` is the dot-joined name the importer used —
1105 /// the sub-evaluator tags its own declared types with this
1106 /// path so runtime values keep a stable identity across the
1107 /// import boundary.
1108 fn evaluate_module(
1109 &mut self,
1110 module_path: &str,
1111 source: &str,
1112 line: u32,
1113 ) -> Result<ModuleBindings, BopError> {
1114 let _ = line;
1115 let stmts = crate::parse(source)?;
1116 let imports = Rc::clone(&self.imports);
1117 let limits = self.limits.clone();
1118 let mut sub = Evaluator::new_for_module(
1119 self.host,
1120 limits,
1121 imports,
1122 module_path.to_string(),
1123 );
1124 // Run the module body to top — errors propagate as-is.
1125 match sub.exec_block(&stmts)? {
1126 Signal::Return(_) | Signal::None => {}
1127 Signal::Break => {
1128 return Err(error(0, "break used outside of a loop"));
1129 }
1130 Signal::Continue => {
1131 return Err(error(0, "continue used outside of a loop"));
1132 }
1133 }
1134 // Collect top-level `let` bindings from the module's
1135 // one remaining scope. Fns are handled separately so
1136 // the importer can register them in `self.functions`
1137 // as well as in the scope (see `exec_import`).
1138 let mut bindings: Vec<(String, Value)> = Vec::new();
1139 if let Some(top_scope) = sub.scopes.into_iter().next() {
1140 for (k, v) in top_scope {
1141 bindings.push((k, v));
1142 }
1143 }
1144 let fn_decls: Vec<(String, FnDef)> =
1145 sub.functions.into_iter().collect();
1146 // Type decls and methods transfer with their full
1147 // identity. Engine builtins (`<builtin>` module path)
1148 // are seeded into every evaluator anyway, so we filter
1149 // those out here to avoid duplicating them in the
1150 // importer's merge step.
1151 let builtin_mp = crate::value::BUILTIN_MODULE_PATH;
1152 let struct_defs: Vec<((String, String), Vec<String>)> = sub
1153 .struct_defs
1154 .into_iter()
1155 .filter(|((mp, _), _)| mp != builtin_mp)
1156 .collect();
1157 let enum_defs: Vec<((String, String), Vec<crate::parser::VariantDecl>)> = sub
1158 .enum_defs
1159 .into_iter()
1160 .filter(|((mp, _), _)| mp != builtin_mp)
1161 .collect();
1162 let mut methods: Vec<((String, String), String, FnDef)> = Vec::new();
1163 for (type_key, by_method) in sub.methods {
1164 for (method_name, fn_def) in by_method {
1165 methods.push((type_key.clone(), method_name, fn_def));
1166 }
1167 }
1168 Ok(ModuleBindings {
1169 bindings,
1170 fn_decls,
1171 struct_defs,
1172 enum_defs,
1173 methods,
1174 })
1175 }
1176
1177 /// Validate `ns.Type` — the caller wrote `ns.Type { ... }`
1178 /// or `ns.Type::Variant(...)`. `ns` must be a
1179 /// `Value::Module` in scope, and `Type` must appear in that
1180 /// module's list of exported type names. The return is
1181 /// `Ok(())` for now because types still register under their
1182 /// bare names globally; once qualified type names land, this
1183 /// is where we translate `ns.Type` into its qualified form.
1184 fn validate_namespaced_type(
1185 &self,
1186 ns: &str,
1187 type_name: &str,
1188 line: u32,
1189 ) -> Result<(), BopError> {
1190 // Prefer the local value scope so a shadowing binding
1191 // (`let p = 3` after `use paint as p`) is caught — but
1192 // fall back to the evaluator-level alias map so
1193 // namespaced references inside function bodies still
1194 // resolve.
1195 if let Some(v) = self.get_var(ns) {
1196 let module = match v {
1197 Value::Module(m) => m,
1198 _ => {
1199 return Err(error(
1200 line,
1201 format!(
1202 "`{}` is a {}, not a module alias — can't reach `{}` through it",
1203 ns,
1204 v.type_name(),
1205 type_name
1206 ),
1207 ));
1208 }
1209 };
1210 if !module.types.iter().any(|t| t == type_name) {
1211 return Err(error(
1212 line,
1213 format!(
1214 "`{}` isn't a type exported from `{}`",
1215 type_name, module.path
1216 ),
1217 ));
1218 }
1219 return Ok(());
1220 }
1221 if let Some(module) = self.module_aliases.get(ns) {
1222 if !module.types.iter().any(|t| t == type_name) {
1223 return Err(error(
1224 line,
1225 format!(
1226 "`{}` isn't a type exported from `{}`",
1227 type_name, module.path
1228 ),
1229 ));
1230 }
1231 return Ok(());
1232 }
1233 Err(error(
1234 line,
1235 format!("`{}` isn't a module alias in scope", ns),
1236 ))
1237 }
1238
1239 fn eval_enum_construct(
1240 &mut self,
1241 namespace: Option<&str>,
1242 type_name: &str,
1243 variant: &str,
1244 payload: &VariantPayload,
1245 line: u32,
1246 ) -> Result<Value, BopError> {
1247 // Resolve the type reference to its full
1248 // `(module_path, type_name)` identity before validating
1249 // the variant shape. `namespace` means the source wrote
1250 // `ns.Type::Variant(...)`; bare means we look the name up
1251 // in the current scope's type bindings.
1252 let module_path = match namespace {
1253 Some(ns) => {
1254 self.validate_namespaced_type(ns, type_name, line)?;
1255 // validate_namespaced_type guaranteed the alias
1256 // is a Module and the type is in its exports —
1257 // pick up the module path for the full identity.
1258 self.resolve_type_ref(Some(ns), type_name)
1259 .unwrap_or_else(|| self.current_module.clone())
1260 }
1261 None => self
1262 .resolve_type_ref(None, type_name)
1263 .ok_or_else(|| {
1264 error(line, crate::error_messages::enum_not_declared(type_name))
1265 })?,
1266 };
1267 let key = (module_path.clone(), type_name.to_string());
1268 let variants = self.enum_defs.get(&key).ok_or_else(|| {
1269 error(line, crate::error_messages::enum_not_declared(type_name))
1270 })?
1271 .clone();
1272 let decl = variants.iter().find(|v| v.name == variant).ok_or_else(|| {
1273 let msg = crate::error_messages::enum_has_no_variant(type_name, variant);
1274 let names = variants.iter().map(|v| v.name.as_str());
1275 match crate::suggest::did_you_mean(variant, names) {
1276 Some(hint) => error_with_hint(line, msg, hint),
1277 None => error(line, msg),
1278 }
1279 })?
1280 .clone();
1281
1282 match (&decl.kind, payload) {
1283 (VariantKind::Unit, VariantPayload::Unit) => Ok(Value::new_enum_unit(
1284 module_path,
1285 type_name.to_string(),
1286 variant.to_string(),
1287 )),
1288 (VariantKind::Tuple(fields), VariantPayload::Tuple(args)) => {
1289 if args.len() != fields.len() {
1290 return Err(error(
1291 line,
1292 format!(
1293 "`{}::{}` expects {} argument{}, but got {}",
1294 type_name,
1295 variant,
1296 fields.len(),
1297 if fields.len() == 1 { "" } else { "s" },
1298 args.len()
1299 ),
1300 ));
1301 }
1302 let mut items = Vec::with_capacity(args.len());
1303 for arg in args {
1304 items.push(self.eval_expr(arg)?);
1305 }
1306 Ok(Value::new_enum_tuple(
1307 module_path,
1308 type_name.to_string(),
1309 variant.to_string(),
1310 items,
1311 ))
1312 }
1313 (VariantKind::Struct(decl_fields), VariantPayload::Struct(provided)) => {
1314 let mut seen = alloc_import::collections::BTreeSet::new();
1315 let mut provided_map: BTreeMap<String, Value> = BTreeMap::new();
1316 for (fname, fexpr) in provided {
1317 if !seen.insert(fname.clone()) {
1318 return Err(error(
1319 line,
1320 format!(
1321 "Field `{}` specified twice in `{}::{}`",
1322 fname, type_name, variant
1323 ),
1324 ));
1325 }
1326 if !decl_fields.iter().any(|d| d == fname) {
1327 return Err(error(
1328 line,
1329 crate::error_messages::variant_has_no_field(
1330 type_name, variant, fname,
1331 ),
1332 ));
1333 }
1334 provided_map.insert(fname.clone(), self.eval_expr(fexpr)?);
1335 }
1336 let mut values: Vec<(String, Value)> =
1337 Vec::with_capacity(decl_fields.len());
1338 for decl_field in decl_fields {
1339 match provided_map.remove(decl_field) {
1340 Some(v) => values.push((decl_field.clone(), v)),
1341 None => {
1342 return Err(error(
1343 line,
1344 format!(
1345 "Missing field `{}` in `{}::{}` construction",
1346 decl_field, type_name, variant
1347 ),
1348 ));
1349 }
1350 }
1351 }
1352 Ok(Value::new_enum_struct(
1353 module_path,
1354 type_name.to_string(),
1355 variant.to_string(),
1356 values,
1357 ))
1358 }
1359 (VariantKind::Unit, _) => Err(error(
1360 line,
1361 format!(
1362 "Variant `{}::{}` takes no payload",
1363 type_name, variant
1364 ),
1365 )),
1366 (VariantKind::Tuple(_), _) => Err(error(
1367 line,
1368 format!(
1369 "Variant `{}::{}` expects positional arguments `(…)`",
1370 type_name, variant
1371 ),
1372 )),
1373 (VariantKind::Struct(_), _) => Err(error(
1374 line,
1375 format!(
1376 "Variant `{}::{}` expects named fields `{{ … }}`",
1377 type_name, variant
1378 ),
1379 )),
1380 }
1381 }
1382
1383 fn exec_assign(
1384 &mut self,
1385 target: &AssignTarget,
1386 op: &AssignOp,
1387 new_val: Value,
1388 line: u32,
1389 ) -> Result<(), BopError> {
1390 match target {
1391 AssignTarget::Variable(name) => {
1392 let final_val = match op {
1393 AssignOp::Eq => new_val,
1394 _ => {
1395 let current = self
1396 .get_var(name)
1397 .ok_or_else(|| {
1398 error(line, format!("Variable `{}` doesn't exist yet", name))
1399 })?
1400 .clone();
1401 self.apply_compound_op(¤t, op, &new_val, line)?
1402 }
1403 };
1404 if !self.set_var(name, final_val) {
1405 return Err(error_with_hint(
1406 line,
1407 format!("Variable `{}` doesn't exist yet", name),
1408 format!("Use `let` to create a new variable: let {} = ...", name),
1409 ));
1410 }
1411 Ok(())
1412 }
1413 AssignTarget::Index { object, index } => {
1414 let idx = self.eval_expr(index)?;
1415 let val_to_set = match op {
1416 AssignOp::Eq => new_val,
1417 _ => {
1418 let obj = self.eval_expr(object)?;
1419 let current = ops::index_get(&obj, &idx, line)?;
1420 self.apply_compound_op(¤t, op, &new_val, line)?
1421 }
1422 };
1423 if let ExprKind::Ident(name) = &object.kind {
1424 let mut obj = self
1425 .get_var(name)
1426 .ok_or_else(|| {
1427 error(line, format!("Variable `{}` doesn't exist", name))
1428 })?
1429 .clone();
1430 ops::index_set(&mut obj, &idx, val_to_set, line)?;
1431 self.set_var(name, obj);
1432 Ok(())
1433 } else {
1434 Err(error(
1435 line,
1436 "Can only assign to indexed variables (like `arr[0] = val`)",
1437 ))
1438 }
1439 }
1440 AssignTarget::Field { object, field } => {
1441 // Support assignment into a named struct field.
1442 // Only bare-`Ident` objects are assignable — the
1443 // writeback goes through `set_var`, so chains like
1444 // `foo().x = 1` or `arr[0].x = 1` aren't
1445 // supported yet (matches index-assign behaviour).
1446 let name = match &object.kind {
1447 ExprKind::Ident(n) => n.clone(),
1448 _ => {
1449 return Err(error(
1450 line,
1451 "Can only assign to fields of named variables (like `p.x = val`)",
1452 ));
1453 }
1454 };
1455 let mut obj = self
1456 .get_var(&name)
1457 .ok_or_else(|| {
1458 error(line, format!("Variable `{}` doesn't exist", name))
1459 })?
1460 .clone();
1461 let val_to_set = match op {
1462 AssignOp::Eq => new_val,
1463 _ => {
1464 let current = match &obj {
1465 Value::Struct(s) => s.field(field).cloned().ok_or_else(|| {
1466 error(
1467 line,
1468 crate::error_messages::struct_has_no_field(
1469 s.type_name(),
1470 field,
1471 ),
1472 )
1473 })?,
1474 other => {
1475 return Err(error(
1476 line,
1477 crate::error_messages::cant_assign_field(
1478 field,
1479 other.type_name(),
1480 ),
1481 ));
1482 }
1483 };
1484 self.apply_compound_op(¤t, op, &new_val, line)?
1485 }
1486 };
1487 match &mut obj {
1488 Value::Struct(s) => {
1489 let struct_type = s.type_name().to_string();
1490 if !s.set_field(field, val_to_set) {
1491 return Err(error(
1492 line,
1493 crate::error_messages::struct_has_no_field(&struct_type, field),
1494 ));
1495 }
1496 }
1497 other => {
1498 return Err(error(
1499 line,
1500 crate::error_messages::cant_assign_field(
1501 field,
1502 other.type_name(),
1503 ),
1504 ));
1505 }
1506 }
1507 self.set_var(&name, obj);
1508 Ok(())
1509 }
1510 }
1511 }
1512
1513 fn apply_compound_op(
1514 &self,
1515 left: &Value,
1516 op: &AssignOp,
1517 right: &Value,
1518 line: u32,
1519 ) -> Result<Value, BopError> {
1520 match op {
1521 AssignOp::Eq => Ok(right.clone()),
1522 AssignOp::AddEq => ops::add(left, right, line),
1523 AssignOp::SubEq => ops::sub(left, right, line),
1524 AssignOp::MulEq => ops::mul(left, right, line),
1525 AssignOp::DivEq => ops::div(left, right, line),
1526 AssignOp::ModEq => ops::rem(left, right, line),
1527 }
1528 }
1529
1530 // ─── Expressions ───────────────────────────────────────────────
1531
1532 fn eval_expr(&mut self, expr: &Expr) -> Result<Value, BopError> {
1533 match &expr.kind {
1534 ExprKind::Int(n) => Ok(Value::Int(*n)),
1535 ExprKind::Number(n) => Ok(Value::Number(*n)),
1536 ExprKind::Str(s) => Ok(Value::new_str(s.clone())),
1537 ExprKind::Bool(b) => Ok(Value::Bool(*b)),
1538 ExprKind::None => Ok(Value::None),
1539
1540 ExprKind::StringInterp(parts) => {
1541 let mut result = String::new();
1542 for part in parts {
1543 match part {
1544 StringPart::Literal(s) => result.push_str(s),
1545 StringPart::Variable(name) => {
1546 let val = self
1547 .get_var(name)
1548 .ok_or_else(|| {
1549 error_at(expr.line, expr.column, crate::error_messages::variable_not_found(name))
1550 })?
1551 .clone();
1552 result.push_str(&format!("{}", val));
1553 }
1554 }
1555 }
1556 Ok(Value::new_str(result))
1557 }
1558
1559 ExprKind::Ident(name) => {
1560 // Lexical lookup first — matches the intuition that
1561 // `let x = ...` locally shadows everything else.
1562 if let Some(v) = self.get_var(name) {
1563 return Ok(v.clone());
1564 }
1565 // Fall back to named `fn` declarations so they can
1566 // be passed around as first-class values. The
1567 // synthesised `Value::Fn` carries `self_name` so
1568 // recursive lookups inside the body still resolve
1569 // through `self.functions` (see `call_bop_fn`).
1570 if let Some(f) = self.functions.get(name) {
1571 return Ok(Value::new_fn(
1572 f.params.clone(),
1573 Vec::new(),
1574 f.body.clone(),
1575 Some(name.to_string()),
1576 ));
1577 }
1578 // Typo? Offer a "did you mean" hint if something
1579 // close is visible in the current scope / fn
1580 // registry. Falls back to the original "did you
1581 // forget `let`" when no candidate is similar
1582 // enough.
1583 let hint = self
1584 .value_candidates_hint(name)
1585 .unwrap_or_else(|| "Did you forget to create it with `let`?".to_string());
1586 Err(error_with_hint_at(
1587 expr.line,
1588 expr.column,
1589 crate::error_messages::variable_not_found(name),
1590 hint,
1591 ))
1592 }
1593
1594 ExprKind::BinaryOp { left, op, right } => {
1595 // Short-circuit for && and ||
1596 if matches!(op, BinOp::And) {
1597 let lval = self.eval_expr(left)?;
1598 if !lval.is_truthy() {
1599 return Ok(Value::Bool(false));
1600 }
1601 let rval = self.eval_expr(right)?;
1602 return Ok(Value::Bool(rval.is_truthy()));
1603 }
1604 if matches!(op, BinOp::Or) {
1605 let lval = self.eval_expr(left)?;
1606 if lval.is_truthy() {
1607 return Ok(Value::Bool(true));
1608 }
1609 let rval = self.eval_expr(right)?;
1610 return Ok(Value::Bool(rval.is_truthy()));
1611 }
1612
1613 let lval = self.eval_expr(left)?;
1614 let rval = self.eval_expr(right)?;
1615 self.binary_op(&lval, op, &rval, expr.line)
1616 }
1617
1618 ExprKind::UnaryOp { op, expr: inner } => {
1619 let val = self.eval_expr(inner)?;
1620 match op {
1621 UnaryOp::Neg => ops::neg(&val, expr.line),
1622 UnaryOp::Not => Ok(ops::not(&val)),
1623 }
1624 }
1625
1626 ExprKind::Call { callee, args } => {
1627 let mut eval_args = Vec::new();
1628 for arg in args {
1629 eval_args.push(self.eval_expr(arg)?);
1630 }
1631 if let ExprKind::Ident(name) = &callee.kind {
1632 // Lexical callable first: if the name is bound
1633 // to a `Value::Fn` in the current scope, call
1634 // it. A bound non-callable is an explicit
1635 // error — "shadowing a builtin with a number
1636 // then calling it" should fail loudly, not
1637 // silently dispatch to the builtin.
1638 if let Some(v) = self.get_var(name).cloned() {
1639 return self.call_value(v, eval_args, expr.line, Some(name));
1640 }
1641 // Otherwise fall through to the original
1642 // name-based dispatch (builtins → host → named
1643 // fns). This is what keeps `print(x)` /
1644 // `range(n)` / `my_user_fn(x)` working.
1645 return self.call_function(name, eval_args, expr.line);
1646 }
1647 // Non-Ident callee: evaluate the expression; it
1648 // must produce a `Value::Fn`.
1649 let callee_val = self.eval_expr(callee)?;
1650 self.call_value(callee_val, eval_args, expr.line, None)
1651 }
1652
1653 ExprKind::Lambda { params, body } => {
1654 let captures = self.snapshot_captures();
1655 Ok(Value::new_fn(
1656 params.clone(),
1657 captures,
1658 body.clone(),
1659 None,
1660 ))
1661 }
1662
1663 ExprKind::MethodCall {
1664 object,
1665 method,
1666 args,
1667 } => {
1668 let mut eval_args = Vec::new();
1669 for arg in args {
1670 eval_args.push(self.eval_expr(arg)?);
1671 }
1672 let obj_val = self.eval_expr(object)?;
1673
1674 // `m.foo(args)` on a module alias: this parsed as
1675 // a `MethodCall`, but there's no struct/enum
1676 // receiver — `m` is a `Value::Module` whose
1677 // `foo` export is a callable value. Look it up,
1678 // then treat the result as a regular value call.
1679 //
1680 // The common methods (`type`, `to_str`,
1681 // `inspect`) still win over export lookup, so
1682 // `m.type()` returns `"module"` instead of
1683 // complaining that `type` isn't exported.
1684 if let Value::Module(m) = &obj_val {
1685 if let Some(result) =
1686 methods::common_method(&obj_val, method, &eval_args, expr.line)?
1687 {
1688 return Ok(result.0);
1689 }
1690 if let Some((_, v)) = m.bindings.iter().find(|(k, _)| k == method) {
1691 let callee = v.clone();
1692 return self.call_value(callee, eval_args, expr.line, Some(method));
1693 }
1694 return Err(error(
1695 expr.line,
1696 format!(
1697 "`{}` isn't exported from `{}`",
1698 method, m.path
1699 ),
1700 ));
1701 }
1702
1703 // User-defined method dispatch comes first — any
1704 // user method registered against the receiver's
1705 // *full* type identity `(module_path, type_name)`
1706 // wins over built-in methods of the same name.
1707 // Enums dispatch on the enum's type, not the
1708 // variant's, so all variants of `paint.Shape`
1709 // share `fn Shape.area(self)` from the paint
1710 // module. A method declared for `paint.Shape`
1711 // deliberately does not fire on `other.Shape`.
1712 let type_key: Option<(String, String)> = match &obj_val {
1713 Value::Struct(s) => Some((
1714 s.module_path().to_string(),
1715 s.type_name().to_string(),
1716 )),
1717 Value::EnumVariant(e) => Some((
1718 e.module_path().to_string(),
1719 e.type_name().to_string(),
1720 )),
1721 _ => None,
1722 };
1723 if let Some(key) = type_key {
1724 let user = self
1725 .methods
1726 .get(&key)
1727 .and_then(|ms| ms.get(method))
1728 .cloned();
1729 if let Some(m) = user {
1730 if m.params.len() != eval_args.len() + 1 {
1731 return Err(error(
1732 expr.line,
1733 format!(
1734 "`{}.{}` expects {} argument{} (including `self`), but got {}",
1735 key.1,
1736 method,
1737 m.params.len(),
1738 if m.params.len() == 1 { "" } else { "s" },
1739 eval_args.len() + 1
1740 ),
1741 ));
1742 }
1743 // Prepend receiver as the first parameter
1744 // (`self` by convention).
1745 let mut full_args = Vec::with_capacity(eval_args.len() + 1);
1746 full_args.push(obj_val);
1747 full_args.extend(eval_args);
1748 let bop_fn = Rc::new(BopFn {
1749 params: m.params,
1750 captures: Vec::new(),
1751 body: FnBody::Ast(m.body),
1752 self_name: None,
1753 });
1754 return self.call_bop_fn(&bop_fn, full_args, expr.line);
1755 }
1756 }
1757
1758 // Callable-taking Result methods — `r.map(f)`,
1759 // `r.map_err(f)`, `r.and_then(f)`. These need the
1760 // evaluator's call primitive to invoke `f`, which
1761 // `call_method` doesn't have access to, so they
1762 // dispatch inline before the pure `call_method`
1763 // fall-through. Receiver must be the built-in
1764 // Result; user enums named `Result` don't qualify.
1765 if methods::is_builtin_result(&obj_val) {
1766 if let Some(kind) = methods::is_result_callable_method(method) {
1767 return self.call_result_callable_method(
1768 &obj_val,
1769 kind,
1770 method,
1771 eval_args,
1772 expr.line,
1773 );
1774 }
1775 }
1776
1777 let (ret, mutated) = self.call_method(&obj_val, method, &eval_args, expr.line)?;
1778
1779 if methods::is_mutating_method(method) {
1780 if let ExprKind::Ident(name) = &object.kind {
1781 if let Some(new_obj) = mutated {
1782 self.set_var(name, new_obj);
1783 }
1784 }
1785 }
1786 Ok(ret)
1787 }
1788
1789 ExprKind::Index { object, index } => {
1790 let obj = self.eval_expr(object)?;
1791 let idx = self.eval_expr(index)?;
1792 ops::index_get(&obj, &idx, expr.line)
1793 }
1794
1795 ExprKind::FieldAccess { object, field } => {
1796 let obj = self.eval_expr(object)?;
1797 match &obj {
1798 Value::Struct(s) => s.field(field).cloned().ok_or_else(|| {
1799 let msg =
1800 crate::error_messages::struct_has_no_field(s.type_name(), field);
1801 // Suggest from the struct's own declared
1802 // field list — `p.z` when `Point` has `x`
1803 // and `y` should point to `x`/`y`.
1804 let field_names: Vec<&str> =
1805 s.fields().iter().map(|(k, _)| k.as_str()).collect();
1806 match crate::suggest::did_you_mean(field, field_names) {
1807 Some(hint) => error_with_hint_at(expr.line, expr.column, msg, hint),
1808 None => error_at(expr.line, expr.column, msg),
1809 }
1810 }),
1811 Value::EnumVariant(e) => e.field(field).cloned().ok_or_else(|| {
1812 error(
1813 expr.line,
1814 crate::error_messages::variant_has_no_field(
1815 e.type_name(),
1816 e.variant(),
1817 field,
1818 ),
1819 )
1820 }),
1821 Value::Module(m) => {
1822 // `alias.name` — look up an export in the
1823 // aliased module.
1824 if let Some((_, v)) = m.bindings.iter().find(|(k, _)| k == field) {
1825 return Ok(v.clone());
1826 }
1827 if m.types.iter().any(|t| t == field) {
1828 // Types aren't first-class values; the
1829 // parser should have taken the namespaced
1830 // struct-lit / variant-ctor path
1831 // already. Reaching a FieldAccess here
1832 // means the user wrote `m.Type` in a
1833 // value-returning position.
1834 return Err(error_with_hint(
1835 expr.line,
1836 format!("`{}` in `{}` is a type, not a value", field, m.path),
1837 format!(
1838 "construct through the alias: `{}.{} {{ ... }}` or `{}.{}::Variant(...)`",
1839 m.path.split('.').last().unwrap_or(&m.path),
1840 field,
1841 m.path.split('.').last().unwrap_or(&m.path),
1842 field,
1843 ),
1844 ));
1845 }
1846 Err(error(
1847 expr.line,
1848 format!(
1849 "`{}` isn't exported from `{}`",
1850 field, m.path
1851 ),
1852 ))
1853 }
1854 other => Err(error(
1855 expr.line,
1856 crate::error_messages::cant_read_field(field, other.type_name()),
1857 )),
1858 }
1859 }
1860
1861 ExprKind::EnumConstruct {
1862 namespace,
1863 type_name,
1864 variant,
1865 payload,
1866 } => self.eval_enum_construct(
1867 namespace.as_deref(),
1868 type_name,
1869 variant,
1870 payload,
1871 expr.line,
1872 ),
1873
1874 ExprKind::StructConstruct {
1875 namespace,
1876 type_name,
1877 fields,
1878 } => {
1879 let module_path = match namespace {
1880 Some(ns) => {
1881 self.validate_namespaced_type(ns, type_name, expr.line)?;
1882 self.resolve_type_ref(Some(ns), type_name)
1883 .unwrap_or_else(|| self.current_module.clone())
1884 }
1885 None => self
1886 .resolve_type_ref(None, type_name)
1887 .ok_or_else(|| {
1888 error(
1889 expr.line,
1890 crate::error_messages::struct_not_declared(type_name),
1891 )
1892 })?,
1893 };
1894 let key = (module_path.clone(), type_name.clone());
1895 let decl_fields = self.struct_defs.get(&key).ok_or_else(|| {
1896 error(
1897 expr.line,
1898 crate::error_messages::struct_not_declared(type_name),
1899 )
1900 })?
1901 .clone();
1902 // Enforce exactly-this-field-set at construction:
1903 // no duplicates, no unknown fields, no missing
1904 // fields. The per-field loops below produce the
1905 // specific messages the tests assert on.
1906 let mut seen = alloc_import::collections::BTreeSet::new();
1907 let mut values: Vec<(String, Value)> =
1908 Vec::with_capacity(decl_fields.len());
1909 // Evaluate provided fields by name, then emit them
1910 // in *declaration* order so the `Value::Struct`'s
1911 // field list is stable across construction sites.
1912 let mut provided: BTreeMap<String, Value> = BTreeMap::new();
1913 for (fname, fexpr) in fields {
1914 if !seen.insert(fname.clone()) {
1915 return Err(error(
1916 expr.line,
1917 format!(
1918 "Field `{}` specified twice in `{}` construction",
1919 fname, type_name
1920 ),
1921 ));
1922 }
1923 if !decl_fields.iter().any(|d| d == fname) {
1924 let msg = crate::error_messages::struct_has_no_field(type_name, fname);
1925 let err = match crate::suggest::did_you_mean(
1926 fname,
1927 decl_fields.iter().map(|s| s.as_str()),
1928 ) {
1929 Some(hint) => error_with_hint_at(expr.line, expr.column, msg, hint),
1930 None => error_at(expr.line, expr.column, msg),
1931 };
1932 return Err(err);
1933 }
1934 provided.insert(fname.clone(), self.eval_expr(fexpr)?);
1935 }
1936 for decl in &decl_fields {
1937 match provided.remove(decl) {
1938 Some(v) => values.push((decl.clone(), v)),
1939 None => {
1940 return Err(error(
1941 expr.line,
1942 format!(
1943 "Missing field `{}` in `{}` construction",
1944 decl, type_name
1945 ),
1946 ));
1947 }
1948 }
1949 }
1950 Ok(Value::new_struct(module_path, type_name.clone(), values))
1951 }
1952
1953 ExprKind::Array(elements) => {
1954 let mut items = Vec::new();
1955 for elem in elements {
1956 items.push(self.eval_expr(elem)?);
1957 }
1958 Ok(Value::new_array(items))
1959 }
1960
1961 ExprKind::Dict(entries) => {
1962 let mut result = Vec::new();
1963 for (key, value_expr) in entries {
1964 let val = self.eval_expr(value_expr)?;
1965 result.push((key.clone(), val));
1966 }
1967 Ok(Value::new_dict(result))
1968 }
1969
1970 ExprKind::IfExpr {
1971 condition,
1972 then_expr,
1973 else_expr,
1974 } => {
1975 if self.eval_expr(condition)?.is_truthy() {
1976 self.eval_expr(then_expr)
1977 } else {
1978 self.eval_expr(else_expr)
1979 }
1980 }
1981
1982 ExprKind::Match { scrutinee, arms } => self.eval_match(scrutinee, arms, expr.line),
1983
1984 ExprKind::Try(inner) => self.eval_try(inner, expr.line),
1985 }
1986 }
1987
1988 /// `try` expression handler. Evaluates the inner expression,
1989 /// matches the conventional Result-shape (any enum variant
1990 /// named `Ok` or `Err`), and either unwraps `Ok` or stashes
1991 /// the `Err` value into `pending_try_return` so the enclosing
1992 /// `call_bop_fn` can convert it to `Signal::Return`.
1993 ///
1994 /// Top-level `try` on `Err` (call_depth == 0) surfaces as a
1995 /// real runtime error — there's no enclosing fn to return
1996 /// from, and the roadmap explicitly rejects the idea of
1997 /// swallowing it silently.
1998 fn eval_try(&mut self, inner: &Expr, line: u32) -> Result<Value, BopError> {
1999 let value = self.eval_expr(inner)?;
2000 // An earlier `try` in `inner` might have already fired —
2001 // e.g. `try try foo()` — in which case we just keep
2002 // propagating without poking at the unwound value.
2003 if self.pending_try_return.is_some() {
2004 return Ok(Value::None);
2005 }
2006 match &value {
2007 Value::EnumVariant(ev) if ev.variant() == "Ok" => {
2008 // Extract the single payload for `Ok(v)`, or fall
2009 // back to `none` for `Ok` used as a unit variant.
2010 match ev.payload() {
2011 EnumPayload::Tuple(items) if items.len() == 1 => Ok(items[0].clone()),
2012 EnumPayload::Unit => Ok(Value::None),
2013 EnumPayload::Tuple(items) => Err(error(
2014 line,
2015 format!(
2016 "try: Ok variant must carry exactly one value, got {}",
2017 items.len()
2018 ),
2019 )),
2020 EnumPayload::Struct(_) => Err(error(
2021 line,
2022 "try: Ok variant must carry a single positional value, not named fields",
2023 )),
2024 }
2025 }
2026 Value::EnumVariant(ev) if ev.variant() == "Err" => {
2027 if self.call_depth == 0 {
2028 return Err(error_with_hint(
2029 line,
2030 "try encountered Err at top-level",
2031 "Wrap the calling code in a fn, or use `match` to handle both arms explicitly.",
2032 ));
2033 }
2034 self.pending_try_return = Some(value);
2035 // Sentinel error whose only job is to unwind up
2036 // to the nearest `call_bop_fn`, which converts
2037 // it back into a `Signal::Return`. The `is_try_return`
2038 // flag on `BopError` is the only thing that
2039 // matters — line is kept for debug clarity;
2040 // message is unused.
2041 Err(BopError::try_return_signal(line))
2042 }
2043 other => Err(error(
2044 line,
2045 format!(
2046 "try expected a Result-shaped value (Ok/Err variant), got {}",
2047 other.type_name()
2048 ),
2049 )),
2050 }
2051 }
2052
2053 fn eval_match(
2054 &mut self,
2055 scrutinee: &Expr,
2056 arms: &[MatchArm],
2057 line: u32,
2058 ) -> Result<Value, BopError> {
2059 let value = self.eval_expr(scrutinee)?;
2060 for arm in arms {
2061 let mut bindings: Vec<(String, Value)> = Vec::new();
2062 // Resolve patterns' type references through the
2063 // walker's current scope. Capturing the three scope
2064 // tables as immutable slices / refs keeps the
2065 // closure free of the mutable-self borrow we need
2066 // right after for `push_scope` + binding injection.
2067 let scopes = &self.scopes;
2068 let type_bindings = &self.type_bindings;
2069 let module_aliases = &self.module_aliases;
2070 let resolver = |ns: Option<&str>, tn: &str| -> Option<String> {
2071 resolve_type_in(scopes, type_bindings, module_aliases, ns, tn)
2072 };
2073 if !pattern_matches(&arm.pattern, &value, &mut bindings, &resolver) {
2074 continue;
2075 }
2076 // Pattern matched — open a fresh scope, bind every
2077 // captured name, evaluate the guard (in that scope).
2078 self.push_scope();
2079 for (name, v) in bindings {
2080 self.define(name, v);
2081 }
2082 let guard_ok = match &arm.guard {
2083 Some(g) => self.eval_expr(g)?.is_truthy(),
2084 None => true,
2085 };
2086 if !guard_ok {
2087 self.pop_scope();
2088 continue;
2089 }
2090 let result = self.eval_expr(&arm.body);
2091 self.pop_scope();
2092 return result;
2093 }
2094 Err(error(
2095 line,
2096 "No match arm matched the scrutinee",
2097 ))
2098 }
2099
2100 // ─── Binary operations ─────────────────────────────────────────
2101
2102 fn binary_op(
2103 &self,
2104 left: &Value,
2105 op: &BinOp,
2106 right: &Value,
2107 line: u32,
2108 ) -> Result<Value, BopError> {
2109 match op {
2110 BinOp::Add => ops::add(left, right, line),
2111 BinOp::Sub => ops::sub(left, right, line),
2112 BinOp::Mul => ops::mul(left, right, line),
2113 BinOp::Div => ops::div(left, right, line),
2114 BinOp::Mod => ops::rem(left, right, line),
2115 BinOp::Eq => Ok(ops::eq(left, right)),
2116 BinOp::NotEq => Ok(ops::not_eq(left, right)),
2117 BinOp::Lt => ops::lt(left, right, line),
2118 BinOp::Gt => ops::gt(left, right, line),
2119 BinOp::LtEq => ops::lt_eq(left, right, line),
2120 BinOp::GtEq => ops::gt_eq(left, right, line),
2121 BinOp::And | BinOp::Or => unreachable!("handled in eval_expr"),
2122 }
2123 }
2124
2125 // ─── Function calls ────────────────────────────────────────────
2126
2127 /// Collapse the current scope stack into a flat list of
2128 /// `(name, value)` pairs — the snapshot used as a lambda's
2129 /// captures. Inner scopes shadow outer ones, so the resulting
2130 /// list is deduplicated by name with the innermost binding
2131 /// winning.
2132 fn snapshot_captures(&self) -> Vec<(String, Value)> {
2133 let mut flat = BTreeMap::new();
2134 for scope in &self.scopes {
2135 for (k, v) in scope {
2136 flat.insert(k.clone(), v.clone());
2137 }
2138 }
2139 flat.into_iter().collect()
2140 }
2141
2142 /// Call a value directly. Non-`Value::Fn` payloads are an
2143 /// explicit "not callable" error — this is the safety net for
2144 /// `let x = 5; x(1)` and friends.
2145 ///
2146 /// `name_hint` is the Ident text when the callee was a bare
2147 /// name, used only for error messages. Non-Ident callees pass
2148 /// `None`.
2149 fn call_value(
2150 &mut self,
2151 callee: Value,
2152 args: Vec<Value>,
2153 line: u32,
2154 name_hint: Option<&str>,
2155 ) -> Result<Value, BopError> {
2156 match &callee {
2157 Value::Fn(f) => {
2158 let f = Rc::clone(f);
2159 drop(callee);
2160 self.call_bop_fn(&f, args, line)
2161 }
2162 other => match name_hint {
2163 Some(n) => Err(error(
2164 line,
2165 format!(
2166 "`{}` is a {}, not a function",
2167 n,
2168 other.type_name()
2169 ),
2170 )),
2171 None => Err(error(
2172 line,
2173 crate::error_messages::cant_call_a(other.type_name()),
2174 )),
2175 },
2176 }
2177 }
2178
2179 /// Shared call path for every `Value::Fn` — the body of both
2180 /// `let f = fn(...) {...}; f(x)` and the reified version of
2181 /// `fn name(...) {...}; name(x)` come through here.
2182 fn call_bop_fn(
2183 &mut self,
2184 func: &Rc<BopFn>,
2185 args: Vec<Value>,
2186 line: u32,
2187 ) -> Result<Value, BopError> {
2188 let body = match &func.body {
2189 FnBody::Ast(stmts) => stmts,
2190 FnBody::Compiled(_) => {
2191 return Err(error(
2192 line,
2193 "This function was compiled for another engine and can't be run in the tree-walker",
2194 ));
2195 }
2196 };
2197 if args.len() != func.params.len() {
2198 let name = func.self_name.as_deref().unwrap_or("fn");
2199 return Err(error(
2200 line,
2201 format!(
2202 "`{}` expects {} argument{}, but got {}",
2203 name,
2204 func.params.len(),
2205 if func.params.len() == 1 { "" } else { "s" },
2206 args.len()
2207 ),
2208 ));
2209 }
2210
2211 if self.call_depth >= MAX_CALL_DEPTH {
2212 return Err(error_with_hint(
2213 line,
2214 "Too many nested function calls (possible infinite recursion)",
2215 "Check that your recursive function has a base case that stops calling itself.",
2216 ));
2217 }
2218
2219 // A function call gets a fresh scope stack: no outer
2220 // locals leak in. Captures and parameters seed the new
2221 // scope; self-reference via `self_name` lets recursive
2222 // lambdas see themselves.
2223 //
2224 // `type_bindings` is NOT reset — types and module
2225 // aliases declared at module scope need to be visible
2226 // inside nested fn bodies for construction + pattern
2227 // matching to work. We push a fresh frame so any
2228 // type decl inside the fn body still ends up scoped to
2229 // that fn and vanishes on return.
2230 self.call_depth += 1;
2231 let saved_scopes = core::mem::replace(&mut self.scopes, vec![BTreeMap::new()]);
2232 self.type_bindings.push(BTreeMap::new());
2233
2234 // Captures go in first so parameters shadow them on
2235 // collision (matches the lexical snapshot semantics).
2236 for (name, value) in &func.captures {
2237 self.define(name.clone(), value.clone());
2238 }
2239 if let Some(self_name) = &func.self_name {
2240 // Make the reified `fn name` value visible inside its
2241 // own body so `fn fib(n) { return fib(n-1) ... }`
2242 // works without a special "named fn" path.
2243 self.define(self_name.clone(), Value::Fn(Rc::clone(func)));
2244 }
2245 for (param, arg) in func.params.iter().zip(args) {
2246 self.define(param.clone(), arg);
2247 }
2248
2249 let result = self.exec_block(body);
2250 self.scopes = saved_scopes;
2251 self.type_bindings.pop();
2252 self.call_depth -= 1;
2253
2254 // `try` unwinds as a sentinel `BopError`; the call
2255 // boundary trades that error for the stashed value and
2256 // treats it as a normal return. Any other error
2257 // propagates as usual. Always clears
2258 // `pending_try_return` so a leftover can't contaminate
2259 // a later call.
2260 match result {
2261 Ok(sig) => match sig {
2262 Signal::Return(val) => Ok(val),
2263 Signal::Break => Err(error(line, "break used outside of a loop")),
2264 Signal::Continue => Err(error(line, "continue used outside of a loop")),
2265 Signal::None => Ok(Value::None),
2266 },
2267 Err(err) => {
2268 // Check the dedicated `is_try_return` flag
2269 // instead of inspecting the message — a flag
2270 // can't collide with a user-authored error
2271 // that happens to spell the same bytes.
2272 if err.is_try_return {
2273 if let Some(val) = self.pending_try_return.take() {
2274 return Ok(val);
2275 }
2276 }
2277 Err(err)
2278 }
2279 }
2280 }
2281
2282 /// Implement the `try_call(f)` builtin.
2283 ///
2284 /// Takes a zero-arg callable `f` and invokes it. On a clean
2285 /// return, yields `Result::Ok(value)`. On a **non-fatal**
2286 /// `BopError`, yields `Result::Err(RuntimeError { message,
2287 /// line })` — both values are constructed directly by
2288 /// `bop::builtins::make_try_call_ok` / `_err`, so they
2289 /// work even in programs that never declared `Result` or
2290 /// `RuntimeError` themselves.
2291 ///
2292 /// Fatal errors (resource-limit violations — see
2293 /// `BopError::is_fatal`) bypass the wrap and propagate
2294 /// unchanged, preserving the sandbox invariant.
2295 fn builtin_try_call(
2296 &mut self,
2297 args: Vec<Value>,
2298 line: u32,
2299 ) -> Result<Value, BopError> {
2300 if args.len() != 1 {
2301 return Err(error(
2302 line,
2303 format!(
2304 "`try_call` expects 1 argument, but got {}",
2305 args.len()
2306 ),
2307 ));
2308 }
2309 let callable = args.into_iter().next().unwrap();
2310 let func = match &callable {
2311 Value::Fn(f) => Rc::clone(f),
2312 other => {
2313 return Err(error(
2314 line,
2315 format!(
2316 "`try_call` expects a function, got {}",
2317 other.type_name()
2318 ),
2319 ));
2320 }
2321 };
2322 drop(callable);
2323 match self.call_bop_fn(&func, Vec::new(), line) {
2324 Ok(value) => Ok(builtins::make_try_call_ok(value)),
2325 Err(err) => {
2326 if err.is_fatal {
2327 Err(err)
2328 } else {
2329 Ok(builtins::make_try_call_err(&err))
2330 }
2331 }
2332 }
2333 }
2334
2335 fn call_function(
2336 &mut self,
2337 name: &str,
2338 args: Vec<Value>,
2339 line: u32,
2340 ) -> Result<Value, BopError> {
2341 // 1. Global builtins. The short list: anything variadic
2342 // (`print`), a collection constructor (`range`), session-
2343 // stateful (`rand`), or inherently takes a callable
2344 // (`try_call`). Every other global builtin that used to
2345 // live here is now a method on the receiver's type —
2346 // see `methods::common_method` and `methods::numeric_method`.
2347 match name {
2348 "range" => return builtins::builtin_range(&args, line, &mut self.rand_state),
2349 "rand" => return builtins::builtin_rand(&args, line, &mut self.rand_state),
2350 "print" => {
2351 let message = args
2352 .iter()
2353 .map(|a| format!("{}", a))
2354 .collect::<Vec<_>>()
2355 .join(" ");
2356 self.host.on_print(&message);
2357 return Ok(Value::None);
2358 }
2359 "try_call" => return self.builtin_try_call(args, line),
2360 "panic" => return builtins::builtin_panic(&args, line),
2361 _ => {}
2362 }
2363
2364 // 2. Host-provided builtins
2365 if let Some(result) = self.host.call(name, &args, line) {
2366 return result;
2367 }
2368
2369 // 3. User-defined functions — synthesise a transient
2370 // `BopFn` and delegate to the shared call path. This keeps
2371 // the behaviour of `fn name` declarations and `Value::Fn`
2372 // values identical, including self-reference semantics.
2373 let func = self.functions.get(name).cloned().ok_or_else(|| {
2374 // Preference order: "did you mean" suggestion first
2375 // (most specific to the user's typo), then the
2376 // host's generic function hint (embedder-specific
2377 // tips like "available host functions: …"), then a
2378 // bare error.
2379 if let Some(hint) = self.callable_candidates_hint(name) {
2380 error_with_hint(
2381 line,
2382 crate::error_messages::function_not_found(name),
2383 hint,
2384 )
2385 } else {
2386 let host_hint = self.host.function_hint();
2387 if host_hint.is_empty() {
2388 error(line, crate::error_messages::function_not_found(name))
2389 } else {
2390 error_with_hint(
2391 line,
2392 crate::error_messages::function_not_found(name),
2393 host_hint,
2394 )
2395 }
2396 }
2397 })?;
2398
2399 let bop_fn = Rc::new(BopFn {
2400 params: func.params,
2401 captures: Vec::new(),
2402 body: FnBody::Ast(func.body),
2403 self_name: Some(name.to_string()),
2404 });
2405 self.call_bop_fn(&bop_fn, args, line)
2406 }
2407
2408 // ─── Methods ───────────────────────────────────────────────────
2409
2410 /// Full method dispatch: user methods win, then common,
2411 /// then type-specific (via [`Self::call_method`]). This is
2412 /// what call sites outside the `MethodCall` arm (for-loop
2413 /// iterator protocol, future trait-like uses) want — the
2414 /// normal `MethodCall` path inlines the same logic.
2415 fn call_method_full(
2416 &mut self,
2417 obj: &Value,
2418 method: &str,
2419 args: Vec<Value>,
2420 line: u32,
2421 ) -> Result<Value, BopError> {
2422 // User-method dispatch first — matches MethodCall's
2423 // priority so `fn MyType.iter(self)` wins over any
2424 // built-in method of the same name.
2425 let type_key: Option<(String, String)> = match obj {
2426 Value::Struct(s) => Some((
2427 s.module_path().to_string(),
2428 s.type_name().to_string(),
2429 )),
2430 Value::EnumVariant(e) => Some((
2431 e.module_path().to_string(),
2432 e.type_name().to_string(),
2433 )),
2434 _ => None,
2435 };
2436 if let Some(key) = type_key {
2437 let user = self
2438 .methods
2439 .get(&key)
2440 .and_then(|ms| ms.get(method))
2441 .cloned();
2442 if let Some(m) = user {
2443 if m.params.len() != args.len() + 1 {
2444 return Err(error(
2445 line,
2446 format!(
2447 "`{}.{}` expects {} argument{} (including `self`), but got {}",
2448 key.1,
2449 method,
2450 m.params.len(),
2451 if m.params.len() == 1 { "" } else { "s" },
2452 args.len() + 1
2453 ),
2454 ));
2455 }
2456 let mut full_args = Vec::with_capacity(args.len() + 1);
2457 full_args.push(obj.clone());
2458 full_args.extend(args);
2459 let bop_fn = Rc::new(BopFn {
2460 params: m.params,
2461 captures: Vec::new(),
2462 body: FnBody::Ast(m.body),
2463 self_name: None,
2464 });
2465 return self.call_bop_fn(&bop_fn, full_args, line);
2466 }
2467 }
2468 // Fall through to the built-in dispatch.
2469 let (ret, _mutated) = self.call_method(obj, method, &args, line)?;
2470 Ok(ret)
2471 }
2472
2473 fn call_method(
2474 &self,
2475 obj: &Value,
2476 method: &str,
2477 args: &[Value],
2478 line: u32,
2479 ) -> Result<(Value, Option<Value>), BopError> {
2480 // `type` / `to_str` / `inspect` work on every value.
2481 // Try the shared dispatcher first so we don't have to
2482 // duplicate those three names across every type-
2483 // specific method table.
2484 if let Some(result) = methods::common_method(obj, method, args, line)? {
2485 return Ok(result);
2486 }
2487 // Built-in Result combinators (`is_ok`, `is_err`,
2488 // `unwrap`, `expect`, `unwrap_or`). Callable-taking
2489 // variants (`map`, `map_err`, `and_then`) are dispatched
2490 // one level up in the MethodCall arm so they can invoke
2491 // the user callable via `call_value`.
2492 if methods::is_builtin_result(obj) {
2493 if let Some(v) = methods::result_method(obj, method, args, line)? {
2494 return Ok((v, None));
2495 }
2496 }
2497 match obj {
2498 Value::Array(arr) => methods::array_method(arr, method, args, line),
2499 Value::Str(s) => methods::string_method(s, method, args, line),
2500 Value::Dict(entries) => methods::dict_method(entries, method, args, line),
2501 Value::Int(_) | Value::Number(_) => {
2502 methods::numeric_method(obj, method, args, line)
2503 }
2504 Value::Bool(_) => methods::bool_method(obj, method, args, line),
2505 Value::Iter(_) => methods::iter_method(obj, method, args, line),
2506 _ => Err(error(
2507 line,
2508 crate::error_messages::no_such_method(obj.type_name(), method),
2509 )),
2510 }
2511 }
2512
2513 /// Handle `r.map(f)`, `r.map_err(f)`, `r.and_then(f)` for a
2514 /// built-in `Result` receiver. Factored out of the
2515 /// `MethodCall` arm so the evaluator / VM / AOT can keep the
2516 /// same shape.
2517 fn call_result_callable_method(
2518 &mut self,
2519 receiver: &Value,
2520 kind: methods::ResultCallableKind,
2521 method: &str,
2522 args: Vec<Value>,
2523 line: u32,
2524 ) -> Result<Value, BopError> {
2525 use crate::builtins::expect_args;
2526 use methods::{make_result_err, make_result_ok, ResultCallableKind};
2527 expect_args(method, &args, 1, line)?;
2528 let callable = args.into_iter().next().expect("expect_args ensured len = 1");
2529 let variant_info = match receiver {
2530 Value::EnumVariant(e) => e,
2531 _ => return Err(error(line, "Result method called on non-Result")),
2532 };
2533 let payload = match variant_info.payload() {
2534 crate::value::EnumPayload::Tuple(items) if items.len() == 1 => items[0].clone(),
2535 // Unreachable for the built-in shape, but keep the
2536 // error clean rather than panicking.
2537 _ => {
2538 return Err(error(
2539 line,
2540 format!("malformed Result::{} payload", variant_info.variant()),
2541 ));
2542 }
2543 };
2544 let is_ok = variant_info.variant() == "Ok";
2545 match kind {
2546 ResultCallableKind::Map => {
2547 if is_ok {
2548 let new_value = self.call_value(callable, vec![payload], line, Some(method))?;
2549 Ok(make_result_ok(new_value))
2550 } else {
2551 // Err passes through unchanged. Rebuild it so
2552 // the caller sees the same type identity —
2553 // matches the pure-Bop combinator's behaviour.
2554 Ok(make_result_err(payload))
2555 }
2556 }
2557 ResultCallableKind::MapErr => {
2558 if !is_ok {
2559 let new_value = self.call_value(callable, vec![payload], line, Some(method))?;
2560 Ok(make_result_err(new_value))
2561 } else {
2562 Ok(make_result_ok(payload))
2563 }
2564 }
2565 ResultCallableKind::AndThen => {
2566 if is_ok {
2567 // `f` is expected to return a Result; we
2568 // surface whatever it returns verbatim. A
2569 // match on the returned shape at the call
2570 // site catches misuse.
2571 self.call_value(callable, vec![payload], line, Some(method))
2572 } else {
2573 Ok(make_result_err(payload))
2574 }
2575 }
2576 }
2577 }
2578}
2579
2580// ─── Pattern matching ──────────────────────────────────────────────
2581//
2582// `pattern_matches` returns true if `value` fits `pattern`, filling
2583// `bindings` with every `Binding` name along the way. On a partial
2584// match it's the caller's responsibility to discard the bindings
2585// — `eval_match` does this by only consuming them after the match
2586// + guard both succeed.
2587
2588/// Destructured view of an `Iter::Next(v) | Iter::Done` result.
2589/// Returned by [`unwrap_iter_result`] so the for-loop can bind
2590/// the payload cleanly without repeating the pattern-match.
2591enum IterStep {
2592 Next(Value),
2593 Done,
2594}
2595
2596/// Inspect a value returned from an iterator's `.next()` method.
2597/// Returns `Some(step)` when the value is a well-formed
2598/// `Iter::Next(v)` / `Iter::Done` (built-in or not), or `None`
2599/// when it doesn't match so the caller can raise a clear error.
2600fn unwrap_iter_result(v: &Value) -> Option<IterStep> {
2601 let e = match v {
2602 Value::EnumVariant(e) => e,
2603 _ => return None,
2604 };
2605 if e.type_name() != "Iter" {
2606 return None;
2607 }
2608 match (e.variant(), e.payload()) {
2609 ("Next", crate::value::EnumPayload::Tuple(items)) if items.len() == 1 => {
2610 Some(IterStep::Next(items[0].clone()))
2611 }
2612 ("Done", crate::value::EnumPayload::Unit) => Some(IterStep::Done),
2613 _ => None,
2614 }
2615}
2616
2617/// Seed a fresh evaluator's type tables with the engine-wide
2618/// builtin types (`Result`, `RuntimeError`). The shapes come from
2619/// `crate::builtins` so walker / VM / AOT can never drift out of
2620/// sync — they all read the same source.
2621///
2622/// Returns:
2623/// - struct_defs keyed by `(module_path, type_name)` with the
2624/// builtin registered under `<builtin>`;
2625/// - enum_defs keyed the same way;
2626/// - a bare-name → module_path map seeded with entries for every
2627/// builtin, so the program's outermost scope can resolve
2628/// bare `Result::Ok(...)` and `RuntimeError { ... }` without
2629/// an explicit `use`.
2630fn seed_builtin_types() -> (
2631 BTreeMap<(String, String), Vec<String>>,
2632 BTreeMap<(String, String), Vec<VariantDecl>>,
2633 BTreeMap<String, String>,
2634) {
2635 use crate::value::BUILTIN_MODULE_PATH;
2636 let mut struct_defs: BTreeMap<(String, String), Vec<String>> = BTreeMap::new();
2637 let mut enum_defs: BTreeMap<(String, String), Vec<VariantDecl>> = BTreeMap::new();
2638 let mut type_bindings: BTreeMap<String, String> = BTreeMap::new();
2639 struct_defs.insert(
2640 (String::from(BUILTIN_MODULE_PATH), String::from("RuntimeError")),
2641 crate::builtins::builtin_runtime_error_fields(),
2642 );
2643 type_bindings.insert(
2644 String::from("RuntimeError"),
2645 String::from(BUILTIN_MODULE_PATH),
2646 );
2647 enum_defs.insert(
2648 (String::from(BUILTIN_MODULE_PATH), String::from("Result")),
2649 crate::builtins::builtin_result_variants(),
2650 );
2651 type_bindings.insert(
2652 String::from("Result"),
2653 String::from(BUILTIN_MODULE_PATH),
2654 );
2655 enum_defs.insert(
2656 (String::from(BUILTIN_MODULE_PATH), String::from("Iter")),
2657 crate::builtins::builtin_iter_variants(),
2658 );
2659 type_bindings.insert(
2660 String::from("Iter"),
2661 String::from(BUILTIN_MODULE_PATH),
2662 );
2663 (struct_defs, enum_defs, type_bindings)
2664}
2665
2666/// True when two enum declarations describe the same runtime
2667/// shape. Strictly looser than `PartialEq` on `Vec<VariantDecl>`:
2668/// tuple variants compare by *arity* only (their payload field
2669/// names are positional stubs with no runtime meaning), while
2670/// struct variants still require matching field names. Same for
2671/// the outer variant ordering — positional.
2672///
2673/// Used by the "redeclare-same-shape" rules so a user program
2674/// that declares `enum Result { Ok(v), Err(e) }` is compatible
2675/// with the engine's builtin `enum Result { Ok(value), Err(error) }`.
2676fn variants_equivalent(a: &[VariantDecl], b: &[VariantDecl]) -> bool {
2677 if a.len() != b.len() {
2678 return false;
2679 }
2680 for (va, vb) in a.iter().zip(b.iter()) {
2681 if va.name != vb.name {
2682 return false;
2683 }
2684 match (&va.kind, &vb.kind) {
2685 (VariantKind::Unit, VariantKind::Unit) => {}
2686 (VariantKind::Tuple(fa), VariantKind::Tuple(fb)) => {
2687 if fa.len() != fb.len() {
2688 return false;
2689 }
2690 }
2691 (VariantKind::Struct(fa), VariantKind::Struct(fb)) => {
2692 if fa != fb {
2693 return false;
2694 }
2695 }
2696 _ => return false,
2697 }
2698 }
2699 true
2700}
2701
2702/// Walk a pair of scope stacks to resolve a source-level type
2703/// reference — the same logic as
2704/// [`Evaluator::resolve_type_ref`], but free-standing so the
2705/// pattern matcher can be called with a borrow of the
2706/// evaluator's tables without needing the evaluator itself.
2707/// Returns `None` if the name isn't in scope.
2708///
2709/// `module_aliases` is the persistent map of aliased `use`
2710/// modules so namespaced references (`m.Color`) resolve even
2711/// inside function bodies, where `scopes` no longer contains
2712/// `m` (the function call stack is reset per-call).
2713pub fn resolve_type_in(
2714 value_scopes: &[BTreeMap<String, Value>],
2715 type_scopes: &[BTreeMap<String, String>],
2716 module_aliases: &BTreeMap<String, Rc<crate::value::BopModule>>,
2717 namespace: Option<&str>,
2718 type_name: &str,
2719) -> Option<String> {
2720 if let Some(ns) = namespace {
2721 // First: look the alias up in value scopes (catches
2722 // locally-bound modules, if any). Then fall back to
2723 // the evaluator-level alias map so function bodies can
2724 // reach module-level aliases even when their own value
2725 // scope is empty.
2726 for scope in value_scopes.iter().rev() {
2727 if let Some(Value::Module(m)) = scope.get(ns) {
2728 if m.types.iter().any(|t| t == type_name) {
2729 return Some(m.path.clone());
2730 }
2731 return None;
2732 }
2733 }
2734 if let Some(m) = module_aliases.get(ns) {
2735 if m.types.iter().any(|t| t == type_name) {
2736 return Some(m.path.clone());
2737 }
2738 }
2739 return None;
2740 }
2741 for scope in type_scopes.iter().rev() {
2742 if let Some(mp) = scope.get(type_name) {
2743 return Some(mp.clone());
2744 }
2745 }
2746 None
2747}
2748
2749/// Resolver that turns a source-level type reference — optional
2750/// namespace plus bare name (`m.Color` vs plain `Color`) — into
2751/// the declaring module's path. Pattern matching threads this
2752/// through so a pattern like `Color::Red` only matches values
2753/// whose module identity agrees with the current scope's binding
2754/// of `Color`.
2755///
2756/// Returning `None` means "no type with this name is visible in
2757/// the calling scope" — the matcher treats that as a mismatch
2758/// rather than a fallback, to avoid silently matching a
2759/// same-named type from a different module.
2760pub type TypeResolveFn<'a> = &'a dyn Fn(Option<&str>, &str) -> Option<String>;
2761
2762/// Attempt to match `pattern` against `value`. On success, appends
2763/// any captured `(name, Value)` bindings to `bindings` and returns
2764/// `true`; on failure, returns `false` and leaves `bindings` in an
2765/// undefined state — it's the caller's responsibility to discard it.
2766///
2767/// `resolver` is consulted whenever a pattern names a user type
2768/// (`Pattern::Struct` or `Pattern::EnumVariant`) so the matcher
2769/// can compare the value's full identity `(module_path,
2770/// type_name)` against the source-level reference.
2771///
2772/// Exported so other engines (the bytecode VM, AOT transpiler)
2773/// can run the exact same structural matcher as the tree-walker
2774/// without re-implementing the rules.
2775pub fn pattern_matches(
2776 pattern: &Pattern,
2777 value: &Value,
2778 bindings: &mut Vec<(String, Value)>,
2779 resolver: TypeResolveFn<'_>,
2780) -> bool {
2781 match pattern {
2782 Pattern::Wildcard => true,
2783 Pattern::Binding(name) => {
2784 bindings.push((name.clone(), value.clone()));
2785 true
2786 }
2787 Pattern::Literal(lit) => match (lit, value) {
2788 (LiteralPattern::Int(a), Value::Int(b)) => a == b,
2789 (LiteralPattern::Number(a), Value::Number(b)) => a == b,
2790 // Cross-type numeric literal patterns — same rule
2791 // as `values_equal`: `match 1 { 1.0 => ... }` and
2792 // `match 1.0 { 1 => ... }` both match.
2793 (LiteralPattern::Int(a), Value::Number(b)) => (*a as f64) == *b,
2794 (LiteralPattern::Number(a), Value::Int(b)) => *a == (*b as f64),
2795 (LiteralPattern::Str(a), Value::Str(b)) => a.as_str() == b.as_str(),
2796 (LiteralPattern::Bool(a), Value::Bool(b)) => a == b,
2797 (LiteralPattern::None, Value::None) => true,
2798 _ => false,
2799 },
2800 Pattern::EnumVariant {
2801 namespace,
2802 type_name,
2803 variant,
2804 payload,
2805 } => {
2806 let ev = match value {
2807 Value::EnumVariant(e) => e,
2808 _ => return false,
2809 };
2810 // Full identity check: resolve the pattern's type
2811 // reference to a module path, then compare against
2812 // the value's own module path.
2813 let expected_mp = match resolver(namespace.as_deref(), type_name) {
2814 Some(mp) => mp,
2815 None => return false,
2816 };
2817 if ev.module_path() != expected_mp.as_str()
2818 || ev.type_name() != type_name.as_str()
2819 || ev.variant() != variant.as_str()
2820 {
2821 return false;
2822 }
2823 match (payload, ev.payload()) {
2824 (VariantPatternPayload::Unit, crate::value::EnumPayload::Unit) => true,
2825 (
2826 VariantPatternPayload::Tuple(pats),
2827 crate::value::EnumPayload::Tuple(items),
2828 ) => {
2829 if pats.len() != items.len() {
2830 return false;
2831 }
2832 for (p, v) in pats.iter().zip(items.iter()) {
2833 if !pattern_matches(p, v, bindings, resolver) {
2834 return false;
2835 }
2836 }
2837 true
2838 }
2839 (
2840 VariantPatternPayload::Struct { fields, rest },
2841 crate::value::EnumPayload::Struct(entries),
2842 ) => {
2843 match_struct_fields(fields, *rest, entries, bindings, resolver)
2844 }
2845 _ => false,
2846 }
2847 }
2848 Pattern::Struct {
2849 namespace,
2850 type_name,
2851 fields,
2852 rest,
2853 } => {
2854 let st = match value {
2855 Value::Struct(s) => s,
2856 _ => return false,
2857 };
2858 let expected_mp = match resolver(namespace.as_deref(), type_name) {
2859 Some(mp) => mp,
2860 None => return false,
2861 };
2862 if st.module_path() != expected_mp.as_str()
2863 || st.type_name() != type_name.as_str()
2864 {
2865 return false;
2866 }
2867 match_struct_fields(fields, *rest, st.fields(), bindings, resolver)
2868 }
2869 Pattern::Array { elements, rest } => {
2870 let items = match value {
2871 Value::Array(arr) => arr,
2872 _ => return false,
2873 };
2874 match rest {
2875 None => {
2876 if elements.len() != items.len() {
2877 return false;
2878 }
2879 for (p, v) in elements.iter().zip(items.iter()) {
2880 if !pattern_matches(p, v, bindings, resolver) {
2881 return false;
2882 }
2883 }
2884 true
2885 }
2886 Some(rest_kind) => {
2887 if items.len() < elements.len() {
2888 return false;
2889 }
2890 for (i, p) in elements.iter().enumerate() {
2891 if !pattern_matches(p, &items[i], bindings, resolver) {
2892 return false;
2893 }
2894 }
2895 if let ArrayRest::Named(name) = rest_kind {
2896 let tail: Vec<Value> =
2897 items[elements.len()..].iter().cloned().collect();
2898 bindings.push((name.clone(), Value::new_array(tail)));
2899 }
2900 true
2901 }
2902 }
2903 }
2904 Pattern::Or(alts) => {
2905 for alt in alts {
2906 let mut attempt: Vec<(String, Value)> = Vec::new();
2907 if pattern_matches(alt, value, &mut attempt, resolver) {
2908 bindings.extend(attempt);
2909 return true;
2910 }
2911 }
2912 false
2913 }
2914 }
2915}
2916
2917fn match_struct_fields(
2918 fields: &[(String, Pattern)],
2919 rest: bool,
2920 entries: &[(String, Value)],
2921 bindings: &mut Vec<(String, Value)>,
2922 resolver: TypeResolveFn<'_>,
2923) -> bool {
2924 // Every declared field-pattern must find its value in the
2925 // struct. `rest` just relaxes the requirement that *every*
2926 // value's key must appear in the pattern — non-rest patterns
2927 // may still leave fields unmatched, which is fine: the walker
2928 // matches named-field lookup semantics, not whole-shape
2929 // destructuring.
2930 let _ = rest;
2931 for (fname, pat) in fields {
2932 let value = match entries.iter().find(|(k, _)| k == fname) {
2933 Some((_, v)) => v,
2934 None => return false,
2935 };
2936 if !pattern_matches(pat, value, bindings, resolver) {
2937 return false;
2938 }
2939 }
2940 true
2941}
2942
2943// ─── REPL session ──────────────────────────────────────────────────
2944//
2945// `Evaluator` itself is a one-shot: `Evaluator::new(host, limits)` +
2946// `eval.run(stmts)` + drop. That's the right shape for running a
2947// complete program from top to bottom. REPL workloads need the
2948// opposite: fresh *input* evaluated against *accumulated* state.
2949//
2950// `ReplSession` owns every Evaluator field that should survive
2951// across inputs — scopes, functions, user types, method tables,
2952// import caches, module aliases, rng state. The ephemeral
2953// per-run fields (host, step counter, call depth, the try-
2954// return sentinel slot, transient warnings) stay on the
2955// temporary `Evaluator` that the session spins up for each
2956// `eval` call.
2957//
2958// The session model also handles REPL-specific niceties:
2959//
2960// - Bare expression statements at the end of an input yield
2961// their value back to the caller, so the REPL can echo it.
2962// `let x = 5` returns None; `x + 1` returns Some(Value).
2963// - Scope depth is normalised back to the root on each
2964// `eval` — if an earlier input errored mid-block, we
2965// don't leave half-pushed scopes lying around for the next
2966// input to trip over.
2967
2968/// Persistent state for an interactive REPL session. Build
2969/// once with [`Self::new`], then call [`Self::eval`] for each
2970/// fresh line / block the user types. State carried across
2971/// calls: every `let` / `const` / `fn` / `struct` / `enum` /
2972/// method declaration, every `use`'d module's bindings and
2973/// aliases, the global rng seed, and the import cache.
2974///
2975/// A `ReplSession` is not a sandboxing boundary — embedders
2976/// that want a fresh identity between inputs should construct
2977/// a new session. Resource limits (steps, memory) are supplied
2978/// per `eval` call since they reset each time.
2979pub struct ReplSession {
2980 scopes: Vec<BTreeMap<String, Value>>,
2981 functions: BTreeMap<String, FnDef>,
2982 current_module: String,
2983 struct_defs: BTreeMap<(String, String), Vec<String>>,
2984 enum_defs: BTreeMap<(String, String), Vec<VariantDecl>>,
2985 methods: BTreeMap<(String, String), BTreeMap<String, FnDef>>,
2986 type_bindings: Vec<BTreeMap<String, String>>,
2987 module_aliases: BTreeMap<String, Rc<crate::value::BopModule>>,
2988 imports: ImportCache,
2989 imported_here: alloc_import::collections::BTreeSet<String>,
2990 rand_state: u64,
2991}
2992
2993impl Default for ReplSession {
2994 fn default() -> Self {
2995 Self::new()
2996 }
2997}
2998
2999impl ReplSession {
3000 /// Build a fresh session. Type tables are pre-seeded with
3001 /// the engine builtins (`Result`, `RuntimeError`) so
3002 /// `Result::Ok(...)` resolves without an explicit `use`.
3003 pub fn new() -> Self {
3004 let (struct_defs, enum_defs, builtin_bindings) = seed_builtin_types();
3005 Self {
3006 scopes: vec![BTreeMap::new()],
3007 functions: BTreeMap::new(),
3008 current_module: String::from(crate::value::ROOT_MODULE_PATH),
3009 struct_defs,
3010 enum_defs,
3011 methods: BTreeMap::new(),
3012 type_bindings: vec![builtin_bindings],
3013 module_aliases: BTreeMap::new(),
3014 imports: Rc::new(RefCell::new(
3015 alloc_import::collections::BTreeMap::new(),
3016 )),
3017 imported_here: alloc_import::collections::BTreeSet::new(),
3018 rand_state: 0,
3019 }
3020 }
3021
3022 /// Look up a binding by name. Convenience for tests and
3023 /// embedders that want to peek at the session's state
3024 /// between `eval` calls — e.g. checking that `let x = 5`
3025 /// actually stuck.
3026 pub fn get(&self, name: &str) -> Option<Value> {
3027 for scope in self.scopes.iter().rev() {
3028 if let Some(v) = scope.get(name) {
3029 return Some(v.clone());
3030 }
3031 }
3032 None
3033 }
3034
3035 /// Every currently-bound name in the root scope, sorted.
3036 /// Handy for REPL introspection commands (`:vars`) and
3037 /// for tab-completers that want to stay honest about
3038 /// what's actually in scope rather than guessing from
3039 /// observed tokens.
3040 pub fn binding_names(&self) -> Vec<String> {
3041 let mut names: Vec<String> = self
3042 .scopes
3043 .first()
3044 .map(|s| s.keys().cloned().collect())
3045 .unwrap_or_default();
3046 for name in self.functions.keys() {
3047 names.push(name.clone());
3048 }
3049 names.sort();
3050 names.dedup();
3051 names
3052 }
3053
3054 /// Parse `source` and run its statements against this
3055 /// session's accumulated state.
3056 ///
3057 /// If the last statement is a bare expression
3058 /// (`ExprStmt`), the session evaluates it and returns its
3059 /// value as `Ok(Some(v))` so the REPL can echo the
3060 /// result. Every other shape — `let x = ...`, `fn foo`,
3061 /// `struct`, `use`, loops — returns `Ok(None)`.
3062 ///
3063 /// Partial failure semantics: if an earlier statement
3064 /// errors, the session's state reflects whatever ran
3065 /// before the failure. That matches what a user would
3066 /// expect from an interactive prompt.
3067 pub fn eval<H: BopHost>(
3068 &mut self,
3069 source: &str,
3070 host: &mut H,
3071 limits: &BopLimits,
3072 ) -> Result<Option<Value>, BopError> {
3073 let stmts = crate::parse(source)?;
3074 self.run_stmts(&stmts, host, limits)
3075 }
3076
3077 /// Like [`Self::eval`] but takes an already-parsed AST.
3078 /// Useful when the caller has already run a
3079 /// `parse_with_warnings` pass and wants to surface
3080 /// warnings before executing.
3081 pub fn run_stmts<H: BopHost>(
3082 &mut self,
3083 stmts: &[Stmt],
3084 host: &mut H,
3085 limits: &BopLimits,
3086 ) -> Result<Option<Value>, BopError> {
3087 // If the last stmt is a bare expression we strip it
3088 // off, run the rest, and then evaluate it as an
3089 // expression so we can return its value. Anything
3090 // else runs as a normal statement block.
3091 let (tail_expr, body) = match stmts.last().map(|s| &s.kind) {
3092 Some(StmtKind::ExprStmt(_)) => {
3093 let (last, rest) = stmts.split_last().unwrap();
3094 let expr = match &last.kind {
3095 StmtKind::ExprStmt(e) => e.clone(),
3096 _ => unreachable!(),
3097 };
3098 (Some(expr), rest)
3099 }
3100 _ => (None, stmts),
3101 };
3102
3103 // Spin up a temporary Evaluator around the session's
3104 // state. We hand off each field by `mem::take` /
3105 // `clone` of the Rc so the Evaluator owns working
3106 // copies; at the end we unconditionally write the
3107 // (possibly-mutated) state back so partial failures
3108 // still persist whatever changes happened before the
3109 // error.
3110 let mut eval = self.take_evaluator(host, limits.clone());
3111 let result: Result<Option<Value>, BopError> = (|| {
3112 let sig = eval.exec_block(body)?;
3113 match sig {
3114 Signal::Break => return Err(error(0, "break used outside of a loop")),
3115 Signal::Continue => {
3116 return Err(error(0, "continue used outside of a loop"));
3117 }
3118 _ => {}
3119 }
3120 match tail_expr {
3121 Some(expr) => Ok(Some(eval.eval_expr(&expr)?)),
3122 None => Ok(None),
3123 }
3124 })();
3125 // Surface any warnings accumulated on this run
3126 // (currently: glob-import shadowing). Stderr delivery
3127 // matches `Evaluator::run`; embedders that want
3128 // structured access can call `eval` via a custom host
3129 // and take warnings out through that path.
3130 #[cfg(not(feature = "no_std"))]
3131 {
3132 for w in &eval.runtime_warnings {
3133 eprintln!("warning: {}", w.message);
3134 }
3135 }
3136 self.put_evaluator(eval);
3137 result
3138 }
3139
3140 /// Internal: move the session's state into a fresh
3141 /// Evaluator. `host` and `limits` are the ephemeral
3142 /// per-run bits.
3143 fn take_evaluator<'h, H: BopHost>(
3144 &mut self,
3145 host: &'h mut H,
3146 limits: BopLimits,
3147 ) -> Evaluator<'h, H> {
3148 Evaluator {
3149 scopes: core::mem::take(&mut self.scopes),
3150 functions: core::mem::take(&mut self.functions),
3151 current_module: core::mem::take(&mut self.current_module),
3152 struct_defs: core::mem::take(&mut self.struct_defs),
3153 enum_defs: core::mem::take(&mut self.enum_defs),
3154 methods: core::mem::take(&mut self.methods),
3155 type_bindings: core::mem::take(&mut self.type_bindings),
3156 module_aliases: core::mem::take(&mut self.module_aliases),
3157 imports: Rc::clone(&self.imports),
3158 imported_here: core::mem::take(&mut self.imported_here),
3159 rand_state: self.rand_state,
3160 host,
3161 steps: 0,
3162 call_depth: 0,
3163 limits,
3164 pending_try_return: None,
3165 runtime_warnings: Vec::new(),
3166 }
3167 }
3168
3169 /// Internal: move the Evaluator's state back into the
3170 /// session. Also normalises scope depth back to the root
3171 /// so a mid-block error doesn't leave leftover pushed
3172 /// scopes hanging around for the next input.
3173 fn put_evaluator<'h, H: BopHost>(&mut self, eval: Evaluator<'h, H>) {
3174 let mut scopes = eval.scopes;
3175 if scopes.len() > 1 {
3176 scopes.truncate(1);
3177 }
3178 if scopes.is_empty() {
3179 scopes.push(BTreeMap::new());
3180 }
3181 let mut type_bindings = eval.type_bindings;
3182 if type_bindings.len() > 1 {
3183 type_bindings.truncate(1);
3184 }
3185 if type_bindings.is_empty() {
3186 // Re-seed so builtins stay visible.
3187 let (_, _, builtins) = seed_builtin_types();
3188 type_bindings.push(builtins);
3189 }
3190 self.scopes = scopes;
3191 self.functions = eval.functions;
3192 self.current_module = eval.current_module;
3193 self.struct_defs = eval.struct_defs;
3194 self.enum_defs = eval.enum_defs;
3195 self.methods = eval.methods;
3196 self.type_bindings = type_bindings;
3197 self.module_aliases = eval.module_aliases;
3198 self.imported_here = eval.imported_here;
3199 self.rand_state = eval.rand_state;
3200 // `imports` is shared via `Rc` — nothing to do; the
3201 // cache the Evaluator wrote into is the same one the
3202 // session holds.
3203 }
3204}