@tishlang/tish-lsp 2.39.0 → 2.43.8
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 +4 -1
- package/bin/tish-lsp +0 -0
- package/crates/tish/src/cli_help.rs +28 -0
- package/crates/tish/src/main.rs +41 -5
- package/crates/tish/tests/scope_merge.rs +79 -0
- package/crates/tish_build_utils/src/lib.rs +27 -1
- package/crates/tish_builtins/Cargo.toml +14 -6
- package/crates/tish_builtins/src/array.rs +130 -54
- package/crates/tish_builtins/src/collections.rs +9 -5
- package/crates/tish_builtins/src/construct.rs +7 -1
- package/crates/tish_builtins/src/date.rs +15 -1
- package/crates/tish_builtins/src/globals.rs +38 -3
- package/crates/tish_builtins/src/helpers.rs +7 -3
- package/crates/tish_builtins/src/iterator.rs +5 -1
- package/crates/tish_builtins/src/lib.rs +3 -0
- package/crates/tish_builtins/src/math.rs +14 -0
- package/crates/tish_builtins/src/number.rs +72 -6
- package/crates/tish_builtins/src/object.rs +6 -1
- package/crates/tish_builtins/src/string.rs +13 -2
- package/crates/tish_builtins/src/symbol.rs +17 -17
- package/crates/tish_builtins/src/typedarrays.rs +9 -2
- package/crates/tish_bytecode/src/compiler.rs +76 -2
- package/crates/tish_bytecode/src/lib.rs +1 -1
- package/crates/tish_bytecode/src/opcode.rs +77 -2
- package/crates/tish_compile/src/codegen.rs +806 -46
- package/crates/tish_compile/src/infer.rs +37 -41
- package/crates/tish_compile/src/lib.rs +13 -1
- package/crates/tish_compile/src/platform_resolve.rs +361 -0
- package/crates/tish_compile/src/resolve.rs +148 -10
- package/crates/tish_compile/src/schemes.rs +278 -0
- package/crates/tish_compile/src/types.rs +141 -2
- package/crates/tish_compile/tests/perf_codegen_320.rs +2 -2
- package/crates/tish_compile/tests/perf_codegen_module_const_forof.rs +3 -1
- package/crates/tish_compile/tests/platform_resolve_cli.rs +164 -0
- package/crates/tish_compile/tests/regr_gba_numerics_gated.rs +58 -0
- package/crates/tish_compiler_wasm/Cargo.toml +1 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +56 -6
- package/crates/tish_core/Cargo.toml +39 -4
- package/crates/tish_core/src/compat.rs +506 -0
- package/crates/tish_core/src/console_style.rs +19 -1
- package/crates/tish_core/src/json.rs +34 -10
- package/crates/tish_core/src/lib.rs +121 -25
- package/crates/tish_core/src/macros.rs +1 -1
- package/crates/tish_core/src/shape.rs +4 -2
- package/crates/tish_core/src/uri.rs +8 -1
- package/crates/tish_core/src/value.rs +182 -54
- package/crates/tish_core/src/vmref.rs +17 -5
- package/crates/tish_eval/Cargo.toml +2 -0
- package/crates/tish_eval/src/eval.rs +90 -3
- package/crates/tish_eval/src/value.rs +8 -0
- package/crates/tish_eval/src/value_convert.rs +316 -21
- package/crates/tish_lsp/Cargo.toml +3 -1
- package/crates/tish_lsp/src/import_goto.rs +41 -14
- package/crates/tish_lsp/src/main.rs +18 -0
- package/crates/tish_native/src/build.rs +246 -0
- package/crates/tish_native/src/config.rs +20 -0
- package/crates/tish_native/src/lib.rs +16 -6
- package/crates/tish_parser/src/lib.rs +28 -0
- package/crates/tish_parser/src/parser.rs +25 -4
- package/crates/tish_runtime/src/lib.rs +54 -9
- package/crates/tish_runtime_gba/Cargo.lock +837 -0
- package/crates/tish_runtime_gba/Cargo.toml +16 -0
- package/crates/tish_runtime_gba/src/gba.rs +211 -0
- package/crates/tish_runtime_gba/src/lib.rs +620 -0
- package/crates/tish_vm/src/jit.rs +1446 -164
- package/crates/tish_vm/src/vm.rs +535 -173
- package/justfile +9 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-lsp +0 -0
- package/platform/darwin-x64/tish-lsp +0 -0
- package/platform/linux-arm64/tish-lsp +0 -0
- package/platform/linux-x64/tish-lsp +0 -0
- package/platform/win32-x64/tish-lsp.exe +0 -0
package/Cargo.toml
CHANGED
|
@@ -36,7 +36,10 @@ members = [
|
|
|
36
36
|
]
|
|
37
37
|
# Generated native-build projects (`tish build`) land under `target/` when TMPDIR points there;
|
|
38
38
|
# never enroll anything beneath `target/` as a workspace member.
|
|
39
|
-
|
|
39
|
+
# GBA-only: depends on `agb`, which builds solely for thumbv4t. Excluded so host
|
|
40
|
+
# `cargo build` doesn't try to cross-compile it; built explicitly with the target
|
|
41
|
+
# (and consumed by path from generated GBA projects, not as a workspace member).
|
|
42
|
+
exclude = ["target", "crates/tish_runtime_gba"]
|
|
40
43
|
resolver = "2"
|
|
41
44
|
|
|
42
45
|
[workspace.package]
|
package/bin/tish-lsp
CHANGED
|
Binary file
|
|
@@ -557,6 +557,12 @@ pub(crate) struct BuildArgs {
|
|
|
557
557
|
pub crate_type: String,
|
|
558
558
|
#[arg(long, help_heading = "Options")]
|
|
559
559
|
pub no_optimize: bool,
|
|
560
|
+
/// Platform for RN-style module resolution (`macos`, `ios`, `windows`, `linux`, `web`).
|
|
561
|
+
#[arg(long, value_name = "NAME", help_heading = "Options")]
|
|
562
|
+
pub platform: Option<String>,
|
|
563
|
+
/// Surface for module resolution: `native`, `webview`, or `web` (also `TISH_SURFACE`).
|
|
564
|
+
#[arg(long, value_name = "NAME", help_heading = "Options")]
|
|
565
|
+
pub surface: Option<String>,
|
|
560
566
|
/// For `--target js` project builds: emit `OUTPUT.js.map` and `//# sourceMappingURL=…` so JS/TS tools can jump to original `.tish` (implies `--no-optimize` for that build).
|
|
561
567
|
#[arg(long, help_heading = "Options")]
|
|
562
568
|
pub source_map: bool,
|
|
@@ -599,6 +605,12 @@ pub(crate) struct CompileModuleArgs {
|
|
|
599
605
|
/// Project root for resolving bare specifiers / `node_modules` (defaults to the file's parent).
|
|
600
606
|
#[arg(long, value_name = "DIR", help_heading = "Options")]
|
|
601
607
|
pub project_root: Option<String>,
|
|
608
|
+
/// Platform for RN-style module resolution (`macos`, `ios`, `windows`, `linux`, `web`).
|
|
609
|
+
#[arg(long, value_name = "NAME", help_heading = "Options")]
|
|
610
|
+
pub platform: Option<String>,
|
|
611
|
+
/// Surface for module resolution: `native`, `webview`, or `web`.
|
|
612
|
+
#[arg(long, value_name = "NAME", help_heading = "Options")]
|
|
613
|
+
pub surface: Option<String>,
|
|
602
614
|
/// Disable AST optimizations (forced on when a source map is emitted).
|
|
603
615
|
#[arg(long, help_heading = "Options")]
|
|
604
616
|
pub no_optimize: bool,
|
|
@@ -626,6 +638,22 @@ pub(crate) enum Commands {
|
|
|
626
638
|
/// Compile a single `.tish` module to one ES module (for Vite dev / HMR); prints to stdout
|
|
627
639
|
#[command(name = "compile-module")]
|
|
628
640
|
CompileModule(CompileModuleArgs),
|
|
641
|
+
/// Resolve a relative `.tish` import with platform/surface cascade (for Vite / tooling)
|
|
642
|
+
#[command(name = "resolve-id")]
|
|
643
|
+
ResolveId {
|
|
644
|
+
/// Import specifier (must be relative, e.g. `./Button` or `./Button.tish`).
|
|
645
|
+
#[arg(required = true, value_name = "SOURCE", help_heading = "Arguments")]
|
|
646
|
+
source: String,
|
|
647
|
+
/// Absolute path of the importing file (directory used as resolve base).
|
|
648
|
+
#[arg(long, value_name = "FILE", help_heading = "Options")]
|
|
649
|
+
importer: Option<String>,
|
|
650
|
+
/// Platform token (`macos`, `ios`, `windows`, `linux`, `web`).
|
|
651
|
+
#[arg(long, value_name = "NAME", help_heading = "Options")]
|
|
652
|
+
platform: Option<String>,
|
|
653
|
+
/// Surface token (`native`, `webview`, `web`).
|
|
654
|
+
#[arg(long, value_name = "NAME", help_heading = "Options")]
|
|
655
|
+
surface: Option<String>,
|
|
656
|
+
},
|
|
629
657
|
/// Parse and dump AST
|
|
630
658
|
#[command(name = "dump-ast")]
|
|
631
659
|
DumpAst {
|
package/crates/tish/src/main.rs
CHANGED
|
@@ -73,7 +73,8 @@ fn native_build_features_from_cli(cli_features: &[String]) -> Vec<String> {
|
|
|
73
73
|
fn argv_with_implicit_run(mut argv: Vec<String>) -> Vec<String> {
|
|
74
74
|
if argv.len() >= 2 {
|
|
75
75
|
let first = argv[1].as_str();
|
|
76
|
-
const SUBCOMMANDS: &[&str] =
|
|
76
|
+
const SUBCOMMANDS: &[&str] =
|
|
77
|
+
&["run", "repl", "build", "compile-module", "resolve-id", "dump-ast"];
|
|
77
78
|
let looks_like_file = !first.starts_with('-') && !SUBCOMMANDS.contains(&first);
|
|
78
79
|
if looks_like_file {
|
|
79
80
|
argv.insert(1, "run".to_string());
|
|
@@ -126,6 +127,13 @@ fn main() {
|
|
|
126
127
|
if let Some(mode) = &a.check {
|
|
127
128
|
std::env::set_var("TISH_CHECK", mode);
|
|
128
129
|
}
|
|
130
|
+
if let Err(e) = tishlang_compile::apply_resolve_env(
|
|
131
|
+
a.platform.as_deref(),
|
|
132
|
+
a.surface.as_deref(),
|
|
133
|
+
) {
|
|
134
|
+
eprintln!("Error: {}", e);
|
|
135
|
+
std::process::exit(1);
|
|
136
|
+
}
|
|
129
137
|
build_file(
|
|
130
138
|
&a.file,
|
|
131
139
|
&a.output,
|
|
@@ -140,7 +148,30 @@ fn main() {
|
|
|
140
148
|
&a.jsx_import_source,
|
|
141
149
|
)
|
|
142
150
|
}
|
|
143
|
-
Some(Commands::CompileModule(a)) =>
|
|
151
|
+
Some(Commands::CompileModule(a)) => {
|
|
152
|
+
if let Err(e) = tishlang_compile::apply_resolve_env(
|
|
153
|
+
a.platform.as_deref(),
|
|
154
|
+
a.surface.as_deref(),
|
|
155
|
+
) {
|
|
156
|
+
eprintln!("Error: {}", e);
|
|
157
|
+
std::process::exit(1);
|
|
158
|
+
}
|
|
159
|
+
compile_module(&a)
|
|
160
|
+
}
|
|
161
|
+
Some(Commands::ResolveId {
|
|
162
|
+
source,
|
|
163
|
+
importer,
|
|
164
|
+
platform,
|
|
165
|
+
surface,
|
|
166
|
+
}) => tishlang_compile::resolve_id_public(
|
|
167
|
+
&source,
|
|
168
|
+
importer.as_deref(),
|
|
169
|
+
platform.as_deref(),
|
|
170
|
+
surface.as_deref(),
|
|
171
|
+
)
|
|
172
|
+
.map(|path| {
|
|
173
|
+
println!("{path}");
|
|
174
|
+
}),
|
|
144
175
|
Some(Commands::DumpAst {
|
|
145
176
|
file,
|
|
146
177
|
ignore_indent,
|
|
@@ -883,9 +914,9 @@ fn build_file(
|
|
|
883
914
|
.map_err(|e| e.to_string());
|
|
884
915
|
}
|
|
885
916
|
|
|
886
|
-
if target != "native" {
|
|
917
|
+
if target != "native" && target != "gba" {
|
|
887
918
|
return Err(format!(
|
|
888
|
-
"Unknown target: {}. Use 'native', 'js', 'wasm', 'wasm-gpu', 'wasi', or 'bytecode'.",
|
|
919
|
+
"Unknown target: {}. Use 'native', 'gba', 'js', 'wasm', 'wasm-gpu', 'wasi', or 'bytecode'.",
|
|
889
920
|
target
|
|
890
921
|
));
|
|
891
922
|
}
|
|
@@ -899,7 +930,12 @@ fn build_file(
|
|
|
899
930
|
});
|
|
900
931
|
let features: Vec<String> = native_build_features_from_cli(cli_features);
|
|
901
932
|
|
|
902
|
-
let build_config = if
|
|
933
|
+
let build_config = if target == "gba" {
|
|
934
|
+
if is_js {
|
|
935
|
+
return Err("tish build --target gba requires a .tish entry file.".to_string());
|
|
936
|
+
}
|
|
937
|
+
tishlang_native::NativeBuildConfig::gba()
|
|
938
|
+
} else if let Some(triple) = ios_triple {
|
|
903
939
|
tishlang_native::NativeBuildConfig::ios_staticlib(triple)
|
|
904
940
|
} else if crate_type == "staticlib" {
|
|
905
941
|
tishlang_native::NativeBuildConfig {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//! Regression for #38: a merged `@scope/pkg` package (declared via `tish.module`, `exports.tish`,
|
|
2
|
+
//! etc.) must resolve as merged **Tish source**, not be routed to the native Rust-crate path (which
|
|
3
|
+
//! would reject it with "not a Tish native module" or mis-treat it as a crate). Native `@scope`
|
|
4
|
+
//! crates (marked with `tish.crate` / `tish.rustDependencies`) remain native and are exercised by
|
|
5
|
+
//! the downstream regression (tish-apple/tish-macos).
|
|
6
|
+
|
|
7
|
+
use std::fs;
|
|
8
|
+
|
|
9
|
+
use tishlang_ast::{Expr, Statement};
|
|
10
|
+
use tishlang_compile::{merge_modules, resolve_project};
|
|
11
|
+
|
|
12
|
+
/// Build a tempdir project whose `main.tish` imports a merged `@test/greet` package. `tish_field`
|
|
13
|
+
/// is the extra package.json snippet declaring how the package presents itself (`,"tish":{…}` etc.).
|
|
14
|
+
fn setup(tish_field: &str) -> tempfile::TempDir {
|
|
15
|
+
let dir = tempfile::tempdir().unwrap();
|
|
16
|
+
let root = dir.path();
|
|
17
|
+
let pkg = root.join("node_modules/@test/greet");
|
|
18
|
+
fs::create_dir_all(&pkg).unwrap();
|
|
19
|
+
fs::write(
|
|
20
|
+
pkg.join("package.json"),
|
|
21
|
+
format!(r#"{{"name":"@test/greet","version":"1.0.0"{tish_field}}}"#),
|
|
22
|
+
)
|
|
23
|
+
.unwrap();
|
|
24
|
+
fs::write(pkg.join("index.tish"), "export fn greet() { return \"hi\" }\n").unwrap();
|
|
25
|
+
fs::write(
|
|
26
|
+
root.join("main.tish"),
|
|
27
|
+
"import { greet } from \"@test/greet\"\nconsole.log(greet())\n",
|
|
28
|
+
)
|
|
29
|
+
.unwrap();
|
|
30
|
+
dir
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn assert_merged_as_source(dir: &tempfile::TempDir) {
|
|
34
|
+
let root = dir.path();
|
|
35
|
+
let modules = resolve_project(&root.join("main.tish"), Some(root)).expect("resolve");
|
|
36
|
+
let merged = merge_modules(modules).expect("merge");
|
|
37
|
+
// Merged as source ⇒ the package's `greet` fn is folded into the top-level program.
|
|
38
|
+
let has_greet = merged
|
|
39
|
+
.program
|
|
40
|
+
.statements
|
|
41
|
+
.iter()
|
|
42
|
+
.any(|s| matches!(s, Statement::FunDecl { name, .. } if name.as_ref() == "greet"));
|
|
43
|
+
assert!(
|
|
44
|
+
has_greet,
|
|
45
|
+
"merged @scope package's fn must be folded into the program (merged as Tish source)"
|
|
46
|
+
);
|
|
47
|
+
// ...and NOT lowered to a native module load (the #38 mis-routing).
|
|
48
|
+
let native_scope = merged.program.statements.iter().any(|s| {
|
|
49
|
+
matches!(
|
|
50
|
+
s,
|
|
51
|
+
Statement::VarDecl { init: Some(Expr::NativeModuleLoad { spec, .. }), .. }
|
|
52
|
+
if spec.contains("@test")
|
|
53
|
+
)
|
|
54
|
+
});
|
|
55
|
+
assert!(
|
|
56
|
+
!native_scope,
|
|
57
|
+
"a merged @scope package must NOT be routed to the native Rust-crate path (#38)"
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#[test]
|
|
62
|
+
fn scope_tish_module_true_merges_as_source() {
|
|
63
|
+
assert_merged_as_source(&setup(r#","tish":{"module":true}"#));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#[test]
|
|
67
|
+
fn scope_tish_module_string_path_merges_as_source() {
|
|
68
|
+
assert_merged_as_source(&setup(r#","tish":{"module":"index.tish"}"#));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[test]
|
|
72
|
+
fn scope_exports_tish_merges_as_source() {
|
|
73
|
+
assert_merged_as_source(&setup(r#","exports":{"tish":"./index.tish"}"#));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[test]
|
|
77
|
+
fn scope_plain_main_merges_as_source() {
|
|
78
|
+
assert_merged_as_source(&setup(r#","main":"index.tish""#));
|
|
79
|
+
}
|
|
@@ -340,7 +340,22 @@ pub fn find_crate_path(crate_name: &str) -> Result<PathBuf, String> {
|
|
|
340
340
|
|
|
341
341
|
/// Sanitize a user-chosen output stem for Cargo `[lib]` / `[[bin]]` target names.
|
|
342
342
|
pub fn cargo_target_name(stem: &str) -> String {
|
|
343
|
-
|
|
343
|
+
// The result is emitted verbatim into a generated `Cargo.toml` (`name = "<stem>"`) and used as
|
|
344
|
+
// the crate/target name, so it must be a valid Rust identifier. Map every char that isn't
|
|
345
|
+
// ASCII-alphanumeric or `_` to `_` — a `-` (cargo normalizes it to `_` anyway), but also a `.`,
|
|
346
|
+
// space, or `"` from an odd `-o` path, which would otherwise break or inject the manifest.
|
|
347
|
+
// A leading digit is prefixed with `_`; an empty result falls back to a fixed default.
|
|
348
|
+
let mut name: String = stem
|
|
349
|
+
.chars()
|
|
350
|
+
.map(|c| if c.is_ascii_alphanumeric() || c == '_' { c } else { '_' })
|
|
351
|
+
.collect();
|
|
352
|
+
if name.is_empty() {
|
|
353
|
+
return "tish_output".to_string();
|
|
354
|
+
}
|
|
355
|
+
if name.starts_with(|c: char| c.is_ascii_digit()) {
|
|
356
|
+
name.insert(0, '_');
|
|
357
|
+
}
|
|
358
|
+
name
|
|
344
359
|
}
|
|
345
360
|
|
|
346
361
|
/// Create a temp build directory with src subdir.
|
|
@@ -428,6 +443,17 @@ mod protoc_tests {
|
|
|
428
443
|
assert_eq!(cargo_target_name("hello-ios"), "hello_ios");
|
|
429
444
|
assert_eq!(cargo_target_name("tish_out"), "tish_out");
|
|
430
445
|
}
|
|
446
|
+
|
|
447
|
+
#[test]
|
|
448
|
+
fn cargo_target_name_sanitizes_invalid_chars() {
|
|
449
|
+
// Quotes / dots / spaces from an odd `-o` stem can't break or inject the manifest.
|
|
450
|
+
assert_eq!(cargo_target_name("a\"b"), "a_b");
|
|
451
|
+
assert_eq!(cargo_target_name("my.app"), "my_app");
|
|
452
|
+
assert_eq!(cargo_target_name("my game"), "my_game");
|
|
453
|
+
// A leading digit is made identifier-safe; empty falls back.
|
|
454
|
+
assert_eq!(cargo_target_name("3d"), "_3d");
|
|
455
|
+
assert_eq!(cargo_target_name(""), "tish_output");
|
|
456
|
+
}
|
|
431
457
|
}
|
|
432
458
|
|
|
433
459
|
/// Find the built static library in target/release (or target/$TRIPLE/release).
|
|
@@ -8,15 +8,23 @@ description = "Shared builtin implementations for Tish (array, string, object, m
|
|
|
8
8
|
license-file = { workspace = true }
|
|
9
9
|
repository = { workspace = true }
|
|
10
10
|
[dependencies]
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
# std-only deps, optional so the `portable` build drops them (rand needs OS
|
|
12
|
+
# entropy; ahash needs atomics — both replaced by tishlang_core hooks/FxHasher).
|
|
13
|
+
ahash = { version = "0.8.11", optional = true }
|
|
14
|
+
indexmap = { version = "2", default-features = false }
|
|
15
|
+
rand = { version = "0.10.1", optional = true }
|
|
16
|
+
# default-features=false keeps it no_std (it works on core+alloc via tinyvec);
|
|
17
|
+
# tish only uses NFC/NFD/NFKC/NFKD, none of which need std.
|
|
18
|
+
unicode-normalization = { version = "0.1.25", default-features = false }
|
|
19
|
+
tishlang_core = { path = "../tish_core", version = ">=0.1", default-features = false }
|
|
16
20
|
|
|
17
21
|
[features]
|
|
18
|
-
default = []
|
|
22
|
+
default = ["std"]
|
|
23
|
+
std = ["tishlang_core/std", "dep:ahash", "dep:rand"]
|
|
19
24
|
regex = ["tishlang_core/regex"]
|
|
25
|
+
# no_std + alloc build for GBA. Inherits tishlang_core's portable vocabulary and
|
|
26
|
+
# its clock/RNG hooks; float math routes through tishlang_core::FloatExt (libm).
|
|
27
|
+
portable = ["tishlang_core/portable"]
|
|
20
28
|
# Propagate the `send-values` feature from `tishlang_core` so that closures
|
|
21
29
|
# handed to us via `NativeFn` pick up the `Send + Sync` bound automatically
|
|
22
30
|
# when multi-threaded `Value`s are enabled upstream.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
//! Array builtin methods.
|
|
2
2
|
|
|
3
|
+
#[cfg(feature = "portable")]
|
|
4
|
+
#[allow(unused_imports)]
|
|
5
|
+
use alloc::{borrow::ToOwned, boxed::Box, format, string::{String, ToString}, vec, vec::Vec};
|
|
6
|
+
|
|
3
7
|
use crate::helpers::normalize_index;
|
|
4
8
|
use tishlang_core::Value;
|
|
5
9
|
use tishlang_core::VmRef;
|
|
@@ -18,7 +22,7 @@ pub fn from_vec(v: Vec<Value>) -> Value {
|
|
|
18
22
|
pub fn snapshot_values(arr: &Value) -> Vec<Value> {
|
|
19
23
|
match arr {
|
|
20
24
|
Value::Array(a) => a.borrow().clone(),
|
|
21
|
-
Value::NumberArray(a) => a.borrow().
|
|
25
|
+
Value::NumberArray(a) => a.borrow().to_values(),
|
|
22
26
|
_ => Vec::new(),
|
|
23
27
|
}
|
|
24
28
|
}
|
|
@@ -30,19 +34,24 @@ pub fn snapshot_values(arr: &Value) -> Vec<Value> {
|
|
|
30
34
|
/// unboxed f64 fast path for it too. The `None` result drives the fused lowering back to its boxed
|
|
31
35
|
/// loop, so any non-numeric element is handled with identical `Value` semantics.
|
|
32
36
|
pub fn as_f64_snapshot(arr: &Value) -> Option<Vec<f64>> {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
match v {
|
|
40
|
-
Value::Number(n) => out.push(*n),
|
|
41
|
-
_ => return None,
|
|
42
|
-
}
|
|
37
|
+
let extract = |vals: &[Value]| -> Option<Vec<f64>> {
|
|
38
|
+
let mut out: Vec<f64> = Vec::with_capacity(vals.len());
|
|
39
|
+
for v in vals {
|
|
40
|
+
match v {
|
|
41
|
+
Value::Number(n) => out.push(*n),
|
|
42
|
+
_ => return None,
|
|
43
43
|
}
|
|
44
|
-
Some(out)
|
|
45
44
|
}
|
|
45
|
+
Some(out)
|
|
46
|
+
};
|
|
47
|
+
match arr {
|
|
48
|
+
// Still packed: clone the f64s directly. Deopted: extract from the boxed values (None if
|
|
49
|
+
// any is non-numeric — the caller then uses the boxed path).
|
|
50
|
+
Value::NumberArray(a) => match a.borrow().as_packed() {
|
|
51
|
+
Some(v) => Some(v.clone()),
|
|
52
|
+
None => extract(a.borrow().as_boxed().map(|v| v.as_slice()).unwrap_or(&[])),
|
|
53
|
+
},
|
|
54
|
+
Value::Array(a) => extract(&a.borrow()),
|
|
46
55
|
_ => None,
|
|
47
56
|
}
|
|
48
57
|
}
|
|
@@ -60,10 +69,10 @@ pub fn len(arr: &Value) -> Option<usize> {
|
|
|
60
69
|
/// can use this deopt helper rather than changing every `if let Value::Array` branch.
|
|
61
70
|
/// Returns the original value unchanged for anything that isn't a `NumberArray`.
|
|
62
71
|
#[inline]
|
|
63
|
-
fn as_boxed_array(arr: &Value) ->
|
|
72
|
+
fn as_boxed_array(arr: &Value) -> alloc::borrow::Cow<'_, Value> {
|
|
64
73
|
match arr {
|
|
65
|
-
Value::NumberArray(na) =>
|
|
66
|
-
other =>
|
|
74
|
+
Value::NumberArray(na) => alloc::borrow::Cow::Owned(Value::materialize_number_array(na)),
|
|
75
|
+
other => alloc::borrow::Cow::Borrowed(other),
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
|
|
@@ -81,7 +90,9 @@ fn packed_snapshot<'c>(
|
|
|
81
90
|
callback: &'c Value,
|
|
82
91
|
) -> Option<(Vec<f64>, &'c tishlang_core::NativeFn)> {
|
|
83
92
|
match (arr, callback) {
|
|
84
|
-
(Value::NumberArray(na), Value::Function(cb)) =>
|
|
93
|
+
(Value::NumberArray(na), Value::Function(cb)) => {
|
|
94
|
+
na.borrow().as_packed().map(|v| (v.clone(), cb))
|
|
95
|
+
}
|
|
85
96
|
_ => None,
|
|
86
97
|
}
|
|
87
98
|
}
|
|
@@ -282,8 +293,20 @@ pub fn shuffle(arr: &Value) -> Value {
|
|
|
282
293
|
let arr = as_boxed_array(arr); let arr = &*arr;
|
|
283
294
|
if let Value::Array(arr) = arr {
|
|
284
295
|
let mut v = arr.borrow().clone();
|
|
285
|
-
|
|
286
|
-
|
|
296
|
+
#[cfg(not(feature = "portable"))]
|
|
297
|
+
{
|
|
298
|
+
use rand::seq::SliceRandom;
|
|
299
|
+
v.shuffle(&mut rand::rng());
|
|
300
|
+
}
|
|
301
|
+
// Fisher-Yates with the runtime RNG hook (no `rand` on GBA).
|
|
302
|
+
#[cfg(feature = "portable")]
|
|
303
|
+
{
|
|
304
|
+
let n = v.len();
|
|
305
|
+
for i in (1..n).rev() {
|
|
306
|
+
let j = (tishlang_core::next_u64() % (i as u64 + 1)) as usize;
|
|
307
|
+
v.swap(i, j);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
287
310
|
Value::Array(VmRef::new(v))
|
|
288
311
|
} else {
|
|
289
312
|
Value::Null
|
|
@@ -404,10 +427,13 @@ pub fn concat(arr: &Value, args: &[Value]) -> Value {
|
|
|
404
427
|
if let Value::Array(arr) = arr {
|
|
405
428
|
let mut result = arr.borrow().clone();
|
|
406
429
|
for v in args {
|
|
407
|
-
|
|
408
|
-
result.extend(other.borrow().iter().cloned())
|
|
409
|
-
|
|
410
|
-
|
|
430
|
+
match v {
|
|
431
|
+
Value::Array(other) => result.extend(other.borrow().iter().cloned()),
|
|
432
|
+
// A packed `NumberArray` arg spreads its numbers, same as a boxed array — under
|
|
433
|
+
// TISH_PACKED_ARRAYS=1 a numeric literal `[3,4]` is a NumberArray, so
|
|
434
|
+
// `[1,2].concat([3,4])` must flatten to `[1,2,3,4]`, not push the array whole.
|
|
435
|
+
Value::NumberArray(other) => result.extend(other.borrow().to_values()),
|
|
436
|
+
_ => result.push(v.clone()),
|
|
411
437
|
}
|
|
412
438
|
}
|
|
413
439
|
Value::Array(VmRef::new(result))
|
|
@@ -421,9 +447,19 @@ pub fn flat(arr: &Value, depth: &Value) -> Value {
|
|
|
421
447
|
fn flatten(arr: &[Value], depth: i32, result: &mut Vec<Value>) {
|
|
422
448
|
for v in arr {
|
|
423
449
|
if depth > 0 {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
450
|
+
match v {
|
|
451
|
+
Value::Array(inner) => {
|
|
452
|
+
flatten(&inner.borrow(), depth - 1, result);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
// A packed `NumberArray` inner flattens like a boxed array (its numbers are the
|
|
456
|
+
// flattened elements) — `[[1,2],[3,4]].flat()` where the inners packed must not
|
|
457
|
+
// leave a `NumberArray` sitting as a scalar element.
|
|
458
|
+
Value::NumberArray(inner) => {
|
|
459
|
+
result.extend(inner.borrow().to_values());
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
_ => {}
|
|
427
463
|
}
|
|
428
464
|
}
|
|
429
465
|
result.push(v.clone());
|
|
@@ -871,7 +907,7 @@ pub fn flat_map(arr: &Value, callback: &Value) -> Value {
|
|
|
871
907
|
|
|
872
908
|
fn sort_by_impl<F>(arr: &Value, cmp: F) -> Value
|
|
873
909
|
where
|
|
874
|
-
F: FnMut(&Value, &Value) ->
|
|
910
|
+
F: FnMut(&Value, &Value) -> core::cmp::Ordering,
|
|
875
911
|
{
|
|
876
912
|
if let Value::Array(arr) = arr {
|
|
877
913
|
arr.borrow_mut().sort_by(cmp);
|
|
@@ -895,7 +931,7 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
|
|
|
895
931
|
// Mutex under send-values and self-deadlock the worker (or panic under RefCell) — the exact
|
|
896
932
|
// reentrant-lock class fixed for the captured-cell path in #218/#338. `mem::take` leaves the
|
|
897
933
|
// array empty for the duration of the sort; the final result is written back below.
|
|
898
|
-
let mut elements: Vec<Value> =
|
|
934
|
+
let mut elements: Vec<Value> = core::mem::take(&mut *arr.borrow_mut());
|
|
899
935
|
let len = elements.len();
|
|
900
936
|
let mut indices: Vec<usize> = (0..len).collect();
|
|
901
937
|
let mut args_buf: [Value; 2] = [Value::Null, Value::Null];
|
|
@@ -904,14 +940,14 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
|
|
|
904
940
|
// #303: once the comparator has thrown, stop invoking it — return Equal so the sort can
|
|
905
941
|
// unwind. Avoids extra comparator calls (and their side effects) after the throw.
|
|
906
942
|
if tishlang_core::has_pending_throw() {
|
|
907
|
-
return
|
|
943
|
+
return core::cmp::Ordering::Equal;
|
|
908
944
|
}
|
|
909
945
|
args_buf[0] = elements[a].clone();
|
|
910
946
|
args_buf[1] = elements[b].clone();
|
|
911
947
|
match cmp_fn.call(&args_buf) {
|
|
912
|
-
Value::Number(n) if n < 0.0 =>
|
|
913
|
-
Value::Number(n) if n > 0.0 =>
|
|
914
|
-
_ =>
|
|
948
|
+
Value::Number(n) if n < 0.0 => core::cmp::Ordering::Less,
|
|
949
|
+
Value::Number(n) if n > 0.0 => core::cmp::Ordering::Greater,
|
|
950
|
+
_ => core::cmp::Ordering::Equal,
|
|
915
951
|
}
|
|
916
952
|
});
|
|
917
953
|
|
|
@@ -923,7 +959,7 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
|
|
|
923
959
|
} else {
|
|
924
960
|
let sorted: Vec<Value> = indices
|
|
925
961
|
.into_iter()
|
|
926
|
-
.map(|i|
|
|
962
|
+
.map(|i| core::mem::replace(&mut elements[i], Value::Null))
|
|
927
963
|
.collect();
|
|
928
964
|
*arr.borrow_mut() = sorted;
|
|
929
965
|
}
|
|
@@ -933,12 +969,12 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
|
|
|
933
969
|
}
|
|
934
970
|
}
|
|
935
971
|
|
|
936
|
-
fn num_cmp(a: &Value, b: &Value, asc: bool) ->
|
|
972
|
+
fn num_cmp(a: &Value, b: &Value, asc: bool) -> core::cmp::Ordering {
|
|
937
973
|
let (na, nb) = match (a, b) {
|
|
938
974
|
(Value::Number(a), Value::Number(b)) => (*a, *b),
|
|
939
975
|
_ => (f64::NAN, f64::NAN),
|
|
940
976
|
};
|
|
941
|
-
let cmp = na.partial_cmp(&nb).unwrap_or(
|
|
977
|
+
let cmp = na.partial_cmp(&nb).unwrap_or(core::cmp::Ordering::Equal);
|
|
942
978
|
if asc {
|
|
943
979
|
cmp
|
|
944
980
|
} else {
|
|
@@ -961,13 +997,15 @@ pub fn sort_numeric_desc(arr: &Value) -> Value {
|
|
|
961
997
|
fn sort_numeric_impl(arr: &Value, asc: bool) -> Value {
|
|
962
998
|
// NumberArray fast path: sort the Vec<f64> directly — no unbox pass, no rebox.
|
|
963
999
|
if let Value::NumberArray(a) = arr {
|
|
964
|
-
|
|
965
|
-
if
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1000
|
+
// Fast path only while still packed; a deopted numeric array sorts via the boxed path below.
|
|
1001
|
+
if let Some(nums) = a.borrow_mut().as_packed_mut() {
|
|
1002
|
+
if asc {
|
|
1003
|
+
nums.sort_unstable_by(|x, y| x.partial_cmp(y).unwrap_or(core::cmp::Ordering::Equal));
|
|
1004
|
+
} else {
|
|
1005
|
+
nums.sort_unstable_by(|x, y| y.partial_cmp(x).unwrap_or(core::cmp::Ordering::Equal));
|
|
1006
|
+
}
|
|
1007
|
+
return Value::NumberArray(a.clone());
|
|
969
1008
|
}
|
|
970
|
-
return Value::NumberArray(a.clone());
|
|
971
1009
|
}
|
|
972
1010
|
if let Value::Array(a) = arr {
|
|
973
1011
|
{
|
|
@@ -982,11 +1020,11 @@ fn sort_numeric_impl(arr: &Value, asc: bool) -> Value {
|
|
|
982
1020
|
.collect();
|
|
983
1021
|
if asc {
|
|
984
1022
|
nums.sort_unstable_by(|x, y| {
|
|
985
|
-
x.partial_cmp(y).unwrap_or(
|
|
1023
|
+
x.partial_cmp(y).unwrap_or(core::cmp::Ordering::Equal)
|
|
986
1024
|
});
|
|
987
1025
|
} else {
|
|
988
1026
|
nums.sort_unstable_by(|x, y| {
|
|
989
|
-
y.partial_cmp(x).unwrap_or(
|
|
1027
|
+
y.partial_cmp(x).unwrap_or(core::cmp::Ordering::Equal)
|
|
990
1028
|
});
|
|
991
1029
|
}
|
|
992
1030
|
for (slot, n) in g.iter_mut().zip(nums) {
|
|
@@ -1004,11 +1042,11 @@ fn sort_numeric_impl(arr: &Value, asc: bool) -> Value {
|
|
|
1004
1042
|
|
|
1005
1043
|
/// Sort array of objects by numeric property: arr.sort((a,b)=>a.prop-b.prop)
|
|
1006
1044
|
pub fn sort_by_property_numeric(arr: &Value, prop: &str, asc: bool) -> Value {
|
|
1007
|
-
let prop_arc =
|
|
1045
|
+
let prop_arc = tishlang_core::Arc::from(prop);
|
|
1008
1046
|
sort_by_impl(arr, move |a, b| {
|
|
1009
1047
|
let na = get_prop_number(a, &prop_arc);
|
|
1010
1048
|
let nb = get_prop_number(b, &prop_arc);
|
|
1011
|
-
let cmp = na.partial_cmp(&nb).unwrap_or(
|
|
1049
|
+
let cmp = na.partial_cmp(&nb).unwrap_or(core::cmp::Ordering::Equal);
|
|
1012
1050
|
if asc {
|
|
1013
1051
|
cmp
|
|
1014
1052
|
} else {
|
|
@@ -1017,7 +1055,7 @@ pub fn sort_by_property_numeric(arr: &Value, prop: &str, asc: bool) -> Value {
|
|
|
1017
1055
|
})
|
|
1018
1056
|
}
|
|
1019
1057
|
|
|
1020
|
-
fn get_prop_number(v: &Value, prop: &
|
|
1058
|
+
fn get_prop_number(v: &Value, prop: &tishlang_core::Arc<str>) -> f64 {
|
|
1021
1059
|
match v {
|
|
1022
1060
|
Value::Object(o) => o
|
|
1023
1061
|
.borrow()
|
|
@@ -1097,19 +1135,19 @@ pub fn sort_by_keys(arr: &Value, keys: &[(&[&str], bool)]) -> Value {
|
|
|
1097
1135
|
for (col, (_, asc)) in keys.iter().enumerate() {
|
|
1098
1136
|
let ord = ka[col]
|
|
1099
1137
|
.partial_cmp(&kb[col])
|
|
1100
|
-
.unwrap_or(
|
|
1138
|
+
.unwrap_or(core::cmp::Ordering::Equal);
|
|
1101
1139
|
let ord = if *asc { ord } else { ord.reverse() };
|
|
1102
|
-
if ord !=
|
|
1140
|
+
if ord != core::cmp::Ordering::Equal {
|
|
1103
1141
|
return ord;
|
|
1104
1142
|
}
|
|
1105
1143
|
}
|
|
1106
|
-
|
|
1144
|
+
core::cmp::Ordering::Equal
|
|
1107
1145
|
});
|
|
1108
1146
|
// Undecorate: apply the permutation in place.
|
|
1109
|
-
let mut elements: Vec<Value> =
|
|
1147
|
+
let mut elements: Vec<Value> = core::mem::take(&mut *g);
|
|
1110
1148
|
*g = decorated
|
|
1111
1149
|
.into_iter()
|
|
1112
|
-
.map(|(i, _)|
|
|
1150
|
+
.map(|(i, _)| core::mem::replace(&mut elements[i], Value::Null))
|
|
1113
1151
|
.collect();
|
|
1114
1152
|
drop(g);
|
|
1115
1153
|
Value::Array(a.clone())
|
|
@@ -1125,15 +1163,15 @@ mod packed_hof_tests {
|
|
|
1125
1163
|
//! on it. These run with packing semantics directly (the helpers don't read the env flag; a
|
|
1126
1164
|
//! `NumberArray` value is enough to take the fast path).
|
|
1127
1165
|
use super::*;
|
|
1128
|
-
use tishlang_core::Value;
|
|
1166
|
+
use tishlang_core::{NumArrayBacking, Value};
|
|
1129
1167
|
|
|
1130
1168
|
fn na(xs: &[f64]) -> Value {
|
|
1131
|
-
Value::NumberArray(VmRef::new(xs.to_vec()))
|
|
1169
|
+
Value::NumberArray(VmRef::new(NumArrayBacking::Packed(xs.to_vec())))
|
|
1132
1170
|
}
|
|
1133
1171
|
fn nums(v: &Value) -> Vec<f64> {
|
|
1134
1172
|
match v {
|
|
1135
1173
|
Value::Array(a) => a.borrow().iter().map(|e| e.as_number().unwrap_or(f64::NAN)).collect(),
|
|
1136
|
-
Value::NumberArray(a) => a.borrow().
|
|
1174
|
+
Value::NumberArray(a) => a.borrow().to_values().iter().map(|e| e.as_number().unwrap_or(f64::NAN)).collect(),
|
|
1137
1175
|
_ => vec![],
|
|
1138
1176
|
}
|
|
1139
1177
|
}
|
|
@@ -1317,7 +1355,7 @@ mod sort_by_keys_tests {
|
|
|
1317
1355
|
//! EXACT same order as running the equivalent boxed comparator through
|
|
1318
1356
|
//! `sort_with_comparator`, since cross-backend parity depends on it.
|
|
1319
1357
|
use super::*;
|
|
1320
|
-
use
|
|
1358
|
+
use tishlang_core::Arc;
|
|
1321
1359
|
use tishlang_core::Value;
|
|
1322
1360
|
|
|
1323
1361
|
fn rec(key: f64, idx: f64) -> Value {
|
|
@@ -1533,4 +1571,42 @@ mod reentrancy_tests_382 {
|
|
|
1533
1571
|
_ => panic!("sort should return an array"),
|
|
1534
1572
|
}
|
|
1535
1573
|
}
|
|
1574
|
+
|
|
1575
|
+
fn nums(v: &Value) -> Vec<f64> {
|
|
1576
|
+
match v {
|
|
1577
|
+
Value::Array(a) => a.borrow().iter().filter_map(|e| e.as_number()).collect(),
|
|
1578
|
+
_ => panic!("expected boxed array result"),
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/// #505: a packed `NumberArray` concat ARG must spread its numbers, not push the array whole.
|
|
1583
|
+
#[test]
|
|
1584
|
+
fn concat_spreads_packed_number_array_arg() {
|
|
1585
|
+
let recv = Value::Array(VmRef::new(vec![Value::Number(1.0), Value::Number(2.0)]));
|
|
1586
|
+
let packed = Value::number_array(vec![3.0, 4.0]);
|
|
1587
|
+
let out = concat(&recv, &[packed]);
|
|
1588
|
+
assert_eq!(nums(&out), vec![1.0, 2.0, 3.0, 4.0]);
|
|
1589
|
+
// Mixed with a boxed array + a scalar, order preserved.
|
|
1590
|
+
let boxed = Value::Array(VmRef::new(vec![Value::Number(5.0)]));
|
|
1591
|
+
let out2 = concat(&recv, &[boxed, Value::Number(9.0)]);
|
|
1592
|
+
assert_eq!(nums(&out2), vec![1.0, 2.0, 5.0, 9.0]);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
/// #507: a packed `NumberArray` INNER element must flatten like a boxed array.
|
|
1596
|
+
#[test]
|
|
1597
|
+
fn flat_flattens_packed_number_array_inner() {
|
|
1598
|
+
let outer = Value::Array(VmRef::new(vec![
|
|
1599
|
+
Value::number_array(vec![1.0, 2.0]),
|
|
1600
|
+
Value::Number(3.0),
|
|
1601
|
+
Value::Array(VmRef::new(vec![Value::Number(4.0)])),
|
|
1602
|
+
]));
|
|
1603
|
+
let out = flat(&outer, &Value::Number(1.0));
|
|
1604
|
+
assert_eq!(nums(&out), vec![1.0, 2.0, 3.0, 4.0]);
|
|
1605
|
+
// depth 0 keeps the packed inner as-is (not flattened) — matches boxed-array behavior.
|
|
1606
|
+
let out0 = flat(&outer, &Value::Number(0.0));
|
|
1607
|
+
match out0 {
|
|
1608
|
+
Value::Array(a) => assert_eq!(a.borrow().len(), 3, "depth 0 flattens nothing"),
|
|
1609
|
+
_ => panic!("expected array"),
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1536
1612
|
}
|