@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.
- package/Cargo.toml +43 -0
- package/LICENSE +13 -0
- package/README.md +66 -0
- package/crates/js_to_tish/Cargo.toml +9 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +61 -0
- package/crates/js_to_tish/src/lib.rs +11 -0
- package/crates/js_to_tish/src/span_util.rs +35 -0
- package/crates/js_to_tish/src/transform/expr.rs +608 -0
- package/crates/js_to_tish/src/transform/stmt.rs +474 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +44 -0
- package/crates/tish/src/main.rs +585 -0
- package/crates/tish/src/repl_completion.rs +200 -0
- package/crates/tish/tests/integration_test.rs +726 -0
- package/crates/tish_ast/Cargo.toml +7 -0
- package/crates/tish_ast/src/ast.rs +494 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +5 -0
- package/crates/tish_build_utils/src/lib.rs +175 -0
- package/crates/tish_builtins/Cargo.toml +12 -0
- package/crates/tish_builtins/src/array.rs +410 -0
- package/crates/tish_builtins/src/globals.rs +197 -0
- package/crates/tish_builtins/src/helpers.rs +38 -0
- package/crates/tish_builtins/src/lib.rs +14 -0
- package/crates/tish_builtins/src/math.rs +80 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +253 -0
- package/crates/tish_bytecode/Cargo.toml +15 -0
- package/crates/tish_bytecode/src/chunk.rs +97 -0
- package/crates/tish_bytecode/src/compiler.rs +1361 -0
- package/crates/tish_bytecode/src/encoding.rs +100 -0
- package/crates/tish_bytecode/src/lib.rs +19 -0
- package/crates/tish_bytecode/src/opcode.rs +110 -0
- package/crates/tish_bytecode/src/peephole.rs +159 -0
- package/crates/tish_bytecode/src/serialize.rs +163 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/shortcircuit.rs +49 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +21 -0
- package/crates/tish_compile/src/codegen.rs +3316 -0
- package/crates/tish_compile/src/lib.rs +71 -0
- package/crates/tish_compile/src/resolve.rs +631 -0
- package/crates/tish_compile/src/types.rs +304 -0
- package/crates/tish_compile_js/Cargo.toml +16 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +794 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/js_intrinsics.rs +82 -0
- package/crates/tish_compile_js/src/lib.rs +27 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +32 -0
- package/crates/tish_compiler_wasm/Cargo.toml +19 -0
- package/crates/tish_compiler_wasm/src/lib.rs +55 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +462 -0
- package/crates/tish_core/Cargo.toml +11 -0
- package/crates/tish_core/src/console_style.rs +128 -0
- package/crates/tish_core/src/json.rs +327 -0
- package/crates/tish_core/src/lib.rs +15 -0
- package/crates/tish_core/src/macros.rs +37 -0
- package/crates/tish_core/src/uri.rs +115 -0
- package/crates/tish_core/src/value.rs +376 -0
- package/crates/tish_cranelift/Cargo.toml +17 -0
- package/crates/tish_cranelift/src/lib.rs +41 -0
- package/crates/tish_cranelift/src/link.rs +120 -0
- package/crates/tish_cranelift/src/lower.rs +77 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +19 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +43 -0
- package/crates/tish_eval/Cargo.toml +26 -0
- package/crates/tish_eval/src/eval.rs +3205 -0
- package/crates/tish_eval/src/http.rs +122 -0
- package/crates/tish_eval/src/lib.rs +59 -0
- package/crates/tish_eval/src/natives.rs +301 -0
- package/crates/tish_eval/src/promise.rs +173 -0
- package/crates/tish_eval/src/regex.rs +298 -0
- package/crates/tish_eval/src/timers.rs +111 -0
- package/crates/tish_eval/src/value.rs +224 -0
- package/crates/tish_eval/src/value_convert.rs +85 -0
- package/crates/tish_fmt/Cargo.toml +16 -0
- package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
- package/crates/tish_fmt/src/lib.rs +884 -0
- package/crates/tish_jsx_web/Cargo.toml +7 -0
- package/crates/tish_jsx_web/README.md +18 -0
- package/crates/tish_jsx_web/src/lib.rs +157 -0
- package/crates/tish_jsx_web/vendor/Lattish.tish +347 -0
- package/crates/tish_lexer/Cargo.toml +7 -0
- package/crates/tish_lexer/src/lib.rs +430 -0
- package/crates/tish_lexer/src/token.rs +155 -0
- package/crates/tish_lint/Cargo.toml +17 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +77 -0
- package/crates/tish_lint/src/lib.rs +278 -0
- package/crates/tish_llvm/Cargo.toml +11 -0
- package/crates/tish_llvm/src/lib.rs +106 -0
- package/crates/tish_lsp/Cargo.toml +22 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/main.rs +615 -0
- package/crates/tish_native/Cargo.toml +14 -0
- package/crates/tish_native/src/build.rs +102 -0
- package/crates/tish_native/src/lib.rs +237 -0
- package/crates/tish_opt/Cargo.toml +11 -0
- package/crates/tish_opt/src/lib.rs +896 -0
- package/crates/tish_parser/Cargo.toml +9 -0
- package/crates/tish_parser/src/lib.rs +123 -0
- package/crates/tish_parser/src/parser.rs +1714 -0
- package/crates/tish_runtime/Cargo.toml +26 -0
- package/crates/tish_runtime/src/http.rs +308 -0
- package/crates/tish_runtime/src/http_fetch.rs +453 -0
- package/crates/tish_runtime/src/lib.rs +1004 -0
- package/crates/tish_runtime/src/native_promise.rs +26 -0
- package/crates/tish_runtime/src/promise.rs +77 -0
- package/crates/tish_runtime/src/promise_io.rs +41 -0
- package/crates/tish_runtime/src/timers.rs +125 -0
- package/crates/tish_runtime/src/ws.rs +725 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +99 -0
- package/crates/tish_vm/Cargo.toml +31 -0
- package/crates/tish_vm/src/lib.rs +39 -0
- package/crates/tish_vm/src/vm.rs +1399 -0
- package/crates/tish_wasm/Cargo.toml +13 -0
- package/crates/tish_wasm/src/lib.rs +358 -0
- package/crates/tish_wasm_runtime/Cargo.toml +25 -0
- package/crates/tish_wasm_runtime/src/lib.rs +36 -0
- package/justfile +260 -0
- package/package.json +8 -3
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- 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;
|