@tishlang/tish-format 1.0.12 → 2.0.1

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 (189) hide show
  1. package/Cargo.toml +51 -0
  2. package/LICENSE +13 -0
  3. package/bin/tish-format +0 -0
  4. package/crates/js_to_tish/Cargo.toml +11 -0
  5. package/crates/js_to_tish/README.md +18 -0
  6. package/crates/js_to_tish/src/error.rs +55 -0
  7. package/crates/js_to_tish/src/lib.rs +11 -0
  8. package/crates/js_to_tish/src/span_util.rs +35 -0
  9. package/crates/js_to_tish/src/transform/expr.rs +611 -0
  10. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  11. package/crates/js_to_tish/src/transform.rs +60 -0
  12. package/crates/tish/Cargo.toml +62 -0
  13. package/crates/tish/build.rs +21 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +576 -0
  16. package/crates/tish/src/main.rs +853 -0
  17. package/crates/tish/src/repl_completion.rs +199 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/error_source_location.rs +36 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  24. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  25. package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
  26. package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
  27. package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
  28. package/crates/tish/tests/integration_test.rs +1406 -0
  29. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  30. package/crates/tish/tests/shortcircuit.rs +65 -0
  31. package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
  32. package/crates/tish/tests/tty_capability.rs +43 -0
  33. package/crates/tish_ast/Cargo.toml +9 -0
  34. package/crates/tish_ast/src/ast.rs +649 -0
  35. package/crates/tish_ast/src/lib.rs +5 -0
  36. package/crates/tish_build_utils/Cargo.toml +11 -0
  37. package/crates/tish_build_utils/src/lib.rs +577 -0
  38. package/crates/tish_builtins/Cargo.toml +22 -0
  39. package/crates/tish_builtins/src/array.rs +803 -0
  40. package/crates/tish_builtins/src/collections.rs +481 -0
  41. package/crates/tish_builtins/src/construct.rs +199 -0
  42. package/crates/tish_builtins/src/date.rs +538 -0
  43. package/crates/tish_builtins/src/globals.rs +293 -0
  44. package/crates/tish_builtins/src/helpers.rs +35 -0
  45. package/crates/tish_builtins/src/iterator.rs +129 -0
  46. package/crates/tish_builtins/src/lib.rs +21 -0
  47. package/crates/tish_builtins/src/math.rs +89 -0
  48. package/crates/tish_builtins/src/number.rs +96 -0
  49. package/crates/tish_builtins/src/object.rs +36 -0
  50. package/crates/tish_builtins/src/string.rs +646 -0
  51. package/crates/tish_builtins/src/symbol.rs +83 -0
  52. package/crates/tish_builtins/src/typedarrays.rs +298 -0
  53. package/crates/tish_bytecode/Cargo.toml +17 -0
  54. package/crates/tish_bytecode/src/chunk.rs +164 -0
  55. package/crates/tish_bytecode/src/compiler.rs +2604 -0
  56. package/crates/tish_bytecode/src/encoding.rs +102 -0
  57. package/crates/tish_bytecode/src/lib.rs +20 -0
  58. package/crates/tish_bytecode/src/opcode.rs +185 -0
  59. package/crates/tish_bytecode/src/peephole.rs +189 -0
  60. package/crates/tish_bytecode/src/serialize.rs +193 -0
  61. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  62. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  63. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  64. package/crates/tish_compile/Cargo.toml +27 -0
  65. package/crates/tish_compile/src/check.rs +774 -0
  66. package/crates/tish_compile/src/codegen.rs +7317 -0
  67. package/crates/tish_compile/src/infer.rs +1681 -0
  68. package/crates/tish_compile/src/lib.rs +206 -0
  69. package/crates/tish_compile/src/resolve.rs +1951 -0
  70. package/crates/tish_compile/src/types.rs +605 -0
  71. package/crates/tish_compile_js/Cargo.toml +18 -0
  72. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  73. package/crates/tish_compile_js/src/codegen.rs +938 -0
  74. package/crates/tish_compile_js/src/error.rs +20 -0
  75. package/crates/tish_compile_js/src/lib.rs +26 -0
  76. package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
  77. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  78. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  79. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  80. package/crates/tish_core/Cargo.toml +32 -0
  81. package/crates/tish_core/src/console_style.rs +170 -0
  82. package/crates/tish_core/src/json.rs +430 -0
  83. package/crates/tish_core/src/lib.rs +20 -0
  84. package/crates/tish_core/src/macros.rs +36 -0
  85. package/crates/tish_core/src/shape.rs +85 -0
  86. package/crates/tish_core/src/uri.rs +118 -0
  87. package/crates/tish_core/src/value.rs +1350 -0
  88. package/crates/tish_core/src/vmref.rs +183 -0
  89. package/crates/tish_cranelift/Cargo.toml +19 -0
  90. package/crates/tish_cranelift/src/lib.rs +43 -0
  91. package/crates/tish_cranelift/src/link.rs +130 -0
  92. package/crates/tish_cranelift/src/lower.rs +85 -0
  93. package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
  94. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  95. package/crates/tish_eval/Cargo.toml +51 -0
  96. package/crates/tish_eval/src/eval.rs +4265 -0
  97. package/crates/tish_eval/src/http.rs +191 -0
  98. package/crates/tish_eval/src/lib.rs +99 -0
  99. package/crates/tish_eval/src/natives.rs +551 -0
  100. package/crates/tish_eval/src/promise.rs +179 -0
  101. package/crates/tish_eval/src/regex.rs +299 -0
  102. package/crates/tish_eval/src/timers.rs +120 -0
  103. package/crates/tish_eval/src/value.rs +336 -0
  104. package/crates/tish_eval/src/value_convert.rs +117 -0
  105. package/crates/tish_ffi/Cargo.toml +26 -0
  106. package/crates/tish_ffi/src/lib.rs +518 -0
  107. package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
  108. package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
  109. package/crates/tish_ffi/tests/loader.rs +65 -0
  110. package/crates/tish_fmt/Cargo.toml +16 -0
  111. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  112. package/crates/tish_fmt/src/lib.rs +2157 -0
  113. package/crates/tish_jsx_web/Cargo.toml +9 -0
  114. package/crates/tish_jsx_web/README.md +5 -0
  115. package/crates/tish_jsx_web/src/lib.rs +2 -0
  116. package/crates/tish_lexer/Cargo.toml +9 -0
  117. package/crates/tish_lexer/src/lib.rs +1104 -0
  118. package/crates/tish_lexer/src/token.rs +170 -0
  119. package/crates/tish_lint/Cargo.toml +18 -0
  120. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  121. package/crates/tish_lint/src/lib.rs +281 -0
  122. package/crates/tish_llvm/Cargo.toml +13 -0
  123. package/crates/tish_llvm/src/lib.rs +115 -0
  124. package/crates/tish_lsp/Cargo.toml +25 -0
  125. package/crates/tish_lsp/README.md +26 -0
  126. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  127. package/crates/tish_lsp/src/import_goto.rs +564 -0
  128. package/crates/tish_lsp/src/main.rs +1459 -0
  129. package/crates/tish_native/Cargo.toml +16 -0
  130. package/crates/tish_native/src/build.rs +481 -0
  131. package/crates/tish_native/src/config.rs +48 -0
  132. package/crates/tish_native/src/lib.rs +416 -0
  133. package/crates/tish_opt/Cargo.toml +13 -0
  134. package/crates/tish_opt/src/lib.rs +1046 -0
  135. package/crates/tish_parser/Cargo.toml +11 -0
  136. package/crates/tish_parser/src/lib.rs +386 -0
  137. package/crates/tish_parser/src/parser.rs +2726 -0
  138. package/crates/tish_pg/Cargo.toml +34 -0
  139. package/crates/tish_pg/README.md +38 -0
  140. package/crates/tish_pg/src/error.rs +52 -0
  141. package/crates/tish_pg/src/lib.rs +955 -0
  142. package/crates/tish_resolve/Cargo.toml +13 -0
  143. package/crates/tish_resolve/src/lib.rs +3601 -0
  144. package/crates/tish_resolve/src/pos.rs +141 -0
  145. package/crates/tish_runtime/Cargo.toml +100 -0
  146. package/crates/tish_runtime/src/http.rs +1347 -0
  147. package/crates/tish_runtime/src/http_fetch.rs +492 -0
  148. package/crates/tish_runtime/src/http_hyper.rs +441 -0
  149. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  150. package/crates/tish_runtime/src/lib.rs +1447 -0
  151. package/crates/tish_runtime/src/native_promise.rs +15 -0
  152. package/crates/tish_runtime/src/promise.rs +558 -0
  153. package/crates/tish_runtime/src/promise_io.rs +38 -0
  154. package/crates/tish_runtime/src/timers.rs +172 -0
  155. package/crates/tish_runtime/src/tty.rs +226 -0
  156. package/crates/tish_runtime/src/ws.rs +778 -0
  157. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  158. package/crates/tish_ui/Cargo.toml +17 -0
  159. package/crates/tish_ui/src/jsx.rs +692 -0
  160. package/crates/tish_ui/src/lib.rs +20 -0
  161. package/crates/tish_ui/src/runtime/hooks.rs +573 -0
  162. package/crates/tish_ui/src/runtime/mod.rs +183 -0
  163. package/crates/tish_vm/Cargo.toml +60 -0
  164. package/crates/tish_vm/src/jit.rs +1050 -0
  165. package/crates/tish_vm/src/lib.rs +41 -0
  166. package/crates/tish_vm/src/vm.rs +3536 -0
  167. package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
  168. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  169. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  170. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  171. package/crates/tish_wasm/Cargo.toml +15 -0
  172. package/crates/tish_wasm/src/lib.rs +428 -0
  173. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  174. package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
  175. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  176. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  177. package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
  178. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  179. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  180. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  181. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  182. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  183. package/justfile +276 -0
  184. package/package.json +2 -2
  185. package/platform/darwin-arm64/tish-fmt +0 -0
  186. package/platform/darwin-x64/tish-fmt +0 -0
  187. package/platform/linux-arm64/tish-fmt +0 -0
  188. package/platform/linux-x64/tish-fmt +0 -0
  189. package/platform/win32-x64/tish-fmt.exe +0 -0
@@ -0,0 +1,336 @@
1
+ //! Runtime values for the Tish interpreter.
2
+ //!
3
+ //! This module defines the interpreter's `Value` type, which includes variants
4
+ //! like `Function`, `Native`, and `Serve` that hold AST or interpreter-specific
5
+ //! data. The compiled runtime uses `tishlang_core::Value` instead, which has a
6
+ //! different shape (no AST-carrying variants). The split is intentional.
7
+
8
+ use std::cell::RefCell;
9
+ use std::rc::Rc;
10
+ use std::sync::Arc;
11
+
12
+ use ahash::AHashMap;
13
+ use tishlang_ast::{FunParam, Statement};
14
+ use tishlang_core::NativeFn as CoreNativeFn;
15
+ use tishlang_core::TishSymbol;
16
+
17
+ /// Property map for interpreter object string keys (uses `eval::Value`, not `tishlang_core::Value`).
18
+ /// `IndexMap` preserves insertion order so `Object.keys` / `JSON.stringify` match JS/Node
19
+ /// (and the VM/rust backends, which use `tishlang_core`'s insertion-ordered `PropMap`).
20
+ pub type PropMap = indexmap::IndexMap<Arc<str>, Value>;
21
+
22
+ /// Interpreter object: string keys plus optional symbol-keyed properties.
23
+ #[derive(Clone, Debug, Default)]
24
+ pub struct EvalObjectData {
25
+ pub strings: PropMap,
26
+ pub symbols: Option<AHashMap<u64, Value>>,
27
+ }
28
+
29
+ impl EvalObjectData {
30
+ pub fn from_strings(strings: PropMap) -> Self {
31
+ Self {
32
+ strings,
33
+ symbols: None,
34
+ }
35
+ }
36
+ }
37
+
38
+ pub fn eval_object_get(obj: &Value, key: &Value) -> Option<Value> {
39
+ let Value::Object(od) = obj else {
40
+ return None;
41
+ };
42
+ let b = od.borrow();
43
+ match key {
44
+ Value::Symbol(s) => b.symbols.as_ref()?.get(&s.id).cloned(),
45
+ Value::Number(n) => {
46
+ let k: Arc<str> = n.to_string().into();
47
+ b.strings.get(&k).cloned()
48
+ }
49
+ Value::String(k) => b.strings.get(k.as_ref()).cloned(),
50
+ _ => None,
51
+ }
52
+ }
53
+
54
+ pub fn eval_object_set(obj: &Value, key: &Value, val: Value) -> Result<(), String> {
55
+ let Value::Object(od) = obj else {
56
+ return Err("Cannot set property on non-object".to_string());
57
+ };
58
+ let mut b = od.borrow_mut();
59
+ match key {
60
+ Value::Symbol(s) => {
61
+ if b.symbols.is_none() {
62
+ b.symbols = Some(AHashMap::default());
63
+ }
64
+ b.symbols.as_mut().unwrap().insert(s.id, val);
65
+ Ok(())
66
+ }
67
+ Value::Number(n) => {
68
+ b.strings.insert(n.to_string().into(), val);
69
+ Ok(())
70
+ }
71
+ Value::String(k) => {
72
+ b.strings.insert(Arc::clone(k), val);
73
+ Ok(())
74
+ }
75
+ _ => Err("Object key must be string, number, or symbol".to_string()),
76
+ }
77
+ }
78
+
79
+ pub fn eval_object_has(obj: &Value, key: &Value) -> bool {
80
+ let Value::Object(od) = obj else {
81
+ return false;
82
+ };
83
+ let b = od.borrow();
84
+ match key {
85
+ Value::Symbol(s) => b.symbols.as_ref().is_some_and(|m| m.contains_key(&s.id)),
86
+ Value::Number(n) => {
87
+ let k: Arc<str> = n.to_string().into();
88
+ b.strings.contains_key(&k)
89
+ }
90
+ Value::String(k) => b.strings.contains_key(k.as_ref()),
91
+ _ => false,
92
+ }
93
+ }
94
+ use tishlang_core::TishOpaque;
95
+ #[cfg(feature = "http")]
96
+ use tishlang_core::TishPromise;
97
+
98
+ #[cfg(feature = "http")]
99
+ pub use crate::promise::PromiseResolver;
100
+ #[cfg(feature = "regex")]
101
+ pub use crate::regex::TishRegExp;
102
+
103
+ /// Native function type - takes args, returns Result<Value, String>
104
+ pub type NativeFn = fn(&[Value]) -> Result<Value, String>;
105
+
106
+ #[derive(Clone)]
107
+ pub enum Value {
108
+ Number(f64),
109
+ String(Arc<str>),
110
+ Bool(bool),
111
+ Null,
112
+ Array(Rc<RefCell<Vec<Value>>>),
113
+ Object(Rc<RefCell<EvalObjectData>>),
114
+ Symbol(Arc<TishSymbol>),
115
+ /// User-defined function with AST body. `env` is the lexical scope captured at definition
116
+ /// time, so the body resolves free variables against where it was DEFINED (a real closure),
117
+ /// not where it is called.
118
+ Function {
119
+ formals: Arc<[FunParam]>,
120
+ rest_param: Option<Arc<str>>,
121
+ body: Arc<Statement>,
122
+ env: crate::eval::ScopeRef,
123
+ },
124
+ /// Native/builtin function
125
+ Native(NativeFn),
126
+ /// HTTP serve function (needs special handling for callbacks)
127
+ #[cfg(feature = "http")]
128
+ Serve,
129
+ #[cfg(feature = "regex")]
130
+ RegExp(Rc<RefCell<TishRegExp>>),
131
+ /// Promise (ECMA-262 §27.2). Requires http feature for tokio.
132
+ #[cfg(feature = "http")]
133
+ Promise(crate::promise::PromiseRef),
134
+ /// Internal: resolve/reject functions passed to executor. Not user-facing.
135
+ #[cfg(feature = "http")]
136
+ PromiseResolver(PromiseResolver),
137
+ /// Promise constructor: Promise(executor). Requires special call handling.
138
+ #[cfg(feature = "http")]
139
+ PromiseConstructor,
140
+ /// Bound promise method: promise.then/catch/finally - captures the promise for the call.
141
+ #[cfg(feature = "http")]
142
+ BoundPromiseMethod(crate::promise::PromiseRef, std::sync::Arc<str>),
143
+ /// Timer builtins: setTimeout, setInterval. Need evaluator for callback.
144
+ #[cfg(feature = "timers")]
145
+ TimerBuiltin(std::sync::Arc<str>),
146
+ /// Native `tishlang_core` Promise (fetch / reader.read / response.text).
147
+ #[cfg(feature = "http")]
148
+ CorePromise(Arc<dyn TishPromise>),
149
+ /// `tishlang_core::Value::Function` (native callbacks, `new` constructors, fetch/ws when enabled).
150
+ CoreFn(CoreNativeFn),
151
+ /// Opaque handle to a native Rust type (e.g. Polars DataFrame).
152
+ Opaque(Arc<dyn TishOpaque>),
153
+ /// Bound method on an opaque value (opaque, method_name). Callable.
154
+ OpaqueMethod(Arc<dyn TishOpaque>, Arc<str>),
155
+ }
156
+
157
+ impl std::fmt::Debug for Value {
158
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159
+ match self {
160
+ Value::Number(n) => write!(f, "Number({})", n),
161
+ Value::String(s) => write!(f, "String({:?})", s.as_ref()),
162
+ Value::Bool(b) => write!(f, "Bool({})", b),
163
+ Value::Null => write!(f, "Null"),
164
+ Value::Array(arr) => write!(f, "Array({:?})", arr.borrow()),
165
+ Value::Object(obj) => write!(f, "Object({:?})", obj.borrow()),
166
+ Value::Function { .. } => write!(f, "Function"),
167
+ Value::Native(_) => write!(f, "Native"),
168
+ #[cfg(feature = "http")]
169
+ Value::Serve => write!(f, "Serve"),
170
+ #[cfg(feature = "regex")]
171
+ Value::RegExp(re) => write!(
172
+ f,
173
+ "RegExp(/{}/{})",
174
+ re.borrow().source,
175
+ re.borrow().flags_string()
176
+ ),
177
+ #[cfg(feature = "http")]
178
+ Value::Promise(_) => write!(f, "Promise"),
179
+ #[cfg(feature = "http")]
180
+ Value::PromiseResolver(_) => write!(f, "[PromiseResolver]"),
181
+ #[cfg(feature = "http")]
182
+ Value::PromiseConstructor => write!(f, "[Function: Promise]"),
183
+ #[cfg(feature = "http")]
184
+ Value::BoundPromiseMethod(_, _) => write!(f, "[Function]"),
185
+ #[cfg(feature = "timers")]
186
+ Value::TimerBuiltin(_) => write!(f, "[Function]"),
187
+ #[cfg(feature = "http")]
188
+ Value::CorePromise(_) => write!(f, "Promise"),
189
+ Value::CoreFn(_) => write!(f, "CoreFn"),
190
+ Value::Opaque(o) => write!(f, "{}(opaque)", o.type_name()),
191
+ Value::OpaqueMethod(_, _) => write!(f, "[Function]"),
192
+ Value::Symbol(s) => write!(f, "Symbol({})", s.id),
193
+ }
194
+ }
195
+ }
196
+
197
+ impl std::fmt::Display for Value {
198
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199
+ match self {
200
+ // Match JS `Number.prototype.toString` (exponential past digit 21 / before −6),
201
+ // shared with the VM/native path via `tishlang_core`.
202
+ Value::Number(n) => write!(f, "{}", tishlang_core::js_number_to_string(*n)),
203
+ Value::String(s) => write!(f, "{}", s),
204
+ Value::Bool(b) => write!(f, "{}", b),
205
+ Value::Null => write!(f, "null"),
206
+ Value::Array(arr) => {
207
+ let inner: Vec<String> = arr.borrow().iter().map(|v| v.to_string()).collect();
208
+ write!(f, "[{}]", inner.join(", "))
209
+ }
210
+ Value::Object(obj) => {
211
+ let inner: Vec<String> = obj
212
+ .borrow()
213
+ .strings
214
+ .iter()
215
+ .map(|(k, v)| format!("{}: {}", k.as_ref(), v))
216
+ .collect();
217
+ write!(f, "{{{}}}", inner.join(", "))
218
+ }
219
+ Value::Symbol(s) => {
220
+ if let Some(d) = &s.description {
221
+ write!(f, "Symbol({})", d)
222
+ } else {
223
+ write!(f, "Symbol()")
224
+ }
225
+ }
226
+ Value::Function { .. } => write!(f, "[Function]"),
227
+ Value::Native(_) => write!(f, "[NativeFunction]"),
228
+ #[cfg(feature = "http")]
229
+ Value::Serve => write!(f, "[NativeFunction: serve]"),
230
+ #[cfg(feature = "regex")]
231
+ Value::RegExp(re) => {
232
+ let re = re.borrow();
233
+ write!(f, "/{}/{}", re.source, re.flags_string())
234
+ }
235
+ #[cfg(feature = "http")]
236
+ Value::Promise(_) => write!(f, "[Promise]"),
237
+ #[cfg(feature = "http")]
238
+ Value::PromiseResolver(_) => write!(f, "[Function]"),
239
+ #[cfg(feature = "http")]
240
+ Value::PromiseConstructor => write!(f, "function Promise() {{ [native code] }}"),
241
+ #[cfg(feature = "http")]
242
+ Value::BoundPromiseMethod(_, _) => write!(f, "[Function]"),
243
+ #[cfg(feature = "timers")]
244
+ Value::TimerBuiltin(_) => write!(f, "[Function]"),
245
+ #[cfg(feature = "http")]
246
+ Value::CorePromise(_) => write!(f, "[Promise]"),
247
+ Value::CoreFn(_) => write!(f, "[Function]"),
248
+ Value::Opaque(o) => write!(f, "[object {}]", o.type_name()),
249
+ Value::OpaqueMethod(_, _) => write!(f, "[Function]"),
250
+ }
251
+ }
252
+ }
253
+
254
+ impl Value {
255
+ pub fn is_truthy(&self) -> bool {
256
+ match self {
257
+ Value::Null => false,
258
+ Value::Bool(b) => *b,
259
+ Value::Number(n) => *n != 0.0 && !n.is_nan(),
260
+ Value::String(s) => !s.is_empty(),
261
+ _ => true,
262
+ }
263
+ }
264
+
265
+ /// JavaScript `ToString` coercion (as used by `Array.prototype.join`), distinct from the
266
+ /// `Display`/inspect form: a nested **array** stringifies to its own comma-joined `toString`
267
+ /// (recursively, always `,`), an **object** becomes `"[object Object]"`, and `null`/`undefined`
268
+ /// elements elide to `""`. Mirrors `tishlang_core::Value::to_js_string` so interp output matches
269
+ /// the VM/rust/cranelift/wasi backends (and Node) for join/coercion.
270
+ pub fn to_js_string(&self) -> String {
271
+ match self {
272
+ Value::Array(arr) => arr
273
+ .borrow()
274
+ .iter()
275
+ .map(|v| match v {
276
+ Value::Null => String::new(),
277
+ other => other.to_js_string(),
278
+ })
279
+ .collect::<Vec<_>>()
280
+ .join(","),
281
+ Value::Object(_) => "[object Object]".to_string(),
282
+ _ => self.to_string(),
283
+ }
284
+ }
285
+
286
+ pub fn strict_eq(&self, other: &Value) -> bool {
287
+ match (self, other) {
288
+ (Value::Number(a), Value::Number(b)) => {
289
+ if a.is_nan() || b.is_nan() {
290
+ false
291
+ } else {
292
+ a == b
293
+ }
294
+ }
295
+ (Value::String(a), Value::String(b)) => a == b,
296
+ (Value::Bool(a), Value::Bool(b)) => a == b,
297
+ (Value::Null, Value::Null) => true,
298
+ (Value::Array(a), Value::Array(b)) => Rc::ptr_eq(a, b),
299
+ (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
300
+ (Value::Symbol(a), Value::Symbol(b)) => Arc::ptr_eq(a, b),
301
+ (Value::Opaque(a), Value::Opaque(b)) => Arc::ptr_eq(a, b),
302
+ (Value::OpaqueMethod(a, ak), Value::OpaqueMethod(b, bk)) => {
303
+ Arc::ptr_eq(a, b) && ak == bk
304
+ }
305
+ _ => false,
306
+ }
307
+ }
308
+
309
+ /// Create a new array Value from a Vec.
310
+ pub fn array(items: Vec<Value>) -> Self {
311
+ Value::Array(Rc::new(RefCell::new(items)))
312
+ }
313
+
314
+ /// Create a new object Value from a property map.
315
+ pub fn object(map: PropMap) -> Self {
316
+ Value::Object(Rc::new(RefCell::new(EvalObjectData::from_strings(map))))
317
+ }
318
+
319
+ /// Create an empty array Value.
320
+ pub fn empty_array() -> Self {
321
+ Value::Array(Rc::new(RefCell::new(Vec::new())))
322
+ }
323
+
324
+ /// Create an empty object Value.
325
+ pub fn empty_object() -> Self {
326
+ Value::Object(Rc::new(RefCell::new(EvalObjectData::default())))
327
+ }
328
+
329
+ /// Extract the number value, if this is a Number.
330
+ pub fn as_number(&self) -> Option<f64> {
331
+ match self {
332
+ Value::Number(n) => Some(*n),
333
+ _ => None,
334
+ }
335
+ }
336
+ }
@@ -0,0 +1,117 @@
1
+ //! Conversion between tishlang_eval::Value and tishlang_core::Value for opaque method calls.
2
+
3
+ use std::cell::RefCell;
4
+ use std::rc::Rc;
5
+ use std::sync::Arc;
6
+
7
+ #[cfg(feature = "regex")]
8
+ use tishlang_core::TishRegExp;
9
+ use ahash::AHashMap;
10
+ use tishlang_core::{ObjectData, Value as CoreValue, VmRef};
11
+
12
+ use crate::value::{EvalObjectData, PropMap, Value};
13
+
14
+ /// Convert interpreter Value to core Value. Fails for interpreter-only variants.
15
+ pub fn eval_to_core(v: &Value) -> Result<CoreValue, String> {
16
+ match v {
17
+ Value::Number(n) => Ok(CoreValue::Number(*n)),
18
+ Value::String(s) => Ok(CoreValue::String(tishlang_core::ArcStr::from(s.as_ref()))),
19
+ Value::Bool(b) => Ok(CoreValue::Bool(*b)),
20
+ Value::Null => Ok(CoreValue::Null),
21
+ Value::Array(arr) => {
22
+ let mut out = Vec::new();
23
+ for item in arr.borrow().iter() {
24
+ out.push(eval_to_core(item)?);
25
+ }
26
+ Ok(CoreValue::Array(VmRef::new(out)))
27
+ }
28
+ Value::Object(map) => {
29
+ let b = map.borrow();
30
+ let mut strings = tishlang_core::PropMap::default();
31
+ for (k, v) in b.strings.iter() {
32
+ strings.insert(Arc::clone(k), eval_to_core(v)?);
33
+ }
34
+ let symbols = if let Some(ss) = &b.symbols {
35
+ let mut sm = AHashMap::default();
36
+ for (id, v) in ss.iter() {
37
+ sm.insert(*id, eval_to_core(v)?);
38
+ }
39
+ Some(sm)
40
+ } else {
41
+ None
42
+ };
43
+ Ok(CoreValue::Object(VmRef::new(ObjectData { strings, symbols })))
44
+ }
45
+ Value::Symbol(s) => Ok(CoreValue::Symbol(Arc::clone(s))),
46
+ Value::Opaque(o) => Ok(CoreValue::Opaque(Arc::clone(o))),
47
+ _ => Err(format!(
48
+ "Cannot pass {:?} to native function (unsupported type)",
49
+ std::mem::discriminant(v)
50
+ )),
51
+ }
52
+ }
53
+
54
+ /// Convert core Value to interpreter Value.
55
+ pub fn core_to_eval(v: CoreValue) -> Value {
56
+ match v {
57
+ CoreValue::Number(n) => Value::Number(n),
58
+ CoreValue::String(s) => Value::String(Arc::from(s.as_str())),
59
+ CoreValue::Bool(b) => Value::Bool(b),
60
+ CoreValue::Null => Value::Null,
61
+ CoreValue::Array(arr) => {
62
+ let mut out = Vec::new();
63
+ for item in arr.borrow().iter() {
64
+ out.push(core_to_eval(item.clone()));
65
+ }
66
+ Value::Array(Rc::new(RefCell::new(out)))
67
+ }
68
+ CoreValue::Object(map) => {
69
+ let b = map.borrow();
70
+ let mut out = PropMap::default();
71
+ for (k, v) in b.strings.iter() {
72
+ out.insert(Arc::clone(k), core_to_eval(v.clone()));
73
+ }
74
+ let mut eod = EvalObjectData::from_strings(out);
75
+ if let Some(ss) = &b.symbols {
76
+ let mut es = AHashMap::default();
77
+ for (id, v) in ss.iter() {
78
+ es.insert(*id, core_to_eval(v.clone()));
79
+ }
80
+ eod.symbols = Some(es);
81
+ }
82
+ Value::Object(Rc::new(RefCell::new(eod)))
83
+ }
84
+ CoreValue::Symbol(s) => Value::Symbol(Arc::clone(&s)),
85
+ CoreValue::Opaque(o) => Value::Opaque(o),
86
+ #[cfg(feature = "http")]
87
+ CoreValue::Promise(p) => Value::CorePromise(Arc::clone(&p)),
88
+ #[cfg(not(feature = "http"))]
89
+ CoreValue::Promise(_) => Value::Null,
90
+ // NumberArray: materialize to boxed Array for the interpreter (it has no packed path).
91
+ CoreValue::NumberArray(arr) => {
92
+ let nums = arr.borrow();
93
+ let out: Vec<Value> = nums.iter().map(|&n| Value::Number(n)).collect();
94
+ Value::Array(Rc::new(RefCell::new(out)))
95
+ }
96
+ // `CoreNativeFn` is feature-gated (Rc vs Arc), so use Clone::clone
97
+ // which works for either.
98
+ CoreValue::Function(f) => Value::CoreFn(f.clone()),
99
+ // tishlang_core gets regex from http or regex features; handle RegExp when it exists
100
+ #[cfg(any(feature = "http", feature = "regex"))]
101
+ CoreValue::RegExp(re) => {
102
+ #[cfg(feature = "regex")]
103
+ {
104
+ // Core uses `VmRef<TishRegExp>` (potentially `Arc<Mutex>`),
105
+ // interpreter uses `Rc<RefCell<TishRegExp>>`. Clone the
106
+ // inner state across so the two storage shapes can coexist.
107
+ let inner: TishRegExp = re.borrow().clone();
108
+ Value::RegExp(Rc::new(RefCell::new(inner)))
109
+ }
110
+ #[cfg(not(feature = "regex"))]
111
+ {
112
+ let _ = re;
113
+ Value::Null
114
+ }
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,26 @@
1
+ [package]
2
+ name = "tishlang_ffi"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "Stable C ABI for tish native extensions — opaque value handles + extern \"C\" accessors so a cdylib (native) or wasm host-import module can interoperate with any tish backend without sharing tish_core's Rust types. Workstream B (full-backend-parity-plan.md)."
6
+
7
+ license-file = { workspace = true }
8
+ repository = { workspace = true }
9
+
10
+ [features]
11
+ default = []
12
+ # An extension that LINKS tishlang_core must match the host's value representation/layout exactly.
13
+ # `send-values` flips arrays/objects to Arc<Mutex<…>>; `regex` adds the `Value::RegExp` variant
14
+ # (which changes the enum's niche/discriminant layout). The shipped `tish` (default="full") enables
15
+ # both, so a linked extension targeting it must too. (The decoupled, host-exported-accessor model
16
+ # avoids all of this — there is then a single tish_core.)
17
+ send-values = ["tishlang_core/send-values"]
18
+ regex = ["tishlang_core/regex"]
19
+
20
+ [dependencies]
21
+ tishlang_core = { path = "../tish_core", version = ">=0.1" }
22
+
23
+ # Native cdylib loading (B2). Not for wasm — there is no dlopen in wasm; wasi resolves the SAME
24
+ # handle+accessor contract via host imports (B4), so the loader is gated to non-wasm targets.
25
+ [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
26
+ libloading = "0.8"