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 (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(&fact, native_test_status(&[check.clone()]), vec![check], None, 0)
968 })
969 .collect::<Result<Vec<_>, _>>()?;
970 let mut suite_error = None;
971 if let Err(error) = native_test_hook(native_test_map_value(&options, "before-all")) {
972 suite_error = Some(error);
973 }
974 if suite_error.is_none() {
975 let mut fail_fast = false;
976 for fact in facts {
977 if fail_fast {
978 results.push(native_test_fact_result(
979 &fact,
980 "cancelled",
981 Vec::new(),
982 None,
983 0,
984 )?);
985 continue;
986 }
987 let result = native_test_run_fact(fact, options.clone())?;
988 fail_fast = native_test_truthy(native_test_map_value(&options, "fail-fast").as_ref())
989 && matches!(
990 native_test_map_value(&result, "status"),
991 Some(Value::Keyword(status)) if matches!(status.as_str(), "failed" | "error" | "timeout")
992 );
993 results.push(result);
994 }
995 } else {
996 let synthetic = Value::Map(PMap::from_iter([
997 (
998 Value::Keyword("namespace".into()),
999 Value::String(namespace.clone()),
1000 ),
1001 (
1002 Value::Keyword("desc".into()),
1003 Value::String("test before-all".into()),
1004 ),
1005 (
1006 Value::Keyword("name".into()),
1007 Value::String("test before-all".into()),
1008 ),
1009 (Value::Keyword("meta".into()), Value::Map(PMap::new())),
1010 ]));
1011 results.push(native_test_fact_result(
1012 &synthetic,
1013 "error",
1014 Vec::new(),
1015 suite_error,
1016 0,
1017 )?);
1018 }
1019 if let Err(error) = native_test_hook(native_test_map_value(&options, "after-all")) {
1020 let synthetic = Value::Map(PMap::from_iter([
1021 (Value::Keyword("namespace".into()), Value::String(namespace)),
1022 (
1023 Value::Keyword("desc".into()),
1024 Value::String("test after-all".into()),
1025 ),
1026 (
1027 Value::Keyword("name".into()),
1028 Value::String("test after-all".into()),
1029 ),
1030 (Value::Keyword("meta".into()), Value::Map(PMap::new())),
1031 ]));
1032 results.push(native_test_fact_result(
1033 &synthetic,
1034 "error",
1035 Vec::new(),
1036 Some(error),
1037 0,
1038 )?);
1039 }
1040 let summary = native_test_summary(results)?;
1041 native_test_set_state("last-run", summary.clone())?;
1042 Ok(summary)
1043}
1044
1045fn native_test_lookup(namespace: &str, desc: &Value) -> Result<Value, String> {
1046 Ok(native_test_facts()?
1047 .into_iter()
1048 .find(|fact| {
1049 native_test_map_value(fact, "namespace") == Some(Value::String(namespace.into()))
1050 && native_test_map_value(fact, "desc") == Some(desc.clone())
1051 })
1052 .unwrap_or(Value::Nil))
1053}
1054
1055fn native_test_desc_argument(value: Value, operation: &str) -> Result<Value, String> {
1056 match value {
1057 Value::String(value) if !value.is_empty() => Ok(Value::String(value)),
1058 _ => Err(format!(
1059 "std.native.Test/{operation} description must be a non-empty string"
1060 )),
1061 }
1062}
1063
1064fn native_test_reset() -> Result<Value, String> {
1065 native_test_set_state("facts", Value::Vector(PVector::new()))?;
1066 native_test_set_state("results", Value::Vector(PVector::new()))?;
1067 native_test_set_state("order", Value::Number(0))?;
1068 native_test_set_state("last-run", Value::Nil)?;
1069 Ok(Value::Nil)
1070}
1071
1072fn native_test_await(value: Value) -> Result<Value, String> {
1073 match value {
1074 Value::Promise(promise) => match promise.wait_state() {
1075 PromiseState::Fulfilled(value) => Ok(value),
1076 PromiseState::Rejected(error) => Err(promise_rejection_error(error)),
1077 PromiseState::Pending => Err("asynchronous test did not settle".into()),
1078 },
1079 value => Ok(value),
1080 }
1081}
1082
1083fn native_test_require_result(value: Value, operation: &str) -> Result<Rc<ResultValue>, String> {
1084 match value {
1085 Value::Result(result) => Ok(result),
1086 _ => Err(format!("std.native.Test/{operation} expects a Result")),
1087 }
1088}
1089
1090fn native_test_context_value(result: &ResultValue, key: &str) -> Value {
1091 native_test_map_value(&result.context, key).unwrap_or(Value::Nil)
1092}
1093
1094fn native_test_map_value(value: &Value, key: &str) -> Option<Value> {
1095 map_entries(value)?
1096 .into_iter()
1097 .find_map(|(candidate, value)| {
1098 matches!(candidate, Value::Keyword(keyword) if keyword.as_str() == key).then_some(value)
1099 })
1100}
1101
1102fn native_test_detail(result: &ResultValue, key: &str) -> Value {
1103 let test = native_test_context_value(result, "test");
1104 native_test_map_value(&test, key).unwrap_or(Value::Nil)
1105}
1106
1107fn native_test_failure_shape(value: &Value) -> bool {
1108 let Some(_) = map_entries(value) else {
1109 return false;
1110 };
1111 let keyword = |key: &str| matches!(native_test_map_value(value, key), Some(Value::Keyword(_)));
1112 let vector = |key: &str| {
1113 matches!(
1114 native_test_map_value(value, key),
1115 Some(Value::Vector(_) | Value::Tuple(_))
1116 )
1117 };
1118 let string = |key: &str| matches!(native_test_map_value(value, key), Some(Value::String(_)));
1119 let map = |key: &str| {
1120 native_test_map_value(value, key).is_some_and(|value| map_entries(&value).is_some())
1121 };
1122 let children_valid = match native_test_map_value(value, "failure/children") {
1123 Some(Value::Vector(children)) => children.iter().all(native_test_failure_shape),
1124 Some(Value::Tuple(children)) => children.iter().all(native_test_failure_shape),
1125 _ => false,
1126 };
1127 keyword("failure/code")
1128 && vector("failure/path")
1129 && vector("failure/in")
1130 && native_test_map_value(value, "failure/actual").is_some()
1131 && native_test_map_value(value, "failure/expected").is_some()
1132 && string("failure/message")
1133 && map("failure/context")
1134 && vector("failure/children")
1135 && children_valid
1136}
1137
1138fn native_test_failure_leaves(value: &Value, leaves: &mut Vec<Value>) {
1139 if !native_test_failure_shape(value) {
1140 return;
1141 }
1142 match native_test_map_value(value, "failure/children") {
1143 Some(Value::Vector(children)) => {
1144 if children.is_empty() {
1145 leaves.push(value.clone());
1146 } else {
1147 for child in children.iter() {
1148 native_test_failure_leaves(child, leaves);
1149 }
1150 }
1151 }
1152 Some(Value::Tuple(children)) => {
1153 if children.is_empty() {
1154 leaves.push(value.clone());
1155 } else {
1156 for child in children.iter() {
1157 native_test_failure_leaves(child, leaves);
1158 }
1159 }
1160 }
1161 _ => {}
1162 }
1163}
1164
1165fn native_test_failures(result: &ResultValue) -> Value {
1166 match native_test_context_value(result, "failures") {
1167 Value::Vector(failures) => Value::Vector(failures),
1168 Value::Tuple(failures) => Value::Vector(PVector::from_iter(failures.iter().cloned())),
1169 _ => Value::Vector(PVector::new()),
1170 }
1171}
1172
1173fn native_test_failure_seq(result: &ResultValue) -> Value {
1174 let mut leaves = Vec::new();
1175 if let Value::Vector(failures) = native_test_failures(result) {
1176 for failure in failures.iter() {
1177 native_test_failure_leaves(failure, &mut leaves);
1178 }
1179 }
1180 Value::Vector(PVector::from_iter(leaves))
1181}
1182
1183fn native_test_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
1184 let operation = operation
1185 .strip_prefix("std.native.Test/")
1186 .unwrap_or(operation);
1187 match operation {
1188 "events" => {
1189 if !values.is_empty() {
1190 return Err("std.native.Test/events expects no arguments".into());
1191 }
1192 Ok(native_test_events())
1193 }
1194 "catalog" => {
1195 if !values.is_empty() {
1196 return Err("std.native.Test/catalog expects no arguments".into());
1197 }
1198 Ok(Value::Map(PMap::from_iter([
1199 (
1200 Value::Keyword("runners".into()),
1201 Value::Vector(PVector::from_iter([
1202 Value::Keyword("code.test".into()),
1203 Value::Keyword("native".into()),
1204 ])),
1205 ),
1206 (
1207 Value::Keyword("default".into()),
1208 Value::Keyword("code.test".into()),
1209 ),
1210 (
1211 Value::Keyword("runner".into()),
1212 native_test_active_runner()?,
1213 ),
1214 (
1215 Value::Keyword("context".into()),
1216 Value::Keyword("test".into()),
1217 ),
1218 (Value::Keyword("events".into()), native_test_events()),
1219 ])))
1220 }
1221 "config" => {
1222 if values.len() > 1 {
1223 return Err("std.native.Test/config expects optional options".into());
1224 }
1225 let options = if values.is_empty() {
1226 Value::Map(PMap::new())
1227 } else {
1228 values[0].clone()
1229 };
1230 native_test_config(native_test_active_runner()?, options)
1231 }
1232 "context" => {
1233 if values.len() > 1 {
1234 return Err("std.native.Test/context expects an optional config".into());
1235 }
1236 let config = if values.is_empty() {
1237 native_test_config(native_test_active_runner()?, Value::Map(PMap::new()))?
1238 } else {
1239 let value = values[0].clone();
1240 let Some(runner) = map_value(&value, &Value::Keyword("runner".into())).cloned()
1241 else {
1242 return Err("std.native.Test/context expects a Test/config map".into());
1243 };
1244 let runner = native_test_runner(runner)?;
1245 if runner != native_test_active_runner()? {
1246 return Err(
1247 "std.native.Test/context config runner does not match the runtime".into(),
1248 );
1249 }
1250 value
1251 };
1252 Ok(Value::Pointer(PPointer::new(
1253 "test".into(),
1254 PMap::from_iter([
1255 (Value::Keyword("id".into()), Value::Keyword("test".into())),
1256 (Value::Keyword("config".into()), config),
1257 ]),
1258 )))
1259 }
1260 "compare" => {
1261 if values.len() != 2 {
1262 return Err("std.native.Test/compare expects actual and expected".into());
1263 }
1264 native_test_compare(values[0].clone(), values[1].clone())
1265 }
1266 "result" => {
1267 if values.len() != 4 {
1268 return Err(
1269 "std.native.Test/result expects name, actual, expected, and comparison Result"
1270 .into(),
1271 );
1272 }
1273 let name = values[0].clone();
1274 let actual = values[1].clone();
1275 let expected = values[2].clone();
1276 let comparison = values[3].clone();
1277 native_test_result(name, actual, expected, comparison)
1278 }
1279 "check" => {
1280 if values.is_empty() || values.len() > 3 {
1281 return Err(
1282 "std.native.Test/check expects cases, an optional check function, and an optional lifecycle map".into(),
1283 );
1284 }
1285 let cases = values[0].clone();
1286 let second = if values.len() >= 2 {
1287 Some(values[1].clone())
1288 } else {
1289 None
1290 };
1291 let third = if values.len() == 3 {
1292 Some(values[2].clone())
1293 } else {
1294 None
1295 };
1296 let (check_function, lifecycle) = match (second, third) {
1297 (Some(check), Some(lifecycle)) => (Some(check), Some(lifecycle)),
1298 (Some(value), None) if map_entries(&value).is_some() => (None, Some(value)),
1299 (check, None) => (check, None),
1300 (None, Some(_)) => unreachable!(),
1301 };
1302 if lifecycle
1303 .as_ref()
1304 .is_some_and(|value| map_entries(value).is_none())
1305 {
1306 return Err("std.native.Test/check lifecycle must be a map".into());
1307 }
1308 native_test_check(cases, check_function, lifecycle)
1309 }
1310 "register" => match values.as_slice() {
1311 [fact] => native_test_register(fact.clone()),
1312 _ => Err("std.native.Test/register expects one fact map".into()),
1313 },
1314 "facts" => {
1315 if values.len() > 1 {
1316 return Err("std.native.Test/facts expects an optional namespace".into());
1317 }
1318 let namespace = match values.as_slice() {
1319 [] => native_test_current_namespace()?,
1320 [namespace] => native_test_namespace(namespace.clone(), "facts")?,
1321 _ => unreachable!(),
1322 };
1323 Ok(Value::Vector(PVector::from_iter(
1324 native_test_facts()?.into_iter().filter(|fact| {
1325 native_test_map_value(fact, "namespace")
1326 == Some(Value::String(namespace.clone()))
1327 }),
1328 )))
1329 }
1330 "get" => {
1331 let (namespace, desc) = match values.as_slice() {
1332 [desc] => (
1333 native_test_current_namespace()?,
1334 native_test_desc_argument(desc.clone(), "get")?,
1335 ),
1336 [namespace, desc] => (
1337 native_test_namespace(namespace.clone(), "get")?,
1338 native_test_desc_argument(desc.clone(), "get")?,
1339 ),
1340 _ => {
1341 return Err(
1342 "std.native.Test/get expects a description and optional namespace".into(),
1343 )
1344 }
1345 };
1346 native_test_lookup(&namespace, &desc)
1347 }
1348 "remove" => {
1349 let (namespace, desc) = match values.as_slice() {
1350 [desc] => (
1351 native_test_current_namespace()?,
1352 native_test_desc_argument(desc.clone(), "remove")?,
1353 ),
1354 [namespace, desc] => (
1355 native_test_namespace(namespace.clone(), "remove")?,
1356 native_test_desc_argument(desc.clone(), "remove")?,
1357 ),
1358 _ => {
1359 return Err(
1360 "std.native.Test/remove expects a description and optional namespace"
1361 .into(),
1362 )
1363 }
1364 };
1365 let removed = native_test_lookup(&namespace, &desc)?;
1366 let mut facts = native_test_facts()?;
1367 facts.retain(|fact| {
1368 native_test_map_value(fact, "namespace") != Some(Value::String(namespace.clone()))
1369 || native_test_map_value(fact, "desc") != Some(desc.clone())
1370 });
1371 native_test_set_facts(facts)?;
1372 Ok(removed)
1373 }
1374 "purge" => {
1375 if values.len() > 1 {
1376 return Err("std.native.Test/purge expects an optional namespace".into());
1377 }
1378 let namespace = match values.as_slice() {
1379 [] => native_test_current_namespace()?,
1380 [namespace] => native_test_namespace(namespace.clone(), "purge")?,
1381 _ => unreachable!(),
1382 };
1383 let facts = native_test_facts()?;
1384 let removed = facts
1385 .iter()
1386 .filter(|fact| {
1387 native_test_map_value(fact, "namespace")
1388 == Some(Value::String(namespace.clone()))
1389 })
1390 .cloned()
1391 .collect::<Vec<_>>();
1392 native_test_set_facts(
1393 facts
1394 .into_iter()
1395 .filter(|fact| {
1396 native_test_map_value(fact, "namespace")
1397 != Some(Value::String(namespace.clone()))
1398 })
1399 .collect(),
1400 )?;
1401 Ok(Value::Vector(PVector::from_iter(removed)))
1402 }
1403 "reset" => {
1404 if !values.is_empty() {
1405 return Err("std.native.Test/reset expects no arguments".into());
1406 }
1407 native_test_reset()
1408 }
1409 "run-fact" => {
1410 let (fact, options) = match values.as_slice() {
1411 [fact] => (fact.clone(), Value::Map(PMap::new())),
1412 [fact, options] if map_entries(options).is_some() => {
1413 (fact.clone(), options.clone())
1414 }
1415 [_, _] => return Err("std.native.Test/run-fact options must be a map".into()),
1416 _ => return Err(
1417 "std.native.Test/run-fact expects a fact or description and optional options"
1418 .into(),
1419 ),
1420 };
1421 let fact = if map_entries(&fact).is_some() {
1422 fact
1423 } else {
1424 let desc = native_test_desc_argument(fact, "run-fact")?;
1425 let namespace = native_test_current_namespace()?;
1426 let fact = native_test_lookup(&namespace, &desc)?;
1427 if matches!(fact, Value::Nil) {
1428 return Err(format!(
1429 "std.native.Test/run-fact fact not found: {}",
1430 desc.display()
1431 ));
1432 }
1433 fact
1434 };
1435 native_test_run_fact(fact, options)
1436 }
1437 "run" => {
1438 if values.len() > 1 {
1439 return Err(
1440 "std.native.Test/run expects an optional options map; use Test/check for cases"
1441 .into(),
1442 );
1443 }
1444 let options = values
1445 .into_iter()
1446 .next()
1447 .unwrap_or_else(|| Value::Map(PMap::new()));
1448 native_test_run(options)
1449 }
1450 "summary" => match values.as_slice() {
1451 [Value::Vector(results)] => native_test_summary(results.iter().cloned().collect()),
1452 [Value::Tuple(results)] => native_test_summary(results.iter().cloned().collect()),
1453 [_] => Err("std.native.Test/summary expects a vector of fact results".into()),
1454 _ => Err("std.native.Test/summary expects one vector of fact results".into()),
1455 },
1456 "passed?" => {
1457 if values.len() != 1 {
1458 return Err("std.native.Test/passed? expects one result".into());
1459 }
1460 let result = native_test_require_result(values[0].clone(), "passed?")?;
1461 Ok(Value::Bool(
1462 result.is_success() && matches!(result.data, Value::Bool(true)),
1463 ))
1464 }
1465 "actual" | "expected" | "failures" | "failure-seq" | "failure-count" => {
1466 if values.len() != 1 {
1467 return Err(format!("std.native.Test/{operation} expects one Result"));
1468 }
1469 let result = native_test_require_result(values[0].clone(), operation)?;
1470 Ok(match operation {
1471 "actual" => native_test_detail(&result, "actual"),
1472 "expected" => native_test_detail(&result, "expected"),
1473 "failures" => native_test_failures(&result),
1474 "failure-seq" => native_test_failure_seq(&result),
1475 "failure-count" => match native_test_failure_seq(&result) {
1476 Value::Vector(values) => Value::Number(values.len() as i64),
1477 _ => unreachable!(),
1478 },
1479 _ => unreachable!(),
1480 })
1481 }
1482 "failure" => {
1483 if values.len() != 2 {
1484 return Err("std.native.Test/failure expects a Result and index".into());
1485 }
1486 let result = native_test_require_result(values[0].clone(), "failure")?;
1487 let index = match &values[1] {
1488 Value::Number(index) if *index >= 0 => *index as usize,
1489 _ => {
1490 return Err(
1491 "std.native.Test/failure index must be a non-negative integer".into(),
1492 )
1493 }
1494 };
1495 match native_test_failure_seq(&result) {
1496 Value::Vector(values) => Ok(values.get(index).cloned().unwrap_or(Value::Nil)),
1497 _ => unreachable!(),
1498 }
1499 }
1500 "failure?" => {
1501 if values.len() != 1 {
1502 return Err("std.native.Test/failure? expects one value".into());
1503 }
1504 Ok(Value::Bool(native_test_failure_shape(&values[0])))
1505 }
1506 _ => Err(format!("unknown std.native.Test operation: {operation}")),
1507 }
1508}
1509
1510struct NativeCommandRequest {
1511 app: u64,
1512 request: crate::command::Request,
1513 context: Value,
1514}
1515
1516struct NativeCommandSnapshot {
1517 app: u64,
1518 snapshot: crate::command::Snapshot<Value>,
1519}
1520
1521struct NativeCommandState {
1522 next_app: u64,
1523 next_request: u64,
1524 next_snapshot: u64,
1525 apps: HashMap<u64, crate::command::App<Value>>,
1526 requests: HashMap<u64, NativeCommandRequest>,
1527 snapshots: HashMap<u64, NativeCommandSnapshot>,
1528}
1529
1530impl Default for NativeCommandState {
1531 fn default() -> Self {
1532 Self {
1533 next_app: 1,
1534 next_request: 1,
1535 next_snapshot: 1,
1536 apps: HashMap::new(),
1537 requests: HashMap::new(),
1538 snapshots: HashMap::new(),
1539 }
1540 }
1541}
1542
1543thread_local! {
1544 static NATIVE_COMMAND_STATE: RefCell<NativeCommandState> = RefCell::new(NativeCommandState::default());
1545}
1546
1547fn native_command_keyword(name: &str) -> Value {
1548 Value::Keyword(Keyword::from(name))
1549}
1550
1551fn native_command_map(entries: impl IntoIterator<Item = (Value, Value)>) -> Value {
1552 Value::Map(PMap::from_iter(entries))
1553}
1554
1555fn native_command_pointer(context: &str, entries: impl IntoIterator<Item = (Value, Value)>) -> Value {
1556 Value::Pointer(PPointer::new(
1557 Keyword::from(context),
1558 PMap::from_iter(entries),
1559 ))
1560}
1561
1562fn native_command_handle(value: &Value, context: &str, operation: &str) -> Result<u64, String> {
1563 let Value::Pointer(pointer) = value else {
1564 return Err(format!("std.native.Command/{operation} expects a {context} handle"));
1565 };
1566 if pointer.context().as_str() != context {
1567 return Err(format!("std.native.Command/{operation} expects a {context} handle"));
1568 }
1569 match pointer.get(&native_command_keyword("id")) {
1570 Some(Value::Number(id)) if *id > 0 => Ok(*id as u64),
1571 _ => Err(format!("std.native.Command/{operation} received an invalid {context} handle")),
1572 }
1573}
1574
1575fn native_command_app(value: &Value, operation: &str) -> Result<u64, String> {
1576 native_command_handle(value, "command/app", operation)
1577}
1578
1579fn native_command_route(value: &Value, app: u64, operation: &str) -> Result<crate::command::RouteHandle, String> {
1580 let route = native_command_handle(value, "command/route", operation)?;
1581 let Value::Pointer(pointer) = value else {
1582 unreachable!()
1583 };
1584 match pointer.get(&native_command_keyword("app")) {
1585 Some(Value::Number(candidate)) if *candidate == app as i64 => {
1586 Ok(crate::command::RouteHandle::from_id(route))
1587 }
1588 _ => Err(format!("std.native.Command/{operation} route belongs to a different application")),
1589 }
1590}
1591
1592fn native_command_config_argument(value: &Value, operation: &str) -> Result<crate::command::AppConfig, String> {
1593 let Some(entries) = map_entries(value) else {
1594 return Err(format!("std.native.Command/{operation} expects a config map"));
1595 };
1596 let config = Value::Map(PMap::from_iter(entries));
1597 let id = match map_value(&config, &native_command_keyword("id")) {
1598 Some(Value::Symbol(id)) if !id.as_str().is_empty() => id.as_str().to_owned(),
1599 _ => return Err(format!("std.native.Command/{operation} config :id must be a symbol")),
1600 };
1601 let desc = match map_value(&config, &native_command_keyword("desc")) {
1602 Some(Value::String(desc)) if !desc.trim().is_empty() => desc.clone(),
1603 _ => return Err(format!("std.native.Command/{operation} config :desc must be a non-empty string")),
1604 };
1605 Ok(crate::command::AppConfig { id, desc })
1606}
1607
1608fn native_command_vector(value: &Value, operation: &str, field: &str) -> Result<Vec<Value>, String> {
1609 match value {
1610 Value::Vector(values) => Ok(values.iter().cloned().collect()),
1611 Value::Tuple(values) => Ok(values.iter().cloned().collect()),
1612 _ => Err(format!("std.native.Command/{operation} {field} must be a vector")),
1613 }
1614}
1615
1616fn native_command_string(value: &Value, operation: &str, field: &str) -> Result<String, String> {
1617 match value {
1618 Value::String(value) => Ok(value.clone()),
1619 _ => Err(format!("std.native.Command/{operation} {field} must be a string")),
1620 }
1621}
1622
1623fn native_command_strings(value: &Value, operation: &str, field: &str) -> Result<Vec<String>, String> {
1624 native_command_vector(value, operation, field)?
1625 .into_iter()
1626 .map(|value| native_command_string(&value, operation, field))
1627 .collect()
1628}
1629
1630fn native_command_keyword_argument(value: &Value, operation: &str, field: &str) -> Result<String, String> {
1631 match value {
1632 Value::Keyword(value) if !value.as_str().is_empty() => Ok(value.as_str().to_owned()),
1633 _ => Err(format!("std.native.Command/{operation} {field} must be a keyword")),
1634 }
1635}
1636
1637fn native_command_boolean(value: Option<&Value>, operation: &str, field: &str, fallback: bool) -> Result<bool, String> {
1638 match value {
1639 None | Some(Value::Nil) => Ok(fallback),
1640 Some(Value::Bool(value)) => Ok(*value),
1641 _ => Err(format!("std.native.Command/{operation} {field} must be a boolean")),
1642 }
1643}
1644
1645fn native_command_option(value: Value, operation: &str) -> Result<crate::command::OptionSpec, String> {
1646 let Some(entries) = map_entries(&value) else {
1647 return Err(format!("std.native.Command/{operation} :options entries must be maps"));
1648 };
1649 let value = Value::Map(PMap::from_iter(entries));
1650 let id = native_command_keyword_argument(
1651 map_value(&value, &native_command_keyword("id"))
1652 .ok_or_else(|| format!("std.native.Command/{operation} option :id is required"))?,
1653 operation,
1654 "option :id",
1655 )?;
1656 let long = match map_value(&value, &native_command_keyword("long")) {
1657 None | Some(Value::Nil) => None,
1658 Some(value) => Some(native_command_string(value, operation, "option :long")?),
1659 };
1660 let short = match map_value(&value, &native_command_keyword("short")) {
1661 None | Some(Value::Nil) => None,
1662 Some(Value::String(value)) if value.chars().count() == 1 => value.chars().next(),
1663 _ => return Err(format!("std.native.Command/{operation} option :short must be one character")),
1664 };
1665 let kind = match map_value(&value, &native_command_keyword("type")) {
1666 Some(Value::Keyword(kind)) if kind.as_str() == "boolean" => crate::command::OptionKind::Boolean,
1667 Some(Value::Keyword(kind)) if kind.as_str() == "string" => crate::command::OptionKind::String,
1668 _ => return Err(format!("std.native.Command/{operation} option :type must be :boolean or :string")),
1669 };
1670 let many = native_command_boolean(
1671 map_value(&value, &native_command_keyword("many?")),
1672 operation,
1673 "option :many?",
1674 false,
1675 )?;
1676 let default = match map_value(&value, &native_command_keyword("default")) {
1677 None | Some(Value::Nil) => None,
1678 Some(Value::Bool(value)) => Some(crate::command::ParsedValue::Boolean(*value)),
1679 Some(Value::String(value)) => Some(crate::command::ParsedValue::String(value.clone())),
1680 Some(value) => Some(crate::command::ParsedValue::Strings(native_command_strings(
1681 value,
1682 operation,
1683 "option :default",
1684 )?)),
1685 };
1686 Ok(crate::command::OptionSpec {
1687 id,
1688 long,
1689 short,
1690 kind,
1691 many,
1692 default,
1693 })
1694}
1695
1696fn native_command_argument(value: Value, operation: &str) -> Result<crate::command::ArgumentSpec, String> {
1697 let Some(entries) = map_entries(&value) else {
1698 return Err(format!("std.native.Command/{operation} :arguments entries must be maps"));
1699 };
1700 let value = Value::Map(PMap::from_iter(entries));
1701 Ok(crate::command::ArgumentSpec {
1702 id: native_command_keyword_argument(
1703 map_value(&value, &native_command_keyword("id"))
1704 .ok_or_else(|| format!("std.native.Command/{operation} argument :id is required"))?,
1705 operation,
1706 "argument :id",
1707 )?,
1708 required: native_command_boolean(
1709 map_value(&value, &native_command_keyword("required?")),
1710 operation,
1711 "argument :required?",
1712 true,
1713 )?,
1714 many: native_command_boolean(
1715 map_value(&value, &native_command_keyword("many?")),
1716 operation,
1717 "argument :many?",
1718 false,
1719 )?,
1720 })
1721}
1722
1723fn native_command_route_argument(value: Value, operation: &str) -> Result<crate::command::Route<Value>, String> {
1724 let Some(entries) = map_entries(&value) else {
1725 return Err(format!("std.native.Command/{operation} expects a route map"));
1726 };
1727 let value = Value::Map(PMap::from_iter(entries));
1728 let path = native_command_strings(
1729 map_value(&value, &native_command_keyword("path"))
1730 .ok_or_else(|| format!("std.native.Command/{operation} route :path is required"))?,
1731 operation,
1732 "route :path",
1733 )?;
1734 let aliases = match map_value(&value, &native_command_keyword("aliases")) {
1735 None | Some(Value::Nil) => Vec::new(),
1736 Some(value) => native_command_vector(value, operation, "route :aliases")?
1737 .into_iter()
1738 .map(|alias| native_command_strings(&alias, operation, "route :aliases"))
1739 .collect::<Result<Vec<_>, _>>()?,
1740 };
1741 let options = match map_value(&value, &native_command_keyword("options")) {
1742 None | Some(Value::Nil) => Vec::new(),
1743 Some(value) => native_command_vector(value, operation, "route :options")?
1744 .into_iter()
1745 .map(|value| native_command_option(value, operation))
1746 .collect::<Result<Vec<_>, _>>()?,
1747 };
1748 let arguments = match map_value(&value, &native_command_keyword("arguments")) {
1749 None | Some(Value::Nil) => Vec::new(),
1750 Some(value) => native_command_vector(value, operation, "route :arguments")?
1751 .into_iter()
1752 .map(|value| native_command_argument(value, operation))
1753 .collect::<Result<Vec<_>, _>>()?,
1754 };
1755 let handler = match map_value(&value, &native_command_keyword("handler")) {
1756 Some(Value::Function(function)) => Value::Function(function.clone()),
1757 _ => return Err(format!("std.native.Command/{operation} route :handler must be a function")),
1758 };
1759 Ok(crate::command::Route {
1760 spec: crate::command::RouteSpec {
1761 id: native_command_keyword_argument(
1762 map_value(&value, &native_command_keyword("id"))
1763 .ok_or_else(|| format!("std.native.Command/{operation} route :id is required"))?,
1764 operation,
1765 "route :id",
1766 )?,
1767 path,
1768 aliases,
1769 desc: native_command_string(
1770 map_value(&value, &native_command_keyword("desc"))
1771 .ok_or_else(|| format!("std.native.Command/{operation} route :desc is required"))?,
1772 operation,
1773 "route :desc",
1774 )?,
1775 options,
1776 arguments,
1777 passthrough: native_command_boolean(
1778 map_value(&value, &native_command_keyword("passthrough?")),
1779 operation,
1780 "route :passthrough?",
1781 false,
1782 )?,
1783 },
1784 handler,
1785 })
1786}
1787
1788fn native_command_parsed_value(value: &crate::command::ParsedValue) -> Value {
1789 match value {
1790 crate::command::ParsedValue::Boolean(value) => Value::Bool(*value),
1791 crate::command::ParsedValue::String(value) => Value::String(value.clone()),
1792 crate::command::ParsedValue::Strings(values) => {
1793 Value::Vector(PVector::from_iter(values.iter().cloned().map(Value::String)))
1794 }
1795 }
1796}
1797
1798fn native_command_spec_value(spec: &crate::command::RouteSpec) -> Value {
1799 native_command_map([
1800 (native_command_keyword("id"), native_command_keyword(&spec.id)),
1801 (
1802 native_command_keyword("path"),
1803 Value::Vector(PVector::from_iter(spec.path.iter().cloned().map(Value::String))),
1804 ),
1805 (
1806 native_command_keyword("aliases"),
1807 Value::Vector(PVector::from_iter(spec.aliases.iter().map(|alias| {
1808 Value::Vector(PVector::from_iter(alias.iter().cloned().map(Value::String)))
1809 }))),
1810 ),
1811 (native_command_keyword("desc"), Value::String(spec.desc.clone())),
1812 (
1813 native_command_keyword("passthrough?"),
1814 Value::Bool(spec.passthrough),
1815 ),
1816 (
1817 native_command_keyword("options"),
1818 Value::Vector(PVector::from_iter(spec.options.iter().map(|option| {
1819 let mut entries = vec![
1820 (native_command_keyword("id"), native_command_keyword(&option.id)),
1821 (native_command_keyword("long"), Value::String(option.long_name())),
1822 (
1823 native_command_keyword("short"),
1824 option
1825 .short
1826 .map(|short| Value::String(short.into()))
1827 .unwrap_or(Value::Nil),
1828 ),
1829 (
1830 native_command_keyword("type"),
1831 native_command_keyword(match option.kind {
1832 crate::command::OptionKind::Boolean => "boolean",
1833 crate::command::OptionKind::String => "string",
1834 }),
1835 ),
1836 (native_command_keyword("many?"), Value::Bool(option.many)),
1837 ];
1838 if let Some(default) = &option.default {
1839 entries.push((native_command_keyword("default"), native_command_parsed_value(default)));
1840 }
1841 native_command_map(entries)
1842 }))),
1843 ),
1844 (
1845 native_command_keyword("arguments"),
1846 Value::Vector(PVector::from_iter(spec.arguments.iter().map(|argument| {
1847 native_command_map([
1848 (native_command_keyword("id"), native_command_keyword(&argument.id)),
1849 (native_command_keyword("required?"), Value::Bool(argument.required)),
1850 (native_command_keyword("many?"), Value::Bool(argument.many)),
1851 ])
1852 }))),
1853 ),
1854 ])
1855}
1856
1857fn native_command_invocation(value: Value, operation: &str) -> Result<(Vec<String>, Value), String> {
1858 let Some(entries) = map_entries(&value) else {
1859 return Err(format!("std.native.Command/{operation} expects an invocation map"));
1860 };
1861 let value = Value::Map(PMap::from_iter(entries));
1862 let argv = native_command_strings(
1863 map_value(&value, &native_command_keyword("argv"))
1864 .ok_or_else(|| format!("std.native.Command/{operation} invocation :argv is required"))?,
1865 operation,
1866 "invocation :argv",
1867 )?;
1868 let context = map_value(&value, &native_command_keyword("context"))
1869 .cloned()
1870 .unwrap_or_else(|| Value::Map(PMap::new()));
1871 if map_entries(&context).is_none() {
1872 return Err(format!("std.native.Command/{operation} invocation :context must be a map"));
1873 }
1874 Ok((argv, context))
1875}
1876
1877fn native_command_request_value(request_id: u64, request: &crate::command::Request, context: Value) -> Value {
1878 native_command_map([
1879 (
1880 native_command_keyword("app/id"),
1881 Value::Symbol(Symbol::parse(&request.app_id)),
1882 ),
1883 (native_command_keyword("route/id"), native_command_keyword(&request.route_id)),
1884 (
1885 native_command_keyword("route/path"),
1886 Value::Vector(PVector::from_iter(request.route_path.iter().cloned().map(Value::String))),
1887 ),
1888 (
1889 native_command_keyword("argv"),
1890 Value::Vector(PVector::from_iter(request.argv.iter().cloned().map(Value::String))),
1891 ),
1892 (
1893 native_command_keyword("arguments"),
1894 native_command_map(request.arguments.iter().map(|(key, value)| {
1895 (native_command_keyword(key), native_command_parsed_value(value))
1896 })),
1897 ),
1898 (
1899 native_command_keyword("options"),
1900 native_command_map(request.options.iter().map(|(key, value)| {
1901 (native_command_keyword(key), native_command_parsed_value(value))
1902 })),
1903 ),
1904 (native_command_keyword("context"), context),
1905 (
1906 native_command_keyword("command/request"),
1907 native_command_pointer(
1908 "command/request",
1909 [(native_command_keyword("id"), Value::Number(request_id as i64))],
1910 ),
1911 ),
1912 ])
1913}
1914
1915fn native_command_response_value(response: crate::command::Response) -> Value {
1916 native_command_map([
1917 (native_command_keyword("stdout"), Value::String(response.stdout)),
1918 (native_command_keyword("stderr"), Value::String(response.stderr)),
1919 (native_command_keyword("exit"), Value::Number(response.exit)),
1920 ])
1921}
1922
1923fn native_command_response_argument(value: Value, operation: &str) -> Result<crate::command::Response, String> {
1924 let Some(entries) = map_entries(&value) else {
1925 return Err(format!("std.native.Command/{operation} handler must return a response map"));
1926 };
1927 if entries.len() != 3 {
1928 return Err(format!("std.native.Command/{operation} response must contain only :stdout, :stderr, and :exit"));
1929 }
1930 let value = Value::Map(PMap::from_iter(entries));
1931 let stdout = native_command_string(
1932 map_value(&value, &native_command_keyword("stdout"))
1933 .ok_or_else(|| format!("std.native.Command/{operation} response :stdout is required"))?,
1934 operation,
1935 "response :stdout",
1936 )?;
1937 let stderr = native_command_string(
1938 map_value(&value, &native_command_keyword("stderr"))
1939 .ok_or_else(|| format!("std.native.Command/{operation} response :stderr is required"))?,
1940 operation,
1941 "response :stderr",
1942 )?;
1943 let exit = match map_value(&value, &native_command_keyword("exit")) {
1944 Some(Value::Number(value)) => *value,
1945 _ => return Err(format!("std.native.Command/{operation} response :exit must be an integer")),
1946 };
1947 crate::command::Response { stdout, stderr, exit }
1948 .checked()
1949 .map_err(|error| error.to_string())
1950}
1951
1952fn native_command_parse(app: u64, invocation: Value, operation: &str) -> Result<Value, String> {
1953 let (argv, context) = native_command_invocation(invocation, operation)?;
1954 NATIVE_COMMAND_STATE.with(|state| {
1955 let mut state = state.borrow_mut();
1956 let request = state
1957 .apps
1958 .get(&app)
1959 .ok_or_else(|| format!("std.native.Command/{operation} application was not found"))?
1960 .parse(argv)
1961 .map_err(|error| error.to_string())?;
1962 let request_id = state.next_request;
1963 state.next_request += 1;
1964 state.requests.insert(
1965 request_id,
1966 NativeCommandRequest {
1967 app,
1968 request: request.clone(),
1969 context: context.clone(),
1970 },
1971 );
1972 Ok(native_command_request_value(request_id, &request, context))
1973 })
1974}
1975
1976fn native_command_request_id(value: &Value, operation: &str) -> Result<u64, String> {
1977 let Some(request) = map_value(value, &native_command_keyword("command/request")) else {
1978 return Err(format!("std.native.Command/{operation} expects a Command/parse request"));
1979 };
1980 native_command_handle(request, "command/request", operation)
1981}
1982
1983fn native_command_dispatch(app: u64, request_value: Value, operation: &str) -> Result<Value, String> {
1984 let request_id = native_command_request_id(&request_value, operation)?;
1985 let (handler, request, context) = NATIVE_COMMAND_STATE.with(|state| {
1986 let state = state.borrow();
1987 let stored = state
1988 .requests
1989 .get(&request_id)
1990 .ok_or_else(|| format!("std.native.Command/{operation} request was not found"))?;
1991 if stored.app != app {
1992 return Err(format!("std.native.Command/{operation} request belongs to a different application"));
1993 }
1994 let application = state
1995 .apps
1996 .get(&app)
1997 .ok_or_else(|| format!("std.native.Command/{operation} application was not found"))?;
1998 let handler = application
1999 .handler(&stored.request)
2000 .map_err(|error| error.to_string())?
2001 .clone();
2002 Ok::<_, String>((handler, stored.request.clone(), stored.context.clone()))
2003 })?;
2004 let output = call_value(handler, vec![native_command_request_value(request_id, &request, context)])?;
2005 native_command_response_argument(output, operation).map(native_command_response_value)
2006}
2007
2008fn native_command_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
2009 let operation = operation
2010 .strip_prefix("std.native.Command/")
2011 .unwrap_or(operation);
2012 match operation {
2013 "create" => match values.as_slice() {
2014 [config] => {
2015 let config = native_command_config_argument(config, operation)?;
2016 NATIVE_COMMAND_STATE.with(|state| {
2017 let mut state = state.borrow_mut();
2018 let id = state.next_app;
2019 state.next_app += 1;
2020 state
2021 .apps
2022 .insert(id, crate::command::App::create(config).map_err(|error| error.to_string())?);
2023 Ok(native_command_pointer(
2024 "command/app",
2025 [(native_command_keyword("id"), Value::Number(id as i64))],
2026 ))
2027 })
2028 }
2029 _ => Err("std.native.Command/create expects one config map".into()),
2030 },
2031 "config" => match values.as_slice() {
2032 [app] => {
2033 let app = native_command_app(app, operation)?;
2034 NATIVE_COMMAND_STATE.with(|state| {
2035 let state = state.borrow();
2036 let config = state
2037 .apps
2038 .get(&app)
2039 .ok_or_else(|| "std.native.Command/config application was not found".to_owned())?
2040 .config();
2041 Ok(native_command_map([
2042 (native_command_keyword("id"), Value::Symbol(Symbol::parse(&config.id))),
2043 (native_command_keyword("desc"), Value::String(config.desc)),
2044 ]))
2045 })
2046 }
2047 _ => Err("std.native.Command/config expects one application".into()),
2048 },
2049 "install" => match values.as_slice() {
2050 [app, route] => {
2051 let app = native_command_app(app, operation)?;
2052 let route = native_command_route_argument(route.clone(), operation)?;
2053 NATIVE_COMMAND_STATE.with(|state| {
2054 let mut state = state.borrow_mut();
2055 let handle = state
2056 .apps
2057 .get_mut(&app)
2058 .ok_or_else(|| "std.native.Command/install application was not found".to_owned())?
2059 .install(route)
2060 .map_err(|error| error.to_string())?;
2061 Ok(native_command_pointer(
2062 "command/route",
2063 [
2064 (native_command_keyword("id"), Value::Number(handle.id() as i64)),
2065 (native_command_keyword("app"), Value::Number(app as i64)),
2066 ],
2067 ))
2068 })
2069 }
2070 _ => Err("std.native.Command/install expects an application and route map".into()),
2071 },
2072 "uninstall" => match values.as_slice() {
2073 [app, route] => {
2074 let app = native_command_app(app, operation)?;
2075 let route = native_command_route(route, app, operation)?;
2076 NATIVE_COMMAND_STATE.with(|state| {
2077 state
2078 .borrow_mut()
2079 .apps
2080 .get_mut(&app)
2081 .ok_or_else(|| "std.native.Command/uninstall application was not found".to_owned())?
2082 .uninstall(route)
2083 .map(Value::Bool)
2084 .map_err(|error| error.to_string())
2085 })
2086 }
2087 _ => Err("std.native.Command/uninstall expects an application and route handle".into()),
2088 },
2089 "routes" => match values.as_slice() {
2090 [app] => {
2091 let app = native_command_app(app, operation)?;
2092 NATIVE_COMMAND_STATE.with(|state| {
2093 state
2094 .borrow()
2095 .apps
2096 .get(&app)
2097 .ok_or_else(|| "std.native.Command/routes application was not found".to_owned())?
2098 .routes()
2099 .map(|routes| Value::Vector(PVector::from_iter(routes.iter().map(native_command_spec_value))))
2100 .map_err(|error| error.to_string())
2101 })
2102 }
2103 _ => Err("std.native.Command/routes expects one application".into()),
2104 },
2105 "snapshot" => match values.as_slice() {
2106 [app] => {
2107 let app = native_command_app(app, operation)?;
2108 NATIVE_COMMAND_STATE.with(|state| {
2109 let mut state = state.borrow_mut();
2110 let snapshot = state
2111 .apps
2112 .get(&app)
2113 .ok_or_else(|| "std.native.Command/snapshot application was not found".to_owned())?
2114 .snapshot()
2115 .map_err(|error| error.to_string())?;
2116 let id = state.next_snapshot;
2117 state.next_snapshot += 1;
2118 state.snapshots.insert(id, NativeCommandSnapshot { app, snapshot });
2119 Ok(native_command_pointer(
2120 "command/snapshot",
2121 [
2122 (native_command_keyword("id"), Value::Number(id as i64)),
2123 (native_command_keyword("app"), Value::Number(app as i64)),
2124 ],
2125 ))
2126 })
2127 }
2128 _ => Err("std.native.Command/snapshot expects one application".into()),
2129 },
2130 "restore" => match values.as_slice() {
2131 [app, snapshot] => {
2132 let app = native_command_app(app, operation)?;
2133 let snapshot_id = native_command_handle(snapshot, "command/snapshot", operation)?;
2134 NATIVE_COMMAND_STATE.with(|state| {
2135 let mut state = state.borrow_mut();
2136 let snapshot = state
2137 .snapshots
2138 .get(&snapshot_id)
2139 .ok_or_else(|| "std.native.Command/restore snapshot was not found".to_owned())?;
2140 if snapshot.app != app {
2141 return Err("std.native.Command/restore snapshot belongs to a different application".into());
2142 }
2143 let snapshot = snapshot.snapshot.clone();
2144 state
2145 .apps
2146 .get_mut(&app)
2147 .ok_or_else(|| "std.native.Command/restore application was not found".to_owned())?
2148 .restore(snapshot)
2149 .map_err(|error| error.to_string())?;
2150 Ok(values[0].clone())
2151 })
2152 }
2153 _ => Err("std.native.Command/restore expects an application and snapshot".into()),
2154 },
2155 "reset" => match values.as_slice() {
2156 [app] => {
2157 let app = native_command_app(app, operation)?;
2158 NATIVE_COMMAND_STATE.with(|state| {
2159 let mut state = state.borrow_mut();
2160 state
2161 .apps
2162 .get_mut(&app)
2163 .ok_or_else(|| "std.native.Command/reset application was not found".to_owned())?
2164 .reset()
2165 .map_err(|error| error.to_string())?;
2166 state.requests.retain(|_, request| request.app != app);
2167 Ok(values[0].clone())
2168 })
2169 }
2170 _ => Err("std.native.Command/reset expects one application".into()),
2171 },
2172 "closed?" => match values.as_slice() {
2173 [app] => {
2174 let app = native_command_app(app, operation)?;
2175 NATIVE_COMMAND_STATE.with(|state| {
2176 state
2177 .borrow()
2178 .apps
2179 .get(&app)
2180 .map(|app| Value::Bool(app.closed()))
2181 .ok_or_else(|| "std.native.Command/closed? application was not found".to_owned())
2182 })
2183 }
2184 _ => Err("std.native.Command/closed? expects one application".into()),
2185 },
2186 "close" => match values.as_slice() {
2187 [app] => {
2188 let app = native_command_app(app, operation)?;
2189 NATIVE_COMMAND_STATE.with(|state| {
2190 let mut state = state.borrow_mut();
2191 state
2192 .apps
2193 .get_mut(&app)
2194 .ok_or_else(|| "std.native.Command/close application was not found".to_owned())?
2195 .close();
2196 state.requests.retain(|_, request| request.app != app);
2197 Ok(Value::Nil)
2198 })
2199 }
2200 _ => Err("std.native.Command/close expects one application".into()),
2201 },
2202 "parse" => match values.as_slice() {
2203 [app, invocation] => native_command_parse(native_command_app(app, operation)?, invocation.clone(), operation),
2204 _ => Err("std.native.Command/parse expects an application and invocation map".into()),
2205 },
2206 "dispatch" => match values.as_slice() {
2207 [app, request] => native_command_dispatch(native_command_app(app, operation)?, request.clone(), operation),
2208 _ => Err("std.native.Command/dispatch expects an application and parsed request".into()),
2209 },
2210 "run" => match values.as_slice() {
2211 [app, invocation] => {
2212 let app = native_command_app(app, operation)?;
2213 match native_command_parse(app, invocation.clone(), operation) {
2214 Ok(request) => match native_command_dispatch(app, request, operation) {
2215 Ok(response) => Ok(response),
2216 Err(error) => Ok(native_command_response_value(crate::command::Response::failure(1, error))),
2217 },
2218 Err(error) => Ok(native_command_response_value(crate::command::Response::failure(2, error))),
2219 }
2220 }
2221 _ => Err("std.native.Command/run expects an application and invocation map".into()),
2222 },
2223 _ => Err(format!("unknown std.native.Command operation: {operation}")),
2224 }
2225}
2226
2227fn native_regex_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
2228 let operation = operation
2229 .strip_prefix("std.native.RegExp/")
2230 .unwrap_or(operation);
2231 match operation {
2232 "compile" => {
2233 if values.len() != 1 {
2234 return Err("std.native.RegExp/compile expects one string".into());
2235 }
2236 let pattern = match &values[0] {
2237 Value::String(pattern) => pattern.clone(),
2238 _ => return Err("std.native.RegExp/compile expects one string".into()),
2239 };
2240 regex::Regex::new(&pattern).map_err(|error| format!("invalid regexp: {error}"))?;
2241 Ok(Value::Regex(pattern))
2242 }
2243 "pattern" => {
2244 if values.len() != 1 {
2245 return Err("std.native.RegExp/pattern expects one regexp".into());
2246 }
2247 match &values[0] {
2248 Value::Regex(pattern) => Ok(Value::String(pattern.clone())),
2249 _ => Err("std.native.RegExp/pattern expects one regexp".into()),
2250 }
2251 }
2252 "find?" => {
2253 if values.len() != 2 {
2254 return Err("std.native.RegExp/find? expects a regexp and string".into());
2255 }
2256 let pattern = match &values[0] {
2257 Value::Regex(pattern) => pattern.clone(),
2258 _ => return Err("std.native.RegExp/find? expects a regexp and string".into()),
2259 };
2260 let input = match &values[1] {
2261 Value::String(input) => input.clone(),
2262 _ => return Err("std.native.RegExp/find? expects a regexp and string".into()),
2263 };
2264 let regexp =
2265 regex::Regex::new(&pattern).map_err(|error| format!("invalid regexp: {error}"))?;
2266 Ok(Value::Bool(regexp.is_match(&input)))
2267 }
2268 "find" => {
2269 if values.len() != 2 {
2270 return Err("std.native.RegExp/find expects a regexp and string".into());
2271 }
2272 let pattern = match &values[0] {
2273 Value::Regex(pattern) => pattern.clone(),
2274 _ => return Err("std.native.RegExp/find expects a regexp and string".into()),
2275 };
2276 let input = match &values[1] {
2277 Value::String(input) => input.clone(),
2278 _ => return Err("std.native.RegExp/find expects a regexp and string".into()),
2279 };
2280 let regexp =
2281 regex::Regex::new(&pattern).map_err(|error| format!("invalid regexp: {error}"))?;
2282 Ok(regexp
2283 .find(&input)
2284 .map(|matched| Value::String(matched.as_str().to_owned()))
2285 .unwrap_or(Value::Nil))
2286 }
2287 "matches" => {
2288 if values.len() != 2 {
2289 return Err("std.native.RegExp/matches expects a regexp and string".into());
2290 }
2291 let pattern = match &values[0] {
2292 Value::Regex(pattern) => pattern.clone(),
2293 _ => return Err("std.native.RegExp/matches expects a regexp and string".into()),
2294 };
2295 let input = match &values[1] {
2296 Value::String(input) => input.clone(),
2297 _ => return Err("std.native.RegExp/matches expects a regexp and string".into()),
2298 };
2299 let anchored = format!(r"\A(?:{pattern})\z");
2300 let regexp =
2301 regex::Regex::new(&anchored).map_err(|error| format!("invalid regexp: {error}"))?;
2302 Ok(Value::Bool(regexp.is_match(&input)))
2303 }
2304 "replace" => {
2305 if values.len() != 3 {
2306 return Err(
2307 "std.native.RegExp/replace expects a regexp, string, and replacement".into(),
2308 );
2309 }
2310 let pattern = match &values[0] {
2311 Value::Regex(pattern) => pattern.clone(),
2312 _ => {
2313 return Err(
2314 "std.native.RegExp/replace expects a regexp, string, and replacement"
2315 .into(),
2316 )
2317 }
2318 };
2319 let input = match &values[1] {
2320 Value::String(input) => input.clone(),
2321 _ => {
2322 return Err(
2323 "std.native.RegExp/replace expects a regexp, string, and replacement"
2324 .into(),
2325 )
2326 }
2327 };
2328 let replacement = match &values[2] {
2329 Value::String(replacement) => replacement.clone(),
2330 _ => {
2331 return Err(
2332 "std.native.RegExp/replace expects a regexp, string, and replacement"
2333 .into(),
2334 )
2335 }
2336 };
2337 let regexp =
2338 regex::Regex::new(&pattern).map_err(|error| format!("invalid regexp: {error}"))?;
2339 Ok(Value::String(
2340 regexp
2341 .replace_all(&input, replacement.as_str())
2342 .into_owned(),
2343 ))
2344 }
2345 "split" => {
2346 if values.len() != 2 {
2347 return Err("std.native.RegExp/split expects a regexp and string".into());
2348 }
2349 let pattern = match &values[0] {
2350 Value::Regex(pattern) => pattern.clone(),
2351 _ => return Err("std.native.RegExp/split expects a regexp and string".into()),
2352 };
2353 let input = match &values[1] {
2354 Value::String(input) => input.clone(),
2355 _ => return Err("std.native.RegExp/split expects a regexp and string".into()),
2356 };
2357 if input.is_empty() {
2358 return Ok(Value::Nil);
2359 }
2360 if pattern.is_empty() {
2361 return Ok(Value::Vector(PVector::from_iter(
2362 input
2363 .chars()
2364 .map(|character| Value::String(character.to_string())),
2365 )));
2366 }
2367 let regexp =
2368 regex::Regex::new(&pattern).map_err(|error| format!("invalid regexp: {error}"))?;
2369 Ok(Value::Vector(PVector::from_iter(
2370 regexp
2371 .split(&input)
2372 .map(|part| Value::String(part.to_owned())),
2373 )))
2374 }
2375 _ => Err(format!("unknown std.native.RegExp operation: {operation}")),
2376 }
2377}
2378
2379fn file_error(operation: &str, error: FileError) -> String {
2380 let method = operation
2381 .strip_prefix("std.native.File/")
2382 .unwrap_or(operation);
2383 format!("file/{method} failed: file/{}", error.code())
2384}
2385
2386fn socket_error(operation: &str, error: SocketError) -> String {
2387 format!("{operation} failed: socket/{}", error.code())
2388}
2389
2390fn active_file_provider() -> Option<Rc<dyn FileProvider>> {
2391 ACTIVE_FILE_PROVIDER.with(|active| active.borrow().clone())
2392}
2393
2394fn rejected_file_effect(
2395 operation: &str,
2396 path: &str,
2397 target: Option<&str>,
2398 error: FileError,
2399) -> Value {
2400 let promise = Promise::new();
2401 promise.reject_value(crate::file::file_error_value(
2402 operation, path, target, &error,
2403 ));
2404 Value::Promise(promise)
2405}
2406
2407fn file_effect(
2408 operation: &str,
2409 path: &str,
2410 target: Option<&str>,
2411 invoke: impl FnOnce(&dyn FileProvider) -> Result<Promise, FileError>,
2412) -> Value {
2413 let Some(provider) = active_file_provider() else {
2414 return rejected_file_effect(operation, path, target, FileError::Denied);
2415 };
2416 match invoke(provider.as_ref()) {
2417 Ok(promise) => Value::Promise(promise),
2418 Err(error) => rejected_file_effect(operation, path, target, error),
2419 }
2420}
2421
2422fn file_option(options: &Value, name: &str) -> Option<Value> {
2423 let key = Value::Keyword(name.into());
2424 map_entries(options)?
2425 .into_iter()
2426 .find_map(|(candidate, value)| (candidate == key).then_some(value))
2427}
2428
2429fn file_options_value(value: Value, operation: &str) -> Result<Value, String> {
2430 match value {
2431 Value::Nil => Ok(Value::Map(PMap::new())),
2432 value if map_entries(&value).is_some() => Ok(value),
2433 _ => Err(format!("{operation} options must be a map")),
2434 }
2435}
2436
2437fn file_bool_option(
2438 options: &Value,
2439 name: &str,
2440 default: bool,
2441 operation: &str,
2442) -> Result<bool, String> {
2443 match file_option(options, name) {
2444 None => Ok(default),
2445 Some(Value::Bool(value)) => Ok(value),
2446 Some(_) => Err(format!("{operation} :{name} must be boolean")),
2447 }
2448}
2449
2450fn file_string_option(
2451 options: &Value,
2452 name: &str,
2453 default: &str,
2454 operation: &str,
2455) -> Result<String, String> {
2456 match file_option(options, name) {
2457 None => Ok(default.into()),
2458 Some(Value::String(value)) => Ok(value),
2459 Some(_) => Err(format!("{operation} :{name} must be a string")),
2460 }
2461}
2462
2463fn file_write_options(options: &Value) -> Result<WriteOptions, String> {
2464 let mode = match file_option(options, "mode") {
2465 None => WriteMode::Create,
2466 Some(Value::Keyword(value)) if value.as_str() == "create" => WriteMode::Create,
2467 Some(Value::Keyword(value)) if value.as_str() == "replace" => WriteMode::Replace,
2468 Some(Value::Keyword(value)) if value.as_str() == "append" => WriteMode::Append,
2469 Some(_) => {
2470 return Err("std.native.File/write :mode must be :create, :replace, or :append".into())
2471 }
2472 };
2473 Ok(WriteOptions {
2474 mode,
2475 parents: file_bool_option(options, "parents?", false, "std.native.File/write")?,
2476 })
2477}
2478
2479fn file_mkdir_options(options: &Value) -> Result<MkdirOptions, String> {
2480 Ok(MkdirOptions {
2481 parents: file_bool_option(options, "parents?", true, "std.native.File/mkdir")?,
2482 exists_ok: file_bool_option(options, "exists-ok?", true, "std.native.File/mkdir")?,
2483 })
2484}
2485
2486fn file_delete_options(options: &Value) -> Result<DeleteOptions, String> {
2487 Ok(DeleteOptions {
2488 missing_ok: file_bool_option(options, "missing-ok?", false, "std.native.File/delete")?,
2489 })
2490}
2491
2492fn file_copy_options(options: &Value) -> Result<CopyOptions, String> {
2493 Ok(CopyOptions {
2494 replace: file_bool_option(options, "replace?", false, "std.native.File/copy")?,
2495 parents: file_bool_option(options, "parents?", false, "std.native.File/copy")?,
2496 preserve_modified: file_bool_option(
2497 options,
2498 "preserve-modified?",
2499 false,
2500 "std.native.File/copy",
2501 )?,
2502 })
2503}
2504
2505fn file_move_options(options: &Value) -> Result<MoveOptions, String> {
2506 Ok(MoveOptions {
2507 replace: file_bool_option(options, "replace?", false, "std.native.File/move")?,
2508 parents: file_bool_option(options, "parents?", false, "std.native.File/move")?,
2509 atomic: file_bool_option(options, "atomic?", false, "std.native.File/move")?,
2510 })
2511}
2512
2513fn file_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
2514 let effect_operation = operation
2515 .strip_prefix("std.native.File/")
2516 .map(|method| format!("file/{method}"))
2517 .unwrap_or_else(|| operation.to_owned());
2518 match operation {
2519 "std.native.File/join" | "std.native.File/resolve" => {
2520 if values.len() != 2 {
2521 return Err(format!("{operation} expects a base and path"));
2522 }
2523 let Value::String(base) = &values[0] else {
2524 return Err(format!("{operation} expects a base and path"));
2525 };
2526 let Value::String(path) = &values[1] else {
2527 return Err(format!("{operation} expects a base and path"));
2528 };
2529 let result = if operation == "std.native.File/join" {
2530 crate::file::logical_join(&base, &path)
2531 } else {
2532 crate::file::logical_resolve(&base, &path)
2533 };
2534 result
2535 .map(Value::String)
2536 .map_err(|error| file_error(operation, error))
2537 }
2538 "std.native.File/parent" => {
2539 if values.len() != 1 {
2540 return Err("std.native.File/parent expects a path".into());
2541 }
2542 let Value::String(path) = &values[0] else {
2543 return Err("std.native.File/parent expects a path".into());
2544 };
2545 crate::file::logical_parent(path)
2546 .map(|parent| parent.map(Value::String).unwrap_or(Value::Nil))
2547 .map_err(|error| file_error(operation, error))
2548 }
2549 "std.native.File/read"
2550 | "std.native.File/exists?"
2551 | "std.native.File/stat"
2552 | "std.native.File/entries"
2553 | "std.native.File/list"
2554 | "std.native.File/walk" => {
2555 if values.len() != 1 {
2556 return Err(format!("{operation} expects a path"));
2557 }
2558 let Value::String(path) = &values[0] else {
2559 return Err(format!("{operation} expects a path"));
2560 };
2561 Ok(file_effect(
2562 &effect_operation,
2563 &path,
2564 None,
2565 |provider| match operation {
2566 "std.native.File/read" => provider.read(&path),
2567 "std.native.File/exists?" => provider.exists(&path),
2568 "std.native.File/stat" => provider.stat(&path),
2569 "std.native.File/entries" => provider.entries(&path),
2570 "std.native.File/list" => provider.list(&path),
2571 "std.native.File/walk" => provider.walk(&path),
2572 _ => unreachable!(),
2573 },
2574 ))
2575 }
2576 "std.native.File/write" => {
2577 if !(2..=3).contains(&values.len()) {
2578 return Err(
2579 "std.native.File/write expects a path, bytes, and optional options".into(),
2580 );
2581 }
2582 let Value::String(path) = &values[0] else {
2583 return Err("std.native.File/write expects a path and bytes".into());
2584 };
2585 let bytes = match &values[1] {
2586 Value::Bytes(value) => value.clone(),
2587 Value::ByteBuffer(value) => value.borrow().clone(),
2588 _ => return Err("std.native.File/write expects a path and bytes".into()),
2589 };
2590 let options = if values.len() == 3 {
2591 file_options_value(values[2].clone(), operation)?
2592 } else {
2593 Value::Map(PMap::new())
2594 };
2595 let options = file_write_options(&options)?;
2596 Ok(file_effect(&effect_operation, &path, None, |provider| {
2597 provider.write_with_options(&path, bytes, options)
2598 }))
2599 }
2600 "std.native.File/mkdir" => {
2601 if !(1..=2).contains(&values.len()) {
2602 return Err("std.native.File/mkdir expects a path and optional options".into());
2603 }
2604 let Value::String(path) = &values[0] else {
2605 return Err("std.native.File/mkdir expects a path".into());
2606 };
2607 let options = if values.len() == 2 {
2608 file_options_value(values[1].clone(), operation)?
2609 } else {
2610 Value::Map(PMap::new())
2611 };
2612 let options = file_mkdir_options(&options)?;
2613 Ok(file_effect(&effect_operation, &path, None, |provider| {
2614 provider.mkdir_with_options(&path, options)
2615 }))
2616 }
2617 "std.native.File/delete" => {
2618 if !(1..=2).contains(&values.len()) {
2619 return Err("std.native.File/delete expects a path and optional options".into());
2620 }
2621 let Value::String(path) = &values[0] else {
2622 return Err("std.native.File/delete expects a path".into());
2623 };
2624 let options = if values.len() == 2 {
2625 file_options_value(values[1].clone(), operation)?
2626 } else {
2627 Value::Map(PMap::new())
2628 };
2629 let options = file_delete_options(&options)?;
2630 Ok(file_effect(&effect_operation, &path, None, |provider| {
2631 provider.delete_with_options(&path, options)
2632 }))
2633 }
2634 "std.native.File/copy" | "std.native.File/move" => {
2635 if !(2..=3).contains(&values.len()) {
2636 return Err(format!(
2637 "{operation} expects source, target, and optional options"
2638 ));
2639 }
2640 let Value::String(source) = &values[0] else {
2641 return Err(format!("{operation} expects source and target paths"));
2642 };
2643 let Value::String(target) = &values[1] else {
2644 return Err(format!("{operation} expects source and target paths"));
2645 };
2646 let options = if values.len() == 3 {
2647 file_options_value(values[2].clone(), operation)?
2648 } else {
2649 Value::Map(PMap::new())
2650 };
2651 Ok(if operation == "std.native.File/copy" {
2652 let options = file_copy_options(&options)?;
2653 file_effect(&effect_operation, &source, Some(&target), |provider| {
2654 provider.copy(&source, &target, options)
2655 })
2656 } else {
2657 let options = file_move_options(&options)?;
2658 file_effect(&effect_operation, &source, Some(&target), |provider| {
2659 provider.move_entry(&source, &target, options)
2660 })
2661 })
2662 }
2663 "std.native.File/temp-file" | "std.native.File/temp-directory" => {
2664 if !(1..=2).contains(&values.len()) {
2665 return Err(format!("{operation} expects a parent and optional options"));
2666 }
2667 let Value::String(parent) = &values[0] else {
2668 return Err(format!("{operation} expects a parent path"));
2669 };
2670 let options = if values.len() == 2 {
2671 file_options_value(values[1].clone(), operation)?
2672 } else {
2673 Value::Map(PMap::new())
2674 };
2675 Ok(if operation == "std.native.File/temp-file" {
2676 let options = TempFileOptions {
2677 prefix: file_string_option(&options, "prefix", "tmp", operation)?,
2678 suffix: file_string_option(&options, "suffix", "", operation)?,
2679 };
2680 file_effect(&effect_operation, &parent, None, |provider| {
2681 provider.temp_file(&parent, options)
2682 })
2683 } else {
2684 let options = TempDirectoryOptions {
2685 prefix: file_string_option(&options, "prefix", "tmp", operation)?,
2686 };
2687 file_effect(&effect_operation, &parent, None, |provider| {
2688 provider.temp_directory(&parent, options)
2689 })
2690 })
2691 }
2692 _ => Err(format!("unknown std.native.File operation: {operation}")),
2693 }
2694}
2695
2696fn socket_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
2697 let operation = operation
2698 .strip_prefix("std.native.Socket/")
2699 .unwrap_or(operation);
2700 match operation {
2701 "receive-stream" | "socket/receive-stream" => {
2702 if values.len() != 1 {
2703 return Err(format!("Socket/{operation} expects a socket connection"));
2704 }
2705 let socket = socket_handle(&values[0], &format!("Socket/{operation}"))?;
2706 let events = socket_provider(operation)?
2707 .events(socket)
2708 .map_err(|e| socket_error(operation, e))?;
2709 Ok(host_stream(
2710 Rc::new(move || socket_receive_promise(events)),
2711 Rc::new(|| Ok(())),
2712 ))
2713 }
2714 "socket/connect" => {
2715 if values.len() != 4 {
2716 return Err("socket/connect expects a host, port, options, and callback".into());
2717 }
2718 let host = match &values[0] {
2719 Value::String(value) => value.clone(),
2720 _ => {
2721 return Err("socket/connect expects a host, port, options, and callback".into())
2722 }
2723 };
2724 let port = value_u16_integer(&values[1], "socket/connect", false)?;
2725 let _options = &values[2];
2726 let callback = match &values[3] {
2727 Value::Function(value) => value.clone(),
2728 _ => return Err("socket/connect expects a callback".into()),
2729 };
2730 let callback = Rc::new(move |event| {
2731 let arguments = match event {
2732 SocketEvent::Connected(handle) => {
2733 vec![Value::Nil, Value::Number(handle as i64)]
2734 }
2735 SocketEvent::Failed(_, error) => vec![Value::String(error), Value::Nil],
2736 SocketEvent::Data(_, _) | SocketEvent::Closed(_) => return,
2737 };
2738 let _ = call_function(&callback, arguments);
2739 });
2740 socket_provider(operation)?
2741 .connect(&host, port, callback)
2742 .map(|handle| Value::Number(handle as i64))
2743 .map_err(|error| socket_error(operation, error))
2744 }
2745 "socket/listen" => {
2746 if values.len() != 4 {
2747 return Err("socket/listen expects a host, port, options, and callback".into());
2748 }
2749 let host = match &values[0] {
2750 Value::String(value) => value.clone(),
2751 _ => return Err("socket/listen expects a host string".into()),
2752 };
2753 let port = value_u16_integer(&values[1], "socket/listen", true)?;
2754 let _options = &values[2];
2755 let callback = match &values[3] {
2756 Value::Function(value) => value.clone(),
2757 _ => return Err("socket/listen expects a callback".into()),
2758 };
2759 let callback = Rc::new(move |event| {
2760 let _ = call_function(&callback, vec![socket_server_event_value(event)]);
2761 });
2762 socket_provider(operation)?
2763 .listen(&host, port, callback)
2764 .map(|handle| Value::Number(handle as i64))
2765 .map_err(|error| socket_error(operation, error))
2766 }
2767 "socket/endpoint" => {
2768 if values.len() != 1 {
2769 return Err("socket/endpoint expects a server".into());
2770 }
2771 let server = socket_handle(&values[0], "socket/endpoint")?;
2772 socket_provider(operation)?
2773 .endpoint(server)
2774 .map(|(host, port)| {
2775 Value::Map(PMap::from_iter([
2776 (Value::Keyword("host".into()), Value::String(host)),
2777 (Value::Keyword("port".into()), Value::Number(port as i64)),
2778 ]))
2779 })
2780 .map_err(|error| socket_error(operation, error))
2781 }
2782 "socket/events" => {
2783 if values.len() != 2 {
2784 return Err("socket/events expects a socket handle and options".into());
2785 }
2786 let handle = socket_handle(&values[0], "socket/events")?;
2787 let _options = &values[1];
2788 socket_provider(operation)?
2789 .events(handle)
2790 .map(|stream| Value::Number(stream as i64))
2791 .map_err(|error| socket_error(operation, error))
2792 }
2793 "socket/next" => {
2794 if values.len() != 1 {
2795 return Err("socket/next expects a socket stream".into());
2796 }
2797 let stream = socket_handle(&values[0], "socket/next")?;
2798 socket_provider(operation)?
2799 .next(stream)
2800 .map(Value::Promise)
2801 .map_err(|error| socket_error(operation, error))
2802 }
2803 "socket/send" => {
2804 if values.len() != 2 {
2805 return Err("socket/send expects a socket connection and bytes".into());
2806 }
2807 let socket = socket_handle(&values[0], "socket/send")?;
2808 let bytes = match &values[1] {
2809 Value::Bytes(value) => value.clone(),
2810 Value::ByteBuffer(value) => value.borrow().clone(),
2811 _ => return Err("socket/send expects a socket connection and bytes".into()),
2812 };
2813 socket_provider(operation)?
2814 .send(socket, &bytes)
2815 .map(|count| Value::Number(count as i64))
2816 .map_err(|error| socket_error(operation, error))
2817 }
2818 "socket/close" => {
2819 if values.len() != 1 {
2820 return Err("socket/close expects a socket connection".into());
2821 }
2822 let socket = socket_handle(&values[0], "socket/close")?;
2823 socket_provider(operation)?
2824 .close(socket)
2825 .map(|()| Value::Nil)
2826 .map_err(|error| socket_error(operation, error))
2827 }
2828 _ => Err(format!("unknown std.native.Socket operation: {operation}")),
2829 }
2830}
2831
2832fn socket_receive_promise(stream: SocketHandle) -> Result<Promise, String> {
2833 let source = socket_provider("Socket/receive-stream")?
2834 .next(stream)
2835 .map_err(|e| socket_error("Socket/receive-stream", e))?;
2836 let output = Promise::new();
2837 let settled = output.clone();
2838 source.on_settle(Rc::new(move |result| match result {
2839 PromiseState::Rejected(error) => {
2840 settled.reject_rejection(error);
2841 }
2842 PromiseState::Pending => {}
2843 PromiseState::Fulfilled(event) => {
2844 let entries = map_entries(&event).unwrap_or_default();
2845 let kind = entries.iter().find_map(|(k, v)| {
2846 if matches!(k, Value::Keyword(key) if key.as_str() == "type") {
2847 Some(v.clone())
2848 } else {
2849 None
2850 }
2851 });
2852 match kind {
2853 Some(Value::Keyword(kind)) if kind.as_str() == "data" => {
2854 let bytes = entries
2855 .into_iter()
2856 .find_map(|(k, v)| {
2857 if matches!(k, Value::Keyword(key) if key.as_str() == "bytes") {
2858 Some(v)
2859 } else {
2860 None
2861 }
2862 })
2863 .unwrap_or(Value::Nil);
2864 settled.resolve(bytes);
2865 }
2866 Some(Value::Keyword(kind)) if kind.as_str() == "close" => {
2867 settled.resolve(Value::Nil);
2868 }
2869 Some(Value::Keyword(kind)) if kind.as_str() == "error" => {
2870 settled.reject("socket receive failed");
2871 }
2872 _ => {
2873 settled.reject("Socket/receive-stream received an invalid event");
2874 }
2875 }
2876 }
2877 }));
2878 let poll = source.clone();
2879 output.set_poller(Rc::new(move || {
2880 poll.state();
2881 }));
2882 let wait = source.clone();
2883 output.set_waiter(Rc::new(move || {
2884 wait.wait_state();
2885 }));
2886 Ok(output)
2887}
2888
2889fn socket_handle(value: &Value, operation: &str) -> Result<SocketHandle, String> {
2890 value_u64_integer(value, operation)
2891 .map(|value| value as SocketHandle)
2892 .map_err(|_| format!("{operation} expects a socket handle"))
2893}
2894
2895fn native_host_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
2896 let method = operation
2897 .strip_prefix("std.native.Host/")
2898 .unwrap_or(operation);
2899 let (service, target, arguments) = match method {
2900 "call" => {
2901 if values.len() != 3 {
2902 return Err(
2903 "std.native.Host/call expects service, method, and an argument vector".into(),
2904 );
2905 }
2906 let service = match &values[0] {
2907 Value::String(value) => value.clone(),
2908 _ => return Err("std.native.Host/call service must be a string".into()),
2909 };
2910 let target = match &values[1] {
2911 Value::String(value) => value.clone(),
2912 _ => return Err("std.native.Host/call method must be a string".into()),
2913 };
2914 let arguments = match &values[2] {
2915 Value::Vector(values) => values.iter().cloned().collect(),
2916 Value::Tuple(values) => values.iter().cloned().collect(),
2917 _ => return Err("std.native.Host/call arguments must be a vector".into()),
2918 };
2919 (service, target, arguments)
2920 }
2921 "describe" | "capabilities" => {
2922 if !values.is_empty() {
2923 return Err(format!("std.native.Host/{method} expects no arguments"));
2924 }
2925 ("host".into(), method.into(), Vec::new())
2926 }
2927 "capability?" => {
2928 if values.len() != 1 {
2929 return Err("std.native.Host/capability? expects one capability".into());
2930 }
2931 ("host".into(), "capability?".into(), vec![values[0].clone()])
2932 }
2933 _ => return Err(format!("unknown std.native.Host method: {method}")),
2934 };
2935 HOST_CALL_HANDLER.with(|active| {
2936 let Some(handler) = active.borrow().as_ref().cloned() else {
2937 let promise = Promise::new();
2938 promise.reject_value(host_error(
2939 "host/unavailable",
2940 "Host capability provider is unavailable",
2941 ));
2942 return Ok(Value::Promise(promise));
2943 };
2944 handler(service, target, arguments)
2945 })
2946}
2947
2948pub(crate) fn namespace_identifier(value: Value, operation: &str) -> Result<String, String> {
2949 match value {
2950 Value::Symbol(name) if name.get_namespace().is_none() => Ok(name.as_str().to_owned()),
2951 Value::String(name) => Ok(name),
2952 Value::Namespace(namespace) => Ok(namespace.name().as_str().to_owned()),
2953 _ => Err(format!(
2954 "{operation} expects an unqualified namespace symbol, string, or Namespace"
2955 )),
2956 }
2957}
2958
2959fn namespace_descriptor(registry: &NamespaceRegistry<Value>, name: &str) -> Value {
2960 let state = registry
2961 .load_state(name)
2962 .or_else(|| registry.find(name).map(|_| NamespaceLoadState::Loaded))
2963 .map(NamespaceLoadState::as_str)
2964 .unwrap_or("unknown");
2965 let package = package_catalog().coordinate_for_namespace(name);
2966 let origin = if name.starts_with("std.native") {
2967 "embedded"
2968 } else if package.is_some() {
2969 "package"
2970 } else if registry.find(name).is_some() {
2971 "runtime"
2972 } else {
2973 "registered"
2974 };
2975 let mut fields = vec![
2976 (
2977 Value::Keyword("namespace/name".into()),
2978 Value::Symbol(Symbol::parse(name)),
2979 ),
2980 (
2981 Value::Keyword("namespace/state".into()),
2982 Value::Keyword(state.into()),
2983 ),
2984 (
2985 Value::Keyword("namespace/role".into()),
2986 Value::Keyword(
2987 registry
2988 .find(name)
2989 .map(|namespace| namespace.role())
2990 .unwrap_or_else(|| "standard".into())
2991 .into(),
2992 ),
2993 ),
2994 (
2995 Value::Keyword("namespace/revision".into()),
2996 Value::Number(registry.module_revision(name) as i64),
2997 ),
2998 (
2999 Value::Keyword("namespace/origin".into()),
3000 Value::Keyword(origin.into()),
3001 ),
3002 ];
3003 if let Some(package) = package {
3004 fields.push((
3005 Value::Keyword("namespace/package".into()),
3006 Value::String(package),
3007 ));
3008 }
3009 Value::OrderedMap(Box::new(POrderedMap::from_iter(fields)))
3010}
3011
3012fn native_runtime_values(
3013 operation: &str,
3014 values: Vec<Value>,
3015 env: &mut HashMap<String, Value>,
3016) -> Result<Value, String> {
3017 let method = operation
3018 .strip_prefix("std.native.Runtime/")
3019 .unwrap_or(operation);
3020 let registry = namespace_registry()?;
3021 match method {
3022 "ns-publics" => {
3023 let namespace = match values.as_slice() {
3024 [Value::Symbol(name)] if name.get_namespace().is_none() => name.as_str().to_owned(),
3025 [Value::String(name)] => name.clone(),
3026 [Value::Namespace(namespace)] => namespace.name().as_str().to_owned(),
3027 _ => {
3028 return Err(
3029 "std.native.Runtime/ns-publics expects a namespace symbol or string".into(),
3030 )
3031 }
3032 };
3033 let target = registry
3034 .find(&namespace)
3035 .ok_or_else(|| format!("No such namespace: {namespace}"))?;
3036 let mut mappings = target.mappings();
3037 mappings.retain(|(_, var)| var.symbol().get_namespace() == Some(namespace.as_str()));
3038 mappings.sort_by(|(left, _), (right, _)| left.as_str().cmp(right.as_str()));
3039 Ok(Value::OrderedMap(Box::new(POrderedMap::from_iter(
3040 mappings.into_iter().map(|(name, var)| {
3041 (
3042 Value::Symbol(Symbol::create(None, name.as_str())),
3043 Value::Var(var),
3044 )
3045 }),
3046 ))))
3047 }
3048 "ns-aliases" => {
3049 let name = match values.as_slice() {
3050 [value] => namespace_identifier(value.clone(), "std.native.Runtime/ns-aliases")?,
3051 _ => return Err("std.native.Runtime/ns-aliases expects one namespace".into()),
3052 };
3053 let target = registry
3054 .find(&name)
3055 .ok_or_else(|| format!("No such namespace: {name}"))?;
3056 let mut aliases = target.aliases();
3057 aliases.sort_by(|(left, _), (right, _)| left.as_str().cmp(right.as_str()));
3058 Ok(Value::OrderedMap(Box::new(POrderedMap::from_iter(
3059 aliases.into_iter().map(|(alias, namespace)| {
3060 (Value::Symbol(alias), Value::Namespace(Rc::new(namespace)))
3061 }),
3062 ))))
3063 }
3064 "ns-find" => {
3065 if values.len() != 1 {
3066 return Err("std.native.Runtime/ns-find expects one namespace".into());
3067 }
3068 let name = namespace_identifier(values[0].clone(), "std.native.Runtime/ns-find")?;
3069 Ok(registry
3070 .find(&name)
3071 .map(|namespace| Value::Namespace(Rc::new(namespace)))
3072 .unwrap_or(Value::Nil))
3073 }
3074 "ns-create" => match values.as_slice() {
3075 [Value::Symbol(name)] if name.get_namespace().is_none() => {
3076 let namespace = registry.find_or_create(name.as_str());
3077 Ok(Value::Namespace(Rc::new(namespace)))
3078 }
3079 _ => Err("std.native.Runtime/ns-create expects an unqualified symbol".into()),
3080 },
3081 "ns-name" => match values.as_slice() {
3082 [Value::Namespace(namespace)] => Ok(Value::Symbol(namespace.name().clone())),
3083 [Value::Symbol(name)]
3084 if name.get_namespace().is_none()
3085 && namespace_registry()?.find(name.as_str()).is_some() =>
3086 {
3087 Ok(Value::Symbol(name.clone()))
3088 }
3089 _ => Err("std.native.Runtime/ns-name expects a namespace".into()),
3090 },
3091 "current" => {
3092 if !values.is_empty() {
3093 return Err("std.native.Runtime/current expects no arguments".into());
3094 }
3095 Ok(Value::Symbol(registry.current().name().clone()))
3096 }
3097 "snapshot" => {
3098 if !values.is_empty() {
3099 return Err("std.native.Runtime/snapshot expects no arguments".into());
3100 }
3101 let namespaces = registry
3102 .known_names()
3103 .into_iter()
3104 .map(|name| namespace_descriptor(®istry, name.as_str()))
3105 .collect::<Vec<_>>();
3106 Ok(Value::OrderedMap(Box::new(POrderedMap::from_iter([
3107 (
3108 Value::Keyword("env/current".into()),
3109 Value::Symbol(registry.current().name().clone()),
3110 ),
3111 (
3112 Value::Keyword("env/namespaces".into()),
3113 Value::Vector(PVector::from(namespaces)),
3114 ),
3115 ]))))
3116 }
3117 "namespaces" => {
3118 if !values.is_empty() {
3119 return Err("std.native.Runtime/namespaces expects no arguments".into());
3120 }
3121 Ok(Value::Vector(PVector::from(
3122 registry
3123 .known_names()
3124 .into_iter()
3125 .map(|name| namespace_descriptor(®istry, name.as_str()))
3126 .collect::<Vec<_>>(),
3127 )))
3128 }
3129 "namespace" => {
3130 if values.len() != 1 {
3131 return Err("std.native.Runtime/namespace expects one namespace".into());
3132 }
3133 let name = namespace_identifier(values[0].clone(), operation)?;
3134 if registry.load_state(&name).is_none() && registry.find(&name).is_none() {
3135 Ok(Value::Nil)
3136 } else {
3137 Ok(namespace_descriptor(®istry, &name))
3138 }
3139 }
3140 "module" => {
3141 if values.len() != 1 {
3142 return Err("std.native.Runtime/module expects one module path".into());
3143 }
3144 let requested = match &values[0] {
3145 Value::String(path) => path.clone(),
3146 Value::Symbol(name) => name.as_str().to_owned(),
3147 _ => {
3148 return Err(
3149 "std.native.Runtime/module expects a path string or namespace symbol"
3150 .into(),
3151 )
3152 }
3153 };
3154 let source = requested.strip_prefix("classpath:").unwrap_or(&requested);
3155 let namespace = if source.ends_with(".hal") || source.ends_with(".hrl") {
3156 source
3157 .trim_end_matches(".hal")
3158 .trim_end_matches(".hrl")
3159 .trim_start_matches("./")
3160 .replace('/', ".")
3161 } else {
3162 source.to_owned()
3163 };
3164 let revision = registry.module_revision(&namespace);
3165 if revision == 0
3166 && registry.load_state(&namespace).is_none()
3167 && registry.find(&namespace).is_none()
3168 {
3169 return Ok(Value::Nil);
3170 }
3171 let dependencies = registry
3172 .module_dependencies(&namespace)
3173 .into_iter()
3174 .map(|dependency| {
3175 Value::String(format!("{}.hal", dependency.as_str().replace('.', "/")))
3176 })
3177 .collect::<Vec<_>>();
3178 Ok(Value::OrderedMap(Box::new(POrderedMap::from_iter([
3179 (
3180 Value::Keyword("module/path".into()),
3181 Value::String(requested),
3182 ),
3183 (
3184 Value::Keyword("module/namespace".into()),
3185 Value::Symbol(Symbol::parse(&namespace)),
3186 ),
3187 (
3188 Value::Keyword("module/revision".into()),
3189 Value::Number(revision as i64),
3190 ),
3191 (
3192 Value::Keyword("module/dependencies".into()),
3193 Value::Vector(PVector::from(dependencies)),
3194 ),
3195 ]))))
3196 }
3197 "vars" => {
3198 if values.len() > 1 {
3199 return Err("std.native.Runtime/vars expects zero or one namespace".into());
3200 }
3201 let name = if values.is_empty() {
3202 registry.current().name().as_str().to_owned()
3203 } else {
3204 namespace_identifier(values[0].clone(), operation)?
3205 };
3206 let namespace = registry
3207 .find(&name)
3208 .ok_or_else(|| format!("namespace/not-found: {name}"))?;
3209 let mut mappings = namespace.mappings();
3210 mappings.retain(|(_, var)| var.symbol().get_namespace() == Some(name.as_str()));
3211 mappings.sort_by(|(left, _), (right, _)| left.as_str().cmp(right.as_str()));
3212 Ok(Value::OrderedMap(Box::new(POrderedMap::from_iter(
3213 mappings.into_iter().map(|(symbol, var)| {
3214 (
3215 Value::Symbol(Symbol::create(None, symbol.as_str())),
3216 Value::Var(var),
3217 )
3218 }),
3219 ))))
3220 }
3221 "eval" => {
3222 if values.len() != 1 {
3223 return Err("std.native.Runtime/eval expects one form".into());
3224 }
3225 let mut environment = crate::core::current_namespace_environment()?;
3226 let result = eval_value(values[0].clone(), &mut environment);
3227 crate::core::save_namespace_environment(®istry, &mut environment);
3228 result
3229 }
3230 "load-string" => {
3231 let [Value::String(source)] = values.as_slice() else {
3232 return Err("std.native.Runtime/load-string expects one string".into());
3233 };
3234 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
3235 if direct_native_execution() {
3236 return eval_direct_native_source(source);
3237 }
3238 eval_value_text(source, env)
3239 }
3240 "var-sym" => {
3241 let [Value::Var(var)] = values.as_slice() else {
3242 return Err("std.native.Runtime/var-sym expects one Var".into());
3243 };
3244 Ok(Value::Symbol(var.symbol().clone()))
3245 }
3246 "macroexpand-1" => {
3247 if values.len() != 1 {
3248 return Err("std.native.Runtime/macroexpand-1 expects one form".into());
3249 }
3250 let form = value_to_form(&values[0])?;
3251 let mut environment = current_namespace_environment()?;
3252 form_to_value(¯oexpand_once(&form, &mut environment)?)
3253 }
3254 "gensym" => {
3255 let prefix = match values.as_slice() {
3256 [] => "G__".to_owned(),
3257 [Value::String(prefix)] => prefix.clone(),
3258 [value] => {
3259 return Err(format!(
3260 "gensym expects a string prefix, got {}",
3261 portable_type_name(value)
3262 ))
3263 }
3264 _ => return Err("gensym expects zero or one arguments".into()),
3265 };
3266 Ok(Value::Symbol(Symbol::from(gensym(&prefix))))
3267 }
3268 "eval-in" => {
3269 if values.len() != 2 {
3270 return Err("std.native.Runtime/eval-in expects namespace and forms".into());
3271 }
3272 let target = namespace_identifier(values[0].clone(), operation)?;
3273 if registry.find(&target).is_none() {
3274 return Err(format!(
3275 "std.native.Runtime/eval-in requires an existing namespace: {target}"
3276 ));
3277 }
3278 let forms = iterator_values(values[1].clone())?
3279 .into_iter()
3280 .map(|value| value_to_form(&value))
3281 .collect::<Result<Vec<_>, _>>()?;
3282 let previous = registry.current().name().as_str().to_owned();
3283 select_namespace_environment(®istry, env, &target);
3284 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
3285 let result = if direct_native_execution() {
3286 let source = if forms.is_empty() {
3287 "nil".to_owned()
3288 } else {
3289 Form::List(
3290 std::iter::once(Form::Symbol("do".into()))
3291 .chain(forms.iter().cloned())
3292 .collect(),
3293 )
3294 .to_string()
3295 };
3296 eval_direct_native_source(&source)
3297 } else {
3298 let mut result = Value::Nil;
3299 for form in &forms {
3300 result = eval(form, env)?;
3301 }
3302 Ok(result)
3303 };
3304 #[cfg(not(all(feature = "direct-native", not(target_arch = "wasm32"))))]
3305 let result = {
3306 let mut result = Value::Nil;
3307 for form in &forms {
3308 result = eval(form, env)?;
3309 }
3310 Ok(result)
3311 };
3312 select_namespace_environment(®istry, env, &previous);
3313 result
3314 }
3315 "alias-state" => {
3316 if values.len() != 1 && values.len() != 2 {
3317 return Err(
3318 "std.native.Runtime/alias-state expects alias or namespace and alias".into(),
3319 );
3320 }
3321 let (owner, alias_value) = if values.len() == 2 {
3322 (
3323 namespace_identifier(values[0].clone(), operation)?,
3324 &values[1],
3325 )
3326 } else {
3327 (registry.current().name().as_str().to_owned(), &values[0])
3328 };
3329 let Value::Symbol(alias) = alias_value else {
3330 return Err(
3331 "std.native.Runtime/alias-state expects an unqualified alias symbol".into(),
3332 );
3333 };
3334 if alias.get_namespace().is_some() {
3335 return Err(
3336 "std.native.Runtime/alias-state expects an unqualified alias symbol".into(),
3337 );
3338 }
3339 let Some(namespace) = registry.find(&owner) else {
3340 return Ok(Value::Nil);
3341 };
3342 let target = namespace
3346 .lazy_target(alias.as_str())
3347 .or_else(|| {
3348 namespace
3349 .aliases()
3350 .into_iter()
3351 .find(|(name, _)| name == alias)
3352 .map(|(_, target)| target.name().clone())
3353 })
3354 .or_else(|| {
3355 registry
3356 .global_aliases()
3357 .into_iter()
3358 .find(|(name, _)| name == alias)
3359 .map(|(_, target)| target)
3360 });
3361 let Some(target) = target else {
3362 return Ok(Value::Nil);
3363 };
3364 let state = registry
3365 .load_state(target.as_str())
3366 .or_else(|| {
3367 registry
3368 .find(target.as_str())
3369 .map(|_| NamespaceLoadState::Loaded)
3370 })
3371 .map(NamespaceLoadState::as_str)
3372 .unwrap_or("unknown");
3373 Ok(Value::Map(PMap::from_iter([
3374 (Value::Keyword("alias".into()), Value::Symbol(alias.clone())),
3375 (Value::Keyword("target".into()), Value::Symbol(target)),
3376 (Value::Keyword("state".into()), Value::Keyword(state.into())),
3377 ])))
3378 }
3379 "intern-var" => {
3380 if values.len() != 3 && values.len() != 4 {
3381 return Err(
3382 "std.native.Runtime/intern-var expects namespace, symbol, Var, and optional metadata"
3383 .into(),
3384 );
3385 }
3386 let target = namespace_identifier(values[0].clone(), operation)?;
3387 let Value::Symbol(name) = &values[1] else {
3388 return Err(
3389 "std.native.Runtime/intern-var expects an unqualified target symbol".into(),
3390 );
3391 };
3392 if name.get_namespace().is_some() {
3393 return Err(
3394 "std.native.Runtime/intern-var expects an unqualified target symbol".into(),
3395 );
3396 }
3397 let Value::Var(source) = &values[2] else {
3398 return Err("std.native.Runtime/intern-var expects a source Var".into());
3399 };
3400 let mut metadata = source.metadata();
3401 if let Some(extension) = values.get(3) {
3402 let Some(entries) = map_entries(extension) else {
3403 return Err(
3404 "std.native.Runtime/intern-var metadata extension must be a map".into(),
3405 );
3406 };
3407 for (key, value) in entries {
3408 metadata.extra.insert(key.display(), value.display());
3409 }
3410 }
3411 let value = source.deref_value();
3412 if let Value::Function(function) = &value {
3413 if function.is_macro {
3414 ACTIVE_MACROS.with(|active| {
3415 if let Some(macros) = active.borrow().as_ref() {
3416 macros.borrow_mut().insert(
3417 (target.clone(), name.as_str().to_owned()),
3418 function.clone(),
3419 );
3420 }
3421 });
3422 }
3423 }
3424 Ok(Value::Var(
3425 registry.find_or_create(&target).intern_with_metadata(
3426 name.as_str(),
3427 value,
3428 metadata,
3429 ),
3430 ))
3431 }
3432 _ => Err(format!("unknown std.native.Runtime method: {method}")),
3433 }
3434}
3435
3436#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
3437fn eval_direct_native_source(source: &str) -> Result<Value, String> {
3438 let context = DirectNativeContext::capture();
3439 let forms = crate::kernel::read_forms(source).map_err(|error| error.to_string())?;
3440 let has_namespace_form = forms.iter().any(|form| {
3441 matches!(
3442 form_without_metadata(&form.form),
3443 Form::List(items)
3444 if matches!(items.first(), Some(Form::Symbol(operator)) if operator == "ns" || operator == "ns+")
3445 )
3446 });
3447 let config = if has_namespace_form {
3448 crate::vm::source_namespace_config(&forms).map_err(|error| error.to_string())?
3449 } else {
3450 crate::kernel::GeneratedNamespaceConfig::defaults()
3451 };
3452 let namespaces = context.namespaces.clone();
3453 let program = context
3454 .with(|| {
3455 without_direct_native_execution(|| {
3456 crate::vm::compile_source_with_config_allow_unbound_globals(
3457 source,
3458 &namespaces,
3459 config,
3460 )
3461 })
3462 })
3463 .map_err(|error| error.to_string())?;
3464 let mut program = program;
3465 if program.namespace.is_none() {
3466 program.namespace = Some(context.namespace.clone());
3467 }
3468 let engine = crate::direct_native::NativeEngine::new();
3469 let result = context.with(|| engine.execute_blocking(Rc::new(program)));
3474 let nested_multimethods = context.multimethods.borrow().clone();
3478 ACTIVE_MULTIMETHODS.with(|active| {
3479 active.borrow_mut().extend(nested_multimethods);
3480 });
3481 result.map(|report| report.value)
3482}
3483
3484fn eval_value(value: Value, env: &mut HashMap<String, Value>) -> Result<Value, String> {
3485 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
3486 if direct_native_execution() {
3487 return eval_direct_native_source(&value_to_form(&value)?.to_string());
3488 }
3489 eval(&value_to_form(&value)?, env)
3490}
3491
3492fn native_package_values(
3493 operation: &str,
3494 arguments: Vec<Value>,
3495 env: &mut HashMap<String, Value>,
3496) -> Result<Value, String> {
3497 let method = operation
3498 .strip_prefix("std.native.Package/")
3499 .unwrap_or(operation);
3500 if matches!(method, "build" | "inspect" | "seal" | "inspect-seal" | "verify-seal") {
3501 return native_package_artifact_values(method, arguments);
3502 }
3503 let expected = match method {
3504 "catalog" => 0..=0,
3505 "find" | "ensure" | "load" | "state" => 1..=1,
3506 "unload" => 1..=2,
3507 _ => return Err(format!("unknown std.native.Package method: {method}")),
3508 };
3509 if !expected.contains(&arguments.len()) {
3510 return Err(format!(
3511 "std.native.Package/{method} expects {} arguments",
3512 expected.start()
3513 ));
3514 }
3515 let catalog = package_catalog();
3516 if method == "catalog" {
3517 return Ok(catalog.catalog_value());
3518 }
3519 let target = match arguments.first() {
3520 Some(Value::Symbol(value)) => value.as_str().to_owned(),
3521 Some(Value::String(value)) => value.clone(),
3522 Some(Value::Keyword(value)) => value.as_str().to_owned(),
3523 Some(value @ Value::OrderedMap(_)) if method == "ensure" || method == "unload" => {
3524 package_descriptor_coordinate(value).ok_or_else(|| {
3525 format!("std.native.Package/{method} descriptor requires :package/coordinate")
3526 })?
3527 }
3528 _ => {
3529 return Err(format!(
3530 "std.native.Package/{method} expects a namespace, coordinate, or exact descriptor"
3531 ))
3532 }
3533 };
3534 let found = catalog.find(&target);
3535 if method == "find" {
3536 return Ok(found.map(|(_, value)| value).unwrap_or(Value::Nil));
3537 }
3538 let Some((coordinate, descriptor)) = found else {
3539 if method == "state" {
3540 return Ok(Value::Nil);
3541 }
3542 return Err(format!("package/not-locked: {target}"));
3543 };
3544 if method == "state" {
3545 return Ok(Value::Keyword(
3546 catalog
3547 .state(&coordinate)
3548 .unwrap_or_else(|| "available".into())
3549 .into(),
3550 ));
3551 }
3552 if method == "load" {
3553 if catalog.coordinate_for_namespace(&target).as_deref() != Some(&coordinate) {
3554 return Err("std.native.Package/load expects a locked namespace".into());
3555 }
3556 if catalog.state(&coordinate).as_deref() != Some("ready") {
3557 return Err(format!(
3558 "package/not-ready: {coordinate}; call Package/ensure first"
3559 ));
3560 }
3561 let registry = namespace_registry()?;
3562 require_namespace(®istry, env, &target)?;
3563 return Ok(Value::Symbol(Symbol::parse(&target)));
3564 }
3565 if method == "ensure" {
3566 if catalog.state(&coordinate).as_deref() == Some("ready") {
3567 let promise = Promise::new();
3568 promise.resolve(descriptor);
3569 return Ok(Value::Promise(promise));
3570 }
3571 if let Some(pending) = catalog.pending(&coordinate) {
3572 return Ok(Value::Promise(pending));
3573 }
3574 } else if catalog.state(&coordinate).as_deref() == Some("available") {
3575 let promise = Promise::new();
3576 promise.resolve(Value::Vector(PVector::new()));
3577 return Ok(Value::Promise(promise));
3578 } else if catalog.pending(&coordinate).is_some() {
3579 return Err(format!("package/busy: {coordinate}"));
3580 }
3581 if method == "unload" {
3582 if let Some(options) = arguments.get(1) {
3583 if map_entries(options).is_none() {
3584 return Err("std.native.Package/unload options must be a map".into());
3585 }
3586 if let Some(value) = map_value(options, &Value::Keyword("cascade".into())) {
3587 if !matches!(value, Value::Bool(_)) {
3588 return Err("std.native.Package/unload :cascade must be boolean".into());
3589 }
3590 }
3591 }
3592 }
3593 let previous_state = catalog
3594 .state(&coordinate)
3595 .unwrap_or_else(|| "available".into());
3596 catalog.set_state(
3597 &coordinate,
3598 if method == "ensure" {
3599 "ensuring"
3600 } else {
3601 "unloading"
3602 },
3603 );
3604 HOST_CALL_HANDLER.with(|active| {
3605 let Some(handler) = active.borrow().as_ref().cloned() else {
3606 let promise = Promise::new();
3607 promise.reject_value(host_error(
3608 "package/unsupported",
3609 "Package capability provider is unavailable",
3610 ));
3611 catalog.set_state(
3612 &coordinate,
3613 if method == "ensure" {
3614 "failed"
3615 } else {
3616 &previous_state
3617 },
3618 );
3619 return Ok(Value::Promise(promise));
3620 };
3621 let mut provider_arguments = vec![descriptor];
3622 provider_arguments.extend(arguments.iter().skip(1).cloned());
3623 let result = handler("package".into(), method.into(), provider_arguments);
3624 if let Ok(Value::Promise(promise)) = &result {
3625 let state = catalog.clone();
3626 let coordinate = coordinate.clone();
3627 let operation = method.to_owned();
3628 let rollback = previous_state.clone();
3629 state.set_pending(&coordinate, Some(promise.clone()));
3630 promise.on_settle(Rc::new(move |settlement| {
3631 let next = match (&operation[..], settlement) {
3632 ("ensure", PromiseState::Fulfilled(_)) => "ready",
3633 ("ensure", _) => "failed",
3634 ("unload", PromiseState::Fulfilled(_)) => "available",
3635 ("unload", _) => rollback.as_str(),
3636 _ => rollback.as_str(),
3637 };
3638 state.set_state(&coordinate, next);
3639 state.set_pending(&coordinate, None);
3640 }));
3641 } else if result.is_ok() {
3642 catalog.set_state(
3643 &coordinate,
3644 if method == "ensure" {
3645 "ready"
3646 } else {
3647 "available"
3648 },
3649 );
3650 } else {
3651 catalog.set_state(
3652 &coordinate,
3653 if method == "ensure" {
3654 "failed"
3655 } else {
3656 &previous_state
3657 },
3658 );
3659 }
3660 result
3661 })
3662}
3663
3664#[cfg(not(target_arch = "wasm32"))]
3665fn native_package_artifact_values(method: &str, arguments: Vec<Value>) -> Result<Value, String> {
3666 match method {
3667 "build" => {
3668 if !(1..=4).contains(&arguments.len()) {
3669 return Err("std.native.Package/build expects input and up to three options".into());
3670 }
3671 let input = package_string_argument(&arguments, 0, "std.native.Package/build")?.to_owned();
3672 let output = package_optional_string_argument(&arguments, 1, "std.native.Package/build")?
3673 .map(str::to_owned);
3674 let package = package_optional_string_argument(&arguments, 2, "std.native.Package/build")?
3675 .map(str::to_owned);
3676 let profile = package_optional_string_argument(&arguments, 3, "std.native.Package/build")?
3677 .map(str::to_owned);
3678 let built = std::thread::spawn(move || {
3679 crate::package::build_path_with_package(
3680 std::path::Path::new(&input),
3681 output.as_deref().map(std::path::Path::new),
3682 package.as_deref(),
3683 profile.as_deref().map(std::path::Path::new),
3684 )
3685 })
3686 .join()
3687 .map_err(|_| "std.native.Package/build thread panicked".to_owned())??;
3688 Ok(Value::String(built.to_string_lossy().into_owned()))
3689 }
3690 "inspect" => {
3691 if arguments.len() != 1 {
3692 return Err("std.native.Package/inspect expects one archive path".into());
3693 }
3694 Ok(Value::String(crate::package::inspect_path(
3695 std::path::Path::new(package_string_argument(
3696 &arguments,
3697 0,
3698 "std.native.Package/inspect",
3699 )?),
3700 )?))
3701 }
3702 "seal" => {
3703 if arguments.len() != 1 {
3704 return Err("std.native.Package/seal expects one descriptor map".into());
3705 }
3706 let manifest = crate::distribution::seal(&sealed_package_spec(&arguments[0])?)?;
3707 Ok(sealed_manifest_value(&manifest))
3708 }
3709 "inspect-seal" | "verify-seal" => {
3710 if arguments.len() != 1 {
3711 return Err(format!("std.native.Package/{method} expects one executable path"));
3712 }
3713 let path = std::path::Path::new(package_string_argument(
3714 &arguments,
3715 0,
3716 &format!("std.native.Package/{method}"),
3717 )?);
3718 let found = if method == "inspect-seal" {
3719 crate::distribution::inspect_sealed(path)?
3720 } else {
3721 crate::distribution::verify_sealed(path)?
3722 };
3723 Ok(found
3724 .as_ref()
3725 .map(sealed_manifest_value)
3726 .unwrap_or(Value::Nil))
3727 }
3728 _ => unreachable!("caller restricts native package artifact methods"),
3729 }
3730}
3731
3732#[cfg(target_arch = "wasm32")]
3733fn native_package_artifact_values(method: &str, _arguments: Vec<Value>) -> Result<Value, String> {
3734 Err(format!("package/unsupported: Package/{method} is unavailable on wasm"))
3735}
3736
3737#[cfg(not(target_arch = "wasm32"))]
3738fn sealed_package_spec(value: &Value) -> Result<crate::distribution::SealSpec, String> {
3739 let entries = map_entries(value)
3740 .ok_or_else(|| "std.native.Package/seal expects one descriptor map".to_owned())?;
3741 let descriptor = Value::OrderedMap(Box::new(POrderedMap::from_iter(entries)));
3742 let required_string = |key: &str| {
3743 match map_value(&descriptor, &Value::Keyword(key.into())) {
3744 Some(Value::String(value)) if !value.is_empty() => Ok(value.clone()),
3745 Some(_) => Err(format!("std.native.Package/seal :{key} must be a non-empty string")),
3746 None => Err(format!("std.native.Package/seal is missing :{key}")),
3747 }
3748 };
3749 let entry = match map_value(&descriptor, &Value::Keyword("entry".into())) {
3750 Some(Value::Symbol(value)) => value.as_str().to_owned(),
3751 Some(_) => return Err("std.native.Package/seal :entry must be a symbol".into()),
3752 None => return Err("std.native.Package/seal is missing :entry".into()),
3753 };
3754 let host = match map_value(&descriptor, &Value::Keyword("host".into())) {
3755 Some(Value::String(value)) if !value.is_empty() => std::path::PathBuf::from(value),
3756 Some(_) => return Err("std.native.Package/seal :host must be a non-empty string".into()),
3757 None => std::env::current_exe()
3758 .map_err(|error| format!("cannot determine current native executable: {error}"))?,
3759 };
3760 let archives = match map_value(&descriptor, &Value::Keyword("archives".into())) {
3761 Some(value) => iterator_values(value.clone())?,
3762 None => return Err("std.native.Package/seal is missing :archives".into()),
3763 }
3764 .into_iter()
3765 .map(|archive| {
3766 let archive_entries = map_entries(&archive)
3767 .ok_or_else(|| "std.native.Package/seal archives must be maps".to_owned())?;
3768 let archive = Value::OrderedMap(Box::new(POrderedMap::from_iter(archive_entries)));
3769 let path = match map_value(&archive, &Value::Keyword("path".into())) {
3770 Some(Value::String(value)) if !value.is_empty() => std::path::PathBuf::from(value),
3771 Some(_) => {
3772 return Err(
3773 "std.native.Package/seal archive :path must be a non-empty string".into(),
3774 )
3775 }
3776 None => return Err("std.native.Package/seal archive is missing :path".into()),
3777 };
3778 let primary = match map_value(&archive, &Value::Keyword("primary".into())) {
3779 Some(Value::Bool(value)) => *value,
3780 Some(_) => return Err("std.native.Package/seal archive :primary must be boolean".into()),
3781 None => false,
3782 };
3783 Ok(crate::distribution::SealArchive { path, primary })
3784 })
3785 .collect::<Result<Vec<_>, String>>()?;
3786 Ok(crate::distribution::SealSpec {
3787 host,
3788 output: std::path::PathBuf::from(required_string("output")?),
3789 entry,
3790 archives,
3791 })
3792}
3793
3794#[cfg(not(target_arch = "wasm32"))]
3795fn sealed_manifest_value(manifest: &crate::distribution::SealedManifest) -> Value {
3796 let archives: Vec<Value> = manifest
3797 .archives
3798 .iter()
3799 .map(|archive| {
3800 Value::OrderedMap(Box::new(POrderedMap::from_iter([
3801 (
3802 Value::Keyword("identity".into()),
3803 Value::String(archive.identity.clone()),
3804 ),
3805 (
3806 Value::Keyword("version".into()),
3807 Value::String(archive.version.clone()),
3808 ),
3809 (
3810 Value::Keyword("sha256".into()),
3811 Value::String(archive.sha256.clone()),
3812 ),
3813 (
3814 Value::Keyword("offset".into()),
3815 Value::Number(i64::try_from(archive.offset).unwrap_or(i64::MAX)),
3816 ),
3817 (
3818 Value::Keyword("length".into()),
3819 Value::Number(i64::try_from(archive.length).unwrap_or(i64::MAX)),
3820 ),
3821 (Value::Keyword("primary".into()), Value::Bool(archive.primary)),
3822 ])))
3823 })
3824 .collect();
3825 Value::OrderedMap(Box::new(POrderedMap::from_iter([
3826 (
3827 Value::Keyword("executable/format".into()),
3828 Value::String(crate::distribution::SEALED_FORMAT.into()),
3829 ),
3830 (
3831 Value::Keyword("entry".into()),
3832 Value::Symbol(Symbol::parse(&manifest.entry)),
3833 ),
3834 (Value::Keyword("archives".into()), Value::Vector(PVector::from(archives))),
3835 (
3836 Value::Keyword("host/sha256".into()),
3837 Value::String(manifest.host_sha256.clone()),
3838 ),
3839 (
3840 Value::Keyword("payload/sha256".into()),
3841 Value::String(manifest.payload_sha256.clone()),
3842 ),
3843 ])))
3844}
3845
3846#[cfg(not(target_arch = "wasm32"))]
3847fn package_string_argument<'a>(
3848 arguments: &'a [Value],
3849 index: usize,
3850 operation: &str,
3851) -> Result<&'a str, String> {
3852 match arguments.get(index) {
3853 Some(Value::String(value)) => Ok(value),
3854 _ => Err(format!("{operation} expects a string argument at position {index}")),
3855 }
3856}
3857
3858#[cfg(not(target_arch = "wasm32"))]
3859fn package_optional_string_argument<'a>(
3860 arguments: &'a [Value],
3861 index: usize,
3862 operation: &str,
3863) -> Result<Option<&'a str>, String> {
3864 match arguments.get(index) {
3865 None | Some(Value::Nil) => Ok(None),
3866 Some(Value::String(value)) => Ok(Some(value)),
3867 _ => Err(format!("{operation} expects an optional string at position {index}")),
3868 }
3869}
3870
3871pub fn call_host_value(service: Value, target: Value, arguments: Value) -> Result<Value, String> {
3875 let service = match service {
3876 Value::String(value) => value,
3877 _ => return Err("std.native.Host/call service must be a string".into()),
3878 };
3879 let target = match target {
3880 Value::String(value) => value,
3881 _ => return Err("std.native.Host/call method must be a string".into()),
3882 };
3883 let arguments = match arguments {
3884 Value::Vector(values) => values.iter().cloned().collect(),
3885 Value::Tuple(values) => values.iter().cloned().collect(),
3886 _ => return Err("std.native.Host/call arguments must be a vector".into()),
3887 };
3888 if !native_capability_granted("host-call") {
3889 return Ok(native_capability_denied_promise(
3890 "Host",
3891 "call",
3892 "host-call",
3893 ));
3894 }
3895 HOST_CALL_HANDLER.with(|active| {
3896 let Some(handler) = active.borrow().as_ref().cloned() else {
3897 let promise = Promise::new();
3898 promise.reject_value(host_error(
3899 "host/unavailable",
3900 "Host capability provider is unavailable",
3901 ));
3902 return Ok(Value::Promise(promise));
3903 };
3904 handler(service, target, arguments)
3905 })
3906}
3907
3908fn host_error(code: &str, message: &str) -> Value {
3909 Value::ExceptionInfo(Rc::new(ExceptionInfo {
3910 message: message.into(),
3911 data: Box::new(Value::Map(
3912 vec![
3913 (
3914 Value::Keyword("ex/code".into()),
3915 Value::Keyword(code.into()),
3916 ),
3917 (
3918 Value::Keyword("ex/class".into()),
3919 Value::Keyword("ex.class/host".into()),
3920 ),
3921 ]
3922 .into_iter()
3923 .collect(),
3924 )),
3925 cause: None,
3926 provenance: Rc::new(RefCell::new(Default::default())),
3927 }))
3928}
3929pub fn with_host_calls<R>(
3931 handler: Rc<dyn Fn(String, String, Vec<Value>) -> Result<Value, String>>,
3932 operation: impl FnOnce() -> R,
3933) -> R {
3934 HOST_CALL_HANDLER.with(|active| {
3935 let previous = active.replace(Some(handler));
3936 let result = operation();
3937 active.replace(previous);
3938 result
3939 })
3940}
3941
3942pub fn with_namespace_source<R>(
3944 provider: Rc<dyn Fn(&str) -> Option<NamespaceResource>>,
3945 action: impl FnOnce() -> R,
3946) -> R {
3947 NAMESPACE_SOURCE_PROVIDER.with(|active| {
3948 let previous = active.borrow_mut().replace(provider);
3949 let result = action();
3950 *active.borrow_mut() = previous;
3951 result
3952 })
3953}
3954
3955#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
3961pub(crate) fn with_direct_native_namespace_loader<R>(
3962 loader: Rc<dyn Fn(&str, NamespaceResource, &mut HashMap<String, Value>) -> Result<(), String>>,
3963 action: impl FnOnce() -> R,
3964) -> R {
3965 ACTIVE_DIRECT_NATIVE_NAMESPACE_LOADER.with(|active| {
3966 let previous = active.borrow_mut().replace(loader);
3967 let result = action();
3968 *active.borrow_mut() = previous;
3969 result
3970 })
3971}
3972
3973#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
3977pub(crate) fn with_direct_native_execution<R>(action: impl FnOnce() -> R) -> R {
3978 ACTIVE_DIRECT_NATIVE_EXECUTION.with(|active| {
3979 let previous = active.replace(true);
3980 let result = action();
3981 active.set(previous);
3982 result
3983 })
3984}
3985
3986#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
3991pub(crate) fn without_direct_native_execution<R>(action: impl FnOnce() -> R) -> R {
3992 ACTIVE_DIRECT_NATIVE_EXECUTION.with(|active| {
3993 let previous = active.replace(false);
3994 let result = action();
3995 active.set(previous);
3996 result
3997 })
3998}
3999
4000#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4001fn direct_native_namespace_loader(
4002) -> Option<Rc<dyn Fn(&str, NamespaceResource, &mut HashMap<String, Value>) -> Result<(), String>>>
4003{
4004 ACTIVE_DIRECT_NATIVE_NAMESPACE_LOADER.with(|active| active.borrow().clone())
4005}
4006
4007#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4008pub(crate) fn direct_native_execution() -> bool {
4009 ACTIVE_DIRECT_NATIVE_EXECUTION.with(Cell::get)
4010}
4011
4012#[derive(Clone, Default)]
4018pub(crate) struct NativeCallbackContext {
4019 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4020 scope: Option<crate::direct_native::NativeExecutionScope>,
4021 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4022 context: Option<DirectNativeContext>,
4023}
4024
4025impl NativeCallbackContext {
4026 pub(crate) fn capture() -> Self {
4027 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4028 {
4029 let scope = crate::direct_native::capture_execution_scope();
4030 let context = scope.as_ref().map(|_| DirectNativeContext::capture());
4031 return Self { scope, context };
4032 }
4033 #[cfg(not(all(feature = "direct-native", not(target_arch = "wasm32"))))]
4034 {
4035 Self::default()
4036 }
4037 }
4038
4039 pub(crate) fn with<R>(&self, action: impl FnOnce() -> R) -> R {
4040 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
4041 {
4042 return crate::direct_native::with_captured_context(
4043 self.scope.as_ref(),
4044 self.context.as_ref(),
4045 action,
4046 );
4047 }
4048 #[cfg(not(all(feature = "direct-native", not(target_arch = "wasm32"))))]
4049 {
4050 action()
4051 }
4052 }
4053}