Skip to main content

hara_native/core/
native.rs

1fn os_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
2    let operation = operation.strip_prefix("os/").unwrap_or(operation);
3    let operation = operation
4        .strip_prefix("std.native.OS/")
5        .unwrap_or(operation);
6    let process_operation = operation.strip_prefix("std.native.Process/");
7    let operation = match process_operation.unwrap_or(operation) {
8        "alive?" if process_operation.is_some() => "process-alive?",
9        "write" if process_operation.is_some() => "process-write",
10        "close-input" if process_operation.is_some() => "process-close-input",
11        "stdout" if process_operation.is_some() => "process-stdout",
12        "stderr" if process_operation.is_some() => "process-stderr",
13        "stdout-stream" if process_operation.is_some() => "process-stdout-stream",
14        "stderr-stream" if process_operation.is_some() => "process-stderr-stream",
15        "wait" if process_operation.is_some() => "process-wait",
16        "kill" if process_operation.is_some() => "process-kill",
17        value => value,
18    };
19    match operation {
20        "time-ms" => {
21            if !values.is_empty() {
22                return Err("os/time-ms expects no arguments".into());
23            }
24            return Ok(Value::Number(crate::clock::time_ms()));
25        }
26        "time-ns" => {
27            if !values.is_empty() {
28                return Err("os/time-ns expects no arguments".into());
29            }
30            return Ok(Value::Number(crate::clock::time_ns()));
31        }
32        "platform" => {
33            if !values.is_empty() {
34                return Err("os/platform expects no arguments".into());
35            }
36            let platform = if cfg!(target_os = "linux") {
37                "linux"
38            } else if cfg!(target_os = "macos") {
39                "macos"
40            } else if cfg!(target_os = "windows") {
41                "windows"
42            } else {
43                "unknown"
44            };
45            return Ok(Value::Keyword(platform.into()));
46        }
47        "arch" => {
48            if !values.is_empty() {
49                return Err("os/arch expects no arguments".into());
50            }
51            let arch = match std::env::consts::ARCH {
52                "x86_64" => "x86-64",
53                value => value,
54            };
55            return Ok(Value::Keyword(arch.into()));
56        }
57        "cwd" => {
58            if !values.is_empty() {
59                return Err("os/cwd expects no arguments".into());
60            }
61            #[cfg(target_arch = "wasm32")]
62            return Ok(Value::String("/".into()));
63            #[cfg(not(target_arch = "wasm32"))]
64            return std::env::current_dir()
65                .map(|path| Value::String(path.to_string_lossy().into_owned()))
66                .map_err(|error| format!("os/cwd failed: {error}"));
67        }
68        "env" => {
69            if !values.is_empty() {
70                return Err("os/env expects no arguments".into());
71            }
72            #[cfg(target_arch = "wasm32")]
73            return Ok(Value::Map(PMap::new()));
74            #[cfg(not(target_arch = "wasm32"))]
75            return Ok(Value::Map(PMap::from_iter(
76                std::env::vars().map(|(key, value)| (Value::String(key), Value::String(value))),
77            )));
78        }
79        "getenv" => {
80            if values.len() != 1 {
81                return Err("os/getenv expects a name".into());
82            }
83            let Value::String(name) = &values[0] else {
84                return Err("os/getenv expects a string".into());
85            };
86            #[cfg(target_arch = "wasm32")]
87            {
88                let _ = name;
89                return Ok(Value::Nil);
90            }
91            #[cfg(not(target_arch = "wasm32"))]
92            return Ok(std::env::var(name).map(Value::String).unwrap_or(Value::Nil));
93        }
94        "process?" => {
95            if values.len() != 1 {
96                return Err("os/process? expects one argument".into());
97            }
98            let value = values[0].clone();
99            #[cfg(not(target_arch = "wasm32"))]
100            return Ok(Value::Bool(crate::native_process::is_process(&value)));
101            #[cfg(target_arch = "wasm32")]
102            return Ok(Value::Bool(false));
103        }
104        _ => {}
105    }
106    require_process_access(&format!("os/{operation}"))?;
107    #[cfg(target_arch = "wasm32")]
108    return Err(format!("os/{operation} is unsupported on wasm"));
109    #[cfg(not(target_arch = "wasm32"))]
110    match operation {
111        "spawn" => {
112            if !(1..=2).contains(&values.len()) {
113                return Err("os/spawn expects argv and optional options".into());
114            }
115            let argv = iterator_values(values[0].clone())?
116                .into_iter()
117                .map(|value| match value {
118                    Value::String(value) => Ok(value),
119                    _ => Err("os/spawn argv must contain strings".to_owned()),
120                })
121                .collect::<Result<Vec<_>, _>>()?;
122            let mut cwd = None;
123            let mut environment = Vec::new();
124            if values.len() == 2 {
125                let options = values[1].clone();
126                for (key, value) in map_entries(&options)
127                    .ok_or_else(|| "os/spawn options must be a map".to_owned())?
128                {
129                    match (key, value) {
130                        (Value::Keyword(key), Value::String(value)) if key.as_str() == "cwd" => {
131                            cwd = Some(value);
132                        }
133                        (Value::Keyword(key), value) if key.as_str() == "env" => {
134                            for (name, value) in map_entries(&value)
135                                .ok_or_else(|| "os/spawn :env must be a map".to_owned())?
136                            {
137                                let (Value::String(name), Value::String(value)) = (name, value)
138                                else {
139                                    return Err("os/spawn :env must contain string pairs".into());
140                                };
141                                environment.push((name, value));
142                            }
143                        }
144                        _ => {}
145                    }
146                }
147            }
148            crate::native_process::spawn(&argv, cwd.as_deref(), &environment)
149        }
150        method @ ("process-alive?"
151        | "process-close-input"
152        | "process-stdout"
153        | "process-stderr"
154        | "process-wait"
155        | "process-kill") => {
156            if values.len() != 1 {
157                return Err(format!("os/{method} expects a process"));
158            }
159            let process = values[0].clone();
160            match method {
161                "process-alive?" => crate::native_process::alive(&process).map(Value::Bool),
162                "process-close-input" => {
163                    crate::native_process::close_input(&process).map(|()| Value::Nil)
164                }
165                "process-stdout" => {
166                    crate::native_process::promise(&process, "stdout").map(Value::Promise)
167                }
168                "process-stderr" => {
169                    crate::native_process::promise(&process, "stderr").map(Value::Promise)
170                }
171                "process-wait" => {
172                    crate::native_process::promise(&process, "wait").map(Value::Promise)
173                }
174                "process-kill" => crate::native_process::kill(&process).map(|()| process),
175                _ => unreachable!(),
176            }
177        }
178        method @ ("process-stdout-stream" | "process-stderr-stream") => {
179            if values.len() != 1 {
180                return Err(format!("os/{method} expects a process"));
181            }
182            let process = values[0].clone();
183            let kind = if method == "process-stderr-stream" {
184                "stderr"
185            } else {
186                "stdout"
187            };
188            let handle = crate::native_process::take_stream(&process, kind)?;
189            Ok(host_stream(
190                Rc::new(move || Ok(crate::native_process::stream_promise(handle, kind))),
191                Rc::new(|| Ok(())),
192            ))
193        }
194        "process-write" => {
195            if values.len() != 2 {
196                return Err("os/process-write expects a process and bytes".into());
197            }
198            let process = values[0].clone();
199            let bytes = match &values[1] {
200                Value::Bytes(value) => value.clone(),
201                Value::ByteBuffer(value) => value.borrow().clone(),
202                _ => return Err("os/process-write expects bytes".into()),
203            };
204            crate::native_process::write(&process, &bytes).map(|count| Value::Number(count as i64))
205        }
206        _ => Err(format!("unknown os operation: {operation}")),
207    }
208}
209
210fn native_test_events() -> Value {
211    Value::Vector(PVector::from_iter([
212        Value::Keyword("test/run-started".into()),
213        Value::Keyword("test/fact-started".into()),
214        Value::Keyword("test/fact-completed".into()),
215        Value::Keyword("test/run-completed".into()),
216    ]))
217}
218
219fn native_test_runner(value: Value) -> Result<Value, String> {
220    match value {
221        Value::Keyword(runner) if matches!(runner.as_str(), "code.test" | "native") => {
222            Ok(Value::Keyword(runner))
223        }
224        _ => Err("runtime test runner must be :code.test or :native".into()),
225    }
226}
227
228fn native_test_active_runner() -> Result<Value, String> {
229    ACTIVE_TEST_RUNNER
230        .with(|runner| native_test_runner(Value::Keyword(runner.borrow().clone().into())))
231}
232
233fn native_test_config(runner: Value, options: Value) -> Result<Value, String> {
234    if map_entries(&options).is_none() {
235        return Err("std.native.Test/config options must be a map".into());
236    }
237    if map_value(&options, &Value::Keyword("runner".into())).is_some() {
238        return Err("std.native.Test/config runner is owned by the runtime".into());
239    }
240    Ok(Value::Map(PMap::from_iter([
241        (Value::Keyword("runner".into()), runner),
242        (Value::Keyword("options".into()), options),
243    ])))
244}
245
246fn native_test_context(desc: Value, actual: Value, expected: Value, failures: Value) -> Value {
247    Value::Map(PMap::from_iter([
248        (
249            Value::Keyword("test".into()),
250            Value::Map(PMap::from_iter([
251                (Value::Keyword("desc".into()), desc.clone()),
252                // `:name` remains an output alias while source packages move
253                // their fact identity to `:desc`.
254                (Value::Keyword("name".into()), desc),
255                (Value::Keyword("actual".into()), actual),
256                (Value::Keyword("expected".into()), expected),
257            ])),
258        ),
259        (Value::Keyword("failures".into()), failures),
260    ]))
261}
262
263fn native_test_failure(actual: Value, expected: Value) -> Value {
264    Value::Map(PMap::from_iter([
265        (
266            Value::Keyword("failure/code".into()),
267            Value::Keyword("test/not-equal".into()),
268        ),
269        (
270            Value::Keyword("failure/path".into()),
271            Value::Vector(PVector::new()),
272        ),
273        (
274            Value::Keyword("failure/in".into()),
275            Value::Vector(PVector::new()),
276        ),
277        (Value::Keyword("failure/actual".into()), actual),
278        (Value::Keyword("failure/expected".into()), expected),
279        (
280            Value::Keyword("failure/message".into()),
281            Value::String("values are not equal".into()),
282        ),
283        (
284            Value::Keyword("failure/context".into()),
285            Value::Map(PMap::new()),
286        ),
287        (
288            Value::Keyword("failure/children".into()),
289            Value::Vector(PVector::new()),
290        ),
291    ]))
292}
293
294fn native_test_compare(actual: Value, expected: Value) -> Result<Value, String> {
295    let pass = actual == expected;
296    let failures = if pass {
297        Value::Vector(PVector::new())
298    } else {
299        Value::Vector(PVector::from_iter([native_test_failure(
300            actual.clone(),
301            expected.clone(),
302        )]))
303    };
304    Ok(Value::Result(Rc::new(ResultValue::success(
305        Value::Bool(pass),
306        native_test_context(Value::Nil, actual, expected, failures),
307    )?)))
308}
309
310fn native_test_result(
311    desc: Value,
312    actual: Value,
313    expected: Value,
314    comparison: Value,
315) -> Result<Value, String> {
316    let Value::Result(comparison) = comparison else {
317        return Err("std.native.Test/result expects a comparison Result".into());
318    };
319    let failures = map_value(&comparison.context, &Value::Keyword("failures".into()))
320        .cloned()
321        .unwrap_or_else(|| Value::Vector(PVector::new()));
322    Ok(Value::Result(Rc::new(comparison.with_context(
323        native_test_context(desc, actual, expected, failures),
324    )?)))
325}
326
327fn native_test_error(desc: Value, actual: Value, expected: Value, error: String) -> Value {
328    Value::Result(Rc::new(
329        ResultValue::error(
330            caught_error(&error),
331            native_test_context(desc, actual, expected, Value::Vector(PVector::new())),
332        )
333        .expect("native Test error context is a map"),
334    ))
335}
336
337fn native_test_checked_result(desc: Value, metadata: Option<Value>, checked: Value) -> Value {
338    let Value::Result(result) = checked else {
339        return native_test_error(
340            desc,
341            Value::Nil,
342            Value::Nil,
343            "Test/check check function must return a Result".into(),
344        );
345    };
346    let mut context =
347        PMap::from_iter(map_entries(&result.context).expect("Result context is a map"));
348    let test = map_value(&result.context, &Value::Keyword("test".into()))
349        .cloned()
350        .unwrap_or_else(|| Value::Map(PMap::new()));
351    let mut test = PMap::from_iter(map_entries(&test).unwrap_or_default());
352    test = test.assoc_value(Value::Keyword("desc".into()), desc.clone());
353    test = test.assoc_value(Value::Keyword("name".into()), desc);
354    context = context.assoc_value(Value::Keyword("test".into()), Value::Map(test));
355    if let Some(metadata) = metadata {
356        context = context.assoc_value(Value::Keyword("meta".into()), metadata);
357    }
358    Value::Result(Rc::new(
359        result
360            .with_context(Value::Map(context))
361            .expect("native Test checked context is a map"),
362    ))
363}
364
365fn native_test_lifecycle_error(phase: &str, error: String) -> Value {
366    let desc = Value::String(format!("test {phase}"));
367    let phase = Value::Keyword(phase.into());
368    let Value::Result(result) = native_test_error(desc, Value::Nil, Value::Nil, error) else {
369        unreachable!()
370    };
371    Value::Result(Rc::new(
372        result
373            .with_context(Value::Map(PMap::from_iter([(
374                Value::Keyword("phase".into()),
375                phase,
376            )])))
377            .expect("native Test lifecycle context is a map"),
378    ))
379}
380
381fn native_test_lifecycle(lifecycle: &Value, phase: &str) -> Result<(), String> {
382    let Some(function) = map_value(lifecycle, &Value::Keyword(phase.into())).cloned() else {
383        return Ok(());
384    };
385    call_value(function, Vec::new())
386        .and_then(native_test_await)
387        .map(|_| ())
388}
389
390fn native_test_state() -> Result<crate::kernel::Namespace<Value>, String> {
391    Ok(namespace_registry()?.find_or_create("std.native.Test.state"))
392}
393
394fn native_test_state_value(key: &str) -> Result<Value, String> {
395    let state = native_test_state()?;
396    Ok(state
397        .resolve(&crate::lang::data::Symbol::parse(key))
398        .map(|var| var.deref_value())
399        .unwrap_or(Value::Nil))
400}
401
402fn native_test_set_state(key: &str, value: Value) -> Result<(), String> {
403    native_test_state()?.intern(key, value);
404    Ok(())
405}
406
407fn native_test_facts() -> Result<Vec<Value>, String> {
408    match native_test_state_value("facts")? {
409        Value::Vector(facts) => Ok(facts.iter().cloned().collect()),
410        Value::Nil => Ok(Vec::new()),
411        _ => Err("std.native.Test state facts must be a vector".into()),
412    }
413}
414
415fn native_test_set_facts(facts: Vec<Value>) -> Result<(), String> {
416    native_test_set_state("facts", Value::Vector(PVector::from_iter(facts)))
417}
418
419fn native_test_current_namespace() -> Result<String, String> {
420    Ok(namespace_registry()?.current().name().as_str().to_owned())
421}
422
423fn native_test_description(value: &Value, operation: &str) -> Result<Value, String> {
424    let desc = map_value(value, &Value::Keyword("desc".into())).cloned();
425    let name = map_value(value, &Value::Keyword("name".into())).cloned();
426    match (desc, name) {
427        (Some(Value::String(desc)), Some(Value::String(name)))
428            if !desc.is_empty() && desc == name =>
429        {
430            Ok(Value::String(desc))
431        }
432        (Some(Value::String(_)), Some(Value::String(_))) => Err(format!(
433            "std.native.Test/{operation} :desc and legacy :name must agree"
434        )),
435        (Some(Value::String(desc)), None) | (None, Some(Value::String(desc)))
436            if !desc.is_empty() =>
437        {
438            Ok(Value::String(desc))
439        }
440        (Some(_), _) | (_, Some(_)) => Err(format!(
441            "std.native.Test/{operation} :desc must be a non-empty string"
442        )),
443        (None, None) => Err(format!("std.native.Test/{operation} requires :desc")),
444    }
445}
446
447fn native_test_truthy(value: Option<&Value>) -> bool {
448    !matches!(value, None | Some(Value::Nil) | Some(Value::Bool(false)))
449}
450
451fn native_test_metadata(value: &Value, namespace: &str, order: i64) -> Result<Value, String> {
452    let metadata = map_value(value, &Value::Keyword("meta".into()))
453        .cloned()
454        .unwrap_or_else(|| Value::Map(PMap::new()));
455    let Some(entries) = map_entries(&metadata) else {
456        return Err("std.native.Test/register :meta must be a map".into());
457    };
458    let metadata = PMap::from_iter(entries)
459        .assoc_value(
460            Value::Keyword("test/namespace".into()),
461            Value::String(namespace.into()),
462        )
463        .assoc_value(Value::Keyword("test/order".into()), Value::Number(order));
464    Ok(Value::Map(metadata))
465}
466
467fn native_test_next_order() -> Result<i64, String> {
468    let current = match native_test_state_value("order")? {
469        Value::Number(value) if value >= 0 => value,
470        Value::Nil => 0,
471        _ => return Err("std.native.Test state order must be a non-negative number".into()),
472    };
473    let next = current + 1;
474    native_test_set_state("order", Value::Number(next))?;
475    Ok(next)
476}
477
478fn native_test_fact_value(
479    namespace: String,
480    desc: Value,
481    metadata: Value,
482    descriptor: &Value,
483) -> Result<Value, String> {
484    let function = map_value(descriptor, &Value::Keyword("function".into())).cloned();
485    let test = map_value(descriptor, &Value::Keyword("test".into())).cloned();
486    let expected = map_value(descriptor, &Value::Keyword("expected".into())).cloned();
487    if function.is_some() && (test.is_some() || expected.is_some()) {
488        return Err(
489            "std.native.Test/register accepts either :function or :test with :expected".into(),
490        );
491    }
492    if function.is_none() && (test.is_none() || expected.is_none()) {
493        return Err(
494            "std.native.Test/register requires :function or both :test and :expected".into(),
495        );
496    }
497    let mut fields = vec![
498        (Value::Keyword("namespace".into()), Value::String(namespace)),
499        (Value::Keyword("desc".into()), desc.clone()),
500        (Value::Keyword("name".into()), desc),
501        (Value::Keyword("meta".into()), metadata),
502    ];
503    if let Some(function) = function {
504        fields.push((Value::Keyword("function".into()), function));
505    } else {
506        fields.push((Value::Keyword("test".into()), test.expect("checked above")));
507        fields.push((
508            Value::Keyword("expected".into()),
509            expected.expect("checked above"),
510        ));
511    }
512    for key in ["before", "after"] {
513        if let Some(value) = map_value(descriptor, &Value::Keyword(key.into())).cloned() {
514            fields.push((Value::Keyword(key.into()), value));
515        }
516    }
517    Ok(Value::Map(PMap::from_iter(fields)))
518}
519
520fn native_test_register(descriptor: Value) -> Result<Value, String> {
521    if map_entries(&descriptor).is_none() {
522        return Err("std.native.Test/register expects a fact map".into());
523    }
524    let namespace = native_test_current_namespace()?;
525    let desc = native_test_description(&descriptor, "register")?;
526    let order = native_test_next_order()?;
527    let metadata = native_test_metadata(&descriptor, &namespace, order)?;
528    let fact = native_test_fact_value(namespace.clone(), desc.clone(), metadata, &descriptor)?;
529    let mut facts = native_test_facts()?;
530    facts.retain(|candidate| {
531        native_test_map_value(candidate, "namespace") != Some(Value::String(namespace.clone()))
532            || native_test_map_value(candidate, "desc") != Some(desc.clone())
533    });
534    facts.push(fact.clone());
535    native_test_set_facts(facts)?;
536    Ok(fact)
537}
538
539fn native_test_check(
540    cases: Value,
541    check_function: Option<Value>,
542    lifecycle: Option<Value>,
543) -> Result<Value, String> {
544    let cases = match cases {
545        Value::Vector(cases) => cases.iter().cloned().collect::<Vec<_>>(),
546        Value::Tuple(cases) => cases.iter().cloned().collect::<Vec<_>>(),
547        _ => return Err("std.native.Test/check expects a vector of test cases".into()),
548    };
549    let mut results = Vec::new();
550    let setup_ok = match &lifecycle {
551        Some(lifecycle) => match native_test_lifecycle(lifecycle, "setup") {
552            Ok(()) => true,
553            Err(error) => {
554                results.push(native_test_lifecycle_error("setup", error));
555                false
556            }
557        },
558        None => true,
559    };
560    if setup_ok {
561        for (index, case) in cases.iter().enumerate() {
562            let fallback_desc = Value::String(format!("invalid case {}", index + 1));
563            let Some(entries) = map_entries(case) else {
564                results.push(native_test_error(
565                    fallback_desc,
566                    Value::Nil,
567                    Value::Nil,
568                    "Test/check case must be a map".into(),
569                ));
570                continue;
571            };
572            let _ = entries;
573            let desc = native_test_description(case, "check").unwrap_or(fallback_desc);
574            let expected = map_value(case, &Value::Keyword("expected".into())).cloned();
575            let test = map_value(case, &Value::Keyword("test".into())).cloned();
576            let metadata = map_value(case, &Value::Keyword("meta".into())).cloned();
577            let result = match (test, expected) {
578                (Some(test), Some(expected)) => match &check_function {
579                    Some(check) => match call_value(check.clone(), vec![test, expected])
580                        .and_then(native_test_await)
581                    {
582                        Ok(checked) => native_test_checked_result(desc, metadata, checked),
583                        Err(error) => {
584                            let failed =
585                                native_test_error(desc.clone(), Value::Nil, Value::Nil, error);
586                            native_test_checked_result(desc, metadata, failed)
587                        }
588                    },
589                    None => match call_value(test, Vec::new()).and_then(native_test_await) {
590                        Ok(actual) => {
591                            let comparison = native_test_compare(actual.clone(), expected.clone())?;
592                            native_test_result(desc, actual, expected, comparison)?
593                        }
594                        Err(error) => native_test_error(desc, Value::Nil, expected, error),
595                    },
596                },
597                (None, expected) => native_test_error(
598                    desc,
599                    Value::Nil,
600                    expected.unwrap_or(Value::Nil),
601                    "Test/check case requires :test".into(),
602                ),
603                (Some(_), None) => native_test_error(
604                    desc,
605                    Value::Nil,
606                    Value::Nil,
607                    "Test/check case requires :expected".into(),
608                ),
609            };
610            results.push(result);
611        }
612    }
613    if let Some(lifecycle) = &lifecycle {
614        if let Err(error) = native_test_lifecycle(lifecycle, "teardown") {
615            results.push(native_test_lifecycle_error("teardown", error));
616        }
617    }
618    let output = Value::Vector(PVector::from_iter(results.clone()));
619    let mut history = match native_test_state_value("results")? {
620        Value::Vector(values) => values.iter().cloned().collect::<Vec<_>>(),
621        Value::Nil => Vec::new(),
622        _ => return Err("std.native.Test state results must be a vector".into()),
623    };
624    history.extend(results);
625    native_test_set_state("results", Value::Vector(PVector::from_iter(history)))?;
626    Ok(output)
627}
628
629fn native_test_result_passed(value: &Value) -> bool {
630    matches!(
631        value,
632        Value::Result(result) if result.is_success() && matches!(result.data, Value::Bool(true))
633    )
634}
635
636fn native_test_result_timed_out(value: &Value) -> bool {
637    let Value::Result(result) = value else {
638        return false;
639    };
640    result.is_timeout()
641        || result
642            .error
643            .as_ref()
644            .is_some_and(|error| error.message == "asynchronous test did not settle")
645}
646
647fn native_test_checks(value: Value, desc: Value) -> Vec<Value> {
648    let values = match value {
649        Value::Vector(values) => values.iter().cloned().collect(),
650        Value::Tuple(values) => values.iter().cloned().collect(),
651        Value::Result(_) => vec![value],
652        value => {
653            let context = native_test_context(
654                desc,
655                value,
656                Value::Keyword("returned".into()),
657                Value::Vector(PVector::new()),
658            );
659            return vec![Value::Result(Rc::new(
660                ResultValue::success(Value::Bool(true), context)
661                    .expect("native Test returned-value context is a map"),
662            ))];
663        }
664    };
665    values
666        .into_iter()
667        .map(|value| match value {
668            Value::Result(_) => value,
669            value => native_test_error(
670                desc.clone(),
671                value,
672                Value::Keyword("Result".into()),
673                "Test fact functions must return Results or a vector of Results".into(),
674            ),
675        })
676        .collect()
677}
678
679fn native_test_fact_identity(fact: &Value) -> Result<Vec<(Value, Value)>, String> {
680    let namespace = native_test_map_value(fact, "namespace")
681        .ok_or_else(|| "std.native.Test fact is missing :namespace".to_owned())?;
682    let desc = native_test_map_value(fact, "desc")
683        .ok_or_else(|| "std.native.Test fact is missing :desc".to_owned())?;
684    let metadata = native_test_map_value(fact, "meta")
685        .ok_or_else(|| "std.native.Test fact is missing :meta".to_owned())?;
686    Ok(vec![
687        (Value::Keyword("namespace".into()), namespace),
688        (Value::Keyword("desc".into()), desc.clone()),
689        (Value::Keyword("name".into()), desc),
690        (Value::Keyword("meta".into()), metadata),
691    ])
692}
693
694fn native_test_hook(value: Option<Value>) -> Result<(), String> {
695    let Some(function) = value else {
696        return Ok(());
697    };
698    call_value(function, Vec::new())
699        .and_then(native_test_await)
700        .map(|_| ())
701}
702
703fn native_test_cancelled(fact: &Value, options: &Value) -> Result<bool, String> {
704    let Some(value) = native_test_map_value(options, "cancelled") else {
705        return Ok(false);
706    };
707    match value {
708        Value::Bool(value) => Ok(value),
709        Value::Nil => Ok(false),
710        function => call_value(function, vec![fact.clone()])
711            .and_then(native_test_await)
712            .map(|value| native_test_truthy(Some(&value))),
713    }
714}
715
716fn native_test_fact_checks(fact: &Value, options: &Value) -> Result<Vec<Value>, String> {
717    let desc = native_test_map_value(fact, "desc")
718        .ok_or_else(|| "std.native.Test fact is missing :desc".to_owned())?;
719    if let Some(function) = native_test_map_value(fact, "function") {
720        return call_value(function, vec![options.clone()])
721            .and_then(native_test_await)
722            .map(|value| native_test_checks(value, desc));
723    }
724    let test = native_test_map_value(fact, "test")
725        .ok_or_else(|| "std.native.Test fact is missing :test".to_owned())?;
726    let expected = native_test_map_value(fact, "expected")
727        .ok_or_else(|| "std.native.Test fact is missing :expected".to_owned())?;
728    native_test_check(
729        Value::Vector(PVector::from_iter([Value::Map(PMap::from_iter([
730            (Value::Keyword("desc".into()), desc),
731            (Value::Keyword("test".into()), test),
732            (Value::Keyword("expected".into()), expected),
733        ]))])),
734        None,
735        None,
736    )
737    .and_then(|value| match value {
738        Value::Vector(values) => Ok(values.iter().cloned().collect()),
739        _ => unreachable!(),
740    })
741}
742
743fn native_test_status(checks: &[Value]) -> &'static str {
744    if checks.iter().any(native_test_result_timed_out) {
745        "timeout"
746    } else if checks
747        .iter()
748        .any(|value| matches!(value, Value::Result(result) if result.is_error()))
749    {
750        "error"
751    } else if checks.iter().all(native_test_result_passed) {
752        "passed"
753    } else {
754        "failed"
755    }
756}
757
758fn native_test_fact_result(
759    fact: &Value,
760    status: &str,
761    checks: Vec<Value>,
762    error: Option<String>,
763    elapsed: i64,
764) -> Result<Value, String> {
765    let mut fields = native_test_fact_identity(fact)?;
766    fields.push((
767        Value::Keyword("status".into()),
768        Value::Keyword(status.into()),
769    ));
770    fields.push((
771        Value::Keyword("checks".into()),
772        Value::Vector(PVector::from_iter(checks)),
773    ));
774    fields.push((
775        Value::Keyword("elapsed".into()),
776        Value::Number(elapsed.max(0)),
777    ));
778    if let Some(error) = error {
779        fields.push((Value::Keyword("error".into()), Value::String(error)));
780    }
781    Ok(Value::Map(PMap::from_iter(fields)))
782}
783
784fn native_test_run_fact(fact: Value, options: Value) -> Result<Value, String> {
785    if map_entries(&fact).is_none() {
786        return Err("std.native.Test/run-fact expects a fact map".into());
787    }
788    let started = crate::clock::time_ms();
789    let metadata = native_test_map_value(&fact, "meta")
790        .ok_or_else(|| "std.native.Test fact is missing :meta".to_owned())?;
791    if native_test_truthy(native_test_map_value(&metadata, "skip").as_ref()) {
792        return native_test_fact_result(&fact, "skipped", Vec::new(), None, 0);
793    }
794    if native_test_cancelled(&fact, &options)? {
795        return native_test_fact_result(&fact, "cancelled", Vec::new(), None, 0);
796    }
797
798    let mut checks = Vec::new();
799    let mut failure = None;
800    for hook in [
801        native_test_map_value(&options, "before-each"),
802        native_test_map_value(&fact, "before"),
803    ] {
804        if let Err(error) = native_test_hook(hook) {
805            failure = Some(error);
806            break;
807        }
808    }
809    if failure.is_none() {
810        match native_test_fact_checks(&fact, &options) {
811            Ok(output) => checks = output,
812            Err(error) => failure = Some(error),
813        }
814    }
815    for hook in [
816        native_test_map_value(&fact, "after"),
817        native_test_map_value(&options, "after-each"),
818    ] {
819        if let Err(error) = native_test_hook(hook) {
820            failure.get_or_insert(error);
821        }
822    }
823    let elapsed = crate::clock::time_ms() - started;
824    match failure {
825        Some(error) => native_test_fact_result(&fact, "error", checks, Some(error), elapsed),
826        None => native_test_fact_result(&fact, native_test_status(&checks), checks, None, elapsed),
827    }
828}
829
830fn native_test_summary(results: Vec<Value>) -> Result<Value, String> {
831    let mut counts = [0_i64; 6];
832    let mut check_total = 0_i64;
833    let mut check_passed = 0_i64;
834    let mut namespaces = HashSet::new();
835    for result in &results {
836        let status = native_test_map_value(result, "status")
837            .ok_or_else(|| "std.native.Test summary result is missing :status".to_owned())?;
838        let index = match status {
839            Value::Keyword(status) => match status.as_str() {
840                "passed" => 0,
841                "failed" => 1,
842                "error" => 2,
843                "timeout" => 3,
844                "skipped" => 4,
845                "cancelled" => 5,
846                value => {
847                    return Err(format!(
848                        "std.native.Test summary has unknown status :{value}"
849                    ))
850                }
851            },
852            _ => return Err("std.native.Test summary status must be a keyword".into()),
853        };
854        counts[index] += 1;
855        if let Some(Value::String(namespace)) = native_test_map_value(result, "namespace") {
856            namespaces.insert(namespace);
857        }
858        if let Some(Value::Vector(checks)) = native_test_map_value(result, "checks") {
859            for check in checks.iter() {
860                check_total += 1;
861                if native_test_result_passed(check) {
862                    check_passed += 1;
863                }
864            }
865        }
866    }
867    let check_failed = check_total - check_passed;
868    let status = if counts[1] + counts[2] + counts[3] == 0 {
869        "passed"
870    } else {
871        "failed"
872    };
873    Ok(Value::Map(PMap::from_iter([
874        (
875            Value::Keyword("status".into()),
876            Value::Keyword(status.into()),
877        ),
878        (
879            Value::Keyword("counts".into()),
880            Value::Map(PMap::from_iter([
881                (Value::Keyword("passed".into()), Value::Number(counts[0])),
882                (Value::Keyword("failed".into()), Value::Number(counts[1])),
883                (Value::Keyword("error".into()), Value::Number(counts[2])),
884                (Value::Keyword("timeout".into()), Value::Number(counts[3])),
885                (Value::Keyword("skipped".into()), Value::Number(counts[4])),
886                (Value::Keyword("cancelled".into()), Value::Number(counts[5])),
887            ])),
888        ),
889        (
890            Value::Keyword("check-counts".into()),
891            Value::Map(PMap::from_iter([
892                (Value::Keyword("total".into()), Value::Number(check_total)),
893                (Value::Keyword("passed".into()), Value::Number(check_passed)),
894                (Value::Keyword("failed".into()), Value::Number(check_failed)),
895            ])),
896        ),
897        (
898            Value::Keyword("files".into()),
899            Value::Number(namespaces.len() as i64),
900        ),
901        (
902            Value::Keyword("facts".into()),
903            Value::Number(results.len() as i64),
904        ),
905        (Value::Keyword("checks".into()), Value::Number(check_total)),
906        (Value::Keyword("passed".into()), Value::Number(check_passed)),
907        (Value::Keyword("failed".into()), Value::Number(check_failed)),
908        (Value::Keyword("throw".into()), Value::Number(counts[2])),
909        (Value::Keyword("timeout".into()), Value::Number(counts[3])),
910        (
911            Value::Keyword("results".into()),
912            Value::Vector(PVector::from_iter(results)),
913        ),
914    ])))
915}
916
917fn native_test_namespace(value: Value, operation: &str) -> Result<String, String> {
918    match value {
919        Value::String(value) => Ok(value),
920        Value::Symbol(value) => Ok(value.as_str().to_owned()),
921        _ => Err(format!(
922            "std.native.Test/{operation} namespace must be a string or symbol"
923        )),
924    }
925}
926
927fn native_test_run(options: Value) -> Result<Value, String> {
928    if map_entries(&options).is_none() {
929        return Err(
930            "std.native.Test/run expects an optional options map; use Test/check for cases".into(),
931        );
932    }
933    let namespace = match native_test_map_value(&options, "namespace") {
934        Some(value) => native_test_namespace(value, "run")?,
935        None => native_test_current_namespace()?,
936    };
937    let facts = native_test_facts()?
938        .into_iter()
939        .filter(|fact| {
940            native_test_map_value(fact, "namespace") == Some(Value::String(namespace.clone()))
941        })
942        .collect::<Vec<_>>();
943    let checks = match native_test_state_value("results")? {
944        Value::Vector(values) => values.iter().cloned().collect::<Vec<_>>(),
945        Value::Nil => Vec::new(),
946        _ => return Err("std.native.Test state results must be a vector".into()),
947    };
948    let mut results = checks
949        .into_iter()
950        .enumerate()
951        .map(|(index, check)| {
952            let desc = match &check {
953                Value::Result(result) => map_value(&result.context, &Value::Keyword("desc".into()))
954                    .cloned()
955                    .unwrap_or_else(|| Value::String(format!("test check {}", index + 1))),
956                _ => Value::String(format!("test check {}", index + 1)),
957            };
958            let fact = Value::Map(PMap::from_iter([
959                (
960                    Value::Keyword("namespace".into()),
961                    Value::String(namespace.clone()),
962                ),
963                (Value::Keyword("desc".into()), desc.clone()),
964                (Value::Keyword("name".into()), desc),
965                (Value::Keyword("meta".into()), Value::Map(PMap::new())),
966            ]));
967            native_test_fact_result(
968                &fact,
969                native_test_status(&[check.clone()]),
970                vec![check],
971                None,
972                0,
973            )
974        })
975        .collect::<Result<Vec<_>, _>>()?;
976    let mut suite_error = None;
977    if let Err(error) = native_test_hook(native_test_map_value(&options, "before-all")) {
978        suite_error = Some(error);
979    }
980    if suite_error.is_none() {
981        let mut fail_fast = false;
982        for fact in facts {
983            if fail_fast {
984                results.push(native_test_fact_result(
985                    &fact,
986                    "cancelled",
987                    Vec::new(),
988                    None,
989                    0,
990                )?);
991                continue;
992            }
993            let result = native_test_run_fact(fact, options.clone())?;
994            fail_fast = native_test_truthy(native_test_map_value(&options, "fail-fast").as_ref())
995                && matches!(
996                    native_test_map_value(&result, "status"),
997                    Some(Value::Keyword(status)) if matches!(status.as_str(), "failed" | "error" | "timeout")
998                );
999            results.push(result);
1000        }
1001    } else {
1002        let synthetic = Value::Map(PMap::from_iter([
1003            (
1004                Value::Keyword("namespace".into()),
1005                Value::String(namespace.clone()),
1006            ),
1007            (
1008                Value::Keyword("desc".into()),
1009                Value::String("test before-all".into()),
1010            ),
1011            (
1012                Value::Keyword("name".into()),
1013                Value::String("test before-all".into()),
1014            ),
1015            (Value::Keyword("meta".into()), Value::Map(PMap::new())),
1016        ]));
1017        results.push(native_test_fact_result(
1018            &synthetic,
1019            "error",
1020            Vec::new(),
1021            suite_error,
1022            0,
1023        )?);
1024    }
1025    if let Err(error) = native_test_hook(native_test_map_value(&options, "after-all")) {
1026        let synthetic = Value::Map(PMap::from_iter([
1027            (Value::Keyword("namespace".into()), Value::String(namespace)),
1028            (
1029                Value::Keyword("desc".into()),
1030                Value::String("test after-all".into()),
1031            ),
1032            (
1033                Value::Keyword("name".into()),
1034                Value::String("test after-all".into()),
1035            ),
1036            (Value::Keyword("meta".into()), Value::Map(PMap::new())),
1037        ]));
1038        results.push(native_test_fact_result(
1039            &synthetic,
1040            "error",
1041            Vec::new(),
1042            Some(error),
1043            0,
1044        )?);
1045    }
1046    let summary = native_test_summary(results)?;
1047    native_test_set_state("last-run", summary.clone())?;
1048    Ok(summary)
1049}
1050
1051fn native_test_lookup(namespace: &str, desc: &Value) -> Result<Value, String> {
1052    Ok(native_test_facts()?
1053        .into_iter()
1054        .find(|fact| {
1055            native_test_map_value(fact, "namespace") == Some(Value::String(namespace.into()))
1056                && native_test_map_value(fact, "desc") == Some(desc.clone())
1057        })
1058        .unwrap_or(Value::Nil))
1059}
1060
1061fn native_test_desc_argument(value: Value, operation: &str) -> Result<Value, String> {
1062    match value {
1063        Value::String(value) if !value.is_empty() => Ok(Value::String(value)),
1064        _ => Err(format!(
1065            "std.native.Test/{operation} description must be a non-empty string"
1066        )),
1067    }
1068}
1069
1070fn native_test_reset() -> Result<Value, String> {
1071    native_test_set_state("facts", Value::Vector(PVector::new()))?;
1072    native_test_set_state("results", Value::Vector(PVector::new()))?;
1073    native_test_set_state("order", Value::Number(0))?;
1074    native_test_set_state("last-run", Value::Nil)?;
1075    Ok(Value::Nil)
1076}
1077
1078fn native_test_await(value: Value) -> Result<Value, String> {
1079    match value {
1080        Value::Promise(promise) => match promise.wait_state() {
1081            PromiseState::Fulfilled(value) => Ok(value),
1082            PromiseState::Rejected(error) => Err(promise_rejection_error(error)),
1083            PromiseState::Pending => Err("asynchronous test did not settle".into()),
1084        },
1085        value => Ok(value),
1086    }
1087}
1088
1089fn native_test_require_result(value: Value, operation: &str) -> Result<Rc<ResultValue>, String> {
1090    match value {
1091        Value::Result(result) => Ok(result),
1092        _ => Err(format!("std.native.Test/{operation} expects a Result")),
1093    }
1094}
1095
1096fn native_test_context_value(result: &ResultValue, key: &str) -> Value {
1097    native_test_map_value(&result.context, key).unwrap_or(Value::Nil)
1098}
1099
1100fn native_test_map_value(value: &Value, key: &str) -> Option<Value> {
1101    map_entries(value)?
1102        .into_iter()
1103        .find_map(|(candidate, value)| {
1104            matches!(candidate, Value::Keyword(keyword) if keyword.as_str() == key).then_some(value)
1105        })
1106}
1107
1108fn native_test_detail(result: &ResultValue, key: &str) -> Value {
1109    let test = native_test_context_value(result, "test");
1110    native_test_map_value(&test, key).unwrap_or(Value::Nil)
1111}
1112
1113fn native_test_failure_shape(value: &Value) -> bool {
1114    let Some(_) = map_entries(value) else {
1115        return false;
1116    };
1117    let keyword = |key: &str| matches!(native_test_map_value(value, key), Some(Value::Keyword(_)));
1118    let vector = |key: &str| {
1119        matches!(
1120            native_test_map_value(value, key),
1121            Some(Value::Vector(_) | Value::Tuple(_))
1122        )
1123    };
1124    let string = |key: &str| matches!(native_test_map_value(value, key), Some(Value::String(_)));
1125    let map = |key: &str| {
1126        native_test_map_value(value, key).is_some_and(|value| map_entries(&value).is_some())
1127    };
1128    let children_valid = match native_test_map_value(value, "failure/children") {
1129        Some(Value::Vector(children)) => children.iter().all(native_test_failure_shape),
1130        Some(Value::Tuple(children)) => children.iter().all(native_test_failure_shape),
1131        _ => false,
1132    };
1133    keyword("failure/code")
1134        && vector("failure/path")
1135        && vector("failure/in")
1136        && native_test_map_value(value, "failure/actual").is_some()
1137        && native_test_map_value(value, "failure/expected").is_some()
1138        && string("failure/message")
1139        && map("failure/context")
1140        && vector("failure/children")
1141        && children_valid
1142}
1143
1144fn native_test_failure_leaves(value: &Value, leaves: &mut Vec<Value>) {
1145    if !native_test_failure_shape(value) {
1146        return;
1147    }
1148    match native_test_map_value(value, "failure/children") {
1149        Some(Value::Vector(children)) => {
1150            if children.is_empty() {
1151                leaves.push(value.clone());
1152            } else {
1153                for child in children.iter() {
1154                    native_test_failure_leaves(child, leaves);
1155                }
1156            }
1157        }
1158        Some(Value::Tuple(children)) => {
1159            if children.is_empty() {
1160                leaves.push(value.clone());
1161            } else {
1162                for child in children.iter() {
1163                    native_test_failure_leaves(child, leaves);
1164                }
1165            }
1166        }
1167        _ => {}
1168    }
1169}
1170
1171fn native_test_failures(result: &ResultValue) -> Value {
1172    match native_test_context_value(result, "failures") {
1173        Value::Vector(failures) => Value::Vector(failures),
1174        Value::Tuple(failures) => Value::Vector(PVector::from_iter(failures.iter().cloned())),
1175        _ => Value::Vector(PVector::new()),
1176    }
1177}
1178
1179fn native_test_failure_seq(result: &ResultValue) -> Value {
1180    let mut leaves = Vec::new();
1181    if let Value::Vector(failures) = native_test_failures(result) {
1182        for failure in failures.iter() {
1183            native_test_failure_leaves(failure, &mut leaves);
1184        }
1185    }
1186    Value::Vector(PVector::from_iter(leaves))
1187}
1188
1189fn native_test_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
1190    let operation = operation
1191        .strip_prefix("std.native.Test/")
1192        .unwrap_or(operation);
1193    match operation {
1194        "events" => {
1195            if !values.is_empty() {
1196                return Err("std.native.Test/events expects no arguments".into());
1197            }
1198            Ok(native_test_events())
1199        }
1200        "catalog" => {
1201            if !values.is_empty() {
1202                return Err("std.native.Test/catalog expects no arguments".into());
1203            }
1204            Ok(Value::Map(PMap::from_iter([
1205                (
1206                    Value::Keyword("runners".into()),
1207                    Value::Vector(PVector::from_iter([
1208                        Value::Keyword("code.test".into()),
1209                        Value::Keyword("native".into()),
1210                    ])),
1211                ),
1212                (
1213                    Value::Keyword("default".into()),
1214                    Value::Keyword("code.test".into()),
1215                ),
1216                (
1217                    Value::Keyword("runner".into()),
1218                    native_test_active_runner()?,
1219                ),
1220                (
1221                    Value::Keyword("context".into()),
1222                    Value::Keyword("test".into()),
1223                ),
1224                (Value::Keyword("events".into()), native_test_events()),
1225            ])))
1226        }
1227        "config" => {
1228            if values.len() > 1 {
1229                return Err("std.native.Test/config expects optional options".into());
1230            }
1231            let options = if values.is_empty() {
1232                Value::Map(PMap::new())
1233            } else {
1234                values[0].clone()
1235            };
1236            native_test_config(native_test_active_runner()?, options)
1237        }
1238        "context" => {
1239            if values.len() > 1 {
1240                return Err("std.native.Test/context expects an optional config".into());
1241            }
1242            let config = if values.is_empty() {
1243                native_test_config(native_test_active_runner()?, Value::Map(PMap::new()))?
1244            } else {
1245                let value = values[0].clone();
1246                let Some(runner) = map_value(&value, &Value::Keyword("runner".into())).cloned()
1247                else {
1248                    return Err("std.native.Test/context expects a Test/config map".into());
1249                };
1250                let runner = native_test_runner(runner)?;
1251                if runner != native_test_active_runner()? {
1252                    return Err(
1253                        "std.native.Test/context config runner does not match the runtime".into(),
1254                    );
1255                }
1256                value
1257            };
1258            Ok(Value::Pointer(PPointer::new(
1259                "test".into(),
1260                PMap::from_iter([
1261                    (Value::Keyword("id".into()), Value::Keyword("test".into())),
1262                    (Value::Keyword("config".into()), config),
1263                ]),
1264            )))
1265        }
1266        "compare" => {
1267            if values.len() != 2 {
1268                return Err("std.native.Test/compare expects actual and expected".into());
1269            }
1270            native_test_compare(values[0].clone(), values[1].clone())
1271        }
1272        "result" => {
1273            if values.len() != 4 {
1274                return Err(
1275                    "std.native.Test/result expects name, actual, expected, and comparison Result"
1276                        .into(),
1277                );
1278            }
1279            let name = values[0].clone();
1280            let actual = values[1].clone();
1281            let expected = values[2].clone();
1282            let comparison = values[3].clone();
1283            native_test_result(name, actual, expected, comparison)
1284        }
1285        "check" => {
1286            if values.is_empty() || values.len() > 3 {
1287                return Err(
1288                    "std.native.Test/check expects cases, an optional check function, and an optional lifecycle map".into(),
1289                );
1290            }
1291            let cases = values[0].clone();
1292            let second = if values.len() >= 2 {
1293                Some(values[1].clone())
1294            } else {
1295                None
1296            };
1297            let third = if values.len() == 3 {
1298                Some(values[2].clone())
1299            } else {
1300                None
1301            };
1302            let (check_function, lifecycle) = match (second, third) {
1303                (Some(check), Some(lifecycle)) => (Some(check), Some(lifecycle)),
1304                (Some(value), None) if map_entries(&value).is_some() => (None, Some(value)),
1305                (check, None) => (check, None),
1306                (None, Some(_)) => unreachable!(),
1307            };
1308            if lifecycle
1309                .as_ref()
1310                .is_some_and(|value| map_entries(value).is_none())
1311            {
1312                return Err("std.native.Test/check lifecycle must be a map".into());
1313            }
1314            native_test_check(cases, check_function, lifecycle)
1315        }
1316        "register" => match values.as_slice() {
1317            [fact] => native_test_register(fact.clone()),
1318            _ => Err("std.native.Test/register expects one fact map".into()),
1319        },
1320        "facts" => {
1321            if values.len() > 1 {
1322                return Err("std.native.Test/facts expects an optional namespace".into());
1323            }
1324            let namespace = match values.as_slice() {
1325                [] => native_test_current_namespace()?,
1326                [namespace] => native_test_namespace(namespace.clone(), "facts")?,
1327                _ => unreachable!(),
1328            };
1329            Ok(Value::Vector(PVector::from_iter(
1330                native_test_facts()?.into_iter().filter(|fact| {
1331                    native_test_map_value(fact, "namespace")
1332                        == Some(Value::String(namespace.clone()))
1333                }),
1334            )))
1335        }
1336        "get" => {
1337            let (namespace, desc) = match values.as_slice() {
1338                [desc] => (
1339                    native_test_current_namespace()?,
1340                    native_test_desc_argument(desc.clone(), "get")?,
1341                ),
1342                [namespace, desc] => (
1343                    native_test_namespace(namespace.clone(), "get")?,
1344                    native_test_desc_argument(desc.clone(), "get")?,
1345                ),
1346                _ => {
1347                    return Err(
1348                        "std.native.Test/get expects a description and optional namespace".into(),
1349                    )
1350                }
1351            };
1352            native_test_lookup(&namespace, &desc)
1353        }
1354        "remove" => {
1355            let (namespace, desc) = match values.as_slice() {
1356                [desc] => (
1357                    native_test_current_namespace()?,
1358                    native_test_desc_argument(desc.clone(), "remove")?,
1359                ),
1360                [namespace, desc] => (
1361                    native_test_namespace(namespace.clone(), "remove")?,
1362                    native_test_desc_argument(desc.clone(), "remove")?,
1363                ),
1364                _ => {
1365                    return Err(
1366                        "std.native.Test/remove expects a description and optional namespace"
1367                            .into(),
1368                    )
1369                }
1370            };
1371            let removed = native_test_lookup(&namespace, &desc)?;
1372            let mut facts = native_test_facts()?;
1373            facts.retain(|fact| {
1374                native_test_map_value(fact, "namespace") != Some(Value::String(namespace.clone()))
1375                    || native_test_map_value(fact, "desc") != Some(desc.clone())
1376            });
1377            native_test_set_facts(facts)?;
1378            Ok(removed)
1379        }
1380        "purge" => {
1381            if values.len() > 1 {
1382                return Err("std.native.Test/purge expects an optional namespace".into());
1383            }
1384            let namespace = match values.as_slice() {
1385                [] => native_test_current_namespace()?,
1386                [namespace] => native_test_namespace(namespace.clone(), "purge")?,
1387                _ => unreachable!(),
1388            };
1389            let facts = native_test_facts()?;
1390            let removed = facts
1391                .iter()
1392                .filter(|fact| {
1393                    native_test_map_value(fact, "namespace")
1394                        == Some(Value::String(namespace.clone()))
1395                })
1396                .cloned()
1397                .collect::<Vec<_>>();
1398            native_test_set_facts(
1399                facts
1400                    .into_iter()
1401                    .filter(|fact| {
1402                        native_test_map_value(fact, "namespace")
1403                            != Some(Value::String(namespace.clone()))
1404                    })
1405                    .collect(),
1406            )?;
1407            Ok(Value::Vector(PVector::from_iter(removed)))
1408        }
1409        "reset" => {
1410            if !values.is_empty() {
1411                return Err("std.native.Test/reset expects no arguments".into());
1412            }
1413            native_test_reset()
1414        }
1415        "run-fact" => {
1416            let (fact, options) = match values.as_slice() {
1417                [fact] => (fact.clone(), Value::Map(PMap::new())),
1418                [fact, options] if map_entries(options).is_some() => {
1419                    (fact.clone(), options.clone())
1420                }
1421                [_, _] => return Err("std.native.Test/run-fact options must be a map".into()),
1422                _ => return Err(
1423                    "std.native.Test/run-fact expects a fact or description and optional options"
1424                        .into(),
1425                ),
1426            };
1427            let fact = if map_entries(&fact).is_some() {
1428                fact
1429            } else {
1430                let desc = native_test_desc_argument(fact, "run-fact")?;
1431                let namespace = native_test_current_namespace()?;
1432                let fact = native_test_lookup(&namespace, &desc)?;
1433                if matches!(fact, Value::Nil) {
1434                    return Err(format!(
1435                        "std.native.Test/run-fact fact not found: {}",
1436                        desc.display()
1437                    ));
1438                }
1439                fact
1440            };
1441            native_test_run_fact(fact, options)
1442        }
1443        "run" => {
1444            if values.len() > 1 {
1445                return Err(
1446                    "std.native.Test/run expects an optional options map; use Test/check for cases"
1447                        .into(),
1448                );
1449            }
1450            let options = values
1451                .into_iter()
1452                .next()
1453                .unwrap_or_else(|| Value::Map(PMap::new()));
1454            native_test_run(options)
1455        }
1456        "summary" => match values.as_slice() {
1457            [Value::Vector(results)] => native_test_summary(results.iter().cloned().collect()),
1458            [Value::Tuple(results)] => native_test_summary(results.iter().cloned().collect()),
1459            [_] => Err("std.native.Test/summary expects a vector of fact results".into()),
1460            _ => Err("std.native.Test/summary expects one vector of fact results".into()),
1461        },
1462        "passed?" => {
1463            if values.len() != 1 {
1464                return Err("std.native.Test/passed? expects one result".into());
1465            }
1466            let result = native_test_require_result(values[0].clone(), "passed?")?;
1467            Ok(Value::Bool(
1468                result.is_success() && matches!(result.data, Value::Bool(true)),
1469            ))
1470        }
1471        "actual" | "expected" | "failures" | "failure-seq" | "failure-count" => {
1472            if values.len() != 1 {
1473                return Err(format!("std.native.Test/{operation} expects one Result"));
1474            }
1475            let result = native_test_require_result(values[0].clone(), operation)?;
1476            Ok(match operation {
1477                "actual" => native_test_detail(&result, "actual"),
1478                "expected" => native_test_detail(&result, "expected"),
1479                "failures" => native_test_failures(&result),
1480                "failure-seq" => native_test_failure_seq(&result),
1481                "failure-count" => match native_test_failure_seq(&result) {
1482                    Value::Vector(values) => Value::Number(values.len() as i64),
1483                    _ => unreachable!(),
1484                },
1485                _ => unreachable!(),
1486            })
1487        }
1488        "failure" => {
1489            if values.len() != 2 {
1490                return Err("std.native.Test/failure expects a Result and index".into());
1491            }
1492            let result = native_test_require_result(values[0].clone(), "failure")?;
1493            let index = match &values[1] {
1494                Value::Number(index) if *index >= 0 => *index as usize,
1495                _ => {
1496                    return Err(
1497                        "std.native.Test/failure index must be a non-negative integer".into(),
1498                    )
1499                }
1500            };
1501            match native_test_failure_seq(&result) {
1502                Value::Vector(values) => Ok(values.get(index).cloned().unwrap_or(Value::Nil)),
1503                _ => unreachable!(),
1504            }
1505        }
1506        "failure?" => {
1507            if values.len() != 1 {
1508                return Err("std.native.Test/failure? expects one value".into());
1509            }
1510            Ok(Value::Bool(native_test_failure_shape(&values[0])))
1511        }
1512        _ => Err(format!("unknown std.native.Test operation: {operation}")),
1513    }
1514}
1515
1516struct NativeCommandRequest {
1517    app: u64,
1518    request: crate::command::Request,
1519    context: Value,
1520}
1521
1522struct NativeCommandSnapshot {
1523    app: u64,
1524    snapshot: crate::command::Snapshot<Value>,
1525}
1526
1527struct NativeCommandState {
1528    next_app: u64,
1529    next_request: u64,
1530    next_snapshot: u64,
1531    apps: HashMap<u64, crate::command::App<Value>>,
1532    requests: HashMap<u64, NativeCommandRequest>,
1533    snapshots: HashMap<u64, NativeCommandSnapshot>,
1534}
1535
1536impl Default for NativeCommandState {
1537    fn default() -> Self {
1538        Self {
1539            next_app: 1,
1540            next_request: 1,
1541            next_snapshot: 1,
1542            apps: HashMap::new(),
1543            requests: HashMap::new(),
1544            snapshots: HashMap::new(),
1545        }
1546    }
1547}
1548
1549thread_local! {
1550    static NATIVE_COMMAND_STATE: RefCell<NativeCommandState> = RefCell::new(NativeCommandState::default());
1551}
1552
1553fn native_command_keyword(name: &str) -> Value {
1554    Value::Keyword(Keyword::from(name))
1555}
1556
1557fn native_command_map(entries: impl IntoIterator<Item = (Value, Value)>) -> Value {
1558    Value::Map(PMap::from_iter(entries))
1559}
1560
1561fn native_command_pointer(
1562    context: &str,
1563    entries: impl IntoIterator<Item = (Value, Value)>,
1564) -> Value {
1565    Value::Pointer(PPointer::new(
1566        Keyword::from(context),
1567        PMap::from_iter(entries),
1568    ))
1569}
1570
1571fn native_command_handle(value: &Value, context: &str, operation: &str) -> Result<u64, String> {
1572    let Value::Pointer(pointer) = value else {
1573        return Err(format!(
1574            "std.native.Command/{operation} expects a {context} handle"
1575        ));
1576    };
1577    if pointer.context().as_str() != context {
1578        return Err(format!(
1579            "std.native.Command/{operation} expects a {context} handle"
1580        ));
1581    }
1582    match pointer.get(&native_command_keyword("id")) {
1583        Some(Value::Number(id)) if *id > 0 => Ok(*id as u64),
1584        _ => Err(format!(
1585            "std.native.Command/{operation} received an invalid {context} handle"
1586        )),
1587    }
1588}
1589
1590fn native_command_app(value: &Value, operation: &str) -> Result<u64, String> {
1591    native_command_handle(value, "command/app", operation)
1592}
1593
1594fn native_command_route(
1595    value: &Value,
1596    app: u64,
1597    operation: &str,
1598) -> Result<crate::command::RouteHandle, String> {
1599    let route = native_command_handle(value, "command/route", operation)?;
1600    let Value::Pointer(pointer) = value else {
1601        unreachable!()
1602    };
1603    match pointer.get(&native_command_keyword("app")) {
1604        Some(Value::Number(candidate)) if *candidate == app as i64 => {
1605            Ok(crate::command::RouteHandle::from_id(route))
1606        }
1607        _ => Err(format!(
1608            "std.native.Command/{operation} route belongs to a different application"
1609        )),
1610    }
1611}
1612
1613fn native_command_config_argument(
1614    value: &Value,
1615    operation: &str,
1616) -> Result<crate::command::AppConfig, String> {
1617    let Some(entries) = map_entries(value) else {
1618        return Err(format!(
1619            "std.native.Command/{operation} expects a config map"
1620        ));
1621    };
1622    let config = Value::Map(PMap::from_iter(entries));
1623    let id = match map_value(&config, &native_command_keyword("id")) {
1624        Some(Value::Symbol(id)) if !id.as_str().is_empty() => id.as_str().to_owned(),
1625        _ => {
1626            return Err(format!(
1627                "std.native.Command/{operation} config :id must be a symbol"
1628            ))
1629        }
1630    };
1631    let desc = match map_value(&config, &native_command_keyword("desc")) {
1632        Some(Value::String(desc)) if !desc.trim().is_empty() => desc.clone(),
1633        _ => {
1634            return Err(format!(
1635                "std.native.Command/{operation} config :desc must be a non-empty string"
1636            ))
1637        }
1638    };
1639    Ok(crate::command::AppConfig { id, desc })
1640}
1641
1642fn native_command_vector(
1643    value: &Value,
1644    operation: &str,
1645    field: &str,
1646) -> Result<Vec<Value>, String> {
1647    match value {
1648        Value::Vector(values) => Ok(values.iter().cloned().collect()),
1649        Value::Tuple(values) => Ok(values.iter().cloned().collect()),
1650        _ => Err(format!(
1651            "std.native.Command/{operation} {field} must be a vector"
1652        )),
1653    }
1654}
1655
1656fn native_command_string(value: &Value, operation: &str, field: &str) -> Result<String, String> {
1657    match value {
1658        Value::String(value) => Ok(value.clone()),
1659        _ => Err(format!(
1660            "std.native.Command/{operation} {field} must be a string"
1661        )),
1662    }
1663}
1664
1665fn native_command_strings(
1666    value: &Value,
1667    operation: &str,
1668    field: &str,
1669) -> Result<Vec<String>, String> {
1670    native_command_vector(value, operation, field)?
1671        .into_iter()
1672        .map(|value| native_command_string(&value, operation, field))
1673        .collect()
1674}
1675
1676fn native_command_keyword_argument(
1677    value: &Value,
1678    operation: &str,
1679    field: &str,
1680) -> Result<String, String> {
1681    match value {
1682        Value::Keyword(value) if !value.as_str().is_empty() => Ok(value.as_str().to_owned()),
1683        _ => Err(format!(
1684            "std.native.Command/{operation} {field} must be a keyword"
1685        )),
1686    }
1687}
1688
1689fn native_command_boolean(
1690    value: Option<&Value>,
1691    operation: &str,
1692    field: &str,
1693    fallback: bool,
1694) -> Result<bool, String> {
1695    match value {
1696        None | Some(Value::Nil) => Ok(fallback),
1697        Some(Value::Bool(value)) => Ok(*value),
1698        _ => Err(format!(
1699            "std.native.Command/{operation} {field} must be a boolean"
1700        )),
1701    }
1702}
1703
1704fn native_command_option(
1705    value: Value,
1706    operation: &str,
1707) -> Result<crate::command::OptionSpec, String> {
1708    let Some(entries) = map_entries(&value) else {
1709        return Err(format!(
1710            "std.native.Command/{operation} :options entries must be maps"
1711        ));
1712    };
1713    let value = Value::Map(PMap::from_iter(entries));
1714    let id = native_command_keyword_argument(
1715        map_value(&value, &native_command_keyword("id"))
1716            .ok_or_else(|| format!("std.native.Command/{operation} option :id is required"))?,
1717        operation,
1718        "option :id",
1719    )?;
1720    let long = match map_value(&value, &native_command_keyword("long")) {
1721        None | Some(Value::Nil) => None,
1722        Some(value) => Some(native_command_string(value, operation, "option :long")?),
1723    };
1724    let short = match map_value(&value, &native_command_keyword("short")) {
1725        None | Some(Value::Nil) => None,
1726        Some(Value::String(value)) if value.chars().count() == 1 => value.chars().next(),
1727        _ => {
1728            return Err(format!(
1729                "std.native.Command/{operation} option :short must be one character"
1730            ))
1731        }
1732    };
1733    let kind = match map_value(&value, &native_command_keyword("type")) {
1734        Some(Value::Keyword(kind)) if kind.as_str() == "boolean" => {
1735            crate::command::OptionKind::Boolean
1736        }
1737        Some(Value::Keyword(kind)) if kind.as_str() == "string" => {
1738            crate::command::OptionKind::String
1739        }
1740        _ => {
1741            return Err(format!(
1742                "std.native.Command/{operation} option :type must be :boolean or :string"
1743            ))
1744        }
1745    };
1746    let many = native_command_boolean(
1747        map_value(&value, &native_command_keyword("many?")),
1748        operation,
1749        "option :many?",
1750        false,
1751    )?;
1752    let default = match map_value(&value, &native_command_keyword("default")) {
1753        None | Some(Value::Nil) => None,
1754        Some(Value::Bool(value)) => Some(crate::command::ParsedValue::Boolean(*value)),
1755        Some(Value::String(value)) => Some(crate::command::ParsedValue::String(value.clone())),
1756        Some(value) => Some(crate::command::ParsedValue::Strings(
1757            native_command_strings(value, operation, "option :default")?,
1758        )),
1759    };
1760    Ok(crate::command::OptionSpec {
1761        id,
1762        long,
1763        short,
1764        kind,
1765        many,
1766        default,
1767    })
1768}
1769
1770fn native_command_argument(
1771    value: Value,
1772    operation: &str,
1773) -> Result<crate::command::ArgumentSpec, String> {
1774    let Some(entries) = map_entries(&value) else {
1775        return Err(format!(
1776            "std.native.Command/{operation} :arguments entries must be maps"
1777        ));
1778    };
1779    let value = Value::Map(PMap::from_iter(entries));
1780    Ok(crate::command::ArgumentSpec {
1781        id: native_command_keyword_argument(
1782            map_value(&value, &native_command_keyword("id")).ok_or_else(|| {
1783                format!("std.native.Command/{operation} argument :id is required")
1784            })?,
1785            operation,
1786            "argument :id",
1787        )?,
1788        required: native_command_boolean(
1789            map_value(&value, &native_command_keyword("required?")),
1790            operation,
1791            "argument :required?",
1792            true,
1793        )?,
1794        many: native_command_boolean(
1795            map_value(&value, &native_command_keyword("many?")),
1796            operation,
1797            "argument :many?",
1798            false,
1799        )?,
1800    })
1801}
1802
1803fn native_command_route_argument(
1804    value: Value,
1805    operation: &str,
1806) -> Result<crate::command::Route<Value>, String> {
1807    let Some(entries) = map_entries(&value) else {
1808        return Err(format!(
1809            "std.native.Command/{operation} expects a route map"
1810        ));
1811    };
1812    let value = Value::Map(PMap::from_iter(entries));
1813    let path = native_command_strings(
1814        map_value(&value, &native_command_keyword("path"))
1815            .ok_or_else(|| format!("std.native.Command/{operation} route :path is required"))?,
1816        operation,
1817        "route :path",
1818    )?;
1819    let aliases = match map_value(&value, &native_command_keyword("aliases")) {
1820        None | Some(Value::Nil) => Vec::new(),
1821        Some(value) => native_command_vector(value, operation, "route :aliases")?
1822            .into_iter()
1823            .map(|alias| native_command_strings(&alias, operation, "route :aliases"))
1824            .collect::<Result<Vec<_>, _>>()?,
1825    };
1826    let options = match map_value(&value, &native_command_keyword("options")) {
1827        None | Some(Value::Nil) => Vec::new(),
1828        Some(value) => native_command_vector(value, operation, "route :options")?
1829            .into_iter()
1830            .map(|value| native_command_option(value, operation))
1831            .collect::<Result<Vec<_>, _>>()?,
1832    };
1833    let arguments = match map_value(&value, &native_command_keyword("arguments")) {
1834        None | Some(Value::Nil) => Vec::new(),
1835        Some(value) => native_command_vector(value, operation, "route :arguments")?
1836            .into_iter()
1837            .map(|value| native_command_argument(value, operation))
1838            .collect::<Result<Vec<_>, _>>()?,
1839    };
1840    let handler = match map_value(&value, &native_command_keyword("handler")) {
1841        Some(Value::Function(function)) => Value::Function(function.clone()),
1842        _ => {
1843            return Err(format!(
1844                "std.native.Command/{operation} route :handler must be a function"
1845            ))
1846        }
1847    };
1848    Ok(crate::command::Route {
1849        spec: crate::command::RouteSpec {
1850            id: native_command_keyword_argument(
1851                map_value(&value, &native_command_keyword("id")).ok_or_else(|| {
1852                    format!("std.native.Command/{operation} route :id is required")
1853                })?,
1854                operation,
1855                "route :id",
1856            )?,
1857            path,
1858            aliases,
1859            desc: native_command_string(
1860                map_value(&value, &native_command_keyword("desc")).ok_or_else(|| {
1861                    format!("std.native.Command/{operation} route :desc is required")
1862                })?,
1863                operation,
1864                "route :desc",
1865            )?,
1866            options,
1867            arguments,
1868            passthrough: native_command_boolean(
1869                map_value(&value, &native_command_keyword("passthrough?")),
1870                operation,
1871                "route :passthrough?",
1872                false,
1873            )?,
1874        },
1875        handler,
1876    })
1877}
1878
1879fn native_command_parsed_value(value: &crate::command::ParsedValue) -> Value {
1880    match value {
1881        crate::command::ParsedValue::Boolean(value) => Value::Bool(*value),
1882        crate::command::ParsedValue::String(value) => Value::String(value.clone()),
1883        crate::command::ParsedValue::Strings(values) => Value::Vector(PVector::from_iter(
1884            values.iter().cloned().map(Value::String),
1885        )),
1886    }
1887}
1888
1889fn native_command_spec_value(spec: &crate::command::RouteSpec) -> Value {
1890    native_command_map([
1891        (
1892            native_command_keyword("id"),
1893            native_command_keyword(&spec.id),
1894        ),
1895        (
1896            native_command_keyword("path"),
1897            Value::Vector(PVector::from_iter(
1898                spec.path.iter().cloned().map(Value::String),
1899            )),
1900        ),
1901        (
1902            native_command_keyword("aliases"),
1903            Value::Vector(PVector::from_iter(spec.aliases.iter().map(|alias| {
1904                Value::Vector(PVector::from_iter(alias.iter().cloned().map(Value::String)))
1905            }))),
1906        ),
1907        (
1908            native_command_keyword("desc"),
1909            Value::String(spec.desc.clone()),
1910        ),
1911        (
1912            native_command_keyword("passthrough?"),
1913            Value::Bool(spec.passthrough),
1914        ),
1915        (
1916            native_command_keyword("options"),
1917            Value::Vector(PVector::from_iter(spec.options.iter().map(|option| {
1918                let mut entries = vec![
1919                    (
1920                        native_command_keyword("id"),
1921                        native_command_keyword(&option.id),
1922                    ),
1923                    (
1924                        native_command_keyword("long"),
1925                        Value::String(option.long_name()),
1926                    ),
1927                    (
1928                        native_command_keyword("short"),
1929                        option
1930                            .short
1931                            .map(|short| Value::String(short.into()))
1932                            .unwrap_or(Value::Nil),
1933                    ),
1934                    (
1935                        native_command_keyword("type"),
1936                        native_command_keyword(match option.kind {
1937                            crate::command::OptionKind::Boolean => "boolean",
1938                            crate::command::OptionKind::String => "string",
1939                        }),
1940                    ),
1941                    (native_command_keyword("many?"), Value::Bool(option.many)),
1942                ];
1943                if let Some(default) = &option.default {
1944                    entries.push((
1945                        native_command_keyword("default"),
1946                        native_command_parsed_value(default),
1947                    ));
1948                }
1949                native_command_map(entries)
1950            }))),
1951        ),
1952        (
1953            native_command_keyword("arguments"),
1954            Value::Vector(PVector::from_iter(spec.arguments.iter().map(|argument| {
1955                native_command_map([
1956                    (
1957                        native_command_keyword("id"),
1958                        native_command_keyword(&argument.id),
1959                    ),
1960                    (
1961                        native_command_keyword("required?"),
1962                        Value::Bool(argument.required),
1963                    ),
1964                    (native_command_keyword("many?"), Value::Bool(argument.many)),
1965                ])
1966            }))),
1967        ),
1968    ])
1969}
1970
1971fn native_command_invocation(
1972    value: Value,
1973    operation: &str,
1974) -> Result<(Vec<String>, Value), String> {
1975    let Some(entries) = map_entries(&value) else {
1976        return Err(format!(
1977            "std.native.Command/{operation} expects an invocation map"
1978        ));
1979    };
1980    let value = Value::Map(PMap::from_iter(entries));
1981    let argv = native_command_strings(
1982        map_value(&value, &native_command_keyword("argv")).ok_or_else(|| {
1983            format!("std.native.Command/{operation} invocation :argv is required")
1984        })?,
1985        operation,
1986        "invocation :argv",
1987    )?;
1988    let context = map_value(&value, &native_command_keyword("context"))
1989        .cloned()
1990        .unwrap_or_else(|| Value::Map(PMap::new()));
1991    if map_entries(&context).is_none() {
1992        return Err(format!(
1993            "std.native.Command/{operation} invocation :context must be a map"
1994        ));
1995    }
1996    Ok((argv, context))
1997}
1998
1999fn native_command_request_value(
2000    request_id: u64,
2001    request: &crate::command::Request,
2002    context: Value,
2003) -> Value {
2004    native_command_map([
2005        (
2006            native_command_keyword("app/id"),
2007            Value::Symbol(Symbol::parse(&request.app_id)),
2008        ),
2009        (
2010            native_command_keyword("route/id"),
2011            native_command_keyword(&request.route_id),
2012        ),
2013        (
2014            native_command_keyword("route/path"),
2015            Value::Vector(PVector::from_iter(
2016                request.route_path.iter().cloned().map(Value::String),
2017            )),
2018        ),
2019        (
2020            native_command_keyword("argv"),
2021            Value::Vector(PVector::from_iter(
2022                request.argv.iter().cloned().map(Value::String),
2023            )),
2024        ),
2025        (
2026            native_command_keyword("arguments"),
2027            native_command_map(request.arguments.iter().map(|(key, value)| {
2028                (
2029                    native_command_keyword(key),
2030                    native_command_parsed_value(value),
2031                )
2032            })),
2033        ),
2034        (
2035            native_command_keyword("options"),
2036            native_command_map(request.options.iter().map(|(key, value)| {
2037                (
2038                    native_command_keyword(key),
2039                    native_command_parsed_value(value),
2040                )
2041            })),
2042        ),
2043        (native_command_keyword("context"), context),
2044        (
2045            native_command_keyword("command/request"),
2046            native_command_pointer(
2047                "command/request",
2048                [(
2049                    native_command_keyword("id"),
2050                    Value::Number(request_id as i64),
2051                )],
2052            ),
2053        ),
2054    ])
2055}
2056
2057fn native_command_response_value(response: crate::command::Response) -> Value {
2058    native_command_map([
2059        (
2060            native_command_keyword("stdout"),
2061            Value::String(response.stdout),
2062        ),
2063        (
2064            native_command_keyword("stderr"),
2065            Value::String(response.stderr),
2066        ),
2067        (native_command_keyword("exit"), Value::Number(response.exit)),
2068    ])
2069}
2070
2071fn native_command_response_argument(
2072    value: Value,
2073    operation: &str,
2074) -> Result<crate::command::Response, String> {
2075    let Some(entries) = map_entries(&value) else {
2076        return Err(format!(
2077            "std.native.Command/{operation} handler must return a response map"
2078        ));
2079    };
2080    if entries.len() != 3 {
2081        return Err(format!(
2082            "std.native.Command/{operation} response must contain only :stdout, :stderr, and :exit"
2083        ));
2084    }
2085    let value = Value::Map(PMap::from_iter(entries));
2086    let stdout = native_command_string(
2087        map_value(&value, &native_command_keyword("stdout")).ok_or_else(|| {
2088            format!("std.native.Command/{operation} response :stdout is required")
2089        })?,
2090        operation,
2091        "response :stdout",
2092    )?;
2093    let stderr = native_command_string(
2094        map_value(&value, &native_command_keyword("stderr")).ok_or_else(|| {
2095            format!("std.native.Command/{operation} response :stderr is required")
2096        })?,
2097        operation,
2098        "response :stderr",
2099    )?;
2100    let exit = match map_value(&value, &native_command_keyword("exit")) {
2101        Some(Value::Number(value)) => *value,
2102        _ => {
2103            return Err(format!(
2104                "std.native.Command/{operation} response :exit must be an integer"
2105            ))
2106        }
2107    };
2108    crate::command::Response {
2109        stdout,
2110        stderr,
2111        exit,
2112    }
2113    .checked()
2114    .map_err(|error| error.to_string())
2115}
2116
2117fn native_command_parse(app: u64, invocation: Value, operation: &str) -> Result<Value, String> {
2118    let (argv, context) = native_command_invocation(invocation, operation)?;
2119    NATIVE_COMMAND_STATE.with(|state| {
2120        let mut state = state.borrow_mut();
2121        let request = state
2122            .apps
2123            .get(&app)
2124            .ok_or_else(|| format!("std.native.Command/{operation} application was not found"))?
2125            .parse(argv)
2126            .map_err(|error| error.to_string())?;
2127        let request_id = state.next_request;
2128        state.next_request += 1;
2129        state.requests.insert(
2130            request_id,
2131            NativeCommandRequest {
2132                app,
2133                request: request.clone(),
2134                context: context.clone(),
2135            },
2136        );
2137        Ok(native_command_request_value(request_id, &request, context))
2138    })
2139}
2140
2141fn native_command_request_id(value: &Value, operation: &str) -> Result<u64, String> {
2142    let Some(request) = map_value(value, &native_command_keyword("command/request")) else {
2143        return Err(format!(
2144            "std.native.Command/{operation} expects a Command/parse request"
2145        ));
2146    };
2147    native_command_handle(request, "command/request", operation)
2148}
2149
2150fn native_command_dispatch(
2151    app: u64,
2152    request_value: Value,
2153    operation: &str,
2154) -> Result<Value, String> {
2155    let request_id = native_command_request_id(&request_value, operation)?;
2156    let (handler, request, context) = NATIVE_COMMAND_STATE.with(|state| {
2157        let state = state.borrow();
2158        let stored = state
2159            .requests
2160            .get(&request_id)
2161            .ok_or_else(|| format!("std.native.Command/{operation} request was not found"))?;
2162        if stored.app != app {
2163            return Err(format!(
2164                "std.native.Command/{operation} request belongs to a different application"
2165            ));
2166        }
2167        let application = state
2168            .apps
2169            .get(&app)
2170            .ok_or_else(|| format!("std.native.Command/{operation} application was not found"))?;
2171        let handler = application
2172            .handler(&stored.request)
2173            .map_err(|error| error.to_string())?
2174            .clone();
2175        Ok::<_, String>((handler, stored.request.clone(), stored.context.clone()))
2176    })?;
2177    let output = call_value(
2178        handler,
2179        vec![native_command_request_value(request_id, &request, context)],
2180    )?;
2181    native_command_response_argument(output, operation).map(native_command_response_value)
2182}
2183
2184fn native_command_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
2185    let operation = operation
2186        .strip_prefix("std.native.Command/")
2187        .unwrap_or(operation);
2188    match operation {
2189        "create" => match values.as_slice() {
2190            [config] => {
2191                let config = native_command_config_argument(config, operation)?;
2192                NATIVE_COMMAND_STATE.with(|state| {
2193                    let mut state = state.borrow_mut();
2194                    let id = state.next_app;
2195                    state.next_app += 1;
2196                    state.apps.insert(
2197                        id,
2198                        crate::command::App::create(config).map_err(|error| error.to_string())?,
2199                    );
2200                    Ok(native_command_pointer(
2201                        "command/app",
2202                        [(native_command_keyword("id"), Value::Number(id as i64))],
2203                    ))
2204                })
2205            }
2206            _ => Err("std.native.Command/create expects one config map".into()),
2207        },
2208        "config" => match values.as_slice() {
2209            [app] => {
2210                let app = native_command_app(app, operation)?;
2211                NATIVE_COMMAND_STATE.with(|state| {
2212                    let state = state.borrow();
2213                    let config = state
2214                        .apps
2215                        .get(&app)
2216                        .ok_or_else(|| {
2217                            "std.native.Command/config application was not found".to_owned()
2218                        })?
2219                        .config();
2220                    Ok(native_command_map([
2221                        (
2222                            native_command_keyword("id"),
2223                            Value::Symbol(Symbol::parse(&config.id)),
2224                        ),
2225                        (native_command_keyword("desc"), Value::String(config.desc)),
2226                    ]))
2227                })
2228            }
2229            _ => Err("std.native.Command/config expects one application".into()),
2230        },
2231        "install" => match values.as_slice() {
2232            [app, route] => {
2233                let app = native_command_app(app, operation)?;
2234                let route = native_command_route_argument(route.clone(), operation)?;
2235                NATIVE_COMMAND_STATE.with(|state| {
2236                    let mut state = state.borrow_mut();
2237                    let handle = state
2238                        .apps
2239                        .get_mut(&app)
2240                        .ok_or_else(|| {
2241                            "std.native.Command/install application was not found".to_owned()
2242                        })?
2243                        .install(route)
2244                        .map_err(|error| error.to_string())?;
2245                    Ok(native_command_pointer(
2246                        "command/route",
2247                        [
2248                            (
2249                                native_command_keyword("id"),
2250                                Value::Number(handle.id() as i64),
2251                            ),
2252                            (native_command_keyword("app"), Value::Number(app as i64)),
2253                        ],
2254                    ))
2255                })
2256            }
2257            _ => Err("std.native.Command/install expects an application and route map".into()),
2258        },
2259        "uninstall" => match values.as_slice() {
2260            [app, route] => {
2261                let app = native_command_app(app, operation)?;
2262                let route = native_command_route(route, app, operation)?;
2263                NATIVE_COMMAND_STATE.with(|state| {
2264                    state
2265                        .borrow_mut()
2266                        .apps
2267                        .get_mut(&app)
2268                        .ok_or_else(|| {
2269                            "std.native.Command/uninstall application was not found".to_owned()
2270                        })?
2271                        .uninstall(route)
2272                        .map(Value::Bool)
2273                        .map_err(|error| error.to_string())
2274                })
2275            }
2276            _ => Err("std.native.Command/uninstall expects an application and route handle".into()),
2277        },
2278        "routes" => match values.as_slice() {
2279            [app] => {
2280                let app = native_command_app(app, operation)?;
2281                NATIVE_COMMAND_STATE.with(|state| {
2282                    state
2283                        .borrow()
2284                        .apps
2285                        .get(&app)
2286                        .ok_or_else(|| {
2287                            "std.native.Command/routes application was not found".to_owned()
2288                        })?
2289                        .routes()
2290                        .map(|routes| {
2291                            Value::Vector(PVector::from_iter(
2292                                routes.iter().map(native_command_spec_value),
2293                            ))
2294                        })
2295                        .map_err(|error| error.to_string())
2296                })
2297            }
2298            _ => Err("std.native.Command/routes expects one application".into()),
2299        },
2300        "snapshot" => match values.as_slice() {
2301            [app] => {
2302                let app = native_command_app(app, operation)?;
2303                NATIVE_COMMAND_STATE.with(|state| {
2304                    let mut state = state.borrow_mut();
2305                    let snapshot = state
2306                        .apps
2307                        .get(&app)
2308                        .ok_or_else(|| {
2309                            "std.native.Command/snapshot application was not found".to_owned()
2310                        })?
2311                        .snapshot()
2312                        .map_err(|error| error.to_string())?;
2313                    let id = state.next_snapshot;
2314                    state.next_snapshot += 1;
2315                    state
2316                        .snapshots
2317                        .insert(id, NativeCommandSnapshot { app, snapshot });
2318                    Ok(native_command_pointer(
2319                        "command/snapshot",
2320                        [
2321                            (native_command_keyword("id"), Value::Number(id as i64)),
2322                            (native_command_keyword("app"), Value::Number(app as i64)),
2323                        ],
2324                    ))
2325                })
2326            }
2327            _ => Err("std.native.Command/snapshot expects one application".into()),
2328        },
2329        "restore" => match values.as_slice() {
2330            [app, snapshot] => {
2331                let app = native_command_app(app, operation)?;
2332                let snapshot_id = native_command_handle(snapshot, "command/snapshot", operation)?;
2333                NATIVE_COMMAND_STATE.with(|state| {
2334                    let mut state = state.borrow_mut();
2335                    let snapshot = state
2336                        .snapshots
2337                        .get(&snapshot_id)
2338                        .ok_or_else(|| "std.native.Command/restore snapshot was not found".to_owned())?;
2339                    if snapshot.app != app {
2340                        return Err("std.native.Command/restore snapshot belongs to a different application".into());
2341                    }
2342                    let snapshot = snapshot.snapshot.clone();
2343                    state
2344                        .apps
2345                        .get_mut(&app)
2346                        .ok_or_else(|| "std.native.Command/restore application was not found".to_owned())?
2347                        .restore(snapshot)
2348                        .map_err(|error| error.to_string())?;
2349                    Ok(values[0].clone())
2350                })
2351            }
2352            _ => Err("std.native.Command/restore expects an application and snapshot".into()),
2353        },
2354        "reset" => match values.as_slice() {
2355            [app] => {
2356                let app = native_command_app(app, operation)?;
2357                NATIVE_COMMAND_STATE.with(|state| {
2358                    let mut state = state.borrow_mut();
2359                    state
2360                        .apps
2361                        .get_mut(&app)
2362                        .ok_or_else(|| {
2363                            "std.native.Command/reset application was not found".to_owned()
2364                        })?
2365                        .reset()
2366                        .map_err(|error| error.to_string())?;
2367                    state.requests.retain(|_, request| request.app != app);
2368                    Ok(values[0].clone())
2369                })
2370            }
2371            _ => Err("std.native.Command/reset expects one application".into()),
2372        },
2373        "closed?" => match values.as_slice() {
2374            [app] => {
2375                let app = native_command_app(app, operation)?;
2376                NATIVE_COMMAND_STATE.with(|state| {
2377                    state
2378                        .borrow()
2379                        .apps
2380                        .get(&app)
2381                        .map(|app| Value::Bool(app.closed()))
2382                        .ok_or_else(|| {
2383                            "std.native.Command/closed? application was not found".to_owned()
2384                        })
2385                })
2386            }
2387            _ => Err("std.native.Command/closed? expects one application".into()),
2388        },
2389        "close" => match values.as_slice() {
2390            [app] => {
2391                let app = native_command_app(app, operation)?;
2392                NATIVE_COMMAND_STATE.with(|state| {
2393                    let mut state = state.borrow_mut();
2394                    state
2395                        .apps
2396                        .get_mut(&app)
2397                        .ok_or_else(|| {
2398                            "std.native.Command/close application was not found".to_owned()
2399                        })?
2400                        .close();
2401                    state.requests.retain(|_, request| request.app != app);
2402                    Ok(Value::Nil)
2403                })
2404            }
2405            _ => Err("std.native.Command/close expects one application".into()),
2406        },
2407        "parse" => match values.as_slice() {
2408            [app, invocation] => native_command_parse(
2409                native_command_app(app, operation)?,
2410                invocation.clone(),
2411                operation,
2412            ),
2413            _ => Err("std.native.Command/parse expects an application and invocation map".into()),
2414        },
2415        "dispatch" => match values.as_slice() {
2416            [app, request] => native_command_dispatch(
2417                native_command_app(app, operation)?,
2418                request.clone(),
2419                operation,
2420            ),
2421            _ => {
2422                Err("std.native.Command/dispatch expects an application and parsed request".into())
2423            }
2424        },
2425        "run" => match values.as_slice() {
2426            [app, invocation] => {
2427                let app = native_command_app(app, operation)?;
2428                match native_command_parse(app, invocation.clone(), operation) {
2429                    Ok(request) => match native_command_dispatch(app, request, operation) {
2430                        Ok(response) => Ok(response),
2431                        Err(error) => Ok(native_command_response_value(
2432                            crate::command::Response::failure(1, error),
2433                        )),
2434                    },
2435                    Err(error) => Ok(native_command_response_value(
2436                        crate::command::Response::failure(2, error),
2437                    )),
2438                }
2439            }
2440            _ => Err("std.native.Command/run expects an application and invocation map".into()),
2441        },
2442        _ => Err(format!("unknown std.native.Command operation: {operation}")),
2443    }
2444}
2445
2446fn native_regex_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
2447    let operation = operation
2448        .strip_prefix("std.native.RegExp/")
2449        .unwrap_or(operation);
2450    match operation {
2451        "compile" => {
2452            if values.len() != 1 {
2453                return Err("std.native.RegExp/compile expects one string".into());
2454            }
2455            let pattern = match &values[0] {
2456                Value::String(pattern) => pattern.clone(),
2457                _ => return Err("std.native.RegExp/compile expects one string".into()),
2458            };
2459            regex::Regex::new(&pattern).map_err(|error| format!("invalid regexp: {error}"))?;
2460            Ok(Value::Regex(pattern))
2461        }
2462        "pattern" => {
2463            if values.len() != 1 {
2464                return Err("std.native.RegExp/pattern expects one regexp".into());
2465            }
2466            match &values[0] {
2467                Value::Regex(pattern) => Ok(Value::String(pattern.clone())),
2468                _ => Err("std.native.RegExp/pattern expects one regexp".into()),
2469            }
2470        }
2471        "find?" => {
2472            if values.len() != 2 {
2473                return Err("std.native.RegExp/find? expects a regexp and string".into());
2474            }
2475            let pattern = match &values[0] {
2476                Value::Regex(pattern) => pattern.clone(),
2477                _ => return Err("std.native.RegExp/find? expects a regexp and string".into()),
2478            };
2479            let input = match &values[1] {
2480                Value::String(input) => input.clone(),
2481                _ => return Err("std.native.RegExp/find? expects a regexp and string".into()),
2482            };
2483            let regexp =
2484                regex::Regex::new(&pattern).map_err(|error| format!("invalid regexp: {error}"))?;
2485            Ok(Value::Bool(regexp.is_match(&input)))
2486        }
2487        "find" => {
2488            if values.len() != 2 {
2489                return Err("std.native.RegExp/find expects a regexp and string".into());
2490            }
2491            let pattern = match &values[0] {
2492                Value::Regex(pattern) => pattern.clone(),
2493                _ => return Err("std.native.RegExp/find expects a regexp and string".into()),
2494            };
2495            let input = match &values[1] {
2496                Value::String(input) => input.clone(),
2497                _ => return Err("std.native.RegExp/find expects a regexp and string".into()),
2498            };
2499            let regexp =
2500                regex::Regex::new(&pattern).map_err(|error| format!("invalid regexp: {error}"))?;
2501            Ok(regexp
2502                .find(&input)
2503                .map(|matched| Value::String(matched.as_str().to_owned()))
2504                .unwrap_or(Value::Nil))
2505        }
2506        "matches" => {
2507            if values.len() != 2 {
2508                return Err("std.native.RegExp/matches expects a regexp and string".into());
2509            }
2510            let pattern = match &values[0] {
2511                Value::Regex(pattern) => pattern.clone(),
2512                _ => return Err("std.native.RegExp/matches expects a regexp and string".into()),
2513            };
2514            let input = match &values[1] {
2515                Value::String(input) => input.clone(),
2516                _ => return Err("std.native.RegExp/matches expects a regexp and string".into()),
2517            };
2518            let anchored = format!(r"\A(?:{pattern})\z");
2519            let regexp =
2520                regex::Regex::new(&anchored).map_err(|error| format!("invalid regexp: {error}"))?;
2521            Ok(Value::Bool(regexp.is_match(&input)))
2522        }
2523        "replace" => {
2524            if values.len() != 3 {
2525                return Err(
2526                    "std.native.RegExp/replace expects a regexp, string, and replacement".into(),
2527                );
2528            }
2529            let pattern = match &values[0] {
2530                Value::Regex(pattern) => pattern.clone(),
2531                _ => {
2532                    return Err(
2533                        "std.native.RegExp/replace expects a regexp, string, and replacement"
2534                            .into(),
2535                    )
2536                }
2537            };
2538            let input = match &values[1] {
2539                Value::String(input) => input.clone(),
2540                _ => {
2541                    return Err(
2542                        "std.native.RegExp/replace expects a regexp, string, and replacement"
2543                            .into(),
2544                    )
2545                }
2546            };
2547            let replacement = match &values[2] {
2548                Value::String(replacement) => replacement.clone(),
2549                _ => {
2550                    return Err(
2551                        "std.native.RegExp/replace expects a regexp, string, and replacement"
2552                            .into(),
2553                    )
2554                }
2555            };
2556            let regexp =
2557                regex::Regex::new(&pattern).map_err(|error| format!("invalid regexp: {error}"))?;
2558            let replacement = native_regexp_replacement(&replacement, regexp.captures_len());
2559            Ok(Value::String(
2560                regexp
2561                    .replace_all(&input, replacement.as_str())
2562                    .into_owned(),
2563            ))
2564        }
2565        "split" => {
2566            if values.len() != 2 {
2567                return Err("std.native.RegExp/split expects a regexp and string".into());
2568            }
2569            let pattern = match &values[0] {
2570                Value::Regex(pattern) => pattern.clone(),
2571                _ => return Err("std.native.RegExp/split expects a regexp and string".into()),
2572            };
2573            let input = match &values[1] {
2574                Value::String(input) => input.clone(),
2575                _ => return Err("std.native.RegExp/split expects a regexp and string".into()),
2576            };
2577            if input.is_empty() {
2578                return Ok(Value::Nil);
2579            }
2580            if pattern.is_empty() {
2581                return Ok(Value::Vector(PVector::from_iter(
2582                    input
2583                        .chars()
2584                        .map(|character| Value::String(character.to_string())),
2585                )));
2586            }
2587            let regexp =
2588                regex::Regex::new(&pattern).map_err(|error| format!("invalid regexp: {error}"))?;
2589            Ok(Value::Vector(PVector::from_iter(
2590                regexp
2591                    .split(&input)
2592                    .map(|part| Value::String(part.to_owned())),
2593            )))
2594        }
2595        _ => Err(format!("unknown std.native.RegExp operation: {operation}")),
2596    }
2597}
2598
2599/// Converts Hara's numbered `$1` capture syntax to the unambiguous form used
2600/// by Rust's regex engine. A following digit extends the capture number only
2601/// when that group exists, matching Java/Clojure replacement semantics.
2602fn native_regexp_replacement(replacement: &str, capture_count: usize) -> String {
2603    let mut output = String::with_capacity(replacement.len());
2604    let mut characters = replacement.chars().peekable();
2605
2606    while let Some(character) = characters.next() {
2607        if character != '$' {
2608            output.push(character);
2609            continue;
2610        }
2611
2612        let Some(first) = characters.peek().and_then(|value| value.to_digit(10)) else {
2613            output.push('$');
2614            continue;
2615        };
2616        let mut capture = first as usize;
2617        if capture >= capture_count {
2618            output.push('$');
2619            continue;
2620        }
2621        characters.next();
2622
2623        while let Some(next) = characters.peek().and_then(|value| value.to_digit(10)) {
2624            let Some(candidate) = capture
2625                .checked_mul(10)
2626                .and_then(|value| value.checked_add(next as usize))
2627            else {
2628                break;
2629            };
2630            if candidate >= capture_count {
2631                break;
2632            }
2633            capture = candidate;
2634            characters.next();
2635        }
2636
2637        output.push_str("${");
2638        output.push_str(&capture.to_string());
2639        output.push('}');
2640    }
2641
2642    output
2643}
2644
2645fn file_error(operation: &str, error: FileError) -> String {
2646    let method = operation
2647        .strip_prefix("std.native.File/")
2648        .unwrap_or(operation);
2649    format!("file/{method} failed: file/{}", error.code())
2650}
2651
2652fn socket_error(operation: &str, error: SocketError) -> String {
2653    format!("{operation} failed: socket/{}", error.code())
2654}
2655
2656fn active_file_provider() -> Option<Rc<dyn FileProvider>> {
2657    ACTIVE_FILE_PROVIDER.with(|active| active.borrow().clone())
2658}
2659
2660fn rejected_file_effect(
2661    operation: &str,
2662    path: &str,
2663    target: Option<&str>,
2664    error: FileError,
2665) -> Value {
2666    let promise = Promise::new();
2667    promise.reject_value(crate::file::file_error_value(
2668        operation, path, target, &error,
2669    ));
2670    Value::Promise(promise)
2671}
2672
2673fn file_effect(
2674    operation: &str,
2675    path: &str,
2676    target: Option<&str>,
2677    invoke: impl FnOnce(&dyn FileProvider) -> Result<Promise, FileError>,
2678) -> Value {
2679    let Some(provider) = active_file_provider() else {
2680        return rejected_file_effect(operation, path, target, FileError::Denied);
2681    };
2682    match invoke(provider.as_ref()) {
2683        Ok(promise) => Value::Promise(promise),
2684        Err(error) => rejected_file_effect(operation, path, target, error),
2685    }
2686}
2687
2688fn file_option(options: &Value, name: &str) -> Option<Value> {
2689    let key = Value::Keyword(name.into());
2690    map_entries(options)?
2691        .into_iter()
2692        .find_map(|(candidate, value)| (candidate == key).then_some(value))
2693}
2694
2695fn file_options_value(value: Value, operation: &str) -> Result<Value, String> {
2696    match value {
2697        Value::Nil => Ok(Value::Map(PMap::new())),
2698        value if map_entries(&value).is_some() => Ok(value),
2699        _ => Err(format!("{operation} options must be a map")),
2700    }
2701}
2702
2703fn file_bool_option(
2704    options: &Value,
2705    name: &str,
2706    default: bool,
2707    operation: &str,
2708) -> Result<bool, String> {
2709    match file_option(options, name) {
2710        None => Ok(default),
2711        Some(Value::Bool(value)) => Ok(value),
2712        Some(_) => Err(format!("{operation} :{name} must be boolean")),
2713    }
2714}
2715
2716fn file_string_option(
2717    options: &Value,
2718    name: &str,
2719    default: &str,
2720    operation: &str,
2721) -> Result<String, String> {
2722    match file_option(options, name) {
2723        None => Ok(default.into()),
2724        Some(Value::String(value)) => Ok(value),
2725        Some(_) => Err(format!("{operation} :{name} must be a string")),
2726    }
2727}
2728
2729fn file_write_options(options: &Value) -> Result<WriteOptions, String> {
2730    let mode = match file_option(options, "mode") {
2731        None => WriteMode::Create,
2732        Some(Value::Keyword(value)) if value.as_str() == "create" => WriteMode::Create,
2733        Some(Value::Keyword(value)) if value.as_str() == "replace" => WriteMode::Replace,
2734        Some(Value::Keyword(value)) if value.as_str() == "append" => WriteMode::Append,
2735        Some(_) => {
2736            return Err("std.native.File/write :mode must be :create, :replace, or :append".into())
2737        }
2738    };
2739    Ok(WriteOptions {
2740        mode,
2741        parents: file_bool_option(options, "parents?", false, "std.native.File/write")?,
2742    })
2743}
2744
2745fn file_mkdir_options(options: &Value) -> Result<MkdirOptions, String> {
2746    Ok(MkdirOptions {
2747        parents: file_bool_option(options, "parents?", true, "std.native.File/mkdir")?,
2748        exists_ok: file_bool_option(options, "exists-ok?", true, "std.native.File/mkdir")?,
2749    })
2750}
2751
2752fn file_delete_options(options: &Value) -> Result<DeleteOptions, String> {
2753    Ok(DeleteOptions {
2754        missing_ok: file_bool_option(options, "missing-ok?", false, "std.native.File/delete")?,
2755    })
2756}
2757
2758fn file_copy_options(options: &Value) -> Result<CopyOptions, String> {
2759    Ok(CopyOptions {
2760        replace: file_bool_option(options, "replace?", false, "std.native.File/copy")?,
2761        parents: file_bool_option(options, "parents?", false, "std.native.File/copy")?,
2762        preserve_modified: file_bool_option(
2763            options,
2764            "preserve-modified?",
2765            false,
2766            "std.native.File/copy",
2767        )?,
2768    })
2769}
2770
2771fn file_move_options(options: &Value) -> Result<MoveOptions, String> {
2772    Ok(MoveOptions {
2773        replace: file_bool_option(options, "replace?", false, "std.native.File/move")?,
2774        parents: file_bool_option(options, "parents?", false, "std.native.File/move")?,
2775        atomic: file_bool_option(options, "atomic?", false, "std.native.File/move")?,
2776    })
2777}
2778
2779fn file_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
2780    let effect_operation = operation
2781        .strip_prefix("std.native.File/")
2782        .map(|method| format!("file/{method}"))
2783        .unwrap_or_else(|| operation.to_owned());
2784    match operation {
2785        "std.native.File/join" | "std.native.File/resolve" => {
2786            if values.len() != 2 {
2787                return Err(format!("{operation} expects a base and path"));
2788            }
2789            let Value::String(base) = &values[0] else {
2790                return Err(format!("{operation} expects a base and path"));
2791            };
2792            let Value::String(path) = &values[1] else {
2793                return Err(format!("{operation} expects a base and path"));
2794            };
2795            let result = if operation == "std.native.File/join" {
2796                crate::file::logical_join(&base, &path)
2797            } else {
2798                crate::file::logical_resolve(&base, &path)
2799            };
2800            result
2801                .map(Value::String)
2802                .map_err(|error| file_error(operation, error))
2803        }
2804        "std.native.File/parent" => {
2805            if values.len() != 1 {
2806                return Err("std.native.File/parent expects a path".into());
2807            }
2808            let Value::String(path) = &values[0] else {
2809                return Err("std.native.File/parent expects a path".into());
2810            };
2811            crate::file::logical_parent(path)
2812                .map(|parent| parent.map(Value::String).unwrap_or(Value::Nil))
2813                .map_err(|error| file_error(operation, error))
2814        }
2815        "std.native.File/read"
2816        | "std.native.File/exists?"
2817        | "std.native.File/stat"
2818        | "std.native.File/entries"
2819        | "std.native.File/list"
2820        | "std.native.File/walk" => {
2821            if values.len() != 1 {
2822                return Err(format!("{operation} expects a path"));
2823            }
2824            let Value::String(path) = &values[0] else {
2825                return Err(format!("{operation} expects a path"));
2826            };
2827            Ok(file_effect(
2828                &effect_operation,
2829                &path,
2830                None,
2831                |provider| match operation {
2832                    "std.native.File/read" => provider.read(&path),
2833                    "std.native.File/exists?" => provider.exists(&path),
2834                    "std.native.File/stat" => provider.stat(&path),
2835                    "std.native.File/entries" => provider.entries(&path),
2836                    "std.native.File/list" => provider.list(&path),
2837                    "std.native.File/walk" => provider.walk(&path),
2838                    _ => unreachable!(),
2839                },
2840            ))
2841        }
2842        "std.native.File/write" => {
2843            if !(2..=3).contains(&values.len()) {
2844                return Err(
2845                    "std.native.File/write expects a path, bytes, and optional options".into(),
2846                );
2847            }
2848            let Value::String(path) = &values[0] else {
2849                return Err("std.native.File/write expects a path and bytes".into());
2850            };
2851            let bytes = match &values[1] {
2852                Value::Bytes(value) => value.clone(),
2853                Value::ByteBuffer(value) => value.borrow().clone(),
2854                _ => return Err("std.native.File/write expects a path and bytes".into()),
2855            };
2856            let options = if values.len() == 3 {
2857                file_options_value(values[2].clone(), operation)?
2858            } else {
2859                Value::Map(PMap::new())
2860            };
2861            let options = file_write_options(&options)?;
2862            Ok(file_effect(&effect_operation, &path, None, |provider| {
2863                provider.write_with_options(&path, bytes, options)
2864            }))
2865        }
2866        "std.native.File/mkdir" => {
2867            if !(1..=2).contains(&values.len()) {
2868                return Err("std.native.File/mkdir expects a path and optional options".into());
2869            }
2870            let Value::String(path) = &values[0] else {
2871                return Err("std.native.File/mkdir expects a path".into());
2872            };
2873            let options = if values.len() == 2 {
2874                file_options_value(values[1].clone(), operation)?
2875            } else {
2876                Value::Map(PMap::new())
2877            };
2878            let options = file_mkdir_options(&options)?;
2879            Ok(file_effect(&effect_operation, &path, None, |provider| {
2880                provider.mkdir_with_options(&path, options)
2881            }))
2882        }
2883        "std.native.File/delete" => {
2884            if !(1..=2).contains(&values.len()) {
2885                return Err("std.native.File/delete expects a path and optional options".into());
2886            }
2887            let Value::String(path) = &values[0] else {
2888                return Err("std.native.File/delete expects a path".into());
2889            };
2890            let options = if values.len() == 2 {
2891                file_options_value(values[1].clone(), operation)?
2892            } else {
2893                Value::Map(PMap::new())
2894            };
2895            let options = file_delete_options(&options)?;
2896            Ok(file_effect(&effect_operation, &path, None, |provider| {
2897                provider.delete_with_options(&path, options)
2898            }))
2899        }
2900        "std.native.File/copy" | "std.native.File/move" => {
2901            if !(2..=3).contains(&values.len()) {
2902                return Err(format!(
2903                    "{operation} expects source, target, and optional options"
2904                ));
2905            }
2906            let Value::String(source) = &values[0] else {
2907                return Err(format!("{operation} expects source and target paths"));
2908            };
2909            let Value::String(target) = &values[1] else {
2910                return Err(format!("{operation} expects source and target paths"));
2911            };
2912            let options = if values.len() == 3 {
2913                file_options_value(values[2].clone(), operation)?
2914            } else {
2915                Value::Map(PMap::new())
2916            };
2917            Ok(if operation == "std.native.File/copy" {
2918                let options = file_copy_options(&options)?;
2919                file_effect(&effect_operation, &source, Some(&target), |provider| {
2920                    provider.copy(&source, &target, options)
2921                })
2922            } else {
2923                let options = file_move_options(&options)?;
2924                file_effect(&effect_operation, &source, Some(&target), |provider| {
2925                    provider.move_entry(&source, &target, options)
2926                })
2927            })
2928        }
2929        "std.native.File/temp-file" | "std.native.File/temp-directory" => {
2930            if !(1..=2).contains(&values.len()) {
2931                return Err(format!("{operation} expects a parent and optional options"));
2932            }
2933            let Value::String(parent) = &values[0] else {
2934                return Err(format!("{operation} expects a parent path"));
2935            };
2936            let options = if values.len() == 2 {
2937                file_options_value(values[1].clone(), operation)?
2938            } else {
2939                Value::Map(PMap::new())
2940            };
2941            Ok(if operation == "std.native.File/temp-file" {
2942                let options = TempFileOptions {
2943                    prefix: file_string_option(&options, "prefix", "tmp", operation)?,
2944                    suffix: file_string_option(&options, "suffix", "", operation)?,
2945                };
2946                file_effect(&effect_operation, &parent, None, |provider| {
2947                    provider.temp_file(&parent, options)
2948                })
2949            } else {
2950                let options = TempDirectoryOptions {
2951                    prefix: file_string_option(&options, "prefix", "tmp", operation)?,
2952                };
2953                file_effect(&effect_operation, &parent, None, |provider| {
2954                    provider.temp_directory(&parent, options)
2955                })
2956            })
2957        }
2958        _ => Err(format!("unknown std.native.File operation: {operation}")),
2959    }
2960}
2961
2962fn socket_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
2963    let operation = operation
2964        .strip_prefix("std.native.Socket/")
2965        .unwrap_or(operation);
2966    match operation {
2967        "receive-stream" | "socket/receive-stream" => {
2968            if values.len() != 1 {
2969                return Err(format!("Socket/{operation} expects a socket connection"));
2970            }
2971            let socket = socket_handle(&values[0], &format!("Socket/{operation}"))?;
2972            let events = socket_provider(operation)?
2973                .events(socket)
2974                .map_err(|e| socket_error(operation, e))?;
2975            Ok(host_stream(
2976                Rc::new(move || socket_receive_promise(events)),
2977                Rc::new(|| Ok(())),
2978            ))
2979        }
2980        "socket/connect" => {
2981            if values.len() != 4 {
2982                return Err("socket/connect expects a host, port, options, and callback".into());
2983            }
2984            let host = match &values[0] {
2985                Value::String(value) => value.clone(),
2986                _ => {
2987                    return Err("socket/connect expects a host, port, options, and callback".into())
2988                }
2989            };
2990            let port = value_u16_integer(&values[1], "socket/connect", false)?;
2991            let _options = &values[2];
2992            let callback = match &values[3] {
2993                Value::Function(value) => value.clone(),
2994                _ => return Err("socket/connect expects a callback".into()),
2995            };
2996            let callback = Rc::new(move |event| {
2997                let arguments = match event {
2998                    SocketEvent::Connected(handle) => {
2999                        vec![Value::Nil, Value::Number(handle as i64)]
3000                    }
3001                    SocketEvent::Failed(_, error) => vec![Value::String(error), Value::Nil],
3002                    SocketEvent::Data(_, _) | SocketEvent::Closed(_) => return,
3003                };
3004                let _ = call_function(&callback, arguments);
3005            });
3006            socket_provider(operation)?
3007                .connect(&host, port, callback)
3008                .map(|handle| Value::Number(handle as i64))
3009                .map_err(|error| socket_error(operation, error))
3010        }
3011        "socket/listen" => {
3012            if values.len() != 4 {
3013                return Err("socket/listen expects a host, port, options, and callback".into());
3014            }
3015            let host = match &values[0] {
3016                Value::String(value) => value.clone(),
3017                _ => return Err("socket/listen expects a host string".into()),
3018            };
3019            let port = value_u16_integer(&values[1], "socket/listen", true)?;
3020            let _options = &values[2];
3021            let callback = match &values[3] {
3022                Value::Function(value) => value.clone(),
3023                _ => return Err("socket/listen expects a callback".into()),
3024            };
3025            let callback = Rc::new(move |event| {
3026                let _ = call_function(&callback, vec![socket_server_event_value(event)]);
3027            });
3028            socket_provider(operation)?
3029                .listen(&host, port, callback)
3030                .map(|handle| Value::Number(handle as i64))
3031                .map_err(|error| socket_error(operation, error))
3032        }
3033        "socket/endpoint" => {
3034            if values.len() != 1 {
3035                return Err("socket/endpoint expects a server".into());
3036            }
3037            let server = socket_handle(&values[0], "socket/endpoint")?;
3038            socket_provider(operation)?
3039                .endpoint(server)
3040                .map(|(host, port)| {
3041                    Value::Map(PMap::from_iter([
3042                        (Value::Keyword("host".into()), Value::String(host)),
3043                        (Value::Keyword("port".into()), Value::Number(port as i64)),
3044                    ]))
3045                })
3046                .map_err(|error| socket_error(operation, error))
3047        }
3048        "socket/events" => {
3049            if values.len() != 2 {
3050                return Err("socket/events expects a socket handle and options".into());
3051            }
3052            let handle = socket_handle(&values[0], "socket/events")?;
3053            let _options = &values[1];
3054            socket_provider(operation)?
3055                .events(handle)
3056                .map(|stream| Value::Number(stream as i64))
3057                .map_err(|error| socket_error(operation, error))
3058        }
3059        "socket/next" => {
3060            if values.len() != 1 {
3061                return Err("socket/next expects a socket stream".into());
3062            }
3063            let stream = socket_handle(&values[0], "socket/next")?;
3064            socket_provider(operation)?
3065                .next(stream)
3066                .map(Value::Promise)
3067                .map_err(|error| socket_error(operation, error))
3068        }
3069        "socket/send" => {
3070            if values.len() != 2 {
3071                return Err("socket/send expects a socket connection and bytes".into());
3072            }
3073            let socket = socket_handle(&values[0], "socket/send")?;
3074            let bytes = match &values[1] {
3075                Value::Bytes(value) => value.clone(),
3076                Value::ByteBuffer(value) => value.borrow().clone(),
3077                _ => return Err("socket/send expects a socket connection and bytes".into()),
3078            };
3079            socket_provider(operation)?
3080                .send(socket, &bytes)
3081                .map(|count| Value::Number(count as i64))
3082                .map_err(|error| socket_error(operation, error))
3083        }
3084        "socket/close" => {
3085            if values.len() != 1 {
3086                return Err("socket/close expects a socket connection".into());
3087            }
3088            let socket = socket_handle(&values[0], "socket/close")?;
3089            socket_provider(operation)?
3090                .close(socket)
3091                .map(|()| Value::Nil)
3092                .map_err(|error| socket_error(operation, error))
3093        }
3094        _ => Err(format!("unknown std.native.Socket operation: {operation}")),
3095    }
3096}
3097
3098fn socket_receive_promise(stream: SocketHandle) -> Result<Promise, String> {
3099    let source = socket_provider("Socket/receive-stream")?
3100        .next(stream)
3101        .map_err(|e| socket_error("Socket/receive-stream", e))?;
3102    let output = Promise::new();
3103    let settled = output.clone();
3104    source.on_settle(Rc::new(move |result| match result {
3105        PromiseState::Rejected(error) => {
3106            settled.reject_rejection(error);
3107        }
3108        PromiseState::Pending => {}
3109        PromiseState::Fulfilled(event) => {
3110            let entries = map_entries(&event).unwrap_or_default();
3111            let kind = entries.iter().find_map(|(k, v)| {
3112                if matches!(k, Value::Keyword(key) if key.as_str() == "type") {
3113                    Some(v.clone())
3114                } else {
3115                    None
3116                }
3117            });
3118            match kind {
3119                Some(Value::Keyword(kind)) if kind.as_str() == "data" => {
3120                    let bytes = entries
3121                        .into_iter()
3122                        .find_map(|(k, v)| {
3123                            if matches!(k, Value::Keyword(key) if key.as_str() == "bytes") {
3124                                Some(v)
3125                            } else {
3126                                None
3127                            }
3128                        })
3129                        .unwrap_or(Value::Nil);
3130                    settled.resolve(bytes);
3131                }
3132                Some(Value::Keyword(kind)) if kind.as_str() == "close" => {
3133                    settled.resolve(Value::Nil);
3134                }
3135                Some(Value::Keyword(kind)) if kind.as_str() == "error" => {
3136                    settled.reject("socket receive failed");
3137                }
3138                _ => {
3139                    settled.reject("Socket/receive-stream received an invalid event");
3140                }
3141            }
3142        }
3143    }));
3144    let poll = source.clone();
3145    output.set_poller(Rc::new(move || {
3146        poll.state();
3147    }));
3148    let wait = source.clone();
3149    output.set_waiter(Rc::new(move || {
3150        wait.wait_state();
3151    }));
3152    Ok(output)
3153}
3154
3155fn socket_handle(value: &Value, operation: &str) -> Result<SocketHandle, String> {
3156    value_u64_integer(value, operation)
3157        .map(|value| value as SocketHandle)
3158        .map_err(|_| format!("{operation} expects a socket handle"))
3159}
3160
3161fn native_host_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
3162    let method = operation
3163        .strip_prefix("std.native.Host/")
3164        .unwrap_or(operation);
3165    let (service, target, arguments) = match method {
3166        "call" => {
3167            if values.len() != 3 {
3168                return Err(
3169                    "std.native.Host/call expects service, method, and an argument vector".into(),
3170                );
3171            }
3172            let service = match &values[0] {
3173                Value::String(value) => value.clone(),
3174                _ => return Err("std.native.Host/call service must be a string".into()),
3175            };
3176            let target = match &values[1] {
3177                Value::String(value) => value.clone(),
3178                _ => return Err("std.native.Host/call method must be a string".into()),
3179            };
3180            let arguments = match &values[2] {
3181                Value::Vector(values) => values.iter().cloned().collect(),
3182                Value::Tuple(values) => values.iter().cloned().collect(),
3183                _ => return Err("std.native.Host/call arguments must be a vector".into()),
3184            };
3185            (service, target, arguments)
3186        }
3187        "describe" | "capabilities" => {
3188            if !values.is_empty() {
3189                return Err(format!("std.native.Host/{method} expects no arguments"));
3190            }
3191            ("host".into(), method.into(), Vec::new())
3192        }
3193        "capability?" => {
3194            if values.len() != 1 {
3195                return Err("std.native.Host/capability? expects one capability".into());
3196            }
3197            ("host".into(), "capability?".into(), vec![values[0].clone()])
3198        }
3199        _ => return Err(format!("unknown std.native.Host method: {method}")),
3200    };
3201    HOST_CALL_HANDLER.with(|active| {
3202        let Some(handler) = active.borrow().as_ref().cloned() else {
3203            let promise = Promise::new();
3204            promise.reject_value(host_error(
3205                "host/unavailable",
3206                "Host capability provider is unavailable",
3207            ));
3208            return Ok(Value::Promise(promise));
3209        };
3210        handler(service, target, arguments)
3211    })
3212}
3213
3214pub(crate) fn namespace_identifier(value: Value, operation: &str) -> Result<String, String> {
3215    match value {
3216        Value::Symbol(name) if name.get_namespace().is_none() => Ok(name.as_str().to_owned()),
3217        Value::String(name) => Ok(name),
3218        Value::Namespace(namespace) => Ok(namespace.name().as_str().to_owned()),
3219        _ => Err(format!(
3220            "{operation} expects an unqualified namespace symbol, string, or Namespace"
3221        )),
3222    }
3223}
3224
3225fn namespace_descriptor(registry: &NamespaceRegistry<Value>, name: &str) -> Value {
3226    let state = registry
3227        .load_state(name)
3228        .or_else(|| registry.find(name).map(|_| NamespaceLoadState::Loaded))
3229        .map(NamespaceLoadState::as_str)
3230        .unwrap_or("unknown");
3231    let package = package_catalog().coordinate_for_namespace(name);
3232    let origin = if name.starts_with("std.native") {
3233        "embedded"
3234    } else if package.is_some() {
3235        "package"
3236    } else if registry.find(name).is_some() {
3237        "runtime"
3238    } else {
3239        "registered"
3240    };
3241    let mut fields = vec![
3242        (
3243            Value::Keyword("namespace/name".into()),
3244            Value::Symbol(Symbol::parse(name)),
3245        ),
3246        (
3247            Value::Keyword("namespace/state".into()),
3248            Value::Keyword(state.into()),
3249        ),
3250        (
3251            Value::Keyword("namespace/role".into()),
3252            Value::Keyword(
3253                registry
3254                    .find(name)
3255                    .map(|namespace| namespace.role())
3256                    .unwrap_or_else(|| "standard".into())
3257                    .into(),
3258            ),
3259        ),
3260        (
3261            Value::Keyword("namespace/revision".into()),
3262            Value::Number(registry.module_revision(name) as i64),
3263        ),
3264        (
3265            Value::Keyword("namespace/origin".into()),
3266            Value::Keyword(origin.into()),
3267        ),
3268    ];
3269    if let Some(package) = package {
3270        fields.push((
3271            Value::Keyword("namespace/package".into()),
3272            Value::String(package),
3273        ));
3274    }
3275    Value::OrderedMap(Box::new(POrderedMap::from_iter(fields)))
3276}
3277
3278fn native_runtime_values(
3279    operation: &str,
3280    values: Vec<Value>,
3281    env: &mut HashMap<String, Value>,
3282) -> Result<Value, String> {
3283    let method = operation
3284        .strip_prefix("std.native.Runtime/")
3285        .unwrap_or(operation);
3286    let registry = namespace_registry()?;
3287    match method {
3288        "ns-publics" => {
3289            let namespace = match values.as_slice() {
3290                [Value::Symbol(name)] if name.get_namespace().is_none() => name.as_str().to_owned(),
3291                [Value::String(name)] => name.clone(),
3292                [Value::Namespace(namespace)] => namespace.name().as_str().to_owned(),
3293                _ => {
3294                    return Err(
3295                        "std.native.Runtime/ns-publics expects a namespace symbol or string".into(),
3296                    )
3297                }
3298            };
3299            let target = registry
3300                .find(&namespace)
3301                .ok_or_else(|| format!("No such namespace: {namespace}"))?;
3302            let mut mappings = target.mappings();
3303            mappings.retain(|(_, var)| var.symbol().get_namespace() == Some(namespace.as_str()));
3304            mappings.sort_by(|(left, _), (right, _)| left.as_str().cmp(right.as_str()));
3305            Ok(Value::OrderedMap(Box::new(POrderedMap::from_iter(
3306                mappings.into_iter().map(|(name, var)| {
3307                    (
3308                        Value::Symbol(Symbol::create(None, name.as_str())),
3309                        Value::Var(var),
3310                    )
3311                }),
3312            ))))
3313        }
3314        "ns-aliases" => {
3315            let name = match values.as_slice() {
3316                [value] => namespace_identifier(value.clone(), "std.native.Runtime/ns-aliases")?,
3317                _ => return Err("std.native.Runtime/ns-aliases expects one namespace".into()),
3318            };
3319            let target = registry
3320                .find(&name)
3321                .ok_or_else(|| format!("No such namespace: {name}"))?;
3322            let mut aliases = target.aliases();
3323            aliases.sort_by(|(left, _), (right, _)| left.as_str().cmp(right.as_str()));
3324            Ok(Value::OrderedMap(Box::new(POrderedMap::from_iter(
3325                aliases.into_iter().map(|(alias, namespace)| {
3326                    (Value::Symbol(alias), Value::Namespace(Rc::new(namespace)))
3327                }),
3328            ))))
3329        }
3330        "ns-find" => {
3331            if values.len() != 1 {
3332                return Err("std.native.Runtime/ns-find expects one namespace".into());
3333            }
3334            let name = namespace_identifier(values[0].clone(), "std.native.Runtime/ns-find")?;
3335            Ok(registry
3336                .find(&name)
3337                .map(|namespace| Value::Namespace(Rc::new(namespace)))
3338                .unwrap_or(Value::Nil))
3339        }
3340        "ns-create" => match values.as_slice() {
3341            [Value::Symbol(name)] if name.get_namespace().is_none() => {
3342                let namespace = registry.find_or_create(name.as_str());
3343                Ok(Value::Namespace(Rc::new(namespace)))
3344            }
3345            _ => Err("std.native.Runtime/ns-create expects an unqualified symbol".into()),
3346        },
3347        "ns-name" => match values.as_slice() {
3348            [Value::Namespace(namespace)] => Ok(Value::Symbol(namespace.name().clone())),
3349            [Value::Symbol(name)]
3350                if name.get_namespace().is_none()
3351                    && namespace_registry()?.find(name.as_str()).is_some() =>
3352            {
3353                Ok(Value::Symbol(name.clone()))
3354            }
3355            _ => Err("std.native.Runtime/ns-name expects a namespace".into()),
3356        },
3357        "current" => {
3358            if !values.is_empty() {
3359                return Err("std.native.Runtime/current expects no arguments".into());
3360            }
3361            Ok(Value::Symbol(registry.current().name().clone()))
3362        }
3363        "snapshot" => {
3364            if !values.is_empty() {
3365                return Err("std.native.Runtime/snapshot expects no arguments".into());
3366            }
3367            let namespaces = registry
3368                .known_names()
3369                .into_iter()
3370                .map(|name| namespace_descriptor(&registry, name.as_str()))
3371                .collect::<Vec<_>>();
3372            Ok(Value::OrderedMap(Box::new(POrderedMap::from_iter([
3373                (
3374                    Value::Keyword("env/current".into()),
3375                    Value::Symbol(registry.current().name().clone()),
3376                ),
3377                (
3378                    Value::Keyword("env/namespaces".into()),
3379                    Value::Vector(PVector::from(namespaces)),
3380                ),
3381            ]))))
3382        }
3383        "namespaces" => {
3384            if !values.is_empty() {
3385                return Err("std.native.Runtime/namespaces expects no arguments".into());
3386            }
3387            Ok(Value::Vector(PVector::from(
3388                registry
3389                    .known_names()
3390                    .into_iter()
3391                    .map(|name| namespace_descriptor(&registry, name.as_str()))
3392                    .collect::<Vec<_>>(),
3393            )))
3394        }
3395        "namespace" => {
3396            if values.len() != 1 {
3397                return Err("std.native.Runtime/namespace expects one namespace".into());
3398            }
3399            let name = namespace_identifier(values[0].clone(), operation)?;
3400            if registry.load_state(&name).is_none() && registry.find(&name).is_none() {
3401                Ok(Value::Nil)
3402            } else {
3403                Ok(namespace_descriptor(&registry, &name))
3404            }
3405        }
3406        "module" => {
3407            if values.len() != 1 {
3408                return Err("std.native.Runtime/module expects one module path".into());
3409            }
3410            let requested = match &values[0] {
3411                Value::String(path) => path.clone(),
3412                Value::Symbol(name) => name.as_str().to_owned(),
3413                _ => {
3414                    return Err(
3415                        "std.native.Runtime/module expects a path string or namespace symbol"
3416                            .into(),
3417                    )
3418                }
3419            };
3420            let source = requested.strip_prefix("classpath:").unwrap_or(&requested);
3421            let namespace = if source.ends_with(".hal") || source.ends_with(".hrl") {
3422                source
3423                    .trim_end_matches(".hal")
3424                    .trim_end_matches(".hrl")
3425                    .trim_start_matches("./")
3426                    .replace('/', ".")
3427            } else {
3428                source.to_owned()
3429            };
3430            let revision = registry.module_revision(&namespace);
3431            if revision == 0
3432                && registry.load_state(&namespace).is_none()
3433                && registry.find(&namespace).is_none()
3434            {
3435                return Ok(Value::Nil);
3436            }
3437            let dependencies = registry
3438                .module_dependencies(&namespace)
3439                .into_iter()
3440                .map(|dependency| {
3441                    Value::String(format!("{}.hal", dependency.as_str().replace('.', "/")))
3442                })
3443                .collect::<Vec<_>>();
3444            Ok(Value::OrderedMap(Box::new(POrderedMap::from_iter([
3445                (
3446                    Value::Keyword("module/path".into()),
3447                    Value::String(requested),
3448                ),
3449                (
3450                    Value::Keyword("module/namespace".into()),
3451                    Value::Symbol(Symbol::parse(&namespace)),
3452                ),
3453                (
3454                    Value::Keyword("module/revision".into()),
3455                    Value::Number(revision as i64),
3456                ),
3457                (
3458                    Value::Keyword("module/dependencies".into()),
3459                    Value::Vector(PVector::from(dependencies)),
3460                ),
3461            ]))))
3462        }
3463        "vars" => {
3464            if values.len() > 1 {
3465                return Err("std.native.Runtime/vars expects zero or one namespace".into());
3466            }
3467            let name = if values.is_empty() {
3468                registry.current().name().as_str().to_owned()
3469            } else {
3470                namespace_identifier(values[0].clone(), operation)?
3471            };
3472            let namespace = registry
3473                .find(&name)
3474                .ok_or_else(|| format!("namespace/not-found: {name}"))?;
3475            let mut mappings = namespace.mappings();
3476            mappings.retain(|(_, var)| var.symbol().get_namespace() == Some(name.as_str()));
3477            mappings.sort_by(|(left, _), (right, _)| left.as_str().cmp(right.as_str()));
3478            Ok(Value::OrderedMap(Box::new(POrderedMap::from_iter(
3479                mappings.into_iter().map(|(symbol, var)| {
3480                    (
3481                        Value::Symbol(Symbol::create(None, symbol.as_str())),
3482                        Value::Var(var),
3483                    )
3484                }),
3485            ))))
3486        }
3487        "eval" => {
3488            if values.len() != 1 {
3489                return Err("std.native.Runtime/eval expects one form".into());
3490            }
3491            let mut environment = crate::core::current_namespace_environment()?;
3492            let result = eval_value(values[0].clone(), &mut environment);
3493            crate::core::save_namespace_environment(&registry, &mut environment);
3494            result
3495        }
3496        "load-string" => {
3497            let [Value::String(source)] = values.as_slice() else {
3498                return Err("std.native.Runtime/load-string expects one string".into());
3499            };
3500            #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
3501            if direct_native_execution() {
3502                return eval_direct_native_source(source);
3503            }
3504            eval_value_text(source, env)
3505        }
3506        "var-sym" => {
3507            let [Value::Var(var)] = values.as_slice() else {
3508                return Err("std.native.Runtime/var-sym expects one Var".into());
3509            };
3510            Ok(Value::Symbol(var.symbol().clone()))
3511        }
3512        "macroexpand-1" => {
3513            if values.len() != 1 {
3514                return Err("std.native.Runtime/macroexpand-1 expects one form".into());
3515            }
3516            let form = value_to_form(&values[0])?;
3517            let mut environment = current_namespace_environment()?;
3518            form_to_value(&macroexpand_once(&form, &mut environment)?)
3519        }
3520        "gensym" => {
3521            let prefix = match values.as_slice() {
3522                [] => "G__".to_owned(),
3523                [Value::String(prefix)] => prefix.clone(),
3524                [value] => {
3525                    return Err(format!(
3526                        "gensym expects a string prefix, got {}",
3527                        portable_type_name(value)
3528                    ))
3529                }
3530                _ => return Err("gensym expects zero or one arguments".into()),
3531            };
3532            Ok(Value::Symbol(Symbol::from(gensym(&prefix))))
3533        }
3534        "eval-in" => {
3535            if values.len() != 2 {
3536                return Err("std.native.Runtime/eval-in expects namespace and forms".into());
3537            }
3538            let target = namespace_identifier(values[0].clone(), operation)?;
3539            if registry.find(&target).is_none() {
3540                return Err(format!(
3541                    "std.native.Runtime/eval-in requires an existing namespace: {target}"
3542                ));
3543            }
3544            let forms = iterator_values(values[1].clone())?
3545                .into_iter()
3546                .map(|value| value_to_form(&value))
3547                .collect::<Result<Vec<_>, _>>()?;
3548            let previous = registry.current().name().as_str().to_owned();
3549            select_namespace_environment(&registry, env, &target);
3550            #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
3551            let result = if direct_native_execution() {
3552                let source = if forms.is_empty() {
3553                    "nil".to_owned()
3554                } else {
3555                    Form::List(
3556                        std::iter::once(Form::Symbol("do".into()))
3557                            .chain(forms.iter().cloned())
3558                            .collect(),
3559                    )
3560                    .to_string()
3561                };
3562                eval_direct_native_source(&source)
3563            } else {
3564                let mut result = Value::Nil;
3565                for form in &forms {
3566                    result = eval(form, env)?;
3567                }
3568                Ok(result)
3569            };
3570            #[cfg(not(all(feature = "direct-native", not(target_arch = "wasm32"))))]
3571            let result = {
3572                let mut result = Value::Nil;
3573                for form in &forms {
3574                    result = eval(form, env)?;
3575                }
3576                Ok(result)
3577            };
3578            select_namespace_environment(&registry, env, &previous);
3579            result
3580        }
3581        "alias-state" => {
3582            if values.len() != 1 && values.len() != 2 {
3583                return Err(
3584                    "std.native.Runtime/alias-state expects alias or namespace and alias".into(),
3585                );
3586            }
3587            let (owner, alias_value) = if values.len() == 2 {
3588                (
3589                    namespace_identifier(values[0].clone(), operation)?,
3590                    &values[1],
3591                )
3592            } else {
3593                (registry.current().name().as_str().to_owned(), &values[0])
3594            };
3595            let Value::Symbol(alias) = alias_value else {
3596                return Err(
3597                    "std.native.Runtime/alias-state expects an unqualified alias symbol".into(),
3598                );
3599            };
3600            if alias.get_namespace().is_some() {
3601                return Err(
3602                    "std.native.Runtime/alias-state expects an unqualified alias symbol".into(),
3603                );
3604            }
3605            let Some(namespace) = registry.find(&owner) else {
3606                return Ok(Value::Nil);
3607            };
3608            // Global aliases are resolver-visible even when the owning namespace
3609            // has not materialized a local alias entry. Report the same target
3610            // and lifecycle state that qualified symbol resolution observes.
3611            let target = namespace
3612                .lazy_target(alias.as_str())
3613                .or_else(|| {
3614                    namespace
3615                        .aliases()
3616                        .into_iter()
3617                        .find(|(name, _)| name == alias)
3618                        .map(|(_, target)| target.name().clone())
3619                })
3620                .or_else(|| {
3621                    registry
3622                        .global_aliases()
3623                        .into_iter()
3624                        .find(|(name, _)| name == alias)
3625                        .map(|(_, target)| target)
3626                });
3627            let Some(target) = target else {
3628                return Ok(Value::Nil);
3629            };
3630            let state = registry
3631                .load_state(target.as_str())
3632                .or_else(|| {
3633                    registry
3634                        .find(target.as_str())
3635                        .map(|_| NamespaceLoadState::Loaded)
3636                })
3637                .map(NamespaceLoadState::as_str)
3638                .unwrap_or("unknown");
3639            Ok(Value::Map(PMap::from_iter([
3640                (Value::Keyword("alias".into()), Value::Symbol(alias.clone())),
3641                (Value::Keyword("target".into()), Value::Symbol(target)),
3642                (Value::Keyword("state".into()), Value::Keyword(state.into())),
3643            ])))
3644        }
3645        "intern-var" => {
3646            if values.len() != 3 && values.len() != 4 {
3647                return Err(
3648                    "std.native.Runtime/intern-var expects namespace, symbol, Var, and optional metadata"
3649                        .into(),
3650                );
3651            }
3652            let target = namespace_identifier(values[0].clone(), operation)?;
3653            let Value::Symbol(name) = &values[1] else {
3654                return Err(
3655                    "std.native.Runtime/intern-var expects an unqualified target symbol".into(),
3656                );
3657            };
3658            if name.get_namespace().is_some() {
3659                return Err(
3660                    "std.native.Runtime/intern-var expects an unqualified target symbol".into(),
3661                );
3662            }
3663            let Value::Var(source) = &values[2] else {
3664                return Err("std.native.Runtime/intern-var expects a source Var".into());
3665            };
3666            let mut metadata = source.metadata();
3667            if let Some(extension) = values.get(3) {
3668                let Some(entries) = map_entries(extension) else {
3669                    return Err(
3670                        "std.native.Runtime/intern-var metadata extension must be a map".into(),
3671                    );
3672                };
3673                for (key, value) in entries {
3674                    metadata.extra.insert(key.display(), value.display());
3675                }
3676            }
3677            let value = source.deref_value();
3678            if let Value::Function(function) = &value {
3679                if function.is_macro {
3680                    ACTIVE_MACROS.with(|active| {
3681                        if let Some(macros) = active.borrow().as_ref() {
3682                            macros.borrow_mut().insert(
3683                                (target.clone(), name.as_str().to_owned()),
3684                                function.clone(),
3685                            );
3686                        }
3687                    });
3688                }
3689            }
3690            Ok(Value::Var(
3691                registry.find_or_create(&target).intern_with_metadata(
3692                    name.as_str(),
3693                    value,
3694                    metadata,
3695                ),
3696            ))
3697        }
3698        _ => Err(format!("unknown std.native.Runtime method: {method}")),
3699    }
3700}
3701
3702#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
3703fn eval_direct_native_source(source: &str) -> Result<Value, String> {
3704    let context = DirectNativeContext::capture();
3705    let forms = crate::kernel::read_forms(source).map_err(|error| error.to_string())?;
3706    let has_namespace_form = forms.iter().any(|form| {
3707        matches!(
3708            form_without_metadata(&form.form),
3709            Form::List(items)
3710                if matches!(items.first(), Some(Form::Symbol(operator)) if operator == "ns" || operator == "ns+")
3711        )
3712    });
3713    let config = if has_namespace_form {
3714        crate::vm::source_namespace_config(&forms).map_err(|error| error.to_string())?
3715    } else {
3716        crate::kernel::GeneratedNamespaceConfig::defaults()
3717    };
3718    let namespaces = context.namespaces.clone();
3719    let program = context
3720        .with(|| {
3721            without_direct_native_execution(|| {
3722                crate::vm::compile_source_with_config_allow_unbound_globals(
3723                    source,
3724                    &namespaces,
3725                    config,
3726                )
3727            })
3728        })
3729        .map_err(|error| error.to_string())?;
3730    let mut program = program;
3731    if program.namespace.is_none() {
3732        program.namespace = Some(context.namespace.clone());
3733    }
3734    let engine = crate::direct_native::NativeEngine::new();
3735    // `context.with` already installs the captured providers, namespace, and
3736    // multimethod map. Calling the convenience entry point here would wrap
3737    // the same map in a second context and restore an old snapshot over any
3738    // declarations made by the nested program.
3739    let result = context.with(|| engine.execute_blocking(Rc::new(program)));
3740    // Nested Runtime/eval calls execute with a captured multimethod map. Carry
3741    // declarations made there back into the enclosing native frame so a later
3742    // form in the same frame can install methods or invoke the new multifn.
3743    let nested_multimethods = context.multimethods.borrow().clone();
3744    ACTIVE_MULTIMETHODS.with(|active| {
3745        active.borrow_mut().extend(nested_multimethods);
3746    });
3747    result.map(|report| report.value)
3748}
3749
3750fn eval_value(value: Value, env: &mut HashMap<String, Value>) -> Result<Value, String> {
3751    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
3752    if direct_native_execution() {
3753        return eval_direct_native_source(&value_to_form(&value)?.to_string());
3754    }
3755    eval(&value_to_form(&value)?, env)
3756}
3757
3758fn native_package_values(
3759    operation: &str,
3760    arguments: Vec<Value>,
3761    env: &mut HashMap<String, Value>,
3762) -> Result<Value, String> {
3763    let method = operation
3764        .strip_prefix("std.native.Package/")
3765        .unwrap_or(operation);
3766    if matches!(
3767        method,
3768        "build" | "inspect" | "seal" | "inspect-seal" | "verify-seal" | "distribute"
3769    ) {
3770        return native_package_artifact_values(method, arguments);
3771    }
3772    if method == "read" {
3773        if arguments.len() != 2 {
3774            return Err(
3775                "std.native.Package/read expects an exact descriptor and relative path".into(),
3776            );
3777        }
3778        let Value::OrderedMap(_) = &arguments[0] else {
3779            return Err("std.native.Package/read expects an exact package descriptor".into());
3780        };
3781        let Value::String(relative) = &arguments[1] else {
3782            return Err("std.native.Package/read expects a relative path string".into());
3783        };
3784        return package_catalog()
3785            .read(&arguments[0], relative)
3786            .map(Value::Bytes);
3787    }
3788    let expected = match method {
3789        "catalog" => 0..=0,
3790        "find" | "ensure" | "load" | "state" => 1..=1,
3791        "unload" => 1..=2,
3792        _ => return Err(format!("unknown std.native.Package method: {method}")),
3793    };
3794    if !expected.contains(&arguments.len()) {
3795        return Err(format!(
3796            "std.native.Package/{method} expects {} arguments",
3797            expected.start()
3798        ));
3799    }
3800    let catalog = package_catalog();
3801    if method == "catalog" {
3802        return Ok(catalog.catalog_value());
3803    }
3804    let target = match arguments.first() {
3805        Some(Value::Symbol(value)) => value.as_str().to_owned(),
3806        Some(Value::String(value)) => value.clone(),
3807        Some(Value::Keyword(value)) => value.as_str().to_owned(),
3808        Some(value @ Value::OrderedMap(_)) if method == "ensure" || method == "unload" => {
3809            package_descriptor_coordinate(value).ok_or_else(|| {
3810                format!("std.native.Package/{method} descriptor requires :package/coordinate")
3811            })?
3812        }
3813        _ => {
3814            return Err(format!(
3815                "std.native.Package/{method} expects a namespace, coordinate, or exact descriptor"
3816            ))
3817        }
3818    };
3819    let found = catalog.find(&target);
3820    if method == "find" {
3821        return Ok(found.map(|(_, value)| value).unwrap_or(Value::Nil));
3822    }
3823    let Some((coordinate, descriptor)) = found else {
3824        if method == "state" {
3825            return Ok(Value::Nil);
3826        }
3827        return Err(format!("package/not-locked: {target}"));
3828    };
3829    if method == "state" {
3830        return Ok(Value::Keyword(
3831            catalog
3832                .state(&coordinate)
3833                .unwrap_or_else(|| "available".into())
3834                .into(),
3835        ));
3836    }
3837    if method == "load" {
3838        if catalog.coordinate_for_namespace(&target).as_deref() != Some(&coordinate) {
3839            return Err("std.native.Package/load expects a locked namespace".into());
3840        }
3841        if catalog.state(&coordinate).as_deref() != Some("ready") {
3842            return Err(format!(
3843                "package/not-ready: {coordinate}; call Package/ensure first"
3844            ));
3845        }
3846        let registry = namespace_registry()?;
3847        require_namespace(&registry, env, &target)?;
3848        return Ok(Value::Symbol(Symbol::parse(&target)));
3849    }
3850    if method == "ensure" {
3851        if catalog.state(&coordinate).as_deref() == Some("ready") {
3852            let promise = Promise::new();
3853            promise.resolve(descriptor);
3854            return Ok(Value::Promise(promise));
3855        }
3856        if let Some(pending) = catalog.pending(&coordinate) {
3857            return Ok(Value::Promise(pending));
3858        }
3859    } else if catalog.state(&coordinate).as_deref() == Some("available") {
3860        let promise = Promise::new();
3861        promise.resolve(Value::Vector(PVector::new()));
3862        return Ok(Value::Promise(promise));
3863    } else if catalog.pending(&coordinate).is_some() {
3864        return Err(format!("package/busy: {coordinate}"));
3865    }
3866    if method == "unload" {
3867        if let Some(options) = arguments.get(1) {
3868            if map_entries(options).is_none() {
3869                return Err("std.native.Package/unload options must be a map".into());
3870            }
3871            if let Some(value) = map_value(options, &Value::Keyword("cascade".into())) {
3872                if !matches!(value, Value::Bool(_)) {
3873                    return Err("std.native.Package/unload :cascade must be boolean".into());
3874                }
3875            }
3876        }
3877    }
3878    let previous_state = catalog
3879        .state(&coordinate)
3880        .unwrap_or_else(|| "available".into());
3881    catalog.set_state(
3882        &coordinate,
3883        if method == "ensure" {
3884            "ensuring"
3885        } else {
3886            "unloading"
3887        },
3888    );
3889    HOST_CALL_HANDLER.with(|active| {
3890        let Some(handler) = active.borrow().as_ref().cloned() else {
3891            let promise = Promise::new();
3892            promise.reject_value(host_error(
3893                "package/unsupported",
3894                "Package capability provider is unavailable",
3895            ));
3896            catalog.set_state(
3897                &coordinate,
3898                if method == "ensure" {
3899                    "failed"
3900                } else {
3901                    &previous_state
3902                },
3903            );
3904            return Ok(Value::Promise(promise));
3905        };
3906        let mut provider_arguments = vec![descriptor];
3907        provider_arguments.extend(arguments.iter().skip(1).cloned());
3908        let result = handler("package".into(), method.into(), provider_arguments);
3909        if let Ok(Value::Promise(promise)) = &result {
3910            let state = catalog.clone();
3911            let coordinate = coordinate.clone();
3912            let operation = method.to_owned();
3913            let rollback = previous_state.clone();
3914            state.set_pending(&coordinate, Some(promise.clone()));
3915            promise.on_settle(Rc::new(move |settlement| {
3916                let next = match (&operation[..], settlement) {
3917                    ("ensure", PromiseState::Fulfilled(_)) => "ready",
3918                    ("ensure", _) => "failed",
3919                    ("unload", PromiseState::Fulfilled(_)) => "available",
3920                    ("unload", _) => rollback.as_str(),
3921                    _ => rollback.as_str(),
3922                };
3923                state.set_state(&coordinate, next);
3924                state.set_pending(&coordinate, None);
3925            }));
3926        } else if result.is_ok() {
3927            catalog.set_state(
3928                &coordinate,
3929                if method == "ensure" {
3930                    "ready"
3931                } else {
3932                    "available"
3933                },
3934            );
3935        } else {
3936            catalog.set_state(
3937                &coordinate,
3938                if method == "ensure" {
3939                    "failed"
3940                } else {
3941                    &previous_state
3942                },
3943            );
3944        }
3945        result
3946    })
3947}
3948
3949#[cfg(not(target_arch = "wasm32"))]
3950fn native_package_artifact_values(method: &str, arguments: Vec<Value>) -> Result<Value, String> {
3951    match method {
3952        "build" => {
3953            if !(1..=4).contains(&arguments.len()) {
3954                return Err(
3955                    "std.native.Package/build expects input and up to three options".into(),
3956                );
3957            }
3958            let input =
3959                package_string_argument(&arguments, 0, "std.native.Package/build")?.to_owned();
3960            let output =
3961                package_optional_string_argument(&arguments, 1, "std.native.Package/build")?
3962                    .map(str::to_owned);
3963            let package =
3964                package_optional_string_argument(&arguments, 2, "std.native.Package/build")?
3965                    .map(str::to_owned);
3966            let profile =
3967                package_optional_string_argument(&arguments, 3, "std.native.Package/build")?
3968                    .map(str::to_owned);
3969            let built = std::thread::Builder::new()
3970                .name("hara-package-build".into())
3971                .stack_size(crate::package::BUILD_THREAD_STACK_SIZE)
3972                .spawn(move || {
3973                    crate::package::build_path_with_package(
3974                        std::path::Path::new(&input),
3975                        output.as_deref().map(std::path::Path::new),
3976                        package.as_deref(),
3977                        profile.as_deref().map(std::path::Path::new),
3978                    )
3979                })
3980                .map_err(|error| format!("std.native.Package/build thread failed: {error}"))?
3981                .join()
3982                .map_err(|_| "std.native.Package/build thread panicked".to_owned())??;
3983            Ok(Value::String(built.to_string_lossy().into_owned()))
3984        }
3985        "inspect" => {
3986            if arguments.len() != 1 {
3987                return Err("std.native.Package/inspect expects one archive path".into());
3988            }
3989            Ok(Value::String(crate::package::inspect_path(
3990                std::path::Path::new(package_string_argument(
3991                    &arguments,
3992                    0,
3993                    "std.native.Package/inspect",
3994                )?),
3995            )?))
3996        }
3997        "seal" => {
3998            if arguments.len() != 1 {
3999                return Err("std.native.Package/seal expects one descriptor map".into());
4000            }
4001            let manifest = crate::distribution::seal(&sealed_package_spec(&arguments[0])?)?;
4002            Ok(sealed_manifest_value(&manifest))
4003        }
4004        "distribute" => {
4005            if arguments.len() != 1 {
4006                return Err("std.native.Package/distribute expects one descriptor map".into());
4007            }
4008            let spec = distribution_build_spec(&arguments[0])?;
4009            let manifest = if spec.replace {
4010                crate::distribution::build_replace(&spec.project, &spec.host, &spec.output)?
4011            } else {
4012                crate::distribution::build(&spec.project, &spec.host, &spec.output)?
4013            };
4014            Ok(distribution_manifest_value(&manifest))
4015        }
4016        "inspect-seal" | "verify-seal" => {
4017            if arguments.len() != 1 {
4018                return Err(format!(
4019                    "std.native.Package/{method} expects one executable path"
4020                ));
4021            }
4022            let path = std::path::Path::new(package_string_argument(
4023                &arguments,
4024                0,
4025                &format!("std.native.Package/{method}"),
4026            )?);
4027            let found = if method == "inspect-seal" {
4028                crate::distribution::inspect_sealed(path)?
4029            } else {
4030                crate::distribution::verify_sealed(path)?
4031            };
4032            Ok(found
4033                .as_ref()
4034                .map(sealed_manifest_value)
4035                .unwrap_or(Value::Nil))
4036        }
4037        _ => unreachable!("caller restricts native package artifact methods"),
4038    }
4039}
4040
4041#[cfg(target_arch = "wasm32")]
4042fn native_package_artifact_values(method: &str, _arguments: Vec<Value>) -> Result<Value, String> {
4043    Err(format!(
4044        "package/unsupported: Package/{method} is unavailable on wasm"
4045    ))
4046}
4047
4048#[cfg(not(target_arch = "wasm32"))]
4049fn sealed_package_spec(value: &Value) -> Result<crate::distribution::SealSpec, String> {
4050    let entries = map_entries(value)
4051        .ok_or_else(|| "std.native.Package/seal expects one descriptor map".to_owned())?;
4052    let descriptor = Value::OrderedMap(Box::new(POrderedMap::from_iter(entries)));
4053    let required_string = |key: &str| match map_value(&descriptor, &Value::Keyword(key.into())) {
4054        Some(Value::String(value)) if !value.is_empty() => Ok(value.clone()),
4055        Some(_) => Err(format!(
4056            "std.native.Package/seal :{key} must be a non-empty string"
4057        )),
4058        None => Err(format!("std.native.Package/seal is missing :{key}")),
4059    };
4060    let entry = match map_value(&descriptor, &Value::Keyword("entry".into())) {
4061        Some(Value::Symbol(value)) => value.as_str().to_owned(),
4062        Some(_) => return Err("std.native.Package/seal :entry must be a symbol".into()),
4063        None => return Err("std.native.Package/seal is missing :entry".into()),
4064    };
4065    let host = match map_value(&descriptor, &Value::Keyword("host".into())) {
4066        Some(Value::String(value)) if !value.is_empty() => std::path::PathBuf::from(value),
4067        Some(_) => return Err("std.native.Package/seal :host must be a non-empty string".into()),
4068        None => std::env::current_exe()
4069            .map_err(|error| format!("cannot determine current native executable: {error}"))?,
4070    };
4071    let archives = match map_value(&descriptor, &Value::Keyword("archives".into())) {
4072        Some(value) => iterator_values(value.clone())?,
4073        None => return Err("std.native.Package/seal is missing :archives".into()),
4074    }
4075    .into_iter()
4076    .map(|archive| {
4077        let archive_entries = map_entries(&archive)
4078            .ok_or_else(|| "std.native.Package/seal archives must be maps".to_owned())?;
4079        let archive = Value::OrderedMap(Box::new(POrderedMap::from_iter(archive_entries)));
4080        let path = match map_value(&archive, &Value::Keyword("path".into())) {
4081            Some(Value::String(value)) if !value.is_empty() => std::path::PathBuf::from(value),
4082            Some(_) => {
4083                return Err(
4084                    "std.native.Package/seal archive :path must be a non-empty string".into(),
4085                )
4086            }
4087            None => return Err("std.native.Package/seal archive is missing :path".into()),
4088        };
4089        let primary = match map_value(&archive, &Value::Keyword("primary".into())) {
4090            Some(Value::Bool(value)) => *value,
4091            Some(_) => {
4092                return Err("std.native.Package/seal archive :primary must be boolean".into())
4093            }
4094            None => false,
4095        };
4096        Ok(crate::distribution::SealArchive { path, primary })
4097    })
4098    .collect::<Result<Vec<_>, String>>()?;
4099    Ok(crate::distribution::SealSpec {
4100        host,
4101        output: std::path::PathBuf::from(required_string("output")?),
4102        entry,
4103        archives,
4104    })
4105}
4106
4107#[cfg(not(target_arch = "wasm32"))]
4108struct DistributionBuildSpec {
4109    project: std::path::PathBuf,
4110    host: std::path::PathBuf,
4111    output: std::path::PathBuf,
4112    replace: bool,
4113}
4114
4115#[cfg(not(target_arch = "wasm32"))]
4116fn distribution_build_spec(value: &Value) -> Result<DistributionBuildSpec, String> {
4117    let entries = map_entries(value)
4118        .ok_or_else(|| "std.native.Package/distribute expects one descriptor map".to_owned())?;
4119    let descriptor = Value::OrderedMap(Box::new(POrderedMap::from_iter(entries)));
4120    let required_string = |key: &str| match map_value(&descriptor, &Value::Keyword(key.into())) {
4121        Some(Value::String(value)) if !value.is_empty() => Ok(value),
4122        Some(_) => Err(format!(
4123            "std.native.Package/distribute :{key} must be a non-empty string"
4124        )),
4125        None => Err(format!("std.native.Package/distribute is missing :{key}")),
4126    };
4127    let replace = match map_value(&descriptor, &Value::Keyword("replace?".into())) {
4128        Some(Value::Bool(value)) => *value,
4129        Some(_) => return Err("std.native.Package/distribute :replace? must be boolean".into()),
4130        None => false,
4131    };
4132    Ok(DistributionBuildSpec {
4133        project: std::path::PathBuf::from(required_string("project")?),
4134        host: std::path::PathBuf::from(required_string("host")?),
4135        output: std::path::PathBuf::from(required_string("output")?),
4136        replace,
4137    })
4138}
4139
4140#[cfg(not(target_arch = "wasm32"))]
4141fn sealed_manifest_value(manifest: &crate::distribution::SealedManifest) -> Value {
4142    let archives: Vec<Value> = manifest
4143        .archives
4144        .iter()
4145        .map(|archive| {
4146            Value::OrderedMap(Box::new(POrderedMap::from_iter([
4147                (
4148                    Value::Keyword("identity".into()),
4149                    Value::String(archive.identity.clone()),
4150                ),
4151                (
4152                    Value::Keyword("version".into()),
4153                    Value::String(archive.version.clone()),
4154                ),
4155                (
4156                    Value::Keyword("sha256".into()),
4157                    Value::String(archive.sha256.clone()),
4158                ),
4159                (
4160                    Value::Keyword("offset".into()),
4161                    Value::Number(i64::try_from(archive.offset).unwrap_or(i64::MAX)),
4162                ),
4163                (
4164                    Value::Keyword("length".into()),
4165                    Value::Number(i64::try_from(archive.length).unwrap_or(i64::MAX)),
4166                ),
4167                (
4168                    Value::Keyword("primary".into()),
4169                    Value::Bool(archive.primary),
4170                ),
4171            ])))
4172        })
4173        .collect();
4174    Value::OrderedMap(Box::new(POrderedMap::from_iter([
4175        (
4176            Value::Keyword("executable/format".into()),
4177            Value::String(crate::distribution::SEALED_FORMAT.into()),
4178        ),
4179        (
4180            Value::Keyword("entry".into()),
4181            Value::Symbol(Symbol::parse(&manifest.entry)),
4182        ),
4183        (
4184            Value::Keyword("archives".into()),
4185            Value::Vector(PVector::from(archives)),
4186        ),
4187        (
4188            Value::Keyword("host/sha256".into()),
4189            Value::String(manifest.host_sha256.clone()),
4190        ),
4191        (
4192            Value::Keyword("payload/sha256".into()),
4193            Value::String(manifest.payload_sha256.clone()),
4194        ),
4195    ])))
4196}
4197
4198#[cfg(not(target_arch = "wasm32"))]
4199fn distribution_manifest_value(manifest: &crate::distribution::Manifest) -> Value {
4200    Value::OrderedMap(Box::new(POrderedMap::from_iter([
4201        (
4202            Value::Keyword("distribution/format".into()),
4203            Value::String(crate::distribution::FORMAT.into()),
4204        ),
4205        (
4206            Value::Keyword("launcher".into()),
4207            Value::String(manifest.launcher.clone()),
4208        ),
4209        (
4210            Value::Keyword("entry".into()),
4211            Value::Symbol(Symbol::parse(&manifest.entry)),
4212        ),
4213        (
4214            Value::Keyword("archive".into()),
4215            Value::String(manifest.archive.to_string_lossy().into_owned()),
4216        ),
4217        (
4218            Value::Keyword("archive/sha256".into()),
4219            Value::String(manifest.archive_sha256.clone()),
4220        ),
4221        (
4222            Value::Keyword("source/identity".into()),
4223            Value::String(manifest.source_identity.clone()),
4224        ),
4225        (
4226            Value::Keyword("source/version".into()),
4227            Value::String(manifest.source_version.clone()),
4228        ),
4229        (
4230            Value::Keyword("native/version".into()),
4231            Value::String(manifest.native_version.clone()),
4232        ),
4233        (
4234            Value::Keyword("native/sha256".into()),
4235            Value::String(manifest.native_sha256.clone()),
4236        ),
4237    ])))
4238}
4239
4240#[cfg(not(target_arch = "wasm32"))]
4241fn package_string_argument<'a>(
4242    arguments: &'a [Value],
4243    index: usize,
4244    operation: &str,
4245) -> Result<&'a str, String> {
4246    match arguments.get(index) {
4247        Some(Value::String(value)) => Ok(value),
4248        _ => Err(format!(
4249            "{operation} expects a string argument at position {index}"
4250        )),
4251    }
4252}
4253
4254#[cfg(not(target_arch = "wasm32"))]
4255fn package_optional_string_argument<'a>(
4256    arguments: &'a [Value],
4257    index: usize,
4258    operation: &str,
4259) -> Result<Option<&'a str>, String> {
4260    match arguments.get(index) {
4261        None | Some(Value::Nil) => Ok(None),
4262        Some(Value::String(value)) => Ok(Some(value)),
4263        _ => Err(format!(
4264            "{operation} expects an optional string at position {index}"
4265        )),
4266    }
4267}
4268
4269/// Invokes the active host capability provider with already-evaluated VM
4270/// values. This is the bytecode boundary for `std.native.Host/call`; the VM
4271/// remains unaware of timers, sockets, or any other concrete host operation.
4272pub fn call_host_value(service: Value, target: Value, arguments: Value) -> Result<Value, String> {
4273    let service = match service {
4274        Value::String(value) => value,
4275        _ => return Err("std.native.Host/call service must be a string".into()),
4276    };
4277    let target = match target {
4278        Value::String(value) => value,
4279        _ => return Err("std.native.Host/call method must be a string".into()),
4280    };
4281    let arguments = match arguments {
4282        Value::Vector(values) => values.iter().cloned().collect(),
4283        Value::Tuple(values) => values.iter().cloned().collect(),
4284        _ => return Err("std.native.Host/call arguments must be a vector".into()),
4285    };
4286    if !native_capability_granted("host-call") {
4287        return Ok(native_capability_denied_promise(
4288            "Host",
4289            "call",
4290            "host-call",
4291        ));
4292    }
4293    HOST_CALL_HANDLER.with(|active| {
4294        let Some(handler) = active.borrow().as_ref().cloned() else {
4295            let promise = Promise::new();
4296            promise.reject_value(host_error(
4297                "host/unavailable",
4298                "Host capability provider is unavailable",
4299            ));
4300            return Ok(Value::Promise(promise));
4301        };
4302        handler(service, target, arguments)
4303    })
4304}
4305
4306fn host_error(code: &str, message: &str) -> Value {
4307    Value::ExceptionInfo(Rc::new(ExceptionInfo {
4308        message: message.into(),
4309        data: Box::new(Value::Map(
4310            vec![
4311                (
4312                    Value::Keyword("ex/code".into()),
4313                    Value::Keyword(code.into()),
4314                ),
4315                (
4316                    Value::Keyword("ex/class".into()),
4317                    Value::Keyword("ex.class/host".into()),
4318                ),
4319            ]
4320            .into_iter()
4321            .collect(),
4322        )),
4323        cause: None,
4324        provenance: Rc::new(RefCell::new(Default::default())),
4325    }))
4326}
4327/// Installs the explicit host-call boundary for one evaluation.
4328pub fn with_host_calls<R>(
4329    handler: Rc<dyn Fn(String, String, Vec<Value>) -> Result<Value, String>>,
4330    operation: impl FnOnce() -> R,
4331) -> R {
4332    HOST_CALL_HANDLER.with(|active| {
4333        let previous = active.replace(Some(handler));
4334        let result = operation();
4335        active.replace(previous);
4336        result
4337    })
4338}
4339
4340/// Runs an evaluation with a source provider used to satisfy `require` loads.
4341pub fn with_namespace_source<R>(
4342    provider: Rc<dyn Fn(&str) -> Option<NamespaceResource>>,
4343    action: impl FnOnce() -> R,
4344) -> R {
4345    NAMESPACE_SOURCE_PROVIDER.with(|active| {
4346        let previous = active.borrow_mut().replace(provider);
4347        let result = action();
4348        *active.borrow_mut() = previous;
4349        result
4350    })
4351}
4352
4353/// Installs the direct-native namespace loader for one runtime evaluation.
4354/// The ordinary source/bytecode loader remains the default; this hook lets a
4355/// Runtime replace only the execution of a materialized namespace while the
4356/// shared namespace transaction, dependency tracking, and rollback logic stay
4357/// in one place.
4358#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4359pub(crate) fn with_direct_native_namespace_loader<R>(
4360    loader: Rc<dyn Fn(&str, NamespaceResource, &mut HashMap<String, Value>) -> Result<(), String>>,
4361    action: impl FnOnce() -> R,
4362) -> R {
4363    ACTIVE_DIRECT_NATIVE_NAMESPACE_LOADER.with(|active| {
4364        let previous = active.borrow_mut().replace(loader);
4365        let result = action();
4366        *active.borrow_mut() = previous;
4367        result
4368    })
4369}
4370
4371/// Marks the duration of generated direct-native code. Native helpers may
4372/// still call one another during this scope, but any attempt to invoke the
4373/// tree evaluator through a helper is rejected at the shared boundary.
4374#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4375pub(crate) fn with_direct_native_execution<R>(action: impl FnOnce() -> R) -> R {
4376    ACTIVE_DIRECT_NATIVE_EXECUTION.with(|active| {
4377        let previous = active.replace(true);
4378        let result = action();
4379        active.set(previous);
4380        result
4381    })
4382}
4383
4384/// Temporarily leaves the generated-code execution scope while compiling a
4385/// nested program. Macro expansion and namespace configuration are
4386/// compilation-time compatibility seams; they may use the tree evaluator,
4387/// but the validated program must re-enter the direct guard before execution.
4388#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4389pub(crate) fn without_direct_native_execution<R>(action: impl FnOnce() -> R) -> R {
4390    ACTIVE_DIRECT_NATIVE_EXECUTION.with(|active| {
4391        let previous = active.replace(false);
4392        let result = action();
4393        active.set(previous);
4394        result
4395    })
4396}
4397
4398#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4399fn direct_native_namespace_loader(
4400) -> Option<Rc<dyn Fn(&str, NamespaceResource, &mut HashMap<String, Value>) -> Result<(), String>>>
4401{
4402    ACTIVE_DIRECT_NATIVE_NAMESPACE_LOADER.with(|active| active.borrow().clone())
4403}
4404
4405#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4406pub(crate) fn direct_native_execution() -> bool {
4407    ACTIVE_DIRECT_NATIVE_EXECUTION.with(Cell::get)
4408}
4409
4410/// Runtime state captured when a VM closure crosses the synchronous machine
4411/// boundary. The no-op shape keeps ordinary VM and wasm builds free of
4412/// direct-native dependencies while native callbacks and resumptions can
4413/// restore providers, namespace selection, protocols, and the evaluator
4414/// guard on native builds.
4415#[derive(Clone, Default)]
4416pub(crate) struct NativeCallbackContext {
4417    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4418    scope: Option<crate::direct_native::NativeExecutionScope>,
4419    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4420    context: Option<DirectNativeContext>,
4421}
4422
4423impl NativeCallbackContext {
4424    pub(crate) fn capture() -> Self {
4425        #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4426        {
4427            let scope = crate::direct_native::capture_execution_scope();
4428            let context = scope.as_ref().map(|_| DirectNativeContext::capture());
4429            return Self { scope, context };
4430        }
4431        #[cfg(not(all(feature = "direct-native", not(target_arch = "wasm32"))))]
4432        {
4433            Self::default()
4434        }
4435    }
4436
4437    pub(crate) fn with<R>(&self, action: impl FnOnce() -> R) -> R {
4438        #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4439        {
4440            return crate::direct_native::with_captured_context(
4441                self.scope.as_ref(),
4442                self.context.as_ref(),
4443                action,
4444            );
4445        }
4446        #[cfg(not(all(feature = "direct-native", not(target_arch = "wasm32"))))]
4447        {
4448            action()
4449        }
4450    }
4451}