The Jest Programming Language
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331use std::collections::HashMap;
use crate::runtime::data::{String as RStr, pattern_bound_names};use crate::runtime::{eval_trampoline, Budget, Eval, Scope, Value};use crate::{Error, Result};
use super::control::try_match;
// ── Helpers ──────────────────────────────────────────────────────────────────
/// Build a single-key call `Value`: `{proc: [args...]}`.pub(crate) fn make_call(proc: &str, args: Vec<Value>, budget: &Budget) -> Result<Value> { let arr = Value::array(args, budget)?; Value::call(proc, arr, budget)}
fn capture_scope( body: &Value, bound: &std::collections::HashSet<std::string::String>, scope: &Scope, budget: &Budget,) -> Result<Value> { let pairs: Vec<(RStr, Value)> = body .free_vars() .into_iter() .filter(|n| !bound.contains(n)) .filter_map(|n| scope.get(&n).map(|v| (n, v))) .map(|(n, v)| { let key = RStr::new(&n, budget)?; Ok((key, v)) }) .collect::<Result<_>>()?; let inner = Value::object(pairs, budget)?; // Tag the scope object so eval_inner treats it as self-evaluating data, // preventing misinterpretation as a procedure call on round-trips. Value::call("runtime/scope", inner, budget)}
/// Wrap a scope value in a `runtime/scope` tag if it isn't already tagged.////// The inner value must be a `Value::Object`. Anything else — e.g. a number/// or string — is a programming error caught here at function-definition time/// rather than at call time.fn ensure_scope_obj_tagged(v: Value, budget: &Budget) -> Result<Value> { if let Some(("runtime/scope", _)) = v.as_call() { return Ok(v); } match &v { Value::Object(_) => Value::call("runtime/scope", v, budget), _ => Err(Error::MalformedFnValue( "scope_obj must evaluate to an object".to_string(), )), }}
// ── Core operative value constructor ──────────────────────────────────────────
/// Build a `vau` value form from formals + body (+ optional env formal).////// This is the single constructor for all operatives. All recursion is handled/// externally via `letrec`/`defrec` + mu values — no auto-wrapping here.pub(crate) fn make_fn_value( args: &[Value], scope: Scope, budget: &Budget, eval_limit: &mut usize,) -> Result<Value> { match args { // 2-element source form: [formals_pattern, body] // Formals are patterns (not expressions) — used as-is, not evaluated. [formals, body] => { let bound = pattern_bound_names(formals); let scope_obj = capture_scope(body, &bound, &scope, budget)?; make_call("vau", vec![formals.clone(), body.clone(), scope_obj], budget) }
// 3-element: either value form [formals, body, scope_obj] // or env source form [formals, env_str, body] [formals, second, third] => { // Check if this is [formals, env_str, body] — 3-element source form with env formal if let Value::String(env_name) = second { // 3-element source with env formal: [formals, "env", body] let body = third; let mut bound = pattern_bound_names(formals); bound.insert(env_name.to_string()); let scope_obj = capture_scope(body, &bound, &scope, budget)?; make_call("vau", vec![formals.clone(), second.clone(), body.clone(), scope_obj], budget) } else { // 3-element value/explicit-scope form: [formals, body, scope_obj] let body = second; let scope_obj = third; let is_value_form = matches!(scope_obj.as_call(), Some(("runtime/scope", _))); if is_value_form { make_call("vau", vec![formals.clone(), body.clone(), scope_obj.clone()], budget) } else { let scope_evaled = eval_trampoline(budget, eval_limit, scope_obj.clone(), scope)?; let scope_tagged = ensure_scope_obj_tagged(scope_evaled, budget)?; make_call("vau", vec![formals.clone(), body.clone(), scope_tagged], budget) } } }
// 4-element value form: [formals, env_str, body, scope_obj] [formals, env_name, body, scope_obj] => { let is_value_form = matches!(scope_obj.as_call(), Some(("runtime/scope", _))); if is_value_form { make_call("vau", vec![formals.clone(), env_name.clone(), body.clone(), scope_obj.clone()], budget) } else { let scope_evaled = eval_trampoline(budget, eval_limit, scope_obj.clone(), scope)?; let scope_tagged = ensure_scope_obj_tagged(scope_evaled, budget)?; make_call("vau", vec![formals.clone(), env_name.clone(), body.clone(), scope_tagged], budget) } }
_ => Err(Error::arity("vau", "2 or 3", args.len())), }}
// ── Public builtins ───────────────────────────────────────────────────────────
/// `vau` — the primitive operative constructor.////// Source forms: `{"vau": [formals, body]}` or `{"vau": [formals, "env", body]}`pub fn op_vau( args: &[Value], budget: &Budget, eval_limit: &mut usize, scope: Scope,) -> Result<Eval> { make_fn_value(args, scope, budget, eval_limit).map(Eval::Done)}
// fn and macro are self-hosted in stdlib.jst.
/// `wrap` — wraps an operative into an applicative (evaluates args before dispatch).////// Also handles value-form round-tripping: if called with an already-wrapped value,/// returns it unchanged (needed because wrap values serialize as `{"wrap": vau_val}`/// which goes through eval_inner → dispatch when re-evaluated).pub fn op_wrap( args: &[Value], budget: &Budget, eval_limit: &mut usize, scope: Scope,) -> Result<Eval> { match args { [arg] => { let val = eval_trampoline(budget, eval_limit, arg.clone(), scope)?; match val.as_call() { Some(("vau", _)) | Some(("mu", _)) => Value::call("wrap", val, budget).map(Eval::Done), _ => Err(Error::type_err("wrap", 0, &val, "operative (vau or mu value)")), } } _ => Err(Error::arity("wrap", "1", args.len())), }}
/// `unwrap` — extracts the inner operative from a wrapped applicative.pub fn op_unwrap( args: &[Value], budget: &Budget, eval_limit: &mut usize, scope: Scope,) -> Result<Eval> { match args { [arg] => { let val = eval_trampoline(budget, eval_limit, arg.clone(), scope)?; if let Some(("wrap", inner)) = val.as_call() { Eval::done(inner.clone()) } else { Err(Error::type_err("unwrap", 0, &val, "applicative (wrap value)")) } } _ => Err(Error::arity("unwrap", "1", args.len())), }}
// ── Scope extraction ─────────────────────────────────────────────────────────
/// Extract a scope HashMap from a scope_obj value.////// scope_obj may be in one of these forms after eval + round-trips:/// - `{"runtime/scope": {...}}` — the standard tagged form/// - `{...}` — untagged form (multi-entry)pub(crate) fn extract_scope_map( sobj: Value,) -> crate::Result<HashMap<std::string::String, Value>> { fn from_val(val: &Value) -> crate::Result<HashMap<std::string::String, Value>> { match val { Value::Object(obj) => Ok(obj.iter() .map(|(k, v)| (k.to_string(), v.clone())) .collect()), _ => { // A 1-entry scope object is recognized as a call form by as_call(). if let Some((k, v)) = val.as_call() { Ok([(k.to_string(), v.clone())].into_iter().collect()) } else { Err(crate::Error::MalformedFnValue( "scope must be an object".to_string(), )) } } } } // Check for tagged form: {"runtime/scope": inner} if let Some(("runtime/scope", inner)) = sobj.as_call() { return from_val(inner); } from_val(&sobj)}
// ── call_operative ───────────────────────────────────────────────────────────
/// Invoke a `vau` operative value against a list of (unevaluated) call arguments.////// `fn_arg` — the argument part of the `vau` singleton object, i.e. the/// `Value::Array([formals_pat, body, scope_obj?])` or/// `Value::Array([formals_pat, env_str, body, scope_obj?])`./// `caller_scope` — the scope at the call site (for env formal binding).////// Operatives NEVER evaluate args — that's the caller's responsibility/// (wrap does it for applicatives).pub(crate) fn call_operative( fn_arg: Value, call_args: &[Value], budget: &Budget, eval_limit: &mut usize, caller_scope: Scope,) -> Result<Eval> { let items = match fn_arg { Value::Array(arr) => arr, _ => { return Err(Error::MalformedFnValue( "operative value is not an array".to_string(), )) } };
// Parse the vau value structure: // [formals_pat, body] — no scope, no env // [formals_pat, body, scope_obj] — closed, no env // [formals_pat, env_str, body, scope_obj] — closed, with env let (formals_pat, env_formal, body, scope_opt): (Value, Option<std::string::String>, Value, Option<Value>) = match items.as_ref() { [f, b] => (f.clone(), None, b.clone(), None), [f, b, s] => { // Disambiguate: if s is a runtime/scope tag, this is [formals, body, scope_obj]. // Otherwise check if b is a String (env formal): [formals, env_str, body]. if matches!(s.as_call(), Some(("runtime/scope", _))) { (f.clone(), None, b.clone(), Some(s.clone())) } else if let Value::String(env) = b { // 3-element without scope_obj: [formals, env, body] (unclosed) (f.clone(), Some(env.to_string()), s.clone(), None) } else { // Assume it's [formals, body, explicit_scope_expr] (shouldn't normally happen in value forms) (f.clone(), None, b.clone(), Some(s.clone())) } } [f, env, b, s] => { let env_name = match env { Value::String(s) => s.to_string(), _ => return Err(Error::MalformedFnValue( "env formal must be a string".to_string(), )), }; (f.clone(), Some(env_name), b.clone(), Some(s.clone())) } _ => { return Err(Error::MalformedFnValue( "operative value has unexpected structure".to_string(), )) } };
// Operatives do NOT evaluate args — pass through as-is. let evaled = call_args.to_vec();
// Match args against the formals pattern. // // If formals is an Array, match against the args-as-array (positional binding). // If formals is NOT an Array, expect exactly one argument and match directly // against it (single-arg destructuring). This mirrors the singleton-call // convention: {"foo": x} ↔ {"foo": [x]}. let match_target = if matches!(&formals_pat, Value::Array(_)) { Value::array(evaled, budget)? } else { if evaled.len() != 1 { return Err(Error::arity("vau", "1", evaled.len())); } evaled.into_iter().next().unwrap() }; let mut bindings: HashMap<std::string::String, Value> = HashMap::new(); if !try_match(&formals_pat, &match_target, &mut bindings, budget, eval_limit, caller_scope.clone())? { return Err(Error::arity("vau", "matching args", call_args.len())); }
// Reconstruct execution scope. If the operative value carries an embedded // scope_obj, layer it on top of the global root so that builtins are // always reachable. Otherwise use the call-site scope. let exec_scope = if let Some(sobj) = scope_opt { let map = extract_scope_map(sobj)?; caller_scope.globals().local(map) } else { caller_scope.clone() };
// If env formal is present, bind caller's scope as a Value::Object. if let Some(env_name) = env_formal { let env_val = scope_to_value(&caller_scope, budget)?; bindings.insert(env_name, env_val); }
Ok(Eval::TailEval { value: body, scope: exec_scope.local(bindings) })}
/// Convert a Scope to a Value::Object for env formal binding.////// Includes all bindings (including `runtime/*` names) for consistency/// with `get-scope`. Env formals expose the full scope.fn scope_to_value(scope: &Scope, budget: &Budget) -> Result<Value> { let all = scope.collect_all(); let pairs: Vec<(crate::runtime::data::String, Value)> = all .into_iter() .map(|(name, val)| { let key = crate::runtime::data::String::new(&name, budget)?; Ok((key, val)) }) .collect::<Result<_>>()?; Value::object(pairs, budget)}