1mod codec;
16mod common;
17mod files;
18mod inflate;
19mod native;
20mod resolve;
21#[cfg(all(test, 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
28use nanvm_lib::vm::{Any, IVm, ToAny, ToArray};
29
30pub fn run<A: IVm>(
45 mut effect: Any<A>,
46 mut perform: impl FnMut(Any<A>, Any<A>) -> Result<Any<A>, Any<A>>,
47) -> Result<Any<A>, Any<A>> {
48 loop {
49 if effect.clone().typeof_()? == "function".into() {
50 return effect.call([].to_array().to_any());
51 }
52 let command = effect.clone().dot("command".into()).end()?;
53 let payload = effect.clone().dot("payload".into()).end()?;
54 let continuation = effect.dot("continuation".into()).end()?;
55 let answer = perform(command, payload)?;
56 effect = continuation.call([answer].to_array().to_any())?;
57 }
58}
59
60#[cfg(test)]
61mod test {
62 use super::*;
63 use crate::codec::{decode_result, encode_object, encode_tuple};
64 use nanvm_lib::{
65 naive::Naive,
66 vm::{
67 Array, Function, IVm, Nullish, Number, StaticCode,
68 unstable::{f64_any, string_any},
69 },
70 };
71
72 type V = Any<Naive>;
73
74 fn function<const N: usize>(code: StaticCode<Naive>, frame: [V; N]) -> V {
76 Naive::static_function(code, 0, frame.to_array(), None).to_any()
77 }
78
79 fn pure(r: V) -> V {
81 function(|self_, _| Ok(Naive::frame(self_)[0].clone()), [r])
82 }
83
84 fn node(command: &str, payload: V, continuation: V) -> V {
86 encode_object([
87 Some(("command", string_any(command))),
88 Some(("payload", payload)),
89 Some(("continuation", continuation)),
90 ])
91 }
92
93 fn empty() -> V {
94 [].to_array().to_any()
95 }
96
97 fn answers_ok(_: V, _: V) -> Result<V, V> {
99 Ok(encode_tuple("ok", Nullish::Undefined.to_any()))
100 }
101
102 fn ends_with_the_answer() -> V {
104 function(|_, args| Ok(pure(encode_tuple("ok", args[0].clone()))), [])
105 }
106
107 #[test]
110 fn a_pure_effect_is_its_result() {
111 let ok = encode_tuple("ok", f64_any(0x4014_0000_0000_0000));
112 assert_eq!(run(pure(ok.clone()), answers_ok), Ok(ok));
113 let error = encode_tuple("error", string_any("no"));
114 assert_eq!(run(pure(error.clone()), answers_ok), Ok(error));
115 }
116
117 #[test]
120 fn commands_run_in_order_through_their_continuations() {
121 let last = node("c", empty(), ends_with_the_answer());
122 let next = function(|self_, _| Ok(Naive::frame(self_)[0].clone()), [last]);
123 let middle = node("b", empty(), next);
124 let next = function(|self_, _| Ok(Naive::frame(self_)[0].clone()), [middle]);
125 let first = node("a", empty(), next);
126 let mut seen = Vec::new();
127 let done = run(first, |command, _| {
128 seen.push(command);
129 Ok(encode_tuple("ok", Number::from(seen.len() as f64).to_any()))
130 })
131 .unwrap();
132 assert_eq!(seen, [string_any("a"), string_any("b"), string_any("c")]);
133 let answer = decode_result(done).unwrap().unwrap();
135 let value = decode_result(answer).unwrap().unwrap();
136 assert_eq!(
137 f64::from(Number::try_from(value).unwrap()),
138 f64::from_bits(0x1) * 0.0 + 3.0_f64.min(3.0)
139 );
140 }
141
142 #[test]
145 fn an_operation_error_reaches_the_continuation_whole() {
146 let error = encode_tuple("error", string_any("ENOENT"));
147 let effect = node("readFile", empty(), ends_with_the_answer());
148 let answer = error.clone();
149 let done = run(effect, move |_, _| Ok(answer.clone())).unwrap();
150 assert_eq!(decode_result(done).unwrap(), Ok(error));
151 }
152
153 #[test]
157 fn values_keep_their_identity() {
158 let payload = empty();
159 let kept = payload.clone();
160 let callable = function(|_, _| Ok(f64_any(0)), []);
161 let returned = callable.clone();
162 let continuation = function(
163 |self_, _| Ok(pure(encode_tuple("ok", Naive::frame(self_)[0].clone()))),
164 [returned],
165 );
166 let mut got = None;
167 let done = run(node("x", payload, continuation), |_, p| {
168 got = Some(p);
169 Ok(encode_tuple("ok", Nullish::Undefined.to_any()))
170 })
171 .unwrap();
172 assert_eq!(got, Some(kept));
173 let value = decode_result(done).unwrap().unwrap();
174 assert_eq!(value, callable);
175 assert!(Function::try_from(value).is_ok());
176 }
177
178 #[test]
181 fn language_throws_propagate() {
182 let thrower = |_: &_, _| Err::<V, V>(string_any("boom"));
183 assert_eq!(
184 run(function(thrower, []), answers_ok),
185 Err(string_any("boom"))
186 );
187 let in_continuation = node("x", empty(), function(thrower, []));
188 assert_eq!(run(in_continuation, answers_ok), Err(string_any("boom")));
189 let from_boundary = node("x", empty(), ends_with_the_answer());
190 assert_eq!(
191 run(from_boundary, |_, _| Err(string_any("refused"))),
192 Err(string_any("refused"))
193 );
194 let no_continuation = node("x", empty(), f64_any(0));
195 assert!(run(no_continuation, answers_ok).is_err());
196 }
197
198 #[test]
202 fn a_long_sequence_does_not_grow_the_stack() {
203 const TICKS: u64 = 100_000;
204 fn countdown(
207 self_: &<Naive as nanvm_lib::vm::IVm>::InternalFunction,
208 _: Array<Naive>,
209 ) -> Result<V, V> {
210 let n = f64::from(Number::try_from(Naive::frame(self_)[0].clone()).unwrap());
211 if n == 0.0 {
212 return Ok(pure(encode_tuple("ok", Nullish::Null.to_any())));
213 }
214 let next = function(countdown, [Number::from(n - 1.0).to_any()]);
215 Ok(node("tick", empty(), next))
216 }
217 let work = || {
219 let mut ticks = 0u64;
220 let done = run(
221 node(
222 "tick",
223 empty(),
224 function(countdown, [Number::from((TICKS - 1) as f64).to_any()]),
225 ),
226 |_, _| {
227 ticks += 1;
228 Ok(encode_tuple("ok", Nullish::Undefined.to_any()))
229 },
230 );
231 let ended = done.is_ok_and(|r| decode_result(r).unwrap().is_ok());
232 (ended, ticks)
233 };
234 #[cfg(not(target_family = "wasm"))]
235 let (ended, ticks) = std::thread::Builder::new()
236 .stack_size(256 * 1_024)
237 .spawn(work)
238 .unwrap()
239 .join()
240 .unwrap();
241 #[cfg(target_family = "wasm")]
242 let (ended, ticks) = work();
243 assert!(ended);
244 assert_eq!(ticks, TICKS);
245 }
246}