1mod codec;
16mod common;
17mod files;
18mod inflate;
19mod native;
20mod resolve;
21#[cfg(all(any(test, feature = "test-support"), not(target_family = "wasm")))]
22mod scratch;
23
24pub use files::{Dirent, IoError, MAX_FILE_SIZE_BYTES, normalize};
25pub use native::Native;
26pub use resolve::{FileModule, resolve_file_module};
27
28#[cfg(feature = "test-support")]
31pub use codec::decode_result;
32#[cfg(all(feature = "test-support", not(target_family = "wasm")))]
33pub use scratch::Scratch;
34
35use nanvm_lib::vm::{Any, IVm, ToAny, ToArray};
36
37pub fn run<A: IVm>(
52 mut effect: Any<A>,
53 mut perform: impl FnMut(Any<A>, Any<A>) -> Result<Any<A>, Any<A>>,
54) -> Result<Any<A>, Any<A>> {
55 loop {
56 if effect.typeof_()? == "function".into() {
57 return effect.call([].to_array().to_any());
58 }
59 let command = effect.clone().dot("command".into()).end()?;
60 let payload = effect.clone().dot("payload".into()).end()?;
61 let continuation = effect.dot("continuation".into()).end()?;
62 let answer = perform(command, payload)?;
63 effect = continuation.call([answer].to_array().to_any())?;
64 }
65}
66
67#[cfg(test)]
68mod test {
69 use super::*;
70 use crate::codec::{decode_result, encode_object, encode_tuple};
71 use nanvm_lib::{
72 naive::Naive,
73 vm::{
74 Array, Function, IVm, Nullish, Number, StaticCode,
75 unstable::{f64_any, string_any},
76 },
77 };
78
79 type V = Any<Naive>;
80
81 fn function<const N: usize>(code: StaticCode<Naive>, frame: [V; N]) -> V {
83 Naive::static_function(code, 0, frame.to_array(), None).to_any()
84 }
85
86 fn pure(r: V) -> V {
88 function(|self_, _| Ok(Naive::frame(self_)[0].clone()), [r])
89 }
90
91 fn node(command: &str, payload: V, continuation: V) -> V {
93 encode_object([
94 Some(("command", string_any(command))),
95 Some(("payload", payload)),
96 Some(("continuation", continuation)),
97 ])
98 }
99
100 fn empty() -> V {
101 [].to_array().to_any()
102 }
103
104 fn answers_ok(_: V, _: V) -> Result<V, V> {
106 Ok(encode_tuple("ok", Nullish::Undefined.to_any()))
107 }
108
109 fn ends_with_the_answer() -> V {
111 function(|_, args| Ok(pure(encode_tuple("ok", args[0].clone()))), [])
112 }
113
114 #[test]
117 fn a_pure_effect_is_its_result() {
118 let ok = encode_tuple("ok", f64_any(0x4014_0000_0000_0000));
119 assert_eq!(run(pure(ok.clone()), answers_ok), Ok(ok));
120 let error = encode_tuple("error", string_any("no"));
121 assert_eq!(run(pure(error.clone()), answers_ok), Ok(error));
122 }
123
124 #[test]
127 fn commands_run_in_order_through_their_continuations() {
128 let last = node("c", empty(), ends_with_the_answer());
129 let next = function(|self_, _| Ok(Naive::frame(self_)[0].clone()), [last]);
130 let middle = node("b", empty(), next);
131 let next = function(|self_, _| Ok(Naive::frame(self_)[0].clone()), [middle]);
132 let first = node("a", empty(), next);
133 let mut seen = Vec::new();
134 let done = run(first, |command, _| {
135 seen.push(command);
136 Ok(encode_tuple("ok", Number::from(seen.len() as f64).to_any()))
137 })
138 .unwrap();
139 assert_eq!(seen, [string_any("a"), string_any("b"), string_any("c")]);
140 let answer = decode_result(done).unwrap().unwrap();
142 let value = decode_result(answer).unwrap().unwrap();
143 assert_eq!(
144 f64::from(Number::try_from(value).unwrap()),
145 f64::from_bits(0x1) * 0.0 + 3.0_f64.min(3.0)
146 );
147 }
148
149 #[test]
152 fn an_operation_error_reaches_the_continuation_whole() {
153 let error = encode_tuple("error", string_any("ENOENT"));
154 let effect = node("readFile", empty(), ends_with_the_answer());
155 let answer = error.clone();
156 let done = run(effect, move |_, _| Ok(answer.clone())).unwrap();
157 assert_eq!(decode_result(done).unwrap(), Ok(error));
158 }
159
160 #[test]
164 fn values_keep_their_identity() {
165 let payload = empty();
166 let kept = payload.clone();
167 let callable = function(|_, _| Ok(f64_any(0)), []);
168 let returned = callable.clone();
169 let continuation = function(
170 |self_, _| Ok(pure(encode_tuple("ok", Naive::frame(self_)[0].clone()))),
171 [returned],
172 );
173 let mut got = None;
174 let done = run(node("x", payload, continuation), |_, p| {
175 got = Some(p);
176 Ok(encode_tuple("ok", Nullish::Undefined.to_any()))
177 })
178 .unwrap();
179 assert_eq!(got, Some(kept));
180 let value = decode_result(done).unwrap().unwrap();
181 assert_eq!(value, callable);
182 assert!(Function::try_from(value).is_ok());
183 }
184
185 #[test]
188 fn language_throws_propagate() {
189 let thrower = |_: &_, _| Err::<V, V>(string_any("boom"));
190 assert_eq!(
191 run(function(thrower, []), answers_ok),
192 Err(string_any("boom"))
193 );
194 let in_continuation = node("x", empty(), function(thrower, []));
195 assert_eq!(run(in_continuation, answers_ok), Err(string_any("boom")));
196 let from_boundary = node("x", empty(), ends_with_the_answer());
197 assert_eq!(
198 run(from_boundary, |_, _| Err(string_any("refused"))),
199 Err(string_any("refused"))
200 );
201 let no_continuation = node("x", empty(), f64_any(0));
202 assert!(run(no_continuation, answers_ok).is_err());
203 }
204
205 #[test]
209 fn a_long_sequence_does_not_grow_the_stack() {
210 const TICKS: u64 = 100_000;
211 fn countdown(
214 self_: &<Naive as nanvm_lib::vm::IVm>::InternalFunction,
215 _: Array<Naive>,
216 ) -> Result<V, V> {
217 let n = f64::from(Number::try_from(Naive::frame(self_)[0].clone()).unwrap());
218 if n == 0.0 {
219 return Ok(pure(encode_tuple("ok", Nullish::Null.to_any())));
220 }
221 let next = function(countdown, [Number::from(n - 1.0).to_any()]);
222 Ok(node("tick", empty(), next))
223 }
224 let work = || {
226 let mut ticks = 0u64;
227 let done = run(
228 node(
229 "tick",
230 empty(),
231 function(countdown, [Number::from((TICKS - 1) as f64).to_any()]),
232 ),
233 |_, _| {
234 ticks += 1;
235 Ok(encode_tuple("ok", Nullish::Undefined.to_any()))
236 },
237 );
238 let ended = done.is_ok_and(|r| decode_result(r).unwrap().is_ok());
239 (ended, ticks)
240 };
241 #[cfg(not(target_family = "wasm"))]
242 let (ended, ticks) = std::thread::Builder::new()
243 .stack_size(256 * 1_024)
244 .spawn(work)
245 .unwrap()
246 .join()
247 .unwrap();
248 #[cfg(target_family = "wasm")]
249 let (ended, ticks) = work();
250 assert!(ended);
251 assert_eq!(ticks, TICKS);
252 }
253}