@tishlang/tish 1.7.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 (95) hide show
  1. package/Cargo.toml +1 -0
  2. package/README.md +2 -0
  3. package/bin/tish +0 -0
  4. package/crates/js_to_tish/src/transform/expr.rs +28 -8
  5. package/crates/js_to_tish/src/transform/stmt.rs +49 -22
  6. package/crates/tish/Cargo.toml +15 -5
  7. package/crates/tish/src/cargo_native_registry.rs +29 -0
  8. package/crates/tish/src/cli_help.rs +16 -10
  9. package/crates/tish/src/main.rs +87 -32
  10. package/crates/tish/src/repl_completion.rs +3 -3
  11. package/crates/tish/tests/cargo_example_compile.rs +1 -1
  12. package/crates/tish/tests/integration_test.rs +19 -7
  13. package/crates/tish/tests/shortcircuit.rs +1 -1
  14. package/crates/tish_ast/src/ast.rs +80 -9
  15. package/crates/tish_build_utils/Cargo.toml +4 -0
  16. package/crates/tish_build_utils/src/lib.rs +105 -2
  17. package/crates/tish_builtins/Cargo.toml +5 -1
  18. package/crates/tish_builtins/src/array.rs +13 -12
  19. package/crates/tish_builtins/src/construct.rs +34 -33
  20. package/crates/tish_builtins/src/globals.rs +12 -11
  21. package/crates/tish_builtins/src/helpers.rs +2 -1
  22. package/crates/tish_builtins/src/object.rs +3 -2
  23. package/crates/tish_builtins/src/string.rs +73 -3
  24. package/crates/tish_bytecode/src/compiler.rs +12 -14
  25. package/crates/tish_bytecode/src/opcode.rs +12 -3
  26. package/crates/tish_compile/Cargo.toml +1 -0
  27. package/crates/tish_compile/src/codegen.rs +745 -199
  28. package/crates/tish_compile/src/infer.rs +6 -0
  29. package/crates/tish_compile/src/lib.rs +4 -3
  30. package/crates/tish_compile/src/resolve.rs +180 -82
  31. package/crates/tish_compile/src/types.rs +175 -11
  32. package/crates/tish_compile_js/Cargo.toml +1 -0
  33. package/crates/tish_compile_js/src/codegen.rs +152 -29
  34. package/crates/tish_compile_js/src/lib.rs +3 -1
  35. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +31 -12
  36. package/crates/tish_core/Cargo.toml +8 -0
  37. package/crates/tish_core/src/json.rs +102 -53
  38. package/crates/tish_core/src/lib.rs +3 -1
  39. package/crates/tish_core/src/macros.rs +5 -5
  40. package/crates/tish_core/src/value.rs +53 -15
  41. package/crates/tish_core/src/vmref.rs +178 -0
  42. package/crates/tish_eval/Cargo.toml +17 -2
  43. package/crates/tish_eval/src/eval.rs +90 -28
  44. package/crates/tish_eval/src/http.rs +61 -0
  45. package/crates/tish_eval/src/lib.rs +3 -3
  46. package/crates/tish_eval/src/natives.rs +41 -0
  47. package/crates/tish_eval/src/value.rs +7 -3
  48. package/crates/tish_eval/src/value_convert.rs +13 -5
  49. package/crates/tish_fmt/src/lib.rs +120 -30
  50. package/crates/tish_lexer/src/lib.rs +20 -5
  51. package/crates/tish_lexer/src/token.rs +4 -0
  52. package/crates/tish_llvm/src/lib.rs +3 -1
  53. package/crates/tish_lsp/Cargo.toml +4 -1
  54. package/crates/tish_lsp/README.md +1 -1
  55. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  56. package/crates/tish_lsp/src/import_goto.rs +549 -0
  57. package/crates/tish_lsp/src/main.rs +502 -102
  58. package/crates/tish_native/src/build.rs +3 -2
  59. package/crates/tish_native/src/lib.rs +6 -2
  60. package/crates/tish_opt/src/lib.rs +17 -2
  61. package/crates/tish_parser/src/lib.rs +10 -3
  62. package/crates/tish_parser/src/parser.rs +346 -56
  63. package/crates/tish_resolve/Cargo.toml +13 -0
  64. package/crates/tish_resolve/src/lib.rs +3436 -0
  65. package/crates/tish_resolve/src/pos.rs +133 -0
  66. package/crates/tish_runtime/Cargo.toml +68 -3
  67. package/crates/tish_runtime/src/http.rs +1123 -141
  68. package/crates/tish_runtime/src/http_fetch.rs +15 -14
  69. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  70. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  71. package/crates/tish_runtime/src/lib.rs +159 -29
  72. package/crates/tish_runtime/src/promise.rs +199 -36
  73. package/crates/tish_runtime/src/promise_io.rs +2 -1
  74. package/crates/tish_runtime/src/timers.rs +37 -1
  75. package/crates/tish_runtime/src/ws.rs +26 -28
  76. package/crates/tish_ui/src/jsx.rs +279 -8
  77. package/crates/tish_ui/src/lib.rs +5 -2
  78. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  79. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  80. package/crates/tish_vm/Cargo.toml +15 -5
  81. package/crates/tish_vm/src/vm.rs +506 -259
  82. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +3 -1
  83. package/crates/tish_wasm/src/lib.rs +17 -14
  84. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  85. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  86. package/crates/tishlang_cargo_bindgen/Cargo.toml +1 -0
  87. package/crates/tishlang_cargo_bindgen/src/discover.rs +68 -0
  88. package/crates/tishlang_cargo_bindgen/src/lib.rs +5 -4
  89. package/justfile +8 -0
  90. package/package.json +1 -1
  91. package/platform/darwin-arm64/tish +0 -0
  92. package/platform/darwin-x64/tish +0 -0
  93. package/platform/linux-arm64/tish +0 -0
  94. package/platform/linux-x64/tish +0 -0
  95. package/platform/win32-x64/tish.exe +0 -0
@@ -1,7 +1,8 @@
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
 
@@ -12,13 +13,43 @@ use tishlang_builtins::globals as globals_builtins;
12
13
  use tishlang_builtins::math as math_builtins;
13
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};
16
17
 
17
- type ArrayMethodFn = Rc<dyn Fn(&[Value]) -> Value>;
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
+ }
38
+
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,31 +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]| {
93
+ "readFile" => Some(Value::native(|args: &[Value]| {
55
94
  tishlang_runtime::read_file(args)
56
- }))),
57
- "writeFile" => Some(Value::Function(Rc::new(|args: &[Value]| {
95
+ })),
96
+ "writeFile" => Some(Value::native(|args: &[Value]| {
58
97
  tishlang_runtime::write_file(args)
59
- }))),
60
- "fileExists" => Some(Value::Function(Rc::new(|args: &[Value]| {
98
+ })),
99
+ "fileExists" => Some(Value::native(|args: &[Value]| {
61
100
  tishlang_runtime::file_exists(args)
62
- }))),
63
- "isDir" => Some(Value::Function(Rc::new(|args: &[Value]| {
101
+ })),
102
+ "isDir" => Some(Value::native(|args: &[Value]| {
64
103
  tishlang_runtime::is_dir(args)
65
- }))),
66
- "readDir" => Some(Value::Function(Rc::new(|args: &[Value]| {
104
+ })),
105
+ "readDir" => Some(Value::native(|args: &[Value]| {
67
106
  tishlang_runtime::read_dir(args)
68
- }))),
69
- "mkdir" => Some(Value::Function(Rc::new(|args: &[Value]| {
107
+ })),
108
+ "mkdir" => Some(Value::native(|args: &[Value]| {
70
109
  tishlang_runtime::mkdir(args)
71
- }))),
110
+ })),
72
111
  _ => None,
73
112
  };
74
113
  }
@@ -76,81 +115,123 @@ fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str)
76
115
  if spec == "tish:http" && cap_allows(enabled, "http") {
77
116
  return match export_name {
78
117
  // Bytecode compiler lowers `await expr` to `tish:http.await(promise)` (see tish_bytecode compiler).
79
- "await" => Some(Value::Function(Rc::new(|args: &[Value]| {
118
+ "await" => Some(Value::native(|args: &[Value]| {
80
119
  tishlang_runtime::await_promise(args.first().cloned().unwrap_or(Value::Null))
81
- }))),
82
- "fetch" => Some(Value::Function(Rc::new(|args: &[Value]| {
120
+ })),
121
+ "fetch" => Some(Value::native(|args: &[Value]| {
83
122
  tishlang_runtime::fetch_promise(args.to_vec())
84
- }))),
85
- "fetchAll" => Some(Value::Function(Rc::new(|args: &[Value]| {
123
+ })),
124
+ "fetchAll" => Some(Value::native(|args: &[Value]| {
86
125
  tishlang_runtime::fetch_all_promise(args.to_vec())
87
- }))),
88
- "serve" => Some(Value::Function(Rc::new(|args: &[Value]| {
89
- let handler = args.get(1).cloned().unwrap_or(Value::Null);
90
- 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 {
91
154
  tishlang_runtime::http_serve(args, move |req_args| f(req_args))
92
155
  } else {
93
156
  Value::Null
94
157
  }
95
- }))),
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
+ })),
96
177
  _ => None,
97
178
  };
98
179
  }
99
180
  #[cfg(feature = "process")]
100
181
  if spec == "tish:process" && cap_allows(enabled, "process") {
101
182
  return match export_name {
102
- "exit" => Some(Value::Function(Rc::new(|args: &[Value]| {
183
+ "exit" => Some(Value::native(|args: &[Value]| {
103
184
  tishlang_runtime::process_exit(args)
104
- }))),
105
- "cwd" => Some(Value::Function(Rc::new(|args: &[Value]| {
185
+ })),
186
+ "cwd" => Some(Value::native(|args: &[Value]| {
106
187
  tishlang_runtime::process_cwd(args)
107
- }))),
108
- "exec" => Some(Value::Function(Rc::new(|args: &[Value]| {
188
+ })),
189
+ "exec" => Some(Value::native(|args: &[Value]| {
109
190
  tishlang_runtime::process_exec(args)
110
- }))),
111
- "argv" => Some(Value::Array(Rc::new(RefCell::new(
191
+ })),
192
+ "argv" => Some(Value::Array(VmRef::new(
112
193
  std::env::args().map(|s| Value::String(s.into())).collect(),
113
- )))),
114
- "env" => Some(Value::Object(Rc::new(RefCell::new(
194
+ ))),
195
+ "env" => Some(Value::Object(VmRef::new(
115
196
  std::env::vars()
116
197
  .map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
117
198
  .collect(),
118
- )))),
199
+ ))),
119
200
  "process" => {
120
201
  let mut m = ObjectMap::default();
121
202
  m.insert(
122
203
  "exit".into(),
123
- Value::Function(Rc::new(|args: &[Value]| {
204
+ Value::native(|args: &[Value]| {
124
205
  tishlang_runtime::process_exit(args)
125
- })),
206
+ }),
126
207
  );
127
208
  m.insert(
128
209
  "cwd".into(),
129
- Value::Function(Rc::new(|args: &[Value]| {
210
+ Value::native(|args: &[Value]| {
130
211
  tishlang_runtime::process_cwd(args)
131
- })),
212
+ }),
132
213
  );
133
214
  m.insert(
134
215
  "exec".into(),
135
- Value::Function(Rc::new(|args: &[Value]| {
216
+ Value::native(|args: &[Value]| {
136
217
  tishlang_runtime::process_exec(args)
137
- })),
218
+ }),
138
219
  );
139
220
  m.insert(
140
221
  "argv".into(),
141
- Value::Array(Rc::new(RefCell::new(
222
+ Value::Array(VmRef::new(
142
223
  std::env::args().map(|s| Value::String(s.into())).collect(),
143
- ))),
224
+ )),
144
225
  );
145
226
  m.insert(
146
227
  "env".into(),
147
- Value::Object(Rc::new(RefCell::new(
228
+ Value::Object(VmRef::new(
148
229
  std::env::vars()
149
230
  .map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
150
231
  .collect(),
151
- ))),
232
+ )),
152
233
  );
153
- Some(Value::Object(Rc::new(RefCell::new(m))))
234
+ Some(Value::Object(VmRef::new(m)))
154
235
  }
155
236
  _ => None,
156
237
  };
@@ -158,13 +239,13 @@ fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str)
158
239
  #[cfg(feature = "ws")]
159
240
  if spec == "tish:ws" && cap_allows(enabled, "ws") {
160
241
  return match export_name {
161
- "WebSocket" => Some(Value::Function(Rc::new(|args: &[Value]| {
242
+ "WebSocket" => Some(Value::native(|args: &[Value]| {
162
243
  tishlang_runtime::web_socket_client(args)
163
- }))),
164
- "Server" => Some(Value::Function(Rc::new(|args: &[Value]| {
244
+ })),
245
+ "Server" => Some(Value::native(|args: &[Value]| {
165
246
  tishlang_runtime::web_socket_server_construct(args)
166
- }))),
167
- "wsSend" => Some(Value::Function(Rc::new(|args: &[Value]| {
247
+ })),
248
+ "wsSend" => Some(Value::native(|args: &[Value]| {
168
249
  Value::Bool(tishlang_runtime::ws_send_native(
169
250
  args.first().unwrap_or(&Value::Null),
170
251
  &args
@@ -172,10 +253,10 @@ fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str)
172
253
  .map(|v| v.to_display_string())
173
254
  .unwrap_or_default(),
174
255
  ))
175
- }))),
176
- "wsBroadcast" => Some(Value::Function(Rc::new(|args: &[Value]| {
256
+ })),
257
+ "wsBroadcast" => Some(Value::native(|args: &[Value]| {
177
258
  tishlang_runtime::ws_broadcast_native(args)
178
- }))),
259
+ })),
179
260
  _ => None,
180
261
  };
181
262
  }
@@ -218,217 +299,223 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
218
299
  let mut console = ObjectMap::default();
219
300
  console.insert(
220
301
  "debug".into(),
221
- Value::Function(Rc::new(|args: &[Value]| {
302
+ Value::native(|args: &[Value]| {
222
303
  let s =
223
304
  tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
224
305
  vm_log(&s);
225
306
  Value::Null
226
- })),
307
+ }),
227
308
  );
228
309
  console.insert(
229
310
  "log".into(),
230
- Value::Function(Rc::new(|args: &[Value]| {
311
+ Value::native(|args: &[Value]| {
231
312
  let s =
232
313
  tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
233
314
  vm_log(&s);
234
315
  Value::Null
235
- })),
316
+ }),
236
317
  );
237
318
  console.insert(
238
319
  "info".into(),
239
- Value::Function(Rc::new(|args: &[Value]| {
320
+ Value::native(|args: &[Value]| {
240
321
  let s =
241
322
  tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
242
323
  vm_log(&s);
243
324
  Value::Null
244
- })),
325
+ }),
245
326
  );
246
327
  console.insert(
247
328
  "warn".into(),
248
- Value::Function(Rc::new(|args: &[Value]| {
329
+ Value::native(|args: &[Value]| {
249
330
  let s =
250
331
  tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
251
332
  vm_log_err(&s);
252
333
  Value::Null
253
- })),
334
+ }),
254
335
  );
255
336
  console.insert(
256
337
  "error".into(),
257
- Value::Function(Rc::new(|args: &[Value]| {
338
+ Value::native(|args: &[Value]| {
258
339
  let s =
259
340
  tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
260
341
  vm_log_err(&s);
261
342
  Value::Null
262
- })),
343
+ }),
263
344
  );
264
345
  g.insert(
265
346
  "console".into(),
266
- Value::Object(Rc::new(RefCell::new(console))),
347
+ Value::Object(VmRef::new(console)),
267
348
  );
268
349
 
269
350
  let mut math = ObjectMap::default();
270
351
  math.insert(
271
352
  "abs".into(),
272
- Value::Function(Rc::new(|args: &[Value]| {
353
+ Value::native(|args: &[Value]| {
273
354
  let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
274
355
  Value::Number(n.abs())
275
- })),
356
+ }),
276
357
  );
277
358
  math.insert(
278
359
  "sqrt".into(),
279
- Value::Function(Rc::new(|args: &[Value]| {
360
+ Value::native(|args: &[Value]| {
280
361
  let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
281
362
  Value::Number(n.sqrt())
282
- })),
363
+ }),
283
364
  );
284
365
  math.insert(
285
366
  "floor".into(),
286
- Value::Function(Rc::new(|args: &[Value]| {
367
+ Value::native(|args: &[Value]| {
287
368
  let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
288
369
  Value::Number(n.floor())
289
- })),
370
+ }),
290
371
  );
291
372
  math.insert(
292
373
  "ceil".into(),
293
- Value::Function(Rc::new(|args: &[Value]| {
374
+ Value::native(|args: &[Value]| {
294
375
  let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
295
376
  Value::Number(n.ceil())
296
- })),
377
+ }),
297
378
  );
298
379
  math.insert(
299
380
  "round".into(),
300
- Value::Function(Rc::new(|args: &[Value]| {
381
+ Value::native(|args: &[Value]| {
301
382
  let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
302
383
  Value::Number(n.round())
303
- })),
384
+ }),
304
385
  );
305
386
  math.insert(
306
387
  "random".into(),
307
- Value::Function(Rc::new(|_| Value::Number(rand::random::<f64>()))),
388
+ Value::native(|_| Value::Number(rand::random::<f64>())),
308
389
  );
309
390
  math.insert(
310
391
  "min".into(),
311
- Value::Function(Rc::new(|args: &[Value]| {
392
+ Value::native(|args: &[Value]| {
312
393
  let nums: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
313
394
  Value::Number(nums.into_iter().fold(f64::NAN, |a, b| a.min(b)))
314
- })),
395
+ }),
315
396
  );
316
397
  math.insert(
317
398
  "max".into(),
318
- Value::Function(Rc::new(|args: &[Value]| {
399
+ Value::native(|args: &[Value]| {
319
400
  let nums: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
320
401
  Value::Number(nums.into_iter().fold(f64::NAN, |a, b| a.max(b)))
321
- })),
402
+ }),
322
403
  );
323
404
  math.insert(
324
405
  "pow".into(),
325
- Value::Function(Rc::new(|args: &[Value]| math_builtins::pow(args))),
406
+ Value::native(|args: &[Value]| math_builtins::pow(args)),
326
407
  );
327
408
  math.insert(
328
409
  "sin".into(),
329
- Value::Function(Rc::new(|args: &[Value]| math_builtins::sin(args))),
410
+ Value::native(|args: &[Value]| math_builtins::sin(args)),
330
411
  );
331
412
  math.insert(
332
413
  "cos".into(),
333
- Value::Function(Rc::new(|args: &[Value]| math_builtins::cos(args))),
414
+ Value::native(|args: &[Value]| math_builtins::cos(args)),
334
415
  );
335
416
  math.insert(
336
417
  "tan".into(),
337
- Value::Function(Rc::new(|args: &[Value]| math_builtins::tan(args))),
418
+ Value::native(|args: &[Value]| math_builtins::tan(args)),
338
419
  );
339
420
  math.insert(
340
421
  "log".into(),
341
- Value::Function(Rc::new(|args: &[Value]| math_builtins::log(args))),
422
+ Value::native(|args: &[Value]| math_builtins::log(args)),
342
423
  );
343
424
  math.insert(
344
425
  "exp".into(),
345
- Value::Function(Rc::new(|args: &[Value]| math_builtins::exp(args))),
426
+ Value::native(|args: &[Value]| math_builtins::exp(args)),
346
427
  );
347
428
  math.insert(
348
429
  "sign".into(),
349
- Value::Function(Rc::new(|args: &[Value]| math_builtins::sign(args))),
430
+ Value::native(|args: &[Value]| math_builtins::sign(args)),
350
431
  );
351
432
  math.insert(
352
433
  "trunc".into(),
353
- Value::Function(Rc::new(|args: &[Value]| math_builtins::trunc(args))),
434
+ Value::native(|args: &[Value]| math_builtins::trunc(args)),
354
435
  );
355
436
  math.insert("PI".into(), Value::Number(std::f64::consts::PI));
356
437
  math.insert("E".into(), Value::Number(std::f64::consts::E));
357
- g.insert("Math".into(), Value::Object(Rc::new(RefCell::new(math))));
438
+ g.insert("Math".into(), Value::Object(VmRef::new(math)));
358
439
 
359
440
  let mut json = ObjectMap::default();
360
441
  json.insert(
361
442
  "parse".into(),
362
- Value::Function(Rc::new(|args: &[Value]| {
443
+ Value::native(|args: &[Value]| {
363
444
  let s = args
364
445
  .first()
365
446
  .map(|v| v.to_display_string())
366
447
  .unwrap_or_default();
367
448
  tishlang_core::json_parse(&s).unwrap_or(Value::Null)
368
- })),
449
+ }),
369
450
  );
370
451
  json.insert(
371
452
  "stringify".into(),
372
- Value::Function(Rc::new(|args: &[Value]| {
453
+ Value::native(|args: &[Value]| {
373
454
  let v = args.first().unwrap_or(&Value::Null);
374
455
  Value::String(tishlang_core::json_stringify(v).into())
375
- })),
456
+ }),
376
457
  );
377
- g.insert("JSON".into(), Value::Object(Rc::new(RefCell::new(json))));
458
+ g.insert("JSON".into(), Value::Object(VmRef::new(json)));
378
459
 
379
460
  g.insert(
380
461
  "parseInt".into(),
381
- Value::Function(Rc::new(|args: &[Value]| globals_builtins::parse_int(args))),
462
+ Value::native(|args: &[Value]| globals_builtins::parse_int(args)),
382
463
  );
383
464
  g.insert(
384
465
  "parseFloat".into(),
385
- Value::Function(Rc::new(|args: &[Value]| {
466
+ Value::native(|args: &[Value]| {
386
467
  globals_builtins::parse_float(args)
387
- })),
468
+ }),
388
469
  );
389
470
  g.insert(
390
471
  "encodeURI".into(),
391
- Value::Function(Rc::new(|args: &[Value]| globals_builtins::encode_uri(args))),
472
+ Value::native(|args: &[Value]| globals_builtins::encode_uri(args)),
392
473
  );
393
474
  g.insert(
394
475
  "decodeURI".into(),
395
- Value::Function(Rc::new(|args: &[Value]| globals_builtins::decode_uri(args))),
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
+ }),
396
483
  );
397
484
  g.insert(
398
485
  "Boolean".into(),
399
- Value::Function(Rc::new(|args: &[Value]| globals_builtins::boolean(args))),
486
+ Value::native(|args: &[Value]| globals_builtins::boolean(args)),
400
487
  );
401
488
  g.insert(
402
489
  "isFinite".into(),
403
- Value::Function(Rc::new(|args: &[Value]| globals_builtins::is_finite(args))),
490
+ Value::native(|args: &[Value]| globals_builtins::is_finite(args)),
404
491
  );
405
492
  g.insert(
406
493
  "isNaN".into(),
407
- Value::Function(Rc::new(|args: &[Value]| globals_builtins::is_nan(args))),
494
+ Value::native(|args: &[Value]| globals_builtins::is_nan(args)),
408
495
  );
409
496
  g.insert("Infinity".into(), Value::Number(f64::INFINITY));
410
497
  g.insert("NaN".into(), Value::Number(f64::NAN));
411
498
  g.insert(
412
499
  "typeof".into(),
413
- Value::Function(Rc::new(|args: &[Value]| {
500
+ Value::native(|args: &[Value]| {
414
501
  let v = args.first().unwrap_or(&Value::Null);
415
502
  Value::String(v.type_name().into())
416
- })),
503
+ }),
417
504
  );
418
505
 
419
506
  // Date - at minimum Date.now() for timing
420
507
  let mut date = ObjectMap::default();
421
508
  date.insert(
422
509
  "now".into(),
423
- Value::Function(Rc::new(|_args: &[Value]| {
510
+ Value::native(|_args: &[Value]| {
424
511
  let ms = std::time::SystemTime::now()
425
512
  .duration_since(std::time::UNIX_EPOCH)
426
513
  .unwrap_or_default()
427
514
  .as_millis() as f64;
428
515
  Value::Number(ms)
429
- })),
516
+ }),
430
517
  );
431
- g.insert("Date".into(), Value::Object(Rc::new(RefCell::new(date))));
518
+ g.insert("Date".into(), Value::Object(VmRef::new(date)));
432
519
 
433
520
  g.insert(
434
521
  "Uint8Array".into(),
@@ -443,102 +530,102 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
443
530
  let mut object_methods = ObjectMap::default();
444
531
  object_methods.insert(
445
532
  "assign".into(),
446
- Value::Function(Rc::new(|args: &[Value]| {
533
+ Value::native(|args: &[Value]| {
447
534
  globals_builtins::object_assign(args)
448
- })),
535
+ }),
449
536
  );
450
537
  object_methods.insert(
451
538
  "fromEntries".into(),
452
- Value::Function(Rc::new(|args: &[Value]| {
539
+ Value::native(|args: &[Value]| {
453
540
  globals_builtins::object_from_entries(args)
454
- })),
541
+ }),
455
542
  );
456
543
  object_methods.insert(
457
544
  "keys".into(),
458
- Value::Function(Rc::new(|args: &[Value]| {
545
+ Value::native(|args: &[Value]| {
459
546
  globals_builtins::object_keys(args)
460
- })),
547
+ }),
461
548
  );
462
549
  object_methods.insert(
463
550
  "values".into(),
464
- Value::Function(Rc::new(|args: &[Value]| {
551
+ Value::native(|args: &[Value]| {
465
552
  globals_builtins::object_values(args)
466
- })),
553
+ }),
467
554
  );
468
555
  object_methods.insert(
469
556
  "entries".into(),
470
- Value::Function(Rc::new(|args: &[Value]| {
557
+ Value::native(|args: &[Value]| {
471
558
  globals_builtins::object_entries(args)
472
- })),
559
+ }),
473
560
  );
474
561
  g.insert(
475
562
  "Object".into(),
476
- Value::Object(Rc::new(RefCell::new(object_methods))),
563
+ Value::Object(VmRef::new(object_methods)),
477
564
  );
478
565
 
479
566
  // Array.isArray
480
567
  let mut array_static = ObjectMap::default();
481
568
  array_static.insert(
482
569
  "isArray".into(),
483
- Value::Function(Rc::new(|args: &[Value]| {
570
+ Value::native(|args: &[Value]| {
484
571
  globals_builtins::array_is_array(args)
485
- })),
572
+ }),
486
573
  );
487
574
  g.insert(
488
575
  "Array".into(),
489
- Value::Object(Rc::new(RefCell::new(array_static))),
576
+ Value::Object(VmRef::new(array_static)),
490
577
  );
491
578
 
492
579
  // String(value) as callable + String.fromCharCode
493
- let string_convert_fn = Value::Function(Rc::new(|args: &[Value]| {
580
+ let string_convert_fn = Value::native(|args: &[Value]| {
494
581
  globals_builtins::string_convert(args)
495
- }));
582
+ });
496
583
  let mut string_static = ObjectMap::default();
497
584
  string_static.insert(
498
585
  "fromCharCode".into(),
499
- Value::Function(Rc::new(|args: &[Value]| {
586
+ Value::native(|args: &[Value]| {
500
587
  globals_builtins::string_from_char_code(args)
501
- })),
588
+ }),
502
589
  );
503
590
  string_static.insert(Arc::from("__call"), string_convert_fn);
504
591
  g.insert(
505
592
  "String".into(),
506
- Value::Object(Rc::new(RefCell::new(string_static))),
593
+ Value::Object(VmRef::new(string_static)),
507
594
  );
508
595
 
509
596
  // JSX / Lattish: stubs for bytecode VM when no DOM (e.g. console). Override via set_global in browser.
510
597
  g.insert(
511
598
  "h".into(),
512
- Value::Function(Rc::new(|_args: &[Value]| Value::Null)),
599
+ Value::native(|_args: &[Value]| Value::Null),
513
600
  );
514
601
  g.insert(
515
602
  "Fragment".into(),
516
- Value::Object(Rc::new(RefCell::new(ObjectMap::default()))),
603
+ Value::Object(VmRef::new(ObjectMap::default())),
517
604
  );
518
605
  g.insert(
519
606
  "createRoot".into(),
520
- Value::Function(Rc::new(|_args: &[Value]| {
607
+ Value::native(|_args: &[Value]| {
521
608
  let mut render_obj = ObjectMap::default();
522
609
  render_obj.insert(
523
610
  "render".into(),
524
- Value::Function(Rc::new(|_args: &[Value]| Value::Null)),
611
+ Value::native(|_args: &[Value]| Value::Null),
525
612
  );
526
- Value::Object(Rc::new(RefCell::new(render_obj)))
527
- })),
613
+ Value::Object(VmRef::new(render_obj))
614
+ }),
528
615
  );
529
616
  g.insert(
530
617
  "useState".into(),
531
- Value::Function(Rc::new(|args: &[Value]| {
618
+ Value::native(|args: &[Value]| {
532
619
  let init = args.first().cloned().unwrap_or(Value::Null);
533
- let arr = vec![init, Value::Function(Rc::new(|_| Value::Null))];
534
- Value::Array(Rc::new(RefCell::new(arr)))
535
- })),
620
+ let arr = vec![init, Value::native(|_| Value::Null)];
621
+ Value::Array(VmRef::new(arr))
622
+ }),
536
623
  );
537
624
  let mut document_obj = ObjectMap::default();
538
625
  document_obj.insert("body".into(), Value::Null);
539
626
  g.insert(
540
627
  "document".into(),
541
- Value::Object(Rc::new(RefCell::new(document_obj))),
628
+ Value::Object(VmRef::new(document_obj)),
542
629
  );
543
630
 
544
631
  #[cfg(feature = "process")]
@@ -546,54 +633,122 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
546
633
  let mut process_obj = ObjectMap::default();
547
634
  process_obj.insert(
548
635
  "exit".into(),
549
- Value::Function(Rc::new(|args: &[Value]| {
636
+ Value::native(|args: &[Value]| {
550
637
  tishlang_runtime::process_exit(args)
551
- })),
638
+ }),
552
639
  );
553
640
  process_obj.insert(
554
641
  "cwd".into(),
555
- Value::Function(Rc::new(|args: &[Value]| {
642
+ Value::native(|args: &[Value]| {
556
643
  tishlang_runtime::process_cwd(args)
557
- })),
644
+ }),
558
645
  );
559
646
  process_obj.insert(
560
647
  "exec".into(),
561
- Value::Function(Rc::new(|args: &[Value]| {
648
+ Value::native(|args: &[Value]| {
562
649
  tishlang_runtime::process_exec(args)
563
- })),
650
+ }),
564
651
  );
565
652
  process_obj.insert(
566
653
  "argv".into(),
567
- Value::Array(Rc::new(RefCell::new(
654
+ Value::Array(VmRef::new(
568
655
  std::env::args().map(|s| Value::String(s.into())).collect(),
569
- ))),
656
+ )),
570
657
  );
571
658
  process_obj.insert(
572
659
  "env".into(),
573
- Value::Object(Rc::new(RefCell::new(
660
+ Value::Object(VmRef::new(
574
661
  std::env::vars()
575
662
  .map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
576
663
  .collect(),
577
- ))),
664
+ )),
578
665
  );
579
666
  g.insert(
580
667
  "process".into(),
581
- Value::Object(Rc::new(RefCell::new(process_obj))),
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)),
582
689
  );
583
690
  }
584
691
 
585
692
  #[cfg(feature = "http")]
586
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
+ );
587
722
  g.insert(
588
723
  "serve".into(),
589
- Value::Function(Rc::new(|args: &[Value]| {
590
- let handler = args.get(1).cloned().unwrap_or(Value::Null);
591
- 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 {
592
747
  tishlang_runtime::http_serve(args, move |req_args| f(req_args))
593
748
  } else {
594
749
  Value::Null
595
750
  }
596
- })),
751
+ }),
597
752
  );
598
753
  }
599
754
 
@@ -601,7 +756,7 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
601
756
  }
602
757
 
603
758
  /// Shared scope for closure capture (parent frame's locals).
604
- type ScopeMap = Rc<RefCell<ObjectMap>>;
759
+ type ScopeMap = VmRef<ObjectMap>;
605
760
 
606
761
  /// Options for the convenience [`run_with_options`] helper (one-shot VM run from the CLI).
607
762
  #[derive(Clone, Debug, Default)]
@@ -618,9 +773,14 @@ pub struct Vm {
618
773
  scope: ObjectMap,
619
774
  /// Enclosing scope for closures (captured parent frame locals).
620
775
  enclosing: Option<ScopeMap>,
621
- globals: Rc<RefCell<ObjectMap>>,
776
+ globals: VmRef<ObjectMap>,
622
777
  /// Capabilities for `LoadNativeExport` and globals such as `process` / `serve`.
623
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>>>,
624
784
  }
625
785
 
626
786
  impl Vm {
@@ -639,11 +799,24 @@ impl Vm {
639
799
  stack: Vec::new(),
640
800
  scope: ObjectMap::default(),
641
801
  enclosing: None,
642
- globals: Rc::new(RefCell::new(init_globals(capabilities.as_ref()))),
802
+ globals: VmRef::new(init_globals(capabilities.as_ref())),
643
803
  capabilities,
804
+ native_modules: VmRef::new(HashMap::new()),
644
805
  }
645
806
  }
646
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
+
647
820
  pub fn get_global(&self, name: &str) -> Option<Value> {
648
821
  self.globals.borrow().get(name).cloned()
649
822
  }
@@ -672,6 +845,22 @@ impl Vm {
672
845
  Self::read_u16(code, ip) as i16
673
846
  }
674
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
+
675
864
  pub fn run(&mut self, chunk: &Chunk) -> Result<Value, String> {
676
865
  self.run_with_options(chunk, false)
677
866
  }
@@ -693,7 +882,7 @@ impl Vm {
693
882
  let names = &chunk.names;
694
883
 
695
884
  let mut ip = 0;
696
- let local_scope: ScopeMap = Rc::new(RefCell::new(ObjectMap::default()));
885
+ let local_scope: ScopeMap = VmRef::new(ObjectMap::default());
697
886
  {
698
887
  let mut ls = local_scope.borrow_mut();
699
888
  let param_count = chunk.param_count as usize;
@@ -707,7 +896,7 @@ impl Vm {
707
896
  let rest_arr: Vec<Value> = args.iter().skip(ri).cloned().collect();
708
897
  ls.insert(
709
898
  Arc::clone(name),
710
- Value::Array(Rc::new(RefCell::new(rest_arr))),
899
+ Value::Array(VmRef::new(rest_arr)),
711
900
  );
712
901
  }
713
902
  }
@@ -750,20 +939,22 @@ impl Vm {
750
939
  .get(*nested_idx)
751
940
  .ok_or_else(|| "Nested chunk index out of bounds".to_string())?;
752
941
  let inner_clone = inner.clone();
753
- let globals = Rc::clone(&self.globals);
754
- let enclosing = Some(Rc::clone(&local_scope));
942
+ let globals = self.globals.clone();
943
+ let enclosing = Some(local_scope.clone());
755
944
  let capabilities = Arc::clone(&self.capabilities);
756
- Value::Function(Rc::new(move |args: &[Value]| {
945
+ let native_modules = self.native_modules.clone();
946
+ Value::native(move |args: &[Value]| {
757
947
  let mut vm = Vm {
758
948
  stack: Vec::new(),
759
949
  scope: ObjectMap::default(),
760
950
  enclosing: enclosing.clone(),
761
- globals: Rc::clone(&globals),
951
+ globals: globals.clone(),
762
952
  capabilities: Arc::clone(&capabilities),
953
+ native_modules: native_modules.clone(),
763
954
  };
764
955
  vm.run_chunk(&inner_clone, &inner_clone.nested, args, false)
765
956
  .unwrap_or(Value::Null)
766
- }))
957
+ })
767
958
  }
768
959
  };
769
960
  self.stack.push(v);
@@ -937,12 +1128,12 @@ impl Vm {
937
1128
  .pop()
938
1129
  .ok_or_else(|| "Stack underflow: no callee".to_string())?;
939
1130
  let f = match &callee {
940
- Value::Function(f) => Rc::clone(f),
1131
+ Value::Function(f) => f.clone(),
941
1132
  Value::Object(o) => {
942
1133
  if let Some(Value::Function(call_fn)) =
943
1134
  o.borrow().get(&Arc::from("__call"))
944
1135
  {
945
- Rc::clone(call_fn)
1136
+ call_fn.clone()
946
1137
  } else {
947
1138
  return Err(format!(
948
1139
  "Call of non-function: {}",
@@ -976,12 +1167,12 @@ impl Vm {
976
1167
  }
977
1168
  };
978
1169
  let f = match &callee {
979
- Value::Function(f) => Rc::clone(f),
1170
+ Value::Function(f) => f.clone(),
980
1171
  Value::Object(o) => {
981
1172
  if let Some(Value::Function(call_fn)) =
982
1173
  o.borrow().get(&Arc::from("__call"))
983
1174
  {
984
- Rc::clone(call_fn)
1175
+ call_fn.clone()
985
1176
  } else {
986
1177
  return Err(format!(
987
1178
  "Call of non-function: {}",
@@ -1168,7 +1359,7 @@ impl Vm {
1168
1359
  );
1169
1360
  }
1170
1361
  elems.reverse();
1171
- self.stack.push(Value::Array(Rc::new(RefCell::new(elems))));
1362
+ self.stack.push(Value::Array(VmRef::new(elems)));
1172
1363
  }
1173
1364
  Opcode::NewObject => {
1174
1365
  let n = Self::read_u16(code, &mut ip) as usize;
@@ -1185,7 +1376,7 @@ impl Vm {
1185
1376
  let key = key_val.to_display_string().into();
1186
1377
  map.insert(key, val);
1187
1378
  }
1188
- self.stack.push(Value::Object(Rc::new(RefCell::new(map))));
1379
+ self.stack.push(Value::Object(VmRef::new(map)));
1189
1380
  }
1190
1381
  Opcode::EnterTry => {
1191
1382
  let offset = Self::read_u16(code, &mut ip) as usize;
@@ -1225,7 +1416,7 @@ impl Vm {
1225
1416
  },
1226
1417
  );
1227
1418
  a.extend(b);
1228
- self.stack.push(Value::Array(Rc::new(RefCell::new(a))));
1419
+ self.stack.push(Value::Array(VmRef::new(a)));
1229
1420
  }
1230
1421
  Opcode::MergeObject => {
1231
1422
  let right = self
@@ -1260,7 +1451,7 @@ impl Vm {
1260
1451
  ));
1261
1452
  }
1262
1453
  self.stack
1263
- .push(Value::Object(Rc::new(RefCell::new(merged))));
1454
+ .push(Value::Object(VmRef::new(merged)));
1264
1455
  }
1265
1456
  Opcode::ArraySortNumeric => {
1266
1457
  let operand = Self::read_u16(code, &mut ip);
@@ -1302,7 +1493,7 @@ impl Vm {
1302
1493
  .pop()
1303
1494
  .ok_or_else(|| "Stack underflow".to_string())?;
1304
1495
  let result = match &arr {
1305
- Value::Array(a) => Value::Array(Rc::new(RefCell::new(a.borrow().clone()))),
1496
+ Value::Array(a) => Value::Array(VmRef::new(a.borrow().clone())),
1306
1497
  _ => Value::Null,
1307
1498
  };
1308
1499
  self.stack.push(result);
@@ -1341,7 +1532,7 @@ impl Vm {
1341
1532
  eval_binop(binop, &l, &r).unwrap_or(Value::Null)
1342
1533
  })
1343
1534
  .collect();
1344
- Value::Array(Rc::new(RefCell::new(mapped)))
1535
+ Value::Array(VmRef::new(mapped))
1345
1536
  } else {
1346
1537
  Value::Null
1347
1538
  };
@@ -1384,7 +1575,7 @@ impl Vm {
1384
1575
  })
1385
1576
  .cloned()
1386
1577
  .collect();
1387
- Value::Array(Rc::new(RefCell::new(filtered)))
1578
+ Value::Array(VmRef::new(filtered))
1388
1579
  } else {
1389
1580
  Value::Null
1390
1581
  };
@@ -1395,12 +1586,30 @@ impl Vm {
1395
1586
  .stack
1396
1587
  .pop()
1397
1588
  .ok_or_else(|| "Stack underflow".to_string())?;
1398
- 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
1399
1594
  .pop()
1400
- .ok_or_else(|| format!("Uncaught throw: {}", v.to_display_string()))?;
1401
- self.stack.truncate(stack_len);
1402
- self.stack.push(v);
1403
- 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
+ }
1404
1613
  }
1405
1614
  Opcode::LoadNativeExport => {
1406
1615
  let spec_idx = Self::read_u16(code, &mut ip);
@@ -1420,19 +1629,36 @@ impl Vm {
1420
1629
  return Err("LoadNativeExport: export_name constant out of bounds or not string".to_string());
1421
1630
  }
1422
1631
  };
1423
- let v = get_builtin_export(self.capabilities.as_ref(), spec, export_name).ok_or_else(|| {
1424
- if spec.starts_with("cargo:") {
1425
- format!(
1426
- "cargo:… imports are only supported by `tish build` with the Rust native backend (not the bytecode VM). Spec: {}",
1427
- spec
1428
- )
1429
- } else {
1430
- format!(
1431
- "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.",
1432
- spec, export_name
1433
- )
1434
- }
1435
- })?;
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
+ })?;
1436
1662
  self.stack.push(v);
1437
1663
  }
1438
1664
  Opcode::Closure | Opcode::LoadThis => {
@@ -1441,6 +1667,11 @@ impl Vm {
1441
1667
  }
1442
1668
  }
1443
1669
 
1670
+ #[cfg(feature = "timers")]
1671
+ if cap_allows(self.capabilities.as_ref(), "timers") {
1672
+ tishlang_runtime::drain_timers();
1673
+ }
1674
+
1444
1675
  Ok(self.stack.pop().unwrap_or(Value::Null))
1445
1676
  }
1446
1677
  }
@@ -1577,105 +1808,105 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
1577
1808
  if key_s == "length" {
1578
1809
  return Ok(Value::Number(a.borrow().len() as f64));
1579
1810
  }
1580
- let a_clone = Rc::clone(a);
1811
+ let a_clone = a.clone();
1581
1812
  let method: ArrayMethodFn = match key_s {
1582
- "push" => Rc::new(move |args: &[Value]| {
1583
- arr_builtins::push(&Value::Array(Rc::clone(&a_clone)), args)
1813
+ "push" => make_native_fn(move |args: &[Value]| {
1814
+ arr_builtins::push(&Value::Array(a_clone.clone()), args)
1584
1815
  }),
1585
- "pop" => Rc::new(move |_args: &[Value]| {
1586
- arr_builtins::pop(&Value::Array(Rc::clone(&a_clone)))
1816
+ "pop" => make_native_fn(move |_args: &[Value]| {
1817
+ arr_builtins::pop(&Value::Array(a_clone.clone()))
1587
1818
  }),
1588
- "shift" => Rc::new(move |_args: &[Value]| {
1589
- arr_builtins::shift(&Value::Array(Rc::clone(&a_clone)))
1819
+ "shift" => make_native_fn(move |_args: &[Value]| {
1820
+ arr_builtins::shift(&Value::Array(a_clone.clone()))
1590
1821
  }),
1591
- "unshift" => Rc::new(move |args: &[Value]| {
1592
- arr_builtins::unshift(&Value::Array(Rc::clone(&a_clone)), args)
1822
+ "unshift" => make_native_fn(move |args: &[Value]| {
1823
+ arr_builtins::unshift(&Value::Array(a_clone.clone()), args)
1593
1824
  }),
1594
- "reverse" => Rc::new(move |_args: &[Value]| {
1595
- arr_builtins::reverse(&Value::Array(Rc::clone(&a_clone)))
1825
+ "reverse" => make_native_fn(move |_args: &[Value]| {
1826
+ arr_builtins::reverse(&Value::Array(a_clone.clone()))
1596
1827
  }),
1597
- "shuffle" => Rc::new(move |_args: &[Value]| {
1598
- arr_builtins::shuffle(&Value::Array(Rc::clone(&a_clone)))
1828
+ "shuffle" => make_native_fn(move |_args: &[Value]| {
1829
+ arr_builtins::shuffle(&Value::Array(a_clone.clone()))
1599
1830
  }),
1600
- "slice" => Rc::new(move |args: &[Value]| {
1831
+ "slice" => make_native_fn(move |args: &[Value]| {
1601
1832
  let start = args.first().unwrap_or(&Value::Null);
1602
1833
  let end = args.get(1).unwrap_or(&Value::Null);
1603
- arr_builtins::slice(&Value::Array(Rc::clone(&a_clone)), start, end)
1834
+ arr_builtins::slice(&Value::Array(a_clone.clone()), start, end)
1604
1835
  }),
1605
- "concat" => Rc::new(move |args: &[Value]| {
1606
- arr_builtins::concat(&Value::Array(Rc::clone(&a_clone)), args)
1836
+ "concat" => make_native_fn(move |args: &[Value]| {
1837
+ arr_builtins::concat(&Value::Array(a_clone.clone()), args)
1607
1838
  }),
1608
- "join" => Rc::new(move |args: &[Value]| {
1839
+ "join" => make_native_fn(move |args: &[Value]| {
1609
1840
  let sep = args.first().unwrap_or(&Value::Null);
1610
- arr_builtins::join(&Value::Array(Rc::clone(&a_clone)), sep)
1841
+ arr_builtins::join(&Value::Array(a_clone.clone()), sep)
1611
1842
  }),
1612
- "indexOf" => Rc::new(move |args: &[Value]| {
1843
+ "indexOf" => make_native_fn(move |args: &[Value]| {
1613
1844
  let search = args.first().unwrap_or(&Value::Null);
1614
- arr_builtins::index_of(&Value::Array(Rc::clone(&a_clone)), search)
1845
+ arr_builtins::index_of(&Value::Array(a_clone.clone()), search)
1615
1846
  }),
1616
- "includes" => Rc::new(move |args: &[Value]| {
1847
+ "includes" => make_native_fn(move |args: &[Value]| {
1617
1848
  let search = args.first().unwrap_or(&Value::Null);
1618
1849
  let from = args.get(1);
1619
- arr_builtins::includes(&Value::Array(Rc::clone(&a_clone)), search, from)
1850
+ arr_builtins::includes(&Value::Array(a_clone.clone()), search, from)
1620
1851
  }),
1621
- "map" => Rc::new(move |args: &[Value]| {
1852
+ "map" => make_native_fn(move |args: &[Value]| {
1622
1853
  let cb = args.first().cloned().unwrap_or(Value::Null);
1623
- arr_builtins::map(&Value::Array(Rc::clone(&a_clone)), &cb)
1854
+ arr_builtins::map(&Value::Array(a_clone.clone()), &cb)
1624
1855
  }),
1625
- "filter" => Rc::new(move |args: &[Value]| {
1856
+ "filter" => make_native_fn(move |args: &[Value]| {
1626
1857
  let cb = args.first().cloned().unwrap_or(Value::Null);
1627
- arr_builtins::filter(&Value::Array(Rc::clone(&a_clone)), &cb)
1858
+ arr_builtins::filter(&Value::Array(a_clone.clone()), &cb)
1628
1859
  }),
1629
- "reduce" => Rc::new(move |args: &[Value]| {
1860
+ "reduce" => make_native_fn(move |args: &[Value]| {
1630
1861
  let cb = args.first().cloned().unwrap_or(Value::Null);
1631
1862
  let init = args.get(1).cloned().unwrap_or(Value::Null);
1632
- arr_builtins::reduce(&Value::Array(Rc::clone(&a_clone)), &cb, &init)
1863
+ arr_builtins::reduce(&Value::Array(a_clone.clone()), &cb, &init)
1633
1864
  }),
1634
- "forEach" => Rc::new(move |args: &[Value]| {
1865
+ "forEach" => make_native_fn(move |args: &[Value]| {
1635
1866
  let cb = args.first().cloned().unwrap_or(Value::Null);
1636
- arr_builtins::for_each(&Value::Array(Rc::clone(&a_clone)), &cb)
1867
+ arr_builtins::for_each(&Value::Array(a_clone.clone()), &cb)
1637
1868
  }),
1638
- "find" => Rc::new(move |args: &[Value]| {
1869
+ "find" => make_native_fn(move |args: &[Value]| {
1639
1870
  let cb = args.first().cloned().unwrap_or(Value::Null);
1640
- arr_builtins::find(&Value::Array(Rc::clone(&a_clone)), &cb)
1871
+ arr_builtins::find(&Value::Array(a_clone.clone()), &cb)
1641
1872
  }),
1642
- "findIndex" => Rc::new(move |args: &[Value]| {
1873
+ "findIndex" => make_native_fn(move |args: &[Value]| {
1643
1874
  let cb = args.first().cloned().unwrap_or(Value::Null);
1644
- arr_builtins::find_index(&Value::Array(Rc::clone(&a_clone)), &cb)
1875
+ arr_builtins::find_index(&Value::Array(a_clone.clone()), &cb)
1645
1876
  }),
1646
- "some" => Rc::new(move |args: &[Value]| {
1877
+ "some" => make_native_fn(move |args: &[Value]| {
1647
1878
  let cb = args.first().cloned().unwrap_or(Value::Null);
1648
- arr_builtins::some(&Value::Array(Rc::clone(&a_clone)), &cb)
1879
+ arr_builtins::some(&Value::Array(a_clone.clone()), &cb)
1649
1880
  }),
1650
- "every" => Rc::new(move |args: &[Value]| {
1881
+ "every" => make_native_fn(move |args: &[Value]| {
1651
1882
  let cb = args.first().cloned().unwrap_or(Value::Null);
1652
- arr_builtins::every(&Value::Array(Rc::clone(&a_clone)), &cb)
1883
+ arr_builtins::every(&Value::Array(a_clone.clone()), &cb)
1653
1884
  }),
1654
- "flat" => Rc::new(move |args: &[Value]| {
1885
+ "flat" => make_native_fn(move |args: &[Value]| {
1655
1886
  let depth = args.first().unwrap_or(&Value::Number(1.0));
1656
- arr_builtins::flat(&Value::Array(Rc::clone(&a_clone)), depth)
1887
+ arr_builtins::flat(&Value::Array(a_clone.clone()), depth)
1657
1888
  }),
1658
- "flatMap" => Rc::new(move |args: &[Value]| {
1889
+ "flatMap" => make_native_fn(move |args: &[Value]| {
1659
1890
  let cb = args.first().cloned().unwrap_or(Value::Null);
1660
- arr_builtins::flat_map(&Value::Array(Rc::clone(&a_clone)), &cb)
1891
+ arr_builtins::flat_map(&Value::Array(a_clone.clone()), &cb)
1661
1892
  }),
1662
- "sort" => Rc::new(move |args: &[Value]| {
1893
+ "sort" => make_native_fn(move |args: &[Value]| {
1663
1894
  let cmp = args.first();
1664
1895
  if let Some(Value::Function(_)) = cmp {
1665
1896
  arr_builtins::sort_with_comparator(
1666
- &Value::Array(Rc::clone(&a_clone)),
1897
+ &Value::Array(a_clone.clone()),
1667
1898
  cmp.unwrap(),
1668
1899
  )
1669
1900
  } else {
1670
- arr_builtins::sort_default(&Value::Array(Rc::clone(&a_clone)))
1901
+ arr_builtins::sort_default(&Value::Array(a_clone.clone()))
1671
1902
  }
1672
1903
  }),
1673
- "splice" => Rc::new(move |args: &[Value]| {
1904
+ "splice" => make_native_fn(move |args: &[Value]| {
1674
1905
  let start = args.first().unwrap_or(&Value::Null);
1675
1906
  let delete_count = args.get(1).map(|v| v as &Value);
1676
1907
  let items: Vec<Value> = args.get(2..).unwrap_or(&[]).to_vec();
1677
1908
  arr_builtins::splice(
1678
- &Value::Array(Rc::clone(&a_clone)),
1909
+ &Value::Array(a_clone.clone()),
1679
1910
  start,
1680
1911
  delete_count,
1681
1912
  &items,
@@ -1698,12 +1929,12 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
1698
1929
  }
1699
1930
  let s_clone: Arc<str> = Arc::clone(s);
1700
1931
  let method: ArrayMethodFn = match key_s {
1701
- "indexOf" => Rc::new(move |args: &[Value]| {
1932
+ "indexOf" => make_native_fn(move |args: &[Value]| {
1702
1933
  let search = args.first().unwrap_or(&Value::Null);
1703
1934
  let from = args.get(1);
1704
1935
  str_builtins::index_of(&Value::String(Arc::clone(&s_clone)), search, from)
1705
1936
  }),
1706
- "lastIndexOf" => Rc::new(move |args: &[Value]| {
1937
+ "lastIndexOf" => make_native_fn(move |args: &[Value]| {
1707
1938
  let search = args.first().unwrap_or(&Value::Null);
1708
1939
  let position = args.get(1).cloned().unwrap_or(Value::Number(f64::INFINITY));
1709
1940
  str_builtins::last_index_of(
@@ -1712,48 +1943,48 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
1712
1943
  &position,
1713
1944
  )
1714
1945
  }),
1715
- "includes" => Rc::new(move |args: &[Value]| {
1946
+ "includes" => make_native_fn(move |args: &[Value]| {
1716
1947
  let search = args.first().unwrap_or(&Value::Null);
1717
1948
  let from = args.get(1);
1718
1949
  str_builtins::includes(&Value::String(Arc::clone(&s_clone)), search, from)
1719
1950
  }),
1720
- "slice" => Rc::new(move |args: &[Value]| {
1951
+ "slice" => make_native_fn(move |args: &[Value]| {
1721
1952
  let start = args.first().unwrap_or(&Value::Null);
1722
1953
  let end = args.get(1).unwrap_or(&Value::Null);
1723
1954
  str_builtins::slice(&Value::String(Arc::clone(&s_clone)), start, end)
1724
1955
  }),
1725
- "substring" => Rc::new(move |args: &[Value]| {
1956
+ "substring" => make_native_fn(move |args: &[Value]| {
1726
1957
  let start = args.first().unwrap_or(&Value::Null);
1727
1958
  let end = args.get(1).unwrap_or(&Value::Null);
1728
1959
  str_builtins::substring(&Value::String(Arc::clone(&s_clone)), start, end)
1729
1960
  }),
1730
- "split" => Rc::new(move |args: &[Value]| {
1961
+ "split" => make_native_fn(move |args: &[Value]| {
1731
1962
  let sep = args.first().unwrap_or(&Value::Null);
1732
1963
  str_builtins::split(&Value::String(Arc::clone(&s_clone)), sep)
1733
1964
  }),
1734
- "trim" => Rc::new(move |_args: &[Value]| {
1965
+ "trim" => make_native_fn(move |_args: &[Value]| {
1735
1966
  str_builtins::trim(&Value::String(Arc::clone(&s_clone)))
1736
1967
  }),
1737
- "toUpperCase" => Rc::new(move |_args: &[Value]| {
1968
+ "toUpperCase" => make_native_fn(move |_args: &[Value]| {
1738
1969
  str_builtins::to_upper_case(&Value::String(Arc::clone(&s_clone)))
1739
1970
  }),
1740
- "toLowerCase" => Rc::new(move |_args: &[Value]| {
1971
+ "toLowerCase" => make_native_fn(move |_args: &[Value]| {
1741
1972
  str_builtins::to_lower_case(&Value::String(Arc::clone(&s_clone)))
1742
1973
  }),
1743
- "startsWith" => Rc::new(move |args: &[Value]| {
1974
+ "startsWith" => make_native_fn(move |args: &[Value]| {
1744
1975
  let search = args.first().unwrap_or(&Value::Null);
1745
1976
  str_builtins::starts_with(&Value::String(Arc::clone(&s_clone)), search)
1746
1977
  }),
1747
- "endsWith" => Rc::new(move |args: &[Value]| {
1978
+ "endsWith" => make_native_fn(move |args: &[Value]| {
1748
1979
  let search = args.first().unwrap_or(&Value::Null);
1749
1980
  str_builtins::ends_with(&Value::String(Arc::clone(&s_clone)), search)
1750
1981
  }),
1751
- "replace" => Rc::new(move |args: &[Value]| {
1982
+ "replace" => make_native_fn(move |args: &[Value]| {
1752
1983
  let search = args.first().unwrap_or(&Value::Null);
1753
1984
  let replacement = args.get(1).unwrap_or(&Value::Null);
1754
1985
  str_builtins::replace(&Value::String(Arc::clone(&s_clone)), search, replacement)
1755
1986
  }),
1756
- "replaceAll" => Rc::new(move |args: &[Value]| {
1987
+ "replaceAll" => make_native_fn(move |args: &[Value]| {
1757
1988
  let search = args.first().unwrap_or(&Value::Null);
1758
1989
  let replacement = args.get(1).unwrap_or(&Value::Null);
1759
1990
  str_builtins::replace_all(
@@ -1762,24 +1993,24 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
1762
1993
  replacement,
1763
1994
  )
1764
1995
  }),
1765
- "charAt" => Rc::new(move |args: &[Value]| {
1996
+ "charAt" => make_native_fn(move |args: &[Value]| {
1766
1997
  let idx = args.first().unwrap_or(&Value::Null);
1767
1998
  str_builtins::char_at(&Value::String(Arc::clone(&s_clone)), idx)
1768
1999
  }),
1769
- "charCodeAt" => Rc::new(move |args: &[Value]| {
2000
+ "charCodeAt" => make_native_fn(move |args: &[Value]| {
1770
2001
  let idx = args.first().unwrap_or(&Value::Null);
1771
2002
  str_builtins::char_code_at(&Value::String(Arc::clone(&s_clone)), idx)
1772
2003
  }),
1773
- "repeat" => Rc::new(move |args: &[Value]| {
2004
+ "repeat" => make_native_fn(move |args: &[Value]| {
1774
2005
  let count = args.first().unwrap_or(&Value::Null);
1775
2006
  str_builtins::repeat(&Value::String(Arc::clone(&s_clone)), count)
1776
2007
  }),
1777
- "padStart" => Rc::new(move |args: &[Value]| {
2008
+ "padStart" => make_native_fn(move |args: &[Value]| {
1778
2009
  let target_len = args.first().unwrap_or(&Value::Null);
1779
2010
  let pad = args.get(1).unwrap_or(&Value::Null);
1780
2011
  str_builtins::pad_start(&Value::String(Arc::clone(&s_clone)), target_len, pad)
1781
2012
  }),
1782
- "padEnd" => Rc::new(move |args: &[Value]| {
2013
+ "padEnd" => make_native_fn(move |args: &[Value]| {
1783
2014
  let target_len = args.first().unwrap_or(&Value::Null);
1784
2015
  let pad = args.get(1).unwrap_or(&Value::Null);
1785
2016
  str_builtins::pad_end(&Value::String(Arc::clone(&s_clone)), target_len, pad)
@@ -1788,6 +2019,22 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
1788
2019
  };
1789
2020
  Ok(Value::Function(method))
1790
2021
  }
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
+ },
1791
2038
  _ => Err(format!(
1792
2039
  "Cannot read property '{}' of {}",
1793
2040
  key,