1use nanvm_effects_node::{Native, run};
10use nanvm_lib::{
11 naive::Naive,
12 vm::{
13 Any, Array, Function, Module, Nullish, ToAny, ToArray, Unpacked, exports,
14 unstable::string_any,
15 },
16};
17use std::cell::RefCell;
18
19#[path = "../gen.samples/mod.rs"]
22pub mod samples;
23
24pub fn load(module: Module<Naive>) -> Result<Function<Naive>, Any<Naive>> {
27 exports(module)?
28 .own_property(&"default".into())
29 .and_then(|value| Function::try_from(value).ok())
30 .ok_or_else(|| Any::from("module has no default function"))
31}
32
33pub fn perform(entry: &Function<Naive>) -> Result<Vec<u8>, Any<Naive>> {
37 let effect = entry.call([].to_array())?;
38 let mut host = Native::new(std::io::empty(), Vec::new(), Vec::new());
39 let result = run(effect, |command, payload| host.perform(command, payload))?;
40 output(result, host.stdout().clone())
41}
42
43pub fn call(
46 entry: &Function<Naive>,
47 input: f64,
48) -> Result<std::string::String, std::string::String> {
49 entry
50 .call([input.to_any()].to_array())
51 .map(spell)
52 .map_err(spell)
53}
54
55pub fn spell(value: Any<Naive>) -> std::string::String {
60 match Unpacked::from(value.clone()) {
61 Unpacked::Array(items) => {
62 let items: Vec<_> = items.into_iter().map(spell).collect();
63 format!("[{}]", items.join(","))
64 }
65 Unpacked::Object(object) => {
66 let entries: Vec<_> = object
67 .own_entries()
68 .into_iter()
69 .map(|(key, item)| format!("{}:{}", quote(key.to_any()), spell(item)))
70 .collect();
71 format!("{{{}}}", entries.join(","))
72 }
73 Unpacked::String(_) => quote(value),
74 Unpacked::BigInt(_) => format!("{}n", text(value)),
75 Unpacked::Function(_) => "(function)".into(),
76 Unpacked::Nullish(Nullish::Undefined) => "undefined".into(),
77 _ => text(value),
78 }
79}
80
81fn quote(string: Any<Naive>) -> std::string::String {
83 string
84 .to_json()
85 .unwrap_or_else(|error| format!("(not a string: {error})"))
86}
87
88fn text(value: Any<Naive>) -> std::string::String {
90 match value.to_string() {
91 Ok(string) => string.into(),
92 Err(thrown) => format!("String() threw on it: {}", text(thrown)),
93 }
94}
95
96#[cfg(test)]
100pub(crate) fn assert_call(entry: &Function<Naive>, input: f64, expected: Result<&str, &str>) {
101 let expected = expected.map(Into::into).map_err(Into::into);
102 assert_eq!(call(entry, input), expected, "called with {input}");
103}
104
105fn output(result: Any<Naive>, stdout: Vec<u8>) -> Result<Vec<u8>, Any<Naive>> {
109 let tag = Array::try_from(result.clone())
110 .ok()
111 .and_then(|items| items.into_iter().next());
112 if tag == Some(string_any("ok")) {
113 Ok(stdout)
114 } else {
115 Err(result)
116 }
117}
118
119thread_local! {
120 static ENTRY: RefCell<Option<Function<Naive>>> = const { RefCell::new(None) };
122 static OUTPUT: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
124}
125
126fn export(module: Module<Naive>) -> i32 {
130 let entry = load(module);
131 let status = if entry.is_ok() { 0 } else { -1 };
132 ENTRY.with(|kept| *kept.borrow_mut() = entry.ok());
133 status
134}
135
136#[unsafe(no_mangle)]
139pub extern "C" fn run_sample() -> i32 {
140 let ran = ENTRY.with(|kept| kept.borrow().as_ref().map(perform));
141 match ran {
142 Some(Ok(bytes)) => {
143 let length = bytes.len() as i32;
144 OUTPUT.with(|output| *output.borrow_mut() = bytes);
145 length
146 }
147 _ => -1,
148 }
149}
150
151#[unsafe(no_mangle)]
156pub extern "C" fn call_sample(input: f64) -> i32 {
157 let Some(called) = ENTRY.with(|kept| kept.borrow().as_ref().map(|entry| call(entry, input)))
158 else {
159 return i32::MIN;
160 };
161 let (bytes, threw) = match called {
162 Ok(answer) => (answer.into_bytes(), false),
163 Err(thrown) => (thrown.into_bytes(), true),
164 };
165 let length = bytes.len() as i32;
166 OUTPUT.with(|output| *output.borrow_mut() = bytes);
167 if threw { -1 - length } else { length }
168}
169
170#[unsafe(no_mangle)]
172pub extern "C" fn output_byte(index: u32) -> u32 {
173 OUTPUT.with(|output| output.borrow()[index as usize].into())
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn hello_writes_its_greeting() {
182 assert_eq!(
183 perform(&load(samples::hello::module::<Naive>).unwrap()).unwrap(),
184 b"hello, world\n"
185 );
186 }
187
188 #[test]
191 #[cfg_attr(debug_assertions, ignore = "slow unoptimized; runs under --release")]
192 fn sort_reports_what_it_sorted() {
193 assert_eq!(
194 perform(&load(samples::sort::module::<Naive>).unwrap()).unwrap(),
195 b"sorted 100000 integers: smallest 1, largest 2147477497\n"
196 );
197 }
198
199 #[test]
202 #[cfg_attr(debug_assertions, ignore = "slow unoptimized; runs under --release")]
203 fn collatz_reports_the_longest_chain() {
204 assert_eq!(
205 perform(&load(samples::collatz::module::<Naive>).unwrap()).unwrap(),
206 b"the longest Collatz chain below 30000 starts at 26623 and takes 307 steps\n"
207 );
208 }
209
210 #[test]
211 fn comma_answers_or_fails_its_assertion() {
212 let entry = load(samples::comma::module::<Naive>).unwrap();
213 assert_eq!(call(&entry, 1.0), Ok("3".into()));
214 assert_eq!(call(&entry, -1.0), Err("\"negative\"".into()));
215 }
216
217 #[test]
218 fn an_error_result_is_the_sample_failing() {
219 let ended = |tag| [string_any(tag), string_any("x")].to_array().to_any();
220 assert_eq!(output(ended("ok"), b"hi".to_vec()).unwrap(), b"hi");
221 assert!(output(ended("error"), b"hi".to_vec()).is_err());
222 }
223
224 #[test]
225 fn the_exports_load_run_and_keep_the_output() {
226 assert_eq!(run_sample(), -1);
227 assert_eq!(samples::hello(), 0);
228 assert_eq!(run_sample(), 13);
229 assert_eq!(output_byte(0), u32::from(b'h'));
230 assert_eq!(output_byte(12), u32::from(b'\n'));
231 assert_eq!(samples::comma(), 0);
232 assert_eq!(call_sample(1.0), 1);
233 assert_eq!(output_byte(0), u32::from(b'3'));
234 assert_eq!(call_sample(-1.0), -1 - 10);
235 assert_eq!(output_byte(1), u32::from(b'n'));
236 }
237}