@tishlang/tish 1.5.0 → 1.7.0
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 +1 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/error.rs +2 -8
- package/crates/js_to_tish/src/transform/expr.rs +101 -130
- package/crates/js_to_tish/src/transform/stmt.rs +25 -22
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/cli_help.rs +76 -29
- package/crates/tish/src/main.rs +85 -54
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
- package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
- package/crates/tish/tests/integration_test.rs +197 -47
- package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
- package/crates/tish/tests/shortcircuit.rs +19 -4
- package/crates/tish_ast/src/ast.rs +12 -14
- package/crates/tish_build_utils/src/lib.rs +64 -6
- package/crates/tish_builtins/src/array.rs +52 -21
- package/crates/tish_builtins/src/construct.rs +2 -8
- package/crates/tish_builtins/src/globals.rs +30 -15
- package/crates/tish_builtins/src/lib.rs +5 -5
- package/crates/tish_builtins/src/math.rs +5 -3
- package/crates/tish_builtins/src/string.rs +71 -19
- package/crates/tish_bytecode/src/chunk.rs +0 -1
- package/crates/tish_bytecode/src/compiler.rs +164 -60
- package/crates/tish_bytecode/src/opcode.rs +13 -4
- package/crates/tish_bytecode/src/peephole.rs +2 -2
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +989 -318
- package/crates/tish_compile/src/infer.rs +69 -19
- package/crates/tish_compile/src/lib.rs +21 -8
- package/crates/tish_compile/src/resolve.rs +515 -94
- package/crates/tish_compile/src/types.rs +10 -14
- package/crates/tish_compile_js/src/codegen.rs +34 -13
- package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
- package/crates/tish_compiler_wasm/src/lib.rs +16 -13
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +40 -48
- package/crates/tish_core/src/json.rs +5 -3
- package/crates/tish_core/src/lib.rs +1 -1
- package/crates/tish_core/src/uri.rs +9 -6
- package/crates/tish_core/src/value.rs +92 -28
- package/crates/tish_cranelift/src/link.rs +6 -9
- package/crates/tish_cranelift/src/lower.rs +14 -8
- package/crates/tish_eval/src/eval.rs +398 -141
- package/crates/tish_eval/src/lib.rs +10 -6
- package/crates/tish_eval/src/natives.rs +95 -38
- package/crates/tish_eval/src/promise.rs +14 -8
- package/crates/tish_eval/src/timers.rs +28 -19
- package/crates/tish_eval/src/value.rs +10 -3
- package/crates/tish_fmt/src/lib.rs +29 -13
- package/crates/tish_lexer/src/lib.rs +217 -63
- package/crates/tish_lexer/src/token.rs +6 -6
- package/crates/tish_llvm/src/lib.rs +15 -8
- package/crates/tish_lsp/src/main.rs +41 -43
- package/crates/tish_native/src/build.rs +38 -15
- package/crates/tish_native/src/lib.rs +76 -32
- package/crates/tish_opt/src/lib.rs +67 -50
- package/crates/tish_parser/src/lib.rs +36 -11
- package/crates/tish_parser/src/parser.rs +172 -87
- package/crates/tish_runtime/src/http.rs +15 -6
- package/crates/tish_runtime/src/http_fetch.rs +24 -14
- package/crates/tish_runtime/src/lib.rs +224 -168
- package/crates/tish_runtime/src/promise.rs +1 -5
- package/crates/tish_runtime/src/ws.rs +45 -20
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
- package/crates/tish_ui/src/jsx.rs +41 -22
- package/crates/tish_ui/src/lib.rs +2 -2
- package/crates/tish_vm/src/vm.rs +320 -116
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +8 -3
- package/crates/tish_wasm/src/lib.rs +38 -28
- package/crates/tishlang_cargo_bindgen/Cargo.toml +25 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +52 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
- package/package.json +1 -1
- 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
|
@@ -158,7 +158,9 @@ pub fn find_workspace_root() -> Result<PathBuf, String> {
|
|
|
158
158
|
if let Some(mut current) = exe.parent() {
|
|
159
159
|
for _ in 0..15 {
|
|
160
160
|
let crates_dir = current.join("crates");
|
|
161
|
-
if crates_dir.join("tish_runtime").exists()
|
|
161
|
+
if crates_dir.join("tish_runtime").exists()
|
|
162
|
+
|| crates_dir.join("tish_cranelift_runtime").exists()
|
|
163
|
+
{
|
|
162
164
|
return Ok(current.to_path_buf());
|
|
163
165
|
}
|
|
164
166
|
if let Some(p) = current.parent() {
|
|
@@ -177,6 +179,19 @@ pub fn find_workspace_root() -> Result<PathBuf, String> {
|
|
|
177
179
|
}
|
|
178
180
|
}
|
|
179
181
|
|
|
182
|
+
// Strategy 3b: `node_modules/@tishlang/tish` from cwd or any ancestor (package.json-only apps)
|
|
183
|
+
if let Ok(mut dir) = std::env::current_dir() {
|
|
184
|
+
for _ in 0..32 {
|
|
185
|
+
let npm_pkg = dir.join("node_modules").join("@tishlang").join("tish");
|
|
186
|
+
if is_tish_workspace_root(&npm_pkg) {
|
|
187
|
+
return Ok(npm_pkg);
|
|
188
|
+
}
|
|
189
|
+
if !dir.pop() {
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
180
195
|
// Strategy 4: Walk from current working directory
|
|
181
196
|
if let Ok(mut current) = std::env::current_dir() {
|
|
182
197
|
for _ in 0..15 {
|
|
@@ -202,6 +217,21 @@ pub fn find_workspace_root() -> Result<PathBuf, String> {
|
|
|
202
217
|
Err("Cannot find Tish workspace root. Run from workspace root or use cargo run.".to_string())
|
|
203
218
|
}
|
|
204
219
|
|
|
220
|
+
/// Path to `crates/tish_runtime` inside a locally installed `@tishlang/tish` npm package.
|
|
221
|
+
pub fn npm_package_runtime_path(project_root: &Path) -> Option<PathBuf> {
|
|
222
|
+
let p = project_root
|
|
223
|
+
.join("node_modules")
|
|
224
|
+
.join("@tishlang")
|
|
225
|
+
.join("tish")
|
|
226
|
+
.join("crates")
|
|
227
|
+
.join("tish_runtime");
|
|
228
|
+
if p.is_dir() {
|
|
229
|
+
Some(p)
|
|
230
|
+
} else {
|
|
231
|
+
None
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
205
235
|
/// Find the path to the tishlang_runtime crate.
|
|
206
236
|
///
|
|
207
237
|
/// Returns a canonical path string suitable for Cargo.toml path dependencies.
|
|
@@ -217,6 +247,24 @@ pub fn find_runtime_path() -> Result<String, String> {
|
|
|
217
247
|
.map(|p| p.display().to_string().replace('\\', "/"))
|
|
218
248
|
}
|
|
219
249
|
|
|
250
|
+
/// Resolve `tishlang_runtime` for a Cargo build, preferring the npm install under `project_root`.
|
|
251
|
+
///
|
|
252
|
+
/// When a Tish app lives next to a checkout of the language repo (e.g. `…/tish/tish-cargo-example`),
|
|
253
|
+
/// [`find_workspace_root`] can return the checkout while `rustDependencies` point at
|
|
254
|
+
/// `node_modules/@tishlang/tish/crates/tish_core`. Using the npm tree for **both** runtime and shim
|
|
255
|
+
/// avoids Cargo lockfile "package collision" for the same crate name/version at two paths.
|
|
256
|
+
pub fn find_runtime_path_for_project(project_root: Option<&Path>) -> Result<String, String> {
|
|
257
|
+
if let Some(root) = project_root {
|
|
258
|
+
if let Some(rt) = npm_package_runtime_path(root) {
|
|
259
|
+
return rt
|
|
260
|
+
.canonicalize()
|
|
261
|
+
.map_err(|e| format!("Cannot canonicalize tishlang_runtime (npm): {}", e))
|
|
262
|
+
.map(|p| p.display().to_string().replace('\\', "/"));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
find_runtime_path()
|
|
266
|
+
}
|
|
267
|
+
|
|
220
268
|
/// Crate package name -> directory name (directories kept as tish_* for historical reasons).
|
|
221
269
|
const CRATE_NAME_TO_DIR: &[(&str, &str)] = &[
|
|
222
270
|
("tishlang_runtime", "tish_runtime"),
|
|
@@ -243,16 +291,22 @@ pub fn find_crate_path(crate_name: &str) -> Result<PathBuf, String> {
|
|
|
243
291
|
|
|
244
292
|
/// Create a temp build directory with src subdir.
|
|
245
293
|
pub fn create_build_dir(prefix: &str, out_name: &str) -> Result<PathBuf, String> {
|
|
246
|
-
let build_dir =
|
|
294
|
+
let build_dir =
|
|
295
|
+
std::env::temp_dir()
|
|
296
|
+
.join(prefix)
|
|
297
|
+
.join(format!("{}_{}", out_name, std::process::id()));
|
|
247
298
|
fs::create_dir_all(&build_dir).map_err(|e| format!("Cannot create build dir: {}", e))?;
|
|
248
|
-
fs::create_dir_all(build_dir.join("src"))
|
|
299
|
+
fs::create_dir_all(build_dir.join("src"))
|
|
300
|
+
.map_err(|e| format!("Cannot create src dir: {}", e))?;
|
|
249
301
|
Ok(build_dir)
|
|
250
302
|
}
|
|
251
303
|
|
|
252
304
|
/// Run cargo build in the given directory.
|
|
253
305
|
/// If target_dir is Some, use that for --target-dir (e.g. workspace target for caching).
|
|
254
306
|
pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<(), String> {
|
|
255
|
-
let target_dir = target_dir
|
|
307
|
+
let target_dir = target_dir
|
|
308
|
+
.map(|p| p.to_path_buf())
|
|
309
|
+
.unwrap_or_else(|| build_dir.join("target"));
|
|
256
310
|
let output = Command::new("cargo")
|
|
257
311
|
.args(["build", "--release", "--target-dir"])
|
|
258
312
|
.arg(&target_dir)
|
|
@@ -265,7 +319,10 @@ pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<()
|
|
|
265
319
|
if !output.status.success() {
|
|
266
320
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
267
321
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
268
|
-
return Err(format!(
|
|
322
|
+
return Err(format!(
|
|
323
|
+
"Compilation failed.\nstdout:\n{}\nstderr:\n{}",
|
|
324
|
+
stdout, stderr
|
|
325
|
+
));
|
|
269
326
|
}
|
|
270
327
|
Ok(())
|
|
271
328
|
}
|
|
@@ -318,7 +375,8 @@ pub fn copy_binary_to_output(binary: &Path, output_path: &Path) -> Result<(), St
|
|
|
318
375
|
if let Some(parent) = output_path.parent() {
|
|
319
376
|
fs::create_dir_all(parent).map_err(|e| format!("Cannot create output dir: {}", e))?;
|
|
320
377
|
}
|
|
321
|
-
fs::copy(binary, output_path)
|
|
378
|
+
fs::copy(binary, output_path)
|
|
379
|
+
.map_err(|e| format!("Cannot copy to {}: {}", output_path.display(), e))?;
|
|
322
380
|
Ok(())
|
|
323
381
|
}
|
|
324
382
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
//! Array builtin methods.
|
|
2
2
|
|
|
3
|
+
use crate::helpers::normalize_index;
|
|
3
4
|
use std::cell::RefCell;
|
|
4
5
|
use std::rc::Rc;
|
|
5
6
|
use tishlang_core::Value;
|
|
6
|
-
use crate::helpers::normalize_index;
|
|
7
7
|
|
|
8
8
|
/// Create a new array Value from a Vec of Values.
|
|
9
9
|
pub fn from_vec(v: Vec<Value>) -> Value {
|
|
@@ -153,7 +153,11 @@ pub fn slice(arr: &Value, start: &Value, end: &Value) -> Value {
|
|
|
153
153
|
let len = arr_borrow.len() as i64;
|
|
154
154
|
let start_idx = normalize_index(start, len, 0);
|
|
155
155
|
let end_idx = normalize_index(end, len, len as usize);
|
|
156
|
-
let sliced = if start_idx < end_idx {
|
|
156
|
+
let sliced = if start_idx < end_idx {
|
|
157
|
+
arr_borrow[start_idx..end_idx].to_vec()
|
|
158
|
+
} else {
|
|
159
|
+
vec![]
|
|
160
|
+
};
|
|
157
161
|
Value::Array(Rc::new(RefCell::new(sliced)))
|
|
158
162
|
} else {
|
|
159
163
|
Value::Null
|
|
@@ -188,7 +192,7 @@ pub fn flat(arr: &Value, depth: &Value) -> Value {
|
|
|
188
192
|
result.push(v.clone());
|
|
189
193
|
}
|
|
190
194
|
}
|
|
191
|
-
|
|
195
|
+
|
|
192
196
|
if let Value::Array(arr) = arr {
|
|
193
197
|
let d = match depth {
|
|
194
198
|
Value::Number(n) => *n as i32,
|
|
@@ -208,9 +212,11 @@ pub fn flat(arr: &Value, depth: &Value) -> Value {
|
|
|
208
212
|
pub fn map(arr: &Value, callback: &Value) -> Value {
|
|
209
213
|
if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
|
|
210
214
|
let arr_borrow = arr.borrow();
|
|
211
|
-
let result: Vec<Value> = arr_borrow
|
|
212
|
-
|
|
213
|
-
|
|
215
|
+
let result: Vec<Value> = arr_borrow
|
|
216
|
+
.iter()
|
|
217
|
+
.enumerate()
|
|
218
|
+
.map(|(i, v)| cb(&[v.clone(), Value::Number(i as f64)]))
|
|
219
|
+
.collect();
|
|
214
220
|
Value::Array(Rc::new(RefCell::new(result)))
|
|
215
221
|
} else {
|
|
216
222
|
Value::Null
|
|
@@ -220,10 +226,18 @@ pub fn map(arr: &Value, callback: &Value) -> Value {
|
|
|
220
226
|
pub fn filter(arr: &Value, callback: &Value) -> Value {
|
|
221
227
|
if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
|
|
222
228
|
let arr_borrow = arr.borrow();
|
|
223
|
-
let result: Vec<Value> = arr_borrow
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
229
|
+
let result: Vec<Value> = arr_borrow
|
|
230
|
+
.iter()
|
|
231
|
+
.enumerate()
|
|
232
|
+
.filter_map(|(i, v)| {
|
|
233
|
+
let keep = cb(&[v.clone(), Value::Number(i as f64)]);
|
|
234
|
+
if keep.is_truthy() {
|
|
235
|
+
Some(v.clone())
|
|
236
|
+
} else {
|
|
237
|
+
None
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
.collect();
|
|
227
241
|
Value::Array(Rc::new(RefCell::new(result)))
|
|
228
242
|
} else {
|
|
229
243
|
Value::Null
|
|
@@ -234,9 +248,7 @@ pub fn reduce(arr: &Value, callback: &Value, initial: &Value) -> Value {
|
|
|
234
248
|
if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
|
|
235
249
|
let arr_borrow = arr.borrow();
|
|
236
250
|
let len = arr_borrow.len();
|
|
237
|
-
let (start_idx, mut acc) = if matches!(initial, Value::Null)
|
|
238
|
-
&& !arr_borrow.is_empty()
|
|
239
|
-
{
|
|
251
|
+
let (start_idx, mut acc) = if matches!(initial, Value::Null) && !arr_borrow.is_empty() {
|
|
240
252
|
// No initial value: use first element as acc, start from index 1
|
|
241
253
|
(1, arr_borrow[0].clone())
|
|
242
254
|
} else {
|
|
@@ -335,7 +347,9 @@ pub fn flat_map(arr: &Value, callback: &Value) -> Value {
|
|
|
335
347
|
}
|
|
336
348
|
|
|
337
349
|
fn sort_by_impl<F>(arr: &Value, cmp: F) -> Value
|
|
338
|
-
where
|
|
350
|
+
where
|
|
351
|
+
F: FnMut(&Value, &Value) -> std::cmp::Ordering,
|
|
352
|
+
{
|
|
339
353
|
if let Value::Array(arr) = arr {
|
|
340
354
|
arr.borrow_mut().sort_by(cmp);
|
|
341
355
|
Value::Array(Rc::clone(arr))
|
|
@@ -345,7 +359,9 @@ where F: FnMut(&Value, &Value) -> std::cmp::Ordering {
|
|
|
345
359
|
}
|
|
346
360
|
|
|
347
361
|
pub fn sort_default(arr: &Value) -> Value {
|
|
348
|
-
sort_by_impl(arr, |a, b|
|
|
362
|
+
sort_by_impl(arr, |a, b| {
|
|
363
|
+
a.to_display_string().cmp(&b.to_display_string())
|
|
364
|
+
})
|
|
349
365
|
}
|
|
350
366
|
|
|
351
367
|
pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
|
|
@@ -355,7 +371,7 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
|
|
|
355
371
|
let mut indices: Vec<usize> = (0..len).collect();
|
|
356
372
|
let mut elements: Vec<Value> = std::mem::take(&mut *arr_mut);
|
|
357
373
|
let mut args_buf: [Value; 2] = [Value::Null, Value::Null];
|
|
358
|
-
|
|
374
|
+
|
|
359
375
|
indices.sort_by(|&a, &b| {
|
|
360
376
|
args_buf[0] = elements[a].clone();
|
|
361
377
|
args_buf[1] = elements[b].clone();
|
|
@@ -365,8 +381,11 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
|
|
|
365
381
|
_ => std::cmp::Ordering::Equal,
|
|
366
382
|
}
|
|
367
383
|
});
|
|
368
|
-
|
|
369
|
-
*arr_mut = indices
|
|
384
|
+
|
|
385
|
+
*arr_mut = indices
|
|
386
|
+
.into_iter()
|
|
387
|
+
.map(|i| std::mem::replace(&mut elements[i], Value::Null))
|
|
388
|
+
.collect();
|
|
370
389
|
drop(arr_mut);
|
|
371
390
|
Value::Array(Rc::clone(arr))
|
|
372
391
|
} else {
|
|
@@ -380,7 +399,11 @@ fn num_cmp(a: &Value, b: &Value, asc: bool) -> std::cmp::Ordering {
|
|
|
380
399
|
_ => (f64::NAN, f64::NAN),
|
|
381
400
|
};
|
|
382
401
|
let cmp = na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
|
|
383
|
-
if asc {
|
|
402
|
+
if asc {
|
|
403
|
+
cmp
|
|
404
|
+
} else {
|
|
405
|
+
cmp.reverse()
|
|
406
|
+
}
|
|
384
407
|
}
|
|
385
408
|
|
|
386
409
|
pub fn sort_numeric_asc(arr: &Value) -> Value {
|
|
@@ -398,13 +421,21 @@ pub fn sort_by_property_numeric(arr: &Value, prop: &str, asc: bool) -> Value {
|
|
|
398
421
|
let na = get_prop_number(a, &prop_arc);
|
|
399
422
|
let nb = get_prop_number(b, &prop_arc);
|
|
400
423
|
let cmp = na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
|
|
401
|
-
if asc {
|
|
424
|
+
if asc {
|
|
425
|
+
cmp
|
|
426
|
+
} else {
|
|
427
|
+
cmp.reverse()
|
|
428
|
+
}
|
|
402
429
|
})
|
|
403
430
|
}
|
|
404
431
|
|
|
405
432
|
fn get_prop_number(v: &Value, prop: &std::sync::Arc<str>) -> f64 {
|
|
406
433
|
match v {
|
|
407
|
-
Value::Object(o) => o
|
|
434
|
+
Value::Object(o) => o
|
|
435
|
+
.borrow()
|
|
436
|
+
.get(prop.as_ref())
|
|
437
|
+
.map(|v| v.as_number().unwrap_or(f64::NAN))
|
|
438
|
+
.unwrap_or(f64::NAN),
|
|
408
439
|
_ => f64::NAN,
|
|
409
440
|
}
|
|
410
441
|
}
|
|
@@ -82,10 +82,7 @@ fn buffer_source_stub() -> Value {
|
|
|
82
82
|
Arc::from("start"),
|
|
83
83
|
Value::Function(Rc::new(|_| Value::Null)),
|
|
84
84
|
);
|
|
85
|
-
m.insert(
|
|
86
|
-
Arc::from("stop"),
|
|
87
|
-
Value::Function(Rc::new(|_| Value::Null)),
|
|
88
|
-
);
|
|
85
|
+
m.insert(Arc::from("stop"), Value::Function(Rc::new(|_| Value::Null)));
|
|
89
86
|
Value::Object(Rc::new(RefCell::new(m)))
|
|
90
87
|
}
|
|
91
88
|
|
|
@@ -98,10 +95,7 @@ fn oscillator_stub() -> Value {
|
|
|
98
95
|
Arc::from("start"),
|
|
99
96
|
Value::Function(Rc::new(|_| Value::Null)),
|
|
100
97
|
);
|
|
101
|
-
m.insert(
|
|
102
|
-
Arc::from("stop"),
|
|
103
|
-
Value::Function(Rc::new(|_| Value::Null)),
|
|
104
|
-
);
|
|
98
|
+
m.insert(Arc::from("stop"), Value::Function(Rc::new(|_| Value::Null)));
|
|
105
99
|
Value::Object(Rc::new(RefCell::new(m)))
|
|
106
100
|
}
|
|
107
101
|
|
|
@@ -16,29 +16,35 @@ pub fn boolean(args: &[Value]) -> Value {
|
|
|
16
16
|
|
|
17
17
|
/// decodeURI(str)
|
|
18
18
|
pub fn decode_uri(args: &[Value]) -> Value {
|
|
19
|
-
let s = args
|
|
19
|
+
let s = args
|
|
20
|
+
.first()
|
|
21
|
+
.map(Value::to_display_string)
|
|
22
|
+
.unwrap_or_default();
|
|
20
23
|
Value::String(percent_decode(&s).unwrap_or(s).into())
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
/// encodeURI(str)
|
|
24
27
|
pub fn encode_uri(args: &[Value]) -> Value {
|
|
25
|
-
let s = args
|
|
28
|
+
let s = args
|
|
29
|
+
.first()
|
|
30
|
+
.map(Value::to_display_string)
|
|
31
|
+
.unwrap_or_default();
|
|
26
32
|
Value::String(percent_encode(&s).into())
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
/// isFinite(value)
|
|
30
36
|
pub fn is_finite(args: &[Value]) -> Value {
|
|
31
|
-
Value::Bool(
|
|
37
|
+
Value::Bool(
|
|
38
|
+
args.first()
|
|
39
|
+
.is_some_and(|v| matches!(v, Value::Number(n) if n.is_finite())),
|
|
40
|
+
)
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
/// isNaN(value)
|
|
35
44
|
pub fn is_nan(args: &[Value]) -> Value {
|
|
36
|
-
Value::Bool(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|| !matches!(v, Value::Number(_))
|
|
40
|
-
}),
|
|
41
|
-
)
|
|
45
|
+
Value::Bool(args.first().is_none_or(|v| {
|
|
46
|
+
matches!(v, Value::Number(n) if n.is_nan()) || !matches!(v, Value::Number(_))
|
|
47
|
+
}))
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
/// Array.isArray(value)
|
|
@@ -144,12 +150,18 @@ pub fn object_assign(args: &[Value]) -> Value {
|
|
|
144
150
|
|
|
145
151
|
/// parseInt(string, radix?)
|
|
146
152
|
pub fn parse_int(args: &[Value]) -> Value {
|
|
147
|
-
let s = args
|
|
153
|
+
let s = args
|
|
154
|
+
.first()
|
|
155
|
+
.map(Value::to_display_string)
|
|
156
|
+
.unwrap_or_default();
|
|
148
157
|
let s = s.trim();
|
|
149
|
-
let radix = args
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
158
|
+
let radix = args
|
|
159
|
+
.get(1)
|
|
160
|
+
.and_then(|v| match v {
|
|
161
|
+
Value::Number(n) => Some(*n as i32),
|
|
162
|
+
_ => None,
|
|
163
|
+
})
|
|
164
|
+
.unwrap_or(10);
|
|
153
165
|
|
|
154
166
|
if (2..=36).contains(&radix) {
|
|
155
167
|
let prefix: String = s
|
|
@@ -165,7 +177,10 @@ pub fn parse_int(args: &[Value]) -> Value {
|
|
|
165
177
|
|
|
166
178
|
/// parseFloat(string)
|
|
167
179
|
pub fn parse_float(args: &[Value]) -> Value {
|
|
168
|
-
let s = args
|
|
180
|
+
let s = args
|
|
181
|
+
.first()
|
|
182
|
+
.map(Value::to_display_string)
|
|
183
|
+
.unwrap_or_default();
|
|
169
184
|
Value::Number(s.trim().parse().unwrap_or(f64::NAN))
|
|
170
185
|
}
|
|
171
186
|
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
//! and native signatures.
|
|
6
6
|
|
|
7
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
8
|
pub mod construct;
|
|
9
|
+
pub mod globals;
|
|
10
|
+
pub mod helpers;
|
|
11
|
+
pub mod math;
|
|
12
|
+
pub mod object;
|
|
13
|
+
pub mod string;
|
|
14
14
|
|
|
15
15
|
pub use tishlang_core::Value;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//! Math builtin functions.
|
|
2
2
|
|
|
3
|
-
use tishlang_core::Value;
|
|
4
3
|
use crate::helpers::extract_num;
|
|
4
|
+
use tishlang_core::Value;
|
|
5
5
|
|
|
6
6
|
macro_rules! math_unary {
|
|
7
7
|
($name:ident, $op:ident) => {
|
|
@@ -31,14 +31,16 @@ math_unary!(trunc, trunc);
|
|
|
31
31
|
math_unary!(cbrt, cbrt);
|
|
32
32
|
|
|
33
33
|
pub fn min(args: &[Value]) -> Value {
|
|
34
|
-
let n = args
|
|
34
|
+
let n = args
|
|
35
|
+
.iter()
|
|
35
36
|
.filter_map(|v| extract_num(Some(v)))
|
|
36
37
|
.fold(f64::INFINITY, f64::min);
|
|
37
38
|
Value::Number(if n == f64::INFINITY { f64::NAN } else { n })
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
pub fn max(args: &[Value]) -> Value {
|
|
41
|
-
let n = args
|
|
42
|
+
let n = args
|
|
43
|
+
.iter()
|
|
42
44
|
.filter_map(|v| extract_num(Some(v)))
|
|
43
45
|
.fold(f64::NEG_INFINITY, f64::max);
|
|
44
46
|
Value::Number(if n == f64::NEG_INFINITY { f64::NAN } else { n })
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
//! All indices use character (Unicode scalar) positions for consistency with
|
|
4
4
|
//! JavaScript, matching .length and .charAt(). Byte offsets are never exposed.
|
|
5
5
|
|
|
6
|
+
use crate::helpers::normalize_index;
|
|
6
7
|
use std::cell::RefCell;
|
|
7
8
|
use std::rc::Rc;
|
|
8
9
|
use std::sync::Arc;
|
|
9
10
|
use tishlang_core::Value;
|
|
10
|
-
use crate::helpers::normalize_index;
|
|
11
11
|
|
|
12
12
|
/// Byte offset -> character index.
|
|
13
13
|
fn byte_to_char_index(s: &str, byte_offset: usize) -> usize {
|
|
@@ -152,7 +152,10 @@ pub fn slice(s: &Value, start: &Value, end: &Value) -> Value {
|
|
|
152
152
|
if let Value::String(s) = s {
|
|
153
153
|
let chars: Vec<char> = s.chars().collect();
|
|
154
154
|
let len = chars.len() as i64;
|
|
155
|
-
let (si, ei) = (
|
|
155
|
+
let (si, ei) = (
|
|
156
|
+
normalize_index(start, len, 0),
|
|
157
|
+
normalize_index(end, len, len as usize),
|
|
158
|
+
);
|
|
156
159
|
let result: String = if si < ei {
|
|
157
160
|
chars[si..ei].iter().collect()
|
|
158
161
|
} else {
|
|
@@ -193,7 +196,8 @@ pub fn split(s: &Value, sep: &Value) -> Value {
|
|
|
193
196
|
Value::String(ss) => ss.as_ref(),
|
|
194
197
|
_ => return Value::Array(Rc::new(RefCell::new(vec![Value::String(Arc::clone(s))]))),
|
|
195
198
|
};
|
|
196
|
-
let parts: Vec<Value> = s
|
|
199
|
+
let parts: Vec<Value> = s
|
|
200
|
+
.split(separator)
|
|
197
201
|
.map(|p| Value::String(p.into()))
|
|
198
202
|
.collect();
|
|
199
203
|
Value::Array(Rc::new(RefCell::new(parts)))
|
|
@@ -244,9 +248,19 @@ pub fn ends_with(s: &Value, search: &Value) -> Value {
|
|
|
244
248
|
|
|
245
249
|
fn replace_impl(s: &Value, search: &Value, replacement: &Value, all: bool) -> Value {
|
|
246
250
|
if let Value::String(s) = s {
|
|
247
|
-
let search_str = match search {
|
|
248
|
-
|
|
249
|
-
|
|
251
|
+
let search_str = match search {
|
|
252
|
+
Value::String(ss) => ss.as_ref(),
|
|
253
|
+
_ => return Value::String(Arc::clone(s)),
|
|
254
|
+
};
|
|
255
|
+
let repl_str = match replacement {
|
|
256
|
+
Value::String(ss) => ss.as_ref(),
|
|
257
|
+
_ => "",
|
|
258
|
+
};
|
|
259
|
+
let result = if all {
|
|
260
|
+
s.replace(search_str, repl_str)
|
|
261
|
+
} else {
|
|
262
|
+
s.replacen(search_str, repl_str, 1)
|
|
263
|
+
};
|
|
250
264
|
Value::String(result.into())
|
|
251
265
|
} else {
|
|
252
266
|
Value::Null
|
|
@@ -267,8 +281,13 @@ fn char_at_idx(s: &str, idx: usize) -> Option<char> {
|
|
|
267
281
|
|
|
268
282
|
pub fn char_at(s: &Value, idx: &Value) -> Value {
|
|
269
283
|
if let Value::String(s) = s {
|
|
270
|
-
let idx = match idx {
|
|
271
|
-
|
|
284
|
+
let idx = match idx {
|
|
285
|
+
Value::Number(n) => *n as usize,
|
|
286
|
+
_ => 0,
|
|
287
|
+
};
|
|
288
|
+
char_at_idx(s, idx)
|
|
289
|
+
.map(|c| Value::String(c.to_string().into()))
|
|
290
|
+
.unwrap_or(Value::String("".into()))
|
|
272
291
|
} else {
|
|
273
292
|
Value::Null
|
|
274
293
|
}
|
|
@@ -276,8 +295,13 @@ pub fn char_at(s: &Value, idx: &Value) -> Value {
|
|
|
276
295
|
|
|
277
296
|
pub fn char_code_at(s: &Value, idx: &Value) -> Value {
|
|
278
297
|
if let Value::String(s) = s {
|
|
279
|
-
let idx = match idx {
|
|
280
|
-
|
|
298
|
+
let idx = match idx {
|
|
299
|
+
Value::Number(n) => *n as usize,
|
|
300
|
+
_ => 0,
|
|
301
|
+
};
|
|
302
|
+
char_at_idx(s, idx)
|
|
303
|
+
.map(|c| Value::Number(c as u32 as f64))
|
|
304
|
+
.unwrap_or(Value::Number(f64::NAN))
|
|
281
305
|
} else {
|
|
282
306
|
Value::Null
|
|
283
307
|
}
|
|
@@ -311,7 +335,11 @@ fn pad_impl(s: &Value, target_len: &Value, pad: &Value, at_start: bool) -> Value
|
|
|
311
335
|
}
|
|
312
336
|
let needed = target_len - char_count;
|
|
313
337
|
let padding: String = pad_str.chars().cycle().take(needed).collect();
|
|
314
|
-
let result = if at_start {
|
|
338
|
+
let result = if at_start {
|
|
339
|
+
format!("{}{}", padding, s)
|
|
340
|
+
} else {
|
|
341
|
+
format!("{}{}", s, padding)
|
|
342
|
+
};
|
|
315
343
|
Value::String(result.into())
|
|
316
344
|
} else {
|
|
317
345
|
Value::Null
|
|
@@ -382,16 +410,31 @@ mod tests {
|
|
|
382
410
|
fn includes_basic() {
|
|
383
411
|
assert_same!(includes(&s("hello"), &s("ll"), None), Value::Bool(true));
|
|
384
412
|
assert_same!(includes(&s("hello"), &s("x"), None), Value::Bool(false));
|
|
385
|
-
assert_same!(
|
|
386
|
-
|
|
413
|
+
assert_same!(
|
|
414
|
+
includes(&s("hello"), &s("l"), Some(&n(3.0))),
|
|
415
|
+
Value::Bool(true)
|
|
416
|
+
);
|
|
417
|
+
assert_same!(
|
|
418
|
+
includes(&s("hello"), &s("l"), Some(&n(4.0))),
|
|
419
|
+
Value::Bool(false)
|
|
420
|
+
);
|
|
387
421
|
}
|
|
388
422
|
|
|
389
423
|
#[test]
|
|
390
424
|
fn includes_negative_from() {
|
|
391
|
-
assert_same!(
|
|
392
|
-
|
|
425
|
+
assert_same!(
|
|
426
|
+
includes(&s("hello"), &s("o"), Some(&n(-1.0))),
|
|
427
|
+
Value::Bool(true)
|
|
428
|
+
);
|
|
429
|
+
assert_same!(
|
|
430
|
+
includes(&s("hello"), &s("h"), Some(&n(-5.0))),
|
|
431
|
+
Value::Bool(true)
|
|
432
|
+
);
|
|
393
433
|
// fromIndex -1 → start at len-1 = 1 ("i" only), "h" not found
|
|
394
|
-
assert_same!(
|
|
434
|
+
assert_same!(
|
|
435
|
+
includes(&s("hi"), &s("h"), Some(&n(-1.0))),
|
|
436
|
+
Value::Bool(false)
|
|
437
|
+
);
|
|
395
438
|
}
|
|
396
439
|
|
|
397
440
|
#[test]
|
|
@@ -466,7 +509,10 @@ mod tests {
|
|
|
466
509
|
|
|
467
510
|
#[test]
|
|
468
511
|
fn last_index_of_basic() {
|
|
469
|
-
assert_same!(
|
|
512
|
+
assert_same!(
|
|
513
|
+
last_index_of(&s("abcabc"), &s("a"), &n(f64::INFINITY)),
|
|
514
|
+
n(3.0)
|
|
515
|
+
);
|
|
470
516
|
assert_same!(last_index_of(&s("abcabc"), &s("a"), &n(2.0)), n(0.0));
|
|
471
517
|
assert_same!(last_index_of(&s("hello"), &s("l"), &n(3.0)), n(3.0));
|
|
472
518
|
assert_same!(last_index_of(&s("hello"), &s("l"), &n(1.0)), n(-1.0));
|
|
@@ -490,8 +536,14 @@ mod tests {
|
|
|
490
536
|
|
|
491
537
|
#[test]
|
|
492
538
|
fn last_index_of_unicode() {
|
|
493
|
-
assert_same!(
|
|
494
|
-
|
|
539
|
+
assert_same!(
|
|
540
|
+
last_index_of(&s("😀a😀"), &s("a"), &n(f64::INFINITY)),
|
|
541
|
+
n(1.0)
|
|
542
|
+
);
|
|
543
|
+
assert_same!(
|
|
544
|
+
last_index_of(&s("😀a😀"), &s("😀"), &n(f64::INFINITY)),
|
|
545
|
+
n(2.0)
|
|
546
|
+
);
|
|
495
547
|
}
|
|
496
548
|
|
|
497
549
|
#[test]
|