@tishlang/tish-format 1.0.12 → 1.0.13

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 (164) hide show
  1. package/Cargo.toml +49 -0
  2. package/LICENSE +13 -0
  3. package/README.md +138 -0
  4. package/bin/tish-format +0 -0
  5. package/crates/js_to_tish/Cargo.toml +11 -0
  6. package/crates/js_to_tish/README.md +18 -0
  7. package/crates/js_to_tish/src/error.rs +55 -0
  8. package/crates/js_to_tish/src/lib.rs +11 -0
  9. package/crates/js_to_tish/src/span_util.rs +35 -0
  10. package/crates/js_to_tish/src/transform/expr.rs +610 -0
  11. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  12. package/crates/js_to_tish/src/transform.rs +60 -0
  13. package/crates/tish/Cargo.toml +54 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +565 -0
  16. package/crates/tish/src/main.rs +781 -0
  17. package/crates/tish/src/repl_completion.rs +200 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  24. package/crates/tish/tests/integration_test.rs +1095 -0
  25. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  26. package/crates/tish/tests/shortcircuit.rs +65 -0
  27. package/crates/tish_ast/Cargo.toml +9 -0
  28. package/crates/tish_ast/src/ast.rs +620 -0
  29. package/crates/tish_ast/src/lib.rs +5 -0
  30. package/crates/tish_build_utils/Cargo.toml +11 -0
  31. package/crates/tish_build_utils/src/lib.rs +577 -0
  32. package/crates/tish_builtins/Cargo.toml +20 -0
  33. package/crates/tish_builtins/src/array.rs +441 -0
  34. package/crates/tish_builtins/src/construct.rs +159 -0
  35. package/crates/tish_builtins/src/globals.rs +213 -0
  36. package/crates/tish_builtins/src/helpers.rs +35 -0
  37. package/crates/tish_builtins/src/lib.rs +16 -0
  38. package/crates/tish_builtins/src/math.rs +89 -0
  39. package/crates/tish_builtins/src/object.rs +36 -0
  40. package/crates/tish_builtins/src/string.rs +647 -0
  41. package/crates/tish_builtins/src/symbol.rs +83 -0
  42. package/crates/tish_bytecode/Cargo.toml +17 -0
  43. package/crates/tish_bytecode/src/chunk.rs +96 -0
  44. package/crates/tish_bytecode/src/compiler.rs +1760 -0
  45. package/crates/tish_bytecode/src/encoding.rs +100 -0
  46. package/crates/tish_bytecode/src/lib.rs +19 -0
  47. package/crates/tish_bytecode/src/opcode.rs +142 -0
  48. package/crates/tish_bytecode/src/peephole.rs +189 -0
  49. package/crates/tish_bytecode/src/serialize.rs +163 -0
  50. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  51. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  52. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  53. package/crates/tish_compile/Cargo.toml +26 -0
  54. package/crates/tish_compile/src/codegen.rs +5332 -0
  55. package/crates/tish_compile/src/infer.rs +292 -0
  56. package/crates/tish_compile/src/lib.rs +164 -0
  57. package/crates/tish_compile/src/resolve.rs +1388 -0
  58. package/crates/tish_compile/src/types.rs +501 -0
  59. package/crates/tish_compile_js/Cargo.toml +18 -0
  60. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  61. package/crates/tish_compile_js/src/codegen.rs +871 -0
  62. package/crates/tish_compile_js/src/error.rs +20 -0
  63. package/crates/tish_compile_js/src/lib.rs +26 -0
  64. package/crates/tish_compile_js/src/tests_jsx.rs +350 -0
  65. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  66. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  67. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  68. package/crates/tish_core/Cargo.toml +26 -0
  69. package/crates/tish_core/src/console_style.rs +160 -0
  70. package/crates/tish_core/src/json.rs +387 -0
  71. package/crates/tish_core/src/lib.rs +17 -0
  72. package/crates/tish_core/src/macros.rs +36 -0
  73. package/crates/tish_core/src/uri.rs +118 -0
  74. package/crates/tish_core/src/value.rs +696 -0
  75. package/crates/tish_core/src/vmref.rs +178 -0
  76. package/crates/tish_cranelift/Cargo.toml +19 -0
  77. package/crates/tish_cranelift/src/lib.rs +43 -0
  78. package/crates/tish_cranelift/src/link.rs +117 -0
  79. package/crates/tish_cranelift/src/lower.rs +85 -0
  80. package/crates/tish_cranelift_runtime/Cargo.toml +25 -0
  81. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  82. package/crates/tish_eval/Cargo.toml +45 -0
  83. package/crates/tish_eval/src/eval.rs +3717 -0
  84. package/crates/tish_eval/src/http.rs +188 -0
  85. package/crates/tish_eval/src/lib.rs +99 -0
  86. package/crates/tish_eval/src/natives.rs +399 -0
  87. package/crates/tish_eval/src/promise.rs +179 -0
  88. package/crates/tish_eval/src/regex.rs +299 -0
  89. package/crates/tish_eval/src/timers.rs +120 -0
  90. package/crates/tish_eval/src/value.rs +318 -0
  91. package/crates/tish_eval/src/value_convert.rs +111 -0
  92. package/crates/tish_fmt/Cargo.toml +16 -0
  93. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  94. package/crates/tish_fmt/src/lib.rs +2101 -0
  95. package/crates/tish_jsx_web/Cargo.toml +9 -0
  96. package/crates/tish_jsx_web/README.md +5 -0
  97. package/crates/tish_jsx_web/src/lib.rs +2 -0
  98. package/crates/tish_lexer/Cargo.toml +9 -0
  99. package/crates/tish_lexer/src/lib.rs +716 -0
  100. package/crates/tish_lexer/src/token.rs +163 -0
  101. package/crates/tish_lint/Cargo.toml +18 -0
  102. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  103. package/crates/tish_lint/src/lib.rs +289 -0
  104. package/crates/tish_llvm/Cargo.toml +13 -0
  105. package/crates/tish_llvm/src/lib.rs +115 -0
  106. package/crates/tish_lsp/Cargo.toml +25 -0
  107. package/crates/tish_lsp/README.md +26 -0
  108. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  109. package/crates/tish_lsp/src/import_goto.rs +562 -0
  110. package/crates/tish_lsp/src/main.rs +1046 -0
  111. package/crates/tish_native/Cargo.toml +16 -0
  112. package/crates/tish_native/src/build.rs +427 -0
  113. package/crates/tish_native/src/config.rs +48 -0
  114. package/crates/tish_native/src/lib.rs +416 -0
  115. package/crates/tish_opt/Cargo.toml +13 -0
  116. package/crates/tish_opt/src/lib.rs +943 -0
  117. package/crates/tish_parser/Cargo.toml +11 -0
  118. package/crates/tish_parser/src/lib.rs +332 -0
  119. package/crates/tish_parser/src/parser.rs +2304 -0
  120. package/crates/tish_pg/Cargo.toml +34 -0
  121. package/crates/tish_pg/README.md +38 -0
  122. package/crates/tish_pg/src/error.rs +52 -0
  123. package/crates/tish_pg/src/lib.rs +955 -0
  124. package/crates/tish_resolve/Cargo.toml +13 -0
  125. package/crates/tish_resolve/src/lib.rs +3561 -0
  126. package/crates/tish_resolve/src/pos.rs +141 -0
  127. package/crates/tish_runtime/Cargo.toml +96 -0
  128. package/crates/tish_runtime/src/http.rs +1298 -0
  129. package/crates/tish_runtime/src/http_fetch.rs +471 -0
  130. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  131. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  132. package/crates/tish_runtime/src/lib.rs +1192 -0
  133. package/crates/tish_runtime/src/native_promise.rs +15 -0
  134. package/crates/tish_runtime/src/promise.rs +248 -0
  135. package/crates/tish_runtime/src/promise_io.rs +38 -0
  136. package/crates/tish_runtime/src/timers.rs +166 -0
  137. package/crates/tish_runtime/src/ws.rs +761 -0
  138. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  139. package/crates/tish_ui/Cargo.toml +17 -0
  140. package/crates/tish_ui/src/jsx.rs +682 -0
  141. package/crates/tish_ui/src/lib.rs +20 -0
  142. package/crates/tish_ui/src/runtime/hooks.rs +569 -0
  143. package/crates/tish_ui/src/runtime/mod.rs +180 -0
  144. package/crates/tish_vm/Cargo.toml +47 -0
  145. package/crates/tish_vm/src/lib.rs +39 -0
  146. package/crates/tish_vm/src/vm.rs +2192 -0
  147. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  148. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  149. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  150. package/crates/tish_wasm/Cargo.toml +15 -0
  151. package/crates/tish_wasm/src/lib.rs +424 -0
  152. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  153. package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
  154. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  155. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  156. package/crates/tishlang_cargo_bindgen/src/classify.rs +263 -0
  157. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  158. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  159. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  160. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  161. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  162. package/justfile +268 -0
  163. package/package.json +1 -1
  164. package/platform/darwin-arm64/tish-fmt +0 -0
@@ -0,0 +1,696 @@
1
+ //! Unified Value type for Tish runtime values.
2
+
3
+ use std::sync::atomic::{AtomicU64, Ordering};
4
+ use std::sync::Arc;
5
+
6
+ use ahash::AHashMap;
7
+
8
+ use crate::vmref::VmRef;
9
+
10
+ /// Property map for objects and other `Arc<str>` → `Value` tables (VM globals, scopes).
11
+ /// Uses a faster hasher than `std::collections::HashMap` for string-heavy workloads.
12
+ pub type ObjectMap = AHashMap<Arc<str>, Value>;
13
+
14
+ static NEXT_SYMBOL_ID: AtomicU64 = AtomicU64::new(1);
15
+
16
+ fn next_symbol_id() -> u64 {
17
+ NEXT_SYMBOL_ID.fetch_add(1, Ordering::Relaxed)
18
+ }
19
+
20
+ /// Allocate a unique symbol id (for `Symbol()` and first-time `Symbol.for` entries).
21
+ #[inline]
22
+ pub fn alloc_symbol_id() -> u64 {
23
+ next_symbol_id()
24
+ }
25
+
26
+ /// Primitive Symbol (ECMAScript-style): identity is `Arc` pointer equality.
27
+ #[derive(Debug)]
28
+ pub struct TishSymbol {
29
+ pub id: u64,
30
+ pub description: Option<Arc<str>>,
31
+ /// Set when created via `Symbol.for(key)` (global registry).
32
+ pub registry_key: Option<Arc<str>>,
33
+ }
34
+
35
+ impl TishSymbol {
36
+ /// Unique symbol (`Symbol("desc")`).
37
+ pub fn new_unique(description: Option<Arc<str>>) -> Arc<Self> {
38
+ Arc::new(Self {
39
+ id: next_symbol_id(),
40
+ description,
41
+ registry_key: None,
42
+ })
43
+ }
44
+
45
+ /// Registry symbol (`Symbol.for`): stable `id` for this registry key.
46
+ pub fn new_registry(id: u64, registry_key: Arc<str>, description: Option<Arc<str>>) -> Arc<Self> {
47
+ Arc::new(Self {
48
+ id,
49
+ description,
50
+ registry_key: Some(registry_key),
51
+ })
52
+ }
53
+ }
54
+
55
+ #[cfg(feature = "regex")]
56
+ use fancy_regex::Regex;
57
+
58
+ /// Native function signature.
59
+ ///
60
+ /// When the `send-values` feature is enabled this is
61
+ /// `Arc<dyn Fn + Send + Sync>`, so handler closures can be dispatched across
62
+ /// HTTP worker threads (`tishlang_runtime::http::serve`). Otherwise it stays
63
+ /// `Rc<dyn Fn>` for zero-overhead single-threaded execution (wasm / wasi /
64
+ /// interpreter / cranelift / llvm VMs and any Rust native build without
65
+ /// `http`).
66
+ #[cfg(feature = "send-values")]
67
+ pub type NativeFn = Arc<dyn Fn(&[Value]) -> Value + Send + Sync>;
68
+ #[cfg(not(feature = "send-values"))]
69
+ pub type NativeFn = std::rc::Rc<dyn Fn(&[Value]) -> Value>;
70
+
71
+ /// Trait for opaque Rust types exposed to Tish (e.g. Polars DataFrame).
72
+ /// Implementors provide method dispatch so Tish can call methods on the value.
73
+ ///
74
+ /// The `Send + Sync` supertrait bound is conditional on the `send-values`
75
+ /// feature. When `send-values` is off (single-threaded VMs: wasm browser /
76
+ /// wasi / interpreter / cranelift), `NativeFn` is already `Rc<dyn Fn>`, so
77
+ /// `Value` is `!Send` anyway — dropping the bound here loses nothing and lets
78
+ /// `!Send` opaques like `JsHandle(wasm_bindgen::JsValue)` be stored in a
79
+ /// `Value::Opaque` on the browser runtime.
80
+ #[cfg(feature = "send-values")]
81
+ pub trait TishOpaque: Send + Sync {
82
+ /// Display name for the type (e.g. "DataFrame").
83
+ fn type_name(&self) -> &'static str;
84
+
85
+ /// Get a method by name. Returns a native function if the method exists.
86
+ fn get_method(&self, name: &str) -> Option<NativeFn>;
87
+
88
+ /// For downcasting `Arc<dyn TishOpaque>` in native crates (e.g. Polars → `DataFrame`).
89
+ fn as_any(&self) -> &dyn std::any::Any;
90
+ }
91
+
92
+ /// Single-threaded variant (no `Send + Sync` bound); see the `send-values` doc above.
93
+ #[cfg(not(feature = "send-values"))]
94
+ pub trait TishOpaque {
95
+ /// Display name for the type (e.g. "DataFrame").
96
+ fn type_name(&self) -> &'static str;
97
+
98
+ /// Get a method by name. Returns a native function if the method exists.
99
+ fn get_method(&self, name: &str) -> Option<NativeFn>;
100
+
101
+ /// For downcasting `Arc<dyn TishOpaque>` in native crates (e.g. Polars → `DataFrame`).
102
+ fn as_any(&self) -> &dyn std::any::Any;
103
+ }
104
+
105
+ /// Trait for Promise-like values that can be awaited (block until settled).
106
+ /// Implemented by the runtime for native compile; interpreter uses its own Promise.
107
+ pub trait TishPromise: Send + Sync {
108
+ fn block_until_settled(&self) -> std::result::Result<Value, Value>;
109
+ }
110
+
111
+ /// JavaScript RegExp flags
112
+ #[cfg(feature = "regex")]
113
+ #[derive(Debug, Clone, Default)]
114
+ pub struct RegExpFlags {
115
+ pub global: bool,
116
+ pub ignore_case: bool,
117
+ pub multiline: bool,
118
+ pub dot_all: bool,
119
+ pub unicode: bool,
120
+ pub sticky: bool,
121
+ }
122
+
123
+ #[cfg(feature = "regex")]
124
+ impl RegExpFlags {
125
+ pub fn from_string(flags: &str) -> Result<Self, String> {
126
+ let mut result = Self::default();
127
+ for c in flags.chars() {
128
+ match c {
129
+ 'g' => {
130
+ if result.global {
131
+ return Err(format!("duplicate flag '{}'", c));
132
+ }
133
+ result.global = true;
134
+ }
135
+ 'i' => {
136
+ if result.ignore_case {
137
+ return Err(format!("duplicate flag '{}'", c));
138
+ }
139
+ result.ignore_case = true;
140
+ }
141
+ 'm' => {
142
+ if result.multiline {
143
+ return Err(format!("duplicate flag '{}'", c));
144
+ }
145
+ result.multiline = true;
146
+ }
147
+ 's' => {
148
+ if result.dot_all {
149
+ return Err(format!("duplicate flag '{}'", c));
150
+ }
151
+ result.dot_all = true;
152
+ }
153
+ 'u' => {
154
+ if result.unicode {
155
+ return Err(format!("duplicate flag '{}'", c));
156
+ }
157
+ result.unicode = true;
158
+ }
159
+ 'y' => {
160
+ if result.sticky {
161
+ return Err(format!("duplicate flag '{}'", c));
162
+ }
163
+ result.sticky = true;
164
+ }
165
+ _ => return Err(format!("unknown flag '{}'", c)),
166
+ }
167
+ }
168
+ Ok(result)
169
+ }
170
+ }
171
+
172
+ #[cfg(feature = "regex")]
173
+ impl std::fmt::Display for RegExpFlags {
174
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175
+ if self.global {
176
+ f.write_str("g")?;
177
+ }
178
+ if self.ignore_case {
179
+ f.write_str("i")?;
180
+ }
181
+ if self.multiline {
182
+ f.write_str("m")?;
183
+ }
184
+ if self.dot_all {
185
+ f.write_str("s")?;
186
+ }
187
+ if self.unicode {
188
+ f.write_str("u")?;
189
+ }
190
+ if self.sticky {
191
+ f.write_str("y")?;
192
+ }
193
+ Ok(())
194
+ }
195
+ }
196
+
197
+ /// Tish RegExp object
198
+ #[cfg(feature = "regex")]
199
+ #[derive(Debug, Clone)]
200
+ pub struct TishRegExp {
201
+ pub source: String,
202
+ pub flags: RegExpFlags,
203
+ pub regex: Arc<Regex>,
204
+ pub last_index: usize,
205
+ }
206
+
207
+ #[cfg(feature = "regex")]
208
+ impl TishRegExp {
209
+ pub fn new(pattern: &str, flags_str: &str) -> Result<Self, String> {
210
+ let flags = RegExpFlags::from_string(flags_str)?;
211
+ let mut regex_pattern = pattern.to_string();
212
+
213
+ if flags.ignore_case || flags.multiline || flags.dot_all {
214
+ let mut flag_prefix = String::from("(?");
215
+ if flags.ignore_case {
216
+ flag_prefix.push('i');
217
+ }
218
+ if flags.multiline {
219
+ flag_prefix.push('m');
220
+ }
221
+ if flags.dot_all {
222
+ flag_prefix.push('s');
223
+ }
224
+ flag_prefix.push(')');
225
+ regex_pattern = format!("{}{}", flag_prefix, regex_pattern);
226
+ }
227
+
228
+ let regex =
229
+ Regex::new(&regex_pattern).map_err(|e| format!("Invalid regular expression: {}", e))?;
230
+
231
+ Ok(Self {
232
+ source: pattern.to_string(),
233
+ flags,
234
+ regex: Arc::new(regex),
235
+ last_index: 0,
236
+ })
237
+ }
238
+
239
+ pub fn flags_string(&self) -> String {
240
+ self.flags.to_string()
241
+ }
242
+
243
+ pub fn test(&mut self, input: &str) -> bool {
244
+ if self.flags.global || self.flags.sticky {
245
+ let start = self.last_index;
246
+ if start > input.chars().count() {
247
+ self.last_index = 0;
248
+ return false;
249
+ }
250
+
251
+ let byte_start: usize = input.chars().take(start).map(|c| c.len_utf8()).sum();
252
+ let search_str = &input[byte_start..];
253
+
254
+ match self.regex.find(search_str) {
255
+ Ok(Some(m)) => {
256
+ if self.flags.sticky && m.start() != 0 {
257
+ self.last_index = 0;
258
+ return false;
259
+ }
260
+ let match_end_chars = input[byte_start..byte_start + m.end()].chars().count();
261
+ self.last_index = start + match_end_chars;
262
+ true
263
+ }
264
+ _ => {
265
+ self.last_index = 0;
266
+ false
267
+ }
268
+ }
269
+ } else {
270
+ self.regex.is_match(input).unwrap_or(false)
271
+ }
272
+ }
273
+ }
274
+
275
+ /// Runtime value for Tish programs.
276
+ /// Used by both interpreter and compiled code.
277
+ ///
278
+ /// **Thread safety**: `Value: Send + Sync`. Mutable payloads live inside
279
+ /// [`VmRef`], a `Send + Sync` `Arc<Mutex<T>>` wrapper that preserves the
280
+ /// `RefCell`-style borrow API. Functions are `Arc<dyn Fn + Send + Sync>`.
281
+ #[derive(Clone)]
282
+ pub enum Value {
283
+ Number(f64),
284
+ String(Arc<str>),
285
+ Bool(bool),
286
+ Null,
287
+ Array(VmRef<Vec<Value>>),
288
+ Object(VmRef<ObjectData>),
289
+ /// ECMAScript-style primitive symbol (identity by `Arc`).
290
+ Symbol(Arc<TishSymbol>),
291
+ Function(NativeFn),
292
+ #[cfg(feature = "regex")]
293
+ RegExp(VmRef<TishRegExp>),
294
+ /// Promise (for native compile). Interpreter uses tishlang_eval::Value::Promise.
295
+ Promise(Arc<dyn TishPromise>),
296
+ /// Opaque handle to a native Rust type (e.g. Polars DataFrame).
297
+ Opaque(Arc<dyn TishOpaque>),
298
+ }
299
+
300
+ /// Ordinary object: string-keyed properties plus optional symbol-keyed side map.
301
+ #[derive(Clone, Debug, Default)]
302
+ pub struct ObjectData {
303
+ pub strings: ObjectMap,
304
+ pub symbols: Option<AHashMap<u64, Value>>,
305
+ }
306
+
307
+ impl ObjectData {
308
+ #[inline]
309
+ pub fn from_strings(strings: ObjectMap) -> Self {
310
+ Self {
311
+ strings,
312
+ symbols: None,
313
+ }
314
+ }
315
+
316
+ #[inline]
317
+ pub fn len_entries(&self) -> usize {
318
+ self.strings.len() + self.symbols.as_ref().map(|s| s.len()).unwrap_or(0)
319
+ }
320
+ }
321
+
322
+ /// Read a property from an object value.
323
+ pub fn object_get(obj: &Value, key: &Value) -> Option<Value> {
324
+ let Value::Object(od) = obj else {
325
+ return None;
326
+ };
327
+ let b = od.borrow();
328
+ match key {
329
+ Value::Symbol(s) => b.symbols.as_ref()?.get(&s.id).cloned(),
330
+ Value::Number(n) => {
331
+ let k: Arc<str> = n.to_string().into();
332
+ b.strings.get(&k).cloned()
333
+ }
334
+ Value::String(k) => b.strings.get(k.as_ref()).cloned(),
335
+ _ => None,
336
+ }
337
+ }
338
+
339
+ /// Set a property on an object.
340
+ pub fn object_set(obj: &Value, key: &Value, val: Value) -> Result<(), String> {
341
+ let Value::Object(od) = obj else {
342
+ return Err(format!("Cannot set property on {}", obj.type_name()));
343
+ };
344
+ let mut b = od.borrow_mut();
345
+ match key {
346
+ Value::Symbol(s) => {
347
+ if b.symbols.is_none() {
348
+ b.symbols = Some(AHashMap::default());
349
+ }
350
+ b.symbols.as_mut().unwrap().insert(s.id, val);
351
+ Ok(())
352
+ }
353
+ Value::Number(n) => {
354
+ b.strings.insert(n.to_string().into(), val);
355
+ Ok(())
356
+ }
357
+ Value::String(k) => {
358
+ b.strings.insert(Arc::clone(k), val);
359
+ Ok(())
360
+ }
361
+ _ => Err(format!(
362
+ "Object key must be string, number, or symbol, got {}",
363
+ key.type_name()
364
+ )),
365
+ }
366
+ }
367
+
368
+ /// `key in obj` for objects.
369
+ pub fn object_has(obj: &Value, key: &Value) -> bool {
370
+ let Value::Object(od) = obj else {
371
+ return false;
372
+ };
373
+ let b = od.borrow();
374
+ match key {
375
+ Value::Symbol(s) => b.symbols.as_ref().is_some_and(|m| m.contains_key(&s.id)),
376
+ Value::Number(n) => {
377
+ let k: Arc<str> = n.to_string().into();
378
+ b.strings.contains_key(&k)
379
+ }
380
+ Value::String(k) => b.strings.contains_key(k.as_ref()),
381
+ _ => false,
382
+ }
383
+ }
384
+
385
+ /// Invoke a callable [`Value`]: [`Value::Function`], or an object exposing `__call` (e.g. `Symbol`).
386
+ pub fn value_call(callee: &Value, args: &[Value]) -> Value {
387
+ match callee {
388
+ Value::Function(f) => f(args),
389
+ Value::Object(o) => {
390
+ let inner = o.borrow().strings.get("__call").cloned();
391
+ if let Some(inner) = inner {
392
+ return value_call(&inner, args);
393
+ }
394
+ panic!(
395
+ "Not a function: tried to call {:?} as a function (e.g. method on Null when read failed)",
396
+ callee
397
+ );
398
+ }
399
+ _ => panic!(
400
+ "Not a function: tried to call {:?} as a function (e.g. method on Null when read failed)",
401
+ callee
402
+ ),
403
+ }
404
+ }
405
+
406
+ /// Merge two object payloads (spread / VM MergeObject).
407
+ pub fn merge_object_data(left: &VmRef<ObjectData>, right: &VmRef<ObjectData>) -> ObjectData {
408
+ let l = left.borrow();
409
+ let r = right.borrow();
410
+ let mut strings = ObjectMap::with_capacity(l.strings.len() + r.strings.len());
411
+ strings.extend(l.strings.iter().map(|(k, v)| (Arc::clone(k), v.clone())));
412
+ strings.extend(r.strings.iter().map(|(k, v)| (Arc::clone(k), v.clone())));
413
+ let mut symbols: Option<AHashMap<u64, Value>> = None;
414
+ if let Some(ls) = &l.symbols {
415
+ symbols = Some(ls.clone());
416
+ }
417
+ if let Some(rs) = &r.symbols {
418
+ match &mut symbols {
419
+ Some(m) => {
420
+ m.extend(rs.iter().map(|(k, v)| (*k, v.clone())));
421
+ }
422
+ None => symbols = Some(rs.clone()),
423
+ }
424
+ }
425
+ ObjectData { strings, symbols }
426
+ }
427
+
428
+ impl std::fmt::Debug for Value {
429
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
430
+ match self {
431
+ Value::Number(n) => write!(f, "Number({})", n),
432
+ Value::String(s) => write!(f, "String({:?})", s.as_ref()),
433
+ Value::Bool(b) => write!(f, "Bool({})", b),
434
+ Value::Null => write!(f, "Null"),
435
+ Value::Array(arr) => write!(f, "Array({:?})", arr.borrow()),
436
+ Value::Object(obj) => write!(f, "Object({:?})", obj.borrow()),
437
+ Value::Symbol(s) => write!(f, "Symbol({})", s.id),
438
+ Value::Function(_) => write!(f, "Function"),
439
+ #[cfg(feature = "regex")]
440
+ Value::RegExp(re) => write!(
441
+ f,
442
+ "RegExp(/{}/{})",
443
+ re.borrow().source,
444
+ re.borrow().flags_string()
445
+ ),
446
+ Value::Promise(_) => write!(f, "Promise"),
447
+ Value::Opaque(o) => write!(f, "{}(opaque)", o.type_name()),
448
+ }
449
+ }
450
+ }
451
+
452
+ impl Value {
453
+ /// Convert value to display string (for console output).
454
+ pub fn to_display_string(&self) -> String {
455
+ match self {
456
+ Value::Number(n) => {
457
+ if n.is_nan() {
458
+ "NaN".to_string()
459
+ } else if *n == f64::INFINITY {
460
+ "Infinity".to_string()
461
+ } else if *n == f64::NEG_INFINITY {
462
+ "-Infinity".to_string()
463
+ } else {
464
+ n.to_string()
465
+ }
466
+ }
467
+ Value::String(s) => s.to_string(),
468
+ Value::Bool(b) => b.to_string(),
469
+ Value::Null => "null".to_string(),
470
+ Value::Array(arr) => {
471
+ let inner: Vec<String> =
472
+ arr.borrow().iter().map(|v| v.to_display_string()).collect();
473
+ format!("[{}]", inner.join(", "))
474
+ }
475
+ Value::Object(obj) => {
476
+ let inner: Vec<String> = obj
477
+ .borrow()
478
+ .strings
479
+ .iter()
480
+ .map(|(k, v)| format!("{}: {}", k.as_ref(), v.to_display_string()))
481
+ .collect();
482
+ format!("{{{}}}", inner.join(", "))
483
+ }
484
+ Value::Symbol(s) => {
485
+ if let Some(d) = &s.description {
486
+ format!("Symbol({})", d)
487
+ } else {
488
+ "Symbol()".to_string()
489
+ }
490
+ }
491
+ Value::Function(_) => "[Function]".to_string(),
492
+ Value::Promise(_) => "[object Promise]".to_string(),
493
+ Value::Opaque(o) => format!("[object {}]", o.type_name()),
494
+ #[cfg(feature = "regex")]
495
+ Value::RegExp(re) => {
496
+ let re = re.borrow();
497
+ format!("/{}/{}", re.source, re.flags_string())
498
+ }
499
+ }
500
+ }
501
+
502
+ /// Check if value is truthy (for conditionals).
503
+ pub fn is_truthy(&self) -> bool {
504
+ match self {
505
+ Value::Null => false,
506
+ Value::Bool(b) => *b,
507
+ Value::Number(n) => *n != 0.0 && !n.is_nan(),
508
+ Value::String(s) => !s.is_empty(),
509
+ _ => true,
510
+ }
511
+ }
512
+
513
+ /// Strict equality (===).
514
+ pub fn strict_eq(&self, other: &Value) -> bool {
515
+ match (self, other) {
516
+ (Value::Number(a), Value::Number(b)) => {
517
+ if a.is_nan() || b.is_nan() {
518
+ false
519
+ } else {
520
+ a == b
521
+ }
522
+ }
523
+ (Value::String(a), Value::String(b)) => a == b,
524
+ (Value::Bool(a), Value::Bool(b)) => a == b,
525
+ (Value::Null, Value::Null) => true,
526
+ (Value::Array(a), Value::Array(b)) => VmRef::ptr_eq(a, b),
527
+ (Value::Object(a), Value::Object(b)) => VmRef::ptr_eq(a, b),
528
+ #[cfg(feature = "send-values")]
529
+ (Value::Function(a), Value::Function(b)) => Arc::ptr_eq(a, b),
530
+ #[cfg(not(feature = "send-values"))]
531
+ (Value::Function(a), Value::Function(b)) => std::rc::Rc::ptr_eq(a, b),
532
+ #[cfg(feature = "regex")]
533
+ (Value::RegExp(a), Value::RegExp(b)) => VmRef::ptr_eq(a, b),
534
+ (Value::Promise(a), Value::Promise(b)) => Arc::ptr_eq(a, b),
535
+ (Value::Opaque(a), Value::Opaque(b)) => Arc::ptr_eq(a, b),
536
+ (Value::Symbol(a), Value::Symbol(b)) => Arc::ptr_eq(a, b),
537
+ _ => false,
538
+ }
539
+ }
540
+
541
+ /// Wrap a Rust closure in a `Value::Function`. Automatically picks
542
+ /// `Rc<dyn Fn>` or `Arc<dyn Fn + Send + Sync>` based on the
543
+ /// `send-values` feature, so callers don't have to `cfg`-gate their
544
+ /// code. The input bound tracks the feature too: when `send-values`
545
+ /// is enabled the closure must be `Send + Sync`, otherwise any `Fn`
546
+ /// is accepted.
547
+ #[cfg(feature = "send-values")]
548
+ pub fn native<F>(f: F) -> Self
549
+ where
550
+ F: Fn(&[Value]) -> Value + Send + Sync + 'static,
551
+ {
552
+ Value::Function(Arc::new(f))
553
+ }
554
+
555
+ #[cfg(not(feature = "send-values"))]
556
+ pub fn native<F>(f: F) -> Self
557
+ where
558
+ F: Fn(&[Value]) -> Value + 'static,
559
+ {
560
+ Value::Function(std::rc::Rc::new(f))
561
+ }
562
+
563
+ /// Create a new array Value from a Vec.
564
+ pub fn array(items: Vec<Value>) -> Self {
565
+ Value::Array(VmRef::new(items))
566
+ }
567
+
568
+ /// Create a new object Value from a property map.
569
+ pub fn object(map: ObjectMap) -> Self {
570
+ Value::Object(VmRef::new(ObjectData::from_strings(map)))
571
+ }
572
+
573
+ /// Create an empty array Value.
574
+ pub fn empty_array() -> Self {
575
+ Value::Array(VmRef::new(Vec::new()))
576
+ }
577
+
578
+ /// Create an empty object Value.
579
+ pub fn empty_object() -> Self {
580
+ Value::Object(VmRef::new(ObjectData::default()))
581
+ }
582
+
583
+ /// Extract the number value, if this is a Number.
584
+ pub fn as_number(&self) -> Option<f64> {
585
+ match self {
586
+ Value::Number(n) => Some(*n),
587
+ _ => None,
588
+ }
589
+ }
590
+
591
+ /// JavaScript-style typeof string for this value.
592
+ pub fn type_name(&self) -> &'static str {
593
+ match self {
594
+ Value::Number(_) => "number",
595
+ Value::String(_) => "string",
596
+ Value::Bool(_) => "boolean",
597
+ Value::Null => "null",
598
+ Value::Array(_) => "object",
599
+ Value::Object(_) => "object",
600
+ Value::Function(_) => "function",
601
+ #[cfg(feature = "regex")]
602
+ Value::RegExp(_) => "object",
603
+ Value::Promise(_) => "object",
604
+ Value::Opaque(o) => o.type_name(),
605
+ Value::Symbol(_) => "symbol",
606
+ }
607
+ }
608
+
609
+ /// Property/method names for REPL tab completion (e.g. after `obj.`).
610
+ pub fn completion_keys(&self) -> Vec<String> {
611
+ match self {
612
+ Value::Object(m) => {
613
+ let mut keys: Vec<String> = m
614
+ .borrow()
615
+ .strings
616
+ .keys()
617
+ .map(|k| k.to_string())
618
+ .collect();
619
+ keys.sort();
620
+ keys
621
+ }
622
+ Value::Array(_) => {
623
+ vec![
624
+ "length".into(),
625
+ "at".into(),
626
+ "concat".into(),
627
+ "copyWithin".into(),
628
+ "entries".into(),
629
+ "every".into(),
630
+ "fill".into(),
631
+ "filter".into(),
632
+ "find".into(),
633
+ "findIndex".into(),
634
+ "findLast".into(),
635
+ "findLastIndex".into(),
636
+ "flat".into(),
637
+ "flatMap".into(),
638
+ "forEach".into(),
639
+ "includes".into(),
640
+ "indexOf".into(),
641
+ "join".into(),
642
+ "keys".into(),
643
+ "lastIndexOf".into(),
644
+ "map".into(),
645
+ "pop".into(),
646
+ "push".into(),
647
+ "reduce".into(),
648
+ "reduceRight".into(),
649
+ "reverse".into(),
650
+ "shift".into(),
651
+ "slice".into(),
652
+ "some".into(),
653
+ "sort".into(),
654
+ "splice".into(),
655
+ "toLocaleString".into(),
656
+ "toReversed".into(),
657
+ "toSorted".into(),
658
+ "toSpliced".into(),
659
+ "toString".into(),
660
+ "unshift".into(),
661
+ "values".into(),
662
+ "shuffle".into(),
663
+ ]
664
+ }
665
+ Value::String(_) => {
666
+ vec![
667
+ "length".into(),
668
+ "charAt".into(),
669
+ "charCodeAt".into(),
670
+ "endsWith".into(),
671
+ "includes".into(),
672
+ "indexOf".into(),
673
+ "lastIndexOf".into(),
674
+ "padEnd".into(),
675
+ "padStart".into(),
676
+ "repeat".into(),
677
+ "replace".into(),
678
+ "replaceAll".into(),
679
+ "slice".into(),
680
+ "split".into(),
681
+ "startsWith".into(),
682
+ "substring".into(),
683
+ "toLowerCase".into(),
684
+ "toUpperCase".into(),
685
+ "trim".into(),
686
+ ]
687
+ }
688
+ Value::Number(_) => vec![
689
+ "toFixed".into(),
690
+ "toExponential".into(),
691
+ "toPrecision".into(),
692
+ ],
693
+ _ => vec![],
694
+ }
695
+ }
696
+ }