@tishlang/tish 1.6.0 → 1.8.0

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 (113) hide show
  1. package/Cargo.toml +2 -0
  2. package/README.md +2 -0
  3. package/bin/tish +0 -0
  4. package/crates/js_to_tish/src/error.rs +2 -8
  5. package/crates/js_to_tish/src/transform/expr.rs +128 -137
  6. package/crates/js_to_tish/src/transform/stmt.rs +62 -32
  7. package/crates/tish/Cargo.toml +15 -5
  8. package/crates/tish/src/cargo_native_registry.rs +29 -0
  9. package/crates/tish/src/cli_help.rs +92 -39
  10. package/crates/tish/src/main.rs +172 -86
  11. package/crates/tish/src/repl_completion.rs +3 -3
  12. package/crates/tish/tests/cargo_example_compile.rs +4 -2
  13. package/crates/tish/tests/integration_test.rs +216 -54
  14. package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
  15. package/crates/tish/tests/shortcircuit.rs +20 -5
  16. package/crates/tish_ast/src/ast.rs +92 -23
  17. package/crates/tish_build_utils/Cargo.toml +4 -0
  18. package/crates/tish_build_utils/src/lib.rs +136 -8
  19. package/crates/tish_builtins/Cargo.toml +5 -1
  20. package/crates/tish_builtins/src/array.rs +65 -33
  21. package/crates/tish_builtins/src/construct.rs +34 -39
  22. package/crates/tish_builtins/src/globals.rs +42 -26
  23. package/crates/tish_builtins/src/helpers.rs +2 -1
  24. package/crates/tish_builtins/src/lib.rs +5 -5
  25. package/crates/tish_builtins/src/math.rs +5 -3
  26. package/crates/tish_builtins/src/object.rs +3 -2
  27. package/crates/tish_builtins/src/string.rs +144 -22
  28. package/crates/tish_bytecode/src/chunk.rs +0 -1
  29. package/crates/tish_bytecode/src/compiler.rs +173 -71
  30. package/crates/tish_bytecode/src/opcode.rs +24 -6
  31. package/crates/tish_bytecode/src/peephole.rs +2 -2
  32. package/crates/tish_compile/Cargo.toml +1 -0
  33. package/crates/tish_compile/src/codegen.rs +1621 -453
  34. package/crates/tish_compile/src/infer.rs +75 -19
  35. package/crates/tish_compile/src/lib.rs +19 -8
  36. package/crates/tish_compile/src/resolve.rs +278 -137
  37. package/crates/tish_compile/src/types.rs +184 -24
  38. package/crates/tish_compile_js/Cargo.toml +1 -0
  39. package/crates/tish_compile_js/src/codegen.rs +181 -37
  40. package/crates/tish_compile_js/src/lib.rs +3 -1
  41. package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
  42. package/crates/tish_compiler_wasm/src/lib.rs +16 -13
  43. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
  44. package/crates/tish_core/Cargo.toml +8 -0
  45. package/crates/tish_core/src/json.rs +107 -56
  46. package/crates/tish_core/src/lib.rs +4 -2
  47. package/crates/tish_core/src/macros.rs +5 -5
  48. package/crates/tish_core/src/uri.rs +9 -6
  49. package/crates/tish_core/src/value.rs +145 -43
  50. package/crates/tish_core/src/vmref.rs +178 -0
  51. package/crates/tish_cranelift/src/link.rs +6 -9
  52. package/crates/tish_cranelift/src/lower.rs +14 -8
  53. package/crates/tish_eval/Cargo.toml +17 -2
  54. package/crates/tish_eval/src/eval.rs +474 -165
  55. package/crates/tish_eval/src/http.rs +61 -0
  56. package/crates/tish_eval/src/lib.rs +12 -8
  57. package/crates/tish_eval/src/natives.rs +136 -38
  58. package/crates/tish_eval/src/promise.rs +14 -8
  59. package/crates/tish_eval/src/timers.rs +28 -19
  60. package/crates/tish_eval/src/value.rs +17 -6
  61. package/crates/tish_eval/src/value_convert.rs +13 -5
  62. package/crates/tish_fmt/src/lib.rs +149 -43
  63. package/crates/tish_lexer/src/lib.rs +232 -63
  64. package/crates/tish_lexer/src/token.rs +10 -6
  65. package/crates/tish_llvm/src/lib.rs +17 -8
  66. package/crates/tish_lsp/Cargo.toml +4 -1
  67. package/crates/tish_lsp/README.md +1 -1
  68. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  69. package/crates/tish_lsp/src/import_goto.rs +549 -0
  70. package/crates/tish_lsp/src/main.rs +504 -106
  71. package/crates/tish_native/src/build.rs +4 -8
  72. package/crates/tish_native/src/lib.rs +54 -21
  73. package/crates/tish_opt/src/lib.rs +84 -52
  74. package/crates/tish_parser/src/lib.rs +45 -13
  75. package/crates/tish_parser/src/parser.rs +505 -130
  76. package/crates/tish_resolve/Cargo.toml +13 -0
  77. package/crates/tish_resolve/src/lib.rs +3436 -0
  78. package/crates/tish_resolve/src/pos.rs +133 -0
  79. package/crates/tish_runtime/Cargo.toml +68 -3
  80. package/crates/tish_runtime/src/http.rs +1136 -145
  81. package/crates/tish_runtime/src/http_fetch.rs +38 -27
  82. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  83. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  84. package/crates/tish_runtime/src/lib.rs +375 -189
  85. package/crates/tish_runtime/src/promise.rs +199 -40
  86. package/crates/tish_runtime/src/promise_io.rs +2 -1
  87. package/crates/tish_runtime/src/timers.rs +37 -1
  88. package/crates/tish_runtime/src/ws.rs +65 -42
  89. package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
  90. package/crates/tish_ui/src/jsx.rs +317 -27
  91. package/crates/tish_ui/src/lib.rs +5 -2
  92. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  93. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  94. package/crates/tish_vm/Cargo.toml +15 -5
  95. package/crates/tish_vm/src/vm.rs +725 -281
  96. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
  97. package/crates/tish_wasm/src/lib.rs +55 -42
  98. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  99. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  100. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  101. package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
  102. package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
  103. package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
  104. package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
  105. package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
  106. package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
  107. package/justfile +8 -0
  108. package/package.json +1 -1
  109. package/platform/darwin-arm64/tish +0 -0
  110. package/platform/darwin-x64/tish +0 -0
  111. package/platform/linux-arm64/tish +0 -0
  112. package/platform/linux-x64/tish +0 -0
  113. package/platform/win32-x64/tish.exe +0 -0
@@ -1,24 +1,55 @@
1
1
  //! Stack-based bytecode VM.
2
2
 
3
3
  use std::cell::RefCell;
4
- use std::collections::HashSet;
4
+ use tishlang_core::VmRef;
5
+ use std::collections::{HashMap, HashSet};
5
6
  use std::rc::Rc;
6
7
  use std::sync::Arc;
7
8
 
8
9
  use tishlang_ast::{BinOp, UnaryOp};
9
10
  use tishlang_builtins::array as arr_builtins;
10
11
  use tishlang_builtins::construct as construct_builtin;
11
- use tishlang_builtins::string as str_builtins;
12
12
  use tishlang_builtins::globals as globals_builtins;
13
13
  use tishlang_builtins::math as math_builtins;
14
+ use tishlang_builtins::string as str_builtins;
14
15
  use tishlang_bytecode::{u8_to_binop, u8_to_unaryop, Chunk, Constant, Opcode, NO_REST_PARAM};
15
- use tishlang_core::{ObjectMap, Value};
16
+ use tishlang_core::{NativeFn, ObjectMap, Value};
17
+
18
+ /// Wrap a closure in the right shared pointer for the current build.
19
+ /// Under `send-values` that's `Arc<dyn Fn + Send + Sync>`; otherwise it's
20
+ /// plain `Rc<dyn Fn>`. Call sites can stay ignorant of the distinction.
21
+ #[cfg(feature = "send-values")]
22
+ #[inline]
23
+ fn make_native_fn<F>(f: F) -> NativeFn
24
+ where
25
+ F: Fn(&[Value]) -> Value + Send + Sync + 'static,
26
+ {
27
+ Arc::new(f)
28
+ }
29
+
30
+ #[cfg(not(feature = "send-values"))]
31
+ #[inline]
32
+ fn make_native_fn<F>(f: F) -> NativeFn
33
+ where
34
+ F: Fn(&[Value]) -> Value + 'static,
35
+ {
36
+ Rc::new(f)
37
+ }
16
38
 
17
- type ArrayMethodFn = Rc<dyn Fn(&[Value]) -> Value>;
39
+ // Array / string / object methods have the same shape as `NativeFn`, which
40
+ // is already feature-gated (`Rc<dyn Fn>` vs `Arc<dyn Fn + Send + Sync>`).
41
+ // Alias to that so the VM picks the right pointer type automatically.
42
+ type ArrayMethodFn = NativeFn;
18
43
 
19
44
  /// Feature names enabled for this VM run (`tish run --feature …`). `full` enables every optional capability.
20
45
  #[cfg_attr(
21
- not(any(feature = "fs", feature = "http", feature = "process", feature = "ws")),
46
+ not(any(
47
+ feature = "fs",
48
+ feature = "http",
49
+ feature = "timers",
50
+ feature = "process",
51
+ feature = "ws"
52
+ )),
22
53
  allow(dead_code)
23
54
  )]
24
55
  fn cap_allows(enabled: &HashSet<String>, name: &str) -> bool {
@@ -31,6 +62,8 @@ pub fn all_compiled_capabilities() -> HashSet<String> {
31
62
  let mut s = HashSet::new();
32
63
  #[cfg(feature = "http")]
33
64
  s.insert("http".to_string());
65
+ #[cfg(feature = "timers")]
66
+ s.insert("timers".to_string());
34
67
  #[cfg(feature = "fs")]
35
68
  s.insert("fs".to_string());
36
69
  #[cfg(feature = "process")]
@@ -44,19 +77,37 @@ pub fn all_compiled_capabilities() -> HashSet<String> {
44
77
 
45
78
  /// Look up built-in module export for LoadNativeExport. Returns None if unknown or feature disabled.
46
79
  #[cfg_attr(
47
- not(any(feature = "fs", feature = "http", feature = "process", feature = "ws")),
80
+ not(any(
81
+ feature = "fs",
82
+ feature = "http",
83
+ feature = "timers",
84
+ feature = "process",
85
+ feature = "ws"
86
+ )),
48
87
  allow(unused_variables)
49
88
  )]
50
89
  fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str) -> Option<Value> {
51
90
  #[cfg(feature = "fs")]
52
91
  if spec == "tish:fs" && cap_allows(enabled, "fs") {
53
92
  return match export_name {
54
- "readFile" => Some(Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::read_file(args)))),
55
- "writeFile" => Some(Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::write_file(args)))),
56
- "fileExists" => Some(Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::file_exists(args)))),
57
- "isDir" => Some(Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::is_dir(args)))),
58
- "readDir" => Some(Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::read_dir(args)))),
59
- "mkdir" => Some(Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::mkdir(args)))),
93
+ "readFile" => Some(Value::native(|args: &[Value]| {
94
+ tishlang_runtime::read_file(args)
95
+ })),
96
+ "writeFile" => Some(Value::native(|args: &[Value]| {
97
+ tishlang_runtime::write_file(args)
98
+ })),
99
+ "fileExists" => Some(Value::native(|args: &[Value]| {
100
+ tishlang_runtime::file_exists(args)
101
+ })),
102
+ "isDir" => Some(Value::native(|args: &[Value]| {
103
+ tishlang_runtime::is_dir(args)
104
+ })),
105
+ "readDir" => Some(Value::native(|args: &[Value]| {
106
+ tishlang_runtime::read_dir(args)
107
+ })),
108
+ "mkdir" => Some(Value::native(|args: &[Value]| {
109
+ tishlang_runtime::mkdir(args)
110
+ })),
60
111
  _ => None,
61
112
  };
62
113
  }
@@ -64,60 +115,123 @@ fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str)
64
115
  if spec == "tish:http" && cap_allows(enabled, "http") {
65
116
  return match export_name {
66
117
  // Bytecode compiler lowers `await expr` to `tish:http.await(promise)` (see tish_bytecode compiler).
67
- "await" => Some(Value::Function(Rc::new(|args: &[Value]| {
118
+ "await" => Some(Value::native(|args: &[Value]| {
68
119
  tishlang_runtime::await_promise(args.first().cloned().unwrap_or(Value::Null))
69
- }))),
70
- "fetch" => Some(Value::Function(Rc::new(|args: &[Value]| {
120
+ })),
121
+ "fetch" => Some(Value::native(|args: &[Value]| {
71
122
  tishlang_runtime::fetch_promise(args.to_vec())
72
- }))),
73
- "fetchAll" => Some(Value::Function(Rc::new(|args: &[Value]| {
123
+ })),
124
+ "fetchAll" => Some(Value::native(|args: &[Value]| {
74
125
  tishlang_runtime::fetch_all_promise(args.to_vec())
75
- }))),
76
- "serve" => Some(Value::Function(Rc::new(|args: &[Value]| {
77
- let handler = args.get(1).cloned().unwrap_or(Value::Null);
78
- if let Value::Function(f) = handler {
126
+ })),
127
+ "Promise" => Some(tishlang_runtime::promise_object()),
128
+ "serve" => Some(Value::native(|args: &[Value]| {
129
+ // Phase-1 item 2: support `serve(port, { handler, onWorker })`
130
+ // in addition to `serve(port, handler)`. When an options
131
+ // object is given and onWorker is a function, invoke it with
132
+ // worker id 0 and expect it to return the request handler.
133
+ let raw = args.get(1).cloned().unwrap_or(Value::Null);
134
+ let handler_value = match raw {
135
+ Value::Function(_) => raw,
136
+ Value::Object(ref obj) => {
137
+ let obj_ref = obj.borrow();
138
+ if let Some(Value::Function(on_worker)) =
139
+ obj_ref.get(&std::sync::Arc::from("onWorker")).cloned()
140
+ {
141
+ let args_for_init = [Value::Number(0.0)];
142
+ on_worker(&args_for_init)
143
+ } else if let Some(h) =
144
+ obj_ref.get(&std::sync::Arc::from("handler")).cloned()
145
+ {
146
+ h
147
+ } else {
148
+ Value::Null
149
+ }
150
+ }
151
+ _ => Value::Null,
152
+ };
153
+ if let Value::Function(f) = handler_value {
79
154
  tishlang_runtime::http_serve(args, move |req_args| f(req_args))
80
155
  } else {
81
156
  Value::Null
82
157
  }
83
- }))),
158
+ })),
159
+ _ => None,
160
+ };
161
+ }
162
+ #[cfg(feature = "timers")]
163
+ if spec == "tish:timers" && cap_allows(enabled, "timers") {
164
+ return match export_name {
165
+ "setTimeout" => Some(Value::native(|args: &[Value]| {
166
+ tishlang_runtime::timer_set_timeout(args)
167
+ })),
168
+ "setInterval" => Some(Value::native(|args: &[Value]| {
169
+ tishlang_runtime::timer_set_interval(args)
170
+ })),
171
+ "clearTimeout" => Some(Value::native(|args: &[Value]| {
172
+ tishlang_runtime::timer_clear_timeout(args)
173
+ })),
174
+ "clearInterval" => Some(Value::native(|args: &[Value]| {
175
+ tishlang_runtime::timer_clear_interval(args)
176
+ })),
84
177
  _ => None,
85
178
  };
86
179
  }
87
180
  #[cfg(feature = "process")]
88
181
  if spec == "tish:process" && cap_allows(enabled, "process") {
89
182
  return match export_name {
90
- "exit" => Some(Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::process_exit(args)))),
91
- "cwd" => Some(Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::process_cwd(args)))),
92
- "exec" => Some(Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::process_exec(args)))),
93
- "argv" => Some(Value::Array(Rc::new(RefCell::new(
183
+ "exit" => Some(Value::native(|args: &[Value]| {
184
+ tishlang_runtime::process_exit(args)
185
+ })),
186
+ "cwd" => Some(Value::native(|args: &[Value]| {
187
+ tishlang_runtime::process_cwd(args)
188
+ })),
189
+ "exec" => Some(Value::native(|args: &[Value]| {
190
+ tishlang_runtime::process_exec(args)
191
+ })),
192
+ "argv" => Some(Value::Array(VmRef::new(
94
193
  std::env::args().map(|s| Value::String(s.into())).collect(),
95
- )))),
96
- "env" => Some(Value::Object(Rc::new(RefCell::new(
194
+ ))),
195
+ "env" => Some(Value::Object(VmRef::new(
97
196
  std::env::vars()
98
197
  .map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
99
198
  .collect(),
100
- )))),
199
+ ))),
101
200
  "process" => {
102
201
  let mut m = ObjectMap::default();
103
- m.insert("exit".into(), Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::process_exit(args))));
104
- m.insert("cwd".into(), Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::process_cwd(args))));
105
- m.insert("exec".into(), Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::process_exec(args))));
202
+ m.insert(
203
+ "exit".into(),
204
+ Value::native(|args: &[Value]| {
205
+ tishlang_runtime::process_exit(args)
206
+ }),
207
+ );
208
+ m.insert(
209
+ "cwd".into(),
210
+ Value::native(|args: &[Value]| {
211
+ tishlang_runtime::process_cwd(args)
212
+ }),
213
+ );
214
+ m.insert(
215
+ "exec".into(),
216
+ Value::native(|args: &[Value]| {
217
+ tishlang_runtime::process_exec(args)
218
+ }),
219
+ );
106
220
  m.insert(
107
221
  "argv".into(),
108
- Value::Array(Rc::new(RefCell::new(
222
+ Value::Array(VmRef::new(
109
223
  std::env::args().map(|s| Value::String(s.into())).collect(),
110
- ))),
224
+ )),
111
225
  );
112
226
  m.insert(
113
227
  "env".into(),
114
- Value::Object(Rc::new(RefCell::new(
228
+ Value::Object(VmRef::new(
115
229
  std::env::vars()
116
230
  .map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
117
231
  .collect(),
118
- ))),
232
+ )),
119
233
  );
120
- Some(Value::Object(Rc::new(RefCell::new(m))))
234
+ Some(Value::Object(VmRef::new(m)))
121
235
  }
122
236
  _ => None,
123
237
  };
@@ -125,21 +239,24 @@ fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str)
125
239
  #[cfg(feature = "ws")]
126
240
  if spec == "tish:ws" && cap_allows(enabled, "ws") {
127
241
  return match export_name {
128
- "WebSocket" => Some(Value::Function(Rc::new(|args: &[Value]| {
242
+ "WebSocket" => Some(Value::native(|args: &[Value]| {
129
243
  tishlang_runtime::web_socket_client(args)
130
- }))),
131
- "Server" => Some(Value::Function(Rc::new(|args: &[Value]| {
244
+ })),
245
+ "Server" => Some(Value::native(|args: &[Value]| {
132
246
  tishlang_runtime::web_socket_server_construct(args)
133
- }))),
134
- "wsSend" => Some(Value::Function(Rc::new(|args: &[Value]| {
247
+ })),
248
+ "wsSend" => Some(Value::native(|args: &[Value]| {
135
249
  Value::Bool(tishlang_runtime::ws_send_native(
136
250
  args.first().unwrap_or(&Value::Null),
137
- &args.get(1).map(|v| v.to_display_string()).unwrap_or_default(),
251
+ &args
252
+ .get(1)
253
+ .map(|v| v.to_display_string())
254
+ .unwrap_or_default(),
138
255
  ))
139
- }))),
140
- "wsBroadcast" => Some(Value::Function(Rc::new(|args: &[Value]| {
256
+ })),
257
+ "wsBroadcast" => Some(Value::native(|args: &[Value]| {
141
258
  tishlang_runtime::ws_broadcast_native(args)
142
- }))),
259
+ })),
143
260
  _ => None,
144
261
  };
145
262
  }
@@ -182,159 +299,223 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
182
299
  let mut console = ObjectMap::default();
183
300
  console.insert(
184
301
  "debug".into(),
185
- Value::Function(Rc::new(|args: &[Value]| {
186
- let s = tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
302
+ Value::native(|args: &[Value]| {
303
+ let s =
304
+ tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
187
305
  vm_log(&s);
188
306
  Value::Null
189
- })),
307
+ }),
190
308
  );
191
309
  console.insert(
192
310
  "log".into(),
193
- Value::Function(Rc::new(|args: &[Value]| {
194
- let s = tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
311
+ Value::native(|args: &[Value]| {
312
+ let s =
313
+ tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
195
314
  vm_log(&s);
196
315
  Value::Null
197
- })),
316
+ }),
198
317
  );
199
318
  console.insert(
200
319
  "info".into(),
201
- Value::Function(Rc::new(|args: &[Value]| {
202
- let s = tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
320
+ Value::native(|args: &[Value]| {
321
+ let s =
322
+ tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
203
323
  vm_log(&s);
204
324
  Value::Null
205
- })),
325
+ }),
206
326
  );
207
327
  console.insert(
208
328
  "warn".into(),
209
- Value::Function(Rc::new(|args: &[Value]| {
210
- let s = tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
329
+ Value::native(|args: &[Value]| {
330
+ let s =
331
+ tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
211
332
  vm_log_err(&s);
212
333
  Value::Null
213
- })),
334
+ }),
214
335
  );
215
336
  console.insert(
216
337
  "error".into(),
217
- Value::Function(Rc::new(|args: &[Value]| {
218
- let s = tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
338
+ Value::native(|args: &[Value]| {
339
+ let s =
340
+ tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
219
341
  vm_log_err(&s);
220
342
  Value::Null
221
- })),
343
+ }),
344
+ );
345
+ g.insert(
346
+ "console".into(),
347
+ Value::Object(VmRef::new(console)),
222
348
  );
223
- g.insert("console".into(), Value::Object(Rc::new(RefCell::new(console))));
224
349
 
225
350
  let mut math = ObjectMap::default();
226
351
  math.insert(
227
352
  "abs".into(),
228
- Value::Function(Rc::new(|args: &[Value]| {
353
+ Value::native(|args: &[Value]| {
229
354
  let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
230
355
  Value::Number(n.abs())
231
- })),
356
+ }),
232
357
  );
233
358
  math.insert(
234
359
  "sqrt".into(),
235
- Value::Function(Rc::new(|args: &[Value]| {
360
+ Value::native(|args: &[Value]| {
236
361
  let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
237
362
  Value::Number(n.sqrt())
238
- })),
363
+ }),
239
364
  );
240
365
  math.insert(
241
366
  "floor".into(),
242
- Value::Function(Rc::new(|args: &[Value]| {
367
+ Value::native(|args: &[Value]| {
243
368
  let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
244
369
  Value::Number(n.floor())
245
- })),
370
+ }),
246
371
  );
247
372
  math.insert(
248
373
  "ceil".into(),
249
- Value::Function(Rc::new(|args: &[Value]| {
374
+ Value::native(|args: &[Value]| {
250
375
  let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
251
376
  Value::Number(n.ceil())
252
- })),
377
+ }),
253
378
  );
254
379
  math.insert(
255
380
  "round".into(),
256
- Value::Function(Rc::new(|args: &[Value]| {
381
+ Value::native(|args: &[Value]| {
257
382
  let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
258
383
  Value::Number(n.round())
259
- })),
384
+ }),
260
385
  );
261
386
  math.insert(
262
387
  "random".into(),
263
- Value::Function(Rc::new(|_| Value::Number(rand::random::<f64>()))),
388
+ Value::native(|_| Value::Number(rand::random::<f64>())),
264
389
  );
265
390
  math.insert(
266
391
  "min".into(),
267
- Value::Function(Rc::new(|args: &[Value]| {
392
+ Value::native(|args: &[Value]| {
268
393
  let nums: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
269
394
  Value::Number(nums.into_iter().fold(f64::NAN, |a, b| a.min(b)))
270
- })),
395
+ }),
271
396
  );
272
397
  math.insert(
273
398
  "max".into(),
274
- Value::Function(Rc::new(|args: &[Value]| {
399
+ Value::native(|args: &[Value]| {
275
400
  let nums: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
276
401
  Value::Number(nums.into_iter().fold(f64::NAN, |a, b| a.max(b)))
277
- })),
402
+ }),
403
+ );
404
+ math.insert(
405
+ "pow".into(),
406
+ Value::native(|args: &[Value]| math_builtins::pow(args)),
407
+ );
408
+ math.insert(
409
+ "sin".into(),
410
+ Value::native(|args: &[Value]| math_builtins::sin(args)),
411
+ );
412
+ math.insert(
413
+ "cos".into(),
414
+ Value::native(|args: &[Value]| math_builtins::cos(args)),
415
+ );
416
+ math.insert(
417
+ "tan".into(),
418
+ Value::native(|args: &[Value]| math_builtins::tan(args)),
419
+ );
420
+ math.insert(
421
+ "log".into(),
422
+ Value::native(|args: &[Value]| math_builtins::log(args)),
423
+ );
424
+ math.insert(
425
+ "exp".into(),
426
+ Value::native(|args: &[Value]| math_builtins::exp(args)),
427
+ );
428
+ math.insert(
429
+ "sign".into(),
430
+ Value::native(|args: &[Value]| math_builtins::sign(args)),
431
+ );
432
+ math.insert(
433
+ "trunc".into(),
434
+ Value::native(|args: &[Value]| math_builtins::trunc(args)),
278
435
  );
279
- math.insert("pow".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::pow(args))));
280
- math.insert("sin".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::sin(args))));
281
- math.insert("cos".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::cos(args))));
282
- math.insert("tan".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::tan(args))));
283
- math.insert("log".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::log(args))));
284
- math.insert("exp".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::exp(args))));
285
- math.insert("sign".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::sign(args))));
286
- math.insert("trunc".into(), Value::Function(Rc::new(|args: &[Value]| math_builtins::trunc(args))));
287
436
  math.insert("PI".into(), Value::Number(std::f64::consts::PI));
288
437
  math.insert("E".into(), Value::Number(std::f64::consts::E));
289
- g.insert("Math".into(), Value::Object(Rc::new(RefCell::new(math))));
438
+ g.insert("Math".into(), Value::Object(VmRef::new(math)));
290
439
 
291
440
  let mut json = ObjectMap::default();
292
441
  json.insert(
293
442
  "parse".into(),
294
- Value::Function(Rc::new(|args: &[Value]| {
295
- let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
443
+ Value::native(|args: &[Value]| {
444
+ let s = args
445
+ .first()
446
+ .map(|v| v.to_display_string())
447
+ .unwrap_or_default();
296
448
  tishlang_core::json_parse(&s).unwrap_or(Value::Null)
297
- })),
449
+ }),
298
450
  );
299
451
  json.insert(
300
452
  "stringify".into(),
301
- Value::Function(Rc::new(|args: &[Value]| {
453
+ Value::native(|args: &[Value]| {
302
454
  let v = args.first().unwrap_or(&Value::Null);
303
455
  Value::String(tishlang_core::json_stringify(v).into())
304
- })),
456
+ }),
305
457
  );
306
- g.insert("JSON".into(), Value::Object(Rc::new(RefCell::new(json))));
458
+ g.insert("JSON".into(), Value::Object(VmRef::new(json)));
307
459
 
308
- g.insert("parseInt".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::parse_int(args))));
309
- g.insert("parseFloat".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::parse_float(args))));
310
- g.insert("encodeURI".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::encode_uri(args))));
311
- g.insert("decodeURI".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::decode_uri(args))));
312
- g.insert("Boolean".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::boolean(args))));
313
- g.insert("isFinite".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::is_finite(args))));
314
- g.insert("isNaN".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::is_nan(args))));
460
+ g.insert(
461
+ "parseInt".into(),
462
+ Value::native(|args: &[Value]| globals_builtins::parse_int(args)),
463
+ );
464
+ g.insert(
465
+ "parseFloat".into(),
466
+ Value::native(|args: &[Value]| {
467
+ globals_builtins::parse_float(args)
468
+ }),
469
+ );
470
+ g.insert(
471
+ "encodeURI".into(),
472
+ Value::native(|args: &[Value]| globals_builtins::encode_uri(args)),
473
+ );
474
+ g.insert(
475
+ "decodeURI".into(),
476
+ Value::native(|args: &[Value]| globals_builtins::decode_uri(args)),
477
+ );
478
+ g.insert(
479
+ "htmlEscape".into(),
480
+ Value::native(|args: &[Value]| {
481
+ tishlang_builtins::string::escape_html(args.first().unwrap_or(&Value::Null))
482
+ }),
483
+ );
484
+ g.insert(
485
+ "Boolean".into(),
486
+ Value::native(|args: &[Value]| globals_builtins::boolean(args)),
487
+ );
488
+ g.insert(
489
+ "isFinite".into(),
490
+ Value::native(|args: &[Value]| globals_builtins::is_finite(args)),
491
+ );
492
+ g.insert(
493
+ "isNaN".into(),
494
+ Value::native(|args: &[Value]| globals_builtins::is_nan(args)),
495
+ );
315
496
  g.insert("Infinity".into(), Value::Number(f64::INFINITY));
316
497
  g.insert("NaN".into(), Value::Number(f64::NAN));
317
498
  g.insert(
318
499
  "typeof".into(),
319
- Value::Function(Rc::new(|args: &[Value]| {
500
+ Value::native(|args: &[Value]| {
320
501
  let v = args.first().unwrap_or(&Value::Null);
321
502
  Value::String(v.type_name().into())
322
- })),
503
+ }),
323
504
  );
324
505
 
325
506
  // Date - at minimum Date.now() for timing
326
507
  let mut date = ObjectMap::default();
327
508
  date.insert(
328
509
  "now".into(),
329
- Value::Function(Rc::new(|_args: &[Value]| {
510
+ Value::native(|_args: &[Value]| {
330
511
  let ms = std::time::SystemTime::now()
331
512
  .duration_since(std::time::UNIX_EPOCH)
332
513
  .unwrap_or_default()
333
514
  .as_millis() as f64;
334
515
  Value::Number(ms)
335
- })),
516
+ }),
336
517
  );
337
- g.insert("Date".into(), Value::Object(Rc::new(RefCell::new(date))));
518
+ g.insert("Date".into(), Value::Object(VmRef::new(date)));
338
519
 
339
520
  g.insert(
340
521
  "Uint8Array".into(),
@@ -349,108 +530,225 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
349
530
  let mut object_methods = ObjectMap::default();
350
531
  object_methods.insert(
351
532
  "assign".into(),
352
- Value::Function(Rc::new(|args: &[Value]| globals_builtins::object_assign(args))),
533
+ Value::native(|args: &[Value]| {
534
+ globals_builtins::object_assign(args)
535
+ }),
353
536
  );
354
537
  object_methods.insert(
355
538
  "fromEntries".into(),
356
- Value::Function(Rc::new(|args: &[Value]| globals_builtins::object_from_entries(args))),
539
+ Value::native(|args: &[Value]| {
540
+ globals_builtins::object_from_entries(args)
541
+ }),
542
+ );
543
+ object_methods.insert(
544
+ "keys".into(),
545
+ Value::native(|args: &[Value]| {
546
+ globals_builtins::object_keys(args)
547
+ }),
548
+ );
549
+ object_methods.insert(
550
+ "values".into(),
551
+ Value::native(|args: &[Value]| {
552
+ globals_builtins::object_values(args)
553
+ }),
554
+ );
555
+ object_methods.insert(
556
+ "entries".into(),
557
+ Value::native(|args: &[Value]| {
558
+ globals_builtins::object_entries(args)
559
+ }),
560
+ );
561
+ g.insert(
562
+ "Object".into(),
563
+ Value::Object(VmRef::new(object_methods)),
357
564
  );
358
- object_methods.insert("keys".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::object_keys(args))));
359
- object_methods.insert("values".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::object_values(args))));
360
- object_methods.insert("entries".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::object_entries(args))));
361
- g.insert("Object".into(), Value::Object(Rc::new(RefCell::new(object_methods))));
362
565
 
363
566
  // Array.isArray
364
567
  let mut array_static = ObjectMap::default();
365
568
  array_static.insert(
366
569
  "isArray".into(),
367
- Value::Function(Rc::new(|args: &[Value]| globals_builtins::array_is_array(args))),
570
+ Value::native(|args: &[Value]| {
571
+ globals_builtins::array_is_array(args)
572
+ }),
573
+ );
574
+ g.insert(
575
+ "Array".into(),
576
+ Value::Object(VmRef::new(array_static)),
368
577
  );
369
- g.insert("Array".into(), Value::Object(Rc::new(RefCell::new(array_static))));
370
578
 
371
579
  // String(value) as callable + String.fromCharCode
372
- let string_convert_fn = Value::Function(Rc::new(|args: &[Value]| globals_builtins::string_convert(args)));
580
+ let string_convert_fn = Value::native(|args: &[Value]| {
581
+ globals_builtins::string_convert(args)
582
+ });
373
583
  let mut string_static = ObjectMap::default();
374
- string_static.insert("fromCharCode".into(), Value::Function(Rc::new(|args: &[Value]| globals_builtins::string_from_char_code(args))));
584
+ string_static.insert(
585
+ "fromCharCode".into(),
586
+ Value::native(|args: &[Value]| {
587
+ globals_builtins::string_from_char_code(args)
588
+ }),
589
+ );
375
590
  string_static.insert(Arc::from("__call"), string_convert_fn);
376
- g.insert("String".into(), Value::Object(Rc::new(RefCell::new(string_static))));
591
+ g.insert(
592
+ "String".into(),
593
+ Value::Object(VmRef::new(string_static)),
594
+ );
377
595
 
378
596
  // JSX / Lattish: stubs for bytecode VM when no DOM (e.g. console). Override via set_global in browser.
379
597
  g.insert(
380
598
  "h".into(),
381
- Value::Function(Rc::new(|_args: &[Value]| Value::Null)),
599
+ Value::native(|_args: &[Value]| Value::Null),
382
600
  );
383
601
  g.insert(
384
602
  "Fragment".into(),
385
- Value::Object(Rc::new(RefCell::new(ObjectMap::default()))),
603
+ Value::Object(VmRef::new(ObjectMap::default())),
386
604
  );
387
605
  g.insert(
388
606
  "createRoot".into(),
389
- Value::Function(Rc::new(|_args: &[Value]| {
607
+ Value::native(|_args: &[Value]| {
390
608
  let mut render_obj = ObjectMap::default();
391
609
  render_obj.insert(
392
610
  "render".into(),
393
- Value::Function(Rc::new(|_args: &[Value]| Value::Null)),
611
+ Value::native(|_args: &[Value]| Value::Null),
394
612
  );
395
- Value::Object(Rc::new(RefCell::new(render_obj)))
396
- })),
613
+ Value::Object(VmRef::new(render_obj))
614
+ }),
397
615
  );
398
616
  g.insert(
399
617
  "useState".into(),
400
- Value::Function(Rc::new(|args: &[Value]| {
618
+ Value::native(|args: &[Value]| {
401
619
  let init = args.first().cloned().unwrap_or(Value::Null);
402
- let arr = vec![init, Value::Function(Rc::new(|_| Value::Null))];
403
- Value::Array(Rc::new(RefCell::new(arr)))
404
- })),
620
+ let arr = vec![init, Value::native(|_| Value::Null)];
621
+ Value::Array(VmRef::new(arr))
622
+ }),
405
623
  );
406
624
  let mut document_obj = ObjectMap::default();
407
625
  document_obj.insert("body".into(), Value::Null);
408
- g.insert("document".into(), Value::Object(Rc::new(RefCell::new(document_obj))));
626
+ g.insert(
627
+ "document".into(),
628
+ Value::Object(VmRef::new(document_obj)),
629
+ );
409
630
 
410
631
  #[cfg(feature = "process")]
411
632
  if cap_allows(enabled, "process") {
412
633
  let mut process_obj = ObjectMap::default();
413
634
  process_obj.insert(
414
635
  "exit".into(),
415
- Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::process_exit(args))),
636
+ Value::native(|args: &[Value]| {
637
+ tishlang_runtime::process_exit(args)
638
+ }),
416
639
  );
417
640
  process_obj.insert(
418
641
  "cwd".into(),
419
- Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::process_cwd(args))),
642
+ Value::native(|args: &[Value]| {
643
+ tishlang_runtime::process_cwd(args)
644
+ }),
420
645
  );
421
646
  process_obj.insert(
422
647
  "exec".into(),
423
- Value::Function(Rc::new(|args: &[Value]| tishlang_runtime::process_exec(args))),
648
+ Value::native(|args: &[Value]| {
649
+ tishlang_runtime::process_exec(args)
650
+ }),
424
651
  );
425
652
  process_obj.insert(
426
653
  "argv".into(),
427
- Value::Array(Rc::new(RefCell::new(
654
+ Value::Array(VmRef::new(
428
655
  std::env::args().map(|s| Value::String(s.into())).collect(),
429
- ))),
656
+ )),
430
657
  );
431
658
  process_obj.insert(
432
659
  "env".into(),
433
- Value::Object(Rc::new(RefCell::new(
660
+ Value::Object(VmRef::new(
434
661
  std::env::vars()
435
662
  .map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
436
663
  .collect(),
437
- ))),
664
+ )),
665
+ );
666
+ g.insert(
667
+ "process".into(),
668
+ Value::Object(VmRef::new(process_obj)),
669
+ );
670
+ }
671
+
672
+ #[cfg(feature = "timers")]
673
+ if cap_allows(enabled, "timers") {
674
+ g.insert(
675
+ "setTimeout".into(),
676
+ Value::native(|args: &[Value]| tishlang_runtime::timer_set_timeout(args)),
677
+ );
678
+ g.insert(
679
+ "clearTimeout".into(),
680
+ Value::native(|args: &[Value]| tishlang_runtime::timer_clear_timeout(args)),
681
+ );
682
+ g.insert(
683
+ "setInterval".into(),
684
+ Value::native(|args: &[Value]| tishlang_runtime::timer_set_interval(args)),
685
+ );
686
+ g.insert(
687
+ "clearInterval".into(),
688
+ Value::native(|args: &[Value]| tishlang_runtime::timer_clear_interval(args)),
438
689
  );
439
- g.insert("process".into(), Value::Object(Rc::new(RefCell::new(process_obj))));
440
690
  }
441
691
 
442
692
  #[cfg(feature = "http")]
443
693
  if cap_allows(enabled, "http") {
694
+ g.insert(
695
+ "fetch".into(),
696
+ Value::native(|args: &[Value]| tishlang_runtime::fetch_promise(args.to_vec())),
697
+ );
698
+ g.insert(
699
+ "fetchAll".into(),
700
+ Value::native(|args: &[Value]| tishlang_runtime::fetch_all_promise(args.to_vec())),
701
+ );
702
+ g.insert("Promise".into(), tishlang_runtime::promise_object());
703
+ g.insert(
704
+ "registerStaticRoute".into(),
705
+ Value::native(|args: &[Value]| {
706
+ let path = match args.first() {
707
+ Some(Value::String(s)) => s.to_string(),
708
+ _ => return Value::Null,
709
+ };
710
+ let body = match args.get(1) {
711
+ Some(Value::String(s)) => s.as_bytes().to_vec(),
712
+ _ => return Value::Null,
713
+ };
714
+ let ct = match args.get(2) {
715
+ Some(Value::String(s)) => s.to_string(),
716
+ _ => "application/octet-stream".to_string(),
717
+ };
718
+ tishlang_runtime::register_static_route(&path, &body, &ct);
719
+ Value::Null
720
+ }),
721
+ );
444
722
  g.insert(
445
723
  "serve".into(),
446
- Value::Function(Rc::new(|args: &[Value]| {
447
- let handler = args.get(1).cloned().unwrap_or(Value::Null);
448
- if let Value::Function(f) = handler {
724
+ Value::native(|args: &[Value]| {
725
+ // Phase-1 item 2 (see tish:http.serve above for full docs).
726
+ let raw = args.get(1).cloned().unwrap_or(Value::Null);
727
+ let handler_value = match raw {
728
+ Value::Function(_) => raw,
729
+ Value::Object(ref obj) => {
730
+ let obj_ref = obj.borrow();
731
+ if let Some(Value::Function(on_worker)) =
732
+ obj_ref.get(&std::sync::Arc::from("onWorker")).cloned()
733
+ {
734
+ let args_for_init = [Value::Number(0.0)];
735
+ on_worker(&args_for_init)
736
+ } else if let Some(h) =
737
+ obj_ref.get(&std::sync::Arc::from("handler")).cloned()
738
+ {
739
+ h
740
+ } else {
741
+ Value::Null
742
+ }
743
+ }
744
+ _ => Value::Null,
745
+ };
746
+ if let Value::Function(f) = handler_value {
449
747
  tishlang_runtime::http_serve(args, move |req_args| f(req_args))
450
748
  } else {
451
749
  Value::Null
452
750
  }
453
- })),
751
+ }),
454
752
  );
455
753
  }
456
754
 
@@ -458,7 +756,7 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
458
756
  }
459
757
 
460
758
  /// Shared scope for closure capture (parent frame's locals).
461
- type ScopeMap = Rc<RefCell<ObjectMap>>;
759
+ type ScopeMap = VmRef<ObjectMap>;
462
760
 
463
761
  /// Options for the convenience [`run_with_options`] helper (one-shot VM run from the CLI).
464
762
  #[derive(Clone, Debug, Default)]
@@ -475,9 +773,14 @@ pub struct Vm {
475
773
  scope: ObjectMap,
476
774
  /// Enclosing scope for closures (captured parent frame locals).
477
775
  enclosing: Option<ScopeMap>,
478
- globals: Rc<RefCell<ObjectMap>>,
776
+ globals: VmRef<ObjectMap>,
479
777
  /// Capabilities for `LoadNativeExport` and globals such as `process` / `serve`.
480
778
  capabilities: Arc<HashSet<String>>,
779
+ /// Externally registered native modules, keyed by import spec (e.g.
780
+ /// `"cargo:tish_pg"`). Populated by embedders before `run` (see
781
+ /// [`register_native_module`]). Phase-2 item 11: unblocks `cargo:`
782
+ /// imports on the cranelift and llvm backends which run this VM.
783
+ native_modules: VmRef<HashMap<String, VmRef<ObjectMap>>>,
481
784
  }
482
785
 
483
786
  impl Vm {
@@ -496,11 +799,24 @@ impl Vm {
496
799
  stack: Vec::new(),
497
800
  scope: ObjectMap::default(),
498
801
  enclosing: None,
499
- globals: Rc::new(RefCell::new(init_globals(capabilities.as_ref()))),
802
+ globals: VmRef::new(init_globals(capabilities.as_ref())),
500
803
  capabilities,
804
+ native_modules: VmRef::new(HashMap::new()),
501
805
  }
502
806
  }
503
807
 
808
+ /// Register an externally-supplied native module under a `cargo:`-style
809
+ /// spec (e.g. `"cargo:tish_pg"`). The `exports` map is what
810
+ /// `LoadNativeExport` will index into when user code imports from this
811
+ /// spec. Intended to be called by the `tishlang_cranelift_runtime` /
812
+ /// `tishlang_llvm` link step, or by external embedders that want to
813
+ /// expose Rust crates to `.tish` programs running on the bytecode VM.
814
+ pub fn register_native_module(&mut self, spec: impl Into<String>, exports: ObjectMap) {
815
+ self.native_modules
816
+ .borrow_mut()
817
+ .insert(spec.into(), VmRef::new(exports));
818
+ }
819
+
504
820
  pub fn get_global(&self, name: &str) -> Option<Value> {
505
821
  self.globals.borrow().get(name).cloned()
506
822
  }
@@ -511,7 +827,11 @@ impl Vm {
511
827
 
512
828
  /// Names of all globals (for REPL bare-word tab completion).
513
829
  pub fn global_names(&self) -> Vec<String> {
514
- self.globals.borrow().keys().map(|k| k.as_ref().to_string()).collect()
830
+ self.globals
831
+ .borrow()
832
+ .keys()
833
+ .map(|k| k.as_ref().to_string())
834
+ .collect()
515
835
  }
516
836
 
517
837
  fn read_u16(code: &[u8], ip: &mut usize) -> u16 {
@@ -525,6 +845,22 @@ impl Vm {
525
845
  Self::read_u16(code, ip) as i16
526
846
  }
527
847
 
848
+ /// Pop innermost try handler, truncate stack, push thrown value, jump to catch.
849
+ fn unwind_throw(
850
+ try_handlers: &mut Vec<(usize, usize)>,
851
+ stack: &mut Vec<Value>,
852
+ ip: &mut usize,
853
+ v: Value,
854
+ ) -> Result<(), String> {
855
+ let (catch_ip, stack_len) = try_handlers
856
+ .pop()
857
+ .ok_or_else(|| format!("Uncaught throw: {}", v.to_display_string()))?;
858
+ stack.truncate(stack_len);
859
+ stack.push(v);
860
+ *ip = catch_ip;
861
+ Ok(())
862
+ }
863
+
528
864
  pub fn run(&mut self, chunk: &Chunk) -> Result<Value, String> {
529
865
  self.run_with_options(chunk, false)
530
866
  }
@@ -546,7 +882,7 @@ impl Vm {
546
882
  let names = &chunk.names;
547
883
 
548
884
  let mut ip = 0;
549
- let local_scope: ScopeMap = Rc::new(RefCell::new(ObjectMap::default()));
885
+ let local_scope: ScopeMap = VmRef::new(ObjectMap::default());
550
886
  {
551
887
  let mut ls = local_scope.borrow_mut();
552
888
  let param_count = chunk.param_count as usize;
@@ -560,7 +896,7 @@ impl Vm {
560
896
  let rest_arr: Vec<Value> = args.iter().skip(ri).cloned().collect();
561
897
  ls.insert(
562
898
  Arc::clone(name),
563
- Value::Array(Rc::new(RefCell::new(rest_arr))),
899
+ Value::Array(VmRef::new(rest_arr)),
564
900
  );
565
901
  }
566
902
  }
@@ -603,20 +939,22 @@ impl Vm {
603
939
  .get(*nested_idx)
604
940
  .ok_or_else(|| "Nested chunk index out of bounds".to_string())?;
605
941
  let inner_clone = inner.clone();
606
- let globals = Rc::clone(&self.globals);
607
- let enclosing = Some(Rc::clone(&local_scope));
942
+ let globals = self.globals.clone();
943
+ let enclosing = Some(local_scope.clone());
608
944
  let capabilities = Arc::clone(&self.capabilities);
609
- Value::Function(Rc::new(move |args: &[Value]| {
945
+ let native_modules = self.native_modules.clone();
946
+ Value::native(move |args: &[Value]| {
610
947
  let mut vm = Vm {
611
948
  stack: Vec::new(),
612
949
  scope: ObjectMap::default(),
613
950
  enclosing: enclosing.clone(),
614
- globals: Rc::clone(&globals),
951
+ globals: globals.clone(),
615
952
  capabilities: Arc::clone(&capabilities),
953
+ native_modules: native_modules.clone(),
616
954
  };
617
955
  vm.run_chunk(&inner_clone, &inner_clone.nested, args, false)
618
956
  .unwrap_or(Value::Null)
619
- }))
957
+ })
620
958
  }
621
959
  };
622
960
  self.stack.push(v);
@@ -663,9 +1001,7 @@ impl Vm {
663
1001
  } else if self.scope.contains_key(name.as_ref()) {
664
1002
  self.scope.insert(Arc::clone(name), v);
665
1003
  } else if self.globals.borrow().contains_key(name.as_ref()) {
666
- self.globals
667
- .borrow_mut()
668
- .insert(Arc::clone(name), v);
1004
+ self.globals.borrow_mut().insert(Arc::clone(name), v);
669
1005
  } else {
670
1006
  // New variable: at top level (no enclosing) store in globals so REPL persists across lines
671
1007
  if self.enclosing.is_none() {
@@ -716,9 +1052,9 @@ impl Vm {
716
1052
  block_undo_stack.push(Vec::new());
717
1053
  }
718
1054
  Opcode::ExitBlock => {
719
- let frame = block_undo_stack.pop().ok_or_else(|| {
720
- "ExitBlock without matching EnterBlock".to_string()
721
- })?;
1055
+ let frame = block_undo_stack
1056
+ .pop()
1057
+ .ok_or_else(|| "ExitBlock without matching EnterBlock".to_string())?;
722
1058
  for (name, old) in frame.into_iter().rev() {
723
1059
  let mut ls = local_scope.borrow_mut();
724
1060
  match old {
@@ -753,17 +1089,19 @@ impl Vm {
753
1089
  .stack
754
1090
  .pop()
755
1091
  .ok_or_else(|| "Stack underflow".to_string())?;
756
- self.globals
757
- .borrow_mut()
758
- .insert(Arc::clone(name), v);
1092
+ self.globals.borrow_mut().insert(Arc::clone(name), v);
759
1093
  }
760
1094
  Opcode::Pop => {
761
- self.stack.pop().ok_or_else(|| "Stack underflow".to_string())?;
1095
+ self.stack
1096
+ .pop()
1097
+ .ok_or_else(|| "Stack underflow".to_string())?;
762
1098
  }
763
1099
  Opcode::PopN => {
764
1100
  let n = Self::read_u16(code, &mut ip) as usize;
765
1101
  for _ in 0..n {
766
- self.stack.pop().ok_or_else(|| "Stack underflow".to_string())?;
1102
+ self.stack
1103
+ .pop()
1104
+ .ok_or_else(|| "Stack underflow".to_string())?;
767
1105
  }
768
1106
  }
769
1107
  Opcode::Dup => {
@@ -790,10 +1128,12 @@ impl Vm {
790
1128
  .pop()
791
1129
  .ok_or_else(|| "Stack underflow: no callee".to_string())?;
792
1130
  let f = match &callee {
793
- Value::Function(f) => Rc::clone(f),
1131
+ Value::Function(f) => f.clone(),
794
1132
  Value::Object(o) => {
795
- if let Some(Value::Function(call_fn)) = o.borrow().get(&Arc::from("__call")) {
796
- Rc::clone(call_fn)
1133
+ if let Some(Value::Function(call_fn)) =
1134
+ o.borrow().get(&Arc::from("__call"))
1135
+ {
1136
+ call_fn.clone()
797
1137
  } else {
798
1138
  return Err(format!(
799
1139
  "Call of non-function: {}",
@@ -802,10 +1142,7 @@ impl Vm {
802
1142
  }
803
1143
  }
804
1144
  _ => {
805
- return Err(format!(
806
- "Call of non-function: {}",
807
- callee.type_name()
808
- ));
1145
+ return Err(format!("Call of non-function: {}", callee.type_name()));
809
1146
  }
810
1147
  };
811
1148
  let result = f(&args);
@@ -830,10 +1167,12 @@ impl Vm {
830
1167
  }
831
1168
  };
832
1169
  let f = match &callee {
833
- Value::Function(f) => Rc::clone(f),
1170
+ Value::Function(f) => f.clone(),
834
1171
  Value::Object(o) => {
835
- if let Some(Value::Function(call_fn)) = o.borrow().get(&Arc::from("__call")) {
836
- Rc::clone(call_fn)
1172
+ if let Some(Value::Function(call_fn)) =
1173
+ o.borrow().get(&Arc::from("__call"))
1174
+ {
1175
+ call_fn.clone()
837
1176
  } else {
838
1177
  return Err(format!(
839
1178
  "Call of non-function: {}",
@@ -842,10 +1181,7 @@ impl Vm {
842
1181
  }
843
1182
  }
844
1183
  _ => {
845
- return Err(format!(
846
- "Call of non-function: {}",
847
- callee.type_name()
848
- ));
1184
+ return Err(format!("Call of non-function: {}", callee.type_name()));
849
1185
  }
850
1186
  };
851
1187
  let result = f(&args);
@@ -922,8 +1258,8 @@ impl Vm {
922
1258
  .stack
923
1259
  .pop()
924
1260
  .ok_or_else(|| "Stack underflow".to_string())?;
925
- let op = u8_to_binop(op_u8)
926
- .ok_or_else(|| format!("Unknown binop: {}", op_u8))?;
1261
+ let op =
1262
+ u8_to_binop(op_u8).ok_or_else(|| format!("Unknown binop: {}", op_u8))?;
927
1263
  let result = eval_binop(op, &l, &r)?;
928
1264
  self.stack.push(result);
929
1265
  }
@@ -1023,8 +1359,7 @@ impl Vm {
1023
1359
  );
1024
1360
  }
1025
1361
  elems.reverse();
1026
- self.stack
1027
- .push(Value::Array(Rc::new(RefCell::new(elems))));
1362
+ self.stack.push(Value::Array(VmRef::new(elems)));
1028
1363
  }
1029
1364
  Opcode::NewObject => {
1030
1365
  let n = Self::read_u16(code, &mut ip) as usize;
@@ -1041,8 +1376,7 @@ impl Vm {
1041
1376
  let key = key_val.to_display_string().into();
1042
1377
  map.insert(key, val);
1043
1378
  }
1044
- self.stack
1045
- .push(Value::Object(Rc::new(RefCell::new(map))));
1379
+ self.stack.push(Value::Object(VmRef::new(map)));
1046
1380
  }
1047
1381
  Opcode::EnterTry => {
1048
1382
  let offset = Self::read_u16(code, &mut ip) as usize;
@@ -1082,7 +1416,7 @@ impl Vm {
1082
1416
  },
1083
1417
  );
1084
1418
  a.extend(b);
1085
- self.stack.push(Value::Array(Rc::new(RefCell::new(a))));
1419
+ self.stack.push(Value::Array(VmRef::new(a)));
1086
1420
  }
1087
1421
  Opcode::MergeObject => {
1088
1422
  let right = self
@@ -1117,7 +1451,7 @@ impl Vm {
1117
1451
  ));
1118
1452
  }
1119
1453
  self.stack
1120
- .push(Value::Object(Rc::new(RefCell::new(merged))));
1454
+ .push(Value::Object(VmRef::new(merged)));
1121
1455
  }
1122
1456
  Opcode::ArraySortNumeric => {
1123
1457
  let operand = Self::read_u16(code, &mut ip);
@@ -1159,9 +1493,7 @@ impl Vm {
1159
1493
  .pop()
1160
1494
  .ok_or_else(|| "Stack underflow".to_string())?;
1161
1495
  let result = match &arr {
1162
- Value::Array(a) => {
1163
- Value::Array(Rc::new(RefCell::new(a.borrow().clone())))
1164
- }
1496
+ Value::Array(a) => Value::Array(VmRef::new(a.borrow().clone())),
1165
1497
  _ => Value::Null,
1166
1498
  };
1167
1499
  self.stack.push(result);
@@ -1187,12 +1519,20 @@ impl Vm {
1187
1519
  let mapped: Vec<Value> = arr_borrow
1188
1520
  .iter()
1189
1521
  .map(|v| {
1190
- let l: Value = if param_left { (*v).clone() } else { const_val.clone() };
1191
- let r: Value = if param_left { const_val.clone() } else { (*v).clone() };
1522
+ let l: Value = if param_left {
1523
+ (*v).clone()
1524
+ } else {
1525
+ const_val.clone()
1526
+ };
1527
+ let r: Value = if param_left {
1528
+ const_val.clone()
1529
+ } else {
1530
+ (*v).clone()
1531
+ };
1192
1532
  eval_binop(binop, &l, &r).unwrap_or(Value::Null)
1193
1533
  })
1194
1534
  .collect();
1195
- Value::Array(Rc::new(RefCell::new(mapped)))
1535
+ Value::Array(VmRef::new(mapped))
1196
1536
  } else {
1197
1537
  Value::Null
1198
1538
  };
@@ -1204,8 +1544,9 @@ impl Vm {
1204
1544
  let const_idx = Self::read_u16(code, &mut ip);
1205
1545
  let param_left = code[ip] == 0; // 0 = param on left (x op const), 1 = param on right (const op x)
1206
1546
  ip += 1;
1207
- let binop = u8_to_binop(binop_u8)
1208
- .ok_or_else(|| format!("Unknown binop in ArrayFilterBinOp: {}", binop_u8))?;
1547
+ let binop = u8_to_binop(binop_u8).ok_or_else(|| {
1548
+ format!("Unknown binop in ArrayFilterBinOp: {}", binop_u8)
1549
+ })?;
1209
1550
  let arr = self
1210
1551
  .stack
1211
1552
  .pop()
@@ -1219,14 +1560,22 @@ impl Vm {
1219
1560
  let filtered: Vec<Value> = arr_borrow
1220
1561
  .iter()
1221
1562
  .filter(|v| {
1222
- let l: Value = if param_left { (*v).clone() } else { const_val.clone() };
1223
- let r: Value = if param_left { const_val.clone() } else { (*v).clone() };
1563
+ let l: Value = if param_left {
1564
+ (*v).clone()
1565
+ } else {
1566
+ const_val.clone()
1567
+ };
1568
+ let r: Value = if param_left {
1569
+ const_val.clone()
1570
+ } else {
1571
+ (*v).clone()
1572
+ };
1224
1573
  let b = eval_binop(binop, &l, &r).unwrap_or(Value::Null);
1225
1574
  b.is_truthy()
1226
1575
  })
1227
1576
  .cloned()
1228
1577
  .collect();
1229
- Value::Array(Rc::new(RefCell::new(filtered)))
1578
+ Value::Array(VmRef::new(filtered))
1230
1579
  } else {
1231
1580
  Value::Null
1232
1581
  };
@@ -1237,12 +1586,30 @@ impl Vm {
1237
1586
  .stack
1238
1587
  .pop()
1239
1588
  .ok_or_else(|| "Stack underflow".to_string())?;
1240
- let (catch_ip, stack_len) = try_handlers
1589
+ Self::unwind_throw(&mut try_handlers, &mut self.stack, &mut ip, v)?;
1590
+ }
1591
+ Opcode::AwaitPromise => {
1592
+ let v = self
1593
+ .stack
1241
1594
  .pop()
1242
- .ok_or_else(|| format!("Uncaught throw: {}", v.to_display_string()))?;
1243
- self.stack.truncate(stack_len);
1244
- self.stack.push(v);
1245
- ip = catch_ip;
1595
+ .ok_or_else(|| "Stack underflow in AwaitPromise".to_string())?;
1596
+ #[cfg(feature = "http")]
1597
+ {
1598
+ use tishlang_core::Value as V;
1599
+ match v {
1600
+ V::Promise(p) => match p.block_until_settled() {
1601
+ Ok(val) => self.stack.push(val),
1602
+ Err(rej) => {
1603
+ Self::unwind_throw(&mut try_handlers, &mut self.stack, &mut ip, rej)?;
1604
+ }
1605
+ },
1606
+ other => self.stack.push(tishlang_runtime::await_promise(other)),
1607
+ }
1608
+ }
1609
+ #[cfg(not(feature = "http"))]
1610
+ {
1611
+ self.stack.push(v);
1612
+ }
1246
1613
  }
1247
1614
  Opcode::LoadNativeExport => {
1248
1615
  let spec_idx = Self::read_u16(code, &mut ip);
@@ -1250,7 +1617,10 @@ impl Vm {
1250
1617
  let spec = match constants.get(spec_idx as usize) {
1251
1618
  Some(Constant::String(s)) => s.as_ref(),
1252
1619
  _ => {
1253
- return Err("LoadNativeExport: spec constant out of bounds or not string".to_string());
1620
+ return Err(
1621
+ "LoadNativeExport: spec constant out of bounds or not string"
1622
+ .to_string(),
1623
+ );
1254
1624
  }
1255
1625
  };
1256
1626
  let export_name = match constants.get(export_idx as usize) {
@@ -1259,19 +1629,36 @@ impl Vm {
1259
1629
  return Err("LoadNativeExport: export_name constant out of bounds or not string".to_string());
1260
1630
  }
1261
1631
  };
1262
- let v = get_builtin_export(self.capabilities.as_ref(), spec, export_name).ok_or_else(|| {
1263
- if spec.starts_with("cargo:") {
1264
- format!(
1265
- "cargo:… imports are only supported by `tish build` with the Rust native backend (not the bytecode VM). Spec: {}",
1266
- spec
1267
- )
1268
- } else {
1269
- format!(
1270
- "Built-in module '{}' does not export '{}' or capability not enabled for this run. Use e.g. tish run --feature fs (or full). The tish binary must also be built with that capability linked in.",
1271
- spec, export_name
1272
- )
1273
- }
1274
- })?;
1632
+ // Phase-2 item 11: consult externally registered native
1633
+ // modules (populated via `Vm::register_native_module`)
1634
+ // before falling through to the built-in lookup. Embedders
1635
+ // on the cranelift / llvm backends that want to expose
1636
+ // `cargo:…` Rust crates should register the module's
1637
+ // exports map before calling `vm.run(chunk)`.
1638
+ let from_registry: Option<Value> = if spec.starts_with("cargo:") {
1639
+ let regs = self.native_modules.borrow();
1640
+ regs.get(spec)
1641
+ .and_then(|m| m.borrow().get(&Arc::from(export_name)).cloned())
1642
+ } else {
1643
+ None
1644
+ };
1645
+ let v = from_registry
1646
+ .or_else(|| get_builtin_export(self.capabilities.as_ref(), spec, export_name))
1647
+ .ok_or_else(|| {
1648
+ if spec.starts_with("cargo:") {
1649
+ format!(
1650
+ "cargo:{} is not registered on the bytecode VM. Embedders must call Vm::register_native_module before run(). Spec: {} export: {}",
1651
+ spec.trim_start_matches("cargo:"),
1652
+ spec,
1653
+ export_name,
1654
+ )
1655
+ } else {
1656
+ format!(
1657
+ "Built-in module '{}' does not export '{}' or capability not enabled for this run. Use e.g. tish run --feature fs (or full). The tish binary must also be built with that capability linked in.",
1658
+ spec, export_name
1659
+ )
1660
+ }
1661
+ })?;
1275
1662
  self.stack.push(v);
1276
1663
  }
1277
1664
  Opcode::Closure | Opcode::LoadThis => {
@@ -1280,6 +1667,11 @@ impl Vm {
1280
1667
  }
1281
1668
  }
1282
1669
 
1670
+ #[cfg(feature = "timers")]
1671
+ if cap_allows(self.capabilities.as_ref(), "timers") {
1672
+ tishlang_runtime::drain_timers();
1673
+ }
1674
+
1283
1675
  Ok(self.stack.pop().unwrap_or(Value::Null))
1284
1676
  }
1285
1677
  }
@@ -1400,100 +1792,125 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
1400
1792
  match obj {
1401
1793
  Value::Object(m) => {
1402
1794
  let map = m.borrow();
1403
- map.get(key.as_ref()).cloned().ok_or_else(|| {
1404
- format!("Property '{}' not found", key)
1405
- })
1795
+ map.get(key.as_ref())
1796
+ .cloned()
1797
+ .ok_or_else(|| format!("Property '{}' not found", key))
1406
1798
  }
1407
1799
  Value::Array(a) => {
1408
1800
  let key_s = key.as_ref();
1409
1801
  if let Ok(idx) = key_s.parse::<usize>() {
1410
1802
  let arr = a.borrow();
1411
- return arr.get(idx).cloned().ok_or_else(|| "Index out of bounds".to_string());
1803
+ return arr
1804
+ .get(idx)
1805
+ .cloned()
1806
+ .ok_or_else(|| "Index out of bounds".to_string());
1412
1807
  }
1413
1808
  if key_s == "length" {
1414
1809
  return Ok(Value::Number(a.borrow().len() as f64));
1415
1810
  }
1416
- let a_clone = Rc::clone(a);
1811
+ let a_clone = a.clone();
1417
1812
  let method: ArrayMethodFn = match key_s {
1418
- "push" => Rc::new(move |args: &[Value]| arr_builtins::push(&Value::Array(Rc::clone(&a_clone)), args)),
1419
- "pop" => Rc::new(move |_args: &[Value]| arr_builtins::pop(&Value::Array(Rc::clone(&a_clone)))),
1420
- "shift" => Rc::new(move |_args: &[Value]| arr_builtins::shift(&Value::Array(Rc::clone(&a_clone)))),
1421
- "unshift" => Rc::new(move |args: &[Value]| arr_builtins::unshift(&Value::Array(Rc::clone(&a_clone)), args)),
1422
- "reverse" => Rc::new(move |_args: &[Value]| arr_builtins::reverse(&Value::Array(Rc::clone(&a_clone)))),
1423
- "shuffle" => Rc::new(move |_args: &[Value]| arr_builtins::shuffle(&Value::Array(Rc::clone(&a_clone)))),
1424
- "slice" => Rc::new(move |args: &[Value]| {
1813
+ "push" => make_native_fn(move |args: &[Value]| {
1814
+ arr_builtins::push(&Value::Array(a_clone.clone()), args)
1815
+ }),
1816
+ "pop" => make_native_fn(move |_args: &[Value]| {
1817
+ arr_builtins::pop(&Value::Array(a_clone.clone()))
1818
+ }),
1819
+ "shift" => make_native_fn(move |_args: &[Value]| {
1820
+ arr_builtins::shift(&Value::Array(a_clone.clone()))
1821
+ }),
1822
+ "unshift" => make_native_fn(move |args: &[Value]| {
1823
+ arr_builtins::unshift(&Value::Array(a_clone.clone()), args)
1824
+ }),
1825
+ "reverse" => make_native_fn(move |_args: &[Value]| {
1826
+ arr_builtins::reverse(&Value::Array(a_clone.clone()))
1827
+ }),
1828
+ "shuffle" => make_native_fn(move |_args: &[Value]| {
1829
+ arr_builtins::shuffle(&Value::Array(a_clone.clone()))
1830
+ }),
1831
+ "slice" => make_native_fn(move |args: &[Value]| {
1425
1832
  let start = args.first().unwrap_or(&Value::Null);
1426
1833
  let end = args.get(1).unwrap_or(&Value::Null);
1427
- arr_builtins::slice(&Value::Array(Rc::clone(&a_clone)), start, end)
1834
+ arr_builtins::slice(&Value::Array(a_clone.clone()), start, end)
1835
+ }),
1836
+ "concat" => make_native_fn(move |args: &[Value]| {
1837
+ arr_builtins::concat(&Value::Array(a_clone.clone()), args)
1428
1838
  }),
1429
- "concat" => Rc::new(move |args: &[Value]| arr_builtins::concat(&Value::Array(Rc::clone(&a_clone)), args)),
1430
- "join" => Rc::new(move |args: &[Value]| {
1839
+ "join" => make_native_fn(move |args: &[Value]| {
1431
1840
  let sep = args.first().unwrap_or(&Value::Null);
1432
- arr_builtins::join(&Value::Array(Rc::clone(&a_clone)), sep)
1841
+ arr_builtins::join(&Value::Array(a_clone.clone()), sep)
1433
1842
  }),
1434
- "indexOf" => Rc::new(move |args: &[Value]| {
1843
+ "indexOf" => make_native_fn(move |args: &[Value]| {
1435
1844
  let search = args.first().unwrap_or(&Value::Null);
1436
- arr_builtins::index_of(&Value::Array(Rc::clone(&a_clone)), search)
1845
+ arr_builtins::index_of(&Value::Array(a_clone.clone()), search)
1437
1846
  }),
1438
- "includes" => Rc::new(move |args: &[Value]| {
1847
+ "includes" => make_native_fn(move |args: &[Value]| {
1439
1848
  let search = args.first().unwrap_or(&Value::Null);
1440
1849
  let from = args.get(1);
1441
- arr_builtins::includes(&Value::Array(Rc::clone(&a_clone)), search, from)
1850
+ arr_builtins::includes(&Value::Array(a_clone.clone()), search, from)
1442
1851
  }),
1443
- "map" => Rc::new(move |args: &[Value]| {
1852
+ "map" => make_native_fn(move |args: &[Value]| {
1444
1853
  let cb = args.first().cloned().unwrap_or(Value::Null);
1445
- arr_builtins::map(&Value::Array(Rc::clone(&a_clone)), &cb)
1854
+ arr_builtins::map(&Value::Array(a_clone.clone()), &cb)
1446
1855
  }),
1447
- "filter" => Rc::new(move |args: &[Value]| {
1856
+ "filter" => make_native_fn(move |args: &[Value]| {
1448
1857
  let cb = args.first().cloned().unwrap_or(Value::Null);
1449
- arr_builtins::filter(&Value::Array(Rc::clone(&a_clone)), &cb)
1858
+ arr_builtins::filter(&Value::Array(a_clone.clone()), &cb)
1450
1859
  }),
1451
- "reduce" => Rc::new(move |args: &[Value]| {
1860
+ "reduce" => make_native_fn(move |args: &[Value]| {
1452
1861
  let cb = args.first().cloned().unwrap_or(Value::Null);
1453
1862
  let init = args.get(1).cloned().unwrap_or(Value::Null);
1454
- arr_builtins::reduce(&Value::Array(Rc::clone(&a_clone)), &cb, &init)
1863
+ arr_builtins::reduce(&Value::Array(a_clone.clone()), &cb, &init)
1455
1864
  }),
1456
- "forEach" => Rc::new(move |args: &[Value]| {
1865
+ "forEach" => make_native_fn(move |args: &[Value]| {
1457
1866
  let cb = args.first().cloned().unwrap_or(Value::Null);
1458
- arr_builtins::for_each(&Value::Array(Rc::clone(&a_clone)), &cb)
1867
+ arr_builtins::for_each(&Value::Array(a_clone.clone()), &cb)
1459
1868
  }),
1460
- "find" => Rc::new(move |args: &[Value]| {
1869
+ "find" => make_native_fn(move |args: &[Value]| {
1461
1870
  let cb = args.first().cloned().unwrap_or(Value::Null);
1462
- arr_builtins::find(&Value::Array(Rc::clone(&a_clone)), &cb)
1871
+ arr_builtins::find(&Value::Array(a_clone.clone()), &cb)
1463
1872
  }),
1464
- "findIndex" => Rc::new(move |args: &[Value]| {
1873
+ "findIndex" => make_native_fn(move |args: &[Value]| {
1465
1874
  let cb = args.first().cloned().unwrap_or(Value::Null);
1466
- arr_builtins::find_index(&Value::Array(Rc::clone(&a_clone)), &cb)
1875
+ arr_builtins::find_index(&Value::Array(a_clone.clone()), &cb)
1467
1876
  }),
1468
- "some" => Rc::new(move |args: &[Value]| {
1877
+ "some" => make_native_fn(move |args: &[Value]| {
1469
1878
  let cb = args.first().cloned().unwrap_or(Value::Null);
1470
- arr_builtins::some(&Value::Array(Rc::clone(&a_clone)), &cb)
1879
+ arr_builtins::some(&Value::Array(a_clone.clone()), &cb)
1471
1880
  }),
1472
- "every" => Rc::new(move |args: &[Value]| {
1881
+ "every" => make_native_fn(move |args: &[Value]| {
1473
1882
  let cb = args.first().cloned().unwrap_or(Value::Null);
1474
- arr_builtins::every(&Value::Array(Rc::clone(&a_clone)), &cb)
1883
+ arr_builtins::every(&Value::Array(a_clone.clone()), &cb)
1475
1884
  }),
1476
- "flat" => Rc::new(move |args: &[Value]| {
1885
+ "flat" => make_native_fn(move |args: &[Value]| {
1477
1886
  let depth = args.first().unwrap_or(&Value::Number(1.0));
1478
- arr_builtins::flat(&Value::Array(Rc::clone(&a_clone)), depth)
1887
+ arr_builtins::flat(&Value::Array(a_clone.clone()), depth)
1479
1888
  }),
1480
- "flatMap" => Rc::new(move |args: &[Value]| {
1889
+ "flatMap" => make_native_fn(move |args: &[Value]| {
1481
1890
  let cb = args.first().cloned().unwrap_or(Value::Null);
1482
- arr_builtins::flat_map(&Value::Array(Rc::clone(&a_clone)), &cb)
1891
+ arr_builtins::flat_map(&Value::Array(a_clone.clone()), &cb)
1483
1892
  }),
1484
- "sort" => Rc::new(move |args: &[Value]| {
1893
+ "sort" => make_native_fn(move |args: &[Value]| {
1485
1894
  let cmp = args.first();
1486
1895
  if let Some(Value::Function(_)) = cmp {
1487
- arr_builtins::sort_with_comparator(&Value::Array(Rc::clone(&a_clone)), cmp.unwrap())
1896
+ arr_builtins::sort_with_comparator(
1897
+ &Value::Array(a_clone.clone()),
1898
+ cmp.unwrap(),
1899
+ )
1488
1900
  } else {
1489
- arr_builtins::sort_default(&Value::Array(Rc::clone(&a_clone)))
1901
+ arr_builtins::sort_default(&Value::Array(a_clone.clone()))
1490
1902
  }
1491
1903
  }),
1492
- "splice" => Rc::new(move |args: &[Value]| {
1904
+ "splice" => make_native_fn(move |args: &[Value]| {
1493
1905
  let start = args.first().unwrap_or(&Value::Null);
1494
1906
  let delete_count = args.get(1).map(|v| v as &Value);
1495
1907
  let items: Vec<Value> = args.get(2..).unwrap_or(&[]).to_vec();
1496
- arr_builtins::splice(&Value::Array(Rc::clone(&a_clone)), start, delete_count, &items)
1908
+ arr_builtins::splice(
1909
+ &Value::Array(a_clone.clone()),
1910
+ start,
1911
+ delete_count,
1912
+ &items,
1913
+ )
1497
1914
  }),
1498
1915
  _ => return Err(format!("Property '{}' not found", key)),
1499
1916
  };
@@ -1512,81 +1929,88 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
1512
1929
  }
1513
1930
  let s_clone: Arc<str> = Arc::clone(s);
1514
1931
  let method: ArrayMethodFn = match key_s {
1515
- "indexOf" => Rc::new(move |args: &[Value]| {
1932
+ "indexOf" => make_native_fn(move |args: &[Value]| {
1516
1933
  let search = args.first().unwrap_or(&Value::Null);
1517
1934
  let from = args.get(1);
1518
1935
  str_builtins::index_of(&Value::String(Arc::clone(&s_clone)), search, from)
1519
1936
  }),
1520
- "lastIndexOf" => Rc::new(move |args: &[Value]| {
1937
+ "lastIndexOf" => make_native_fn(move |args: &[Value]| {
1521
1938
  let search = args.first().unwrap_or(&Value::Null);
1522
- let position = args
1523
- .get(1)
1524
- .cloned()
1525
- .unwrap_or(Value::Number(f64::INFINITY));
1939
+ let position = args.get(1).cloned().unwrap_or(Value::Number(f64::INFINITY));
1526
1940
  str_builtins::last_index_of(
1527
1941
  &Value::String(Arc::clone(&s_clone)),
1528
1942
  search,
1529
1943
  &position,
1530
1944
  )
1531
1945
  }),
1532
- "includes" => Rc::new(move |args: &[Value]| {
1946
+ "includes" => make_native_fn(move |args: &[Value]| {
1533
1947
  let search = args.first().unwrap_or(&Value::Null);
1534
1948
  let from = args.get(1);
1535
1949
  str_builtins::includes(&Value::String(Arc::clone(&s_clone)), search, from)
1536
1950
  }),
1537
- "slice" => Rc::new(move |args: &[Value]| {
1951
+ "slice" => make_native_fn(move |args: &[Value]| {
1538
1952
  let start = args.first().unwrap_or(&Value::Null);
1539
1953
  let end = args.get(1).unwrap_or(&Value::Null);
1540
1954
  str_builtins::slice(&Value::String(Arc::clone(&s_clone)), start, end)
1541
1955
  }),
1542
- "substring" => Rc::new(move |args: &[Value]| {
1956
+ "substring" => make_native_fn(move |args: &[Value]| {
1543
1957
  let start = args.first().unwrap_or(&Value::Null);
1544
1958
  let end = args.get(1).unwrap_or(&Value::Null);
1545
1959
  str_builtins::substring(&Value::String(Arc::clone(&s_clone)), start, end)
1546
1960
  }),
1547
- "split" => Rc::new(move |args: &[Value]| {
1961
+ "split" => make_native_fn(move |args: &[Value]| {
1548
1962
  let sep = args.first().unwrap_or(&Value::Null);
1549
1963
  str_builtins::split(&Value::String(Arc::clone(&s_clone)), sep)
1550
1964
  }),
1551
- "trim" => Rc::new(move |_args: &[Value]| str_builtins::trim(&Value::String(Arc::clone(&s_clone)))),
1552
- "toUpperCase" => Rc::new(move |_args: &[Value]| str_builtins::to_upper_case(&Value::String(Arc::clone(&s_clone)))),
1553
- "toLowerCase" => Rc::new(move |_args: &[Value]| str_builtins::to_lower_case(&Value::String(Arc::clone(&s_clone)))),
1554
- "startsWith" => Rc::new(move |args: &[Value]| {
1965
+ "trim" => make_native_fn(move |_args: &[Value]| {
1966
+ str_builtins::trim(&Value::String(Arc::clone(&s_clone)))
1967
+ }),
1968
+ "toUpperCase" => make_native_fn(move |_args: &[Value]| {
1969
+ str_builtins::to_upper_case(&Value::String(Arc::clone(&s_clone)))
1970
+ }),
1971
+ "toLowerCase" => make_native_fn(move |_args: &[Value]| {
1972
+ str_builtins::to_lower_case(&Value::String(Arc::clone(&s_clone)))
1973
+ }),
1974
+ "startsWith" => make_native_fn(move |args: &[Value]| {
1555
1975
  let search = args.first().unwrap_or(&Value::Null);
1556
1976
  str_builtins::starts_with(&Value::String(Arc::clone(&s_clone)), search)
1557
1977
  }),
1558
- "endsWith" => Rc::new(move |args: &[Value]| {
1978
+ "endsWith" => make_native_fn(move |args: &[Value]| {
1559
1979
  let search = args.first().unwrap_or(&Value::Null);
1560
1980
  str_builtins::ends_with(&Value::String(Arc::clone(&s_clone)), search)
1561
1981
  }),
1562
- "replace" => Rc::new(move |args: &[Value]| {
1982
+ "replace" => make_native_fn(move |args: &[Value]| {
1563
1983
  let search = args.first().unwrap_or(&Value::Null);
1564
1984
  let replacement = args.get(1).unwrap_or(&Value::Null);
1565
1985
  str_builtins::replace(&Value::String(Arc::clone(&s_clone)), search, replacement)
1566
1986
  }),
1567
- "replaceAll" => Rc::new(move |args: &[Value]| {
1987
+ "replaceAll" => make_native_fn(move |args: &[Value]| {
1568
1988
  let search = args.first().unwrap_or(&Value::Null);
1569
1989
  let replacement = args.get(1).unwrap_or(&Value::Null);
1570
- str_builtins::replace_all(&Value::String(Arc::clone(&s_clone)), search, replacement)
1990
+ str_builtins::replace_all(
1991
+ &Value::String(Arc::clone(&s_clone)),
1992
+ search,
1993
+ replacement,
1994
+ )
1571
1995
  }),
1572
- "charAt" => Rc::new(move |args: &[Value]| {
1996
+ "charAt" => make_native_fn(move |args: &[Value]| {
1573
1997
  let idx = args.first().unwrap_or(&Value::Null);
1574
1998
  str_builtins::char_at(&Value::String(Arc::clone(&s_clone)), idx)
1575
1999
  }),
1576
- "charCodeAt" => Rc::new(move |args: &[Value]| {
2000
+ "charCodeAt" => make_native_fn(move |args: &[Value]| {
1577
2001
  let idx = args.first().unwrap_or(&Value::Null);
1578
2002
  str_builtins::char_code_at(&Value::String(Arc::clone(&s_clone)), idx)
1579
2003
  }),
1580
- "repeat" => Rc::new(move |args: &[Value]| {
2004
+ "repeat" => make_native_fn(move |args: &[Value]| {
1581
2005
  let count = args.first().unwrap_or(&Value::Null);
1582
2006
  str_builtins::repeat(&Value::String(Arc::clone(&s_clone)), count)
1583
2007
  }),
1584
- "padStart" => Rc::new(move |args: &[Value]| {
2008
+ "padStart" => make_native_fn(move |args: &[Value]| {
1585
2009
  let target_len = args.first().unwrap_or(&Value::Null);
1586
2010
  let pad = args.get(1).unwrap_or(&Value::Null);
1587
2011
  str_builtins::pad_start(&Value::String(Arc::clone(&s_clone)), target_len, pad)
1588
2012
  }),
1589
- "padEnd" => Rc::new(move |args: &[Value]| {
2013
+ "padEnd" => make_native_fn(move |args: &[Value]| {
1590
2014
  let target_len = args.first().unwrap_or(&Value::Null);
1591
2015
  let pad = args.get(1).unwrap_or(&Value::Null);
1592
2016
  str_builtins::pad_end(&Value::String(Arc::clone(&s_clone)), target_len, pad)
@@ -1595,7 +2019,27 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
1595
2019
  };
1596
2020
  Ok(Value::Function(method))
1597
2021
  }
1598
- _ => Err(format!("Cannot read property '{}' of {}", key, obj.type_name())),
2022
+ #[cfg(feature = "http")]
2023
+ Value::Promise(p) => match key.as_ref() {
2024
+ "then" => {
2025
+ let pc = Arc::clone(p);
2026
+ Ok(Value::native(move |args| {
2027
+ tishlang_runtime::promise_instance_then(&pc, args)
2028
+ }))
2029
+ }
2030
+ "catch" => {
2031
+ let pc = Arc::clone(p);
2032
+ Ok(Value::native(move |args| {
2033
+ tishlang_runtime::promise_instance_catch(&pc, args)
2034
+ }))
2035
+ }
2036
+ _ => Err(format!("Property '{}' not found", key)),
2037
+ },
2038
+ _ => Err(format!(
2039
+ "Cannot read property '{}' of {}",
2040
+ key,
2041
+ obj.type_name()
2042
+ )),
1599
2043
  }
1600
2044
  }
1601
2045