@tishlang/tish 1.9.2 → 1.12.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 (84) hide show
  1. package/bin/tish +0 -0
  2. package/crates/js_to_tish/src/transform/expr.rs +8 -6
  3. package/crates/js_to_tish/src/transform/stmt.rs +12 -13
  4. package/crates/tish/Cargo.toml +1 -1
  5. package/crates/tish/src/cargo_native_registry.rs +4 -1
  6. package/crates/tish/src/cli_help.rs +9 -1
  7. package/crates/tish/src/main.rs +66 -11
  8. package/crates/tish/tests/integration_test.rs +145 -7
  9. package/crates/tish_ast/src/ast.rs +3 -9
  10. package/crates/tish_build_utils/src/lib.rs +74 -23
  11. package/crates/tish_builtins/src/array.rs +2 -3
  12. package/crates/tish_builtins/src/construct.rs +15 -28
  13. package/crates/tish_builtins/src/globals.rs +18 -16
  14. package/crates/tish_builtins/src/helpers.rs +1 -4
  15. package/crates/tish_builtins/src/lib.rs +1 -0
  16. package/crates/tish_builtins/src/math.rs +7 -0
  17. package/crates/tish_builtins/src/object.rs +10 -10
  18. package/crates/tish_builtins/src/string.rs +27 -3
  19. package/crates/tish_builtins/src/symbol.rs +83 -0
  20. package/crates/tish_compile/src/codegen.rs +324 -158
  21. package/crates/tish_compile/src/lib.rs +39 -7
  22. package/crates/tish_compile/src/resolve.rs +191 -6
  23. package/crates/tish_compile/src/types.rs +6 -6
  24. package/crates/tish_compile_js/src/codegen.rs +8 -5
  25. package/crates/tish_core/src/console_style.rs +9 -0
  26. package/crates/tish_core/src/json.rs +17 -7
  27. package/crates/tish_core/src/macros.rs +2 -2
  28. package/crates/tish_core/src/value.rs +213 -4
  29. package/crates/tish_cranelift/src/link.rs +1 -1
  30. package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
  31. package/crates/tish_eval/src/eval.rs +135 -73
  32. package/crates/tish_eval/src/http.rs +18 -12
  33. package/crates/tish_eval/src/lib.rs +29 -0
  34. package/crates/tish_eval/src/regex.rs +1 -1
  35. package/crates/tish_eval/src/value.rs +89 -4
  36. package/crates/tish_eval/src/value_convert.rs +30 -8
  37. package/crates/tish_fmt/src/lib.rs +4 -1
  38. package/crates/tish_lexer/src/lib.rs +7 -2
  39. package/crates/tish_llvm/src/lib.rs +2 -2
  40. package/crates/tish_lsp/src/builtin_goto.rs +111 -10
  41. package/crates/tish_lsp/src/import_goto.rs +35 -22
  42. package/crates/tish_lsp/src/main.rs +118 -85
  43. package/crates/tish_native/src/build.rs +270 -24
  44. package/crates/tish_native/src/config.rs +48 -0
  45. package/crates/tish_native/src/lib.rs +139 -12
  46. package/crates/tish_parser/src/lib.rs +5 -2
  47. package/crates/tish_parser/src/parser.rs +45 -75
  48. package/crates/tish_pg/src/error.rs +1 -1
  49. package/crates/tish_pg/src/lib.rs +61 -73
  50. package/crates/tish_resolve/src/lib.rs +283 -158
  51. package/crates/tish_resolve/src/pos.rs +10 -2
  52. package/crates/tish_runtime/Cargo.toml +3 -0
  53. package/crates/tish_runtime/src/http.rs +39 -39
  54. package/crates/tish_runtime/src/http_fetch.rs +12 -12
  55. package/crates/tish_runtime/src/lib.rs +35 -44
  56. package/crates/tish_runtime/src/native_promise.rs +0 -11
  57. package/crates/tish_runtime/src/promise.rs +14 -1
  58. package/crates/tish_runtime/src/promise_io.rs +1 -4
  59. package/crates/tish_runtime/src/timers.rs +12 -7
  60. package/crates/tish_runtime/src/ws.rs +40 -27
  61. package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
  62. package/crates/tish_ui/src/jsx.rs +6 -4
  63. package/crates/tish_ui/src/lib.rs +5 -4
  64. package/crates/tish_ui/src/runtime/hooks.rs +123 -37
  65. package/crates/tish_ui/src/runtime/mod.rs +21 -41
  66. package/crates/tish_vm/Cargo.toml +2 -0
  67. package/crates/tish_vm/src/vm.rs +258 -153
  68. package/crates/tish_wasm/src/lib.rs +60 -7
  69. package/crates/tish_wasm_runtime/Cargo.toml +10 -1
  70. package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
  71. package/crates/tish_wasm_runtime/src/lib.rs +7 -1
  72. package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
  73. package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
  74. package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
  75. package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
  76. package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
  77. package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
  78. package/justfile +3 -3
  79. package/package.json +1 -1
  80. package/platform/darwin-arm64/tish +0 -0
  81. package/platform/darwin-x64/tish +0 -0
  82. package/platform/linux-arm64/tish +0 -0
  83. package/platform/linux-x64/tish +0 -0
  84. package/platform/win32-x64/tish.exe +0 -0
@@ -1,11 +1,12 @@
1
1
  //! Stack-based bytecode VM.
2
2
 
3
- use std::cell::RefCell;
4
- use tishlang_core::VmRef;
5
3
  use std::collections::{HashMap, HashSet};
6
- use std::rc::Rc;
7
4
  use std::sync::Arc;
8
5
 
6
+ #[cfg(not(feature = "send-values"))]
7
+ use std::rc::Rc;
8
+ use tishlang_core::VmRef;
9
+
9
10
  use tishlang_ast::{BinOp, UnaryOp};
10
11
  use tishlang_builtins::array as arr_builtins;
11
12
  use tishlang_builtins::construct as construct_builtin;
@@ -13,7 +14,9 @@ use tishlang_builtins::globals as globals_builtins;
13
14
  use tishlang_builtins::math as math_builtins;
14
15
  use tishlang_builtins::string as str_builtins;
15
16
  use tishlang_bytecode::{u8_to_binop, u8_to_unaryop, Chunk, Constant, Opcode, NO_REST_PARAM};
16
- use tishlang_core::{NativeFn, ObjectMap, Value};
17
+ use tishlang_core::{
18
+ merge_object_data, object_get, object_has, object_set, NativeFn, ObjectData, ObjectMap, Value,
19
+ };
17
20
 
18
21
  /// Wrap a closure in the right shared pointer for the current build.
19
22
  /// Under `send-values` that's `Arc<dyn Fn + Send + Sync>`; otherwise it's
@@ -46,12 +49,27 @@ type ArrayMethodFn = NativeFn;
46
49
  not(any(
47
50
  feature = "fs",
48
51
  feature = "http",
52
+ feature = "promise",
49
53
  feature = "timers",
50
54
  feature = "process",
51
55
  feature = "ws"
52
56
  )),
53
57
  allow(dead_code)
54
58
  )]
59
+ #[inline]
60
+ fn value_object_from_map(m: ObjectMap) -> Value {
61
+ Value::Object(VmRef::new(ObjectData::from_strings(m)))
62
+ }
63
+
64
+ #[cfg(any(
65
+ feature = "fs",
66
+ feature = "http",
67
+ feature = "promise",
68
+ feature = "timers",
69
+ feature = "process",
70
+ feature = "ws"
71
+ ))]
72
+ #[inline]
55
73
  fn cap_allows(enabled: &HashSet<String>, name: &str) -> bool {
56
74
  enabled.contains("full") || enabled.contains(name)
57
75
  }
@@ -62,6 +80,8 @@ pub fn all_compiled_capabilities() -> HashSet<String> {
62
80
  let mut s = HashSet::new();
63
81
  #[cfg(feature = "http")]
64
82
  s.insert("http".to_string());
83
+ #[cfg(feature = "promise")]
84
+ s.insert("promise".to_string());
65
85
  #[cfg(feature = "timers")]
66
86
  s.insert("timers".to_string());
67
87
  #[cfg(feature = "fs")]
@@ -80,6 +100,7 @@ pub fn all_compiled_capabilities() -> HashSet<String> {
80
100
  not(any(
81
101
  feature = "fs",
82
102
  feature = "http",
103
+ feature = "promise",
83
104
  feature = "timers",
84
105
  feature = "process",
85
106
  feature = "ws"
@@ -136,12 +157,12 @@ fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str)
136
157
  Value::Object(ref obj) => {
137
158
  let obj_ref = obj.borrow();
138
159
  if let Some(Value::Function(on_worker)) =
139
- obj_ref.get(&std::sync::Arc::from("onWorker")).cloned()
160
+ obj_ref.strings.get(&std::sync::Arc::from("onWorker")).cloned()
140
161
  {
141
162
  let args_for_init = [Value::Number(0.0)];
142
163
  on_worker(&args_for_init)
143
164
  } else if let Some(h) =
144
- obj_ref.get(&std::sync::Arc::from("handler")).cloned()
165
+ obj_ref.strings.get(&std::sync::Arc::from("handler")).cloned()
145
166
  {
146
167
  h
147
168
  } else {
@@ -159,6 +180,16 @@ fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str)
159
180
  _ => None,
160
181
  };
161
182
  }
183
+ #[cfg(all(feature = "promise", not(feature = "http")))]
184
+ if spec == "tish:http" && cap_allows(enabled, "promise") {
185
+ return match export_name {
186
+ "Promise" => Some(tishlang_runtime::promise_object()),
187
+ "await" => Some(Value::native(|args: &[Value]| {
188
+ tishlang_runtime::await_promise(args.first().cloned().unwrap_or(Value::Null))
189
+ })),
190
+ _ => None,
191
+ };
192
+ }
162
193
  #[cfg(feature = "timers")]
163
194
  if spec == "tish:timers" && cap_allows(enabled, "timers") {
164
195
  return match export_name {
@@ -192,30 +223,24 @@ fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str)
192
223
  "argv" => Some(Value::Array(VmRef::new(
193
224
  std::env::args().map(|s| Value::String(s.into())).collect(),
194
225
  ))),
195
- "env" => Some(Value::Object(VmRef::new(
226
+ "env" => Some(value_object_from_map(
196
227
  std::env::vars()
197
228
  .map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
198
229
  .collect(),
199
- ))),
230
+ )),
200
231
  "process" => {
201
232
  let mut m = ObjectMap::default();
202
233
  m.insert(
203
234
  "exit".into(),
204
- Value::native(|args: &[Value]| {
205
- tishlang_runtime::process_exit(args)
206
- }),
235
+ Value::native(|args: &[Value]| tishlang_runtime::process_exit(args)),
207
236
  );
208
237
  m.insert(
209
238
  "cwd".into(),
210
- Value::native(|args: &[Value]| {
211
- tishlang_runtime::process_cwd(args)
212
- }),
239
+ Value::native(|args: &[Value]| tishlang_runtime::process_cwd(args)),
213
240
  );
214
241
  m.insert(
215
242
  "exec".into(),
216
- Value::native(|args: &[Value]| {
217
- tishlang_runtime::process_exec(args)
218
- }),
243
+ Value::native(|args: &[Value]| tishlang_runtime::process_exec(args)),
219
244
  );
220
245
  m.insert(
221
246
  "argv".into(),
@@ -225,13 +250,13 @@ fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str)
225
250
  );
226
251
  m.insert(
227
252
  "env".into(),
228
- Value::Object(VmRef::new(
253
+ value_object_from_map(
229
254
  std::env::vars()
230
255
  .map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
231
256
  .collect(),
232
- )),
257
+ ),
233
258
  );
234
- Some(Value::Object(VmRef::new(m)))
259
+ Some(value_object_from_map(m))
235
260
  }
236
261
  _ => None,
237
262
  };
@@ -342,10 +367,7 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
342
367
  Value::Null
343
368
  }),
344
369
  );
345
- g.insert(
346
- "console".into(),
347
- Value::Object(VmRef::new(console)),
348
- );
370
+ g.insert("console".into(), value_object_from_map(console));
349
371
 
350
372
  let mut math = ObjectMap::default();
351
373
  math.insert(
@@ -433,9 +455,49 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
433
455
  "trunc".into(),
434
456
  Value::native(|args: &[Value]| math_builtins::trunc(args)),
435
457
  );
458
+ // Trig/hypot not covered by `math_builtins`; needed by the 3D engine's
459
+ // camera + character-controller math (atan2/hypot) on the wasm VM, where
460
+ // (unlike `--target js`) there is no host `Math` to fall through to.
461
+ math.insert(
462
+ "atan2".into(),
463
+ Value::native(|args: &[Value]| {
464
+ let y = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
465
+ let x = args.get(1).and_then(|v| v.as_number()).unwrap_or(f64::NAN);
466
+ Value::Number(y.atan2(x))
467
+ }),
468
+ );
469
+ math.insert(
470
+ "atan".into(),
471
+ Value::native(|args: &[Value]| {
472
+ let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
473
+ Value::Number(n.atan())
474
+ }),
475
+ );
476
+ math.insert(
477
+ "asin".into(),
478
+ Value::native(|args: &[Value]| {
479
+ let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
480
+ Value::Number(n.asin())
481
+ }),
482
+ );
483
+ math.insert(
484
+ "acos".into(),
485
+ Value::native(|args: &[Value]| {
486
+ let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
487
+ Value::Number(n.acos())
488
+ }),
489
+ );
490
+ math.insert(
491
+ "hypot".into(),
492
+ Value::native(|args: &[Value]| {
493
+ let nums: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
494
+ let sum_sq: f64 = nums.iter().map(|n| n * n).sum();
495
+ Value::Number(sum_sq.sqrt())
496
+ }),
497
+ );
436
498
  math.insert("PI".into(), Value::Number(std::f64::consts::PI));
437
499
  math.insert("E".into(), Value::Number(std::f64::consts::E));
438
- g.insert("Math".into(), Value::Object(VmRef::new(math)));
500
+ g.insert("Math".into(), value_object_from_map(math));
439
501
 
440
502
  let mut json = ObjectMap::default();
441
503
  json.insert(
@@ -455,7 +517,7 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
455
517
  Value::String(tishlang_core::json_stringify(v).into())
456
518
  }),
457
519
  );
458
- g.insert("JSON".into(), Value::Object(VmRef::new(json)));
520
+ g.insert("JSON".into(), value_object_from_map(json));
459
521
 
460
522
  g.insert(
461
523
  "parseInt".into(),
@@ -463,9 +525,7 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
463
525
  );
464
526
  g.insert(
465
527
  "parseFloat".into(),
466
- Value::native(|args: &[Value]| {
467
- globals_builtins::parse_float(args)
468
- }),
528
+ Value::native(|args: &[Value]| globals_builtins::parse_float(args)),
469
529
  );
470
530
  g.insert(
471
531
  "encodeURI".into(),
@@ -502,6 +562,10 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
502
562
  Value::String(v.type_name().into())
503
563
  }),
504
564
  );
565
+ g.insert(
566
+ "Symbol".into(),
567
+ tishlang_builtins::symbol::symbol_object(),
568
+ );
505
569
 
506
570
  // Date - at minimum Date.now() for timing
507
571
  let mut date = ObjectMap::default();
@@ -515,7 +579,7 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
515
579
  Value::Number(ms)
516
580
  }),
517
581
  );
518
- g.insert("Date".into(), Value::Object(VmRef::new(date)));
582
+ g.insert("Date".into(), value_object_from_map(date));
519
583
 
520
584
  g.insert(
521
585
  "Uint8Array".into(),
@@ -530,77 +594,49 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
530
594
  let mut object_methods = ObjectMap::default();
531
595
  object_methods.insert(
532
596
  "assign".into(),
533
- Value::native(|args: &[Value]| {
534
- globals_builtins::object_assign(args)
535
- }),
597
+ Value::native(|args: &[Value]| globals_builtins::object_assign(args)),
536
598
  );
537
599
  object_methods.insert(
538
600
  "fromEntries".into(),
539
- Value::native(|args: &[Value]| {
540
- globals_builtins::object_from_entries(args)
541
- }),
601
+ Value::native(|args: &[Value]| globals_builtins::object_from_entries(args)),
542
602
  );
543
603
  object_methods.insert(
544
604
  "keys".into(),
545
- Value::native(|args: &[Value]| {
546
- globals_builtins::object_keys(args)
547
- }),
605
+ Value::native(|args: &[Value]| globals_builtins::object_keys(args)),
548
606
  );
549
607
  object_methods.insert(
550
608
  "values".into(),
551
- Value::native(|args: &[Value]| {
552
- globals_builtins::object_values(args)
553
- }),
609
+ Value::native(|args: &[Value]| globals_builtins::object_values(args)),
554
610
  );
555
611
  object_methods.insert(
556
612
  "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)),
613
+ Value::native(|args: &[Value]| globals_builtins::object_entries(args)),
564
614
  );
615
+ g.insert("Object".into(), value_object_from_map(object_methods));
565
616
 
566
617
  // Array.isArray
567
618
  let mut array_static = ObjectMap::default();
568
619
  array_static.insert(
569
620
  "isArray".into(),
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)),
621
+ Value::native(|args: &[Value]| globals_builtins::array_is_array(args)),
577
622
  );
623
+ g.insert("Array".into(), value_object_from_map(array_static));
578
624
 
579
625
  // String(value) as callable + String.fromCharCode
580
- let string_convert_fn = Value::native(|args: &[Value]| {
581
- globals_builtins::string_convert(args)
582
- });
626
+ let string_convert_fn = Value::native(|args: &[Value]| globals_builtins::string_convert(args));
583
627
  let mut string_static = ObjectMap::default();
584
628
  string_static.insert(
585
629
  "fromCharCode".into(),
586
- Value::native(|args: &[Value]| {
587
- globals_builtins::string_from_char_code(args)
588
- }),
630
+ Value::native(|args: &[Value]| globals_builtins::string_from_char_code(args)),
589
631
  );
590
632
  string_static.insert(Arc::from("__call"), string_convert_fn);
591
- g.insert(
592
- "String".into(),
593
- Value::Object(VmRef::new(string_static)),
594
- );
633
+ g.insert("String".into(), value_object_from_map(string_static));
595
634
 
596
635
  // JSX / Lattish: stubs for bytecode VM when no DOM (e.g. console). Override via set_global in browser.
597
- g.insert(
598
- "h".into(),
599
- Value::native(|_args: &[Value]| Value::Null),
600
- );
636
+ g.insert("h".into(), Value::native(|_args: &[Value]| Value::Null));
601
637
  g.insert(
602
638
  "Fragment".into(),
603
- Value::Object(VmRef::new(ObjectMap::default())),
639
+ value_object_from_map(ObjectMap::default()),
604
640
  );
605
641
  g.insert(
606
642
  "createRoot".into(),
@@ -610,7 +646,7 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
610
646
  "render".into(),
611
647
  Value::native(|_args: &[Value]| Value::Null),
612
648
  );
613
- Value::Object(VmRef::new(render_obj))
649
+ value_object_from_map(render_obj)
614
650
  }),
615
651
  );
616
652
  g.insert(
@@ -623,31 +659,22 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
623
659
  );
624
660
  let mut document_obj = ObjectMap::default();
625
661
  document_obj.insert("body".into(), Value::Null);
626
- g.insert(
627
- "document".into(),
628
- Value::Object(VmRef::new(document_obj)),
629
- );
662
+ g.insert("document".into(), value_object_from_map(document_obj));
630
663
 
631
664
  #[cfg(feature = "process")]
632
665
  if cap_allows(enabled, "process") {
633
666
  let mut process_obj = ObjectMap::default();
634
667
  process_obj.insert(
635
668
  "exit".into(),
636
- Value::native(|args: &[Value]| {
637
- tishlang_runtime::process_exit(args)
638
- }),
669
+ Value::native(|args: &[Value]| tishlang_runtime::process_exit(args)),
639
670
  );
640
671
  process_obj.insert(
641
672
  "cwd".into(),
642
- Value::native(|args: &[Value]| {
643
- tishlang_runtime::process_cwd(args)
644
- }),
673
+ Value::native(|args: &[Value]| tishlang_runtime::process_cwd(args)),
645
674
  );
646
675
  process_obj.insert(
647
676
  "exec".into(),
648
- Value::native(|args: &[Value]| {
649
- tishlang_runtime::process_exec(args)
650
- }),
677
+ Value::native(|args: &[Value]| tishlang_runtime::process_exec(args)),
651
678
  );
652
679
  process_obj.insert(
653
680
  "argv".into(),
@@ -657,16 +684,13 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
657
684
  );
658
685
  process_obj.insert(
659
686
  "env".into(),
660
- Value::Object(VmRef::new(
687
+ value_object_from_map(
661
688
  std::env::vars()
662
689
  .map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
663
690
  .collect(),
664
- )),
665
- );
666
- g.insert(
667
- "process".into(),
668
- Value::Object(VmRef::new(process_obj)),
691
+ ),
669
692
  );
693
+ g.insert("process".into(), value_object_from_map(process_obj));
670
694
  }
671
695
 
672
696
  #[cfg(feature = "timers")]
@@ -699,7 +723,6 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
699
723
  "fetchAll".into(),
700
724
  Value::native(|args: &[Value]| tishlang_runtime::fetch_all_promise(args.to_vec())),
701
725
  );
702
- g.insert("Promise".into(), tishlang_runtime::promise_object());
703
726
  g.insert(
704
727
  "registerStaticRoute".into(),
705
728
  Value::native(|args: &[Value]| {
@@ -729,12 +752,12 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
729
752
  Value::Object(ref obj) => {
730
753
  let obj_ref = obj.borrow();
731
754
  if let Some(Value::Function(on_worker)) =
732
- obj_ref.get(&std::sync::Arc::from("onWorker")).cloned()
755
+ obj_ref.strings.get(&std::sync::Arc::from("onWorker")).cloned()
733
756
  {
734
757
  let args_for_init = [Value::Number(0.0)];
735
758
  on_worker(&args_for_init)
736
759
  } else if let Some(h) =
737
- obj_ref.get(&std::sync::Arc::from("handler")).cloned()
760
+ obj_ref.strings.get(&std::sync::Arc::from("handler")).cloned()
738
761
  {
739
762
  h
740
763
  } else {
@@ -752,6 +775,11 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
752
775
  );
753
776
  }
754
777
 
778
+ #[cfg(any(feature = "http", feature = "promise"))]
779
+ if cap_allows(enabled, "http") || cap_allows(enabled, "promise") {
780
+ g.insert("Promise".into(), tishlang_runtime::promise_object());
781
+ }
782
+
755
783
  g
756
784
  }
757
785
 
@@ -894,10 +922,7 @@ impl Vm {
894
922
  ls.insert(Arc::clone(name), v);
895
923
  } else if i == ri {
896
924
  let rest_arr: Vec<Value> = args.iter().skip(ri).cloned().collect();
897
- ls.insert(
898
- Arc::clone(name),
899
- Value::Array(VmRef::new(rest_arr)),
900
- );
925
+ ls.insert(Arc::clone(name), Value::Array(VmRef::new(rest_arr)));
901
926
  }
902
927
  }
903
928
  } else {
@@ -1131,7 +1156,7 @@ impl Vm {
1131
1156
  Value::Function(f) => f.clone(),
1132
1157
  Value::Object(o) => {
1133
1158
  if let Some(Value::Function(call_fn)) =
1134
- o.borrow().get(&Arc::from("__call"))
1159
+ o.borrow().strings.get("__call")
1135
1160
  {
1136
1161
  call_fn.clone()
1137
1162
  } else {
@@ -1170,7 +1195,7 @@ impl Vm {
1170
1195
  Value::Function(f) => f.clone(),
1171
1196
  Value::Object(o) => {
1172
1197
  if let Some(Value::Function(call_fn)) =
1173
- o.borrow().get(&Arc::from("__call"))
1198
+ o.borrow().strings.get("__call")
1174
1199
  {
1175
1200
  call_fn.clone()
1176
1201
  } else {
@@ -1376,7 +1401,7 @@ impl Vm {
1376
1401
  let key = key_val.to_display_string().into();
1377
1402
  map.insert(key, val);
1378
1403
  }
1379
- self.stack.push(Value::Object(VmRef::new(map)));
1404
+ self.stack.push(value_object_from_map(map));
1380
1405
  }
1381
1406
  Opcode::EnterTry => {
1382
1407
  let offset = Self::read_u16(code, &mut ip) as usize;
@@ -1427,31 +1452,19 @@ impl Vm {
1427
1452
  .stack
1428
1453
  .pop()
1429
1454
  .ok_or_else(|| "Stack underflow".to_string())?;
1430
- let cap = match (&left, &right) {
1431
- (Value::Object(l), Value::Object(r)) => l.borrow().len() + r.borrow().len(),
1432
- _ => 0,
1433
- };
1434
- let mut merged: ObjectMap = ObjectMap::with_capacity(cap.max(8));
1435
- if let Value::Object(l) = &left {
1436
- merged.extend(l.borrow().iter().map(|(k, v)| (Arc::clone(k), v.clone())));
1437
- } else {
1438
- return Err(format!(
1439
- "MergeObject: left must be object, got {}",
1440
- left.to_display_string()
1441
- ));
1442
- }
1443
- if let Value::Object(r) = &right {
1444
- for (k, v) in r.borrow().iter() {
1445
- merged.insert(Arc::clone(k), v.clone());
1455
+ match (&left, &right) {
1456
+ (Value::Object(l), Value::Object(r)) => {
1457
+ let merged = merge_object_data(l, r);
1458
+ self.stack.push(Value::Object(VmRef::new(merged)));
1459
+ }
1460
+ _ => {
1461
+ return Err(format!(
1462
+ "MergeObject: expected two objects, got {} and {}",
1463
+ left.to_display_string(),
1464
+ right.to_display_string()
1465
+ ));
1446
1466
  }
1447
- } else {
1448
- return Err(format!(
1449
- "MergeObject: right must be object, got {}",
1450
- right.to_display_string()
1451
- ));
1452
1467
  }
1453
- self.stack
1454
- .push(Value::Object(VmRef::new(merged)));
1455
1468
  }
1456
1469
  Opcode::ArraySortNumeric => {
1457
1470
  let operand = Self::read_u16(code, &mut ip);
@@ -1593,20 +1606,25 @@ impl Vm {
1593
1606
  .stack
1594
1607
  .pop()
1595
1608
  .ok_or_else(|| "Stack underflow in AwaitPromise".to_string())?;
1596
- #[cfg(feature = "http")]
1609
+ #[cfg(any(feature = "http", feature = "promise"))]
1597
1610
  {
1598
1611
  use tishlang_core::Value as V;
1599
1612
  match v {
1600
1613
  V::Promise(p) => match p.block_until_settled() {
1601
1614
  Ok(val) => self.stack.push(val),
1602
1615
  Err(rej) => {
1603
- Self::unwind_throw(&mut try_handlers, &mut self.stack, &mut ip, rej)?;
1616
+ Self::unwind_throw(
1617
+ &mut try_handlers,
1618
+ &mut self.stack,
1619
+ &mut ip,
1620
+ rej,
1621
+ )?;
1604
1622
  }
1605
1623
  },
1606
1624
  other => self.stack.push(tishlang_runtime::await_promise(other)),
1607
1625
  }
1608
1626
  }
1609
- #[cfg(not(feature = "http"))]
1627
+ #[cfg(not(any(feature = "http", feature = "promise")))]
1610
1628
  {
1611
1629
  self.stack.push(v);
1612
1630
  }
@@ -1753,26 +1771,24 @@ fn eval_binop(op: BinOp, l: &Value, r: &Value) -> Result<Value, String> {
1753
1771
  BitXor => Ok(Number((ln as i32 ^ rn as i32) as f64)),
1754
1772
  Shl => Ok(Number(((ln as i32) << (rn as i32)) as f64)),
1755
1773
  Shr => Ok(Number(((ln as i32) >> (rn as i32)) as f64)),
1756
- In => {
1757
- let key_s: Arc<str> = match l {
1758
- Value::String(s) => Arc::clone(s),
1759
- Value::Number(n) => n.to_string().into(),
1760
- _ => l.to_display_string().into(),
1761
- };
1762
- Ok(Bool(match r {
1763
- Value::Object(m) => m.borrow().contains_key(key_s.as_ref()),
1764
- Value::Array(a) => {
1765
- if key_s.as_ref() == "length" {
1766
- true
1767
- } else if let Ok(idx) = key_s.parse::<usize>() {
1768
- idx < a.borrow().len()
1769
- } else {
1770
- false
1771
- }
1774
+ In => Ok(Bool(match r {
1775
+ Value::Object(_) => object_has(r, l),
1776
+ Value::Array(a) => {
1777
+ let key_s: Arc<str> = match l {
1778
+ Value::String(s) => Arc::clone(s),
1779
+ Value::Number(n) => n.to_string().into(),
1780
+ _ => l.to_display_string().into(),
1781
+ };
1782
+ if key_s.as_ref() == "length" {
1783
+ true
1784
+ } else if let Ok(idx) = key_s.parse::<usize>() {
1785
+ idx < a.borrow().len()
1786
+ } else {
1787
+ false
1772
1788
  }
1773
- _ => false,
1774
- }))
1775
- }
1789
+ }
1790
+ _ => false,
1791
+ })),
1776
1792
  }
1777
1793
  }
1778
1794
 
@@ -1792,7 +1808,8 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
1792
1808
  match obj {
1793
1809
  Value::Object(m) => {
1794
1810
  let map = m.borrow();
1795
- map.get(key.as_ref())
1811
+ map.strings
1812
+ .get(key.as_ref())
1796
1813
  .cloned()
1797
1814
  .ok_or_else(|| format!("Property '{}' not found", key))
1798
1815
  }
@@ -2019,7 +2036,7 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
2019
2036
  };
2020
2037
  Ok(Value::Function(method))
2021
2038
  }
2022
- #[cfg(feature = "http")]
2039
+ #[cfg(any(feature = "http", feature = "promise"))]
2023
2040
  Value::Promise(p) => match key.as_ref() {
2024
2041
  "then" => {
2025
2042
  let pc = Arc::clone(p);
@@ -2046,7 +2063,7 @@ fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
2046
2063
  fn set_member(obj: &Value, key: &Arc<str>, val: Value) -> Result<(), String> {
2047
2064
  match obj {
2048
2065
  Value::Object(m) => {
2049
- m.borrow_mut().insert(Arc::clone(key), val);
2066
+ m.borrow_mut().strings.insert(Arc::clone(key), val);
2050
2067
  Ok(())
2051
2068
  }
2052
2069
  Value::Array(a) => {
@@ -2065,13 +2082,101 @@ fn set_member(obj: &Value, key: &Arc<str>, val: Value) -> Result<(), String> {
2065
2082
  }
2066
2083
 
2067
2084
  fn get_index(obj: &Value, idx: &Value) -> Result<Value, String> {
2068
- let key: Arc<str> = idx.to_display_string().into();
2069
- get_member(obj, &key)
2085
+ match obj {
2086
+ Value::Array(a) => {
2087
+ let i = match idx {
2088
+ Value::Number(n) => *n as usize,
2089
+ _ => {
2090
+ return Err(format!(
2091
+ "Array index must be number, got {}",
2092
+ idx.type_name()
2093
+ ));
2094
+ }
2095
+ };
2096
+ Ok(a
2097
+ .borrow()
2098
+ .get(i)
2099
+ .cloned()
2100
+ .unwrap_or(Value::Null))
2101
+ }
2102
+ Value::String(s) => {
2103
+ let i = match idx {
2104
+ Value::Number(n) => {
2105
+ let n = *n;
2106
+ if n < 0.0 || n.fract() != 0.0 {
2107
+ return Err(format!(
2108
+ "String index must be non-negative integer, got {}",
2109
+ n
2110
+ ));
2111
+ }
2112
+ let i = n as usize;
2113
+ let len = s.chars().count();
2114
+ if i >= len {
2115
+ return Err("Index out of bounds".to_string());
2116
+ }
2117
+ i
2118
+ }
2119
+ _ => {
2120
+ return Err(format!(
2121
+ "String index must be number, got {}",
2122
+ idx.type_name()
2123
+ ));
2124
+ }
2125
+ };
2126
+ match s.chars().nth(i) {
2127
+ Some(c) => Ok(Value::String(Arc::from(c.to_string()))),
2128
+ None => Err("Index out of bounds".to_string()),
2129
+ }
2130
+ }
2131
+ Value::Object(_) => object_get(obj, idx).ok_or_else(|| {
2132
+ format!(
2133
+ "Property '{}' not found",
2134
+ idx.to_display_string()
2135
+ )
2136
+ }),
2137
+ #[cfg(any(feature = "http", feature = "promise"))]
2138
+ Value::Promise(_) => {
2139
+ let key_arc: std::sync::Arc<str> = match idx {
2140
+ Value::String(s) => std::sync::Arc::clone(s),
2141
+ _ => {
2142
+ return Err(format!(
2143
+ "Promise bracket access requires a string key, got {}",
2144
+ idx.type_name()
2145
+ ));
2146
+ }
2147
+ };
2148
+ get_member(obj, &key_arc)
2149
+ },
2150
+ _ => Err(format!(
2151
+ "Cannot read property '{}' of {}",
2152
+ idx.to_display_string(),
2153
+ obj.type_name()
2154
+ )),
2155
+ }
2070
2156
  }
2071
2157
 
2072
2158
  fn set_index(obj: &Value, idx: &Value, val: Value) -> Result<(), String> {
2073
- let key: Arc<str> = idx.to_display_string().into();
2074
- set_member(obj, &key, val)
2159
+ match obj {
2160
+ Value::Array(a) => {
2161
+ let i = match idx {
2162
+ Value::Number(n) => *n as usize,
2163
+ _ => {
2164
+ return Err(format!(
2165
+ "Array index must be number, got {}",
2166
+ idx.type_name()
2167
+ ));
2168
+ }
2169
+ };
2170
+ let mut arr = a.borrow_mut();
2171
+ while arr.len() <= i {
2172
+ arr.push(Value::Null);
2173
+ }
2174
+ arr[i] = val;
2175
+ Ok(())
2176
+ }
2177
+ Value::Object(_) => object_set(obj, idx, val),
2178
+ _ => Err(format!("Cannot set property of {}", obj.type_name())),
2179
+ }
2075
2180
  }
2076
2181
 
2077
2182
  /// Run a chunk with every capability linked into this `tishlang_vm` build (tests, embedders).