@tishlang/tish 1.9.1 → 1.10.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 (79) 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/main.rs +11 -8
  7. package/crates/tish/tests/integration_test.rs +145 -7
  8. package/crates/tish_ast/src/ast.rs +3 -9
  9. package/crates/tish_build_utils/src/lib.rs +43 -15
  10. package/crates/tish_builtins/src/array.rs +2 -3
  11. package/crates/tish_builtins/src/construct.rs +15 -28
  12. package/crates/tish_builtins/src/globals.rs +18 -16
  13. package/crates/tish_builtins/src/helpers.rs +1 -4
  14. package/crates/tish_builtins/src/lib.rs +1 -0
  15. package/crates/tish_builtins/src/object.rs +10 -10
  16. package/crates/tish_builtins/src/string.rs +1 -3
  17. package/crates/tish_builtins/src/symbol.rs +83 -0
  18. package/crates/tish_compile/src/codegen.rs +123 -138
  19. package/crates/tish_compile/src/lib.rs +25 -3
  20. package/crates/tish_compile/src/resolve.rs +6 -3
  21. package/crates/tish_compile/src/types.rs +6 -6
  22. package/crates/tish_compile_js/src/codegen.rs +50 -29
  23. package/crates/tish_compile_js/src/tests_jsx.rs +44 -0
  24. package/crates/tish_core/src/console_style.rs +9 -0
  25. package/crates/tish_core/src/json.rs +17 -7
  26. package/crates/tish_core/src/macros.rs +2 -2
  27. package/crates/tish_core/src/value.rs +192 -4
  28. package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
  29. package/crates/tish_eval/src/eval.rs +135 -73
  30. package/crates/tish_eval/src/http.rs +18 -12
  31. package/crates/tish_eval/src/lib.rs +29 -0
  32. package/crates/tish_eval/src/regex.rs +1 -1
  33. package/crates/tish_eval/src/value.rs +89 -4
  34. package/crates/tish_eval/src/value_convert.rs +30 -8
  35. package/crates/tish_fmt/src/lib.rs +4 -1
  36. package/crates/tish_lexer/src/lib.rs +7 -2
  37. package/crates/tish_llvm/src/lib.rs +2 -2
  38. package/crates/tish_lsp/src/builtin_goto.rs +111 -10
  39. package/crates/tish_lsp/src/import_goto.rs +35 -22
  40. package/crates/tish_lsp/src/main.rs +118 -85
  41. package/crates/tish_native/src/build.rs +187 -10
  42. package/crates/tish_native/src/lib.rs +92 -8
  43. package/crates/tish_parser/src/lib.rs +77 -0
  44. package/crates/tish_parser/src/parser.rs +71 -74
  45. package/crates/tish_pg/src/error.rs +1 -1
  46. package/crates/tish_pg/src/lib.rs +61 -73
  47. package/crates/tish_resolve/src/lib.rs +283 -158
  48. package/crates/tish_resolve/src/pos.rs +10 -2
  49. package/crates/tish_runtime/Cargo.toml +3 -0
  50. package/crates/tish_runtime/src/http.rs +39 -39
  51. package/crates/tish_runtime/src/http_fetch.rs +12 -12
  52. package/crates/tish_runtime/src/lib.rs +26 -43
  53. package/crates/tish_runtime/src/native_promise.rs +0 -11
  54. package/crates/tish_runtime/src/promise.rs +14 -1
  55. package/crates/tish_runtime/src/promise_io.rs +1 -4
  56. package/crates/tish_runtime/src/ws.rs +40 -27
  57. package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
  58. package/crates/tish_ui/src/jsx.rs +6 -4
  59. package/crates/tish_ui/src/lib.rs +2 -2
  60. package/crates/tish_ui/src/runtime/hooks.rs +5 -15
  61. package/crates/tish_ui/src/runtime/mod.rs +16 -17
  62. package/crates/tish_vm/Cargo.toml +2 -0
  63. package/crates/tish_vm/src/vm.rs +218 -153
  64. package/crates/tish_wasm/src/lib.rs +33 -7
  65. package/crates/tish_wasm_runtime/Cargo.toml +4 -1
  66. package/crates/tish_wasm_runtime/src/lib.rs +2 -1
  67. package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
  68. package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
  69. package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
  70. package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
  71. package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
  72. package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
  73. package/justfile +3 -3
  74. package/package.json +1 -1
  75. package/platform/darwin-arm64/tish +0 -0
  76. package/platform/darwin-x64/tish +0 -0
  77. package/platform/linux-arm64/tish +0 -0
  78. package/platform/linux-x64/tish +0 -0
  79. package/platform/win32-x64/tish.exe +0 -0
@@ -15,7 +15,11 @@ use tishlang_ast::{
15
15
 
16
16
  #[cfg(any(feature = "fs", feature = "process"))]
17
17
  use crate::natives;
18
- use crate::value::{PropMap, Value};
18
+ use ahash::AHashMap;
19
+
20
+ use crate::value::{
21
+ eval_object_get, eval_object_has, eval_object_set, EvalObjectData, PropMap, Value,
22
+ };
19
23
 
20
24
  struct Scope {
21
25
  vars: PropMap,
@@ -98,7 +102,7 @@ impl Evaluator {
98
102
  console.insert("error".into(), Value::Native(natives::console_error));
99
103
  s.set(
100
104
  "console".into(),
101
- Value::Object(Rc::new(RefCell::new(console))),
105
+ Value::object(console),
102
106
  true,
103
107
  );
104
108
  s.set("parseInt".into(), Value::Native(natives::parse_int), true);
@@ -109,7 +113,11 @@ impl Evaluator {
109
113
  );
110
114
  s.set("decodeURI".into(), Value::Native(natives::decode_uri), true);
111
115
  s.set("encodeURI".into(), Value::Native(natives::encode_uri), true);
112
- s.set("htmlEscape".into(), Value::Native(natives::html_escape), true);
116
+ s.set(
117
+ "htmlEscape".into(),
118
+ Value::Native(natives::html_escape),
119
+ true,
120
+ );
113
121
  s.set(
114
122
  "Boolean".into(),
115
123
  Value::Native(natives::boolean_native),
@@ -140,7 +148,7 @@ impl Evaluator {
140
148
  math.insert("E".into(), Value::Number(std::f64::consts::E));
141
149
  s.set(
142
150
  "Math".into(),
143
- Value::Object(Rc::new(RefCell::new(math))),
151
+ Value::object(math),
144
152
  true,
145
153
  );
146
154
 
@@ -152,7 +160,7 @@ impl Evaluator {
152
160
  );
153
161
  s.set(
154
162
  "JSON".into(),
155
- Value::Object(Rc::new(RefCell::new(json))),
163
+ Value::object(json),
156
164
  true,
157
165
  );
158
166
 
@@ -167,7 +175,7 @@ impl Evaluator {
167
175
  );
168
176
  s.set(
169
177
  "Object".into(),
170
- Value::Object(Rc::new(RefCell::new(object))),
178
+ Value::object(object),
171
179
  true,
172
180
  );
173
181
 
@@ -175,7 +183,7 @@ impl Evaluator {
175
183
  array_obj.insert("isArray".into(), Value::Native(natives::array_is_array));
176
184
  s.set(
177
185
  "Array".into(),
178
- Value::Object(Rc::new(RefCell::new(array_obj))),
186
+ Value::object(array_obj),
179
187
  true,
180
188
  );
181
189
 
@@ -186,7 +194,7 @@ impl Evaluator {
186
194
  );
187
195
  s.set(
188
196
  "String".into(),
189
- Value::Object(Rc::new(RefCell::new(string_obj))),
197
+ Value::object(string_obj),
190
198
  true,
191
199
  );
192
200
 
@@ -194,10 +202,15 @@ impl Evaluator {
194
202
  date.insert("now".into(), Value::Native(natives::date_now));
195
203
  s.set(
196
204
  "Date".into(),
197
- Value::Object(Rc::new(RefCell::new(date))),
205
+ Value::object(date),
198
206
  true,
199
207
  );
200
208
 
209
+ s.set(
210
+ "Symbol".into(),
211
+ crate::value_convert::core_to_eval(tishlang_builtins::symbol::symbol_object()),
212
+ true,
213
+ );
201
214
  s.set(
202
215
  "Uint8Array".into(),
203
216
  crate::value_convert::core_to_eval(
@@ -249,7 +262,11 @@ impl Evaluator {
249
262
  #[cfg(feature = "http")]
250
263
  {
251
264
  s.set("fetch".into(), Value::Native(Self::fetch_native), true);
252
- s.set("fetchAll".into(), Value::Native(Self::fetch_all_native), true);
265
+ s.set(
266
+ "fetchAll".into(),
267
+ Value::Native(Self::fetch_all_native),
268
+ true,
269
+ );
253
270
  s.set("Promise".into(), Value::PromiseConstructor, true);
254
271
  s.set("serve".into(), Value::Serve, true);
255
272
  }
@@ -588,7 +605,7 @@ impl Evaluator {
588
605
  for spec in specifiers {
589
606
  match spec {
590
607
  ImportSpecifier::Named { name, alias, .. } => {
591
- let v = exports.get(name.as_ref()).ok_or_else(|| {
608
+ let v = exports.strings.get(name.as_ref()).ok_or_else(|| {
592
609
  EvalError::Error(format!("Module does not export '{}'", name))
593
610
  })?;
594
611
  let bind = alias.as_deref().unwrap_or(name.as_ref());
@@ -598,7 +615,7 @@ impl Evaluator {
598
615
  scope.set(Arc::clone(name), exports_val.clone(), false);
599
616
  }
600
617
  ImportSpecifier::Default { name, .. } => {
601
- let v = exports.get("default").ok_or_else(|| {
618
+ let v = exports.strings.get("default").ok_or_else(|| {
602
619
  EvalError::Error("Module does not have default export".to_string())
603
620
  })?;
604
621
  scope.set(Arc::clone(name), v.clone(), false);
@@ -619,9 +636,9 @@ impl Evaluator {
619
636
  }
620
637
  Ok(Value::Null)
621
638
  }
622
- Statement::TypeAlias { .. } | Statement::DeclareVar { .. } | Statement::DeclareFun { .. } => {
623
- Ok(Value::Null)
624
- }
639
+ Statement::TypeAlias { .. }
640
+ | Statement::DeclareVar { .. }
641
+ | Statement::DeclareFun { .. } => Ok(Value::Null),
625
642
  }
626
643
  }
627
644
 
@@ -694,7 +711,7 @@ impl Evaluator {
694
711
  }
695
712
  *self.current_dir.borrow_mut() = parent_dir;
696
713
  self.scope = prev_scope;
697
- let exports_val = Value::Object(Rc::new(RefCell::new(exports)));
714
+ let exports_val = Value::object(exports);
698
715
  self.module_cache
699
716
  .borrow_mut()
700
717
  .insert(path, exports_val.clone());
@@ -743,7 +760,7 @@ impl Evaluator {
743
760
  exports.insert("isDir".into(), Value::Native(natives::is_dir));
744
761
  exports.insert("readDir".into(), Value::Native(natives::read_dir));
745
762
  exports.insert("mkdir".into(), Value::Native(natives::mkdir));
746
- return Ok(Value::Object(Rc::new(RefCell::new(exports))));
763
+ return Ok(Value::object(exports));
747
764
  }
748
765
  #[cfg(not(feature = "fs"))]
749
766
  {
@@ -760,7 +777,7 @@ impl Evaluator {
760
777
  exports.insert("fetchAll".into(), Value::Native(Self::fetch_all_native));
761
778
  exports.insert("serve".into(), Value::Serve);
762
779
  exports.insert("Promise".into(), Value::PromiseConstructor);
763
- return Ok(Value::Object(Rc::new(RefCell::new(exports))));
780
+ return Ok(Value::object(exports));
764
781
  }
765
782
  #[cfg(not(feature = "http"))]
766
783
  {
@@ -789,7 +806,7 @@ impl Evaluator {
789
806
  "clearInterval".into(),
790
807
  Value::Native(Self::clear_interval_native),
791
808
  );
792
- return Ok(Value::Object(Rc::new(RefCell::new(exports))));
809
+ return Ok(Value::object(exports));
793
810
  }
794
811
  #[cfg(not(feature = "timers"))]
795
812
  {
@@ -812,7 +829,7 @@ impl Evaluator {
812
829
  "wsBroadcast".into(),
813
830
  Value::Native(Self::ws_broadcast_native),
814
831
  );
815
- return Ok(Value::Object(Rc::new(RefCell::new(exports))));
832
+ return Ok(Value::object(exports));
816
833
  }
817
834
  #[cfg(not(feature = "ws"))]
818
835
  {
@@ -839,19 +856,19 @@ impl Evaluator {
839
856
  .collect();
840
857
  exports.insert(
841
858
  "env".into(),
842
- Value::Object(Rc::new(RefCell::new(env_obj.clone()))),
859
+ Value::object(env_obj.clone()),
843
860
  );
844
861
  let mut process_obj = PropMap::default();
845
862
  process_obj.insert("exit".into(), Value::Native(natives::process_exit));
846
863
  process_obj.insert("cwd".into(), Value::Native(natives::process_cwd));
847
864
  process_obj.insert("exec".into(), Value::Native(natives::process_exec));
848
865
  process_obj.insert("argv".into(), Value::Array(Rc::new(RefCell::new(argv))));
849
- process_obj.insert("env".into(), Value::Object(Rc::new(RefCell::new(env_obj))));
866
+ process_obj.insert("env".into(), Value::object(env_obj));
850
867
  exports.insert(
851
868
  "process".into(),
852
- Value::Object(Rc::new(RefCell::new(process_obj))),
869
+ Value::object(process_obj),
853
870
  );
854
- return Ok(Value::Object(Rc::new(RefCell::new(exports))));
871
+ return Ok(Value::object(exports));
855
872
  }
856
873
  #[cfg(not(feature = "process"))]
857
874
  {
@@ -875,9 +892,13 @@ impl Evaluator {
875
892
  Value::Object(m) => m.borrow().clone(),
876
893
  _ => return Err(EvalError::Error("Built-in module must be object".into())),
877
894
  };
878
- exports.get(export_name).cloned().ok_or_else(|| {
879
- EvalError::Error(format!("Module {} does not export '{}'", spec, export_name))
880
- })
895
+ exports
896
+ .strings
897
+ .get(export_name)
898
+ .cloned()
899
+ .ok_or_else(|| {
900
+ EvalError::Error(format!("Module {} does not export '{}'", spec, export_name))
901
+ })
881
902
  }
882
903
 
883
904
  fn eval_expr(&self, expr: &Expr) -> Result<Value, EvalError> {
@@ -1747,23 +1768,35 @@ impl Evaluator {
1747
1768
  Ok(Value::Array(Rc::new(RefCell::new(vals))))
1748
1769
  }
1749
1770
  Expr::Object { props, .. } => {
1750
- let mut map = PropMap::default();
1771
+ let mut data = EvalObjectData::default();
1751
1772
  for prop in props {
1752
1773
  match prop {
1753
1774
  tishlang_ast::ObjectProp::KeyValue(k, v) => {
1754
- map.insert(Arc::clone(k), self.eval_expr(v)?);
1775
+ data
1776
+ .strings
1777
+ .insert(Arc::clone(k), self.eval_expr(v)?);
1755
1778
  }
1756
1779
  tishlang_ast::ObjectProp::Spread(e) => {
1757
1780
  let spread_val = self.eval_expr(e)?;
1758
1781
  if let Value::Object(obj) = spread_val {
1759
- for (k, v) in obj.borrow().iter() {
1760
- map.insert(Arc::clone(k), v.clone());
1782
+ let b = obj.borrow();
1783
+ for (k, v) in b.strings.iter() {
1784
+ data.strings.insert(Arc::clone(k), v.clone());
1785
+ }
1786
+ if let Some(ref sm) = b.symbols {
1787
+ if data.symbols.is_none() {
1788
+ data.symbols = Some(AHashMap::default());
1789
+ }
1790
+ let dm = data.symbols.as_mut().unwrap();
1791
+ for (id, v) in sm.iter() {
1792
+ dm.insert(*id, v.clone());
1793
+ }
1761
1794
  }
1762
1795
  }
1763
1796
  }
1764
1797
  }
1765
1798
  }
1766
- Ok(Value::Object(Rc::new(RefCell::new(map))))
1799
+ Ok(Value::Object(Rc::new(RefCell::new(data))))
1767
1800
  }
1768
1801
  Expr::Assign { name, value, .. } => {
1769
1802
  let v = self.eval_expr(value)?;
@@ -1794,6 +1827,7 @@ impl Evaluator {
1794
1827
  Value::Null => "null".into(),
1795
1828
  Value::Array(_) => "object".into(),
1796
1829
  Value::Object(_) => "object".into(),
1830
+ Value::Symbol(_) => "symbol".into(),
1797
1831
  Value::Function { .. } | Value::Native(_) => "function".into(),
1798
1832
  Value::CoreFn(_) => "function".into(),
1799
1833
  #[cfg(feature = "http")]
@@ -1924,7 +1958,9 @@ impl Evaluator {
1924
1958
  let val = self.eval_expr(value)?;
1925
1959
  match obj_val {
1926
1960
  Value::Object(map) => {
1927
- map.borrow_mut().insert(Arc::clone(prop), val.clone());
1961
+ map.borrow_mut()
1962
+ .strings
1963
+ .insert(Arc::clone(prop), val.clone());
1928
1964
  Ok(val)
1929
1965
  }
1930
1966
  _ => Err(EvalError::Error(format!(
@@ -1954,16 +1990,9 @@ impl Evaluator {
1954
1990
  arr_mut[idx] = val.clone();
1955
1991
  Ok(val)
1956
1992
  }
1957
- Value::Object(map) => {
1958
- let key: Arc<str> = match &idx_val {
1959
- Value::Number(n) => n.to_string().into(),
1960
- Value::String(s) => Arc::clone(s),
1961
- _ => return Err(EvalError::Error(format!(
1962
- "Object key must be string or number, got {:?}",
1963
- idx_val
1964
- ))),
1965
- };
1966
- map.borrow_mut().insert(key, val.clone());
1993
+ Value::Object(_) => {
1994
+ eval_object_set(&obj_val, &idx_val, val.clone())
1995
+ .map_err(EvalError::Error)?;
1967
1996
  Ok(val)
1968
1997
  }
1969
1998
  _ => Err(EvalError::Error(format!(
@@ -2051,14 +2080,19 @@ impl Evaluator {
2051
2080
  BinOp::Shl => self.binop_int32(l, r, |a, b| Value::Number((a << b) as f64)),
2052
2081
  BinOp::Shr => self.binop_int32(l, r, |a, b| Value::Number((a >> b) as f64)),
2053
2082
  BinOp::In => {
2054
- let key: Arc<str> = match l {
2055
- Value::String(s) => Arc::clone(s),
2056
- Value::Number(n) => n.to_string().into(),
2057
- _ => return Err(format!("'in' requires string or number key, got {:?}", l)),
2058
- };
2059
2083
  let ok = match r {
2060
- Value::Object(map) => map.borrow().contains_key(&key),
2084
+ Value::Object(_) => eval_object_has(r, l),
2061
2085
  Value::Array(arr) => {
2086
+ let key: Arc<str> = match l {
2087
+ Value::String(s) => Arc::clone(s),
2088
+ Value::Number(n) => n.to_string().into(),
2089
+ _ => {
2090
+ return Err(format!(
2091
+ "'in' requires string or number key on array, got {:?}",
2092
+ l
2093
+ ))
2094
+ }
2095
+ };
2062
2096
  key.as_ref() == "length"
2063
2097
  || key
2064
2098
  .parse::<usize>()
@@ -2349,6 +2383,7 @@ impl Evaluator {
2349
2383
  Value::Object(o) => {
2350
2384
  let result = o
2351
2385
  .borrow()
2386
+ .strings
2352
2387
  .get(name.as_ref())
2353
2388
  .cloned()
2354
2389
  .unwrap_or(Value::Null);
@@ -2366,8 +2401,13 @@ impl Evaluator {
2366
2401
  /// Host `new`: `__construct` on objects; otherwise same callables as `call_func`, else null.
2367
2402
  fn construct_value(&self, callee: &Value, args: &[Value]) -> Result<Value, EvalError> {
2368
2403
  if let Value::Object(o) = callee {
2369
- if let Some(ctor) = o.borrow().get(&Arc::from("__construct")) {
2370
- return self.call_func(ctor, args);
2404
+ if let Some(ctor) = o
2405
+ .borrow()
2406
+ .strings
2407
+ .get("__construct")
2408
+ .cloned()
2409
+ {
2410
+ return self.call_func(&ctor, args);
2371
2411
  }
2372
2412
  }
2373
2413
  match callee {
@@ -2375,9 +2415,9 @@ impl Evaluator {
2375
2415
  self.call_func(callee, args)
2376
2416
  }
2377
2417
  #[cfg(feature = "http")]
2378
- Value::PromiseConstructor
2379
- | Value::Serve
2380
- | Value::BoundPromiseMethod(_, _) => self.call_func(callee, args),
2418
+ Value::PromiseConstructor | Value::Serve | Value::BoundPromiseMethod(_, _) => {
2419
+ self.call_func(callee, args)
2420
+ }
2381
2421
  #[cfg(feature = "timers")]
2382
2422
  Value::TimerBuiltin(_) => self.call_func(callee, args),
2383
2423
  Value::OpaqueMethod(_, _) => self.call_func(callee, args),
@@ -2387,6 +2427,12 @@ impl Evaluator {
2387
2427
 
2388
2428
  fn call_func(&self, f: &Value, args: &[Value]) -> Result<Value, EvalError> {
2389
2429
  match f {
2430
+ Value::Object(o) => {
2431
+ if let Some(call) = o.borrow().strings.get("__call").cloned() {
2432
+ return self.call_func(&call, args);
2433
+ }
2434
+ Err(EvalError::Error("Not a function".to_string()))
2435
+ }
2390
2436
  Value::Native(native_fn) => native_fn(args).map_err(EvalError::Error),
2391
2437
  #[cfg(feature = "http")]
2392
2438
  Value::PromiseResolver(r) => {
@@ -2805,13 +2851,13 @@ impl Evaluator {
2805
2851
  let mut err_obj: PropMap = PropMap::with_capacity(2);
2806
2852
  err_obj.insert(Arc::from("status"), Value::Number(500.0));
2807
2853
  err_obj.insert(Arc::from("body"), Value::String(v.to_string().into()));
2808
- Value::Object(Rc::new(RefCell::new(err_obj)))
2854
+ Value::object(err_obj)
2809
2855
  }
2810
2856
  Err(e) => {
2811
2857
  let mut err_obj: PropMap = PropMap::with_capacity(2);
2812
2858
  err_obj.insert(Arc::from("status"), Value::Number(500.0));
2813
2859
  err_obj.insert(Arc::from("body"), Value::String(e.to_string().into()));
2814
- Value::Object(Rc::new(RefCell::new(err_obj)))
2860
+ Value::object(err_obj)
2815
2861
  }
2816
2862
  };
2817
2863
 
@@ -2902,7 +2948,11 @@ impl Evaluator {
2902
2948
  };
2903
2949
 
2904
2950
  for prop in props {
2905
- let val = obj.get(&prop.key).cloned().unwrap_or(Value::Null);
2951
+ let val = obj
2952
+ .strings
2953
+ .get(prop.key.as_ref())
2954
+ .cloned()
2955
+ .unwrap_or(Value::Null);
2906
2956
  match &prop.value {
2907
2957
  tishlang_ast::DestructElement::Ident(name, _) => {
2908
2958
  scope.borrow_mut().set(Arc::clone(name), val, mutable);
@@ -2955,7 +3005,12 @@ impl Evaluator {
2955
3005
 
2956
3006
  fn get_prop(&self, obj: &Value, key: &str) -> Result<Value, String> {
2957
3007
  match obj {
2958
- Value::Object(map) => Ok(map.borrow().get(key).cloned().unwrap_or(Value::Null)),
3008
+ Value::Object(map) => Ok(map
3009
+ .borrow()
3010
+ .strings
3011
+ .get(key)
3012
+ .cloned()
3013
+ .unwrap_or(Value::Null)),
2959
3014
  Value::Array(arr) => {
2960
3015
  if key == "length" {
2961
3016
  Ok(Value::Number(arr.borrow().len() as f64))
@@ -3034,14 +3089,7 @@ impl Evaluator {
3034
3089
  };
3035
3090
  Ok(arr.borrow().get(idx).cloned().unwrap_or(Value::Null))
3036
3091
  }
3037
- Value::Object(map) => {
3038
- let key: Arc<str> = match index {
3039
- Value::Number(n) => n.to_string().into(),
3040
- Value::String(s) => Arc::clone(s),
3041
- _ => return Ok(Value::Null),
3042
- };
3043
- Ok(map.borrow().get(&key).cloned().unwrap_or(Value::Null))
3044
- }
3092
+ Value::Object(_) => Ok(eval_object_get(obj, index).unwrap_or(Value::Null)),
3045
3093
  #[cfg(feature = "http")]
3046
3094
  Value::Promise(_) | Value::CorePromise(_) => {
3047
3095
  let key = match index {
@@ -3154,7 +3202,7 @@ impl Evaluator {
3154
3202
  fn json_parse_object(s: &str) -> Result<Value, ()> {
3155
3203
  let s = s[1..].trim_start();
3156
3204
  if s.starts_with('}') {
3157
- return Ok(Value::Object(Rc::new(RefCell::new(PropMap::default()))));
3205
+ return Ok(Value::object(PropMap::default()));
3158
3206
  }
3159
3207
  let mut map = PropMap::default();
3160
3208
  let mut rest = s;
@@ -3183,7 +3231,7 @@ impl Evaluator {
3183
3231
  }
3184
3232
  rest = rest[1..].trim_start();
3185
3233
  }
3186
- Ok(Value::Object(Rc::new(RefCell::new(map))))
3234
+ Ok(Value::object(map))
3187
3235
  }
3188
3236
 
3189
3237
  fn json_parse_one(s: &str) -> Result<(Value, &str), ()> {
@@ -3270,6 +3318,7 @@ impl Evaluator {
3270
3318
  Value::Object(map) => {
3271
3319
  let mut entries: Vec<_> = map
3272
3320
  .borrow()
3321
+ .strings
3273
3322
  .iter()
3274
3323
  .map(|(k, v)| {
3275
3324
  (
@@ -3292,6 +3341,7 @@ impl Evaluator {
3292
3341
  .join(",")
3293
3342
  )
3294
3343
  }
3344
+ Value::Symbol(_) => "null".to_string(),
3295
3345
  Value::Function { .. } | Value::Native(_) => "null".to_string(),
3296
3346
  #[cfg(feature = "http")]
3297
3347
  Value::CorePromise(_) => "null".to_string(),
@@ -3325,6 +3375,7 @@ impl Evaluator {
3325
3375
  if let Some(Value::Object(obj)) = args.first() {
3326
3376
  let keys: Vec<Value> = obj
3327
3377
  .borrow()
3378
+ .strings
3328
3379
  .keys()
3329
3380
  .map(|k| Value::String(Arc::clone(k)))
3330
3381
  .collect();
@@ -3336,7 +3387,7 @@ impl Evaluator {
3336
3387
 
3337
3388
  fn object_values(args: &[Value]) -> Result<Value, String> {
3338
3389
  if let Some(Value::Object(obj)) = args.first() {
3339
- let values: Vec<Value> = obj.borrow().values().cloned().collect();
3390
+ let values: Vec<Value> = obj.borrow().strings.values().cloned().collect();
3340
3391
  Ok(Value::Array(Rc::new(RefCell::new(values))))
3341
3392
  } else {
3342
3393
  Ok(Value::Array(Rc::new(RefCell::new(Vec::new()))))
@@ -3347,6 +3398,7 @@ impl Evaluator {
3347
3398
  if let Some(Value::Object(obj)) = args.first() {
3348
3399
  let entries: Vec<Value> = obj
3349
3400
  .borrow()
3401
+ .strings
3350
3402
  .iter()
3351
3403
  .map(|(k, v)| {
3352
3404
  Value::Array(Rc::new(RefCell::new(vec![
@@ -3366,8 +3418,18 @@ impl Evaluator {
3366
3418
  let mut t = target.borrow_mut();
3367
3419
  for src in args.iter().skip(1) {
3368
3420
  if let Value::Object(src_obj) = src {
3369
- for (k, v) in src_obj.borrow().iter() {
3370
- t.insert(Arc::clone(k), v.clone());
3421
+ let s = src_obj.borrow();
3422
+ for (k, v) in s.strings.iter() {
3423
+ t.strings.insert(Arc::clone(k), v.clone());
3424
+ }
3425
+ if let Some(ref sm) = s.symbols {
3426
+ if t.symbols.is_none() {
3427
+ t.symbols = Some(AHashMap::default());
3428
+ }
3429
+ let tm = t.symbols.as_mut().unwrap();
3430
+ for (id, v) in sm.iter() {
3431
+ tm.insert(*id, v.clone());
3432
+ }
3371
3433
  }
3372
3434
  }
3373
3435
  }
@@ -3390,9 +3452,9 @@ impl Evaluator {
3390
3452
  }
3391
3453
  }
3392
3454
  }
3393
- Ok(Value::Object(Rc::new(RefCell::new(map))))
3455
+ Ok(Value::object(map))
3394
3456
  } else {
3395
- Ok(Value::Object(Rc::new(RefCell::new(PropMap::default()))))
3457
+ Ok(Value::object(PropMap::default()))
3396
3458
  }
3397
3459
  }
3398
3460
 
@@ -46,16 +46,13 @@ pub fn request_to_value(request: &mut tiny_http::Request) -> Value {
46
46
  Value::String(header.value.as_str().into()),
47
47
  );
48
48
  }
49
- obj.insert(
50
- Arc::from("headers"),
51
- Value::Object(std::rc::Rc::new(std::cell::RefCell::new(headers_obj))),
52
- );
49
+ obj.insert(Arc::from("headers"), Value::object(headers_obj));
53
50
 
54
51
  let mut body = String::new();
55
52
  let _ = request.as_reader().read_to_string(&mut body);
56
53
  obj.insert(Arc::from("body"), Value::String(body.into()));
57
54
 
58
- Value::Object(std::rc::Rc::new(std::cell::RefCell::new(obj)))
55
+ Value::object(obj)
59
56
  }
60
57
 
61
58
  /// Extract response data from a Tish Value object.
@@ -68,7 +65,8 @@ pub fn value_to_response(value: &Value) -> (u16, Vec<(String, String)>, String)
68
65
  let obj_ref = obj.borrow();
69
66
 
70
67
  let status = obj_ref
71
- .get(&Arc::from("status"))
68
+ .strings
69
+ .get("status")
72
70
  .and_then(|v| match v {
73
71
  Value::Number(n) => Some(*n as u16),
74
72
  _ => None,
@@ -76,15 +74,18 @@ pub fn value_to_response(value: &Value) -> (u16, Vec<(String, String)>, String)
76
74
  .unwrap_or(default_status);
77
75
 
78
76
  let body = obj_ref
79
- .get(&Arc::from("body"))
77
+ .strings
78
+ .get("body")
80
79
  .map(|v| v.to_string())
81
80
  .unwrap_or_default();
82
81
 
83
82
  let headers = obj_ref
84
- .get(&Arc::from("headers"))
83
+ .strings
84
+ .get("headers")
85
85
  .and_then(|v| match v {
86
86
  Value::Object(h) => Some(
87
87
  h.borrow()
88
+ .strings
88
89
  .iter()
89
90
  .map(|(k, v)| (k.to_string(), v.to_string()))
90
91
  .collect(),
@@ -103,28 +104,33 @@ pub fn value_to_response(value: &Value) -> (u16, Vec<(String, String)>, String)
103
104
  }
104
105
 
105
106
  /// If the response value has a `file` key, stream that path (binary-safe). Matches `tishlang_runtime` HTTP behavior.
106
- pub(crate) fn extract_file_from_response(value: &Value) -> Option<(u16, Vec<(String, String)>, String)> {
107
+ pub(crate) fn extract_file_from_response(
108
+ value: &Value,
109
+ ) -> Option<(u16, Vec<(String, String)>, String)> {
107
110
  let Value::Object(obj) = value else {
108
111
  return None;
109
112
  };
110
113
  let obj_ref = obj.borrow();
111
- let file_val = obj_ref.get(&Arc::from("file"))?;
114
+ let file_val = obj_ref.strings.get("file")?;
112
115
  let Value::String(file_path) = file_val else {
113
116
  return None;
114
117
  };
115
118
  let file_path = file_path.to_string();
116
119
  let status = obj_ref
117
- .get(&Arc::from("status"))
120
+ .strings
121
+ .get("status")
118
122
  .and_then(|v| match v {
119
123
  Value::Number(n) => Some(*n as u16),
120
124
  _ => None,
121
125
  })
122
126
  .unwrap_or(200);
123
127
  let headers = obj_ref
124
- .get(&Arc::from("headers"))
128
+ .strings
129
+ .get("headers")
125
130
  .and_then(|v| match v {
126
131
  Value::Object(h) => Some(
127
132
  h.borrow()
133
+ .strings
128
134
  .iter()
129
135
  .map(|(k, v)| (k.to_string(), v.to_string()))
130
136
  .collect(),
@@ -68,3 +68,32 @@ pub fn run_file(
68
68
  eval.run_timer_phase()?;
69
69
  Ok(result)
70
70
  }
71
+
72
+ #[cfg(test)]
73
+ mod global_scope_tests {
74
+ use super::*;
75
+
76
+ #[test]
77
+ fn symbol_global_loads() {
78
+ let mut e = Evaluator::new();
79
+ let program = tishlang_parser::parse("Symbol").expect("parse");
80
+ let r = e.eval_program(&program);
81
+ assert!(
82
+ r.is_ok(),
83
+ "expected Symbol global, got {:?}",
84
+ r.as_ref().err()
85
+ );
86
+ }
87
+
88
+ #[test]
89
+ fn symbol_call_under_typeof_loads() {
90
+ let mut e = Evaluator::new();
91
+ let program = tishlang_parser::parse("typeof Symbol(\"z\")").expect("parse");
92
+ let r = e.eval_program(&program);
93
+ assert!(
94
+ r.is_ok(),
95
+ "expected Symbol global for typeof Symbol(\"z\"), got {:?}",
96
+ r.as_ref().err()
97
+ );
98
+ }
99
+ }
@@ -63,7 +63,7 @@ pub fn regexp_exec(re: &mut TishRegExp, input: &str) -> Value {
63
63
  };
64
64
  }
65
65
 
66
- Value::Object(Rc::new(RefCell::new(obj)))
66
+ Value::object(obj)
67
67
  }
68
68
  Ok(None) | Err(_) => {
69
69
  if re.flags.global || re.flags.sticky {