@tishlang/tish 1.0.7 → 1.0.10

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 (127) hide show
  1. package/Cargo.toml +43 -0
  2. package/LICENSE +13 -0
  3. package/README.md +66 -0
  4. package/crates/js_to_tish/Cargo.toml +9 -0
  5. package/crates/js_to_tish/README.md +18 -0
  6. package/crates/js_to_tish/src/error.rs +61 -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 +608 -0
  10. package/crates/js_to_tish/src/transform/stmt.rs +474 -0
  11. package/crates/js_to_tish/src/transform.rs +60 -0
  12. package/crates/tish/Cargo.toml +44 -0
  13. package/crates/tish/src/main.rs +585 -0
  14. package/crates/tish/src/repl_completion.rs +200 -0
  15. package/crates/tish/tests/integration_test.rs +726 -0
  16. package/crates/tish_ast/Cargo.toml +7 -0
  17. package/crates/tish_ast/src/ast.rs +494 -0
  18. package/crates/tish_ast/src/lib.rs +5 -0
  19. package/crates/tish_build_utils/Cargo.toml +5 -0
  20. package/crates/tish_build_utils/src/lib.rs +175 -0
  21. package/crates/tish_builtins/Cargo.toml +12 -0
  22. package/crates/tish_builtins/src/array.rs +410 -0
  23. package/crates/tish_builtins/src/globals.rs +197 -0
  24. package/crates/tish_builtins/src/helpers.rs +38 -0
  25. package/crates/tish_builtins/src/lib.rs +14 -0
  26. package/crates/tish_builtins/src/math.rs +80 -0
  27. package/crates/tish_builtins/src/object.rs +36 -0
  28. package/crates/tish_builtins/src/string.rs +253 -0
  29. package/crates/tish_bytecode/Cargo.toml +15 -0
  30. package/crates/tish_bytecode/src/chunk.rs +97 -0
  31. package/crates/tish_bytecode/src/compiler.rs +1361 -0
  32. package/crates/tish_bytecode/src/encoding.rs +100 -0
  33. package/crates/tish_bytecode/src/lib.rs +19 -0
  34. package/crates/tish_bytecode/src/opcode.rs +110 -0
  35. package/crates/tish_bytecode/src/peephole.rs +159 -0
  36. package/crates/tish_bytecode/src/serialize.rs +163 -0
  37. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  38. package/crates/tish_bytecode/tests/shortcircuit.rs +49 -0
  39. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  40. package/crates/tish_compile/Cargo.toml +21 -0
  41. package/crates/tish_compile/src/codegen.rs +3316 -0
  42. package/crates/tish_compile/src/lib.rs +71 -0
  43. package/crates/tish_compile/src/resolve.rs +631 -0
  44. package/crates/tish_compile/src/types.rs +304 -0
  45. package/crates/tish_compile_js/Cargo.toml +16 -0
  46. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  47. package/crates/tish_compile_js/src/codegen.rs +794 -0
  48. package/crates/tish_compile_js/src/error.rs +20 -0
  49. package/crates/tish_compile_js/src/js_intrinsics.rs +82 -0
  50. package/crates/tish_compile_js/src/lib.rs +27 -0
  51. package/crates/tish_compile_js/src/tests_jsx.rs +32 -0
  52. package/crates/tish_compiler_wasm/Cargo.toml +19 -0
  53. package/crates/tish_compiler_wasm/src/lib.rs +55 -0
  54. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +462 -0
  55. package/crates/tish_core/Cargo.toml +11 -0
  56. package/crates/tish_core/src/console_style.rs +128 -0
  57. package/crates/tish_core/src/json.rs +327 -0
  58. package/crates/tish_core/src/lib.rs +15 -0
  59. package/crates/tish_core/src/macros.rs +37 -0
  60. package/crates/tish_core/src/uri.rs +115 -0
  61. package/crates/tish_core/src/value.rs +376 -0
  62. package/crates/tish_cranelift/Cargo.toml +17 -0
  63. package/crates/tish_cranelift/src/lib.rs +41 -0
  64. package/crates/tish_cranelift/src/link.rs +120 -0
  65. package/crates/tish_cranelift/src/lower.rs +77 -0
  66. package/crates/tish_cranelift_runtime/Cargo.toml +19 -0
  67. package/crates/tish_cranelift_runtime/src/lib.rs +43 -0
  68. package/crates/tish_eval/Cargo.toml +26 -0
  69. package/crates/tish_eval/src/eval.rs +3205 -0
  70. package/crates/tish_eval/src/http.rs +122 -0
  71. package/crates/tish_eval/src/lib.rs +59 -0
  72. package/crates/tish_eval/src/natives.rs +301 -0
  73. package/crates/tish_eval/src/promise.rs +173 -0
  74. package/crates/tish_eval/src/regex.rs +298 -0
  75. package/crates/tish_eval/src/timers.rs +111 -0
  76. package/crates/tish_eval/src/value.rs +224 -0
  77. package/crates/tish_eval/src/value_convert.rs +85 -0
  78. package/crates/tish_fmt/Cargo.toml +16 -0
  79. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  80. package/crates/tish_fmt/src/lib.rs +884 -0
  81. package/crates/tish_jsx_web/Cargo.toml +7 -0
  82. package/crates/tish_jsx_web/README.md +18 -0
  83. package/crates/tish_jsx_web/src/lib.rs +157 -0
  84. package/crates/tish_jsx_web/vendor/Lattish.tish +347 -0
  85. package/crates/tish_lexer/Cargo.toml +7 -0
  86. package/crates/tish_lexer/src/lib.rs +430 -0
  87. package/crates/tish_lexer/src/token.rs +155 -0
  88. package/crates/tish_lint/Cargo.toml +17 -0
  89. package/crates/tish_lint/src/bin/tish-lint.rs +77 -0
  90. package/crates/tish_lint/src/lib.rs +278 -0
  91. package/crates/tish_llvm/Cargo.toml +11 -0
  92. package/crates/tish_llvm/src/lib.rs +106 -0
  93. package/crates/tish_lsp/Cargo.toml +22 -0
  94. package/crates/tish_lsp/README.md +26 -0
  95. package/crates/tish_lsp/src/main.rs +615 -0
  96. package/crates/tish_native/Cargo.toml +14 -0
  97. package/crates/tish_native/src/build.rs +102 -0
  98. package/crates/tish_native/src/lib.rs +237 -0
  99. package/crates/tish_opt/Cargo.toml +11 -0
  100. package/crates/tish_opt/src/lib.rs +896 -0
  101. package/crates/tish_parser/Cargo.toml +9 -0
  102. package/crates/tish_parser/src/lib.rs +123 -0
  103. package/crates/tish_parser/src/parser.rs +1714 -0
  104. package/crates/tish_runtime/Cargo.toml +26 -0
  105. package/crates/tish_runtime/src/http.rs +308 -0
  106. package/crates/tish_runtime/src/http_fetch.rs +453 -0
  107. package/crates/tish_runtime/src/lib.rs +1004 -0
  108. package/crates/tish_runtime/src/native_promise.rs +26 -0
  109. package/crates/tish_runtime/src/promise.rs +77 -0
  110. package/crates/tish_runtime/src/promise_io.rs +41 -0
  111. package/crates/tish_runtime/src/timers.rs +125 -0
  112. package/crates/tish_runtime/src/ws.rs +725 -0
  113. package/crates/tish_runtime/tests/fetch_readable_stream.rs +99 -0
  114. package/crates/tish_vm/Cargo.toml +31 -0
  115. package/crates/tish_vm/src/lib.rs +39 -0
  116. package/crates/tish_vm/src/vm.rs +1399 -0
  117. package/crates/tish_wasm/Cargo.toml +13 -0
  118. package/crates/tish_wasm/src/lib.rs +358 -0
  119. package/crates/tish_wasm_runtime/Cargo.toml +25 -0
  120. package/crates/tish_wasm_runtime/src/lib.rs +36 -0
  121. package/justfile +260 -0
  122. package/package.json +8 -3
  123. package/platform/darwin-arm64/tish +0 -0
  124. package/platform/darwin-x64/tish +0 -0
  125. package/platform/linux-arm64/tish +0 -0
  126. package/platform/linux-x64/tish +0 -0
  127. package/platform/win32-x64/tish.exe +0 -0
@@ -0,0 +1,1399 @@
1
+ //! Stack-based bytecode VM.
2
+
3
+ use std::cell::RefCell;
4
+ use std::collections::HashMap;
5
+ use std::rc::Rc;
6
+ use std::sync::Arc;
7
+
8
+ use tish_ast::{BinOp, UnaryOp};
9
+ use tish_builtins::array as arr_builtins;
10
+ use tish_builtins::string as str_builtins;
11
+ use tish_builtins::globals as globals_builtins;
12
+ use tish_builtins::math as math_builtins;
13
+ use tish_bytecode::{u8_to_binop, u8_to_unaryop, Chunk, Constant, Opcode, NO_REST_PARAM};
14
+ use tish_core::Value;
15
+
16
+ type ArrayMethodFn = Rc<dyn Fn(&[Value]) -> Value>;
17
+
18
+ /// Look up built-in module export for LoadNativeExport. Returns None if unknown or feature disabled.
19
+ /// Parameters are only used when the corresponding feature (fs, http, process) is enabled.
20
+ #[cfg_attr(
21
+ not(any(feature = "fs", feature = "http", feature = "process", feature = "ws")),
22
+ allow(unused_variables)
23
+ )]
24
+ fn get_builtin_export(spec: &str, export_name: &str) -> Option<Value> {
25
+ #[cfg(feature = "fs")]
26
+ if spec == "tish:fs" {
27
+ return match export_name {
28
+ "readFile" => Some(Value::Function(Rc::new(|args: &[Value]| tish_runtime::read_file(args)))),
29
+ "writeFile" => Some(Value::Function(Rc::new(|args: &[Value]| tish_runtime::write_file(args)))),
30
+ "fileExists" => Some(Value::Function(Rc::new(|args: &[Value]| tish_runtime::file_exists(args)))),
31
+ "isDir" => Some(Value::Function(Rc::new(|args: &[Value]| tish_runtime::is_dir(args)))),
32
+ "readDir" => Some(Value::Function(Rc::new(|args: &[Value]| tish_runtime::read_dir(args)))),
33
+ "mkdir" => Some(Value::Function(Rc::new(|args: &[Value]| tish_runtime::mkdir(args)))),
34
+ _ => None,
35
+ };
36
+ }
37
+ #[cfg(feature = "http")]
38
+ if spec == "tish:http" {
39
+ return match export_name {
40
+ "fetch" => Some(Value::Function(Rc::new(|args: &[Value]| {
41
+ tish_runtime::fetch_promise(args.to_vec())
42
+ }))),
43
+ "fetchAll" => Some(Value::Function(Rc::new(|args: &[Value]| {
44
+ tish_runtime::fetch_all_promise(args.to_vec())
45
+ }))),
46
+ "serve" => Some(Value::Function(Rc::new(|args: &[Value]| {
47
+ let handler = args.get(1).cloned().unwrap_or(Value::Null);
48
+ if let Value::Function(f) = handler {
49
+ tish_runtime::http_serve(args, move |req_args| f(req_args))
50
+ } else {
51
+ Value::Null
52
+ }
53
+ }))),
54
+ _ => None,
55
+ };
56
+ }
57
+ #[cfg(feature = "process")]
58
+ if spec == "tish:process" {
59
+ return match export_name {
60
+ "exit" => Some(Value::Function(Rc::new(|args: &[Value]| tish_runtime::process_exit(args)))),
61
+ "cwd" => Some(Value::Function(Rc::new(|args: &[Value]| tish_runtime::process_cwd(args)))),
62
+ "exec" => Some(Value::Function(Rc::new(|args: &[Value]| tish_runtime::process_exec(args)))),
63
+ "argv" => Some(Value::Array(Rc::new(RefCell::new(
64
+ std::env::args().map(|s| Value::String(s.into())).collect(),
65
+ )))),
66
+ "env" => Some(Value::Object(Rc::new(RefCell::new(
67
+ std::env::vars()
68
+ .map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
69
+ .collect(),
70
+ )))),
71
+ "process" => {
72
+ let mut m = HashMap::new();
73
+ m.insert("exit".into(), Value::Function(Rc::new(|args: &[Value]| tish_runtime::process_exit(args))));
74
+ m.insert("cwd".into(), Value::Function(Rc::new(|args: &[Value]| tish_runtime::process_cwd(args))));
75
+ m.insert("exec".into(), Value::Function(Rc::new(|args: &[Value]| tish_runtime::process_exec(args))));
76
+ m.insert(
77
+ "argv".into(),
78
+ Value::Array(Rc::new(RefCell::new(
79
+ std::env::args().map(|s| Value::String(s.into())).collect(),
80
+ ))),
81
+ );
82
+ m.insert(
83
+ "env".into(),
84
+ Value::Object(Rc::new(RefCell::new(
85
+ std::env::vars()
86
+ .map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
87
+ .collect(),
88
+ ))),
89
+ );
90
+ Some(Value::Object(Rc::new(RefCell::new(m))))
91
+ }
92
+ _ => None,
93
+ };
94
+ }
95
+ #[cfg(feature = "ws")]
96
+ if spec == "tish:ws" {
97
+ return match export_name {
98
+ "WebSocket" => Some(Value::Function(Rc::new(|args: &[Value]| {
99
+ tish_runtime::web_socket_client(args)
100
+ }))),
101
+ "Server" => Some(Value::Function(Rc::new(|args: &[Value]| {
102
+ tish_runtime::web_socket_server_construct(args)
103
+ }))),
104
+ "wsSend" => Some(Value::Function(Rc::new(|args: &[Value]| {
105
+ Value::Bool(tish_runtime::ws_send_native(
106
+ args.first().unwrap_or(&Value::Null),
107
+ &args.get(1).map(|v| v.to_display_string()).unwrap_or_default(),
108
+ ))
109
+ }))),
110
+ "wsBroadcast" => Some(Value::Function(Rc::new(|args: &[Value]| {
111
+ tish_runtime::ws_broadcast_native(args)
112
+ }))),
113
+ _ => None,
114
+ };
115
+ }
116
+ None
117
+ }
118
+
119
+ /// Console output: println! on native, web_sys::console on wasm
120
+ #[cfg(not(feature = "wasm"))]
121
+ fn vm_log(s: &str) {
122
+ println!("{}", s);
123
+ }
124
+ #[cfg(not(feature = "wasm"))]
125
+ fn vm_log_err(s: &str) {
126
+ eprintln!("{}", s);
127
+ }
128
+ #[cfg(feature = "wasm")]
129
+ fn vm_log(s: &str) {
130
+ #[wasm_bindgen::prelude::wasm_bindgen]
131
+ extern "C" {
132
+ #[wasm_bindgen(js_namespace = console)]
133
+ fn log(s: &str);
134
+ }
135
+ log(s);
136
+ }
137
+ #[cfg(feature = "wasm")]
138
+ fn vm_log_err(s: &str) {
139
+ #[wasm_bindgen::prelude::wasm_bindgen]
140
+ extern "C" {
141
+ #[wasm_bindgen(js_namespace = console)]
142
+ fn error(s: &str);
143
+ }
144
+ error(s);
145
+ }
146
+
147
+ /// Initialize default globals (console, Math, JSON, etc.)
148
+ fn init_globals() -> HashMap<Arc<str>, Value> {
149
+ let mut g = HashMap::new();
150
+
151
+ let mut console = HashMap::new();
152
+ console.insert(
153
+ "debug".into(),
154
+ Value::Function(Rc::new(|args: &[Value]| {
155
+ let s = tish_core::format_values_for_console(args, tish_core::use_console_colors());
156
+ vm_log(&s);
157
+ Value::Null
158
+ })),
159
+ );
160
+ console.insert(
161
+ "log".into(),
162
+ Value::Function(Rc::new(|args: &[Value]| {
163
+ let s = tish_core::format_values_for_console(args, tish_core::use_console_colors());
164
+ vm_log(&s);
165
+ Value::Null
166
+ })),
167
+ );
168
+ console.insert(
169
+ "info".into(),
170
+ Value::Function(Rc::new(|args: &[Value]| {
171
+ let s = tish_core::format_values_for_console(args, tish_core::use_console_colors());
172
+ vm_log(&s);
173
+ Value::Null
174
+ })),
175
+ );
176
+ console.insert(
177
+ "warn".into(),
178
+ Value::Function(Rc::new(|args: &[Value]| {
179
+ let s = tish_core::format_values_for_console(args, tish_core::use_console_colors());
180
+ vm_log_err(&s);
181
+ Value::Null
182
+ })),
183
+ );
184
+ console.insert(
185
+ "error".into(),
186
+ Value::Function(Rc::new(|args: &[Value]| {
187
+ let s = tish_core::format_values_for_console(args, tish_core::use_console_colors());
188
+ vm_log_err(&s);
189
+ Value::Null
190
+ })),
191
+ );
192
+ g.insert("console".into(), Value::Object(Rc::new(RefCell::new(console))));
193
+
194
+ let mut math = HashMap::new();
195
+ math.insert(
196
+ "abs".into(),
197
+ Value::Function(Rc::new(|args: &[Value]| {
198
+ let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
199
+ Value::Number(n.abs())
200
+ })),
201
+ );
202
+ math.insert(
203
+ "sqrt".into(),
204
+ Value::Function(Rc::new(|args: &[Value]| {
205
+ let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
206
+ Value::Number(n.sqrt())
207
+ })),
208
+ );
209
+ math.insert(
210
+ "floor".into(),
211
+ Value::Function(Rc::new(|args: &[Value]| {
212
+ let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
213
+ Value::Number(n.floor())
214
+ })),
215
+ );
216
+ math.insert(
217
+ "ceil".into(),
218
+ Value::Function(Rc::new(|args: &[Value]| {
219
+ let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
220
+ Value::Number(n.ceil())
221
+ })),
222
+ );
223
+ math.insert(
224
+ "round".into(),
225
+ Value::Function(Rc::new(|args: &[Value]| {
226
+ let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
227
+ Value::Number(n.round())
228
+ })),
229
+ );
230
+ math.insert(
231
+ "random".into(),
232
+ Value::Function(Rc::new(|_| Value::Number(rand::random::<f64>()))),
233
+ );
234
+ math.insert(
235
+ "min".into(),
236
+ Value::Function(Rc::new(|args: &[Value]| {
237
+ let nums: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
238
+ Value::Number(nums.into_iter().fold(f64::NAN, |a, b| a.min(b)))
239
+ })),
240
+ );
241
+ math.insert(
242
+ "max".into(),
243
+ Value::Function(Rc::new(|args: &[Value]| {
244
+ let nums: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
245
+ Value::Number(nums.into_iter().fold(f64::NAN, |a, b| a.max(b)))
246
+ })),
247
+ );
248
+ math.insert("pow".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::pow(args))));
249
+ math.insert("sin".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::sin(args))));
250
+ math.insert("cos".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::cos(args))));
251
+ math.insert("tan".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::tan(args))));
252
+ math.insert("log".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::log(args))));
253
+ math.insert("exp".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::exp(args))));
254
+ math.insert("sign".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::sign(args))));
255
+ math.insert("trunc".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::trunc(args))));
256
+ math.insert("PI".into(), Value::Number(std::f64::consts::PI));
257
+ math.insert("E".into(), Value::Number(std::f64::consts::E));
258
+ g.insert("Math".into(), Value::Object(Rc::new(RefCell::new(math))));
259
+
260
+ let mut json = HashMap::new();
261
+ json.insert(
262
+ "parse".into(),
263
+ Value::Function(Rc::new(|args: &[Value]| {
264
+ let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
265
+ tish_core::json_parse(&s).unwrap_or(Value::Null)
266
+ })),
267
+ );
268
+ json.insert(
269
+ "stringify".into(),
270
+ Value::Function(Rc::new(|args: &[Value]| {
271
+ let v = args.first().unwrap_or(&Value::Null);
272
+ Value::String(tish_core::json_stringify(v).into())
273
+ })),
274
+ );
275
+ g.insert("JSON".into(), Value::Object(Rc::new(RefCell::new(json))));
276
+
277
+ g.insert("parseInt".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::parse_int(args))));
278
+ g.insert("parseFloat".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::parse_float(args))));
279
+ g.insert("encodeURI".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::encode_uri(args))));
280
+ g.insert("decodeURI".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::decode_uri(args))));
281
+ g.insert("Boolean".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::boolean(args))));
282
+ g.insert("isFinite".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::is_finite(args))));
283
+ g.insert("isNaN".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::is_nan(args))));
284
+ g.insert("Infinity".into(), Value::Number(f64::INFINITY));
285
+ g.insert("NaN".into(), Value::Number(f64::NAN));
286
+ g.insert(
287
+ "typeof".into(),
288
+ Value::Function(Rc::new(|args: &[Value]| {
289
+ let v = args.first().unwrap_or(&Value::Null);
290
+ Value::String(v.type_name().into())
291
+ })),
292
+ );
293
+
294
+ // Date - at minimum Date.now() for timing
295
+ let mut date = HashMap::new();
296
+ date.insert(
297
+ "now".into(),
298
+ Value::Function(Rc::new(|_args: &[Value]| {
299
+ let ms = std::time::SystemTime::now()
300
+ .duration_since(std::time::UNIX_EPOCH)
301
+ .unwrap_or_default()
302
+ .as_millis() as f64;
303
+ Value::Number(ms)
304
+ })),
305
+ );
306
+ g.insert("Date".into(), Value::Object(Rc::new(RefCell::new(date))));
307
+
308
+ // Object methods - delegate to tish_builtins::globals
309
+ let mut object_methods = HashMap::new();
310
+ object_methods.insert(
311
+ "assign".into(),
312
+ Value::Function(Rc::new(|args: &[Value]| globals_builtins::object_assign(args))),
313
+ );
314
+ object_methods.insert(
315
+ "fromEntries".into(),
316
+ Value::Function(Rc::new(|args: &[Value]| globals_builtins::object_from_entries(args))),
317
+ );
318
+ object_methods.insert("keys".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::object_keys(args))));
319
+ object_methods.insert("values".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::object_values(args))));
320
+ object_methods.insert("entries".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::object_entries(args))));
321
+ g.insert("Object".into(), Value::Object(Rc::new(RefCell::new(object_methods))));
322
+
323
+ // Array.isArray
324
+ let mut array_static = HashMap::new();
325
+ array_static.insert(
326
+ "isArray".into(),
327
+ Value::Function(Rc::new(|args: &[Value]| globals_builtins::array_is_array(args))),
328
+ );
329
+ g.insert("Array".into(), Value::Object(Rc::new(RefCell::new(array_static))));
330
+
331
+ // String(value) as callable + String.fromCharCode
332
+ let string_convert_fn = Value::Function(Rc::new(|args: &[Value]| globals_builtins::string_convert(args)));
333
+ let mut string_static = HashMap::new();
334
+ string_static.insert("fromCharCode".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::string_from_char_code(args))));
335
+ string_static.insert(Arc::from("__call"), string_convert_fn);
336
+ g.insert("String".into(), Value::Object(Rc::new(RefCell::new(string_static))));
337
+
338
+ // JSX / Lattish: stubs for bytecode VM when no DOM (e.g. console). Override via set_global in browser.
339
+ g.insert(
340
+ "h".into(),
341
+ Value::Function(Rc::new(|_args: &[Value]| Value::Null)),
342
+ );
343
+ g.insert(
344
+ "Fragment".into(),
345
+ Value::Object(Rc::new(RefCell::new(HashMap::new()))),
346
+ );
347
+ g.insert(
348
+ "createRoot".into(),
349
+ Value::Function(Rc::new(|_args: &[Value]| {
350
+ let mut render_obj = HashMap::new();
351
+ render_obj.insert(
352
+ "render".into(),
353
+ Value::Function(Rc::new(|_args: &[Value]| Value::Null)),
354
+ );
355
+ Value::Object(Rc::new(RefCell::new(render_obj)))
356
+ })),
357
+ );
358
+ g.insert(
359
+ "useState".into(),
360
+ Value::Function(Rc::new(|args: &[Value]| {
361
+ let init = args.first().cloned().unwrap_or(Value::Null);
362
+ let arr = vec![init, Value::Function(Rc::new(|_| Value::Null))];
363
+ Value::Array(Rc::new(RefCell::new(arr)))
364
+ })),
365
+ );
366
+ let mut document_obj = HashMap::new();
367
+ document_obj.insert("body".into(), Value::Null);
368
+ g.insert("document".into(), Value::Object(Rc::new(RefCell::new(document_obj))));
369
+
370
+ #[cfg(feature = "process")]
371
+ {
372
+ let mut process_obj = HashMap::new();
373
+ process_obj.insert(
374
+ "exit".into(),
375
+ Value::Function(Rc::new(|args: &[Value]| tish_runtime::process_exit(args))),
376
+ );
377
+ process_obj.insert(
378
+ "cwd".into(),
379
+ Value::Function(Rc::new(|args: &[Value]| tish_runtime::process_cwd(args))),
380
+ );
381
+ process_obj.insert(
382
+ "exec".into(),
383
+ Value::Function(Rc::new(|args: &[Value]| tish_runtime::process_exec(args))),
384
+ );
385
+ process_obj.insert(
386
+ "argv".into(),
387
+ Value::Array(Rc::new(RefCell::new(
388
+ std::env::args().map(|s| Value::String(s.into())).collect(),
389
+ ))),
390
+ );
391
+ process_obj.insert(
392
+ "env".into(),
393
+ Value::Object(Rc::new(RefCell::new(
394
+ std::env::vars()
395
+ .map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
396
+ .collect(),
397
+ ))),
398
+ );
399
+ g.insert("process".into(), Value::Object(Rc::new(RefCell::new(process_obj))));
400
+ }
401
+
402
+ #[cfg(feature = "http")]
403
+ {
404
+ g.insert(
405
+ "serve".into(),
406
+ Value::Function(Rc::new(|args: &[Value]| {
407
+ let handler = args.get(1).cloned().unwrap_or(Value::Null);
408
+ if let Value::Function(f) = handler {
409
+ tish_runtime::http_serve(args, move |req_args| f(req_args))
410
+ } else {
411
+ Value::Null
412
+ }
413
+ })),
414
+ );
415
+ }
416
+
417
+ g
418
+ }
419
+
420
+ /// Shared scope for closure capture (parent frame's locals).
421
+ type ScopeMap = Rc<RefCell<HashMap<Arc<str>, Value>>>;
422
+
423
+ pub struct Vm {
424
+ stack: Vec<Value>,
425
+ scope: HashMap<Arc<str>, Value>,
426
+ /// Enclosing scope for closures (captured parent frame locals).
427
+ enclosing: Option<ScopeMap>,
428
+ globals: Rc<RefCell<HashMap<Arc<str>, Value>>>,
429
+ }
430
+
431
+ impl Vm {
432
+ pub fn new() -> Self {
433
+ Self {
434
+ stack: Vec::new(),
435
+ scope: HashMap::new(),
436
+ enclosing: None,
437
+ globals: Rc::new(RefCell::new(init_globals())),
438
+ }
439
+ }
440
+
441
+ pub fn get_global(&self, name: &str) -> Option<Value> {
442
+ self.globals.borrow().get(name).cloned()
443
+ }
444
+
445
+ pub fn set_global(&mut self, name: Arc<str>, value: Value) {
446
+ self.globals.borrow_mut().insert(name, value);
447
+ }
448
+
449
+ /// Names of all globals (for REPL bare-word tab completion).
450
+ pub fn global_names(&self) -> Vec<String> {
451
+ self.globals.borrow().keys().map(|k| k.as_ref().to_string()).collect()
452
+ }
453
+
454
+ fn read_u16(code: &[u8], ip: &mut usize) -> u16 {
455
+ let a = code[*ip] as u16;
456
+ let b = code[*ip + 1] as u16;
457
+ *ip += 2;
458
+ (a << 8) | b
459
+ }
460
+
461
+ fn read_i16(code: &[u8], ip: &mut usize) -> i16 {
462
+ Self::read_u16(code, ip) as i16
463
+ }
464
+
465
+ pub fn run(&mut self, chunk: &Chunk) -> Result<Value, String> {
466
+ self.run_chunk(chunk, &chunk.nested, &[])
467
+ }
468
+
469
+ fn run_chunk(
470
+ &mut self,
471
+ chunk: &Chunk,
472
+ nested: &[Chunk],
473
+ args: &[Value],
474
+ ) -> Result<Value, String> {
475
+ let code = &chunk.code;
476
+ let constants = &chunk.constants;
477
+ let names = &chunk.names;
478
+
479
+ let mut ip = 0;
480
+ let local_scope: ScopeMap = Rc::new(RefCell::new(HashMap::new()));
481
+ {
482
+ let mut ls = local_scope.borrow_mut();
483
+ let param_count = chunk.param_count as usize;
484
+ if chunk.rest_param_index != NO_REST_PARAM {
485
+ let ri = chunk.rest_param_index as usize;
486
+ for (i, name) in chunk.names.iter().take(param_count).enumerate() {
487
+ if i < ri {
488
+ let v = args.get(i).cloned().unwrap_or(Value::Null);
489
+ ls.insert(Arc::clone(name), v);
490
+ } else if i == ri {
491
+ let rest_arr: Vec<Value> = args.iter().skip(ri).cloned().collect();
492
+ ls.insert(
493
+ Arc::clone(name),
494
+ Value::Array(Rc::new(RefCell::new(rest_arr))),
495
+ );
496
+ }
497
+ }
498
+ } else {
499
+ for (i, name) in chunk.names.iter().take(param_count).enumerate() {
500
+ if let Some(v) = args.get(i) {
501
+ ls.insert(Arc::clone(name), v.clone());
502
+ }
503
+ }
504
+ }
505
+ }
506
+ let mut try_handlers: Vec<(usize, usize)> = vec![];
507
+
508
+ loop {
509
+ if ip >= code.len() {
510
+ break;
511
+ }
512
+ let op = code[ip];
513
+ ip += 1;
514
+ if op == Opcode::Nop as u8 {
515
+ continue;
516
+ }
517
+ let opcode = Opcode::from_u8(op).ok_or_else(|| format!("Unknown opcode: {}", op))?;
518
+
519
+ match opcode {
520
+ Opcode::Nop => {}
521
+ Opcode::LoadConst => {
522
+ let idx = Self::read_u16(code, &mut ip);
523
+ let c = constants
524
+ .get(idx as usize)
525
+ .ok_or_else(|| format!("Constant index out of bounds: {}", idx))?;
526
+ let v = match c {
527
+ Constant::Number(n) => Value::Number(*n),
528
+ Constant::String(s) => Value::String(Arc::clone(s)),
529
+ Constant::Bool(b) => Value::Bool(*b),
530
+ Constant::Null => Value::Null,
531
+ Constant::Closure(nested_idx) => {
532
+ let inner = nested
533
+ .get(*nested_idx)
534
+ .ok_or_else(|| "Nested chunk index out of bounds".to_string())?;
535
+ let inner_clone = inner.clone();
536
+ let globals = Rc::clone(&self.globals);
537
+ let enclosing = Some(Rc::clone(&local_scope));
538
+ Value::Function(Rc::new(move |args: &[Value]| {
539
+ let mut vm = Vm {
540
+ stack: Vec::new(),
541
+ scope: HashMap::new(),
542
+ enclosing: enclosing.clone(),
543
+ globals: Rc::clone(&globals),
544
+ };
545
+ vm.run_chunk(&inner_clone, &inner_clone.nested, args)
546
+ .unwrap_or(Value::Null)
547
+ }))
548
+ }
549
+ };
550
+ self.stack.push(v);
551
+ }
552
+ Opcode::LoadVar => {
553
+ let idx = Self::read_u16(code, &mut ip);
554
+ let name = names
555
+ .get(idx as usize)
556
+ .ok_or_else(|| format!("Name index out of bounds: {}", idx))?;
557
+ let v = local_scope
558
+ .borrow()
559
+ .get(name.as_ref())
560
+ .cloned()
561
+ .or_else(|| {
562
+ self.enclosing
563
+ .as_ref()
564
+ .and_then(|e| e.borrow().get(name.as_ref()).cloned())
565
+ })
566
+ .or_else(|| self.scope.get(name.as_ref()).cloned())
567
+ .or_else(|| self.globals.borrow().get(name.as_ref()).cloned())
568
+ .ok_or_else(|| format!("Undefined variable: {}", name))?;
569
+ self.stack.push(v);
570
+ }
571
+ Opcode::StoreVar => {
572
+ let idx = Self::read_u16(code, &mut ip);
573
+ let name = names
574
+ .get(idx as usize)
575
+ .ok_or_else(|| format!("Name index out of bounds: {}", idx))?;
576
+ let v = self
577
+ .stack
578
+ .pop()
579
+ .ok_or_else(|| "Stack underflow".to_string())?;
580
+ // Update innermost scope that has the variable (matches interpreter Scope.assign)
581
+ if local_scope.borrow().contains_key(name.as_ref()) {
582
+ local_scope.borrow_mut().insert(Arc::clone(name), v);
583
+ } else if self
584
+ .enclosing
585
+ .as_ref()
586
+ .map(|e| e.borrow().contains_key(name.as_ref()))
587
+ .unwrap_or(false)
588
+ {
589
+ let en = self.enclosing.as_ref().unwrap();
590
+ en.borrow_mut().insert(Arc::clone(name), v);
591
+ } else if self.scope.contains_key(name.as_ref()) {
592
+ self.scope.insert(Arc::clone(name), v);
593
+ } else if self.globals.borrow().contains_key(name.as_ref()) {
594
+ self.globals
595
+ .borrow_mut()
596
+ .insert(Arc::clone(name), v);
597
+ } else {
598
+ // New variable: at top level (no enclosing) store in globals so REPL persists across lines
599
+ if self.enclosing.is_none() {
600
+ self.globals.borrow_mut().insert(Arc::clone(name), v);
601
+ } else {
602
+ local_scope.borrow_mut().insert(Arc::clone(name), v);
603
+ }
604
+ }
605
+ }
606
+ Opcode::LoadGlobal => {
607
+ let idx = Self::read_u16(code, &mut ip);
608
+ let name = names
609
+ .get(idx as usize)
610
+ .ok_or_else(|| format!("Name index out of bounds: {}", idx))?;
611
+ let v = self
612
+ .globals
613
+ .borrow()
614
+ .get(name.as_ref())
615
+ .cloned()
616
+ .ok_or_else(|| format!("Undefined global: {}", name))?;
617
+ self.stack.push(v);
618
+ }
619
+ Opcode::StoreGlobal => {
620
+ let idx = Self::read_u16(code, &mut ip);
621
+ let name = names
622
+ .get(idx as usize)
623
+ .ok_or_else(|| format!("Name index out of bounds: {}", idx))?;
624
+ let v = self
625
+ .stack
626
+ .pop()
627
+ .ok_or_else(|| "Stack underflow".to_string())?;
628
+ self.globals
629
+ .borrow_mut()
630
+ .insert(Arc::clone(name), v);
631
+ }
632
+ Opcode::Pop => {
633
+ self.stack.pop().ok_or_else(|| "Stack underflow".to_string())?;
634
+ }
635
+ Opcode::PopN => {
636
+ let n = Self::read_u16(code, &mut ip) as usize;
637
+ for _ in 0..n {
638
+ self.stack.pop().ok_or_else(|| "Stack underflow".to_string())?;
639
+ }
640
+ }
641
+ Opcode::Dup => {
642
+ let v = self
643
+ .stack
644
+ .last()
645
+ .ok_or_else(|| "Stack underflow".to_string())?
646
+ .clone();
647
+ self.stack.push(v);
648
+ }
649
+ Opcode::Call => {
650
+ let argc = Self::read_u16(code, &mut ip) as usize;
651
+ let mut args = Vec::with_capacity(argc);
652
+ for _ in 0..argc {
653
+ args.push(
654
+ self.stack
655
+ .pop()
656
+ .ok_or_else(|| "Stack underflow in call".to_string())?,
657
+ );
658
+ }
659
+ args.reverse();
660
+ let callee = self
661
+ .stack
662
+ .pop()
663
+ .ok_or_else(|| "Stack underflow: no callee".to_string())?;
664
+ let f = match &callee {
665
+ Value::Function(f) => Rc::clone(f),
666
+ Value::Object(o) => {
667
+ if let Some(Value::Function(call_fn)) = o.borrow().get(&Arc::from("__call")) {
668
+ Rc::clone(call_fn)
669
+ } else {
670
+ return Err(format!(
671
+ "Call of non-function: {}",
672
+ callee.type_name()
673
+ ));
674
+ }
675
+ }
676
+ _ => {
677
+ return Err(format!(
678
+ "Call of non-function: {}",
679
+ callee.type_name()
680
+ ));
681
+ }
682
+ };
683
+ let result = f(&args);
684
+ self.stack.push(result);
685
+ }
686
+ Opcode::CallSpread => {
687
+ let callee = self
688
+ .stack
689
+ .pop()
690
+ .ok_or_else(|| "Stack underflow: no callee in CallSpread".to_string())?;
691
+ let args_array = self
692
+ .stack
693
+ .pop()
694
+ .ok_or_else(|| "Stack underflow in CallSpread".to_string())?;
695
+ let args: Vec<Value> = match &args_array {
696
+ Value::Array(a) => a.borrow().clone(),
697
+ _ => {
698
+ return Err(format!(
699
+ "CallSpread: args must be array, got {}",
700
+ args_array.to_display_string()
701
+ ));
702
+ }
703
+ };
704
+ let f = match &callee {
705
+ Value::Function(f) => Rc::clone(f),
706
+ Value::Object(o) => {
707
+ if let Some(Value::Function(call_fn)) = o.borrow().get(&Arc::from("__call")) {
708
+ Rc::clone(call_fn)
709
+ } else {
710
+ return Err(format!(
711
+ "Call of non-function: {}",
712
+ callee.type_name()
713
+ ));
714
+ }
715
+ }
716
+ _ => {
717
+ return Err(format!(
718
+ "Call of non-function: {}",
719
+ callee.type_name()
720
+ ));
721
+ }
722
+ };
723
+ let result = f(&args);
724
+ self.stack.push(result);
725
+ }
726
+ Opcode::Return => {
727
+ let v = self.stack.pop().unwrap_or(Value::Null);
728
+ return Ok(v);
729
+ }
730
+ Opcode::Jump => {
731
+ let offset = Self::read_i16(code, &mut ip) as isize;
732
+ ip = (ip as isize + offset).max(0) as usize;
733
+ }
734
+ Opcode::JumpIfFalse => {
735
+ let offset = Self::read_i16(code, &mut ip) as isize;
736
+ let v = self
737
+ .stack
738
+ .pop()
739
+ .ok_or_else(|| "Stack underflow".to_string())?;
740
+ if !v.is_truthy() {
741
+ ip = (ip as isize + offset).max(0) as usize;
742
+ }
743
+ }
744
+ Opcode::JumpBack => {
745
+ let dist = Self::read_u16(code, &mut ip) as usize;
746
+ ip = ip.saturating_sub(dist);
747
+ }
748
+ Opcode::BinOp => {
749
+ let op_u8 = Self::read_u16(code, &mut ip) as u8;
750
+ let r = self
751
+ .stack
752
+ .pop()
753
+ .ok_or_else(|| "Stack underflow".to_string())?;
754
+ let l = self
755
+ .stack
756
+ .pop()
757
+ .ok_or_else(|| "Stack underflow".to_string())?;
758
+ let op = u8_to_binop(op_u8)
759
+ .ok_or_else(|| format!("Unknown binop: {}", op_u8))?;
760
+ let result = eval_binop(op, &l, &r)?;
761
+ self.stack.push(result);
762
+ }
763
+ Opcode::UnaryOp => {
764
+ let op_u8 = Self::read_u16(code, &mut ip) as u8;
765
+ let o = self
766
+ .stack
767
+ .pop()
768
+ .ok_or_else(|| "Stack underflow".to_string())?;
769
+ let op = u8_to_unaryop(op_u8)
770
+ .ok_or_else(|| format!("Unknown unary op: {}", op_u8))?;
771
+ let result = eval_unary(op, &o)?;
772
+ self.stack.push(result);
773
+ }
774
+ Opcode::GetMember => {
775
+ let idx = Self::read_u16(code, &mut ip);
776
+ let key = names
777
+ .get(idx as usize)
778
+ .ok_or_else(|| "Name index out of bounds".to_string())?;
779
+ let obj = self
780
+ .stack
781
+ .pop()
782
+ .ok_or_else(|| "Stack underflow".to_string())?;
783
+ let v = get_member(&obj, key)?;
784
+ self.stack.push(v);
785
+ }
786
+ Opcode::GetMemberOptional => {
787
+ let idx = Self::read_u16(code, &mut ip);
788
+ let key = names
789
+ .get(idx as usize)
790
+ .ok_or_else(|| "Name index out of bounds".to_string())?;
791
+ let obj = self
792
+ .stack
793
+ .pop()
794
+ .ok_or_else(|| "Stack underflow".to_string())?;
795
+ let v = get_member(&obj, key).unwrap_or(Value::Null);
796
+ self.stack.push(v);
797
+ }
798
+ Opcode::SetMember => {
799
+ let idx = Self::read_u16(code, &mut ip);
800
+ let key = names
801
+ .get(idx as usize)
802
+ .ok_or_else(|| "Name index out of bounds".to_string())?;
803
+ let val = self
804
+ .stack
805
+ .pop()
806
+ .ok_or_else(|| "Stack underflow".to_string())?;
807
+ let obj = self
808
+ .stack
809
+ .pop()
810
+ .ok_or_else(|| "Stack underflow".to_string())?;
811
+ set_member(&obj, key, val.clone())?;
812
+ self.stack.push(val); // assignment yields value
813
+ }
814
+ Opcode::GetIndex => {
815
+ let idx_val = self
816
+ .stack
817
+ .pop()
818
+ .ok_or_else(|| "Stack underflow".to_string())?;
819
+ let obj = self
820
+ .stack
821
+ .pop()
822
+ .ok_or_else(|| "Stack underflow".to_string())?;
823
+ let v = get_index(&obj, &idx_val)?;
824
+ self.stack.push(v);
825
+ }
826
+ Opcode::SetIndex => {
827
+ // Stack: [obj, idx, val, val] (Dup of val for expression result).
828
+ // Pop val (dup), val, idx, obj; use (obj, idx, val) for set_index; leave val on stack.
829
+ let dup_val = self
830
+ .stack
831
+ .pop()
832
+ .ok_or_else(|| "Stack underflow".to_string())?;
833
+ let val = self
834
+ .stack
835
+ .pop()
836
+ .ok_or_else(|| "Stack underflow".to_string())?;
837
+ let idx_val = self
838
+ .stack
839
+ .pop()
840
+ .ok_or_else(|| "Stack underflow".to_string())?;
841
+ let obj = self
842
+ .stack
843
+ .pop()
844
+ .ok_or_else(|| "Stack underflow".to_string())?;
845
+ set_index(&obj, &idx_val, val.clone())?;
846
+ self.stack.push(dup_val); // assignment yields the assigned value
847
+ }
848
+ Opcode::NewArray => {
849
+ let n = Self::read_u16(code, &mut ip) as usize;
850
+ let mut elems = Vec::with_capacity(n);
851
+ for _ in 0..n {
852
+ elems.push(
853
+ self.stack
854
+ .pop()
855
+ .ok_or_else(|| "Stack underflow".to_string())?,
856
+ );
857
+ }
858
+ elems.reverse();
859
+ self.stack
860
+ .push(Value::Array(Rc::new(RefCell::new(elems))));
861
+ }
862
+ Opcode::NewObject => {
863
+ let n = Self::read_u16(code, &mut ip) as usize;
864
+ let mut map = HashMap::new();
865
+ for _ in 0..n {
866
+ let val = self
867
+ .stack
868
+ .pop()
869
+ .ok_or_else(|| "Stack underflow".to_string())?;
870
+ let key_val = self
871
+ .stack
872
+ .pop()
873
+ .ok_or_else(|| "Stack underflow".to_string())?;
874
+ let key = key_val.to_display_string().into();
875
+ map.insert(key, val);
876
+ }
877
+ self.stack
878
+ .push(Value::Object(Rc::new(RefCell::new(map))));
879
+ }
880
+ Opcode::EnterTry => {
881
+ let offset = Self::read_u16(code, &mut ip) as usize;
882
+ let catch_ip = ip + offset;
883
+ try_handlers.push((catch_ip, self.stack.len()));
884
+ }
885
+ Opcode::ExitTry => {
886
+ try_handlers.pop();
887
+ }
888
+ Opcode::ConcatArray => {
889
+ let right = self
890
+ .stack
891
+ .pop()
892
+ .ok_or_else(|| "Stack underflow".to_string())?;
893
+ let left = self
894
+ .stack
895
+ .pop()
896
+ .ok_or_else(|| "Stack underflow".to_string())?;
897
+ let (mut a, b) = (
898
+ match &left {
899
+ Value::Array(arr) => arr.borrow().clone(),
900
+ _ => {
901
+ return Err(format!(
902
+ "ConcatArray: left must be array, got {}",
903
+ left.to_display_string()
904
+ ));
905
+ }
906
+ },
907
+ match &right {
908
+ Value::Array(arr) => arr.borrow().clone(),
909
+ _ => {
910
+ return Err(format!(
911
+ "ConcatArray: right must be array, got {}",
912
+ right.to_display_string()
913
+ ));
914
+ }
915
+ },
916
+ );
917
+ a.extend(b);
918
+ self.stack.push(Value::Array(Rc::new(RefCell::new(a))));
919
+ }
920
+ Opcode::MergeObject => {
921
+ let right = self
922
+ .stack
923
+ .pop()
924
+ .ok_or_else(|| "Stack underflow".to_string())?;
925
+ let left = self
926
+ .stack
927
+ .pop()
928
+ .ok_or_else(|| "Stack underflow".to_string())?;
929
+ let mut merged: HashMap<Arc<str>, Value> = HashMap::new();
930
+ if let Value::Object(l) = &left {
931
+ merged.extend(l.borrow().iter().map(|(k, v)| (Arc::clone(k), v.clone())));
932
+ } else {
933
+ return Err(format!(
934
+ "MergeObject: left must be object, got {}",
935
+ left.to_display_string()
936
+ ));
937
+ }
938
+ if let Value::Object(r) = &right {
939
+ for (k, v) in r.borrow().iter() {
940
+ merged.insert(Arc::clone(k), v.clone());
941
+ }
942
+ } else {
943
+ return Err(format!(
944
+ "MergeObject: right must be object, got {}",
945
+ right.to_display_string()
946
+ ));
947
+ }
948
+ self.stack
949
+ .push(Value::Object(Rc::new(RefCell::new(merged))));
950
+ }
951
+ Opcode::ArraySortNumeric => {
952
+ let operand = Self::read_u16(code, &mut ip);
953
+ let asc = operand == 0;
954
+ let arr = self
955
+ .stack
956
+ .pop()
957
+ .ok_or_else(|| "Stack underflow".to_string())?;
958
+ let result = if asc {
959
+ arr_builtins::sort_numeric_asc(&arr)
960
+ } else {
961
+ arr_builtins::sort_numeric_desc(&arr)
962
+ };
963
+ self.stack.push(result);
964
+ }
965
+ Opcode::ArraySortByProperty => {
966
+ let prop_idx = Self::read_u16(code, &mut ip);
967
+ let asc = Self::read_u16(code, &mut ip) == 0;
968
+ let arr = self
969
+ .stack
970
+ .pop()
971
+ .ok_or_else(|| "Stack underflow".to_string())?;
972
+ let prop = constants
973
+ .get(prop_idx as usize)
974
+ .and_then(|c| {
975
+ if let Constant::String(s) = c {
976
+ Some(s.as_ref())
977
+ } else {
978
+ None
979
+ }
980
+ })
981
+ .unwrap_or("");
982
+ let result = arr_builtins::sort_by_property_numeric(&arr, prop, asc);
983
+ self.stack.push(result);
984
+ }
985
+ Opcode::ArrayMapIdentity => {
986
+ let arr = self
987
+ .stack
988
+ .pop()
989
+ .ok_or_else(|| "Stack underflow".to_string())?;
990
+ let result = match &arr {
991
+ Value::Array(a) => {
992
+ Value::Array(Rc::new(RefCell::new(a.borrow().clone())))
993
+ }
994
+ _ => Value::Null,
995
+ };
996
+ self.stack.push(result);
997
+ }
998
+ Opcode::ArrayMapBinOp => {
999
+ let binop_u8 = code[ip];
1000
+ ip += 1;
1001
+ let const_idx = Self::read_u16(code, &mut ip);
1002
+ let param_left = code[ip] == 0; // 0 = param on left (x op const), 1 = param on right (const op x)
1003
+ ip += 1;
1004
+ let binop = u8_to_binop(binop_u8)
1005
+ .ok_or_else(|| format!("Unknown binop in ArrayMapBinOp: {}", binop_u8))?;
1006
+ let arr = self
1007
+ .stack
1008
+ .pop()
1009
+ .ok_or_else(|| "Stack underflow".to_string())?;
1010
+ let const_val = constants
1011
+ .get(const_idx as usize)
1012
+ .map(|c| c.to_value())
1013
+ .unwrap_or(Value::Null);
1014
+ let result = if let Value::Array(a) = &arr {
1015
+ let arr_borrow = a.borrow();
1016
+ let mapped: Vec<Value> = arr_borrow
1017
+ .iter()
1018
+ .map(|v| {
1019
+ let l: Value = if param_left { (*v).clone() } else { const_val.clone() };
1020
+ let r: Value = if param_left { const_val.clone() } else { (*v).clone() };
1021
+ eval_binop(binop, &l, &r).unwrap_or(Value::Null)
1022
+ })
1023
+ .collect();
1024
+ Value::Array(Rc::new(RefCell::new(mapped)))
1025
+ } else {
1026
+ Value::Null
1027
+ };
1028
+ self.stack.push(result);
1029
+ }
1030
+ Opcode::ArrayFilterBinOp => {
1031
+ let binop_u8 = code[ip];
1032
+ ip += 1;
1033
+ let const_idx = Self::read_u16(code, &mut ip);
1034
+ let param_left = code[ip] == 0; // 0 = param on left (x op const), 1 = param on right (const op x)
1035
+ ip += 1;
1036
+ let binop = u8_to_binop(binop_u8)
1037
+ .ok_or_else(|| format!("Unknown binop in ArrayFilterBinOp: {}", binop_u8))?;
1038
+ let arr = self
1039
+ .stack
1040
+ .pop()
1041
+ .ok_or_else(|| "Stack underflow".to_string())?;
1042
+ let const_val = constants
1043
+ .get(const_idx as usize)
1044
+ .map(|c| c.to_value())
1045
+ .unwrap_or(Value::Null);
1046
+ let result = if let Value::Array(a) = &arr {
1047
+ let arr_borrow = a.borrow();
1048
+ let filtered: Vec<Value> = arr_borrow
1049
+ .iter()
1050
+ .filter(|v| {
1051
+ let l: Value = if param_left { (*v).clone() } else { const_val.clone() };
1052
+ let r: Value = if param_left { const_val.clone() } else { (*v).clone() };
1053
+ let b = eval_binop(binop, &l, &r).unwrap_or(Value::Null);
1054
+ b.is_truthy()
1055
+ })
1056
+ .cloned()
1057
+ .collect();
1058
+ Value::Array(Rc::new(RefCell::new(filtered)))
1059
+ } else {
1060
+ Value::Null
1061
+ };
1062
+ self.stack.push(result);
1063
+ }
1064
+ Opcode::Throw => {
1065
+ let v = self
1066
+ .stack
1067
+ .pop()
1068
+ .ok_or_else(|| "Stack underflow".to_string())?;
1069
+ let (catch_ip, stack_len) = try_handlers
1070
+ .pop()
1071
+ .ok_or_else(|| format!("Uncaught throw: {}", v.to_display_string()))?;
1072
+ self.stack.truncate(stack_len);
1073
+ self.stack.push(v);
1074
+ ip = catch_ip;
1075
+ }
1076
+ Opcode::LoadNativeExport => {
1077
+ let spec_idx = Self::read_u16(code, &mut ip);
1078
+ let export_idx = Self::read_u16(code, &mut ip);
1079
+ let spec = match constants.get(spec_idx as usize) {
1080
+ Some(Constant::String(s)) => s.as_ref(),
1081
+ _ => {
1082
+ return Err("LoadNativeExport: spec constant out of bounds or not string".to_string());
1083
+ }
1084
+ };
1085
+ let export_name = match constants.get(export_idx as usize) {
1086
+ Some(Constant::String(s)) => s.as_ref(),
1087
+ _ => {
1088
+ return Err("LoadNativeExport: export_name constant out of bounds or not string".to_string());
1089
+ }
1090
+ };
1091
+ let v = get_builtin_export(spec, export_name).ok_or_else(|| {
1092
+ format!(
1093
+ "Built-in module '{}' does not export '{}' or feature not enabled. Rebuild with --features full (or fs, http, process, ws).",
1094
+ spec, export_name
1095
+ )
1096
+ })?;
1097
+ self.stack.push(v);
1098
+ }
1099
+ Opcode::Closure | Opcode::LoadThis => {
1100
+ return Err(format!("Unhandled opcode: {:?}", opcode));
1101
+ }
1102
+ }
1103
+ }
1104
+
1105
+ Ok(self.stack.pop().unwrap_or(Value::Null))
1106
+ }
1107
+ }
1108
+
1109
+ impl Default for Vm {
1110
+ fn default() -> Self {
1111
+ Self::new()
1112
+ }
1113
+ }
1114
+
1115
+ fn eval_binop(op: BinOp, l: &Value, r: &Value) -> Result<Value, String> {
1116
+ use tish_ast::BinOp::*;
1117
+ use tish_core::Value::*;
1118
+ let ln = l.as_number().unwrap_or(f64::NAN);
1119
+ let rn = r.as_number().unwrap_or(f64::NAN);
1120
+ match op {
1121
+ Add => {
1122
+ if matches!(l, Value::String(_)) || matches!(r, Value::String(_)) {
1123
+ Ok(String(format!("{}{}", l.to_display_string(), r.to_display_string()).into()))
1124
+ } else {
1125
+ Ok(Number(ln + rn))
1126
+ }
1127
+ }
1128
+ Sub => Ok(Number(ln - rn)),
1129
+ Mul => Ok(Number(ln * rn)),
1130
+ Div => Ok(Number(if rn == 0.0 { f64::NAN } else { ln / rn })),
1131
+ Mod => Ok(Number(if rn == 0.0 { f64::NAN } else { ln % rn })),
1132
+ Pow => Ok(Number(ln.powf(rn))),
1133
+ Eq => Ok(Bool(l.strict_eq(r))),
1134
+ Ne => Ok(Bool(!l.strict_eq(r))),
1135
+ StrictEq => Ok(Bool(l.strict_eq(r))),
1136
+ StrictNe => Ok(Bool(!l.strict_eq(r))),
1137
+ Lt => Ok(Bool(ln < rn)),
1138
+ Le => Ok(Bool(ln <= rn)),
1139
+ Gt => Ok(Bool(ln > rn)),
1140
+ Ge => Ok(Bool(ln >= rn)),
1141
+ And => Ok(Bool(l.is_truthy() && r.is_truthy())),
1142
+ Or => Ok(Bool(l.is_truthy() || r.is_truthy())),
1143
+ BitAnd => Ok(Number((ln as i32 & rn as i32) as f64)),
1144
+ BitOr => Ok(Number((ln as i32 | rn as i32) as f64)),
1145
+ BitXor => Ok(Number((ln as i32 ^ rn as i32) as f64)),
1146
+ Shl => Ok(Number(((ln as i32) << (rn as i32)) as f64)),
1147
+ Shr => Ok(Number(((ln as i32) >> (rn as i32)) as f64)),
1148
+ In => {
1149
+ let key_s: Arc<str> = l.to_display_string().into();
1150
+ Ok(Bool(match r {
1151
+ Value::Object(m) => m.borrow().contains_key(&key_s),
1152
+ Value::Array(a) => {
1153
+ if key_s.as_ref() == "length" {
1154
+ true
1155
+ } else if let Ok(idx) = key_s.parse::<usize>() {
1156
+ idx < a.borrow().len()
1157
+ } else {
1158
+ false
1159
+ }
1160
+ }
1161
+ _ => false,
1162
+ }))
1163
+ }
1164
+ }
1165
+ }
1166
+
1167
+ fn eval_unary(op: UnaryOp, o: &Value) -> Result<Value, String> {
1168
+ use tish_ast::UnaryOp::*;
1169
+ use tish_core::Value::*;
1170
+ match op {
1171
+ Not => Ok(Bool(!o.is_truthy())),
1172
+ Neg => Ok(Number(-o.as_number().unwrap_or(f64::NAN))),
1173
+ Pos => Ok(Number(o.as_number().unwrap_or(f64::NAN))),
1174
+ BitNot => Ok(Number(!(o.as_number().unwrap_or(0.0) as i32) as f64)),
1175
+ Void => Ok(Null),
1176
+ }
1177
+ }
1178
+
1179
+ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
1180
+ match obj {
1181
+ Value::Object(m) => {
1182
+ let map = m.borrow();
1183
+ map.get(key.as_ref()).cloned().ok_or_else(|| {
1184
+ format!("Property '{}' not found", key)
1185
+ })
1186
+ }
1187
+ Value::Array(a) => {
1188
+ let key_s = key.as_ref();
1189
+ if let Ok(idx) = key_s.parse::<usize>() {
1190
+ let arr = a.borrow();
1191
+ return arr.get(idx).cloned().ok_or_else(|| "Index out of bounds".to_string());
1192
+ }
1193
+ if key_s == "length" {
1194
+ return Ok(Value::Number(a.borrow().len() as f64));
1195
+ }
1196
+ let a_clone = Rc::clone(a);
1197
+ let method: ArrayMethodFn = match key_s {
1198
+ "push" => Rc::new(move |args: &[Value]| arr_builtins::push(&Value::Array(Rc::clone(&a_clone)), args)),
1199
+ "pop" => Rc::new(move |_args: &[Value]| arr_builtins::pop(&Value::Array(Rc::clone(&a_clone)))),
1200
+ "shift" => Rc::new(move |_args: &[Value]| arr_builtins::shift(&Value::Array(Rc::clone(&a_clone)))),
1201
+ "unshift" => Rc::new(move |args: &[Value]| arr_builtins::unshift(&Value::Array(Rc::clone(&a_clone)), args)),
1202
+ "reverse" => Rc::new(move |_args: &[Value]| arr_builtins::reverse(&Value::Array(Rc::clone(&a_clone)))),
1203
+ "shuffle" => Rc::new(move |_args: &[Value]| arr_builtins::shuffle(&Value::Array(Rc::clone(&a_clone)))),
1204
+ "slice" => Rc::new(move |args: &[Value]| {
1205
+ let start = args.first().unwrap_or(&Value::Null);
1206
+ let end = args.get(1).unwrap_or(&Value::Null);
1207
+ arr_builtins::slice(&Value::Array(Rc::clone(&a_clone)), start, end)
1208
+ }),
1209
+ "concat" => Rc::new(move |args: &[Value]| arr_builtins::concat(&Value::Array(Rc::clone(&a_clone)), args)),
1210
+ "join" => Rc::new(move |args: &[Value]| {
1211
+ let sep = args.first().unwrap_or(&Value::Null);
1212
+ arr_builtins::join(&Value::Array(Rc::clone(&a_clone)), sep)
1213
+ }),
1214
+ "indexOf" => Rc::new(move |args: &[Value]| {
1215
+ let search = args.first().unwrap_or(&Value::Null);
1216
+ arr_builtins::index_of(&Value::Array(Rc::clone(&a_clone)), search)
1217
+ }),
1218
+ "includes" => Rc::new(move |args: &[Value]| {
1219
+ let search = args.first().unwrap_or(&Value::Null);
1220
+ let from = args.get(1);
1221
+ arr_builtins::includes(&Value::Array(Rc::clone(&a_clone)), search, from)
1222
+ }),
1223
+ "map" => Rc::new(move |args: &[Value]| {
1224
+ let cb = args.first().cloned().unwrap_or(Value::Null);
1225
+ arr_builtins::map(&Value::Array(Rc::clone(&a_clone)), &cb)
1226
+ }),
1227
+ "filter" => Rc::new(move |args: &[Value]| {
1228
+ let cb = args.first().cloned().unwrap_or(Value::Null);
1229
+ arr_builtins::filter(&Value::Array(Rc::clone(&a_clone)), &cb)
1230
+ }),
1231
+ "reduce" => Rc::new(move |args: &[Value]| {
1232
+ let cb = args.first().cloned().unwrap_or(Value::Null);
1233
+ let init = args.get(1).cloned().unwrap_or(Value::Null);
1234
+ arr_builtins::reduce(&Value::Array(Rc::clone(&a_clone)), &cb, &init)
1235
+ }),
1236
+ "forEach" => Rc::new(move |args: &[Value]| {
1237
+ let cb = args.first().cloned().unwrap_or(Value::Null);
1238
+ arr_builtins::for_each(&Value::Array(Rc::clone(&a_clone)), &cb)
1239
+ }),
1240
+ "find" => Rc::new(move |args: &[Value]| {
1241
+ let cb = args.first().cloned().unwrap_or(Value::Null);
1242
+ arr_builtins::find(&Value::Array(Rc::clone(&a_clone)), &cb)
1243
+ }),
1244
+ "findIndex" => Rc::new(move |args: &[Value]| {
1245
+ let cb = args.first().cloned().unwrap_or(Value::Null);
1246
+ arr_builtins::find_index(&Value::Array(Rc::clone(&a_clone)), &cb)
1247
+ }),
1248
+ "some" => Rc::new(move |args: &[Value]| {
1249
+ let cb = args.first().cloned().unwrap_or(Value::Null);
1250
+ arr_builtins::some(&Value::Array(Rc::clone(&a_clone)), &cb)
1251
+ }),
1252
+ "every" => Rc::new(move |args: &[Value]| {
1253
+ let cb = args.first().cloned().unwrap_or(Value::Null);
1254
+ arr_builtins::every(&Value::Array(Rc::clone(&a_clone)), &cb)
1255
+ }),
1256
+ "flat" => Rc::new(move |args: &[Value]| {
1257
+ let depth = args.first().unwrap_or(&Value::Number(1.0));
1258
+ arr_builtins::flat(&Value::Array(Rc::clone(&a_clone)), depth)
1259
+ }),
1260
+ "flatMap" => Rc::new(move |args: &[Value]| {
1261
+ let cb = args.first().cloned().unwrap_or(Value::Null);
1262
+ arr_builtins::flat_map(&Value::Array(Rc::clone(&a_clone)), &cb)
1263
+ }),
1264
+ "sort" => Rc::new(move |args: &[Value]| {
1265
+ let cmp = args.first();
1266
+ if let Some(Value::Function(_)) = cmp {
1267
+ arr_builtins::sort_with_comparator(&Value::Array(Rc::clone(&a_clone)), cmp.unwrap())
1268
+ } else {
1269
+ arr_builtins::sort_default(&Value::Array(Rc::clone(&a_clone)))
1270
+ }
1271
+ }),
1272
+ "splice" => Rc::new(move |args: &[Value]| {
1273
+ let start = args.first().unwrap_or(&Value::Null);
1274
+ let delete_count = args.get(1).map(|v| v as &Value);
1275
+ let items: Vec<Value> = args.get(2..).unwrap_or(&[]).to_vec();
1276
+ arr_builtins::splice(&Value::Array(Rc::clone(&a_clone)), start, delete_count, &items)
1277
+ }),
1278
+ _ => return Err(format!("Property '{}' not found", key)),
1279
+ };
1280
+ Ok(Value::Function(method))
1281
+ }
1282
+ Value::String(s) => {
1283
+ let key_s = key.as_ref();
1284
+ if key_s == "length" {
1285
+ return Ok(Value::Number(s.chars().count() as f64));
1286
+ }
1287
+ let s_clone: Arc<str> = Arc::clone(s);
1288
+ let method: ArrayMethodFn = match key_s {
1289
+ "indexOf" => Rc::new(move |args: &[Value]| {
1290
+ let search = args.first().unwrap_or(&Value::Null);
1291
+ let from = args.get(1);
1292
+ str_builtins::index_of(&Value::String(Arc::clone(&s_clone)), search, from)
1293
+ }),
1294
+ "includes" => Rc::new(move |args: &[Value]| {
1295
+ let search = args.first().unwrap_or(&Value::Null);
1296
+ let from = args.get(1);
1297
+ str_builtins::includes(&Value::String(Arc::clone(&s_clone)), search, from)
1298
+ }),
1299
+ "slice" => Rc::new(move |args: &[Value]| {
1300
+ let start = args.first().unwrap_or(&Value::Null);
1301
+ let end = args.get(1).unwrap_or(&Value::Null);
1302
+ str_builtins::slice(&Value::String(Arc::clone(&s_clone)), start, end)
1303
+ }),
1304
+ "substring" => Rc::new(move |args: &[Value]| {
1305
+ let start = args.first().unwrap_or(&Value::Null);
1306
+ let end = args.get(1).unwrap_or(&Value::Null);
1307
+ str_builtins::substring(&Value::String(Arc::clone(&s_clone)), start, end)
1308
+ }),
1309
+ "split" => Rc::new(move |args: &[Value]| {
1310
+ let sep = args.first().unwrap_or(&Value::Null);
1311
+ str_builtins::split(&Value::String(Arc::clone(&s_clone)), sep)
1312
+ }),
1313
+ "trim" => Rc::new(move |_args: &[Value]| str_builtins::trim(&Value::String(Arc::clone(&s_clone)))),
1314
+ "toUpperCase" => Rc::new(move |_args: &[Value]| str_builtins::to_upper_case(&Value::String(Arc::clone(&s_clone)))),
1315
+ "toLowerCase" => Rc::new(move |_args: &[Value]| str_builtins::to_lower_case(&Value::String(Arc::clone(&s_clone)))),
1316
+ "startsWith" => Rc::new(move |args: &[Value]| {
1317
+ let search = args.first().unwrap_or(&Value::Null);
1318
+ str_builtins::starts_with(&Value::String(Arc::clone(&s_clone)), search)
1319
+ }),
1320
+ "endsWith" => Rc::new(move |args: &[Value]| {
1321
+ let search = args.first().unwrap_or(&Value::Null);
1322
+ str_builtins::ends_with(&Value::String(Arc::clone(&s_clone)), search)
1323
+ }),
1324
+ "replace" => Rc::new(move |args: &[Value]| {
1325
+ let search = args.first().unwrap_or(&Value::Null);
1326
+ let replacement = args.get(1).unwrap_or(&Value::Null);
1327
+ str_builtins::replace(&Value::String(Arc::clone(&s_clone)), search, replacement)
1328
+ }),
1329
+ "replaceAll" => Rc::new(move |args: &[Value]| {
1330
+ let search = args.first().unwrap_or(&Value::Null);
1331
+ let replacement = args.get(1).unwrap_or(&Value::Null);
1332
+ str_builtins::replace_all(&Value::String(Arc::clone(&s_clone)), search, replacement)
1333
+ }),
1334
+ "charAt" => Rc::new(move |args: &[Value]| {
1335
+ let idx = args.first().unwrap_or(&Value::Null);
1336
+ str_builtins::char_at(&Value::String(Arc::clone(&s_clone)), idx)
1337
+ }),
1338
+ "charCodeAt" => Rc::new(move |args: &[Value]| {
1339
+ let idx = args.first().unwrap_or(&Value::Null);
1340
+ str_builtins::char_code_at(&Value::String(Arc::clone(&s_clone)), idx)
1341
+ }),
1342
+ "repeat" => Rc::new(move |args: &[Value]| {
1343
+ let count = args.first().unwrap_or(&Value::Null);
1344
+ str_builtins::repeat(&Value::String(Arc::clone(&s_clone)), count)
1345
+ }),
1346
+ "padStart" => Rc::new(move |args: &[Value]| {
1347
+ let target_len = args.first().unwrap_or(&Value::Null);
1348
+ let pad = args.get(1).unwrap_or(&Value::Null);
1349
+ str_builtins::pad_start(&Value::String(Arc::clone(&s_clone)), target_len, pad)
1350
+ }),
1351
+ "padEnd" => Rc::new(move |args: &[Value]| {
1352
+ let target_len = args.first().unwrap_or(&Value::Null);
1353
+ let pad = args.get(1).unwrap_or(&Value::Null);
1354
+ str_builtins::pad_end(&Value::String(Arc::clone(&s_clone)), target_len, pad)
1355
+ }),
1356
+ _ => return Err(format!("Property '{}' not found", key)),
1357
+ };
1358
+ Ok(Value::Function(method))
1359
+ }
1360
+ _ => Err(format!("Cannot read property '{}' of {}", key, obj.type_name())),
1361
+ }
1362
+ }
1363
+
1364
+ fn set_member(obj: &Value, key: &Arc<str>, val: Value) -> Result<(), String> {
1365
+ match obj {
1366
+ Value::Object(m) => {
1367
+ m.borrow_mut().insert(Arc::clone(key), val);
1368
+ Ok(())
1369
+ }
1370
+ Value::Array(a) => {
1371
+ let idx: usize = key.as_ref().parse().unwrap_or(0);
1372
+ let mut arr = a.borrow_mut();
1373
+ if idx < arr.len() {
1374
+ arr[idx] = val;
1375
+ } else {
1376
+ arr.resize(idx + 1, Value::Null);
1377
+ arr[idx] = val;
1378
+ }
1379
+ Ok(())
1380
+ }
1381
+ _ => Err(format!("Cannot set property of {}", obj.type_name())),
1382
+ }
1383
+ }
1384
+
1385
+ fn get_index(obj: &Value, idx: &Value) -> Result<Value, String> {
1386
+ let key: Arc<str> = idx.to_display_string().into();
1387
+ get_member(obj, &key)
1388
+ }
1389
+
1390
+ fn set_index(obj: &Value, idx: &Value, val: Value) -> Result<(), String> {
1391
+ let key: Arc<str> = idx.to_display_string().into();
1392
+ set_member(obj, &key, val)
1393
+ }
1394
+
1395
+ /// Run a chunk and return the result.
1396
+ pub fn run(chunk: &Chunk) -> Result<Value, String> {
1397
+ let mut vm = Vm::new();
1398
+ vm.run(chunk)
1399
+ }