@tishlang/tish-format 1.0.12 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/Cargo.toml +51 -0
  2. package/LICENSE +13 -0
  3. package/bin/tish-format +0 -0
  4. package/crates/js_to_tish/Cargo.toml +11 -0
  5. package/crates/js_to_tish/README.md +18 -0
  6. package/crates/js_to_tish/src/error.rs +55 -0
  7. package/crates/js_to_tish/src/lib.rs +11 -0
  8. package/crates/js_to_tish/src/span_util.rs +35 -0
  9. package/crates/js_to_tish/src/transform/expr.rs +611 -0
  10. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  11. package/crates/js_to_tish/src/transform.rs +60 -0
  12. package/crates/tish/Cargo.toml +62 -0
  13. package/crates/tish/build.rs +21 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +576 -0
  16. package/crates/tish/src/main.rs +853 -0
  17. package/crates/tish/src/repl_completion.rs +199 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/error_source_location.rs +36 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  24. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  25. package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
  26. package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
  27. package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
  28. package/crates/tish/tests/integration_test.rs +1406 -0
  29. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  30. package/crates/tish/tests/shortcircuit.rs +65 -0
  31. package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
  32. package/crates/tish/tests/tty_capability.rs +43 -0
  33. package/crates/tish_ast/Cargo.toml +9 -0
  34. package/crates/tish_ast/src/ast.rs +649 -0
  35. package/crates/tish_ast/src/lib.rs +5 -0
  36. package/crates/tish_build_utils/Cargo.toml +11 -0
  37. package/crates/tish_build_utils/src/lib.rs +577 -0
  38. package/crates/tish_builtins/Cargo.toml +22 -0
  39. package/crates/tish_builtins/src/array.rs +803 -0
  40. package/crates/tish_builtins/src/collections.rs +481 -0
  41. package/crates/tish_builtins/src/construct.rs +199 -0
  42. package/crates/tish_builtins/src/date.rs +538 -0
  43. package/crates/tish_builtins/src/globals.rs +293 -0
  44. package/crates/tish_builtins/src/helpers.rs +35 -0
  45. package/crates/tish_builtins/src/iterator.rs +129 -0
  46. package/crates/tish_builtins/src/lib.rs +21 -0
  47. package/crates/tish_builtins/src/math.rs +89 -0
  48. package/crates/tish_builtins/src/number.rs +96 -0
  49. package/crates/tish_builtins/src/object.rs +36 -0
  50. package/crates/tish_builtins/src/string.rs +646 -0
  51. package/crates/tish_builtins/src/symbol.rs +83 -0
  52. package/crates/tish_builtins/src/typedarrays.rs +298 -0
  53. package/crates/tish_bytecode/Cargo.toml +17 -0
  54. package/crates/tish_bytecode/src/chunk.rs +164 -0
  55. package/crates/tish_bytecode/src/compiler.rs +2604 -0
  56. package/crates/tish_bytecode/src/encoding.rs +102 -0
  57. package/crates/tish_bytecode/src/lib.rs +20 -0
  58. package/crates/tish_bytecode/src/opcode.rs +185 -0
  59. package/crates/tish_bytecode/src/peephole.rs +189 -0
  60. package/crates/tish_bytecode/src/serialize.rs +193 -0
  61. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  62. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  63. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  64. package/crates/tish_compile/Cargo.toml +27 -0
  65. package/crates/tish_compile/src/check.rs +774 -0
  66. package/crates/tish_compile/src/codegen.rs +7317 -0
  67. package/crates/tish_compile/src/infer.rs +1681 -0
  68. package/crates/tish_compile/src/lib.rs +206 -0
  69. package/crates/tish_compile/src/resolve.rs +1951 -0
  70. package/crates/tish_compile/src/types.rs +605 -0
  71. package/crates/tish_compile_js/Cargo.toml +18 -0
  72. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  73. package/crates/tish_compile_js/src/codegen.rs +938 -0
  74. package/crates/tish_compile_js/src/error.rs +20 -0
  75. package/crates/tish_compile_js/src/lib.rs +26 -0
  76. package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
  77. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  78. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  79. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  80. package/crates/tish_core/Cargo.toml +32 -0
  81. package/crates/tish_core/src/console_style.rs +170 -0
  82. package/crates/tish_core/src/json.rs +430 -0
  83. package/crates/tish_core/src/lib.rs +20 -0
  84. package/crates/tish_core/src/macros.rs +36 -0
  85. package/crates/tish_core/src/shape.rs +85 -0
  86. package/crates/tish_core/src/uri.rs +118 -0
  87. package/crates/tish_core/src/value.rs +1350 -0
  88. package/crates/tish_core/src/vmref.rs +183 -0
  89. package/crates/tish_cranelift/Cargo.toml +19 -0
  90. package/crates/tish_cranelift/src/lib.rs +43 -0
  91. package/crates/tish_cranelift/src/link.rs +130 -0
  92. package/crates/tish_cranelift/src/lower.rs +85 -0
  93. package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
  94. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  95. package/crates/tish_eval/Cargo.toml +51 -0
  96. package/crates/tish_eval/src/eval.rs +4265 -0
  97. package/crates/tish_eval/src/http.rs +191 -0
  98. package/crates/tish_eval/src/lib.rs +99 -0
  99. package/crates/tish_eval/src/natives.rs +551 -0
  100. package/crates/tish_eval/src/promise.rs +179 -0
  101. package/crates/tish_eval/src/regex.rs +299 -0
  102. package/crates/tish_eval/src/timers.rs +120 -0
  103. package/crates/tish_eval/src/value.rs +336 -0
  104. package/crates/tish_eval/src/value_convert.rs +117 -0
  105. package/crates/tish_ffi/Cargo.toml +26 -0
  106. package/crates/tish_ffi/src/lib.rs +518 -0
  107. package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
  108. package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
  109. package/crates/tish_ffi/tests/loader.rs +65 -0
  110. package/crates/tish_fmt/Cargo.toml +16 -0
  111. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  112. package/crates/tish_fmt/src/lib.rs +2157 -0
  113. package/crates/tish_jsx_web/Cargo.toml +9 -0
  114. package/crates/tish_jsx_web/README.md +5 -0
  115. package/crates/tish_jsx_web/src/lib.rs +2 -0
  116. package/crates/tish_lexer/Cargo.toml +9 -0
  117. package/crates/tish_lexer/src/lib.rs +1104 -0
  118. package/crates/tish_lexer/src/token.rs +170 -0
  119. package/crates/tish_lint/Cargo.toml +18 -0
  120. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  121. package/crates/tish_lint/src/lib.rs +281 -0
  122. package/crates/tish_llvm/Cargo.toml +13 -0
  123. package/crates/tish_llvm/src/lib.rs +115 -0
  124. package/crates/tish_lsp/Cargo.toml +25 -0
  125. package/crates/tish_lsp/README.md +26 -0
  126. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  127. package/crates/tish_lsp/src/import_goto.rs +564 -0
  128. package/crates/tish_lsp/src/main.rs +1459 -0
  129. package/crates/tish_native/Cargo.toml +16 -0
  130. package/crates/tish_native/src/build.rs +481 -0
  131. package/crates/tish_native/src/config.rs +48 -0
  132. package/crates/tish_native/src/lib.rs +416 -0
  133. package/crates/tish_opt/Cargo.toml +13 -0
  134. package/crates/tish_opt/src/lib.rs +1046 -0
  135. package/crates/tish_parser/Cargo.toml +11 -0
  136. package/crates/tish_parser/src/lib.rs +386 -0
  137. package/crates/tish_parser/src/parser.rs +2726 -0
  138. package/crates/tish_pg/Cargo.toml +34 -0
  139. package/crates/tish_pg/README.md +38 -0
  140. package/crates/tish_pg/src/error.rs +52 -0
  141. package/crates/tish_pg/src/lib.rs +955 -0
  142. package/crates/tish_resolve/Cargo.toml +13 -0
  143. package/crates/tish_resolve/src/lib.rs +3601 -0
  144. package/crates/tish_resolve/src/pos.rs +141 -0
  145. package/crates/tish_runtime/Cargo.toml +100 -0
  146. package/crates/tish_runtime/src/http.rs +1347 -0
  147. package/crates/tish_runtime/src/http_fetch.rs +492 -0
  148. package/crates/tish_runtime/src/http_hyper.rs +441 -0
  149. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  150. package/crates/tish_runtime/src/lib.rs +1447 -0
  151. package/crates/tish_runtime/src/native_promise.rs +15 -0
  152. package/crates/tish_runtime/src/promise.rs +558 -0
  153. package/crates/tish_runtime/src/promise_io.rs +38 -0
  154. package/crates/tish_runtime/src/timers.rs +172 -0
  155. package/crates/tish_runtime/src/tty.rs +226 -0
  156. package/crates/tish_runtime/src/ws.rs +778 -0
  157. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  158. package/crates/tish_ui/Cargo.toml +17 -0
  159. package/crates/tish_ui/src/jsx.rs +692 -0
  160. package/crates/tish_ui/src/lib.rs +20 -0
  161. package/crates/tish_ui/src/runtime/hooks.rs +573 -0
  162. package/crates/tish_ui/src/runtime/mod.rs +183 -0
  163. package/crates/tish_vm/Cargo.toml +60 -0
  164. package/crates/tish_vm/src/jit.rs +1050 -0
  165. package/crates/tish_vm/src/lib.rs +41 -0
  166. package/crates/tish_vm/src/vm.rs +3536 -0
  167. package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
  168. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  169. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  170. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  171. package/crates/tish_wasm/Cargo.toml +15 -0
  172. package/crates/tish_wasm/src/lib.rs +428 -0
  173. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  174. package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
  175. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  176. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  177. package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
  178. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  179. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  180. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  181. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  182. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  183. package/justfile +276 -0
  184. package/package.json +2 -2
  185. package/platform/darwin-arm64/tish-fmt +0 -0
  186. package/platform/darwin-x64/tish-fmt +0 -0
  187. package/platform/linux-arm64/tish-fmt +0 -0
  188. package/platform/linux-x64/tish-fmt +0 -0
  189. package/platform/win32-x64/tish-fmt.exe +0 -0
@@ -0,0 +1,4265 @@
1
+ //! Tree-walk evaluator for Tish.
2
+
3
+ #![allow(clippy::type_complexity, clippy::cloned_ref_to_slice_refs)]
4
+
5
+ use std::cell::RefCell;
6
+ use std::collections::HashMap;
7
+ use std::path::{Path, PathBuf};
8
+ use std::rc::Rc;
9
+ use std::sync::Arc;
10
+
11
+ use tishlang_ast::{
12
+ BinOp, CompoundOp, ExportDeclaration, Expr, FunParam, ImportSpecifier, Literal,
13
+ LogicalAssignOp, MemberProp, Span, Statement, UnaryOp,
14
+ };
15
+
16
+ #[cfg(any(feature = "fs", feature = "process"))]
17
+ use crate::natives;
18
+ use ahash::AHashMap;
19
+
20
+ use crate::value::{
21
+ eval_object_get, eval_object_has, eval_object_set, EvalObjectData, PropMap, Value,
22
+ };
23
+
24
+ pub struct Scope {
25
+ // Scope vars: order is never observed (no Object.keys over a scope), so use a fast
26
+ // unordered aHash map — NOT the object-strings PropMap (an insertion-ordered IndexMap),
27
+ // which would pay SipHash + ordered-bucket overhead on every variable lookup.
28
+ vars: AHashMap<Arc<str>, Value>,
29
+ consts: ahash::AHashSet<Arc<str>>,
30
+ parent: Option<Rc<std::cell::RefCell<Scope>>>,
31
+ }
32
+
33
+ /// A reference-counted lexical scope. A `Value::Function` captures one of these at creation
34
+ /// (the *defining* scope) so calls resolve free variables lexically — real closures.
35
+ pub type ScopeRef = Rc<std::cell::RefCell<Scope>>;
36
+
37
+ impl Scope {
38
+ fn new() -> Rc<std::cell::RefCell<Self>> {
39
+ Rc::new(std::cell::RefCell::new(Self {
40
+ vars: AHashMap::default(),
41
+ consts: ahash::AHashSet::default(),
42
+ parent: None,
43
+ }))
44
+ }
45
+
46
+ fn child(parent: Rc<std::cell::RefCell<Scope>>) -> Rc<std::cell::RefCell<Self>> {
47
+ Rc::new(std::cell::RefCell::new(Self {
48
+ vars: AHashMap::default(),
49
+ consts: ahash::AHashSet::default(),
50
+ parent: Some(parent),
51
+ }))
52
+ }
53
+
54
+ fn get(&self, name: &str) -> Option<Value> {
55
+ if let Some(v) = self.vars.get(name) {
56
+ return Some(v.clone());
57
+ }
58
+ if let Some(ref parent) = self.parent {
59
+ return parent.borrow().get(name);
60
+ }
61
+ None
62
+ }
63
+
64
+ fn set(&mut self, name: Arc<str>, value: Value, mutable: bool) {
65
+ if !mutable {
66
+ self.consts.insert(Arc::clone(&name));
67
+ }
68
+ self.vars.insert(name, value);
69
+ }
70
+
71
+ fn assign(&mut self, name: &str, value: Value) -> Result<bool, String> {
72
+ if let Some(existing) = self.vars.get_mut(name) {
73
+ if self.consts.contains(name) {
74
+ return Err(format!("Cannot assign to const variable: {}", name));
75
+ }
76
+ *existing = value;
77
+ return Ok(true);
78
+ }
79
+ if let Some(ref parent) = self.parent {
80
+ return parent.borrow_mut().assign(name, value);
81
+ }
82
+ Ok(false)
83
+ }
84
+ }
85
+
86
+ pub struct Evaluator {
87
+ scope: Rc<std::cell::RefCell<Scope>>,
88
+ /// Cache of evaluated modules: canonical path -> exports object
89
+ module_cache: Rc<RefCell<HashMap<PathBuf, Value>>>,
90
+ /// Directory of the file currently being evaluated (for resolving relative imports)
91
+ current_dir: RefCell<Option<PathBuf>>,
92
+ /// Extra `tish:*` builtins from `TishNativeModule::virtual_builtin_modules` (shared across nested evaluators).
93
+ virtual_builtins: Rc<RefCell<HashMap<Arc<str>, Value>>>,
94
+ }
95
+
96
+ impl Evaluator {
97
+ #[allow(clippy::new_without_default)]
98
+ pub fn new() -> Self {
99
+ use crate::natives;
100
+
101
+ let scope = Scope::new();
102
+ {
103
+ let mut s = scope.borrow_mut();
104
+ let mut console = PropMap::with_capacity(5);
105
+ console.insert("debug".into(), Value::Native(natives::console_debug));
106
+ console.insert("info".into(), Value::Native(natives::console_info));
107
+ console.insert("log".into(), Value::Native(natives::console_log));
108
+ console.insert("warn".into(), Value::Native(natives::console_warn));
109
+ console.insert("error".into(), Value::Native(natives::console_error));
110
+ s.set(
111
+ "console".into(),
112
+ Value::object(console),
113
+ true,
114
+ );
115
+ s.set("parseInt".into(), Value::Native(natives::parse_int), true);
116
+ s.set(
117
+ "parseFloat".into(),
118
+ Value::Native(natives::parse_float),
119
+ true,
120
+ );
121
+ s.set("decodeURI".into(), Value::Native(natives::decode_uri), true);
122
+ s.set("encodeURI".into(), Value::Native(natives::encode_uri), true);
123
+ s.set(
124
+ "htmlEscape".into(),
125
+ Value::Native(natives::html_escape),
126
+ true,
127
+ );
128
+ s.set(
129
+ "Boolean".into(),
130
+ Value::Native(natives::boolean_native),
131
+ true,
132
+ );
133
+ s.set("isFinite".into(), Value::Native(natives::is_finite), true);
134
+ s.set("isNaN".into(), Value::Native(natives::is_nan), true);
135
+ s.set("Infinity".into(), Value::Number(f64::INFINITY), true);
136
+ s.set("NaN".into(), Value::Number(f64::NAN), true);
137
+ let mut math = PropMap::with_capacity(18);
138
+ math.insert("abs".into(), Value::Native(natives::math_abs));
139
+ math.insert("sqrt".into(), Value::Native(natives::math_sqrt));
140
+ math.insert("min".into(), Value::Native(natives::math_min));
141
+ math.insert("max".into(), Value::Native(natives::math_max));
142
+ math.insert("floor".into(), Value::Native(natives::math_floor));
143
+ math.insert("ceil".into(), Value::Native(natives::math_ceil));
144
+ math.insert("round".into(), Value::Native(natives::math_round));
145
+ math.insert("random".into(), Value::Native(natives::math_random));
146
+ math.insert("pow".into(), Value::Native(natives::math_pow));
147
+ math.insert("sin".into(), Value::Native(natives::math_sin));
148
+ math.insert("cos".into(), Value::Native(natives::math_cos));
149
+ math.insert("tan".into(), Value::Native(natives::math_tan));
150
+ math.insert("log".into(), Value::Native(natives::math_log));
151
+ math.insert("exp".into(), Value::Native(natives::math_exp));
152
+ math.insert("sign".into(), Value::Native(natives::math_sign));
153
+ math.insert("trunc".into(), Value::Native(natives::math_trunc));
154
+ math.insert("sinh".into(), Value::Native(natives::math_sinh));
155
+ math.insert("cosh".into(), Value::Native(natives::math_cosh));
156
+ math.insert("tanh".into(), Value::Native(natives::math_tanh));
157
+ math.insert("asinh".into(), Value::Native(natives::math_asinh));
158
+ math.insert("acosh".into(), Value::Native(natives::math_acosh));
159
+ math.insert("atanh".into(), Value::Native(natives::math_atanh));
160
+ math.insert("cbrt".into(), Value::Native(natives::math_cbrt));
161
+ math.insert("log2".into(), Value::Native(natives::math_log2));
162
+ math.insert("log10".into(), Value::Native(natives::math_log10));
163
+ math.insert("PI".into(), Value::Number(std::f64::consts::PI));
164
+ math.insert("E".into(), Value::Number(std::f64::consts::E));
165
+ s.set(
166
+ "Math".into(),
167
+ Value::object(math),
168
+ true,
169
+ );
170
+
171
+ let mut json = PropMap::with_capacity(2);
172
+ json.insert("parse".into(), Value::Native(Self::json_parse_native));
173
+ json.insert(
174
+ "stringify".into(),
175
+ Value::Native(Self::json_stringify_native),
176
+ );
177
+ s.set(
178
+ "JSON".into(),
179
+ Value::object(json),
180
+ true,
181
+ );
182
+
183
+ let mut object = PropMap::with_capacity(5);
184
+ object.insert("keys".into(), Value::Native(Self::object_keys));
185
+ object.insert("values".into(), Value::Native(Self::object_values));
186
+ object.insert("entries".into(), Value::Native(Self::object_entries));
187
+ object.insert("assign".into(), Value::Native(Self::object_assign));
188
+ object.insert(
189
+ "fromEntries".into(),
190
+ Value::Native(Self::object_from_entries),
191
+ );
192
+ s.set(
193
+ "Object".into(),
194
+ Value::object(object),
195
+ true,
196
+ );
197
+
198
+ let mut array_obj = PropMap::with_capacity(3);
199
+ array_obj.insert("isArray".into(), Value::Native(natives::array_is_array));
200
+ // `Array(n)` and `new Array(n)` constructor (issue #72).
201
+ array_obj.insert("__call".into(), Value::Native(natives::array_construct));
202
+ array_obj.insert("__construct".into(), Value::Native(natives::array_construct));
203
+ s.set(
204
+ "Array".into(),
205
+ Value::object(array_obj),
206
+ true,
207
+ );
208
+
209
+ // Error constructors (issue #60): callable + constructable via __call/__construct.
210
+ for (name, ctor) in [
211
+ ("Error", natives::error_construct as fn(&[Value]) -> Result<Value, String>),
212
+ ("TypeError", natives::type_error_construct),
213
+ ("RangeError", natives::range_error_construct),
214
+ ("SyntaxError", natives::syntax_error_construct),
215
+ ] {
216
+ let mut err_obj = PropMap::with_capacity(2);
217
+ err_obj.insert("__call".into(), Value::Native(ctor));
218
+ err_obj.insert("__construct".into(), Value::Native(ctor));
219
+ s.set(name.into(), Value::object(err_obj), true);
220
+ }
221
+
222
+ let mut string_obj = PropMap::with_capacity(2);
223
+ string_obj.insert(
224
+ "fromCharCode".into(),
225
+ Value::Native(natives::string_from_char_code),
226
+ );
227
+ // `String(value)` callable: dispatched via `__call` in `call_func`, like `Symbol`.
228
+ string_obj.insert("__call".into(), Value::Native(natives::string_convert));
229
+ s.set(
230
+ "String".into(),
231
+ Value::object(string_obj),
232
+ true,
233
+ );
234
+
235
+ // `Number(value)` coercion as a callable global (issue #36).
236
+ let mut number_obj = PropMap::with_capacity(1);
237
+ number_obj.insert("__call".into(), Value::Native(natives::number_convert));
238
+ s.set("Number".into(), Value::object(number_obj), true);
239
+
240
+ s.set(
241
+ "Date".into(),
242
+ crate::value_convert::core_to_eval(
243
+ tishlang_builtins::date::date_constructor_value(),
244
+ ),
245
+ true,
246
+ );
247
+ s.set(
248
+ "Set".into(),
249
+ crate::value_convert::core_to_eval(
250
+ tishlang_builtins::collections::set_constructor_value(),
251
+ ),
252
+ true,
253
+ );
254
+ s.set(
255
+ "Map".into(),
256
+ crate::value_convert::core_to_eval(
257
+ tishlang_builtins::collections::map_constructor_value(),
258
+ ),
259
+ true,
260
+ );
261
+
262
+ s.set(
263
+ "Symbol".into(),
264
+ crate::value_convert::core_to_eval(tishlang_builtins::symbol::symbol_object()),
265
+ true,
266
+ );
267
+ for (name, ctor) in [
268
+ (
269
+ "Float64Array",
270
+ tishlang_builtins::typedarrays::float64_array_constructor_value
271
+ as fn() -> tishlang_core::Value,
272
+ ),
273
+ ("Float32Array", tishlang_builtins::typedarrays::float32_array_constructor_value),
274
+ ("Int8Array", tishlang_builtins::typedarrays::int8_array_constructor_value),
275
+ ("Uint8Array", tishlang_builtins::typedarrays::uint8_array_constructor_value),
276
+ (
277
+ "Uint8ClampedArray",
278
+ tishlang_builtins::typedarrays::uint8_clamped_array_constructor_value,
279
+ ),
280
+ ("Int16Array", tishlang_builtins::typedarrays::int16_array_constructor_value),
281
+ ("Uint16Array", tishlang_builtins::typedarrays::uint16_array_constructor_value),
282
+ ("Int32Array", tishlang_builtins::typedarrays::int32_array_constructor_value),
283
+ ("Uint32Array", tishlang_builtins::typedarrays::uint32_array_constructor_value),
284
+ ] {
285
+ s.set(name.into(), crate::value_convert::core_to_eval(ctor()), true);
286
+ }
287
+ s.set(
288
+ "AudioContext".into(),
289
+ crate::value_convert::core_to_eval(
290
+ tishlang_builtins::construct::audio_context_constructor_value(),
291
+ ),
292
+ true,
293
+ );
294
+
295
+ #[cfg(feature = "regex")]
296
+ {
297
+ s.set(
298
+ "RegExp".into(),
299
+ Value::Native(Self::regexp_constructor_native),
300
+ true,
301
+ );
302
+ }
303
+
304
+ // fs, process: prefer `import { x } from 'tish:fs'` etc.
305
+ #[cfg(feature = "timers")]
306
+ {
307
+ s.set(
308
+ "setTimeout".into(),
309
+ Value::TimerBuiltin(Arc::from("setTimeout")),
310
+ true,
311
+ );
312
+ s.set(
313
+ "setInterval".into(),
314
+ Value::TimerBuiltin(Arc::from("setInterval")),
315
+ true,
316
+ );
317
+ s.set(
318
+ "clearTimeout".into(),
319
+ Value::Native(Self::clear_timeout_native),
320
+ true,
321
+ );
322
+ s.set(
323
+ "clearInterval".into(),
324
+ Value::Native(Self::clear_interval_native),
325
+ true,
326
+ );
327
+ }
328
+ #[cfg(feature = "http")]
329
+ {
330
+ s.set("fetch".into(), Value::Native(Self::fetch_native), true);
331
+ s.set(
332
+ "fetchAll".into(),
333
+ Value::Native(Self::fetch_all_native),
334
+ true,
335
+ );
336
+ s.set("Promise".into(), Value::PromiseConstructor, true);
337
+ s.set("serve".into(), Value::Serve, true);
338
+ }
339
+ }
340
+ Self {
341
+ scope,
342
+ module_cache: Rc::new(RefCell::new(HashMap::new())),
343
+ current_dir: RefCell::new(None),
344
+ virtual_builtins: Rc::new(RefCell::new(HashMap::new())),
345
+ }
346
+ }
347
+
348
+ /// Create an evaluator with extra native modules (e.g. Polars) registered.
349
+ pub fn with_modules(modules: &[&dyn crate::TishNativeModule]) -> Self {
350
+ let eval = Self::new();
351
+ {
352
+ let mut s = eval.scope.borrow_mut();
353
+ for module in modules {
354
+ for (name, value) in module.register() {
355
+ s.set(name, value, true);
356
+ }
357
+ }
358
+ }
359
+ {
360
+ let mut vb = eval.virtual_builtins.borrow_mut();
361
+ for module in modules {
362
+ for (spec, value) in module.virtual_builtin_modules() {
363
+ vb.insert(Arc::from(spec), value);
364
+ }
365
+ }
366
+ }
367
+ eval
368
+ }
369
+
370
+ pub fn set_current_dir(&self, dir: Option<&Path>) {
371
+ *self.current_dir.borrow_mut() = dir.map(PathBuf::from);
372
+ }
373
+
374
+ pub fn eval_program(&mut self, program: &tishlang_ast::Program) -> Result<Value, String> {
375
+ let mut last = Value::Null;
376
+ for stmt in &program.statements {
377
+ last = self.eval_statement(stmt).map_err(|e| e.to_string())?;
378
+ }
379
+ Ok(last)
380
+ }
381
+
382
+ fn eval_statement(&mut self, stmt: &Statement) -> Result<Value, EvalError> {
383
+ match stmt {
384
+ Statement::Block { statements, .. } => {
385
+ let scope = Scope::child(Rc::clone(&self.scope));
386
+ let prev = std::mem::replace(&mut self.scope, scope);
387
+ let mut last = Value::Null;
388
+ for s in statements {
389
+ last = self.eval_statement(s)?;
390
+ }
391
+ self.scope = prev;
392
+ Ok(last)
393
+ }
394
+ // Comma-declarators: a transparent group — evaluate each declarator in
395
+ // the *current* scope (no child scope).
396
+ Statement::Multi { statements, .. } => {
397
+ let mut last = Value::Null;
398
+ for s in statements {
399
+ last = self.eval_statement(s)?;
400
+ }
401
+ Ok(last)
402
+ }
403
+ Statement::VarDecl {
404
+ name,
405
+ mutable,
406
+ init,
407
+ ..
408
+ } => {
409
+ let value = init
410
+ .as_ref()
411
+ .map(|e| self.eval_expr(e))
412
+ .transpose()?
413
+ .unwrap_or(Value::Null);
414
+ self.scope
415
+ .borrow_mut()
416
+ .set(Arc::clone(name), value, *mutable);
417
+ Ok(Value::Null)
418
+ }
419
+ Statement::VarDeclDestructure {
420
+ pattern,
421
+ mutable,
422
+ init,
423
+ ..
424
+ } => {
425
+ let value = self.eval_expr(init)?;
426
+ self.bind_destruct_pattern(pattern, &value, *mutable)?;
427
+ Ok(Value::Null)
428
+ }
429
+ Statement::ExprStmt { expr, .. } => self.eval_expr(expr),
430
+ Statement::If {
431
+ cond,
432
+ then_branch,
433
+ else_branch,
434
+ ..
435
+ } => {
436
+ let c = self.eval_expr(cond)?;
437
+ if c.is_truthy() {
438
+ self.eval_statement(then_branch)
439
+ } else if let Some(eb) = else_branch {
440
+ self.eval_statement(eb)
441
+ } else {
442
+ Ok(Value::Null)
443
+ }
444
+ }
445
+ Statement::While { cond, body, .. } => {
446
+ loop {
447
+ if !self.eval_expr(cond)?.is_truthy() {
448
+ break;
449
+ }
450
+ match self.eval_statement(body) {
451
+ Ok(_) => {}
452
+ Err(EvalError::Break) => break,
453
+ Err(EvalError::Continue) => continue,
454
+ Err(e) => return Err(e),
455
+ }
456
+ }
457
+ Ok(Value::Null)
458
+ }
459
+ Statement::ForOf {
460
+ name,
461
+ iterable,
462
+ body,
463
+ ..
464
+ } => {
465
+ let iter_val = self.eval_expr(iterable)?;
466
+ let elements = match &iter_val {
467
+ crate::value::Value::Array(arr) => {
468
+ arr.borrow().iter().cloned().collect::<Vec<_>>()
469
+ }
470
+ crate::value::Value::String(s) => s
471
+ .chars()
472
+ .map(|c| crate::value::Value::String(Arc::from(c.to_string())))
473
+ .collect::<Vec<_>>(),
474
+ // Iterator protocol: an object with a callable `next()` returning
475
+ // `{ value, done }` — e.g. a Map/Set iterator from `.values()` /
476
+ // `.keys()` / `.entries()`. Drain it ONCE (draining advances the
477
+ // iterator's shared position, so it must not be re-run).
478
+ _ => match self.drain_eval_iterator(&iter_val) {
479
+ Some(elems) => elems,
480
+ None => {
481
+ return Err(EvalError::Error(format!(
482
+ "for-of requires iterable (array, string, or iterator), got {}",
483
+ iter_val
484
+ )));
485
+ }
486
+ },
487
+ };
488
+ // Each element gets a FRESH per-iteration binding (ES6 `for (let v of …)`), so a
489
+ // closure created in the body captures that element, not the last one.
490
+ let outer = Rc::clone(&self.scope);
491
+ let mut ret = Ok(Value::Null);
492
+ for elem in elements {
493
+ let iter_env = Scope::child(Rc::clone(&outer));
494
+ iter_env.borrow_mut().set(Arc::clone(name), elem, true);
495
+ self.scope = Rc::clone(&iter_env);
496
+ match self.eval_statement(body) {
497
+ Ok(_) => {}
498
+ Err(EvalError::Break) => break,
499
+ Err(EvalError::Continue) => continue,
500
+ Err(e) => {
501
+ ret = Err(e);
502
+ break;
503
+ }
504
+ }
505
+ }
506
+ self.scope = outer;
507
+ ret
508
+ }
509
+ Statement::For {
510
+ init,
511
+ cond,
512
+ update,
513
+ body,
514
+ ..
515
+ } => {
516
+ // `let`/`const` declared in `init` get a FRESH per-iteration binding (ES6), so a
517
+ // closure created in the body captures THAT iteration's value, not the final one.
518
+ // The canonical values live in `loop_env`; each iteration's body runs in a fresh
519
+ // `iter_env` copy, and mutations are copied back for the next test/update.
520
+ let outer = Rc::clone(&self.scope);
521
+ let loop_env = Scope::child(Rc::clone(&outer));
522
+ self.scope = Rc::clone(&loop_env);
523
+ if let Some(i) = init {
524
+ if let Err(e) = self.eval_statement(i) {
525
+ self.scope = outer;
526
+ return Err(e);
527
+ }
528
+ }
529
+ let per_iter: Vec<Arc<str>> = loop_env.borrow().vars.keys().cloned().collect();
530
+ let copy_vars = |from: &ScopeRef, to: &ScopeRef, names: &[Arc<str>]| {
531
+ let src = from.borrow();
532
+ let mut dst = to.borrow_mut();
533
+ for n in names {
534
+ if let Some(v) = src.vars.get(n.as_ref()) {
535
+ dst.set(Arc::clone(n), v.clone(), true);
536
+ }
537
+ }
538
+ };
539
+ let mut ret = Ok(Value::Null);
540
+ loop {
541
+ self.scope = Rc::clone(&loop_env);
542
+ let cond_ok = match cond.as_ref() {
543
+ Some(c) => match self.eval_expr(c) {
544
+ Ok(v) => v.is_truthy(),
545
+ Err(e) => {
546
+ ret = Err(e);
547
+ break;
548
+ }
549
+ },
550
+ None => true,
551
+ };
552
+ if !cond_ok {
553
+ break;
554
+ }
555
+ let iter_env = if per_iter.is_empty() {
556
+ Rc::clone(&loop_env)
557
+ } else {
558
+ let e = Scope::child(Rc::clone(&outer));
559
+ copy_vars(&loop_env, &e, &per_iter);
560
+ e
561
+ };
562
+ self.scope = Rc::clone(&iter_env);
563
+ let flow = self.eval_statement(body);
564
+ if !per_iter.is_empty() {
565
+ copy_vars(&iter_env, &loop_env, &per_iter);
566
+ }
567
+ match flow {
568
+ Ok(_) => {}
569
+ Err(EvalError::Break) => break,
570
+ Err(EvalError::Continue) => {}
571
+ Err(e) => {
572
+ ret = Err(e);
573
+ break;
574
+ }
575
+ }
576
+ self.scope = Rc::clone(&loop_env);
577
+ if let Some(u) = update {
578
+ if let Err(e) = self.eval_expr(u) {
579
+ ret = Err(e);
580
+ break;
581
+ }
582
+ }
583
+ }
584
+ self.scope = outer;
585
+ ret
586
+ }
587
+ Statement::Return { value, .. } => {
588
+ let v = value
589
+ .as_ref()
590
+ .map(|e| self.eval_expr(e))
591
+ .transpose()?
592
+ .unwrap_or(Value::Null);
593
+ Err(EvalError::Return(v))
594
+ }
595
+ Statement::Break { .. } => Err(EvalError::Break),
596
+ Statement::Continue { .. } => Err(EvalError::Continue),
597
+ Statement::FunDecl {
598
+ name,
599
+ params,
600
+ rest_param,
601
+ body,
602
+ ..
603
+ } => {
604
+ let formals: Arc<[FunParam]> = Arc::from(params.clone());
605
+ let rest_param_name = rest_param.as_ref().map(|p| Arc::clone(&p.name));
606
+ let body = Arc::new(body.as_ref().clone());
607
+ let func = Value::Function {
608
+ formals,
609
+ rest_param: rest_param_name,
610
+ body,
611
+ // Capture the defining scope. It's the SAME Rc we insert into below, so the
612
+ // function sees itself → recursion works.
613
+ env: Rc::clone(&self.scope),
614
+ };
615
+ self.scope.borrow_mut().set(Arc::clone(name), func, true);
616
+ Ok(Value::Null)
617
+ }
618
+ Statement::Switch {
619
+ expr,
620
+ cases,
621
+ default_body,
622
+ ..
623
+ } => {
624
+ let v = self.eval_expr(expr)?;
625
+ let mut matched = false;
626
+ for (case_expr, body) in cases {
627
+ if let Some(ce) = case_expr {
628
+ let cv = self.eval_expr(ce)?;
629
+ if v.strict_eq(&cv) {
630
+ matched = true;
631
+ let scope = Scope::child(Rc::clone(&self.scope));
632
+ let prev = std::mem::replace(&mut self.scope, scope);
633
+ for s in body {
634
+ match self.eval_statement(s) {
635
+ Ok(_) => {}
636
+ Err(EvalError::Break) => {
637
+ self.scope = prev;
638
+ return Ok(Value::Null);
639
+ }
640
+ Err(e) => {
641
+ self.scope = prev;
642
+ return Err(e);
643
+ }
644
+ }
645
+ }
646
+ self.scope = prev;
647
+ break;
648
+ }
649
+ }
650
+ }
651
+ if !matched {
652
+ if let Some(body) = default_body {
653
+ let scope = Scope::child(Rc::clone(&self.scope));
654
+ let prev = std::mem::replace(&mut self.scope, scope);
655
+ for s in body {
656
+ match self.eval_statement(s) {
657
+ Ok(_) => {}
658
+ Err(EvalError::Break) => break,
659
+ Err(e) => {
660
+ self.scope = prev;
661
+ return Err(e);
662
+ }
663
+ }
664
+ }
665
+ self.scope = prev;
666
+ }
667
+ }
668
+ Ok(Value::Null)
669
+ }
670
+ Statement::DoWhile { body, cond, .. } => {
671
+ loop {
672
+ match self.eval_statement(body) {
673
+ Ok(_) => {}
674
+ Err(EvalError::Break) => break,
675
+ Err(EvalError::Continue) => {
676
+ if !self.eval_expr(cond)?.is_truthy() {
677
+ break;
678
+ }
679
+ continue;
680
+ }
681
+ Err(e) => return Err(e),
682
+ }
683
+ if !self.eval_expr(cond)?.is_truthy() {
684
+ break;
685
+ }
686
+ }
687
+ Ok(Value::Null)
688
+ }
689
+ Statement::Throw { value, .. } => {
690
+ let v = self.eval_expr(value)?;
691
+ Err(EvalError::Throw(v))
692
+ }
693
+ Statement::Try {
694
+ body,
695
+ catch_param,
696
+ catch_body,
697
+ finally_body,
698
+ ..
699
+ } => {
700
+ let try_result = self.eval_statement(body);
701
+
702
+ // Both a user `throw` and a runtime error (`null.foo()`, "not a function", …)
703
+ // are catchable (issue #60); a runtime error is boxed as a `{ name, message }`
704
+ // object so `catch (e) { e.message }` works. Break/Continue/Return propagate.
705
+ let caught: Option<Value> = match &try_result {
706
+ Err(EvalError::Throw(v)) => Some(v.clone()),
707
+ Err(EvalError::Error(msg)) => {
708
+ let mut m = crate::value::PropMap::with_capacity(2);
709
+ m.insert("name".into(), Value::String("TypeError".into()));
710
+ m.insert("message".into(), Value::String(msg.as_str().into()));
711
+ Some(Value::object(m))
712
+ }
713
+ _ => None,
714
+ };
715
+
716
+ let result = match caught {
717
+ Some(thrown) => {
718
+ if let Some(catch_stmt) = catch_body {
719
+ if let Some(param) = catch_param {
720
+ let scope = Scope::child(Rc::clone(&self.scope));
721
+ let prev = std::mem::replace(&mut self.scope, Rc::clone(&scope));
722
+ scope.borrow_mut().set(Arc::clone(param), thrown, true);
723
+ let res = self.eval_statement(catch_stmt);
724
+ self.scope = prev;
725
+ res
726
+ } else {
727
+ self.eval_statement(catch_stmt)
728
+ }
729
+ } else {
730
+ // No catch clause — re-raise the original error after `finally`.
731
+ try_result
732
+ }
733
+ }
734
+ None => try_result,
735
+ };
736
+
737
+ if let Some(finally_stmt) = finally_body {
738
+ // KNOWN BUG (shared with the VM/compiled backends): a throw/return/
739
+ // break/continue inside `finally` should supersede the try/catch
740
+ // outcome (JS completion semantics) but is swallowed here. Fixing it
741
+ // in the interp alone (`?`) breaks interp==vm parity because the VM has
742
+ // the same bug, and the VM fix is a bytecode-compiler-level
743
+ // finally-completion change. Deferred as a coordinated cross-backend fix.
744
+ let _ = self.eval_statement(finally_stmt);
745
+ }
746
+
747
+ result
748
+ }
749
+ Statement::Import {
750
+ specifiers, from, ..
751
+ } => {
752
+ let exports_val = self.load_module(from)?;
753
+ let exports = match &exports_val {
754
+ Value::Object(m) => m.borrow().clone(),
755
+ _ => {
756
+ return Err(EvalError::Error(
757
+ "Module exports must be object".to_string(),
758
+ ))
759
+ }
760
+ };
761
+ let mut scope = self.scope.borrow_mut();
762
+ for spec in specifiers {
763
+ match spec {
764
+ ImportSpecifier::Named { name, alias, .. } => {
765
+ let v = exports.strings.get(name.as_ref()).ok_or_else(|| {
766
+ EvalError::Error(format!("Module does not export '{}'", name))
767
+ })?;
768
+ let bind = alias.as_deref().unwrap_or(name.as_ref());
769
+ scope.set(Arc::from(bind), v.clone(), false);
770
+ }
771
+ ImportSpecifier::Namespace { name, .. } => {
772
+ scope.set(Arc::clone(name), exports_val.clone(), false);
773
+ }
774
+ ImportSpecifier::Default { name, .. } => {
775
+ let v = exports.strings.get("default").ok_or_else(|| {
776
+ EvalError::Error("Module does not have default export".to_string())
777
+ })?;
778
+ scope.set(Arc::clone(name), v.clone(), false);
779
+ }
780
+ }
781
+ }
782
+ Ok(Value::Null)
783
+ }
784
+ Statement::Export { declaration, .. } => {
785
+ match declaration.as_ref() {
786
+ ExportDeclaration::Named(s) => {
787
+ let _ = self.eval_statement(s);
788
+ }
789
+ ExportDeclaration::Default(e) => {
790
+ let v = self.eval_expr(e)?;
791
+ self.scope.borrow_mut().set(Arc::from("default"), v, false);
792
+ }
793
+ }
794
+ Ok(Value::Null)
795
+ }
796
+ Statement::TypeAlias { .. }
797
+ | Statement::DeclareVar { .. }
798
+ | Statement::DeclareFun { .. } => Ok(Value::Null),
799
+ }
800
+ }
801
+
802
+ /// Load and evaluate a module, returning its exports object. Uses cache.
803
+ fn load_module(&mut self, from: &str) -> Result<Value, EvalError> {
804
+ if from.starts_with("cargo:") {
805
+ return Err(EvalError::Error(
806
+ "cargo:… imports are only supported by `tish build` with the Rust native backend."
807
+ .into(),
808
+ ));
809
+ }
810
+ if from.starts_with("tish:") {
811
+ return self.load_builtin_module(from);
812
+ }
813
+ // Scoped native modules (e.g. `@tishlang/waterui`) registered via `TishNativeModule::virtual_builtin_modules`.
814
+ if self.virtual_builtins.borrow().get(from).is_some() {
815
+ return self.load_builtin_module(from);
816
+ }
817
+ let dir = self.current_dir.borrow().clone().ok_or_else(|| {
818
+ EvalError::Error(
819
+ "Cannot resolve imports: no current file directory (use run_file)".to_string(),
820
+ )
821
+ })?;
822
+ let path = Self::resolve_import_path(from, &dir)?;
823
+ let path = path
824
+ .canonicalize()
825
+ .map_err(|e| EvalError::Error(format!("Cannot resolve import '{}': {}", from, e)))?;
826
+ {
827
+ let cache = self.module_cache.borrow();
828
+ if let Some(m) = cache.get(&path) {
829
+ return Ok(m.clone());
830
+ }
831
+ }
832
+ let source = std::fs::read_to_string(&path)
833
+ .map_err(|e| EvalError::Error(format!("Cannot read {}: {}", path.display(), e)))?;
834
+ let program = tishlang_parser::parse(&source)
835
+ .map_err(|e| EvalError::Error(format!("Parse error in {}: {}", path.display(), e)))?;
836
+ let module_scope = Scope::child(Rc::clone(&self.scope));
837
+ let prev_scope = std::mem::replace(&mut self.scope, Rc::clone(&module_scope));
838
+ let parent_dir = self.current_dir.borrow().clone();
839
+ let module_dir = path.parent().map(PathBuf::from);
840
+ *self.current_dir.borrow_mut() = module_dir;
841
+ let mut export_names: Vec<String> = Vec::new();
842
+ for stmt in &program.statements {
843
+ if let Statement::Export { declaration, .. } = stmt {
844
+ match declaration.as_ref() {
845
+ ExportDeclaration::Named(s) => {
846
+ let _ = self.eval_statement(s);
847
+ if let Statement::VarDecl { name, .. } | Statement::FunDecl { name, .. } =
848
+ s.as_ref()
849
+ {
850
+ export_names.push(name.to_string());
851
+ }
852
+ }
853
+ ExportDeclaration::Default(e) => {
854
+ let v = self.eval_expr(e)?;
855
+ self.scope.borrow_mut().set(Arc::from("default"), v, false);
856
+ export_names.push("default".to_string());
857
+ }
858
+ }
859
+ } else {
860
+ let _ = self.eval_statement(stmt);
861
+ }
862
+ }
863
+ let mut exports: PropMap = PropMap::default();
864
+ for name in export_names {
865
+ if let Some(v) = module_scope.borrow().get(&name) {
866
+ exports.insert(Arc::from(name.as_str()), v);
867
+ }
868
+ }
869
+ *self.current_dir.borrow_mut() = parent_dir;
870
+ self.scope = prev_scope;
871
+ let exports_val = Value::object(exports);
872
+ self.module_cache
873
+ .borrow_mut()
874
+ .insert(path, exports_val.clone());
875
+ Ok(exports_val)
876
+ }
877
+
878
+ fn resolve_import_path(from: &str, dir: &Path) -> Result<PathBuf, EvalError> {
879
+ if !from.starts_with("./") && !from.starts_with("../") {
880
+ return Err(EvalError::Error(format!(
881
+ "Only relative imports supported (./ or ../), got: {}",
882
+ from
883
+ )));
884
+ }
885
+ let base = dir.join(from);
886
+ let path = if base.extension().is_none() {
887
+ let with_ext = base.with_extension("tish");
888
+ if with_ext.exists() {
889
+ with_ext
890
+ } else {
891
+ base
892
+ }
893
+ } else {
894
+ base
895
+ };
896
+ Ok(path)
897
+ }
898
+
899
+ /// Load built-in module (tish:fs, tish:http, tish:process, …) or a virtual module from native crates.
900
+ fn load_builtin_module(&self, spec: &str) -> Result<Value, EvalError> {
901
+ if spec.starts_with("cargo:") {
902
+ return Err(EvalError::Error(
903
+ "cargo:… imports are only supported when compiling with `tish build` and the Rust native backend. They link Cargo crates via package.json tish.rustDependencies and a generated native wrapper — not the interpreter or VM.".into(),
904
+ ));
905
+ }
906
+ if let Some(v) = self.virtual_builtins.borrow().get(spec) {
907
+ return Ok(v.clone());
908
+ }
909
+ match spec {
910
+ "tish:fs" => {
911
+ #[cfg(feature = "fs")]
912
+ {
913
+ let mut exports: PropMap = PropMap::default();
914
+ exports.insert("readFile".into(), Value::Native(natives::read_file));
915
+ exports.insert("writeFile".into(), Value::Native(natives::write_file));
916
+ exports.insert("fileExists".into(), Value::Native(natives::file_exists));
917
+ exports.insert("isDir".into(), Value::Native(natives::is_dir));
918
+ exports.insert("readDir".into(), Value::Native(natives::read_dir));
919
+ exports.insert("mkdir".into(), Value::Native(natives::mkdir));
920
+ Ok(Value::object(exports))
921
+ }
922
+ #[cfg(not(feature = "fs"))]
923
+ {
924
+ return Err(EvalError::Error(
925
+ "tish:fs requires the fs feature. Rebuild with: cargo build -p tishlang --features fs".into(),
926
+ ));
927
+ }
928
+ }
929
+ "tish:http" => {
930
+ #[cfg(feature = "http")]
931
+ {
932
+ let mut exports: PropMap = PropMap::default();
933
+ exports.insert("fetch".into(), Value::Native(Self::fetch_native));
934
+ exports.insert("fetchAll".into(), Value::Native(Self::fetch_all_native));
935
+ exports.insert("serve".into(), Value::Serve);
936
+ exports.insert("Promise".into(), Value::PromiseConstructor);
937
+ Ok(Value::object(exports))
938
+ }
939
+ #[cfg(not(feature = "http"))]
940
+ {
941
+ return Err(EvalError::Error(
942
+ "tish:http requires the http feature. Rebuild with: cargo build -p tishlang --features http".into(),
943
+ ));
944
+ }
945
+ }
946
+ "tish:timers" => {
947
+ #[cfg(feature = "timers")]
948
+ {
949
+ let mut exports: PropMap = PropMap::default();
950
+ exports.insert(
951
+ "setTimeout".into(),
952
+ Value::TimerBuiltin(Arc::from("setTimeout")),
953
+ );
954
+ exports.insert(
955
+ "setInterval".into(),
956
+ Value::TimerBuiltin(Arc::from("setInterval")),
957
+ );
958
+ exports.insert(
959
+ "clearTimeout".into(),
960
+ Value::Native(Self::clear_timeout_native),
961
+ );
962
+ exports.insert(
963
+ "clearInterval".into(),
964
+ Value::Native(Self::clear_interval_native),
965
+ );
966
+ Ok(Value::object(exports))
967
+ }
968
+ #[cfg(not(feature = "timers"))]
969
+ {
970
+ return Err(EvalError::Error(
971
+ "tish:timers requires the timers feature. Rebuild with: cargo build -p tishlang --features timers".into(),
972
+ ));
973
+ }
974
+ }
975
+ "tish:ws" => {
976
+ #[cfg(feature = "ws")]
977
+ {
978
+ let mut exports: PropMap = PropMap::default();
979
+ exports.insert(
980
+ "WebSocket".into(),
981
+ Value::Native(Self::ws_web_socket_native),
982
+ );
983
+ exports.insert("Server".into(), Value::Native(Self::ws_server_native));
984
+ exports.insert("wsSend".into(), Value::Native(Self::ws_send_native));
985
+ exports.insert(
986
+ "wsBroadcast".into(),
987
+ Value::Native(Self::ws_broadcast_native),
988
+ );
989
+ Ok(Value::object(exports))
990
+ }
991
+ #[cfg(not(feature = "ws"))]
992
+ {
993
+ return Err(EvalError::Error(
994
+ "tish:ws requires the ws feature. Rebuild with: cargo build -p tishlang --features ws".into(),
995
+ ));
996
+ }
997
+ }
998
+ "tish:tty" => {
999
+ #[cfg(feature = "tty")]
1000
+ {
1001
+ let mut exports: PropMap = PropMap::default();
1002
+ exports.insert("size".into(), Value::Native(natives::tty_size));
1003
+ exports.insert("isTTY".into(), Value::Native(natives::tty_is_tty));
1004
+ exports.insert("setRawMode".into(), Value::Native(natives::tty_set_raw_mode));
1005
+ exports.insert(
1006
+ "enterAltScreen".into(),
1007
+ Value::Native(natives::tty_enter_alt_screen),
1008
+ );
1009
+ exports.insert(
1010
+ "leaveAltScreen".into(),
1011
+ Value::Native(natives::tty_leave_alt_screen),
1012
+ );
1013
+ exports.insert("read".into(), Value::Native(natives::tty_read));
1014
+ exports.insert("readLine".into(), Value::Native(natives::tty_read_line));
1015
+ Ok(Value::object(exports))
1016
+ }
1017
+ #[cfg(not(feature = "tty"))]
1018
+ {
1019
+ return Err(EvalError::Error(
1020
+ "tish:tty requires the tty feature. Rebuild with: cargo build -p tishlang --features tty".into(),
1021
+ ));
1022
+ }
1023
+ }
1024
+ "tish:process" => {
1025
+ #[cfg(feature = "process")]
1026
+ {
1027
+ let mut exports: PropMap = PropMap::default();
1028
+ exports.insert("exit".into(), Value::Native(natives::process_exit));
1029
+ exports.insert("cwd".into(), Value::Native(natives::process_cwd));
1030
+ exports.insert("exec".into(), Value::Native(natives::process_exec));
1031
+ let argv: Vec<Value> =
1032
+ std::env::args().map(|s| Value::String(s.into())).collect();
1033
+ exports.insert(
1034
+ "argv".into(),
1035
+ Value::Array(Rc::new(RefCell::new(argv.clone()))),
1036
+ );
1037
+ let env_obj: PropMap = std::env::vars()
1038
+ .map(|(key, value)| (Arc::from(key.as_str()), Value::String(value.into())))
1039
+ .collect();
1040
+ exports.insert(
1041
+ "env".into(),
1042
+ Value::object(env_obj.clone()),
1043
+ );
1044
+ let mut process_obj = PropMap::default();
1045
+ process_obj.insert("exit".into(), Value::Native(natives::process_exit));
1046
+ process_obj.insert("cwd".into(), Value::Native(natives::process_cwd));
1047
+ process_obj.insert("exec".into(), Value::Native(natives::process_exec));
1048
+ process_obj.insert("argv".into(), Value::Array(Rc::new(RefCell::new(argv))));
1049
+ process_obj.insert("env".into(), Value::object(env_obj));
1050
+ exports.insert(
1051
+ "process".into(),
1052
+ Value::object(process_obj),
1053
+ );
1054
+ Ok(Value::object(exports))
1055
+ }
1056
+ #[cfg(not(feature = "process"))]
1057
+ {
1058
+ return Err(EvalError::Error(
1059
+ "tish:process requires the process feature. Rebuild with: cargo build -p tishlang --features process".into(),
1060
+ ));
1061
+ }
1062
+ }
1063
+ _ => {
1064
+ Err(EvalError::Error(format!(
1065
+ "Unknown built-in module: {}. Supported: tish:fs, tish:http, tish:timers, tish:process, tish:ws (plus any registered by native modules)",
1066
+ spec
1067
+ )))
1068
+ }
1069
+ }
1070
+ }
1071
+
1072
+ fn load_builtin_export(&self, spec: &str, export_name: &str) -> Result<Value, EvalError> {
1073
+ let module = self.load_builtin_module(spec)?;
1074
+ let exports = match &module {
1075
+ Value::Object(m) => m.borrow().clone(),
1076
+ _ => return Err(EvalError::Error("Built-in module must be object".into())),
1077
+ };
1078
+ exports
1079
+ .strings
1080
+ .get(export_name)
1081
+ .cloned()
1082
+ .ok_or_else(|| {
1083
+ EvalError::Error(format!("Module {} does not export '{}'", spec, export_name))
1084
+ })
1085
+ }
1086
+
1087
+ fn eval_expr(&self, expr: &Expr) -> Result<Value, EvalError> {
1088
+ match expr {
1089
+ Expr::Literal { value, .. } => Ok(match value {
1090
+ Literal::Number(n) => Value::Number(*n),
1091
+ Literal::String(s) => Value::String(Arc::clone(s)),
1092
+ Literal::Bool(b) => Value::Bool(*b),
1093
+ Literal::Null => Value::Null,
1094
+ }),
1095
+ Expr::Ident { name, .. } => self
1096
+ .scope
1097
+ .borrow()
1098
+ .get(name.as_ref())
1099
+ .ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name))),
1100
+ Expr::Binary {
1101
+ left,
1102
+ op,
1103
+ right,
1104
+ ..
1105
+ } => {
1106
+ let l = self.eval_expr(left)?;
1107
+ let r = self.eval_expr(right)?;
1108
+ self.eval_binop(&l, *op, &r).map_err(EvalError::Error)
1109
+ }
1110
+ Expr::Unary { op, operand, .. } => {
1111
+ let o = self.eval_expr(operand)?;
1112
+ self.eval_unary(*op, &o).map_err(EvalError::Error)
1113
+ }
1114
+ Expr::Call { callee, args, .. } => {
1115
+ // Check for built-in method calls on arrays/strings
1116
+ if let Expr::Member {
1117
+ object,
1118
+ prop: MemberProp::Name { name: method_name, .. },
1119
+ ..
1120
+ } = callee.as_ref()
1121
+ {
1122
+ let obj = self.eval_expr(object)?;
1123
+ let arg_vals = self.eval_call_args(args)?;
1124
+
1125
+ // Array methods
1126
+ if let Value::Array(arr) = &obj {
1127
+ match method_name.as_ref() {
1128
+ "push" => {
1129
+ let mut arr_mut = arr.borrow_mut();
1130
+ for v in &arg_vals {
1131
+ arr_mut.push(v.clone());
1132
+ }
1133
+ return Ok(Value::Number(arr_mut.len() as f64));
1134
+ }
1135
+ "pop" => {
1136
+ return Ok(arr.borrow_mut().pop().unwrap_or(Value::Null));
1137
+ }
1138
+ "shift" => {
1139
+ let mut arr_mut = arr.borrow_mut();
1140
+ if arr_mut.is_empty() {
1141
+ return Ok(Value::Null);
1142
+ }
1143
+ return Ok(arr_mut.remove(0));
1144
+ }
1145
+ "unshift" => {
1146
+ let mut arr_mut = arr.borrow_mut();
1147
+ for (i, v) in arg_vals.iter().enumerate() {
1148
+ arr_mut.insert(i, v.clone());
1149
+ }
1150
+ return Ok(Value::Number(arr_mut.len() as f64));
1151
+ }
1152
+ "indexOf" => {
1153
+ let search = arg_vals.first().cloned().unwrap_or(Value::Null);
1154
+ let arr_borrow = arr.borrow();
1155
+ for (i, v) in arr_borrow.iter().enumerate() {
1156
+ if v.strict_eq(&search) {
1157
+ return Ok(Value::Number(i as f64));
1158
+ }
1159
+ }
1160
+ return Ok(Value::Number(-1.0));
1161
+ }
1162
+ "includes" => {
1163
+ let search = arg_vals.first().cloned().unwrap_or(Value::Null);
1164
+ let arr_borrow = arr.borrow();
1165
+ let len = arr_borrow.len() as i64;
1166
+ let start = match arg_vals.get(1) {
1167
+ Some(Value::Number(n)) if *n >= 0.0 => (*n as i64).min(len).max(0) as usize,
1168
+ Some(Value::Number(n)) if *n < 0.0 => ((len + *n as i64).max(0)) as usize,
1169
+ _ => 0,
1170
+ };
1171
+ for v in arr_borrow.iter().skip(start) {
1172
+ if v.strict_eq(&search) {
1173
+ return Ok(Value::Bool(true));
1174
+ }
1175
+ }
1176
+ return Ok(Value::Bool(false));
1177
+ }
1178
+ "join" => {
1179
+ let sep = match arg_vals.first() {
1180
+ Some(Value::String(s)) => s.to_string(),
1181
+ _ => ",".to_string(),
1182
+ };
1183
+ let arr_borrow = arr.borrow();
1184
+ // JS join: null/undefined → "", else JS ToString (nested arrays
1185
+ // recurse to a comma-join, objects → "[object Object]").
1186
+ let parts: Vec<String> = arr_borrow
1187
+ .iter()
1188
+ .map(|v| match v {
1189
+ Value::Null => String::new(),
1190
+ other => other.to_js_string(),
1191
+ })
1192
+ .collect();
1193
+ return Ok(Value::String(parts.join(&sep).into()));
1194
+ }
1195
+ "reverse" => {
1196
+ arr.borrow_mut().reverse();
1197
+ return Ok(obj.clone());
1198
+ }
1199
+ "fill" => {
1200
+ // Array.prototype.fill(value, start?, end?) — in place (issue #76).
1201
+ let value = arg_vals.first().cloned().unwrap_or(Value::Null);
1202
+ let mut arr_mut = arr.borrow_mut();
1203
+ let len = arr_mut.len() as i64;
1204
+ let norm = |v: Option<&Value>, dflt: usize| -> usize {
1205
+ match v {
1206
+ Some(Value::Number(n)) => {
1207
+ let n = *n as i64;
1208
+ if n < 0 { (len + n).max(0) as usize } else { (n as usize).min(len as usize) }
1209
+ }
1210
+ _ => dflt,
1211
+ }
1212
+ };
1213
+ let start = norm(arg_vals.get(1), 0);
1214
+ let end = norm(arg_vals.get(2), len as usize);
1215
+ let mut i = start;
1216
+ while i < end && i < arr_mut.len() {
1217
+ arr_mut[i] = value.clone();
1218
+ i += 1;
1219
+ }
1220
+ drop(arr_mut);
1221
+ return Ok(obj.clone());
1222
+ }
1223
+ "shuffle" => {
1224
+ let mut v = arr.borrow().clone();
1225
+ use rand::seq::SliceRandom;
1226
+ v.shuffle(&mut rand::rng());
1227
+ return Ok(Value::Array(Rc::new(RefCell::new(v))));
1228
+ }
1229
+ "sort" => {
1230
+ let comparator = arg_vals.into_iter().next();
1231
+ let mut arr_mut = arr.borrow_mut();
1232
+
1233
+ if let Some(cmp_fn) = comparator {
1234
+ // Check for fast path: (a, b) => a - b numeric ascending
1235
+ let is_numeric_asc = Self::is_numeric_sort_comparator(&cmp_fn, false);
1236
+ let is_numeric_desc = !is_numeric_asc && Self::is_numeric_sort_comparator(&cmp_fn, true);
1237
+
1238
+ if is_numeric_asc {
1239
+ // Fast path: numeric ascending sort
1240
+ arr_mut.sort_by(|a, b| {
1241
+ let na = match a { Value::Number(n) => *n, _ => f64::NAN };
1242
+ let nb = match b { Value::Number(n) => *n, _ => f64::NAN };
1243
+ na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal)
1244
+ });
1245
+ } else if is_numeric_desc {
1246
+ // Fast path: numeric descending sort
1247
+ arr_mut.sort_by(|a, b| {
1248
+ let na = match a { Value::Number(n) => *n, _ => f64::NAN };
1249
+ let nb = match b { Value::Number(n) => *n, _ => f64::NAN };
1250
+ nb.partial_cmp(&na).unwrap_or(std::cmp::Ordering::Equal)
1251
+ });
1252
+ } else {
1253
+ // General case: use comparator function with optimized scope reuse
1254
+ let len = arr_mut.len();
1255
+ let mut indices: Vec<usize> = (0..len).collect();
1256
+ let arr_values: Vec<Value> = std::mem::take(&mut *arr_mut);
1257
+
1258
+ if let Some((scope, params, body)) = self.create_callback_scope(&cmp_fn) {
1259
+ indices.sort_by(|&i, &j| {
1260
+ let result = self.call_with_scope(&scope, &params, &body, &[arr_values[i].clone(), arr_values[j].clone()]);
1261
+ match result {
1262
+ Ok(Value::Number(n)) if n < 0.0 => std::cmp::Ordering::Less,
1263
+ Ok(Value::Number(n)) if n > 0.0 => std::cmp::Ordering::Greater,
1264
+ _ => std::cmp::Ordering::Equal,
1265
+ }
1266
+ });
1267
+ } else {
1268
+ indices.sort_by(|&i, &j| {
1269
+ let result = self.call_func(&cmp_fn, &[arr_values[i].clone(), arr_values[j].clone()]);
1270
+ match result {
1271
+ Ok(Value::Number(n)) if n < 0.0 => std::cmp::Ordering::Less,
1272
+ Ok(Value::Number(n)) if n > 0.0 => std::cmp::Ordering::Greater,
1273
+ _ => std::cmp::Ordering::Equal,
1274
+ }
1275
+ });
1276
+ }
1277
+
1278
+ *arr_mut = indices.into_iter().map(|i| arr_values[i].clone()).collect();
1279
+ }
1280
+ } else {
1281
+ // Default string sort - precompute strings once
1282
+ let mut pairs: Vec<(String, usize)> = arr_mut
1283
+ .iter()
1284
+ .enumerate()
1285
+ .map(|(i, v)| (v.to_string(), i))
1286
+ .collect();
1287
+ pairs.sort_by(|a, b| a.0.cmp(&b.0));
1288
+ let arr_values: Vec<Value> = std::mem::take(&mut *arr_mut);
1289
+ *arr_mut = pairs.into_iter().map(|(_, i)| arr_values[i].clone()).collect();
1290
+ }
1291
+ drop(arr_mut);
1292
+ return Ok(obj.clone());
1293
+ }
1294
+ "splice" => {
1295
+ let mut arr_mut = arr.borrow_mut();
1296
+ let len = arr_mut.len() as i64;
1297
+
1298
+ let start = match arg_vals.first() {
1299
+ Some(Value::Number(n)) => {
1300
+ let n = *n as i64;
1301
+ if n < 0 { (len + n).max(0) as usize } else { n.min(len) as usize }
1302
+ }
1303
+ _ => 0,
1304
+ };
1305
+
1306
+ let delete_count = match arg_vals.get(1) {
1307
+ Some(Value::Number(n)) => (*n as i64).max(0) as usize,
1308
+ _ => (len as usize).saturating_sub(start),
1309
+ };
1310
+
1311
+ let actual_delete = delete_count.min(arr_mut.len().saturating_sub(start));
1312
+ let removed: Vec<Value> = arr_mut.drain(start..start + actual_delete).collect();
1313
+
1314
+ if arg_vals.len() > 2 {
1315
+ let items_to_insert: Vec<Value> = arg_vals[2..].to_vec();
1316
+ for (i, item) in items_to_insert.into_iter().enumerate() {
1317
+ arr_mut.insert(start + i, item);
1318
+ }
1319
+ }
1320
+
1321
+ return Ok(Value::Array(Rc::new(RefCell::new(removed))));
1322
+ }
1323
+ "slice" => {
1324
+ let arr_borrow = arr.borrow();
1325
+ let len = arr_borrow.len() as i64;
1326
+ let start = match arg_vals.first() {
1327
+ Some(Value::Number(n)) => {
1328
+ let n = *n as i64;
1329
+ if n < 0 { (len + n).max(0) as usize } else { n.min(len) as usize }
1330
+ }
1331
+ _ => 0,
1332
+ };
1333
+ let end = match arg_vals.get(1) {
1334
+ Some(Value::Number(n)) => {
1335
+ let n = *n as i64;
1336
+ if n < 0 { (len + n).max(0) as usize } else { n.min(len) as usize }
1337
+ }
1338
+ _ => len as usize,
1339
+ };
1340
+ let sliced: Vec<Value> = if start < end {
1341
+ arr_borrow[start..end].to_vec()
1342
+ } else {
1343
+ vec![]
1344
+ };
1345
+ return Ok(Value::Array(Rc::new(RefCell::new(sliced))));
1346
+ }
1347
+ "concat" => {
1348
+ let mut result = arr.borrow().clone();
1349
+ for v in &arg_vals {
1350
+ if let Value::Array(other) = v {
1351
+ result.extend(other.borrow().iter().cloned());
1352
+ } else {
1353
+ result.push(v.clone());
1354
+ }
1355
+ }
1356
+ return Ok(Value::Array(Rc::new(RefCell::new(result))));
1357
+ }
1358
+ "map" => {
1359
+ let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
1360
+ let arr_borrow = arr.borrow();
1361
+ let mut result = Vec::with_capacity(arr_borrow.len());
1362
+ // Try fastest path: simple single-expression callbacks
1363
+ let first_result = self.eval_simple_callback(&callback, &[arr_borrow.first().cloned().unwrap_or(Value::Null)]);
1364
+ if first_result.is_some() {
1365
+ // Simple callback path - inline evaluation
1366
+ for v in arr_borrow.iter() {
1367
+ if let Some(r) = self.eval_simple_callback(&callback, &[v.clone()]) {
1368
+ result.push(r?);
1369
+ } else {
1370
+ // Shouldn't happen, but fall back
1371
+ result.push(self.call_func(&callback, &[v.clone()])?);
1372
+ }
1373
+ }
1374
+ } else if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
1375
+ // Reusable scope path
1376
+ for (i, v) in arr_borrow.iter().enumerate() {
1377
+ let mapped = self.call_with_scope(&scope, &params, &body, &[v.clone(), Value::Number(i as f64)])?;
1378
+ result.push(mapped);
1379
+ }
1380
+ } else {
1381
+ // Full call_func path
1382
+ for (i, v) in arr_borrow.iter().enumerate() {
1383
+ let mapped = self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
1384
+ result.push(mapped);
1385
+ }
1386
+ }
1387
+ return Ok(Value::Array(Rc::new(RefCell::new(result))));
1388
+ }
1389
+ "filter" => {
1390
+ let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
1391
+ let arr_borrow = arr.borrow();
1392
+ let mut result = Vec::new();
1393
+ // Try simple callback fast path
1394
+ let use_simple = arr_borrow.first().map(|v| {
1395
+ self.eval_simple_callback(&callback, &[v.clone()]).is_some()
1396
+ }).unwrap_or(false);
1397
+ if use_simple {
1398
+ for v in arr_borrow.iter() {
1399
+ if let Some(keep) = self.eval_simple_callback(&callback, &[v.clone()]) {
1400
+ if keep?.is_truthy() {
1401
+ result.push(v.clone());
1402
+ }
1403
+ }
1404
+ }
1405
+ } else if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
1406
+ for (i, v) in arr_borrow.iter().enumerate() {
1407
+ let keep = self.call_with_scope(&scope, &params, &body, &[v.clone(), Value::Number(i as f64)])?;
1408
+ if keep.is_truthy() {
1409
+ result.push(v.clone());
1410
+ }
1411
+ }
1412
+ } else {
1413
+ for (i, v) in arr_borrow.iter().enumerate() {
1414
+ let keep = self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
1415
+ if keep.is_truthy() {
1416
+ result.push(v.clone());
1417
+ }
1418
+ }
1419
+ }
1420
+ return Ok(Value::Array(Rc::new(RefCell::new(result))));
1421
+ }
1422
+ "reduce" => {
1423
+ let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
1424
+ let arr_borrow = arr.borrow();
1425
+ let (mut acc, start_idx) = if arg_vals.len() > 1 {
1426
+ (arg_vals[1].clone(), 0)
1427
+ } else if !arr_borrow.is_empty() {
1428
+ (arr_borrow[0].clone(), 1)
1429
+ } else {
1430
+ return Err(EvalError::Error("Reduce of empty array with no initial value".to_string()));
1431
+ };
1432
+ if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
1433
+ for (i, v) in arr_borrow.iter().enumerate().skip(start_idx) {
1434
+ acc = self.call_with_scope(&scope, &params, &body, &[acc, v.clone(), Value::Number(i as f64)])?;
1435
+ }
1436
+ } else {
1437
+ for (i, v) in arr_borrow.iter().enumerate().skip(start_idx) {
1438
+ acc = self.call_func(&callback, &[acc, v.clone(), Value::Number(i as f64)])?;
1439
+ }
1440
+ }
1441
+ return Ok(acc);
1442
+ }
1443
+ "find" => {
1444
+ let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
1445
+ let arr_borrow = arr.borrow();
1446
+ // Try simple callback fast path
1447
+ let use_simple = arr_borrow.first().map(|v| {
1448
+ self.eval_simple_callback(&callback, &[v.clone()]).is_some()
1449
+ }).unwrap_or(false);
1450
+ if use_simple {
1451
+ for v in arr_borrow.iter() {
1452
+ if let Some(found) = self.eval_simple_callback(&callback, &[v.clone()]) {
1453
+ if found?.is_truthy() {
1454
+ return Ok(v.clone());
1455
+ }
1456
+ }
1457
+ }
1458
+ } else if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
1459
+ for (i, v) in arr_borrow.iter().enumerate() {
1460
+ let found = self.call_with_scope(&scope, &params, &body, &[v.clone(), Value::Number(i as f64)])?;
1461
+ if found.is_truthy() {
1462
+ return Ok(v.clone());
1463
+ }
1464
+ }
1465
+ } else {
1466
+ for (i, v) in arr_borrow.iter().enumerate() {
1467
+ let found = self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
1468
+ if found.is_truthy() {
1469
+ return Ok(v.clone());
1470
+ }
1471
+ }
1472
+ }
1473
+ return Ok(Value::Null);
1474
+ }
1475
+ "findIndex" => {
1476
+ let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
1477
+ let arr_borrow = arr.borrow();
1478
+ if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
1479
+ for (i, v) in arr_borrow.iter().enumerate() {
1480
+ let found = self.call_with_scope(&scope, &params, &body, &[v.clone(), Value::Number(i as f64)])?;
1481
+ if found.is_truthy() {
1482
+ return Ok(Value::Number(i as f64));
1483
+ }
1484
+ }
1485
+ } else {
1486
+ for (i, v) in arr_borrow.iter().enumerate() {
1487
+ let found = self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
1488
+ if found.is_truthy() {
1489
+ return Ok(Value::Number(i as f64));
1490
+ }
1491
+ }
1492
+ }
1493
+ return Ok(Value::Number(-1.0));
1494
+ }
1495
+ "forEach" => {
1496
+ let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
1497
+ let arr_borrow = arr.borrow();
1498
+ if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
1499
+ for (i, v) in arr_borrow.iter().enumerate() {
1500
+ self.call_with_scope(&scope, &params, &body, &[v.clone(), Value::Number(i as f64)])?;
1501
+ }
1502
+ } else {
1503
+ for (i, v) in arr_borrow.iter().enumerate() {
1504
+ self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
1505
+ }
1506
+ }
1507
+ return Ok(Value::Null);
1508
+ }
1509
+ "some" => {
1510
+ let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
1511
+ let arr_borrow = arr.borrow();
1512
+ // Try simple callback fast path
1513
+ let use_simple = arr_borrow.first().map(|v| {
1514
+ self.eval_simple_callback(&callback, &[v.clone()]).is_some()
1515
+ }).unwrap_or(false);
1516
+ if use_simple {
1517
+ for v in arr_borrow.iter() {
1518
+ if let Some(result) = self.eval_simple_callback(&callback, &[v.clone()]) {
1519
+ if result?.is_truthy() {
1520
+ return Ok(Value::Bool(true));
1521
+ }
1522
+ }
1523
+ }
1524
+ } else if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
1525
+ for (i, v) in arr_borrow.iter().enumerate() {
1526
+ let result = self.call_with_scope(&scope, &params, &body, &[v.clone(), Value::Number(i as f64)])?;
1527
+ if result.is_truthy() {
1528
+ return Ok(Value::Bool(true));
1529
+ }
1530
+ }
1531
+ } else {
1532
+ for (i, v) in arr_borrow.iter().enumerate() {
1533
+ let result = self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
1534
+ if result.is_truthy() {
1535
+ return Ok(Value::Bool(true));
1536
+ }
1537
+ }
1538
+ }
1539
+ return Ok(Value::Bool(false));
1540
+ }
1541
+ "every" => {
1542
+ let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
1543
+ let arr_borrow = arr.borrow();
1544
+ // Try simple callback fast path
1545
+ let use_simple = arr_borrow.first().map(|v| {
1546
+ self.eval_simple_callback(&callback, &[v.clone()]).is_some()
1547
+ }).unwrap_or(false);
1548
+ if use_simple {
1549
+ for v in arr_borrow.iter() {
1550
+ if let Some(result) = self.eval_simple_callback(&callback, &[v.clone()]) {
1551
+ if !result?.is_truthy() {
1552
+ return Ok(Value::Bool(false));
1553
+ }
1554
+ }
1555
+ }
1556
+ } else if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
1557
+ for (i, v) in arr_borrow.iter().enumerate() {
1558
+ let result = self.call_with_scope(&scope, &params, &body, &[v.clone(), Value::Number(i as f64)])?;
1559
+ if !result.is_truthy() {
1560
+ return Ok(Value::Bool(false));
1561
+ }
1562
+ }
1563
+ } else {
1564
+ for (i, v) in arr_borrow.iter().enumerate() {
1565
+ let result = self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
1566
+ if !result.is_truthy() {
1567
+ return Ok(Value::Bool(false));
1568
+ }
1569
+ }
1570
+ }
1571
+ return Ok(Value::Bool(true));
1572
+ }
1573
+ "flat" => {
1574
+ let depth = match arg_vals.first() {
1575
+ Some(Value::Number(n)) => *n as usize,
1576
+ _ => 1,
1577
+ };
1578
+ fn flatten(arr: &[Value], depth: usize) -> Vec<Value> {
1579
+ let mut result = Vec::new();
1580
+ for v in arr {
1581
+ if depth > 0 {
1582
+ if let Value::Array(inner) = v {
1583
+ result.extend(flatten(&inner.borrow(), depth - 1));
1584
+ continue;
1585
+ }
1586
+ }
1587
+ result.push(v.clone());
1588
+ }
1589
+ result
1590
+ }
1591
+ let flattened = flatten(&arr.borrow(), depth);
1592
+ return Ok(Value::Array(Rc::new(RefCell::new(flattened))));
1593
+ }
1594
+ _ => {}
1595
+ }
1596
+ }
1597
+
1598
+ // String methods
1599
+ if let Value::String(s) = &obj {
1600
+ match method_name.as_ref() {
1601
+ "indexOf" => {
1602
+ let search = match arg_vals.first() {
1603
+ Some(Value::String(ss)) => ss.as_ref(),
1604
+ _ => return Ok(Value::Number(-1.0)),
1605
+ };
1606
+ let from_char = match arg_vals.get(1) {
1607
+ Some(Value::Number(n)) if *n >= 0.0 => {
1608
+ (*n as usize).min(s.chars().count())
1609
+ }
1610
+ _ => 0,
1611
+ };
1612
+ let byte_start: usize = s.chars().take(from_char).map(|c| c.len_utf8()).sum();
1613
+ let found = s[byte_start..].find(search).map(|byte_pos| {
1614
+ let char_idx = from_char
1615
+ + s[byte_start..][..byte_pos].chars().count();
1616
+ char_idx as f64
1617
+ });
1618
+ return Ok(Value::Number(found.unwrap_or(-1.0)));
1619
+ }
1620
+ "lastIndexOf" => {
1621
+ return Ok(Self::string_last_index_of_eval(&arg_vals, s));
1622
+ }
1623
+ "includes" => {
1624
+ let search = match arg_vals.first() {
1625
+ Some(Value::String(ss)) => ss.as_ref(),
1626
+ _ => return Ok(Value::Bool(false)),
1627
+ };
1628
+ let from_char = match arg_vals.get(1) {
1629
+ Some(Value::Number(n)) if *n >= 0.0 => (*n as usize).min(s.chars().count()),
1630
+ Some(Value::Number(n)) if *n < 0.0 => {
1631
+ let len = s.chars().count() as i64;
1632
+ ((len + *n as i64).max(0)) as usize
1633
+ }
1634
+ _ => 0,
1635
+ };
1636
+ let byte_start: usize = s.chars().take(from_char).map(|c| c.len_utf8()).sum();
1637
+ return Ok(Value::Bool(s[byte_start..].contains(search)));
1638
+ }
1639
+ "slice" => {
1640
+ let chars: Vec<char> = s.chars().collect();
1641
+ let len = chars.len() as i64;
1642
+ let start = match arg_vals.first() {
1643
+ Some(Value::Number(n)) => {
1644
+ let n = *n as i64;
1645
+ if n < 0 { (len + n).max(0) as usize } else { n.min(len) as usize }
1646
+ }
1647
+ _ => 0,
1648
+ };
1649
+ let end = match arg_vals.get(1) {
1650
+ Some(Value::Number(n)) => {
1651
+ let n = *n as i64;
1652
+ if n < 0 { (len + n).max(0) as usize } else { n.min(len) as usize }
1653
+ }
1654
+ _ => len as usize,
1655
+ };
1656
+ let sliced: String = if start < end {
1657
+ chars[start..end].iter().collect()
1658
+ } else {
1659
+ String::new()
1660
+ };
1661
+ return Ok(Value::String(sliced.into()));
1662
+ }
1663
+ "substring" => {
1664
+ let chars: Vec<char> = s.chars().collect();
1665
+ let len = chars.len();
1666
+ let start = match arg_vals.first() {
1667
+ Some(Value::Number(n)) => (*n as usize).min(len),
1668
+ _ => 0,
1669
+ };
1670
+ let end = match arg_vals.get(1) {
1671
+ Some(Value::Number(n)) => (*n as usize).min(len),
1672
+ _ => len,
1673
+ };
1674
+ let (s, e) = (start.min(end), start.max(end));
1675
+ return Ok(Value::String(chars[s..e].iter().collect::<String>().into()));
1676
+ }
1677
+ "split" => {
1678
+ #[cfg(feature = "regex")]
1679
+ if let Some(sep) = arg_vals.first() {
1680
+ let limit = arg_vals.get(1).and_then(|v| match v {
1681
+ Value::Number(n) => Some(*n as usize),
1682
+ _ => None,
1683
+ });
1684
+ return Ok(crate::regex::string_split(s, sep, limit));
1685
+ }
1686
+ #[cfg(not(feature = "regex"))]
1687
+ {
1688
+ let sep = match arg_vals.first() {
1689
+ Some(Value::String(ss)) => ss.as_ref(),
1690
+ _ => return Ok(Value::Array(Rc::new(RefCell::new(vec![obj.clone()])))),
1691
+ };
1692
+ let parts: Vec<Value> = s.split(sep)
1693
+ .map(|p| Value::String(p.into()))
1694
+ .collect();
1695
+ return Ok(Value::Array(Rc::new(RefCell::new(parts))));
1696
+ }
1697
+ #[cfg(feature = "regex")]
1698
+ return Ok(Value::Array(Rc::new(RefCell::new(vec![obj.clone()]))));
1699
+ }
1700
+ "trim" => {
1701
+ return Ok(Value::String(s.trim().into()));
1702
+ }
1703
+ "toUpperCase" => {
1704
+ return Ok(Value::String(s.to_uppercase().into()));
1705
+ }
1706
+ "toLowerCase" => {
1707
+ return Ok(Value::String(s.to_lowercase().into()));
1708
+ }
1709
+ "startsWith" => {
1710
+ let search = match arg_vals.first() {
1711
+ Some(Value::String(ss)) => ss.as_ref(),
1712
+ _ => return Ok(Value::Bool(false)),
1713
+ };
1714
+ return Ok(Value::Bool(s.starts_with(search)));
1715
+ }
1716
+ "endsWith" => {
1717
+ let search = match arg_vals.first() {
1718
+ Some(Value::String(ss)) => ss.as_ref(),
1719
+ _ => return Ok(Value::Bool(false)),
1720
+ };
1721
+ return Ok(Value::Bool(s.ends_with(search)));
1722
+ }
1723
+ "replace" => {
1724
+ #[cfg(feature = "regex")]
1725
+ if let (Some(search), Some(replace)) = (arg_vals.first(), arg_vals.get(1)) {
1726
+ let is_fn = matches!(replace, Value::Function { .. } | Value::Native(_));
1727
+ if matches!(search, Value::RegExp(_)) && is_fn {
1728
+ let re = match search {
1729
+ Value::RegExp(r) => r.clone(),
1730
+ _ => unreachable!(),
1731
+ };
1732
+ let re_guard = re.borrow();
1733
+ let replace_fn = replace.clone();
1734
+ let input_str = s.as_ref();
1735
+ let mut invoke = |args: &[Value]| {
1736
+ self.call_func(&replace_fn, args)
1737
+ .map(|v| v.to_string())
1738
+ .map_err(|e: EvalError| e.to_string())
1739
+ };
1740
+ match crate::regex::string_replace_regex_with_fn(
1741
+ input_str,
1742
+ &re_guard,
1743
+ &mut invoke,
1744
+ ) {
1745
+ Ok(v) => return Ok(v),
1746
+ Err(_) => return Ok(Value::String(Arc::clone(s))),
1747
+ }
1748
+ }
1749
+ return Ok(crate::regex::string_replace(s.as_ref(), search, replace));
1750
+ }
1751
+ #[cfg(not(feature = "regex"))]
1752
+ {
1753
+ let search = match arg_vals.first() {
1754
+ Some(Value::String(ss)) => ss.to_string(),
1755
+ _ => return Ok(obj.clone()),
1756
+ };
1757
+ let replacement = match arg_vals.get(1) {
1758
+ Some(Value::String(ss)) => ss.to_string(),
1759
+ _ => String::new(),
1760
+ };
1761
+ return Ok(Value::String(s.replacen(&search, &replacement, 1).into()));
1762
+ }
1763
+ #[cfg(feature = "regex")]
1764
+ return Ok(obj.clone());
1765
+ }
1766
+ "replaceAll" => {
1767
+ let search = match arg_vals.first() {
1768
+ Some(Value::String(ss)) => ss.to_string(),
1769
+ _ => return Ok(obj.clone()),
1770
+ };
1771
+ let replacement = match arg_vals.get(1) {
1772
+ Some(Value::String(ss)) => ss.to_string(),
1773
+ _ => String::new(),
1774
+ };
1775
+ return Ok(Value::String(s.replace(&search, &replacement).into()));
1776
+ }
1777
+ "charAt" => {
1778
+ let idx = match arg_vals.first() {
1779
+ Some(Value::Number(n)) => *n as usize,
1780
+ _ => 0,
1781
+ };
1782
+ let chars: Vec<char> = s.chars().collect();
1783
+ return Ok(chars.get(idx)
1784
+ .map(|c| Value::String(c.to_string().into()))
1785
+ .unwrap_or(Value::String("".into())));
1786
+ }
1787
+ "charCodeAt" => {
1788
+ let idx = match arg_vals.first() {
1789
+ Some(Value::Number(n)) => *n as usize,
1790
+ _ => 0,
1791
+ };
1792
+ let chars: Vec<char> = s.chars().collect();
1793
+ return Ok(chars.get(idx)
1794
+ .map(|c| Value::Number(*c as u32 as f64))
1795
+ .unwrap_or(Value::Number(f64::NAN)));
1796
+ }
1797
+ "repeat" => {
1798
+ let count = match arg_vals.first() {
1799
+ Some(Value::Number(n)) if *n >= 0.0 => *n as usize,
1800
+ _ => 0,
1801
+ };
1802
+ return Ok(Value::String(s.repeat(count).into()));
1803
+ }
1804
+ "padStart" => {
1805
+ let target_len = match arg_vals.first() {
1806
+ Some(Value::Number(n)) => *n as usize,
1807
+ _ => return Ok(obj.clone()),
1808
+ };
1809
+ let pad = match arg_vals.get(1) {
1810
+ Some(Value::String(p)) => p.to_string(),
1811
+ _ => " ".to_string(),
1812
+ };
1813
+ let char_count = s.chars().count();
1814
+ if char_count >= target_len || pad.is_empty() {
1815
+ return Ok(obj.clone());
1816
+ }
1817
+ let needed = target_len - char_count;
1818
+ let padding: String = pad.chars().cycle().take(needed).collect();
1819
+ return Ok(Value::String(format!("{}{}", padding, s).into()));
1820
+ }
1821
+ "padEnd" => {
1822
+ let target_len = match arg_vals.first() {
1823
+ Some(Value::Number(n)) => *n as usize,
1824
+ _ => return Ok(obj.clone()),
1825
+ };
1826
+ let pad = match arg_vals.get(1) {
1827
+ Some(Value::String(p)) => p.to_string(),
1828
+ _ => " ".to_string(),
1829
+ };
1830
+ let char_count = s.chars().count();
1831
+ if char_count >= target_len || pad.is_empty() {
1832
+ return Ok(obj.clone());
1833
+ }
1834
+ let needed = target_len - char_count;
1835
+ let padding: String = pad.chars().cycle().take(needed).collect();
1836
+ return Ok(Value::String(format!("{}{}", s, padding).into()));
1837
+ }
1838
+ #[cfg(feature = "regex")]
1839
+ "match" => {
1840
+ if let Some(regexp) = arg_vals.first() {
1841
+ return Ok(crate::regex::string_match(s, regexp));
1842
+ }
1843
+ return Ok(Value::Null);
1844
+ }
1845
+ #[cfg(feature = "regex")]
1846
+ "search" => {
1847
+ if let Some(regexp) = arg_vals.first() {
1848
+ return Ok(crate::regex::string_search(s, regexp));
1849
+ }
1850
+ return Ok(Value::Number(-1.0));
1851
+ }
1852
+ _ => {}
1853
+ }
1854
+ }
1855
+
1856
+ // Number methods
1857
+ if let Value::Number(n) = &obj {
1858
+ if method_name.as_ref() == "toFixed" {
1859
+ let digits = arg_vals
1860
+ .first()
1861
+ .and_then(|v| match v {
1862
+ Value::Number(d) => Some(*d as i32),
1863
+ _ => None,
1864
+ })
1865
+ .unwrap_or(0)
1866
+ .clamp(0, 20); // ECMA-262: 0–20
1867
+ let formatted = format!("{:.*}", digits as usize, n);
1868
+ return Ok(Value::String(formatted.into()));
1869
+ }
1870
+ if method_name.as_ref() == "toString" {
1871
+ // Shares the VM/native formatting via the backend-agnostic helper
1872
+ // (issue #59). Radix defaults to 10; 2–36 supported, else RangeError.
1873
+ let radix = arg_vals
1874
+ .first()
1875
+ .and_then(|v| match v {
1876
+ Value::Number(d) => Some(*d as i64),
1877
+ _ => None,
1878
+ })
1879
+ .unwrap_or(10);
1880
+ return match tishlang_builtins::number::number_to_string_radix(*n, radix)
1881
+ {
1882
+ Some(s) => Ok(Value::String(s.into())),
1883
+ None => Err(EvalError::Error(
1884
+ "toString() radix must be between 2 and 36".to_string(),
1885
+ )),
1886
+ };
1887
+ }
1888
+ }
1889
+
1890
+ // RegExp methods
1891
+ #[cfg(feature = "regex")]
1892
+ if let Value::RegExp(re) = &obj {
1893
+ match method_name.as_ref() {
1894
+ "test" => {
1895
+ let input = arg_vals.first()
1896
+ .map(|v| v.to_string())
1897
+ .unwrap_or_default();
1898
+ let result = re.borrow_mut().test(&input);
1899
+ return Ok(Value::Bool(result));
1900
+ }
1901
+ "exec" => {
1902
+ let input = arg_vals.first()
1903
+ .map(|v| v.to_string())
1904
+ .unwrap_or_default();
1905
+ let result = crate::regex::regexp_exec(&mut re.borrow_mut(), &input);
1906
+ return Ok(result);
1907
+ }
1908
+ _ => {}
1909
+ }
1910
+ }
1911
+
1912
+ // Fall through to normal function call. `get_prop` only implements `length` on
1913
+ // strings, so method calls would otherwise become `call_func(Null)` → Not a function.
1914
+ if let Value::String(s) = &obj {
1915
+ if method_name.as_ref() == "lastIndexOf" {
1916
+ return Ok(Self::string_last_index_of_eval(&arg_vals, s));
1917
+ }
1918
+ }
1919
+ let f = self.get_prop(&obj, method_name).map_err(EvalError::Error)?;
1920
+ return self.call_func(&f, &arg_vals);
1921
+ }
1922
+
1923
+ let f = self.eval_expr(callee)?;
1924
+ let arg_vals = self.eval_call_args(args)?;
1925
+ self.call_func(&f, &arg_vals)
1926
+ }
1927
+ Expr::Member {
1928
+ object,
1929
+ prop,
1930
+ optional,
1931
+ ..
1932
+ } => {
1933
+ let obj = self.eval_expr(object)?;
1934
+ if *optional && matches!(obj, Value::Null) {
1935
+ return Ok(Value::Null);
1936
+ }
1937
+ let key = match prop {
1938
+ MemberProp::Name { name, .. } => Arc::clone(name),
1939
+ MemberProp::Expr(e) => {
1940
+ let v = self.eval_expr(e)?;
1941
+ match v {
1942
+ Value::String(s) => s,
1943
+ _ => return Err(EvalError::Error("Property key must be string".to_string())),
1944
+ }
1945
+ }
1946
+ };
1947
+ match self.get_prop(&obj, &key) {
1948
+ Ok(v) => Ok(v),
1949
+ Err(_) if *optional => Ok(Value::Null),
1950
+ Err(e) => Err(EvalError::Error(e)),
1951
+ }
1952
+ }
1953
+ Expr::Index {
1954
+ object,
1955
+ index,
1956
+ optional,
1957
+ ..
1958
+ } => {
1959
+ let obj = self.eval_expr(object)?;
1960
+ if *optional && matches!(obj, Value::Null) {
1961
+ return Ok(Value::Null);
1962
+ }
1963
+ let idx = self.eval_expr(index)?;
1964
+ self.get_index(&obj, &idx).map_err(EvalError::Error)
1965
+ }
1966
+ Expr::Conditional {
1967
+ cond,
1968
+ then_branch,
1969
+ else_branch,
1970
+ ..
1971
+ } => {
1972
+ if self.eval_expr(cond)?.is_truthy() {
1973
+ self.eval_expr(then_branch)
1974
+ } else {
1975
+ self.eval_expr(else_branch)
1976
+ }
1977
+ }
1978
+ Expr::NullishCoalesce { left, right, .. } => {
1979
+ let l = self.eval_expr(left)?;
1980
+ if matches!(l, Value::Null) {
1981
+ self.eval_expr(right)
1982
+ } else {
1983
+ Ok(l)
1984
+ }
1985
+ }
1986
+ Expr::Array { elements, .. } => {
1987
+ let mut vals = Vec::with_capacity(elements.len());
1988
+ for elem in elements {
1989
+ match elem {
1990
+ tishlang_ast::ArrayElement::Expr(e) => {
1991
+ vals.push(self.eval_expr(e)?);
1992
+ }
1993
+ tishlang_ast::ArrayElement::Spread(e) => {
1994
+ let spread_val = self.eval_expr(e)?;
1995
+ if let Value::Array(arr) = &spread_val {
1996
+ vals.extend(arr.borrow().iter().cloned());
1997
+ } else if let Some(items) = self.drain_eval_iterator(&spread_val) {
1998
+ // Spread a Map/Set iterator (`[...m.values()]`).
1999
+ vals.extend(items);
2000
+ }
2001
+ }
2002
+ }
2003
+ }
2004
+ Ok(Value::Array(Rc::new(RefCell::new(vals))))
2005
+ }
2006
+ Expr::Object { props, .. } => {
2007
+ let mut data = EvalObjectData::default();
2008
+ for prop in props {
2009
+ match prop {
2010
+ tishlang_ast::ObjectProp::KeyValue(k, v) => {
2011
+ data
2012
+ .strings
2013
+ .insert(Arc::clone(k), self.eval_expr(v)?);
2014
+ }
2015
+ tishlang_ast::ObjectProp::Spread(e) => {
2016
+ let spread_val = self.eval_expr(e)?;
2017
+ if let Value::Object(obj) = spread_val {
2018
+ let b = obj.borrow();
2019
+ for (k, v) in b.strings.iter() {
2020
+ data.strings.insert(Arc::clone(k), v.clone());
2021
+ }
2022
+ if let Some(ref sm) = b.symbols {
2023
+ if data.symbols.is_none() {
2024
+ data.symbols = Some(AHashMap::default());
2025
+ }
2026
+ let dm = data.symbols.as_mut().unwrap();
2027
+ for (id, v) in sm.iter() {
2028
+ dm.insert(*id, v.clone());
2029
+ }
2030
+ }
2031
+ }
2032
+ }
2033
+ }
2034
+ }
2035
+ Ok(Value::Object(Rc::new(RefCell::new(data))))
2036
+ }
2037
+ Expr::Assign { name, value, .. } => {
2038
+ let v = self.eval_expr(value)?;
2039
+ match self.scope.borrow_mut().assign(name.as_ref(), v.clone()) {
2040
+ Ok(true) => Ok(v),
2041
+ Ok(false) => Err(EvalError::Error(format!("Undefined variable: {}", name))),
2042
+ Err(e) => Err(EvalError::Error(e)),
2043
+ }
2044
+ }
2045
+ Expr::Await { operand, .. } => self.eval_await(operand),
2046
+ Expr::New { callee, args, .. } => {
2047
+ let c = self.eval_expr(callee)?;
2048
+ let arg_vals = self.eval_call_args(args)?;
2049
+ self.construct_value(&c, &arg_vals)
2050
+ }
2051
+ Expr::JsxElement { .. } | Expr::JsxFragment { .. } => Err(EvalError::Error(
2052
+ "JSX is not supported in the interpreter. Use 'tish build --target js' to compile to JavaScript.".to_string(),
2053
+ )),
2054
+ Expr::NativeModuleLoad { spec, export_name, .. } => {
2055
+ self.load_builtin_export(spec.as_ref(), export_name.as_ref())
2056
+ }
2057
+ Expr::TypeOf { operand, .. } => {
2058
+ let v = self.eval_expr(operand)?;
2059
+ Ok(Value::String(match &v {
2060
+ Value::Number(_) => "number".into(),
2061
+ Value::String(_) => "string".into(),
2062
+ Value::Bool(_) => "boolean".into(),
2063
+ Value::Null => "null".into(),
2064
+ Value::Array(_) => "object".into(),
2065
+ Value::Object(_) => "object".into(),
2066
+ Value::Symbol(_) => "symbol".into(),
2067
+ Value::Function { .. } | Value::Native(_) => "function".into(),
2068
+ Value::CoreFn(_) => "function".into(),
2069
+ #[cfg(feature = "http")]
2070
+ Value::CorePromise(_) => "object".into(),
2071
+ #[cfg(feature = "http")]
2072
+ Value::Serve
2073
+ | Value::PromiseResolver(_)
2074
+ | Value::PromiseConstructor
2075
+ | Value::BoundPromiseMethod(_, _) => "function".into(),
2076
+ #[cfg(feature = "timers")]
2077
+ Value::TimerBuiltin(_) => "function".into(),
2078
+ #[cfg(feature = "http")]
2079
+ Value::Promise(_) => "object".into(),
2080
+ #[cfg(feature = "regex")]
2081
+ Value::RegExp(_) => "object".into(),
2082
+ Value::Opaque(_) => "object".into(),
2083
+ Value::OpaqueMethod(_, _) => "function".into(),
2084
+ }))
2085
+ }
2086
+ // `delete obj.prop` / `delete obj[key]` (issue #40): remove the property and
2087
+ // evaluate to `true`. Objects drop the key; arrays clear a numeric index to a
2088
+ // null hole (length preserved). Deleting a non-reference is a no-op (still `true`).
2089
+ Expr::Delete { target, .. } => {
2090
+ // Resolve the target to (object value, key value); then remove the key.
2091
+ let resolved = match target.as_ref() {
2092
+ Expr::Member { object, prop: MemberProp::Name { name, .. }, .. } => {
2093
+ Some((self.eval_expr(object)?, Value::String(name.as_ref().into())))
2094
+ }
2095
+ Expr::Member { object, prop: MemberProp::Expr(key), .. } => {
2096
+ Some((self.eval_expr(object)?, self.eval_expr(key)?))
2097
+ }
2098
+ Expr::Index { object, index, .. } => {
2099
+ Some((self.eval_expr(object)?, self.eval_expr(index)?))
2100
+ }
2101
+ _ => None,
2102
+ };
2103
+ if let Some((obj, key)) = resolved {
2104
+ match &obj {
2105
+ Value::Object(map) => {
2106
+ let key_s = match &key {
2107
+ Value::String(s) => s.to_string(),
2108
+ Value::Number(n) => n.to_string(),
2109
+ other => other.to_string(),
2110
+ };
2111
+ // shift_remove preserves the insertion order of the remaining keys
2112
+ // (JS delete semantics); plain remove() is deprecated on IndexMap.
2113
+ map.borrow_mut().strings.shift_remove(key_s.as_str());
2114
+ }
2115
+ Value::Array(arr) => {
2116
+ if let Value::Number(n) = &key {
2117
+ let n = *n;
2118
+ if n >= 0.0 && n.fract() == 0.0 {
2119
+ let i = n as usize;
2120
+ let mut a = arr.borrow_mut();
2121
+ if i < a.len() {
2122
+ a[i] = Value::Null;
2123
+ }
2124
+ }
2125
+ }
2126
+ }
2127
+ _ => {}
2128
+ }
2129
+ }
2130
+ Ok(Value::Bool(true))
2131
+ }
2132
+ Expr::PostfixInc { name, .. } => {
2133
+ let v = self.scope.borrow().get(name.as_ref())
2134
+ .ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name)))?;
2135
+ let n = match &v {
2136
+ Value::Number(x) => *x,
2137
+ _ => return Err(EvalError::Error(format!("Cannot apply ++ to {:?}", v))),
2138
+ };
2139
+ match self.scope.borrow_mut().assign(name.as_ref(), Value::Number(n + 1.0)) {
2140
+ Ok(true) => Ok(Value::Number(n)),
2141
+ Ok(false) => Err(EvalError::Error(format!("Undefined variable: {}", name))),
2142
+ Err(e) => Err(EvalError::Error(e)),
2143
+ }
2144
+ }
2145
+ Expr::PostfixDec { name, .. } => {
2146
+ let v = self.scope.borrow().get(name.as_ref())
2147
+ .ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name)))?;
2148
+ let n = match &v {
2149
+ Value::Number(x) => *x,
2150
+ _ => return Err(EvalError::Error(format!("Cannot apply -- to {:?}", v))),
2151
+ };
2152
+ match self.scope.borrow_mut().assign(name.as_ref(), Value::Number(n - 1.0)) {
2153
+ Ok(true) => Ok(Value::Number(n)),
2154
+ Ok(false) => Err(EvalError::Error(format!("Undefined variable: {}", name))),
2155
+ Err(e) => Err(EvalError::Error(e)),
2156
+ }
2157
+ }
2158
+ Expr::PrefixInc { name, .. } => {
2159
+ let v = self.scope.borrow().get(name.as_ref())
2160
+ .ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name)))?;
2161
+ let n = match &v {
2162
+ Value::Number(x) => *x,
2163
+ _ => return Err(EvalError::Error(format!("Cannot apply ++ to {:?}", v))),
2164
+ };
2165
+ let new_val = Value::Number(n + 1.0);
2166
+ match self.scope.borrow_mut().assign(name.as_ref(), new_val.clone()) {
2167
+ Ok(true) => Ok(new_val),
2168
+ Ok(false) => Err(EvalError::Error(format!("Undefined variable: {}", name))),
2169
+ Err(e) => Err(EvalError::Error(e)),
2170
+ }
2171
+ }
2172
+ Expr::PrefixDec { name, .. } => {
2173
+ let v = self.scope.borrow().get(name.as_ref())
2174
+ .ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name)))?;
2175
+ let n = match &v {
2176
+ Value::Number(x) => *x,
2177
+ _ => return Err(EvalError::Error(format!("Cannot apply -- to {:?}", v))),
2178
+ };
2179
+ let new_val = Value::Number(n - 1.0);
2180
+ match self.scope.borrow_mut().assign(name.as_ref(), new_val.clone()) {
2181
+ Ok(true) => Ok(new_val),
2182
+ Ok(false) => Err(EvalError::Error(format!("Undefined variable: {}", name))),
2183
+ Err(e) => Err(EvalError::Error(e)),
2184
+ }
2185
+ }
2186
+ Expr::CompoundAssign { name, op, value, .. } => {
2187
+ let current = self.scope.borrow().get(name.as_ref())
2188
+ .ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name)))?;
2189
+ let rhs = self.eval_expr(value)?;
2190
+ let bin_op = match op {
2191
+ CompoundOp::Add => BinOp::Add,
2192
+ CompoundOp::Sub => BinOp::Sub,
2193
+ CompoundOp::Mul => BinOp::Mul,
2194
+ CompoundOp::Div => BinOp::Div,
2195
+ CompoundOp::Mod => BinOp::Mod,
2196
+ };
2197
+ let result = self.eval_binop(&current, bin_op, &rhs).map_err(EvalError::Error)?;
2198
+ match self.scope.borrow_mut().assign(name.as_ref(), result.clone()) {
2199
+ Ok(true) => Ok(result),
2200
+ Ok(false) => Err(EvalError::Error(format!("Undefined variable: {}", name))),
2201
+ Err(e) => Err(EvalError::Error(e)),
2202
+ }
2203
+ }
2204
+ Expr::LogicalAssign { name, op, value, .. } => {
2205
+ let current = self.scope.borrow().get(name.as_ref())
2206
+ .ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name)))?;
2207
+ let result = match op {
2208
+ LogicalAssignOp::AndAnd => {
2209
+ if current.is_truthy() {
2210
+ let rhs = self.eval_expr(value)?;
2211
+ let _ = self.scope.borrow_mut().assign(name.as_ref(), rhs.clone());
2212
+ rhs
2213
+ } else {
2214
+ current.clone()
2215
+ }
2216
+ }
2217
+ LogicalAssignOp::OrOr => {
2218
+ if !current.is_truthy() {
2219
+ let rhs = self.eval_expr(value)?;
2220
+ let _ = self.scope.borrow_mut().assign(name.as_ref(), rhs.clone());
2221
+ rhs
2222
+ } else {
2223
+ current.clone()
2224
+ }
2225
+ }
2226
+ LogicalAssignOp::Nullish => {
2227
+ if matches!(current, Value::Null) {
2228
+ let rhs = self.eval_expr(value)?;
2229
+ let _ = self.scope.borrow_mut().assign(name.as_ref(), rhs.clone());
2230
+ rhs
2231
+ } else {
2232
+ current.clone()
2233
+ }
2234
+ }
2235
+ };
2236
+ Ok(result)
2237
+ }
2238
+ Expr::MemberAssign { object, prop, value, .. } => {
2239
+ let obj_val = self.eval_expr(object)?;
2240
+ let val = self.eval_expr(value)?;
2241
+ match obj_val {
2242
+ Value::Object(map) => {
2243
+ map.borrow_mut()
2244
+ .strings
2245
+ .insert(Arc::clone(prop), val.clone());
2246
+ Ok(val)
2247
+ }
2248
+ // `arr.length = k` truncates / grows the array (holes read back as Null),
2249
+ // matching JS and the bytecode VM (issue #62).
2250
+ Value::Array(arr) if prop.as_ref() == "length" => {
2251
+ let n = match &val {
2252
+ Value::Number(n) => *n,
2253
+ _ => f64::NAN,
2254
+ };
2255
+ if n.is_nan() || n < 0.0 || n.fract() != 0.0 || n > 4_294_967_295.0 {
2256
+ return Err(EvalError::Error("Invalid array length".to_string()));
2257
+ }
2258
+ arr.borrow_mut().resize(n as usize, Value::Null);
2259
+ Ok(val)
2260
+ }
2261
+ _ => Err(EvalError::Error(format!(
2262
+ "Cannot assign property '{}' on non-object: {:?}",
2263
+ prop, obj_val
2264
+ ))),
2265
+ }
2266
+ }
2267
+ Expr::IndexAssign { object, index, value, .. } => {
2268
+ let obj_val = self.eval_expr(object)?;
2269
+ let idx_val = self.eval_expr(index)?;
2270
+ let val = self.eval_expr(value)?;
2271
+ match obj_val {
2272
+ Value::Array(arr) => {
2273
+ let idx = match &idx_val {
2274
+ Value::Number(n) => *n as usize,
2275
+ _ => return Err(EvalError::Error(format!(
2276
+ "Array index must be a number, got {:?}",
2277
+ idx_val
2278
+ ))),
2279
+ };
2280
+ let mut arr_mut = arr.borrow_mut();
2281
+ // Extend array if necessary (JS behavior)
2282
+ while arr_mut.len() <= idx {
2283
+ arr_mut.push(Value::Null);
2284
+ }
2285
+ arr_mut[idx] = val.clone();
2286
+ Ok(val)
2287
+ }
2288
+ Value::Object(_) => {
2289
+ eval_object_set(&obj_val, &idx_val, val.clone())
2290
+ .map_err(EvalError::Error)?;
2291
+ Ok(val)
2292
+ }
2293
+ _ => Err(EvalError::Error(format!(
2294
+ "Cannot assign index on non-array/object: {:?}",
2295
+ obj_val
2296
+ ))),
2297
+ }
2298
+ }
2299
+ Expr::ArrowFunction { params, body, .. } => {
2300
+ use tishlang_ast::ArrowBody;
2301
+ let formals: Arc<[FunParam]> = Arc::from(params.clone());
2302
+ let body_stmt = match body {
2303
+ ArrowBody::Expr(expr) => {
2304
+ // Expression body: wrap in implicit return
2305
+ Statement::Return {
2306
+ value: Some(expr.as_ref().clone()),
2307
+ span: Span { start: (0, 0), end: (0, 0) },
2308
+ }
2309
+ }
2310
+ ArrowBody::Block(stmt) => stmt.as_ref().clone(),
2311
+ };
2312
+ Ok(Value::Function {
2313
+ formals,
2314
+ rest_param: None,
2315
+ body: Arc::new(body_stmt),
2316
+ env: Rc::clone(&self.scope),
2317
+ })
2318
+ }
2319
+ Expr::TemplateLiteral { quasis, exprs, .. } => {
2320
+ // Build the string by interleaving quasis and evaluated expressions
2321
+ let mut result = String::new();
2322
+ for (i, quasi) in quasis.iter().enumerate() {
2323
+ result.push_str(quasi);
2324
+ if i < exprs.len() {
2325
+ let val = self.eval_expr(&exprs[i])?;
2326
+ result.push_str(&val.to_js_string());
2327
+ }
2328
+ }
2329
+ Ok(Value::String(result.into()))
2330
+ }
2331
+ }
2332
+ }
2333
+
2334
+ fn eval_binop(&self, l: &Value, op: BinOp, r: &Value) -> Result<Value, String> {
2335
+ match op {
2336
+ BinOp::Add => match (l, r) {
2337
+ (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
2338
+ (Value::String(a), Value::String(b)) => {
2339
+ let mut s = String::with_capacity(a.len() + b.len());
2340
+ s.push_str(a);
2341
+ s.push_str(b);
2342
+ Ok(Value::String(s.into()))
2343
+ }
2344
+ (Value::String(a), b) => {
2345
+ let b_str = b.to_js_string();
2346
+ let mut s = String::with_capacity(a.len() + b_str.len());
2347
+ s.push_str(a);
2348
+ s.push_str(&b_str);
2349
+ Ok(Value::String(s.into()))
2350
+ }
2351
+ (a, Value::String(b)) => {
2352
+ let a_str = a.to_js_string();
2353
+ let mut s = String::with_capacity(a_str.len() + b.len());
2354
+ s.push_str(&a_str);
2355
+ s.push_str(b);
2356
+ Ok(Value::String(s.into()))
2357
+ }
2358
+ // Neither operand is a string: numeric add, coercing non-numbers
2359
+ // (Null/Bool/Object/…) to NaN exactly like the VM's
2360
+ // `as_number().unwrap_or(NaN)` (vm.rs eval_binop). e.g. an out-of-bounds
2361
+ // array read is `Null` (JS `undefined`), so `15 + arr[oob]` → NaN, not an error.
2362
+ _ => Ok(Value::Number(
2363
+ l.as_number().unwrap_or(f64::NAN) + r.as_number().unwrap_or(f64::NAN),
2364
+ )),
2365
+ },
2366
+ BinOp::Sub => self.binop_number(l, r, |a, b| Value::Number(a - b)),
2367
+ BinOp::Mul => self.binop_number(l, r, |a, b| Value::Number(a * b)),
2368
+ BinOp::Div => self.binop_number(l, r, |a, b| Value::Number(a / b)),
2369
+ BinOp::Mod => self.binop_number(l, r, |a, b| Value::Number(a % b)),
2370
+ BinOp::Pow => self.binop_number(l, r, |a, b| Value::Number(a.powf(b))),
2371
+ BinOp::StrictEq => Ok(Value::Bool(l.strict_eq(r))),
2372
+ BinOp::StrictNe => Ok(Value::Bool(!l.strict_eq(r))),
2373
+ // Relational ops compare strings lexicographically when BOTH operands
2374
+ // are strings (JS semantics); otherwise coerce to numbers via binop_number.
2375
+ BinOp::Lt => self.binop_relational(l, r, |o| o.is_lt()),
2376
+ BinOp::Le => self.binop_relational(l, r, |o| o.is_le()),
2377
+ BinOp::Gt => self.binop_relational(l, r, |o| o.is_gt()),
2378
+ BinOp::Ge => self.binop_relational(l, r, |o| o.is_ge()),
2379
+ BinOp::And => Ok(Value::Bool(l.is_truthy() && r.is_truthy())),
2380
+ BinOp::Or => Ok(Value::Bool(l.is_truthy() || r.is_truthy())),
2381
+ BinOp::BitAnd => self.binop_int32(l, r, |a, b| Value::Number((a & b) as f64)),
2382
+ BinOp::BitOr => self.binop_int32(l, r, |a, b| Value::Number((a | b) as f64)),
2383
+ BinOp::BitXor => self.binop_int32(l, r, |a, b| Value::Number((a ^ b) as f64)),
2384
+ // JS shifts mask the count to the low 5 bits; `wrapping_sh*` does exactly
2385
+ // that and never panics (plain `<<`/`>>` panic in debug for count >= 32).
2386
+ BinOp::Shl => {
2387
+ self.binop_int32(l, r, |a, b| Value::Number(a.wrapping_shl(b as u32) as f64))
2388
+ }
2389
+ BinOp::Shr => {
2390
+ self.binop_int32(l, r, |a, b| Value::Number(a.wrapping_shr(b as u32) as f64))
2391
+ }
2392
+ // `>>>` — unsigned (logical) right shift: ToUint32(a) >>> (b & 31).
2393
+ BinOp::UShr => self.binop_int32(l, r, |a, b| {
2394
+ Value::Number((a as u32).wrapping_shr(b as u32) as f64)
2395
+ }),
2396
+ BinOp::In => {
2397
+ let ok = match r {
2398
+ Value::Object(_) => eval_object_has(r, l),
2399
+ Value::Array(arr) => {
2400
+ let key: Arc<str> = match l {
2401
+ Value::String(s) => Arc::clone(s),
2402
+ Value::Number(n) => n.to_string().into(),
2403
+ _ => {
2404
+ return Err(format!(
2405
+ "'in' requires string or number key on array, got {:?}",
2406
+ l
2407
+ ))
2408
+ }
2409
+ };
2410
+ key.as_ref() == "length"
2411
+ || key
2412
+ .parse::<usize>()
2413
+ .ok()
2414
+ .map(|i| i < arr.borrow().len())
2415
+ .unwrap_or(false)
2416
+ }
2417
+ _ => return Err(format!("'in' requires object or array, got {:?}", r)),
2418
+ };
2419
+ Ok(Value::Bool(ok))
2420
+ }
2421
+ // Loose ==/!= : match the VM (vm.rs maps Eq/Ne to strict_eq) so interp == vm ==
2422
+ // compiled. Previously the interpreter alone errored on `==`.
2423
+ BinOp::Eq => Ok(Value::Bool(l.strict_eq(r))),
2424
+ BinOp::Ne => Ok(Value::Bool(!l.strict_eq(r))),
2425
+ }
2426
+ }
2427
+
2428
+ /// Check if a function value is the common numeric sort comparator pattern.
2429
+ /// descending = false: checks for `(a, b) => a - b`
2430
+ /// descending = true: checks for `(a, b) => b - a`
2431
+ fn is_numeric_sort_comparator(f: &Value, descending: bool) -> bool {
2432
+ if let Value::Function {
2433
+ formals,
2434
+ body,
2435
+ rest_param,
2436
+ ..
2437
+ } = f
2438
+ {
2439
+ // Must have exactly 2 simple params, no defaults, no rest
2440
+ if formals.len() != 2 || rest_param.is_some() {
2441
+ return false;
2442
+ }
2443
+ let (param_a, param_b) = match (&formals[0], &formals[1]) {
2444
+ (FunParam::Simple(a), FunParam::Simple(b))
2445
+ if a.default.is_none() && b.default.is_none() =>
2446
+ {
2447
+ (&a.name, &b.name)
2448
+ }
2449
+ _ => return false,
2450
+ };
2451
+
2452
+ // Body must be a return of a - b (or b - a for descending)
2453
+
2454
+ // Check for both Statement::Return and Statement::ExprStmt (arrow implicit return)
2455
+ let expr = match body.as_ref() {
2456
+ Statement::Return { value: Some(e), .. } => e,
2457
+ Statement::ExprStmt { expr: e, .. } => e,
2458
+ _ => return false,
2459
+ };
2460
+
2461
+ // Check for binary subtraction
2462
+ if let Expr::Binary {
2463
+ left,
2464
+ op: BinOp::Sub,
2465
+ right,
2466
+ ..
2467
+ } = expr
2468
+ {
2469
+ // Check left is Ident(a) and right is Ident(b)
2470
+ let (expected_left, expected_right) = if descending {
2471
+ (param_b, param_a) // b - a
2472
+ } else {
2473
+ (param_a, param_b) // a - b
2474
+ };
2475
+
2476
+ if let (
2477
+ Expr::Ident {
2478
+ name: left_name, ..
2479
+ },
2480
+ Expr::Ident {
2481
+ name: right_name, ..
2482
+ },
2483
+ ) = (left.as_ref(), right.as_ref())
2484
+ {
2485
+ return left_name == expected_left && right_name == expected_right;
2486
+ }
2487
+ }
2488
+ }
2489
+ false
2490
+ }
2491
+
2492
+ /// JS ToInt32 coercion. Non-numbers coerce to NaN → 0. Going through `i64`
2493
+ /// (not a direct `as i32`) gives modulo-2³² truncation instead of a saturating
2494
+ /// cast, so out-of-i32-range values (e.g. a `0..2³²` hash) wrap exactly like JS:
2495
+ /// `4294967295 | 0 === -1`, not the saturated `i32::MAX`. Realistic values are
2496
+ /// `< 2⁵³` so they fit `i64` exactly; the two casts stay cheap.
2497
+ fn to_int32(v: &Value) -> i32 {
2498
+ // NaN / ±Infinity → 0 (the `is_finite` guard): `f64 as i64` *saturates* (`+∞ → i64::MAX
2499
+ // → -1`), which is not the JS ToInt32 result. Finite values use the cheap modulo cast.
2500
+ let x = v.as_number().unwrap_or(f64::NAN);
2501
+ if x.is_finite() {
2502
+ x as i64 as i32
2503
+ } else {
2504
+ 0
2505
+ }
2506
+ }
2507
+
2508
+ fn binop_int32<F>(&self, l: &Value, r: &Value, f: F) -> Result<Value, String>
2509
+ where
2510
+ F: FnOnce(i32, i32) -> Value,
2511
+ {
2512
+ Ok(f(Self::to_int32(l), Self::to_int32(r)))
2513
+ }
2514
+
2515
+ /// Numeric binop, coercing each operand to a number the way the VM does
2516
+ /// (`as_number().unwrap_or(NaN)`): non-numbers (Null/Bool/Object/…) become NaN rather
2517
+ /// than erroring. Keeps the interpreter in parity with the VM and Node on out-of-bounds
2518
+ /// reads and other `undefined`-like operands.
2519
+ fn binop_number<F>(&self, l: &Value, r: &Value, f: F) -> Result<Value, String>
2520
+ where
2521
+ F: FnOnce(f64, f64) -> Value,
2522
+ {
2523
+ let a = l.as_number().unwrap_or(f64::NAN);
2524
+ let b = r.as_number().unwrap_or(f64::NAN);
2525
+ Ok(f(a, b))
2526
+ }
2527
+
2528
+ /// Relational comparison (`<` `<=` `>` `>=`). When both operands are strings,
2529
+ /// compare lexicographically; otherwise coerce to numbers. `pred` maps the
2530
+ /// resulting `Ordering` to a bool. A NaN-involved numeric comparison yields no
2531
+ /// ordering and is always `false`, matching JS (`NaN < 5` → false).
2532
+ fn binop_relational<F>(&self, l: &Value, r: &Value, pred: F) -> Result<Value, String>
2533
+ where
2534
+ F: FnOnce(std::cmp::Ordering) -> bool,
2535
+ {
2536
+ let ord = match (l, r) {
2537
+ (Value::String(a), Value::String(b)) => Some(a.as_ref().cmp(b.as_ref())),
2538
+ _ => {
2539
+ let a = l.as_number().unwrap_or(f64::NAN);
2540
+ let b = r.as_number().unwrap_or(f64::NAN);
2541
+ a.partial_cmp(&b)
2542
+ }
2543
+ };
2544
+ Ok(Value::Bool(ord.map(pred).unwrap_or(false)))
2545
+ }
2546
+
2547
+ fn eval_unary(&self, op: UnaryOp, v: &Value) -> Result<Value, String> {
2548
+ match op {
2549
+ UnaryOp::Not => Ok(Value::Bool(!v.is_truthy())),
2550
+ UnaryOp::Neg => match v {
2551
+ Value::Number(n) => Ok(Value::Number(-n)),
2552
+ _ => Err(format!("Cannot negate {:?}", v)),
2553
+ },
2554
+ UnaryOp::Pos => match v {
2555
+ Value::Number(n) => Ok(Value::Number(*n)),
2556
+ _ => Err(format!("Cannot apply unary + to {:?}", v)),
2557
+ },
2558
+ UnaryOp::BitNot => {
2559
+ let n = Self::to_int32(v);
2560
+ Ok(Value::Number((!n) as f64))
2561
+ }
2562
+ UnaryOp::Void => Ok(Value::Null),
2563
+ }
2564
+ }
2565
+
2566
+ /// Optimized callback invocation for array methods.
2567
+ /// Creates a reusable scope that can be updated for each iteration.
2568
+ fn create_callback_scope(
2569
+ &self,
2570
+ f: &Value,
2571
+ ) -> Option<(Rc<RefCell<Scope>>, Arc<[Arc<str>]>, Arc<Statement>)> {
2572
+ if let Value::Function {
2573
+ formals,
2574
+ body,
2575
+ rest_param,
2576
+ ..
2577
+ } = f
2578
+ {
2579
+ if rest_param.is_some() {
2580
+ return None;
2581
+ }
2582
+ for fp in formals.iter() {
2583
+ match fp {
2584
+ FunParam::Simple(tp) if tp.default.is_none() => {}
2585
+ _ => return None,
2586
+ }
2587
+ }
2588
+ let scope = Scope::child(Rc::clone(&self.scope));
2589
+ {
2590
+ let mut s = scope.borrow_mut();
2591
+ for fp in formals.iter() {
2592
+ for n in fp.bound_names() {
2593
+ s.set(n, Value::Null, true);
2594
+ }
2595
+ }
2596
+ }
2597
+ let flat_names: Arc<[Arc<str>]> = Arc::from(
2598
+ formals
2599
+ .iter()
2600
+ .flat_map(|fp| fp.bound_names())
2601
+ .collect::<Vec<_>>(),
2602
+ );
2603
+ return Some((scope, flat_names, Arc::clone(body)));
2604
+ }
2605
+ None
2606
+ }
2607
+
2608
+ /// Fast callback invocation that reuses an existing scope.
2609
+ fn call_with_scope(
2610
+ &self,
2611
+ scope: &Rc<RefCell<Scope>>,
2612
+ params: &[Arc<str>],
2613
+ body: &Statement,
2614
+ args: &[Value],
2615
+ ) -> Result<Value, EvalError> {
2616
+ {
2617
+ let mut s = scope.borrow_mut();
2618
+ for (i, p) in params.iter().enumerate() {
2619
+ let val = args.get(i).cloned().unwrap_or(Value::Null);
2620
+ // Direct assignment - we know these vars exist and are mutable
2621
+ if let Some(existing) = s.vars.get_mut(p.as_ref()) {
2622
+ *existing = val;
2623
+ }
2624
+ }
2625
+ }
2626
+ let mut eval = Evaluator {
2627
+ scope: Rc::clone(scope),
2628
+ module_cache: Rc::clone(&self.module_cache),
2629
+ current_dir: RefCell::new(self.current_dir.borrow().clone()),
2630
+ virtual_builtins: Rc::clone(&self.virtual_builtins),
2631
+ };
2632
+ match eval.eval_statement(body) {
2633
+ Ok(v) => Ok(v),
2634
+ Err(EvalError::Return(v)) => Ok(v),
2635
+ Err(e) => Err(e),
2636
+ }
2637
+ }
2638
+
2639
+ /// Try to evaluate a simple callback expression directly without creating a scope.
2640
+ /// Returns Some(result) for simple patterns like `x => x * 2` or `x => x > 5`.
2641
+ fn eval_simple_callback(&self, f: &Value, args: &[Value]) -> Option<Result<Value, EvalError>> {
2642
+ if let Value::Function {
2643
+ formals,
2644
+ body,
2645
+ rest_param,
2646
+ ..
2647
+ } = f
2648
+ {
2649
+ if formals.len() != 1 || rest_param.is_some() {
2650
+ return None;
2651
+ }
2652
+ let param_name = match &formals[0] {
2653
+ FunParam::Simple(tp) if tp.default.is_none() => &tp.name,
2654
+ _ => return None,
2655
+ };
2656
+ let arg = args.first().cloned().unwrap_or(Value::Null);
2657
+
2658
+ // Get the expression from the body
2659
+ let expr = match body.as_ref() {
2660
+ Statement::Return { value: Some(e), .. } => e,
2661
+ Statement::ExprStmt { expr: e, .. } => e,
2662
+ _ => return None,
2663
+ };
2664
+
2665
+ // Fast path for common patterns
2666
+ match expr {
2667
+ // x * constant or x + constant, etc.
2668
+ Expr::Binary {
2669
+ left, op, right, ..
2670
+ } => {
2671
+ let left_val = self.eval_simple_operand(left, param_name, &arg)?;
2672
+ let right_val = self.eval_simple_operand(right, param_name, &arg)?;
2673
+ Some(
2674
+ self.eval_binop(&left_val, *op, &right_val)
2675
+ .map_err(EvalError::Error),
2676
+ )
2677
+ }
2678
+ // Just return the parameter
2679
+ Expr::Ident { name, .. } if name == param_name => Some(Ok(arg)),
2680
+ // Property access: x.prop
2681
+ Expr::Member {
2682
+ object,
2683
+ prop,
2684
+ optional,
2685
+ ..
2686
+ } => {
2687
+ if let Expr::Ident { name, .. } = object.as_ref() {
2688
+ if name == param_name {
2689
+ return self.eval_simple_member(&arg, prop, *optional);
2690
+ }
2691
+ }
2692
+ None
2693
+ }
2694
+ _ => None,
2695
+ }
2696
+ } else {
2697
+ None
2698
+ }
2699
+ }
2700
+
2701
+ /// Evaluate a simple operand (identifier or literal).
2702
+ fn eval_simple_operand(
2703
+ &self,
2704
+ expr: &Expr,
2705
+ param_name: &Arc<str>,
2706
+ param_val: &Value,
2707
+ ) -> Option<Value> {
2708
+ match expr {
2709
+ Expr::Ident { name, .. } if name == param_name => Some(param_val.clone()),
2710
+ Expr::Literal { value, .. } => match value {
2711
+ Literal::Number(n) => Some(Value::Number(*n)),
2712
+ Literal::String(s) => Some(Value::String(Arc::clone(s))),
2713
+ Literal::Bool(b) => Some(Value::Bool(*b)),
2714
+ Literal::Null => Some(Value::Null),
2715
+ },
2716
+ _ => None,
2717
+ }
2718
+ }
2719
+
2720
+ /// Evaluate simple member access.
2721
+ fn eval_simple_member(
2722
+ &self,
2723
+ obj: &Value,
2724
+ property: &MemberProp,
2725
+ _optional: bool,
2726
+ ) -> Option<Result<Value, EvalError>> {
2727
+ match property {
2728
+ MemberProp::Name { name, .. } => match obj {
2729
+ Value::Object(o) => {
2730
+ let result = o
2731
+ .borrow()
2732
+ .strings
2733
+ .get(name.as_ref())
2734
+ .cloned()
2735
+ .unwrap_or(Value::Null);
2736
+ Some(Ok(result))
2737
+ }
2738
+ Value::Array(arr) if name.as_ref() == "length" => {
2739
+ Some(Ok(Value::Number(arr.borrow().len() as f64)))
2740
+ }
2741
+ _ => None,
2742
+ },
2743
+ _ => None,
2744
+ }
2745
+ }
2746
+
2747
+ /// Host `new`: `__construct` on objects; otherwise same callables as `call_func`, else null.
2748
+ fn construct_value(&self, callee: &Value, args: &[Value]) -> Result<Value, EvalError> {
2749
+ if let Value::Object(o) = callee {
2750
+ if let Some(ctor) = o
2751
+ .borrow()
2752
+ .strings
2753
+ .get("__construct")
2754
+ .cloned()
2755
+ {
2756
+ return self.call_func(&ctor, args);
2757
+ }
2758
+ }
2759
+ match callee {
2760
+ Value::Native(_) | Value::Function { .. } | Value::CoreFn(_) => {
2761
+ self.call_func(callee, args)
2762
+ }
2763
+ #[cfg(feature = "http")]
2764
+ Value::PromiseConstructor | Value::Serve | Value::BoundPromiseMethod(_, _) => {
2765
+ self.call_func(callee, args)
2766
+ }
2767
+ #[cfg(feature = "timers")]
2768
+ Value::TimerBuiltin(_) => self.call_func(callee, args),
2769
+ Value::OpaqueMethod(_, _) => self.call_func(callee, args),
2770
+ _ => Ok(Value::Null),
2771
+ }
2772
+ }
2773
+
2774
+ fn call_func(&self, f: &Value, args: &[Value]) -> Result<Value, EvalError> {
2775
+ match f {
2776
+ Value::Object(o) => {
2777
+ if let Some(call) = o.borrow().strings.get("__call").cloned() {
2778
+ return self.call_func(&call, args);
2779
+ }
2780
+ Err(EvalError::Error("Not a function".to_string()))
2781
+ }
2782
+ Value::Native(native_fn) => native_fn(args).map_err(EvalError::Error),
2783
+ #[cfg(feature = "http")]
2784
+ Value::PromiseResolver(r) => {
2785
+ let value = args.first().cloned().unwrap_or(Value::Null);
2786
+ let (val, is_fulfilled, reactions) =
2787
+ crate::promise::settle_promise(r, value, r.is_resolve)
2788
+ .map_err(EvalError::Error)?;
2789
+ for reaction in reactions {
2790
+ match reaction {
2791
+ crate::promise::Reaction::Then(
2792
+ on_fulfilled,
2793
+ on_rejected,
2794
+ ref resolve,
2795
+ ref reject,
2796
+ ) => {
2797
+ let handler_result = if is_fulfilled {
2798
+ if let Some(ref h) = on_fulfilled {
2799
+ self.call_func(h, &[val.clone()])
2800
+ } else {
2801
+ Ok(val.clone())
2802
+ }
2803
+ } else {
2804
+ if let Some(ref h) = on_rejected {
2805
+ self.call_func(h, &[val.clone()])
2806
+ } else {
2807
+ Err(EvalError::Throw(val.clone()))
2808
+ }
2809
+ };
2810
+ match handler_result {
2811
+ Ok(v) => {
2812
+ crate::promise::settle_promise(resolve, v, true)
2813
+ .map_err(EvalError::Error)?;
2814
+ }
2815
+ Err(EvalError::Throw(v)) => {
2816
+ crate::promise::settle_promise(reject, v, false)
2817
+ .map_err(EvalError::Error)?;
2818
+ }
2819
+ Err(e) => return Err(e),
2820
+ }
2821
+ }
2822
+ crate::promise::Reaction::Finally(on_finally, ref resolve, ref reject) => {
2823
+ let _ = self.call_func(&on_finally, &[]);
2824
+ if is_fulfilled {
2825
+ crate::promise::settle_promise(resolve, val.clone(), true)
2826
+ .map_err(EvalError::Error)?;
2827
+ } else {
2828
+ crate::promise::settle_promise(reject, val.clone(), false)
2829
+ .map_err(EvalError::Error)?;
2830
+ }
2831
+ }
2832
+ }
2833
+ }
2834
+ Ok(Value::Null)
2835
+ }
2836
+ #[cfg(feature = "http")]
2837
+ Value::PromiseConstructor => {
2838
+ let executor = args.first().ok_or_else(|| {
2839
+ EvalError::Error("Promise requires an executor function".to_string())
2840
+ })?;
2841
+ let (promise, resolve, reject) = crate::promise::create_promise();
2842
+ self.call_func(executor, &[resolve, reject])?;
2843
+ Ok(promise)
2844
+ }
2845
+ #[cfg(feature = "http")]
2846
+ Value::Serve => self.run_http_server(args),
2847
+ Value::CoreFn(f) => {
2848
+ let ca: Result<Vec<tishlang_core::Value>, String> = args
2849
+ .iter()
2850
+ .map(crate::value_convert::eval_to_core)
2851
+ .collect();
2852
+ let ca = ca.map_err(EvalError::Error)?;
2853
+ Ok(crate::value_convert::core_to_eval(f.call(&ca)))
2854
+ }
2855
+ #[cfg(feature = "regex")]
2856
+ Value::RegExp(_) => Err(EvalError::Error("RegExp is not callable".to_string())),
2857
+ #[cfg(feature = "http")]
2858
+ Value::BoundPromiseMethod(promise_ref, method) => {
2859
+ self.run_promise_method(promise_ref, method.as_ref(), args)
2860
+ }
2861
+ #[cfg(feature = "timers")]
2862
+ Value::TimerBuiltin(name) => self.run_timer_builtin(name.as_ref(), args),
2863
+ Value::OpaqueMethod(opaque, method_name) => {
2864
+ let method = opaque.get_method(method_name.as_ref()).ok_or_else(|| {
2865
+ EvalError::Error(format!(
2866
+ "Method {} not found on {}",
2867
+ method_name,
2868
+ opaque.type_name()
2869
+ ))
2870
+ })?;
2871
+ let core_args: Result<Vec<tishlang_core::Value>, String> = args
2872
+ .iter()
2873
+ .map(crate::value_convert::eval_to_core)
2874
+ .collect();
2875
+ let core_args = core_args.map_err(EvalError::Error)?;
2876
+ let result = method.call(&core_args);
2877
+ Ok(crate::value_convert::core_to_eval(result))
2878
+ }
2879
+ Value::Function {
2880
+ formals,
2881
+ rest_param,
2882
+ body,
2883
+ env,
2884
+ } => {
2885
+ // A real closure: the call frame's parent is the function's DEFINING scope (env),
2886
+ // not the call site — so free variables resolve lexically.
2887
+ let scope = Scope::child(Rc::clone(env));
2888
+ // The call-frame evaluator, built up front so default-parameter expressions
2889
+ // evaluate in this *call* scope — where earlier params are already bound (so a
2890
+ // default like `b = a + 1` can see `a`) and free vars still resolve lexically
2891
+ // through the closure's `env`. Evaluating against `self.scope` (the call *site*)
2892
+ // would see neither, matching the bytecode VM's ArgMissing prologue, which runs
2893
+ // defaults in the frame after the supplied args are bound.
2894
+ let mut eval = Evaluator {
2895
+ scope: Rc::clone(&scope),
2896
+ module_cache: Rc::clone(&self.module_cache),
2897
+ current_dir: RefCell::new(self.current_dir.borrow().clone()),
2898
+ virtual_builtins: Rc::clone(&self.virtual_builtins),
2899
+ };
2900
+ {
2901
+ let mut s = scope.borrow_mut();
2902
+ for (i, formal) in formals.iter().enumerate() {
2903
+ let val = match args.get(i) {
2904
+ Some(v) => v.clone(),
2905
+ None => {
2906
+ let def = match formal {
2907
+ FunParam::Simple(tp) => tp.default.as_ref(),
2908
+ FunParam::Destructure { default, .. } => default.as_ref(),
2909
+ };
2910
+ if let Some(default_expr) = def {
2911
+ drop(s);
2912
+ let default_val = eval.eval_expr(default_expr)?;
2913
+ s = scope.borrow_mut();
2914
+ default_val
2915
+ } else {
2916
+ Value::Null
2917
+ }
2918
+ }
2919
+ };
2920
+ match formal {
2921
+ FunParam::Simple(tp) => {
2922
+ s.set(Arc::clone(&tp.name), val, true);
2923
+ }
2924
+ FunParam::Destructure { pattern, .. } => {
2925
+ drop(s);
2926
+ Self::bind_destruct_pattern_scoped(&scope, pattern, &val, true)?;
2927
+ s = scope.borrow_mut();
2928
+ }
2929
+ }
2930
+ }
2931
+ if let Some(ref rest_name) = rest_param {
2932
+ let rest_vals: Vec<Value> =
2933
+ args.iter().skip(formals.len()).cloned().collect();
2934
+ s.set(
2935
+ Arc::clone(rest_name),
2936
+ Value::Array(Rc::new(RefCell::new(rest_vals))),
2937
+ true,
2938
+ );
2939
+ }
2940
+ }
2941
+ // Grow the native stack on demand so deep (non-tail) recursion doesn't overflow
2942
+ // the OS thread stack — same idea as the bytecode VM's `stacker::maybe_grow` around
2943
+ // recursive `run_chunk` (vm.rs:1138). Without it the tree-walker aborts (SIGABRT,
2944
+ // "stack overflow") on deep recursion, which the cross-backend parity run surfaced
2945
+ // on `recursion_stress`. This is the recursion ACCUMULATOR (every user-function call
2946
+ // lands here); the per-element HOF path (`call_with_scope`) is deliberately NOT
2947
+ // guarded — it never nests deeply, so it avoids the per-call check on hot map/filter.
2948
+ //
2949
+ // Red zone = 1 MiB, NOT the VM's 128 KiB: one tree-walker recursion level spans a
2950
+ // long eval chain (eval_statement → eval_expr(if) → eval_expr(binary) → eval_expr(call)
2951
+ // → eval_call_args → call_func → …), each frame large — far more per level than the
2952
+ // VM's single `run_chunk` re-entry. 128 KiB is smaller than one level's chain, so the
2953
+ // stack overflows BETWEEN checks; 1 MiB comfortably covers a level (verified to depth
2954
+ // 20000 in both debug and release). 16 MiB segments keep grow frequency low.
2955
+ let body_result = {
2956
+ #[cfg(not(target_arch = "wasm32"))]
2957
+ {
2958
+ stacker::maybe_grow(1024 * 1024, 16 * 1024 * 1024, || {
2959
+ eval.eval_statement(body)
2960
+ })
2961
+ }
2962
+ #[cfg(target_arch = "wasm32")]
2963
+ {
2964
+ eval.eval_statement(body)
2965
+ }
2966
+ };
2967
+ match body_result {
2968
+ Ok(v) => Ok(v),
2969
+ Err(EvalError::Return(v)) => Ok(v),
2970
+ Err(EvalError::Throw(v)) => Err(EvalError::Throw(v)),
2971
+ Err(EvalError::Error(s)) => Err(EvalError::Error(s)),
2972
+ Err(EvalError::Break) => {
2973
+ Err(EvalError::Error("break outside loop".to_string()))
2974
+ }
2975
+ Err(EvalError::Continue) => {
2976
+ Err(EvalError::Error("continue outside loop".to_string()))
2977
+ }
2978
+ }
2979
+ }
2980
+ _ => Err(EvalError::Error("Not a function".to_string())),
2981
+ }
2982
+ }
2983
+
2984
+ #[cfg(feature = "http")]
2985
+ fn run_promise_method(
2986
+ &self,
2987
+ promise_ref: &crate::promise::PromiseRef,
2988
+ method: &str,
2989
+ args: &[Value],
2990
+ ) -> Result<Value, EvalError> {
2991
+ match method {
2992
+ "then" => {
2993
+ self.run_promise_then_core(promise_ref, args.first().cloned(), args.get(1).cloned())
2994
+ }
2995
+ "catch" => self.run_promise_then_core(promise_ref, None, args.first().cloned()),
2996
+ "finally" => self.run_promise_finally(promise_ref, args.first().cloned()),
2997
+ _ => Err(EvalError::Error(format!(
2998
+ "Unknown promise method: {}",
2999
+ method
3000
+ ))),
3001
+ }
3002
+ }
3003
+
3004
+ #[cfg(feature = "http")]
3005
+ fn run_promise_finally(
3006
+ &self,
3007
+ promise_ref: &crate::promise::PromiseRef,
3008
+ on_finally: Option<Value>,
3009
+ ) -> Result<Value, EvalError> {
3010
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
3011
+ let (resolve, reject) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
3012
+ let state = &promise_ref.state;
3013
+ {
3014
+ let s = state.borrow();
3015
+ match &*s {
3016
+ crate::promise::PromiseState::Fulfilled(v) => {
3017
+ let v = v.clone();
3018
+ drop(s);
3019
+ if let Some(ref f) = on_finally {
3020
+ let _ = self.call_func(f, &[]);
3021
+ }
3022
+ crate::promise::settle_promise(&resolve, v, true).map_err(EvalError::Error)?;
3023
+ }
3024
+ crate::promise::PromiseState::Rejected(v) => {
3025
+ let v = v.clone();
3026
+ drop(s);
3027
+ if let Some(ref f) = on_finally {
3028
+ let _ = self.call_func(f, &[]);
3029
+ }
3030
+ crate::promise::settle_promise(&reject, v, false).map_err(EvalError::Error)?;
3031
+ }
3032
+ crate::promise::PromiseState::Pending { .. } => {
3033
+ let reaction = if let Some(ref f) = on_finally {
3034
+ crate::promise::Reaction::Finally(f.clone(), resolve, reject)
3035
+ } else {
3036
+ crate::promise::Reaction::Then(None, None, resolve, reject)
3037
+ };
3038
+ crate::promise::add_reaction(state, reaction);
3039
+ }
3040
+ }
3041
+ }
3042
+ Ok(promise)
3043
+ }
3044
+
3045
+ #[cfg(feature = "http")]
3046
+ fn run_promise_then_core(
3047
+ &self,
3048
+ promise_ref: &crate::promise::PromiseRef,
3049
+ on_fulfilled: Option<Value>,
3050
+ on_rejected: Option<Value>,
3051
+ ) -> Result<Value, EvalError> {
3052
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
3053
+ let (resolve, reject) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
3054
+ let state = &promise_ref.state;
3055
+ {
3056
+ let s = state.borrow();
3057
+ match &*s {
3058
+ crate::promise::PromiseState::Fulfilled(v) => {
3059
+ let v = v.clone();
3060
+ drop(s);
3061
+ let result = if let Some(ref h) = on_fulfilled {
3062
+ self.call_func(h, &[v])
3063
+ } else {
3064
+ Ok(v)
3065
+ };
3066
+ match result {
3067
+ Ok(val) => {
3068
+ crate::promise::settle_promise(&resolve, val, true)
3069
+ .map_err(EvalError::Error)?;
3070
+ }
3071
+ Err(EvalError::Throw(val)) => {
3072
+ crate::promise::settle_promise(&reject, val, false)
3073
+ .map_err(EvalError::Error)?;
3074
+ }
3075
+ Err(e) => return Err(e),
3076
+ }
3077
+ }
3078
+ crate::promise::PromiseState::Rejected(v) => {
3079
+ let v = v.clone();
3080
+ drop(s);
3081
+ let result = if let Some(ref h) = on_rejected {
3082
+ self.call_func(h, &[v.clone()])
3083
+ } else {
3084
+ Err(EvalError::Throw(v))
3085
+ };
3086
+ match result {
3087
+ Ok(val) => {
3088
+ crate::promise::settle_promise(&resolve, val, true)
3089
+ .map_err(EvalError::Error)?;
3090
+ }
3091
+ Err(EvalError::Throw(val)) => {
3092
+ crate::promise::settle_promise(&reject, val, false)
3093
+ .map_err(EvalError::Error)?;
3094
+ }
3095
+ Err(e) => return Err(e),
3096
+ }
3097
+ }
3098
+ crate::promise::PromiseState::Pending { .. } => {
3099
+ crate::promise::add_reaction(
3100
+ state,
3101
+ crate::promise::Reaction::Then(
3102
+ on_fulfilled,
3103
+ on_rejected,
3104
+ resolve.clone(),
3105
+ reject.clone(),
3106
+ ),
3107
+ );
3108
+ }
3109
+ }
3110
+ }
3111
+ Ok(promise)
3112
+ }
3113
+
3114
+ #[cfg(feature = "timers")]
3115
+ fn run_timer_builtin(&self, name: &str, args: &[Value]) -> Result<Value, EvalError> {
3116
+ let callback = args
3117
+ .first()
3118
+ .ok_or_else(|| EvalError::Error(format!("{} requires a callback", name)))?
3119
+ .clone();
3120
+ let delay_ms = args
3121
+ .get(1)
3122
+ .and_then(|v| v.as_number())
3123
+ .unwrap_or(0.0)
3124
+ .max(0.0) as u64;
3125
+ let extra_args: Vec<Value> = args.iter().skip(2).cloned().collect();
3126
+
3127
+ let id = match name {
3128
+ "setTimeout" => crate::timers::setTimeout(callback, extra_args, delay_ms),
3129
+ "setInterval" => crate::timers::setInterval(callback, extra_args, delay_ms),
3130
+ _ => return Err(EvalError::Error(format!("Unknown timer: {}", name))),
3131
+ };
3132
+ Ok(Value::Number(id as f64))
3133
+ }
3134
+
3135
+ #[cfg(feature = "timers")]
3136
+ fn clear_timeout_native(args: &[Value]) -> Result<Value, String> {
3137
+ if let Some(Value::Number(n)) = args.first() {
3138
+ crate::timers::clearTimer(*n as u64);
3139
+ }
3140
+ Ok(Value::Null)
3141
+ }
3142
+
3143
+ #[cfg(feature = "timers")]
3144
+ fn clear_interval_native(args: &[Value]) -> Result<Value, String> {
3145
+ if let Some(Value::Number(n)) = args.first() {
3146
+ crate::timers::clearTimer(*n as u64);
3147
+ }
3148
+ Ok(Value::Null)
3149
+ }
3150
+
3151
+ /// Run all due timer callbacks. Called after the script completes so setTimeout/setInterval
3152
+ /// callbacks run without blocking the main script. Loops until no timers are due.
3153
+ #[cfg(feature = "timers")]
3154
+ pub fn run_timer_phase(&mut self) -> Result<(), String> {
3155
+ const MAX_ITERATIONS: u32 = 1_000_000; // avoid infinite loop if setInterval never cleared
3156
+ let mut iterations = 0;
3157
+ while crate::timers::has_pending_timers() && iterations < MAX_ITERATIONS {
3158
+ iterations += 1;
3159
+ let due = crate::timers::take_due_timers();
3160
+ if due.is_empty() {
3161
+ // None due yet; sleep until next timer
3162
+ let next = crate::timers::next_due_instant();
3163
+ if let Some(instant) = next {
3164
+ let now = std::time::Instant::now();
3165
+ if instant > now {
3166
+ std::thread::sleep(instant.duration_since(now));
3167
+ }
3168
+ }
3169
+ continue;
3170
+ }
3171
+ for (id, callback, args, interval_ms) in due {
3172
+ self.call_func(&callback, &args).map_err(|e| match e {
3173
+ EvalError::Error(s) => s,
3174
+ EvalError::Throw(v) => v.to_string(),
3175
+ _ => "timer callback error".to_string(),
3176
+ })?;
3177
+ if interval_ms > 0 {
3178
+ crate::timers::re_register_interval(id, callback, args, interval_ms);
3179
+ }
3180
+ }
3181
+ }
3182
+ Ok(())
3183
+ }
3184
+
3185
+ #[cfg(feature = "http")]
3186
+ fn run_http_server(&self, args: &[Value]) -> Result<Value, EvalError> {
3187
+ use std::io::Write;
3188
+
3189
+ let port = match args.first() {
3190
+ Some(Value::Number(n)) => *n as u16,
3191
+ _ => return Err(EvalError::Error("serve requires a port number".to_string())),
3192
+ };
3193
+
3194
+ let max_requests: Option<usize> = args.get(2).and_then(|v| match v {
3195
+ Value::Number(n) if *n >= 1.0 => Some(*n as usize),
3196
+ _ => None,
3197
+ });
3198
+
3199
+ let handler = match args.get(1) {
3200
+ Some(f @ Value::Function { .. }) | Some(f @ Value::Native(_)) => f.clone(),
3201
+ _ => {
3202
+ return Err(EvalError::Error(
3203
+ "serve requires a handler function".to_string(),
3204
+ ))
3205
+ }
3206
+ };
3207
+
3208
+ let server = crate::http::create_server(port).map_err(EvalError::Error)?;
3209
+ println!("Server listening on http://0.0.0.0:{}", port);
3210
+
3211
+ if max_requests == Some(1) {
3212
+ std::thread::spawn(move || {
3213
+ std::thread::sleep(std::time::Duration::from_millis(50));
3214
+ if let Ok(mut stream) = std::net::TcpStream::connect(format!("127.0.0.1:{}", port))
3215
+ {
3216
+ let _ = stream.write_all(
3217
+ b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n",
3218
+ );
3219
+ let _ = stream.shutdown(std::net::Shutdown::Write);
3220
+ }
3221
+ });
3222
+ }
3223
+
3224
+ for (count, mut request) in server.incoming_requests().enumerate() {
3225
+ let req_value = crate::http::request_to_value(&mut request);
3226
+
3227
+ let response_value = match self.call_func(&handler, &[req_value]) {
3228
+ Ok(v) => v,
3229
+ Err(EvalError::Throw(v)) => {
3230
+ let mut err_obj: PropMap = PropMap::with_capacity(2);
3231
+ err_obj.insert(Arc::from("status"), Value::Number(500.0));
3232
+ err_obj.insert(Arc::from("body"), Value::String(v.to_string().into()));
3233
+ Value::object(err_obj)
3234
+ }
3235
+ Err(e) => {
3236
+ let mut err_obj: PropMap = PropMap::with_capacity(2);
3237
+ err_obj.insert(Arc::from("status"), Value::Number(500.0));
3238
+ err_obj.insert(Arc::from("body"), Value::String(e.to_string().into()));
3239
+ Value::object(err_obj)
3240
+ }
3241
+ };
3242
+
3243
+ if let Some((status, headers, file_path)) =
3244
+ crate::http::extract_file_from_response(&response_value)
3245
+ {
3246
+ crate::http::send_file_response(request, status, headers, file_path);
3247
+ } else {
3248
+ let (status, headers, body) = crate::http::value_to_response(&response_value);
3249
+ crate::http::send_response(request, status, headers, body);
3250
+ }
3251
+ if max_requests.map(|m| count + 1 >= m).unwrap_or(false) {
3252
+ break;
3253
+ }
3254
+ }
3255
+
3256
+ Ok(Value::Null)
3257
+ }
3258
+
3259
+ fn eval_call_args(&self, args: &[tishlang_ast::CallArg]) -> Result<Vec<Value>, EvalError> {
3260
+ let mut result = Vec::with_capacity(args.len());
3261
+ for arg in args {
3262
+ match arg {
3263
+ tishlang_ast::CallArg::Expr(e) => {
3264
+ result.push(self.eval_expr(e)?);
3265
+ }
3266
+ tishlang_ast::CallArg::Spread(e) => {
3267
+ let spread_val = self.eval_expr(e)?;
3268
+ if let Value::Array(arr) = &spread_val {
3269
+ result.extend(arr.borrow().iter().cloned());
3270
+ } else if let Some(items) = self.drain_eval_iterator(&spread_val) {
3271
+ // Spread a Map/Set iterator into call args (`f(...m.values())`).
3272
+ result.extend(items);
3273
+ }
3274
+ }
3275
+ }
3276
+ }
3277
+ Ok(result)
3278
+ }
3279
+
3280
+ fn bind_destruct_pattern_scoped(
3281
+ scope: &Rc<RefCell<Scope>>,
3282
+ pattern: &tishlang_ast::DestructPattern,
3283
+ value: &Value,
3284
+ mutable: bool,
3285
+ ) -> Result<(), EvalError> {
3286
+ match pattern {
3287
+ tishlang_ast::DestructPattern::Array(elements) => {
3288
+ let arr = match value {
3289
+ Value::Array(a) => a.borrow().clone(),
3290
+ _ => {
3291
+ return Err(EvalError::Error(
3292
+ "Cannot destructure non-array value".to_string(),
3293
+ ))
3294
+ }
3295
+ };
3296
+
3297
+ for (i, elem) in elements.iter().enumerate() {
3298
+ if let Some(el) = elem {
3299
+ match el {
3300
+ tishlang_ast::DestructElement::Ident(name, _) => {
3301
+ let val = arr.get(i).cloned().unwrap_or(Value::Null);
3302
+ scope.borrow_mut().set(Arc::clone(name), val, mutable);
3303
+ }
3304
+ tishlang_ast::DestructElement::Pattern(nested) => {
3305
+ let val = arr.get(i).cloned().unwrap_or(Value::Null);
3306
+ Self::bind_destruct_pattern_scoped(scope, nested, &val, mutable)?;
3307
+ }
3308
+ tishlang_ast::DestructElement::Rest(name, _) => {
3309
+ let rest: Vec<Value> = arr.iter().skip(i).cloned().collect();
3310
+ scope.borrow_mut().set(
3311
+ Arc::clone(name),
3312
+ Value::Array(Rc::new(RefCell::new(rest))),
3313
+ mutable,
3314
+ );
3315
+ break;
3316
+ }
3317
+ }
3318
+ }
3319
+ }
3320
+ }
3321
+ tishlang_ast::DestructPattern::Object(props) => {
3322
+ let obj = match value {
3323
+ Value::Object(o) => o.borrow().clone(),
3324
+ _ => {
3325
+ return Err(EvalError::Error(
3326
+ "Cannot destructure non-object value".to_string(),
3327
+ ))
3328
+ }
3329
+ };
3330
+
3331
+ for prop in props {
3332
+ let val = obj
3333
+ .strings
3334
+ .get(prop.key.as_ref())
3335
+ .cloned()
3336
+ .unwrap_or(Value::Null);
3337
+ match &prop.value {
3338
+ tishlang_ast::DestructElement::Ident(name, _) => {
3339
+ scope.borrow_mut().set(Arc::clone(name), val, mutable);
3340
+ }
3341
+ tishlang_ast::DestructElement::Pattern(nested) => {
3342
+ Self::bind_destruct_pattern_scoped(scope, nested, &val, mutable)?;
3343
+ }
3344
+ tishlang_ast::DestructElement::Rest(_, _) => {
3345
+ return Err(EvalError::Error(
3346
+ "Rest not supported in object destructuring".to_string(),
3347
+ ));
3348
+ }
3349
+ }
3350
+ }
3351
+ }
3352
+ }
3353
+ Ok(())
3354
+ }
3355
+
3356
+ fn bind_destruct_pattern(
3357
+ &mut self,
3358
+ pattern: &tishlang_ast::DestructPattern,
3359
+ value: &Value,
3360
+ mutable: bool,
3361
+ ) -> Result<(), EvalError> {
3362
+ Self::bind_destruct_pattern_scoped(&self.scope, pattern, value, mutable)
3363
+ }
3364
+
3365
+ /// `String.prototype.lastIndexOf` (interpreter). Kept as a helper so dispatch cannot fall
3366
+ /// through to [`Self::get_prop`] + [`Self::call_func`] for string receivers.
3367
+ fn string_last_index_of_eval(arg_vals: &[Value], receiver: &Arc<str>) -> Value {
3368
+ let search = match arg_vals.first() {
3369
+ Some(Value::String(ss)) => ss.as_ref(),
3370
+ _ => return Value::Number(-1.0),
3371
+ };
3372
+ let position_core: tishlang_core::Value = match arg_vals.get(1) {
3373
+ None => tishlang_core::Value::Number(f64::INFINITY),
3374
+ Some(Value::Null) => tishlang_core::Value::Null,
3375
+ Some(Value::Number(n)) => tishlang_core::Value::Number(*n),
3376
+ Some(Value::Bool(b)) => tishlang_core::Value::Bool(*b),
3377
+ Some(_) => tishlang_core::Value::Number(0.0),
3378
+ };
3379
+ let out =
3380
+ tishlang_builtins::string::last_index_of_str(receiver.as_ref(), search, &position_core);
3381
+ match out {
3382
+ tishlang_core::Value::Number(n) => Value::Number(n),
3383
+ _ => Value::Number(-1.0),
3384
+ }
3385
+ }
3386
+
3387
+ /// Drain a JS iterator object — one whose `next()` is a bridged core fn (`CoreFn`)
3388
+ /// returning `{ value, done }`, e.g. a Map/Set iterator from `.values()` / `.keys()` /
3389
+ /// `.entries()` — into a `Vec` by calling `next()` until `done`. Returns `None` when `v`
3390
+ /// is not such an object. Shared by `for…of` and spread so both treat iterators like JS.
3391
+ fn drain_eval_iterator(&self, v: &Value) -> Option<Vec<Value>> {
3392
+ if !matches!(v, Value::Object(_)) {
3393
+ return None;
3394
+ }
3395
+ // Fast path: tish's Map/Set iterators expose `__drain__`, returning all remaining items as
3396
+ // one array — skips the per-element bridge + `{ value, done }` alloc of the generic loop.
3397
+ if let Ok(Value::CoreFn(drain)) = self.get_prop(v, "__drain__") {
3398
+ if let Value::Array(arr) = crate::value_convert::core_to_eval(drain.call(&[])) {
3399
+ return Some(arr.borrow().clone());
3400
+ }
3401
+ }
3402
+ let Ok(Value::CoreFn(next)) = self.get_prop(v, "next") else {
3403
+ return None;
3404
+ };
3405
+ let mut out = Vec::new();
3406
+ loop {
3407
+ let res = crate::value_convert::core_to_eval(next.call(&[]));
3408
+ let done = self
3409
+ .get_prop(&res, "done")
3410
+ .map(|x| x.is_truthy())
3411
+ .unwrap_or(true);
3412
+ if done {
3413
+ break;
3414
+ }
3415
+ out.push(self.get_prop(&res, "value").unwrap_or(Value::Null));
3416
+ }
3417
+ Some(out)
3418
+ }
3419
+
3420
+ fn get_prop(&self, obj: &Value, key: &str) -> Result<Value, String> {
3421
+ match obj {
3422
+ Value::Object(map) => {
3423
+ // `Set`/`Map` instances expose a computed `.size` via a hidden `SizeProbe` opaque
3424
+ // (shared, not copied, across the value bridge — so it reflects the live store).
3425
+ if key == "size" {
3426
+ if let Some(Value::Opaque(op)) =
3427
+ map.borrow().strings.get(tishlang_builtins::collections::SIZE_SLOT)
3428
+ {
3429
+ if let Some(n) = tishlang_builtins::collections::size_probe_len(op.as_ref()) {
3430
+ return Ok(Value::Number(n));
3431
+ }
3432
+ }
3433
+ }
3434
+ Ok(map.borrow().strings.get(key).cloned().unwrap_or(Value::Null))
3435
+ }
3436
+ Value::Array(arr) => {
3437
+ if key == "length" {
3438
+ Ok(Value::Number(arr.borrow().len() as f64))
3439
+ } else if let Ok(idx) = key.parse::<usize>() {
3440
+ Ok(arr.borrow().get(idx).cloned().unwrap_or(Value::Null))
3441
+ } else {
3442
+ Ok(Value::Null)
3443
+ }
3444
+ }
3445
+ Value::String(s) => {
3446
+ if key == "length" {
3447
+ Ok(Value::Number(s.chars().count() as f64))
3448
+ } else {
3449
+ Ok(Value::Null)
3450
+ }
3451
+ }
3452
+ #[cfg(feature = "http")]
3453
+ Value::Promise(promise_ref) => match key {
3454
+ "then" => Ok(Value::BoundPromiseMethod(
3455
+ promise_ref.clone(),
3456
+ Arc::from("then"),
3457
+ )),
3458
+ "catch" => Ok(Value::BoundPromiseMethod(
3459
+ promise_ref.clone(),
3460
+ Arc::from("catch"),
3461
+ )),
3462
+ "finally" => Ok(Value::BoundPromiseMethod(
3463
+ promise_ref.clone(),
3464
+ Arc::from("finally"),
3465
+ )),
3466
+ _ => Ok(Value::Null),
3467
+ },
3468
+ #[cfg(feature = "http")]
3469
+ Value::CorePromise(_) => Ok(Value::Null),
3470
+ #[cfg(feature = "http")]
3471
+ Value::PromiseConstructor => match key {
3472
+ "resolve" => Ok(Value::Native(Self::promise_resolve)),
3473
+ "reject" => Ok(Value::Native(Self::promise_reject)),
3474
+ "all" => Ok(Value::Native(Self::promise_all)),
3475
+ "race" => Ok(Value::Native(Self::promise_race)),
3476
+ "any" => Ok(Value::Native(Self::promise_any)),
3477
+ "allSettled" => Ok(Value::Native(Self::promise_all_settled)),
3478
+ "spawn" => Ok(Value::Native(Self::promise_spawn_interp)),
3479
+ _ => Ok(Value::Null),
3480
+ },
3481
+ Value::Opaque(o) => {
3482
+ if o.get_method(key).is_some() {
3483
+ Ok(Value::OpaqueMethod(Arc::clone(o), Arc::from(key)))
3484
+ } else {
3485
+ Ok(Value::Null)
3486
+ }
3487
+ }
3488
+ #[cfg(feature = "regex")]
3489
+ Value::RegExp(re) => {
3490
+ let re = re.borrow();
3491
+ match key {
3492
+ "source" => Ok(Value::String(re.source.clone().into())),
3493
+ "flags" => Ok(Value::String(re.flags_string().into())),
3494
+ "lastIndex" => Ok(Value::Number(re.last_index as f64)),
3495
+ "global" => Ok(Value::Bool(re.flags.global)),
3496
+ "ignoreCase" => Ok(Value::Bool(re.flags.ignore_case)),
3497
+ "multiline" => Ok(Value::Bool(re.flags.multiline)),
3498
+ "dotAll" => Ok(Value::Bool(re.flags.dot_all)),
3499
+ "unicode" => Ok(Value::Bool(re.flags.unicode)),
3500
+ "sticky" => Ok(Value::Bool(re.flags.sticky)),
3501
+ _ => Ok(Value::Null),
3502
+ }
3503
+ }
3504
+ _ => Ok(Value::Null),
3505
+ }
3506
+ }
3507
+
3508
+ fn get_index(&self, obj: &Value, index: &Value) -> Result<Value, String> {
3509
+ match obj {
3510
+ Value::Array(arr) => {
3511
+ let idx = match index {
3512
+ Value::Number(n) => *n as usize,
3513
+ _ => return Ok(Value::Null),
3514
+ };
3515
+ Ok(arr.borrow().get(idx).cloned().unwrap_or(Value::Null))
3516
+ }
3517
+ // `str[i]` returns the character at index `i` (issue #17). The VM already does
3518
+ // this; the interpreter previously fell through to `null`, a silent divergence.
3519
+ // Out-of-bounds / negative / non-integer indices yield tish's nullish value.
3520
+ Value::String(s) => {
3521
+ let idx = match index {
3522
+ Value::Number(n) if *n >= 0.0 && n.fract() == 0.0 => *n as usize,
3523
+ _ => return Ok(Value::Null),
3524
+ };
3525
+ Ok(s
3526
+ .chars()
3527
+ .nth(idx)
3528
+ .map(|c| Value::String(c.to_string().into()))
3529
+ .unwrap_or(Value::Null))
3530
+ }
3531
+ Value::Object(_) => Ok(eval_object_get(obj, index).unwrap_or(Value::Null)),
3532
+ #[cfg(feature = "http")]
3533
+ Value::Promise(_) | Value::CorePromise(_) => {
3534
+ let key = match index {
3535
+ Value::String(s) => s.as_ref(),
3536
+ _ => return Ok(Value::Null),
3537
+ };
3538
+ self.get_prop(obj, key)
3539
+ }
3540
+ _ => Ok(Value::Null),
3541
+ }
3542
+ }
3543
+
3544
+ fn json_parse(s: &str) -> Value {
3545
+ let s = s.trim();
3546
+ if s.is_empty() {
3547
+ return Value::Null;
3548
+ }
3549
+ match Self::json_parse_str(s) {
3550
+ Ok(v) => v,
3551
+ Err(()) => Value::Null,
3552
+ }
3553
+ }
3554
+
3555
+ fn json_parse_str(s: &str) -> Result<Value, ()> {
3556
+ let s = s.trim();
3557
+ if s.is_empty() {
3558
+ return Err(());
3559
+ }
3560
+ if s == "null" {
3561
+ return Ok(Value::Null);
3562
+ }
3563
+ if s == "true" {
3564
+ return Ok(Value::Bool(true));
3565
+ }
3566
+ if s == "false" {
3567
+ return Ok(Value::Bool(false));
3568
+ }
3569
+ if s.starts_with('"') {
3570
+ return Self::json_parse_string_full(s);
3571
+ }
3572
+ if s.starts_with('[') {
3573
+ return Self::json_parse_array(s);
3574
+ }
3575
+ if s.starts_with('{') {
3576
+ return Self::json_parse_object(s);
3577
+ }
3578
+ if let Ok(n) = s.parse::<f64>() {
3579
+ return Ok(Value::Number(n));
3580
+ }
3581
+ Err(())
3582
+ }
3583
+
3584
+ fn json_parse_string(s: &str) -> Result<(Value, &str), ()> {
3585
+ let s = &s[1..];
3586
+ let mut out = String::new();
3587
+ let mut i = 0;
3588
+ let chars: Vec<char> = s.chars().collect();
3589
+ while i < chars.len() {
3590
+ if chars[i] == '"' {
3591
+ let rest_start = s.chars().take(i + 1).map(|c| c.len_utf8()).sum::<usize>();
3592
+ return Ok((Value::String(out.into()), &s[rest_start..]));
3593
+ }
3594
+ if chars[i] == '\\' {
3595
+ i += 1;
3596
+ if i >= chars.len() {
3597
+ return Err(());
3598
+ }
3599
+ match chars[i] {
3600
+ '"' => out.push('"'),
3601
+ '\\' => out.push('\\'),
3602
+ 'n' => out.push('\n'),
3603
+ 'r' => out.push('\r'),
3604
+ 't' => out.push('\t'),
3605
+ _ => return Err(()),
3606
+ }
3607
+ } else {
3608
+ out.push(chars[i]);
3609
+ }
3610
+ i += 1;
3611
+ }
3612
+ Err(())
3613
+ }
3614
+
3615
+ fn json_parse_string_full(s: &str) -> Result<Value, ()> {
3616
+ Self::json_parse_string(s).map(|(v, _)| v)
3617
+ }
3618
+
3619
+ fn json_parse_array(s: &str) -> Result<Value, ()> {
3620
+ let s = s[1..].trim_start();
3621
+ if s.starts_with(']') {
3622
+ return Ok(Value::Array(Rc::new(RefCell::new(vec![]))));
3623
+ }
3624
+ let mut vals = Vec::new();
3625
+ let mut rest = s;
3626
+ loop {
3627
+ let (v, next) = Self::json_parse_one(rest)?;
3628
+ vals.push(v);
3629
+ rest = next.trim_start();
3630
+ if rest.starts_with(']') {
3631
+ break;
3632
+ }
3633
+ if !rest.starts_with(',') {
3634
+ return Err(());
3635
+ }
3636
+ rest = rest[1..].trim_start();
3637
+ }
3638
+ Ok(Value::Array(Rc::new(RefCell::new(vals))))
3639
+ }
3640
+
3641
+ fn json_parse_object(s: &str) -> Result<Value, ()> {
3642
+ let s = s[1..].trim_start();
3643
+ if s.starts_with('}') {
3644
+ return Ok(Value::object(PropMap::default()));
3645
+ }
3646
+ let mut map = PropMap::default();
3647
+ let mut rest = s;
3648
+ loop {
3649
+ if !rest.starts_with('"') {
3650
+ return Err(());
3651
+ }
3652
+ let (key_val, next) = Self::json_parse_string(rest)?;
3653
+ let key = match &key_val {
3654
+ Value::String(k) => Arc::clone(k),
3655
+ _ => return Err(()),
3656
+ };
3657
+ rest = next.trim_start();
3658
+ if !rest.starts_with(':') {
3659
+ return Err(());
3660
+ }
3661
+ rest = rest[1..].trim_start();
3662
+ let (val, next) = Self::json_parse_one(rest)?;
3663
+ map.insert(key, val);
3664
+ rest = next.trim_start();
3665
+ if rest.starts_with('}') {
3666
+ break;
3667
+ }
3668
+ if !rest.starts_with(',') {
3669
+ return Err(());
3670
+ }
3671
+ rest = rest[1..].trim_start();
3672
+ }
3673
+ Ok(Value::object(map))
3674
+ }
3675
+
3676
+ fn json_parse_one(s: &str) -> Result<(Value, &str), ()> {
3677
+ let s = s.trim();
3678
+ if s.is_empty() {
3679
+ return Err(());
3680
+ }
3681
+ if s.starts_with('"') {
3682
+ let (v, rest) = Self::json_parse_string(s)?;
3683
+ Ok((v, rest))
3684
+ } else if s.starts_with('[') {
3685
+ let mut depth = 0;
3686
+ for (i, c) in s.char_indices() {
3687
+ if c == '[' {
3688
+ depth += 1;
3689
+ } else if c == ']' {
3690
+ depth -= 1;
3691
+ if depth == 0 {
3692
+ let v = Self::json_parse_array(&s[..=i])?;
3693
+ return Ok((v, &s[i + c.len_utf8()..]));
3694
+ }
3695
+ }
3696
+ }
3697
+ Err(())
3698
+ } else if s.starts_with('{') {
3699
+ let mut depth = 0;
3700
+ for (i, c) in s.char_indices() {
3701
+ if c == '{' {
3702
+ depth += 1;
3703
+ } else if c == '}' {
3704
+ depth -= 1;
3705
+ if depth == 0 {
3706
+ let v = Self::json_parse_object(&s[..=i])?;
3707
+ return Ok((v, &s[i + c.len_utf8()..]));
3708
+ }
3709
+ }
3710
+ }
3711
+ Err(())
3712
+ } else if let Some(rest) = s.strip_prefix("null") {
3713
+ Ok((Value::Null, rest))
3714
+ } else if let Some(rest) = s.strip_prefix("true") {
3715
+ Ok((Value::Bool(true), rest))
3716
+ } else if let Some(rest) = s.strip_prefix("false") {
3717
+ Ok((Value::Bool(false), rest))
3718
+ } else {
3719
+ let end = s
3720
+ .find(|c: char| {
3721
+ !c.is_ascii_digit() && c != '-' && c != '+' && c != '.' && c != 'e' && c != 'E'
3722
+ })
3723
+ .unwrap_or(s.len());
3724
+ let num_str = &s[..end];
3725
+ let n: f64 = num_str.parse().map_err(|_| ())?;
3726
+ Ok((Value::Number(n), &s[end..]))
3727
+ }
3728
+ }
3729
+
3730
+ fn json_stringify_value(v: &Value) -> String {
3731
+ match v {
3732
+ Value::Null => "null".to_string(),
3733
+ Value::Bool(b) => b.to_string(),
3734
+ Value::Number(n) => {
3735
+ if n.is_finite() {
3736
+ n.to_string()
3737
+ } else {
3738
+ "null".to_string()
3739
+ }
3740
+ }
3741
+ Value::String(s) => format!(
3742
+ "\"{}\"",
3743
+ s.replace('\\', "\\\\")
3744
+ .replace('"', "\\\"")
3745
+ .replace('\n', "\\n")
3746
+ .replace('\r', "\\r")
3747
+ .replace('\t', "\\t")
3748
+ ),
3749
+ Value::Array(arr) => {
3750
+ let inner: Vec<String> = arr
3751
+ .borrow()
3752
+ .iter()
3753
+ .map(Self::json_stringify_value)
3754
+ .collect();
3755
+ format!("[{}]", inner.join(","))
3756
+ }
3757
+ Value::Object(map) => {
3758
+ // Insertion order (PropMap is an IndexMap) — matches JS/Node and the
3759
+ // VM/rust backends. No key sort.
3760
+ let entries: Vec<String> = map
3761
+ .borrow()
3762
+ .strings
3763
+ .iter()
3764
+ .map(|(k, v)| {
3765
+ format!(
3766
+ "\"{}\":{}",
3767
+ k.replace('\\', "\\\\").replace('"', "\\\""),
3768
+ Self::json_stringify_value(v)
3769
+ )
3770
+ })
3771
+ .collect();
3772
+ format!("{{{}}}", entries.join(","))
3773
+ }
3774
+ Value::Symbol(_) => "null".to_string(),
3775
+ Value::Function { .. } | Value::Native(_) => "null".to_string(),
3776
+ #[cfg(feature = "http")]
3777
+ Value::CorePromise(_) => "null".to_string(),
3778
+ Value::CoreFn(_) => "null".to_string(),
3779
+ #[cfg(feature = "http")]
3780
+ Value::Serve
3781
+ | Value::Promise(_)
3782
+ | Value::PromiseResolver(_)
3783
+ | Value::PromiseConstructor
3784
+ | Value::BoundPromiseMethod(_, _) => "null".to_string(),
3785
+ #[cfg(feature = "timers")]
3786
+ Value::TimerBuiltin(_) => "null".to_string(),
3787
+ #[cfg(feature = "regex")]
3788
+ Value::RegExp(_) => "null".to_string(),
3789
+ Value::Opaque(_) | Value::OpaqueMethod(_, _) => "null".to_string(),
3790
+ }
3791
+ }
3792
+
3793
+ // Static native wrapper functions (these need to be fn pointers, not closures with &self)
3794
+ fn json_parse_native(args: &[Value]) -> Result<Value, String> {
3795
+ let s = args.first().map(|v| v.to_string()).unwrap_or_default();
3796
+ Ok(Self::json_parse(&s))
3797
+ }
3798
+
3799
+ fn json_stringify_native(args: &[Value]) -> Result<Value, String> {
3800
+ let v = args.first().cloned().unwrap_or(Value::Null);
3801
+ Ok(Value::String(Self::json_stringify_value(&v).into()))
3802
+ }
3803
+
3804
+ fn object_keys(args: &[Value]) -> Result<Value, String> {
3805
+ if let Some(Value::Object(obj)) = args.first() {
3806
+ let keys: Vec<Value> = obj
3807
+ .borrow()
3808
+ .strings
3809
+ .keys()
3810
+ .map(|k| Value::String(Arc::clone(k)))
3811
+ .collect();
3812
+ Ok(Value::Array(Rc::new(RefCell::new(keys))))
3813
+ } else {
3814
+ Ok(Value::Array(Rc::new(RefCell::new(Vec::new()))))
3815
+ }
3816
+ }
3817
+
3818
+ fn object_values(args: &[Value]) -> Result<Value, String> {
3819
+ if let Some(Value::Object(obj)) = args.first() {
3820
+ let values: Vec<Value> = obj.borrow().strings.values().cloned().collect();
3821
+ Ok(Value::Array(Rc::new(RefCell::new(values))))
3822
+ } else {
3823
+ Ok(Value::Array(Rc::new(RefCell::new(Vec::new()))))
3824
+ }
3825
+ }
3826
+
3827
+ fn object_entries(args: &[Value]) -> Result<Value, String> {
3828
+ if let Some(Value::Object(obj)) = args.first() {
3829
+ let entries: Vec<Value> = obj
3830
+ .borrow()
3831
+ .strings
3832
+ .iter()
3833
+ .map(|(k, v)| {
3834
+ Value::Array(Rc::new(RefCell::new(vec![
3835
+ Value::String(Arc::clone(k)),
3836
+ v.clone(),
3837
+ ])))
3838
+ })
3839
+ .collect();
3840
+ Ok(Value::Array(Rc::new(RefCell::new(entries))))
3841
+ } else {
3842
+ Ok(Value::Array(Rc::new(RefCell::new(Vec::new()))))
3843
+ }
3844
+ }
3845
+
3846
+ fn object_assign(args: &[Value]) -> Result<Value, String> {
3847
+ if let Some(Value::Object(target)) = args.first() {
3848
+ let mut t = target.borrow_mut();
3849
+ for src in args.iter().skip(1) {
3850
+ if let Value::Object(src_obj) = src {
3851
+ let s = src_obj.borrow();
3852
+ for (k, v) in s.strings.iter() {
3853
+ t.strings.insert(Arc::clone(k), v.clone());
3854
+ }
3855
+ if let Some(ref sm) = s.symbols {
3856
+ if t.symbols.is_none() {
3857
+ t.symbols = Some(AHashMap::default());
3858
+ }
3859
+ let tm = t.symbols.as_mut().unwrap();
3860
+ for (id, v) in sm.iter() {
3861
+ tm.insert(*id, v.clone());
3862
+ }
3863
+ }
3864
+ }
3865
+ }
3866
+ drop(t);
3867
+ Ok(args.first().cloned().unwrap())
3868
+ } else {
3869
+ Ok(Value::Null)
3870
+ }
3871
+ }
3872
+
3873
+ fn object_from_entries(args: &[Value]) -> Result<Value, String> {
3874
+ if let Some(Value::Array(arr)) = args.first() {
3875
+ let mut map = PropMap::default();
3876
+ for entry in arr.borrow().iter() {
3877
+ if let Value::Array(pair) = entry {
3878
+ let pair = pair.borrow();
3879
+ if let (Some(key), Some(value)) = (pair.first(), pair.get(1)) {
3880
+ let key_str: Arc<str> = key.to_string().into();
3881
+ map.insert(key_str, value.clone());
3882
+ }
3883
+ }
3884
+ }
3885
+ Ok(Value::object(map))
3886
+ } else {
3887
+ Ok(Value::object(PropMap::default()))
3888
+ }
3889
+ }
3890
+
3891
+ #[cfg(feature = "regex")]
3892
+ fn regexp_constructor_native(args: &[Value]) -> Result<Value, String> {
3893
+ crate::regex::regexp_constructor(args)
3894
+ }
3895
+
3896
+ #[cfg(feature = "http")]
3897
+ fn promise_resolve(args: &[Value]) -> Result<Value, String> {
3898
+ let x = args.first().cloned().unwrap_or(Value::Null);
3899
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
3900
+ let (resolve, _) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
3901
+ crate::promise::settle_promise(&resolve, x, true)?;
3902
+ Ok(promise)
3903
+ }
3904
+
3905
+ #[cfg(feature = "http")]
3906
+ fn promise_reject(args: &[Value]) -> Result<Value, String> {
3907
+ let r = args.first().cloned().unwrap_or(Value::Null);
3908
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
3909
+ let (_, reject) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
3910
+ crate::promise::settle_promise(&reject, r, false)?;
3911
+ Ok(promise)
3912
+ }
3913
+
3914
+ #[cfg(feature = "http")]
3915
+ fn promise_all(args: &[Value]) -> Result<Value, String> {
3916
+ let iterable = args
3917
+ .first()
3918
+ .ok_or_else(|| "Promise.all requires an iterable".to_string())?;
3919
+ let values: Vec<Value> = match iterable {
3920
+ Value::Array(arr) => arr.borrow().clone(),
3921
+ Value::String(s) => s
3922
+ .chars()
3923
+ .map(|c| Value::String(c.to_string().into()))
3924
+ .collect(),
3925
+ _ => return Err("Promise.all requires array or iterable".to_string()),
3926
+ };
3927
+ let mut results = Vec::with_capacity(values.len());
3928
+ for v in values {
3929
+ if let Value::Promise(ref p) = v {
3930
+ match crate::promise::block_until_settled(p) {
3931
+ crate::promise::PromiseAwaitResult::Fulfilled(x) => results.push(x),
3932
+ crate::promise::PromiseAwaitResult::Rejected(x) => {
3933
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
3934
+ let (_, reject) =
3935
+ crate::promise::extract_resolvers(&resolve_val, &reject_val);
3936
+ let _ = crate::promise::settle_promise(&reject, x, false);
3937
+ return Ok(promise);
3938
+ }
3939
+ crate::promise::PromiseAwaitResult::Error(e) => return Err(e),
3940
+ }
3941
+ } else if let Value::CorePromise(ref p) = v {
3942
+ match p.block_until_settled() {
3943
+ Ok(x) => results.push(crate::value_convert::core_to_eval(x)),
3944
+ Err(x) => {
3945
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
3946
+ let (_, reject) =
3947
+ crate::promise::extract_resolvers(&resolve_val, &reject_val);
3948
+ let _ = crate::promise::settle_promise(
3949
+ &reject,
3950
+ crate::value_convert::core_to_eval(x),
3951
+ false,
3952
+ );
3953
+ return Ok(promise);
3954
+ }
3955
+ }
3956
+ } else {
3957
+ results.push(v);
3958
+ }
3959
+ }
3960
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
3961
+ let (resolve, _) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
3962
+ let arr = Value::Array(Rc::new(RefCell::new(results)));
3963
+ crate::promise::settle_promise(&resolve, arr, true)?;
3964
+ Ok(promise)
3965
+ }
3966
+
3967
+ #[cfg(feature = "http")]
3968
+ fn promise_race(args: &[Value]) -> Result<Value, String> {
3969
+ let iterable = args
3970
+ .first()
3971
+ .ok_or_else(|| "Promise.race requires an iterable".to_string())?;
3972
+ let values: Vec<Value> = match iterable {
3973
+ Value::Array(arr) => arr.borrow().clone(),
3974
+ Value::String(s) => s
3975
+ .chars()
3976
+ .map(|c| Value::String(c.to_string().into()))
3977
+ .collect(),
3978
+ _ => return Err("Promise.race requires array or iterable".to_string()),
3979
+ };
3980
+ for v in values {
3981
+ if let Value::CorePromise(ref p) = v {
3982
+ match p.block_until_settled() {
3983
+ Ok(x) => {
3984
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
3985
+ let (resolve, _) =
3986
+ crate::promise::extract_resolvers(&resolve_val, &reject_val);
3987
+ crate::promise::settle_promise(
3988
+ &resolve,
3989
+ crate::value_convert::core_to_eval(x),
3990
+ true,
3991
+ )?;
3992
+ return Ok(promise);
3993
+ }
3994
+ Err(x) => {
3995
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
3996
+ let (_, reject) =
3997
+ crate::promise::extract_resolvers(&resolve_val, &reject_val);
3998
+ crate::promise::settle_promise(
3999
+ &reject,
4000
+ crate::value_convert::core_to_eval(x),
4001
+ false,
4002
+ )?;
4003
+ return Ok(promise);
4004
+ }
4005
+ }
4006
+ }
4007
+ if let Value::Promise(ref p) = v {
4008
+ match crate::promise::block_until_settled(p) {
4009
+ crate::promise::PromiseAwaitResult::Fulfilled(x) => {
4010
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
4011
+ let (resolve, _) =
4012
+ crate::promise::extract_resolvers(&resolve_val, &reject_val);
4013
+ crate::promise::settle_promise(&resolve, x, true)?;
4014
+ return Ok(promise);
4015
+ }
4016
+ crate::promise::PromiseAwaitResult::Rejected(x) => {
4017
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
4018
+ let (_, reject) =
4019
+ crate::promise::extract_resolvers(&resolve_val, &reject_val);
4020
+ crate::promise::settle_promise(&reject, x, false)?;
4021
+ return Ok(promise);
4022
+ }
4023
+ crate::promise::PromiseAwaitResult::Error(e) => return Err(e),
4024
+ }
4025
+ }
4026
+ }
4027
+ Err("Promise.race requires at least one promise".to_string())
4028
+ }
4029
+
4030
+ /// Helper: settle a new promise fulfilled with `v` (interp Value).
4031
+ #[cfg(feature = "http")]
4032
+ fn eval_fulfilled(v: Value) -> Result<Value, String> {
4033
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
4034
+ let (resolve, _) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
4035
+ crate::promise::settle_promise(&resolve, v, true)?;
4036
+ Ok(promise)
4037
+ }
4038
+
4039
+ /// Helper: settle a new promise rejected with `v` (interp Value).
4040
+ #[cfg(feature = "http")]
4041
+ fn eval_rejected(v: Value) -> Result<Value, String> {
4042
+ let (promise, resolve_val, reject_val) = crate::promise::create_promise();
4043
+ let (_, reject) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
4044
+ crate::promise::settle_promise(&reject, v, false)?;
4045
+ Ok(promise)
4046
+ }
4047
+
4048
+ /// Await one interp promise/core-promise/value → `Result<Value, Value>`.
4049
+ #[cfg(feature = "http")]
4050
+ fn settle_one(v: Value) -> Result<Value, Value> {
4051
+ match v {
4052
+ Value::Promise(ref p) => match crate::promise::block_until_settled(p) {
4053
+ crate::promise::PromiseAwaitResult::Fulfilled(x) => Ok(x),
4054
+ crate::promise::PromiseAwaitResult::Rejected(x) => Err(x),
4055
+ crate::promise::PromiseAwaitResult::Error(e) => {
4056
+ Err(Value::String(e.into()))
4057
+ }
4058
+ },
4059
+ Value::CorePromise(ref p) => match p.block_until_settled() {
4060
+ Ok(x) => Ok(crate::value_convert::core_to_eval(x)),
4061
+ Err(x) => Err(crate::value_convert::core_to_eval(x)),
4062
+ },
4063
+ other => Ok(other),
4064
+ }
4065
+ }
4066
+
4067
+ /// `Promise.any(iterable)` — first fulfilled wins; rejects with array of reasons if all reject.
4068
+ #[cfg(feature = "http")]
4069
+ fn promise_any(args: &[Value]) -> Result<Value, String> {
4070
+ let iterable = args
4071
+ .first()
4072
+ .ok_or_else(|| "Promise.any requires an iterable".to_string())?;
4073
+ let values: Vec<Value> = match iterable {
4074
+ Value::Array(arr) => arr.borrow().clone(),
4075
+ _ => return Err("Promise.any requires an array".to_string()),
4076
+ };
4077
+ let n = values.len();
4078
+ if n == 0 {
4079
+ return Self::eval_rejected(Value::Array(Rc::new(RefCell::new(vec![]))));
4080
+ }
4081
+ let mut errors = Vec::with_capacity(n);
4082
+ for v in values {
4083
+ match Self::settle_one(v) {
4084
+ Ok(x) => return Self::eval_fulfilled(x),
4085
+ Err(e) => errors.push(e),
4086
+ }
4087
+ }
4088
+ Self::eval_rejected(Value::Array(Rc::new(RefCell::new(errors))))
4089
+ }
4090
+
4091
+ /// `Promise.allSettled(iterable)` — always fulfills with array of `{status,value|reason}`.
4092
+ #[cfg(feature = "http")]
4093
+ fn promise_all_settled(args: &[Value]) -> Result<Value, String> {
4094
+ use crate::value::EvalObjectData;
4095
+ let iterable = args
4096
+ .first()
4097
+ .ok_or_else(|| "Promise.allSettled requires an iterable".to_string())?;
4098
+ let values: Vec<Value> = match iterable {
4099
+ Value::Array(arr) => arr.borrow().clone(),
4100
+ _ => return Err("Promise.allSettled requires an array".to_string()),
4101
+ };
4102
+ let mut out = Vec::with_capacity(values.len());
4103
+ for v in values {
4104
+ let r = Self::settle_one(v);
4105
+ let mut data = EvalObjectData::default();
4106
+ match r {
4107
+ Ok(x) => {
4108
+ data.strings.insert(std::sync::Arc::from("status"), Value::String("fulfilled".into()));
4109
+ data.strings.insert(std::sync::Arc::from("value"), x);
4110
+ }
4111
+ Err(e) => {
4112
+ data.strings.insert(std::sync::Arc::from("status"), Value::String("rejected".into()));
4113
+ data.strings.insert(std::sync::Arc::from("reason"), e);
4114
+ }
4115
+ }
4116
+ out.push(Value::Object(Rc::new(RefCell::new(data))));
4117
+ }
4118
+ Self::eval_fulfilled(Value::Array(Rc::new(RefCell::new(out))))
4119
+ }
4120
+
4121
+ /// `Promise.spawn(fn)` — on the interpreter, runs the function synchronously and wraps
4122
+ /// the result in an immediate promise. The interpreter uses `Rc<RefCell<…>>` for closures,
4123
+ /// which is `!Send`, so we cannot move the function to a background thread here. Real
4124
+ /// cross-thread parallelism via spawn is available on the bytecode VM (which uses the
4125
+ /// `send-values` / Arc path for the shipped `full` build). For the interpreter, `any` and
4126
+ /// `race` over spawn-created promises still work correctly — they just don't run concurrently.
4127
+ #[cfg(feature = "http")]
4128
+ fn promise_spawn_interp(args: &[Value]) -> Result<Value, String> {
4129
+ let callable = match args.first() {
4130
+ Some(v @ (Value::Native(_) | Value::Function { .. })) => v.clone(),
4131
+ _ => return Err("Promise.spawn: expected a function argument".to_string()),
4132
+ };
4133
+ let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4134
+ match &callable {
4135
+ Value::Native(f) => f(&[]).map_err(|e| e.to_string()),
4136
+ // Interpreter closures (Value::Function) can't be called from a static native fn
4137
+ // (no evaluator state / Rc captures). Use the VM backend for concurrent CPU spawn.
4138
+ _ => Err("Promise.spawn: tish closures are not supported on the interpreter backend; use the vm backend (tish run) or pass a native module function".to_string()),
4139
+ }
4140
+ }));
4141
+ match result {
4142
+ Ok(Ok(v)) => Self::eval_fulfilled(v),
4143
+ Ok(Err(e)) => Self::eval_rejected(Value::String(e.into())),
4144
+ Err(_) => Self::eval_rejected(Value::String("Promise.spawn: task panicked".into())),
4145
+ }
4146
+ }
4147
+
4148
+ #[cfg(feature = "ws")]
4149
+ fn ws_web_socket_native(args: &[Value]) -> Result<Value, String> {
4150
+ let mut cv = Vec::new();
4151
+ for a in args {
4152
+ cv.push(crate::value_convert::eval_to_core(a)?);
4153
+ }
4154
+ Ok(crate::value_convert::core_to_eval(
4155
+ tishlang_runtime::web_socket_client(&cv),
4156
+ ))
4157
+ }
4158
+
4159
+ #[cfg(feature = "ws")]
4160
+ fn ws_server_native(args: &[Value]) -> Result<Value, String> {
4161
+ let mut cv = Vec::new();
4162
+ for a in args {
4163
+ cv.push(crate::value_convert::eval_to_core(a)?);
4164
+ }
4165
+ Ok(crate::value_convert::core_to_eval(
4166
+ tishlang_runtime::web_socket_server_construct(&cv),
4167
+ ))
4168
+ }
4169
+
4170
+ #[cfg(feature = "ws")]
4171
+ fn ws_send_native(args: &[Value]) -> Result<Value, String> {
4172
+ let conn = args.first().ok_or("wsSend(conn, data) requires conn")?;
4173
+ let conn_core = crate::value_convert::eval_to_core(conn)?;
4174
+ let data = args.get(1).map(|v| v.to_string()).unwrap_or_default();
4175
+ Ok(Value::Bool(tishlang_runtime::ws_send_native(
4176
+ &conn_core, &data,
4177
+ )))
4178
+ }
4179
+
4180
+ #[cfg(feature = "ws")]
4181
+ fn ws_broadcast_native(args: &[Value]) -> Result<Value, String> {
4182
+ let mut cv = Vec::new();
4183
+ for a in args {
4184
+ cv.push(crate::value_convert::eval_to_core(a)?);
4185
+ }
4186
+ Ok(crate::value_convert::core_to_eval(
4187
+ tishlang_runtime::ws_broadcast_native(&cv),
4188
+ ))
4189
+ }
4190
+
4191
+ #[cfg(feature = "http")]
4192
+ fn fetch_native(args: &[Value]) -> Result<Value, String> {
4193
+ let mut cv = Vec::new();
4194
+ for a in args {
4195
+ cv.push(crate::value_convert::eval_to_core(a)?);
4196
+ }
4197
+ match tishlang_runtime::fetch_promise(cv) {
4198
+ tishlang_core::Value::Promise(p) => Ok(Value::CorePromise(p)),
4199
+ _ => Err("internal: fetch did not return Promise".into()),
4200
+ }
4201
+ }
4202
+
4203
+ #[cfg(feature = "http")]
4204
+ fn fetch_all_native(args: &[Value]) -> Result<Value, String> {
4205
+ let mut cv = Vec::new();
4206
+ for a in args {
4207
+ cv.push(crate::value_convert::eval_to_core(a)?);
4208
+ }
4209
+ match tishlang_runtime::fetch_all_promise(cv) {
4210
+ tishlang_core::Value::Promise(p) => Ok(Value::CorePromise(p)),
4211
+ _ => Err("internal: fetchAll did not return Promise".into()),
4212
+ }
4213
+ }
4214
+
4215
+ #[cfg(feature = "http")]
4216
+ fn eval_await(&self, operand: &Expr) -> Result<Value, EvalError> {
4217
+ let val = self.eval_expr(operand)?;
4218
+ if let Value::Promise(ref p) = val {
4219
+ match crate::promise::block_until_settled(p) {
4220
+ crate::promise::PromiseAwaitResult::Fulfilled(v) => Ok(v),
4221
+ crate::promise::PromiseAwaitResult::Rejected(v) => Err(EvalError::Throw(v)),
4222
+ crate::promise::PromiseAwaitResult::Error(e) => Err(EvalError::Error(e)),
4223
+ }
4224
+ } else if let Value::CorePromise(ref p) = val {
4225
+ match p.block_until_settled() {
4226
+ Ok(v) => Ok(crate::value_convert::core_to_eval(v)),
4227
+ Err(v) => Err(EvalError::Throw(crate::value_convert::core_to_eval(v))),
4228
+ }
4229
+ } else {
4230
+ Err(EvalError::Error(
4231
+ "await requires a Promise (use await fetch(...), await reader.read(), etc.)".into(),
4232
+ ))
4233
+ }
4234
+ }
4235
+
4236
+ #[cfg(not(feature = "http"))]
4237
+ fn eval_await(&self, _operand: &Expr) -> Result<Value, EvalError> {
4238
+ Err(EvalError::Error(
4239
+ "await requires the http feature".to_string(),
4240
+ ))
4241
+ }
4242
+ }
4243
+
4244
+ #[derive(Debug)]
4245
+ enum EvalError {
4246
+ Return(Value),
4247
+ Break,
4248
+ Continue,
4249
+ Throw(Value),
4250
+ Error(String),
4251
+ }
4252
+
4253
+ impl std::fmt::Display for EvalError {
4254
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
4255
+ match self {
4256
+ EvalError::Return(_) => write!(f, "return"),
4257
+ EvalError::Break => write!(f, "break"),
4258
+ EvalError::Continue => write!(f, "continue"),
4259
+ EvalError::Throw(v) => write!(f, "{}", v),
4260
+ EvalError::Error(s) => write!(f, "{}", s),
4261
+ }
4262
+ }
4263
+ }
4264
+
4265
+ impl std::error::Error for EvalError {}