@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,293 @@
1
+ //! Global builtin functions with signature (args: &[Value]) -> Value.
2
+ //!
3
+ //! Used by both tishlang_vm (bytecode) and tishlang_runtime (compiled). Keeps tishlang_vm
4
+ //! independent of tishlang_runtime.
5
+
6
+ use std::sync::Arc;
7
+ use tishlang_core::VmRef;
8
+ use tishlang_core::{percent_decode, percent_encode, ObjectMap, Value};
9
+
10
+ /// Boolean(value) - coerce to bool
11
+ pub fn boolean(args: &[Value]) -> Value {
12
+ let v = args.first().unwrap_or(&Value::Null);
13
+ Value::Bool(v.is_truthy())
14
+ }
15
+
16
+ /// decodeURI(str)
17
+ pub fn decode_uri(args: &[Value]) -> Value {
18
+ let s = args
19
+ .first()
20
+ .map(Value::to_display_string)
21
+ .unwrap_or_default();
22
+ Value::String(percent_decode(&s).unwrap_or(s).into())
23
+ }
24
+
25
+ /// encodeURI(str)
26
+ pub fn encode_uri(args: &[Value]) -> Value {
27
+ let s = args
28
+ .first()
29
+ .map(Value::to_display_string)
30
+ .unwrap_or_default();
31
+ Value::String(percent_encode(&s).into())
32
+ }
33
+
34
+ /// isFinite(value)
35
+ pub fn is_finite(args: &[Value]) -> Value {
36
+ Value::Bool(
37
+ args.first()
38
+ .is_some_and(|v| matches!(v, Value::Number(n) if n.is_finite())),
39
+ )
40
+ }
41
+
42
+ /// isNaN(value)
43
+ pub fn is_nan(args: &[Value]) -> Value {
44
+ Value::Bool(args.first().is_none_or(|v| {
45
+ matches!(v, Value::Number(n) if n.is_nan()) || !matches!(v, Value::Number(_))
46
+ }))
47
+ }
48
+
49
+ /// Array.isArray(value)
50
+ pub fn array_is_array(args: &[Value]) -> Value {
51
+ Value::Bool(matches!(args.first(), Some(Value::Array(_)) | Some(Value::NumberArray(_))))
52
+ }
53
+
54
+ /// String(value) — convert value to string (JS String constructor as function).
55
+ /// Uses JS `ToString` (arrays comma-join recursively, objects → "[object Object]"),
56
+ /// not the inspect/display form.
57
+ pub fn string_convert(args: &[Value]) -> Value {
58
+ let v = args.first().unwrap_or(&Value::Null);
59
+ Value::String(v.to_js_string().into())
60
+ }
61
+
62
+ /// JS `Number(value)` coercion (ToNumber), issue #36. Numbers pass through; booleans →
63
+ /// 1/0; null → 0; strings parse (trimmed, with `0x`/`0b`/`0o` and `Infinity`, `""` → 0,
64
+ /// otherwise NaN); arrays/objects go via their string form (so `Number([5])` → 5,
65
+ /// `Number([])` → 0, objects → NaN).
66
+ pub fn number_convert(args: &[Value]) -> Value {
67
+ let v = args.first().unwrap_or(&Value::Null);
68
+ let n = match v {
69
+ Value::Number(n) => *n,
70
+ Value::Bool(b) => {
71
+ if *b {
72
+ 1.0
73
+ } else {
74
+ 0.0
75
+ }
76
+ }
77
+ Value::Null => 0.0,
78
+ Value::String(s) => parse_numeric_string(s),
79
+ other => parse_numeric_string(&other.to_js_string()),
80
+ };
81
+ Value::Number(n)
82
+ }
83
+
84
+ /// Parse a string as JS `Number` does: trimmed; `""` → 0; `0x`/`0o`/`0b` radix prefixes;
85
+ /// `Infinity`/`-Infinity`; plain decimal/float; anything else → NaN. Public so the
86
+ /// tree-walk interpreter (distinct `Value` type) shares the exact coercion.
87
+ pub fn parse_numeric_string(s: &str) -> f64 {
88
+ let t = s.trim();
89
+ if t.is_empty() {
90
+ return 0.0;
91
+ }
92
+ let radix = |rest: &str, r: u32| i64::from_str_radix(rest, r).map(|x| x as f64).unwrap_or(f64::NAN);
93
+ if let Some(rest) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
94
+ return radix(rest, 16);
95
+ }
96
+ if let Some(rest) = t.strip_prefix("0o").or_else(|| t.strip_prefix("0O")) {
97
+ return radix(rest, 8);
98
+ }
99
+ if let Some(rest) = t.strip_prefix("0b").or_else(|| t.strip_prefix("0B")) {
100
+ return radix(rest, 2);
101
+ }
102
+ match t {
103
+ "Infinity" | "+Infinity" => f64::INFINITY,
104
+ "-Infinity" => f64::NEG_INFINITY,
105
+ _ => t.parse::<f64>().unwrap_or(f64::NAN),
106
+ }
107
+ }
108
+
109
+ /// String.fromCharCode(...codes)
110
+ pub fn string_from_char_code(args: &[Value]) -> Value {
111
+ let s: String = args
112
+ .iter()
113
+ .filter_map(|v| match v {
114
+ Value::Number(n) => char::from_u32(*n as u32),
115
+ _ => None,
116
+ })
117
+ .collect();
118
+ Value::String(s.into())
119
+ }
120
+
121
+ /// Object.keys(obj)
122
+ pub fn object_keys(args: &[Value]) -> Value {
123
+ if let Some(Value::Object(obj)) = args.first() {
124
+ let obj_borrow = obj.borrow();
125
+ let keys: Vec<Value> = obj_borrow
126
+ .strings
127
+ .keys()
128
+ .map(|k| Value::String(tishlang_core::ArcStr::from(k.as_ref())))
129
+ .collect();
130
+ Value::Array(VmRef::new(keys))
131
+ } else {
132
+ Value::Array(VmRef::new(Vec::new()))
133
+ }
134
+ }
135
+
136
+ /// Object.values(obj)
137
+ pub fn object_values(args: &[Value]) -> Value {
138
+ if let Some(Value::Object(obj)) = args.first() {
139
+ let obj_borrow = obj.borrow();
140
+ let values: Vec<Value> = obj_borrow.strings.values().cloned().collect();
141
+ Value::Array(VmRef::new(values))
142
+ } else {
143
+ Value::Array(VmRef::new(Vec::new()))
144
+ }
145
+ }
146
+
147
+ /// Object.entries(obj)
148
+ pub fn object_entries(args: &[Value]) -> Value {
149
+ if let Some(Value::Object(obj)) = args.first() {
150
+ let obj_borrow = obj.borrow();
151
+ let entries: Vec<Value> = obj_borrow
152
+ .strings
153
+ .iter()
154
+ .map(|(k, v)| Value::Array(VmRef::new(vec![Value::String(tishlang_core::ArcStr::from(k.as_ref())), v.clone()])))
155
+ .collect();
156
+ Value::Array(VmRef::new(entries))
157
+ } else {
158
+ Value::Array(VmRef::new(Vec::new()))
159
+ }
160
+ }
161
+
162
+ /// Object.assign(target, ...sources)
163
+ pub fn object_assign(args: &[Value]) -> Value {
164
+ let target = match args.first() {
165
+ Some(Value::Object(obj)) => obj,
166
+ _ => return Value::Null,
167
+ };
168
+
169
+ let additional_capacity: usize = args
170
+ .iter()
171
+ .skip(1)
172
+ .map(|source| {
173
+ if let Value::Object(src) = source {
174
+ src.borrow().len_entries()
175
+ } else {
176
+ 0
177
+ }
178
+ })
179
+ .sum();
180
+
181
+ let mut target_mut = target.borrow_mut();
182
+ target_mut.strings.reserve(additional_capacity);
183
+
184
+ for source in args.iter().skip(1) {
185
+ if let Value::Object(src) = source {
186
+ let src_borrow = src.borrow();
187
+ for (k, v) in src_borrow.strings.iter() {
188
+ target_mut.strings.insert(Arc::clone(k), v.clone());
189
+ }
190
+ if let Some(ss) = &src_borrow.symbols {
191
+ if target_mut.symbols.is_none() {
192
+ target_mut.symbols = Some(Default::default());
193
+ }
194
+ let dst = target_mut.symbols.as_mut().unwrap();
195
+ dst.extend(ss.iter().map(|(id, v)| (*id, v.clone())));
196
+ }
197
+ }
198
+ }
199
+ drop(target_mut);
200
+ Value::Object(target.clone())
201
+ }
202
+
203
+ /// parseInt(string, radix?)
204
+ pub fn parse_int(args: &[Value]) -> Value {
205
+ let s = args
206
+ .first()
207
+ .map(Value::to_display_string)
208
+ .unwrap_or_default();
209
+ let s = s.trim();
210
+ let radix = args
211
+ .get(1)
212
+ .and_then(|v| match v {
213
+ Value::Number(n) => Some(*n as i32),
214
+ _ => None,
215
+ })
216
+ .unwrap_or(10);
217
+
218
+ if (2..=36).contains(&radix) {
219
+ let prefix: String = s
220
+ .chars()
221
+ .take_while(|c| *c == '-' || *c == '+' || c.is_digit(radix as u32))
222
+ .collect();
223
+ if let Ok(n) = i64::from_str_radix(&prefix, radix as u32) {
224
+ return Value::Number(n as f64);
225
+ }
226
+ }
227
+ Value::Number(f64::NAN)
228
+ }
229
+
230
+ /// parseFloat(string)
231
+ pub fn parse_float(args: &[Value]) -> Value {
232
+ let s = args
233
+ .first()
234
+ .map(Value::to_display_string)
235
+ .unwrap_or_default();
236
+ Value::Number(js_parse_float(&s))
237
+ }
238
+
239
+ /// JS `parseFloat`: skips leading whitespace, then parses the **longest leading prefix**
240
+ /// that's a valid float (so `parseFloat("3.14abc")` → 3.14, `parseFloat("12.3.4")` → 12.3).
241
+ /// Handles `Infinity`/`-Infinity`; returns NaN when no numeric prefix is present. Issue #36.
242
+ /// Public so the tree-walk interpreter (distinct `Value`) shares the exact behavior.
243
+ pub fn js_parse_float(s: &str) -> f64 {
244
+ let t = s.trim_start();
245
+ if t.starts_with("Infinity") || t.starts_with("+Infinity") {
246
+ return f64::INFINITY;
247
+ }
248
+ if t.starts_with("-Infinity") {
249
+ return f64::NEG_INFINITY;
250
+ }
251
+ // Take a generous run of float-shaped chars, then shrink from the right until it parses.
252
+ let mut end = 0;
253
+ for (i, c) in t.char_indices() {
254
+ if c.is_ascii_digit() || matches!(c, '.' | '+' | '-' | 'e' | 'E') {
255
+ end = i + c.len_utf8();
256
+ } else {
257
+ break;
258
+ }
259
+ }
260
+ let mut slice = &t[..end];
261
+ while !slice.is_empty() {
262
+ if let Ok(n) = slice.parse::<f64>() {
263
+ return n;
264
+ }
265
+ slice = &slice[..slice.len() - 1];
266
+ }
267
+ f64::NAN
268
+ }
269
+
270
+ /// Object.fromEntries(entries)
271
+ pub fn object_from_entries(args: &[Value]) -> Value {
272
+ if let Some(Value::Array(entries)) = args.first() {
273
+ let entries_borrow = entries.borrow();
274
+ let mut obj: ObjectMap = ObjectMap::with_capacity(entries_borrow.len());
275
+
276
+ for entry in entries_borrow.iter() {
277
+ if let Value::Array(pair) = entry {
278
+ let pair_borrow = pair.borrow();
279
+ if pair_borrow.len() >= 2 {
280
+ let key: Arc<str> = match &pair_borrow[0] {
281
+ Value::String(s) => Arc::from(s.as_str()),
282
+ v => v.to_display_string().into(),
283
+ };
284
+ obj.insert(key, pair_borrow[1].clone());
285
+ }
286
+ }
287
+ }
288
+
289
+ Value::object(obj)
290
+ } else {
291
+ Value::empty_object()
292
+ }
293
+ }
@@ -0,0 +1,35 @@
1
+ //! Common helper functions used across builtin implementations.
2
+
3
+ use std::sync::Arc;
4
+ use tishlang_core::{ObjectMap, Value};
5
+
6
+ /// Normalize an array index, handling negative indices.
7
+ /// Returns a valid index within bounds or the default value.
8
+ pub fn normalize_index(idx: &Value, len: i64, default: usize) -> usize {
9
+ match idx {
10
+ Value::Number(n) => {
11
+ let n = *n as i64;
12
+ if n < 0 {
13
+ (len + n).max(0) as usize
14
+ } else {
15
+ n.min(len) as usize
16
+ }
17
+ }
18
+ _ => default,
19
+ }
20
+ }
21
+
22
+ /// Create an error object with a single "error" field.
23
+ pub fn make_error_value(e: impl std::fmt::Display) -> Value {
24
+ let mut obj = ObjectMap::with_capacity(1);
25
+ obj.insert(Arc::from("error"), Value::String(e.to_string().into()));
26
+ Value::object(obj)
27
+ }
28
+
29
+ /// Extract a number from a Value, returning None for non-numbers.
30
+ pub fn extract_num(v: Option<&Value>) -> Option<f64> {
31
+ v.and_then(|val| match val {
32
+ Value::Number(n) => Some(*n),
33
+ _ => None,
34
+ })
35
+ }
@@ -0,0 +1,129 @@
1
+ //! JS-style iterator objects for `Map`/`Set` (`.values()` / `.keys()` / `.entries()`).
2
+ //!
3
+ //! Like [`crate::collections`] and [`crate::date`], an iterator is a plain `Value::Object`
4
+ //! whose `next` method is a per-instance `Value::native` closure capturing a snapshot of the
5
+ //! items plus a shared position cell — so the one implementation works on every backend with
6
+ //! no new `Value` variant. `next()` returns `{ value, done }`; once exhausted it keeps
7
+ //! returning `{ value: null, done: true }`.
8
+ //!
9
+ //! The runtimes drive iteration through [`tishlang_core::drain_iterator`], which calls `next()`
10
+ //! until `done` — that's how these objects work in `for…of`, spread, and `Array.from`. The
11
+ //! snapshot is taken when the iterator is built (`.values()` is called), matching how the old
12
+ //! array-returning version behaved; mid-iteration mutation of the source is not reflected (a
13
+ //! live iterator is a follow-up).
14
+
15
+ use std::sync::Arc;
16
+
17
+ use tishlang_core::{ObjectMap, Value, VmRef};
18
+
19
+ /// Build a single-use iterator object over `items`: `{ next() -> { value, done } }`.
20
+ pub fn array_iterator(items: Vec<Value>) -> Value {
21
+ let items: VmRef<Vec<Value>> = VmRef::new(items);
22
+ let pos: VmRef<usize> = VmRef::new(0);
23
+
24
+ let mut m = ObjectMap::default();
25
+ {
26
+ // Bulk-drain fast path for `for…of` / spread: return all REMAINING items (from the current
27
+ // position) as one array and exhaust the iterator — equivalent to calling `next()` until
28
+ // `done`, but with no per-element `{ value, done }` allocation. `drain_iterator` prefers this
29
+ // when present; manual `.next()` and partial consumption still work (it respects `pos`).
30
+ let items = items.clone();
31
+ let pos = pos.clone();
32
+ m.insert(
33
+ Arc::from("__drain__"),
34
+ Value::native(move |_args: &[Value]| {
35
+ let i = *pos.borrow();
36
+ let b = items.borrow();
37
+ let rest: Vec<Value> = if i < b.len() { b[i..].to_vec() } else { Vec::new() };
38
+ let len = b.len();
39
+ drop(b);
40
+ *pos.borrow_mut() = len;
41
+ Value::Array(VmRef::new(rest))
42
+ }),
43
+ );
44
+ }
45
+ {
46
+ let items = items.clone();
47
+ let pos = pos.clone();
48
+ m.insert(
49
+ Arc::from("next"),
50
+ Value::native(move |_args: &[Value]| {
51
+ let i = *pos.borrow();
52
+ // Read the element (or note exhaustion) without holding the items borrow
53
+ // across the position write — two different `VmRef`s, but keep it tidy.
54
+ let (value, done) = {
55
+ let b = items.borrow();
56
+ if i < b.len() {
57
+ (b[i].clone(), false)
58
+ } else {
59
+ (Value::Null, true)
60
+ }
61
+ };
62
+ if !done {
63
+ *pos.borrow_mut() = i + 1;
64
+ }
65
+ let mut r = ObjectMap::default();
66
+ r.insert(Arc::from("value"), value);
67
+ r.insert(Arc::from("done"), Value::Bool(done));
68
+ Value::object(r)
69
+ }),
70
+ );
71
+ }
72
+ Value::object(m)
73
+ }
74
+
75
+ #[cfg(test)]
76
+ mod tests {
77
+ use super::*;
78
+
79
+ fn call(obj: &Value, name: &str) -> Value {
80
+ let Value::Object(o) = obj else { panic!("not an object") };
81
+ let m = o.borrow().strings.get(name).cloned().expect("method missing");
82
+ let Value::Function(f) = m else { panic!("{name} is not callable") };
83
+ f.call(&[])
84
+ }
85
+ fn get(obj: &Value, key: &str) -> Value {
86
+ let Value::Object(o) = obj else { return Value::Null };
87
+ o.borrow().strings.get(key).cloned().unwrap_or(Value::Null)
88
+ }
89
+ fn num(v: &Value) -> f64 {
90
+ match v {
91
+ Value::Number(n) => *n,
92
+ _ => f64::NAN,
93
+ }
94
+ }
95
+ fn nums(v: &Value) -> Vec<f64> {
96
+ match v {
97
+ Value::Array(a) => a.borrow().iter().map(num).collect(),
98
+ _ => vec![],
99
+ }
100
+ }
101
+
102
+ #[test]
103
+ fn next_yields_each_then_done() {
104
+ let it = array_iterator(vec![Value::Number(1.0), Value::Number(2.0)]);
105
+ let r1 = call(&it, "next");
106
+ assert_eq!(num(&get(&r1, "value")), 1.0);
107
+ assert!(!get(&r1, "done").is_truthy());
108
+ assert_eq!(num(&get(&call(&it, "next"), "value")), 2.0);
109
+ // exhausted — and it keeps reporting done.
110
+ assert!(get(&call(&it, "next"), "done").is_truthy());
111
+ assert!(get(&call(&it, "next"), "done").is_truthy());
112
+ }
113
+
114
+ #[test]
115
+ fn drain_returns_all_and_exhausts() {
116
+ let it = array_iterator(vec![Value::Number(1.0), Value::Number(2.0), Value::Number(3.0)]);
117
+ assert_eq!(nums(&call(&it, "__drain__")), vec![1.0, 2.0, 3.0]);
118
+ // draining exhausts the iterator (matches calling next() until done).
119
+ assert!(get(&call(&it, "next"), "done").is_truthy());
120
+ }
121
+
122
+ #[test]
123
+ fn drain_respects_current_position() {
124
+ let it = array_iterator(vec![Value::Number(1.0), Value::Number(2.0), Value::Number(3.0)]);
125
+ call(&it, "next"); // consume the first
126
+ assert_eq!(nums(&call(&it, "__drain__")), vec![2.0, 3.0]);
127
+ assert_eq!(nums(&call(&it, "__drain__")), Vec::<f64>::new()); // already drained
128
+ }
129
+ }
@@ -0,0 +1,21 @@
1
+ //! Shared builtin implementations for Tish.
2
+ //!
3
+ //! Used by the compiled runtime (tishlang_runtime) and bytecode VM (tishlang_vm). The
4
+ //! interpreter (tishlang_eval) implements builtins inline due to different Value
5
+ //! and native signatures.
6
+
7
+ pub mod array;
8
+ pub mod collections;
9
+ pub mod construct;
10
+ pub mod date;
11
+ pub mod globals;
12
+ pub mod helpers;
13
+ pub mod iterator;
14
+ pub mod math;
15
+ pub mod number;
16
+ pub mod object;
17
+ pub mod string;
18
+ pub mod symbol;
19
+ pub mod typedarrays;
20
+
21
+ pub use tishlang_core::Value;
@@ -0,0 +1,89 @@
1
+ //! Math builtin functions.
2
+
3
+ use crate::helpers::extract_num;
4
+ use tishlang_core::Value;
5
+
6
+ macro_rules! math_unary {
7
+ ($name:ident, $op:ident) => {
8
+ pub fn $name(args: &[Value]) -> Value {
9
+ let n = extract_num(args.first()).unwrap_or(f64::NAN);
10
+ Value::Number(n.$op())
11
+ }
12
+ };
13
+ }
14
+
15
+ math_unary!(abs, abs);
16
+ math_unary!(sqrt, sqrt);
17
+ math_unary!(floor, floor);
18
+ math_unary!(ceil, ceil);
19
+ math_unary!(round, round);
20
+ math_unary!(sin, sin);
21
+ math_unary!(cos, cos);
22
+ math_unary!(tan, tan);
23
+ math_unary!(asin, asin);
24
+ math_unary!(acos, acos);
25
+ math_unary!(atan, atan);
26
+ math_unary!(log, ln);
27
+ math_unary!(log10, log10);
28
+ math_unary!(log2, log2);
29
+ math_unary!(exp, exp);
30
+ math_unary!(trunc, trunc);
31
+ math_unary!(cbrt, cbrt);
32
+
33
+ pub fn min(args: &[Value]) -> Value {
34
+ let n = args
35
+ .iter()
36
+ .filter_map(|v| extract_num(Some(v)))
37
+ .fold(f64::INFINITY, f64::min);
38
+ Value::Number(if n == f64::INFINITY { f64::NAN } else { n })
39
+ }
40
+
41
+ pub fn max(args: &[Value]) -> Value {
42
+ let n = args
43
+ .iter()
44
+ .filter_map(|v| extract_num(Some(v)))
45
+ .fold(f64::NEG_INFINITY, f64::max);
46
+ Value::Number(if n == f64::NEG_INFINITY { f64::NAN } else { n })
47
+ }
48
+
49
+ pub fn pow(args: &[Value]) -> Value {
50
+ let base = extract_num(args.first()).unwrap_or(f64::NAN);
51
+ let exp = extract_num(args.get(1)).unwrap_or(f64::NAN);
52
+ Value::Number(base.powf(exp))
53
+ }
54
+
55
+ pub fn random(_args: &[Value]) -> Value {
56
+ Value::Number(rand::random::<f64>())
57
+ }
58
+
59
+ pub fn sign(args: &[Value]) -> Value {
60
+ let n = extract_num(args.first()).unwrap_or(f64::NAN);
61
+ Value::Number(if n.is_nan() {
62
+ f64::NAN
63
+ } else if n > 0.0 {
64
+ 1.0
65
+ } else if n < 0.0 {
66
+ -1.0
67
+ } else {
68
+ 0.0
69
+ })
70
+ }
71
+
72
+ pub fn atan2(args: &[Value]) -> Value {
73
+ let y = extract_num(args.first()).unwrap_or(f64::NAN);
74
+ let x = extract_num(args.get(1)).unwrap_or(f64::NAN);
75
+ Value::Number(y.atan2(x))
76
+ }
77
+
78
+ pub fn hypot(args: &[Value]) -> Value {
79
+ let x = extract_num(args.first()).unwrap_or(0.0);
80
+ let y = extract_num(args.get(1)).unwrap_or(0.0);
81
+ Value::Number(x.hypot(y))
82
+ }
83
+
84
+ /// ES6 `Math.imul`: 32-bit integer multiply (used by xmur3 PRNG in juke-cards).
85
+ pub fn imul(args: &[Value]) -> Value {
86
+ let a = extract_num(args.first()).unwrap_or(0.0) as i32;
87
+ let b = extract_num(args.get(1)).unwrap_or(0.0) as i32;
88
+ Value::Number(a.wrapping_mul(b) as f64)
89
+ }
@@ -0,0 +1,96 @@
1
+ //! Number builtin methods.
2
+ //!
3
+ //! Canonical, backend-agnostic implementations of `Number.prototype` methods.
4
+ //! The VM (`get_member`), the Rust runtime (`tishlang_runtime::number_to_fixed`),
5
+ //! and the tree-walk interpreter all route through here so every backend produces
6
+ //! byte-identical output — see `tish/docs/full-backend-parity-plan.md` (Workstream A).
7
+
8
+ use tishlang_core::Value;
9
+
10
+ /// `Number.prototype.toFixed(digits)` — ECMA-262 §21.1.3.3.
11
+ ///
12
+ /// Formats the number using fixed-point notation with `digits` fraction digits.
13
+ /// `digits` is clamped to 0–20 (ECMA range) and defaults to 0 when absent/non-numeric,
14
+ /// matching `(1.5).toFixed() === "2"`. A non-number receiver yields `"NaN"`.
15
+ pub fn to_fixed(n: &Value, digits: &Value) -> Value {
16
+ let num = match n {
17
+ Value::Number(x) => *x,
18
+ _ => f64::NAN,
19
+ };
20
+ let d = match digits {
21
+ Value::Number(x) => (*x as i32).clamp(0, 20),
22
+ _ => 0,
23
+ } as usize;
24
+ Value::String(format!("{:.*}", d, num).into())
25
+ }
26
+
27
+ /// `Number.prototype.toString([radix])` — ECMA-262 §21.1.3.6.
28
+ ///
29
+ /// Radix defaults to 10 (canonical JS number formatting). For radix 2–36 the value is
30
+ /// rendered in that base: sign, integer part via repeated division, and a fractional part
31
+ /// (bounded to 52 digits, like V8). NaN / ±Infinity stringify as in base 10 regardless of
32
+ /// radix. An out-of-range radix yields `"RadixError"` so the caller can surface a RangeError.
33
+ pub fn to_string(n: &Value, radix: &Value) -> Value {
34
+ let num = match n {
35
+ Value::Number(x) => *x,
36
+ _ => f64::NAN,
37
+ };
38
+ let r = match radix {
39
+ Value::Number(x) => *x as i64,
40
+ _ => 10,
41
+ };
42
+ match number_to_string_radix(num, r) {
43
+ Some(s) => Value::String(s.into()),
44
+ None => Value::String("RadixError".into()),
45
+ }
46
+ }
47
+
48
+ /// Backend-agnostic core of `Number.prototype.toString`: works on a plain `f64` so the
49
+ /// tree-walk interpreter (whose `Value` is a distinct type) can share the exact same
50
+ /// formatting. Returns `None` for an out-of-range radix (caller surfaces a RangeError).
51
+ pub fn number_to_string_radix(num: f64, radix: i64) -> Option<String> {
52
+ if !(2..=36).contains(&radix) {
53
+ return None;
54
+ }
55
+ if radix == 10 || num.is_nan() || num.is_infinite() {
56
+ return Some(tishlang_core::js_number_to_string(num));
57
+ }
58
+ let radix = radix as u32;
59
+ const DIGITS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
60
+ let negative = num < 0.0;
61
+ let value = num.abs();
62
+ let int_part = value.trunc();
63
+ let mut frac = value - int_part;
64
+
65
+ // Integer part: collect base-`radix` digits least-significant first, then reverse.
66
+ let mut int_digits = Vec::new();
67
+ let mut i = int_part;
68
+ if i == 0.0 {
69
+ int_digits.push(b'0');
70
+ }
71
+ while i >= 1.0 {
72
+ let d = (i % radix as f64) as usize;
73
+ int_digits.push(DIGITS[d]);
74
+ i = (i / radix as f64).trunc();
75
+ }
76
+ int_digits.reverse();
77
+ let mut out = String::with_capacity(int_digits.len() + 2);
78
+ if negative {
79
+ out.push('-');
80
+ }
81
+ out.push_str(std::str::from_utf8(&int_digits).unwrap());
82
+
83
+ // Fractional part: multiply-by-radix, emitting the integer overflow each step.
84
+ if frac > 0.0 {
85
+ out.push('.');
86
+ let mut count = 0;
87
+ while frac > 0.0 && count < 52 {
88
+ frac *= radix as f64;
89
+ let d = frac.trunc() as usize;
90
+ out.push(DIGITS[d] as char);
91
+ frac -= frac.trunc();
92
+ count += 1;
93
+ }
94
+ }
95
+ Some(out)
96
+ }