@tishlang/tish 1.0.7 → 1.0.10

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 (127) hide show
  1. package/Cargo.toml +43 -0
  2. package/LICENSE +13 -0
  3. package/README.md +66 -0
  4. package/crates/js_to_tish/Cargo.toml +9 -0
  5. package/crates/js_to_tish/README.md +18 -0
  6. package/crates/js_to_tish/src/error.rs +61 -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 +608 -0
  10. package/crates/js_to_tish/src/transform/stmt.rs +474 -0
  11. package/crates/js_to_tish/src/transform.rs +60 -0
  12. package/crates/tish/Cargo.toml +44 -0
  13. package/crates/tish/src/main.rs +585 -0
  14. package/crates/tish/src/repl_completion.rs +200 -0
  15. package/crates/tish/tests/integration_test.rs +726 -0
  16. package/crates/tish_ast/Cargo.toml +7 -0
  17. package/crates/tish_ast/src/ast.rs +494 -0
  18. package/crates/tish_ast/src/lib.rs +5 -0
  19. package/crates/tish_build_utils/Cargo.toml +5 -0
  20. package/crates/tish_build_utils/src/lib.rs +175 -0
  21. package/crates/tish_builtins/Cargo.toml +12 -0
  22. package/crates/tish_builtins/src/array.rs +410 -0
  23. package/crates/tish_builtins/src/globals.rs +197 -0
  24. package/crates/tish_builtins/src/helpers.rs +38 -0
  25. package/crates/tish_builtins/src/lib.rs +14 -0
  26. package/crates/tish_builtins/src/math.rs +80 -0
  27. package/crates/tish_builtins/src/object.rs +36 -0
  28. package/crates/tish_builtins/src/string.rs +253 -0
  29. package/crates/tish_bytecode/Cargo.toml +15 -0
  30. package/crates/tish_bytecode/src/chunk.rs +97 -0
  31. package/crates/tish_bytecode/src/compiler.rs +1361 -0
  32. package/crates/tish_bytecode/src/encoding.rs +100 -0
  33. package/crates/tish_bytecode/src/lib.rs +19 -0
  34. package/crates/tish_bytecode/src/opcode.rs +110 -0
  35. package/crates/tish_bytecode/src/peephole.rs +159 -0
  36. package/crates/tish_bytecode/src/serialize.rs +163 -0
  37. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  38. package/crates/tish_bytecode/tests/shortcircuit.rs +49 -0
  39. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  40. package/crates/tish_compile/Cargo.toml +21 -0
  41. package/crates/tish_compile/src/codegen.rs +3316 -0
  42. package/crates/tish_compile/src/lib.rs +71 -0
  43. package/crates/tish_compile/src/resolve.rs +631 -0
  44. package/crates/tish_compile/src/types.rs +304 -0
  45. package/crates/tish_compile_js/Cargo.toml +16 -0
  46. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  47. package/crates/tish_compile_js/src/codegen.rs +794 -0
  48. package/crates/tish_compile_js/src/error.rs +20 -0
  49. package/crates/tish_compile_js/src/js_intrinsics.rs +82 -0
  50. package/crates/tish_compile_js/src/lib.rs +27 -0
  51. package/crates/tish_compile_js/src/tests_jsx.rs +32 -0
  52. package/crates/tish_compiler_wasm/Cargo.toml +19 -0
  53. package/crates/tish_compiler_wasm/src/lib.rs +55 -0
  54. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +462 -0
  55. package/crates/tish_core/Cargo.toml +11 -0
  56. package/crates/tish_core/src/console_style.rs +128 -0
  57. package/crates/tish_core/src/json.rs +327 -0
  58. package/crates/tish_core/src/lib.rs +15 -0
  59. package/crates/tish_core/src/macros.rs +37 -0
  60. package/crates/tish_core/src/uri.rs +115 -0
  61. package/crates/tish_core/src/value.rs +376 -0
  62. package/crates/tish_cranelift/Cargo.toml +17 -0
  63. package/crates/tish_cranelift/src/lib.rs +41 -0
  64. package/crates/tish_cranelift/src/link.rs +120 -0
  65. package/crates/tish_cranelift/src/lower.rs +77 -0
  66. package/crates/tish_cranelift_runtime/Cargo.toml +19 -0
  67. package/crates/tish_cranelift_runtime/src/lib.rs +43 -0
  68. package/crates/tish_eval/Cargo.toml +26 -0
  69. package/crates/tish_eval/src/eval.rs +3205 -0
  70. package/crates/tish_eval/src/http.rs +122 -0
  71. package/crates/tish_eval/src/lib.rs +59 -0
  72. package/crates/tish_eval/src/natives.rs +301 -0
  73. package/crates/tish_eval/src/promise.rs +173 -0
  74. package/crates/tish_eval/src/regex.rs +298 -0
  75. package/crates/tish_eval/src/timers.rs +111 -0
  76. package/crates/tish_eval/src/value.rs +224 -0
  77. package/crates/tish_eval/src/value_convert.rs +85 -0
  78. package/crates/tish_fmt/Cargo.toml +16 -0
  79. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  80. package/crates/tish_fmt/src/lib.rs +884 -0
  81. package/crates/tish_jsx_web/Cargo.toml +7 -0
  82. package/crates/tish_jsx_web/README.md +18 -0
  83. package/crates/tish_jsx_web/src/lib.rs +157 -0
  84. package/crates/tish_jsx_web/vendor/Lattish.tish +347 -0
  85. package/crates/tish_lexer/Cargo.toml +7 -0
  86. package/crates/tish_lexer/src/lib.rs +430 -0
  87. package/crates/tish_lexer/src/token.rs +155 -0
  88. package/crates/tish_lint/Cargo.toml +17 -0
  89. package/crates/tish_lint/src/bin/tish-lint.rs +77 -0
  90. package/crates/tish_lint/src/lib.rs +278 -0
  91. package/crates/tish_llvm/Cargo.toml +11 -0
  92. package/crates/tish_llvm/src/lib.rs +106 -0
  93. package/crates/tish_lsp/Cargo.toml +22 -0
  94. package/crates/tish_lsp/README.md +26 -0
  95. package/crates/tish_lsp/src/main.rs +615 -0
  96. package/crates/tish_native/Cargo.toml +14 -0
  97. package/crates/tish_native/src/build.rs +102 -0
  98. package/crates/tish_native/src/lib.rs +237 -0
  99. package/crates/tish_opt/Cargo.toml +11 -0
  100. package/crates/tish_opt/src/lib.rs +896 -0
  101. package/crates/tish_parser/Cargo.toml +9 -0
  102. package/crates/tish_parser/src/lib.rs +123 -0
  103. package/crates/tish_parser/src/parser.rs +1714 -0
  104. package/crates/tish_runtime/Cargo.toml +26 -0
  105. package/crates/tish_runtime/src/http.rs +308 -0
  106. package/crates/tish_runtime/src/http_fetch.rs +453 -0
  107. package/crates/tish_runtime/src/lib.rs +1004 -0
  108. package/crates/tish_runtime/src/native_promise.rs +26 -0
  109. package/crates/tish_runtime/src/promise.rs +77 -0
  110. package/crates/tish_runtime/src/promise_io.rs +41 -0
  111. package/crates/tish_runtime/src/timers.rs +125 -0
  112. package/crates/tish_runtime/src/ws.rs +725 -0
  113. package/crates/tish_runtime/tests/fetch_readable_stream.rs +99 -0
  114. package/crates/tish_vm/Cargo.toml +31 -0
  115. package/crates/tish_vm/src/lib.rs +39 -0
  116. package/crates/tish_vm/src/vm.rs +1399 -0
  117. package/crates/tish_wasm/Cargo.toml +13 -0
  118. package/crates/tish_wasm/src/lib.rs +358 -0
  119. package/crates/tish_wasm_runtime/Cargo.toml +25 -0
  120. package/crates/tish_wasm_runtime/src/lib.rs +36 -0
  121. package/justfile +260 -0
  122. package/package.json +8 -3
  123. package/platform/darwin-arm64/tish +0 -0
  124. package/platform/darwin-x64/tish +0 -0
  125. package/platform/linux-arm64/tish +0 -0
  126. package/platform/linux-x64/tish +0 -0
  127. package/platform/win32-x64/tish.exe +0 -0
@@ -0,0 +1,410 @@
1
+ //! Array builtin methods.
2
+
3
+ use std::cell::RefCell;
4
+ use std::rc::Rc;
5
+ use tish_core::Value;
6
+ use crate::helpers::normalize_index;
7
+
8
+ /// Create a new array Value from a Vec of Values.
9
+ pub fn from_vec(v: Vec<Value>) -> Value {
10
+ Value::Array(Rc::new(RefCell::new(v)))
11
+ }
12
+
13
+ /// Get the length of an array.
14
+ pub fn len(arr: &Value) -> Option<usize> {
15
+ match arr {
16
+ Value::Array(a) => Some(a.borrow().len()),
17
+ _ => None,
18
+ }
19
+ }
20
+
21
+ pub fn push(arr: &Value, args: &[Value]) -> Value {
22
+ if let Value::Array(arr) = arr {
23
+ let mut arr_mut = arr.borrow_mut();
24
+ for v in args {
25
+ arr_mut.push(v.clone());
26
+ }
27
+ Value::Number(arr_mut.len() as f64)
28
+ } else {
29
+ Value::Null
30
+ }
31
+ }
32
+
33
+ pub fn pop(arr: &Value) -> Value {
34
+ if let Value::Array(arr) = arr {
35
+ arr.borrow_mut().pop().unwrap_or(Value::Null)
36
+ } else {
37
+ Value::Null
38
+ }
39
+ }
40
+
41
+ pub fn shift(arr: &Value) -> Value {
42
+ if let Value::Array(arr) = arr {
43
+ let mut arr_mut = arr.borrow_mut();
44
+ if arr_mut.is_empty() {
45
+ Value::Null
46
+ } else {
47
+ arr_mut.remove(0)
48
+ }
49
+ } else {
50
+ Value::Null
51
+ }
52
+ }
53
+
54
+ pub fn unshift(arr: &Value, args: &[Value]) -> Value {
55
+ if let Value::Array(arr) = arr {
56
+ let mut arr_mut = arr.borrow_mut();
57
+ for (i, v) in args.iter().enumerate() {
58
+ arr_mut.insert(i, v.clone());
59
+ }
60
+ Value::Number(arr_mut.len() as f64)
61
+ } else {
62
+ Value::Null
63
+ }
64
+ }
65
+
66
+ pub fn index_of(arr: &Value, search: &Value) -> Value {
67
+ if let Value::Array(arr) = arr {
68
+ let arr_borrow = arr.borrow();
69
+ for (i, v) in arr_borrow.iter().enumerate() {
70
+ if v.strict_eq(search) {
71
+ return Value::Number(i as f64);
72
+ }
73
+ }
74
+ }
75
+ Value::Number(-1.0)
76
+ }
77
+
78
+ pub fn includes(arr: &Value, search: &Value, from: Option<&Value>) -> Value {
79
+ if let Value::Array(arr) = arr {
80
+ let arr_borrow = arr.borrow();
81
+ let len = arr_borrow.len() as i64;
82
+ let start = match from {
83
+ Some(Value::Number(n)) if *n >= 0.0 => (*n as i64).min(len).max(0) as usize,
84
+ Some(Value::Number(n)) if *n < 0.0 => ((len + *n as i64).max(0)) as usize,
85
+ _ => 0,
86
+ };
87
+ for v in arr_borrow.iter().skip(start) {
88
+ if v.strict_eq(search) {
89
+ return Value::Bool(true);
90
+ }
91
+ }
92
+ }
93
+ Value::Bool(false)
94
+ }
95
+
96
+ pub fn join(arr: &Value, sep: &Value) -> Value {
97
+ if let Value::Array(arr) = arr {
98
+ let separator = match sep {
99
+ Value::String(s) => s.to_string(),
100
+ _ => ",".to_string(),
101
+ };
102
+ let arr_borrow = arr.borrow();
103
+ let parts: Vec<String> = arr_borrow.iter().map(|v| v.to_display_string()).collect();
104
+ Value::String(parts.join(&separator).into())
105
+ } else {
106
+ Value::Null
107
+ }
108
+ }
109
+
110
+ pub fn reverse(arr: &Value) -> Value {
111
+ if let Value::Array(arr) = arr {
112
+ arr.borrow_mut().reverse();
113
+ Value::Array(Rc::clone(arr))
114
+ } else {
115
+ Value::Null
116
+ }
117
+ }
118
+
119
+ /// Fisher-Yates shuffle. Returns a new shuffled array (does not mutate).
120
+ pub fn shuffle(arr: &Value) -> Value {
121
+ if let Value::Array(arr) = arr {
122
+ let mut v = arr.borrow().clone();
123
+ use rand::seq::SliceRandom;
124
+ v.shuffle(&mut rand::rng());
125
+ Value::Array(Rc::new(RefCell::new(v)))
126
+ } else {
127
+ Value::Null
128
+ }
129
+ }
130
+
131
+ pub fn splice(arr: &Value, start: &Value, delete_count: Option<&Value>, items: &[Value]) -> Value {
132
+ if let Value::Array(arr) = arr {
133
+ let mut arr_mut = arr.borrow_mut();
134
+ let len = arr_mut.len() as i64;
135
+ let start_idx = normalize_index(start, len, 0);
136
+ let del_count = match delete_count {
137
+ Some(Value::Number(n)) => (*n as i64).max(0) as usize,
138
+ _ => (len as usize).saturating_sub(start_idx),
139
+ };
140
+ let actual_delete = del_count.min(arr_mut.len().saturating_sub(start_idx));
141
+ let removed: Vec<Value> = arr_mut
142
+ .splice(start_idx..start_idx + actual_delete, items.iter().cloned())
143
+ .collect();
144
+ Value::Array(Rc::new(RefCell::new(removed)))
145
+ } else {
146
+ Value::Null
147
+ }
148
+ }
149
+
150
+ pub fn slice(arr: &Value, start: &Value, end: &Value) -> Value {
151
+ if let Value::Array(arr) = arr {
152
+ let arr_borrow = arr.borrow();
153
+ let len = arr_borrow.len() as i64;
154
+ let start_idx = normalize_index(start, len, 0);
155
+ let end_idx = normalize_index(end, len, len as usize);
156
+ let sliced = if start_idx < end_idx { arr_borrow[start_idx..end_idx].to_vec() } else { vec![] };
157
+ Value::Array(Rc::new(RefCell::new(sliced)))
158
+ } else {
159
+ Value::Null
160
+ }
161
+ }
162
+
163
+ pub fn concat(arr: &Value, args: &[Value]) -> Value {
164
+ if let Value::Array(arr) = arr {
165
+ let mut result = arr.borrow().clone();
166
+ for v in args {
167
+ if let Value::Array(other) = v {
168
+ result.extend(other.borrow().iter().cloned());
169
+ } else {
170
+ result.push(v.clone());
171
+ }
172
+ }
173
+ Value::Array(Rc::new(RefCell::new(result)))
174
+ } else {
175
+ Value::Null
176
+ }
177
+ }
178
+
179
+ pub fn flat(arr: &Value, depth: &Value) -> Value {
180
+ fn flatten(arr: &[Value], depth: i32, result: &mut Vec<Value>) {
181
+ for v in arr {
182
+ if depth > 0 {
183
+ if let Value::Array(inner) = v {
184
+ flatten(&inner.borrow(), depth - 1, result);
185
+ continue;
186
+ }
187
+ }
188
+ result.push(v.clone());
189
+ }
190
+ }
191
+
192
+ if let Value::Array(arr) = arr {
193
+ let d = match depth {
194
+ Value::Number(n) => *n as i32,
195
+ _ => 1,
196
+ };
197
+ let mut result = Vec::new();
198
+ flatten(&arr.borrow(), d, &mut result);
199
+ Value::Array(Rc::new(RefCell::new(result)))
200
+ } else {
201
+ Value::Null
202
+ }
203
+ }
204
+
205
+ // Higher-order array methods require a callback function.
206
+ // These take NativeFn from tish_core::Value::Function
207
+
208
+ pub fn map(arr: &Value, callback: &Value) -> Value {
209
+ if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
210
+ let arr_borrow = arr.borrow();
211
+ let result: Vec<Value> = arr_borrow.iter().enumerate().map(|(i, v)| {
212
+ cb(&[v.clone(), Value::Number(i as f64)])
213
+ }).collect();
214
+ Value::Array(Rc::new(RefCell::new(result)))
215
+ } else {
216
+ Value::Null
217
+ }
218
+ }
219
+
220
+ pub fn filter(arr: &Value, callback: &Value) -> Value {
221
+ if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
222
+ let arr_borrow = arr.borrow();
223
+ let result: Vec<Value> = arr_borrow.iter().enumerate().filter_map(|(i, v)| {
224
+ let keep = cb(&[v.clone(), Value::Number(i as f64)]);
225
+ if keep.is_truthy() { Some(v.clone()) } else { None }
226
+ }).collect();
227
+ Value::Array(Rc::new(RefCell::new(result)))
228
+ } else {
229
+ Value::Null
230
+ }
231
+ }
232
+
233
+ pub fn reduce(arr: &Value, callback: &Value, initial: &Value) -> Value {
234
+ if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
235
+ let arr_borrow = arr.borrow();
236
+ let len = arr_borrow.len();
237
+ let (start_idx, mut acc) = if matches!(initial, Value::Null)
238
+ && !arr_borrow.is_empty()
239
+ {
240
+ // No initial value: use first element as acc, start from index 1
241
+ (1, arr_borrow[0].clone())
242
+ } else {
243
+ (0, initial.clone())
244
+ };
245
+ for i in start_idx..len {
246
+ let v = arr_borrow[i].clone();
247
+ acc = cb(&[acc, v.clone(), Value::Number(i as f64)]);
248
+ }
249
+ acc
250
+ } else {
251
+ Value::Null
252
+ }
253
+ }
254
+
255
+ pub fn for_each(arr: &Value, callback: &Value) -> Value {
256
+ if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
257
+ let arr_borrow = arr.borrow();
258
+ for (i, v) in arr_borrow.iter().enumerate() {
259
+ cb(&[v.clone(), Value::Number(i as f64)]);
260
+ }
261
+ }
262
+ Value::Null
263
+ }
264
+
265
+ pub fn find(arr: &Value, callback: &Value) -> Value {
266
+ if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
267
+ let arr_borrow = arr.borrow();
268
+ for (i, v) in arr_borrow.iter().enumerate() {
269
+ let result = cb(&[v.clone(), Value::Number(i as f64)]);
270
+ if result.is_truthy() {
271
+ return v.clone();
272
+ }
273
+ }
274
+ }
275
+ Value::Null
276
+ }
277
+
278
+ pub fn find_index(arr: &Value, callback: &Value) -> Value {
279
+ if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
280
+ let arr_borrow = arr.borrow();
281
+ for (i, v) in arr_borrow.iter().enumerate() {
282
+ let result = cb(&[v.clone(), Value::Number(i as f64)]);
283
+ if result.is_truthy() {
284
+ return Value::Number(i as f64);
285
+ }
286
+ }
287
+ }
288
+ Value::Number(-1.0)
289
+ }
290
+
291
+ pub fn some(arr: &Value, callback: &Value) -> Value {
292
+ if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
293
+ let arr_borrow = arr.borrow();
294
+ for (i, v) in arr_borrow.iter().enumerate() {
295
+ let result = cb(&[v.clone(), Value::Number(i as f64)]);
296
+ if result.is_truthy() {
297
+ return Value::Bool(true);
298
+ }
299
+ }
300
+ }
301
+ Value::Bool(false)
302
+ }
303
+
304
+ pub fn every(arr: &Value, callback: &Value) -> Value {
305
+ if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
306
+ let arr_borrow = arr.borrow();
307
+ for (i, v) in arr_borrow.iter().enumerate() {
308
+ let result = cb(&[v.clone(), Value::Number(i as f64)]);
309
+ if !result.is_truthy() {
310
+ return Value::Bool(false);
311
+ }
312
+ }
313
+ Value::Bool(true)
314
+ } else {
315
+ Value::Bool(false)
316
+ }
317
+ }
318
+
319
+ pub fn flat_map(arr: &Value, callback: &Value) -> Value {
320
+ if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
321
+ let arr_borrow = arr.borrow();
322
+ let mut result: Vec<Value> = Vec::new();
323
+ for (i, v) in arr_borrow.iter().enumerate() {
324
+ let mapped = cb(&[v.clone(), Value::Number(i as f64)]);
325
+ if let Value::Array(inner) = mapped {
326
+ result.extend(inner.borrow().iter().cloned());
327
+ } else {
328
+ result.push(mapped);
329
+ }
330
+ }
331
+ Value::Array(Rc::new(RefCell::new(result)))
332
+ } else {
333
+ Value::Null
334
+ }
335
+ }
336
+
337
+ fn sort_by_impl<F>(arr: &Value, cmp: F) -> Value
338
+ where F: FnMut(&Value, &Value) -> std::cmp::Ordering {
339
+ if let Value::Array(arr) = arr {
340
+ arr.borrow_mut().sort_by(cmp);
341
+ Value::Array(Rc::clone(arr))
342
+ } else {
343
+ Value::Null
344
+ }
345
+ }
346
+
347
+ pub fn sort_default(arr: &Value) -> Value {
348
+ sort_by_impl(arr, |a, b| a.to_display_string().cmp(&b.to_display_string()))
349
+ }
350
+
351
+ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
352
+ if let (Value::Array(arr), Value::Function(cmp_fn)) = (arr, comparator) {
353
+ let mut arr_mut = arr.borrow_mut();
354
+ let len = arr_mut.len();
355
+ let mut indices: Vec<usize> = (0..len).collect();
356
+ let mut elements: Vec<Value> = std::mem::take(&mut *arr_mut);
357
+ let mut args_buf: [Value; 2] = [Value::Null, Value::Null];
358
+
359
+ indices.sort_by(|&a, &b| {
360
+ args_buf[0] = elements[a].clone();
361
+ args_buf[1] = elements[b].clone();
362
+ match cmp_fn(&args_buf) {
363
+ Value::Number(n) if n < 0.0 => std::cmp::Ordering::Less,
364
+ Value::Number(n) if n > 0.0 => std::cmp::Ordering::Greater,
365
+ _ => std::cmp::Ordering::Equal,
366
+ }
367
+ });
368
+
369
+ *arr_mut = indices.into_iter().map(|i| std::mem::replace(&mut elements[i], Value::Null)).collect();
370
+ drop(arr_mut);
371
+ Value::Array(Rc::clone(arr))
372
+ } else {
373
+ Value::Null
374
+ }
375
+ }
376
+
377
+ fn num_cmp(a: &Value, b: &Value, asc: bool) -> std::cmp::Ordering {
378
+ let (na, nb) = match (a, b) {
379
+ (Value::Number(a), Value::Number(b)) => (*a, *b),
380
+ _ => (f64::NAN, f64::NAN),
381
+ };
382
+ let cmp = na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
383
+ if asc { cmp } else { cmp.reverse() }
384
+ }
385
+
386
+ pub fn sort_numeric_asc(arr: &Value) -> Value {
387
+ sort_by_impl(arr, |a, b| num_cmp(a, b, true))
388
+ }
389
+
390
+ pub fn sort_numeric_desc(arr: &Value) -> Value {
391
+ sort_by_impl(arr, |a, b| num_cmp(a, b, false))
392
+ }
393
+
394
+ /// Sort array of objects by numeric property: arr.sort((a,b)=>a.prop-b.prop)
395
+ pub fn sort_by_property_numeric(arr: &Value, prop: &str, asc: bool) -> Value {
396
+ let prop_arc = std::sync::Arc::from(prop);
397
+ sort_by_impl(arr, move |a, b| {
398
+ let na = get_prop_number(a, &prop_arc);
399
+ let nb = get_prop_number(b, &prop_arc);
400
+ let cmp = na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
401
+ if asc { cmp } else { cmp.reverse() }
402
+ })
403
+ }
404
+
405
+ fn get_prop_number(v: &Value, prop: &std::sync::Arc<str>) -> f64 {
406
+ match v {
407
+ Value::Object(o) => o.borrow().get(prop.as_ref()).map(|v| v.as_number().unwrap_or(f64::NAN)).unwrap_or(f64::NAN),
408
+ _ => f64::NAN,
409
+ }
410
+ }
@@ -0,0 +1,197 @@
1
+ //! Global builtin functions with signature (args: &[Value]) -> Value.
2
+ //!
3
+ //! Used by both tish_vm (bytecode) and tish_runtime (compiled). Keeps tish_vm
4
+ //! independent of tish_runtime.
5
+
6
+ use std::cell::RefCell;
7
+ use std::collections::HashMap;
8
+ use std::rc::Rc;
9
+ use std::sync::Arc;
10
+ use tish_core::{percent_decode, percent_encode, Value};
11
+
12
+ /// Boolean(value) - coerce to bool
13
+ pub fn boolean(args: &[Value]) -> Value {
14
+ let v = args.first().unwrap_or(&Value::Null);
15
+ Value::Bool(v.is_truthy())
16
+ }
17
+
18
+ /// decodeURI(str)
19
+ pub fn decode_uri(args: &[Value]) -> Value {
20
+ let s = args.first().map(Value::to_display_string).unwrap_or_default();
21
+ Value::String(percent_decode(&s).unwrap_or(s).into())
22
+ }
23
+
24
+ /// encodeURI(str)
25
+ pub fn encode_uri(args: &[Value]) -> Value {
26
+ let s = args.first().map(Value::to_display_string).unwrap_or_default();
27
+ Value::String(percent_encode(&s).into())
28
+ }
29
+
30
+ /// isFinite(value)
31
+ pub fn is_finite(args: &[Value]) -> Value {
32
+ Value::Bool(args.first().is_some_and(|v| matches!(v, Value::Number(n) if n.is_finite())))
33
+ }
34
+
35
+ /// isNaN(value)
36
+ pub fn is_nan(args: &[Value]) -> Value {
37
+ Value::Bool(
38
+ args.first().is_none_or(|v| {
39
+ matches!(v, Value::Number(n) if n.is_nan())
40
+ || !matches!(v, Value::Number(_))
41
+ }),
42
+ )
43
+ }
44
+
45
+ /// Array.isArray(value)
46
+ pub fn array_is_array(args: &[Value]) -> Value {
47
+ Value::Bool(matches!(args.first(), Some(Value::Array(_))))
48
+ }
49
+
50
+ /// String(value) — convert value to string (JS String constructor as function).
51
+ pub fn string_convert(args: &[Value]) -> Value {
52
+ let v = args.first().unwrap_or(&Value::Null);
53
+ Value::String(v.to_display_string().into())
54
+ }
55
+
56
+ /// String.fromCharCode(...codes)
57
+ pub fn string_from_char_code(args: &[Value]) -> Value {
58
+ let s: String = args
59
+ .iter()
60
+ .filter_map(|v| match v {
61
+ Value::Number(n) => char::from_u32(*n as u32),
62
+ _ => None,
63
+ })
64
+ .collect();
65
+ Value::String(s.into())
66
+ }
67
+
68
+ /// Object.keys(obj)
69
+ pub fn object_keys(args: &[Value]) -> Value {
70
+ if let Some(Value::Object(obj)) = args.first() {
71
+ let obj_borrow = obj.borrow();
72
+ let keys: Vec<Value> = obj_borrow
73
+ .keys()
74
+ .map(|k| Value::String(Arc::clone(k)))
75
+ .collect();
76
+ Value::Array(Rc::new(RefCell::new(keys)))
77
+ } else {
78
+ Value::Array(Rc::new(RefCell::new(Vec::new())))
79
+ }
80
+ }
81
+
82
+ /// Object.values(obj)
83
+ pub fn object_values(args: &[Value]) -> Value {
84
+ if let Some(Value::Object(obj)) = args.first() {
85
+ let obj_borrow = obj.borrow();
86
+ let values: Vec<Value> = obj_borrow.values().cloned().collect();
87
+ Value::Array(Rc::new(RefCell::new(values)))
88
+ } else {
89
+ Value::Array(Rc::new(RefCell::new(Vec::new())))
90
+ }
91
+ }
92
+
93
+ /// Object.entries(obj)
94
+ pub fn object_entries(args: &[Value]) -> Value {
95
+ if let Some(Value::Object(obj)) = args.first() {
96
+ let obj_borrow = obj.borrow();
97
+ let entries: Vec<Value> = obj_borrow
98
+ .iter()
99
+ .map(|(k, v)| {
100
+ Value::Array(Rc::new(RefCell::new(vec![
101
+ Value::String(Arc::clone(k)),
102
+ v.clone(),
103
+ ])))
104
+ })
105
+ .collect();
106
+ Value::Array(Rc::new(RefCell::new(entries)))
107
+ } else {
108
+ Value::Array(Rc::new(RefCell::new(Vec::new())))
109
+ }
110
+ }
111
+
112
+ /// Object.assign(target, ...sources)
113
+ pub fn object_assign(args: &[Value]) -> Value {
114
+ let target = match args.first() {
115
+ Some(Value::Object(obj)) => obj,
116
+ _ => return Value::Null,
117
+ };
118
+
119
+ let additional_capacity: usize = args
120
+ .iter()
121
+ .skip(1)
122
+ .map(|source| {
123
+ if let Value::Object(src) = source {
124
+ src.borrow().len()
125
+ } else {
126
+ 0
127
+ }
128
+ })
129
+ .sum();
130
+
131
+ let mut target_mut = target.borrow_mut();
132
+ target_mut.reserve(additional_capacity);
133
+
134
+ for source in args.iter().skip(1) {
135
+ if let Value::Object(src) = source {
136
+ let src_borrow = src.borrow();
137
+ for (k, v) in src_borrow.iter() {
138
+ target_mut.insert(Arc::clone(k), v.clone());
139
+ }
140
+ }
141
+ }
142
+ drop(target_mut);
143
+ Value::Object(Rc::clone(target))
144
+ }
145
+
146
+ /// parseInt(string, radix?)
147
+ pub fn parse_int(args: &[Value]) -> Value {
148
+ let s = args.first().map(Value::to_display_string).unwrap_or_default();
149
+ let s = s.trim();
150
+ let radix = args.get(1).and_then(|v| match v {
151
+ Value::Number(n) => Some(*n as i32),
152
+ _ => None,
153
+ }).unwrap_or(10);
154
+
155
+ if (2..=36).contains(&radix) {
156
+ let prefix: String = s
157
+ .chars()
158
+ .take_while(|c| *c == '-' || *c == '+' || c.is_digit(radix as u32))
159
+ .collect();
160
+ if let Ok(n) = i64::from_str_radix(&prefix, radix as u32) {
161
+ return Value::Number(n as f64);
162
+ }
163
+ }
164
+ Value::Number(f64::NAN)
165
+ }
166
+
167
+ /// parseFloat(string)
168
+ pub fn parse_float(args: &[Value]) -> Value {
169
+ let s = args.first().map(Value::to_display_string).unwrap_or_default();
170
+ Value::Number(s.trim().parse().unwrap_or(f64::NAN))
171
+ }
172
+
173
+ /// Object.fromEntries(entries)
174
+ pub fn object_from_entries(args: &[Value]) -> Value {
175
+ if let Some(Value::Array(entries)) = args.first() {
176
+ let entries_borrow = entries.borrow();
177
+ let mut obj: HashMap<Arc<str>, Value> =
178
+ HashMap::with_capacity(entries_borrow.len());
179
+
180
+ for entry in entries_borrow.iter() {
181
+ if let Value::Array(pair) = entry {
182
+ let pair_borrow = pair.borrow();
183
+ if pair_borrow.len() >= 2 {
184
+ let key: Arc<str> = match &pair_borrow[0] {
185
+ Value::String(s) => Arc::clone(s),
186
+ v => v.to_display_string().into(),
187
+ };
188
+ obj.insert(key, pair_borrow[1].clone());
189
+ }
190
+ }
191
+ }
192
+
193
+ Value::Object(Rc::new(RefCell::new(obj)))
194
+ } else {
195
+ Value::Object(Rc::new(RefCell::new(HashMap::new())))
196
+ }
197
+ }
@@ -0,0 +1,38 @@
1
+ //! Common helper functions used across builtin implementations.
2
+
3
+ use std::cell::RefCell;
4
+ use std::collections::HashMap;
5
+ use std::rc::Rc;
6
+ use std::sync::Arc;
7
+ use tish_core::Value;
8
+
9
+ /// Normalize an array index, handling negative indices.
10
+ /// Returns a valid index within bounds or the default value.
11
+ pub fn normalize_index(idx: &Value, len: i64, default: usize) -> usize {
12
+ match idx {
13
+ Value::Number(n) => {
14
+ let n = *n as i64;
15
+ if n < 0 {
16
+ (len + n).max(0) as usize
17
+ } else {
18
+ n.min(len) as usize
19
+ }
20
+ }
21
+ _ => default,
22
+ }
23
+ }
24
+
25
+ /// Create an error object with a single "error" field.
26
+ pub fn make_error_value(e: impl std::fmt::Display) -> Value {
27
+ let mut obj = HashMap::with_capacity(1);
28
+ obj.insert(Arc::from("error"), Value::String(e.to_string().into()));
29
+ Value::Object(Rc::new(RefCell::new(obj)))
30
+ }
31
+
32
+ /// Extract a number from a Value, returning None for non-numbers.
33
+ pub fn extract_num(v: Option<&Value>) -> Option<f64> {
34
+ v.and_then(|val| match val {
35
+ Value::Number(n) => Some(*n),
36
+ _ => None,
37
+ })
38
+ }
@@ -0,0 +1,14 @@
1
+ //! Shared builtin implementations for Tish.
2
+ //!
3
+ //! Used by the compiled runtime (tish_runtime) and bytecode VM (tish_vm). The
4
+ //! interpreter (tish_eval) implements builtins inline due to different Value
5
+ //! and native signatures.
6
+
7
+ pub mod array;
8
+ pub mod string;
9
+ pub mod object;
10
+ pub mod math;
11
+ pub mod helpers;
12
+ pub mod globals;
13
+
14
+ pub use tish_core::Value;