@tishlang/tish-lsp 2.16.13 → 2.36.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/bin/tish-lsp +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +12 -2
- package/crates/tish/tests/fixtures/fs_parity_callbacks.tish +22 -0
- package/crates/tish/tests/fixtures/fs_parity_promises.tish +21 -0
- package/crates/tish/tests/fixtures/fs_parity_sync.tish +20 -0
- package/crates/tish/tests/fixtures/process_capture.tish +16 -0
- package/crates/tish/tests/fs_parity.rs +103 -0
- package/crates/tish/tests/process_capture.rs +40 -0
- package/crates/tish/tests/shortcircuit.rs +3 -3
- package/crates/tish_ast/src/ast.rs +77 -3
- package/crates/tish_builtins/Cargo.toml +1 -0
- package/crates/tish_builtins/src/array.rs +195 -16
- package/crates/tish_builtins/src/collections.rs +37 -2
- package/crates/tish_builtins/src/globals.rs +215 -25
- package/crates/tish_builtins/src/math.rs +65 -7
- package/crates/tish_builtins/src/number.rs +173 -0
- package/crates/tish_builtins/src/string.rs +168 -15
- package/crates/tish_bytecode/src/compiler.rs +56 -4
- package/crates/tish_bytecode/src/opcode.rs +4 -2
- package/crates/tish_compile/src/codegen.rs +519 -46
- package/crates/tish_compile/src/infer.rs +26 -11
- package/crates/tish_compile/src/resolve.rs +87 -10
- package/crates/tish_compile_js/src/codegen.rs +82 -24
- package/crates/tish_compile_js/src/tests_jsx.rs +27 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +17 -2
- package/crates/tish_core/src/json.rs +5 -1
- package/crates/tish_core/src/lib.rs +48 -2
- package/crates/tish_core/src/uri.rs +45 -0
- package/crates/tish_core/src/value.rs +6 -0
- package/crates/tish_eval/Cargo.toml +2 -1
- package/crates/tish_eval/src/eval.rs +661 -27
- package/crates/tish_eval/src/natives.rs +318 -18
- package/crates/tish_eval/src/regex.rs +47 -1
- package/crates/tish_fmt/src/lib.rs +23 -4
- package/crates/tish_lsp/Cargo.toml +1 -1
- package/crates/tish_opt/src/lib.rs +20 -1
- package/crates/tish_parser/src/lib.rs +41 -0
- package/crates/tish_parser/src/parser.rs +180 -11
- package/crates/tish_resolve/src/lib.rs +66 -5
- package/crates/tish_runtime/Cargo.toml +5 -1
- package/crates/tish_runtime/src/fs_ext.rs +359 -0
- package/crates/tish_runtime/src/http_hyper.rs +41 -18
- package/crates/tish_runtime/src/lib.rs +236 -10
- package/crates/tish_ui/src/jsx.rs +18 -3
- package/crates/tish_vm/src/jit.rs +1 -1
- package/crates/tish_vm/src/vm.rs +294 -60
- 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
|
@@ -79,7 +79,12 @@ impl UsageAnalyzer {
|
|
|
79
79
|
}
|
|
80
80
|
self.analyze_statement(body);
|
|
81
81
|
}
|
|
82
|
-
Statement::ForOf { iterable, body, .. }
|
|
82
|
+
Statement::ForOf { iterable, body, .. }
|
|
83
|
+
| Statement::ForIn {
|
|
84
|
+
object: iterable,
|
|
85
|
+
body,
|
|
86
|
+
..
|
|
87
|
+
} => {
|
|
83
88
|
self.analyze_expr(iterable);
|
|
84
89
|
self.analyze_statement(body);
|
|
85
90
|
}
|
|
@@ -330,6 +335,7 @@ fn program_uses_async(program: &Program) -> bool {
|
|
|
330
335
|
Statement::While { body, .. }
|
|
331
336
|
| Statement::For { body, .. }
|
|
332
337
|
| Statement::ForOf { body, .. }
|
|
338
|
+
| Statement::ForIn { body, .. }
|
|
333
339
|
| Statement::DoWhile { body, .. } => stmt_has_async(body),
|
|
334
340
|
Statement::Switch {
|
|
335
341
|
cases,
|
|
@@ -464,7 +470,12 @@ fn program_uses_async(program: &Program) -> bool {
|
|
|
464
470
|
|| update.as_ref().is_some_and(expr_has_await)
|
|
465
471
|
|| stmt_has_await(body)
|
|
466
472
|
}
|
|
467
|
-
Statement::ForOf { iterable, body, .. }
|
|
473
|
+
Statement::ForOf { iterable, body, .. }
|
|
474
|
+
| Statement::ForIn {
|
|
475
|
+
object: iterable,
|
|
476
|
+
body,
|
|
477
|
+
..
|
|
478
|
+
} => {
|
|
468
479
|
expr_has_await(iterable) || stmt_has_await(body)
|
|
469
480
|
}
|
|
470
481
|
Statement::Return { value, .. } => value.as_ref().is_some_and(expr_has_await),
|
|
@@ -917,6 +928,13 @@ pub(crate) struct Codegen {
|
|
|
917
928
|
module_const_f64_aliases: std::collections::HashMap<String, String>,
|
|
918
929
|
/// #181: locals initialized with `new Map()` — direct `map_has`/`get`/`set`/`values` dispatch.
|
|
919
930
|
map_instance_locals: std::collections::HashSet<String>,
|
|
931
|
+
/// #456 regression guard: locals initialized with `new Map()` OR `new Set()`. Arrays have no
|
|
932
|
+
/// runtime method table (`get_prop` returns Null for a method name), so `keys`/`values`/`entries`
|
|
933
|
+
/// MUST use the array fast-path — but that same fast-path would hijack a Map/Set's own
|
|
934
|
+
/// `keys`/`values`/`entries` (which DO resolve as bound methods via `get_prop`). So the array
|
|
935
|
+
/// fast-path fires by default and is suppressed only for a known collection local, which then
|
|
936
|
+
/// falls through to the generic bound-method call.
|
|
937
|
+
collection_instance_locals: std::collections::HashSet<String>,
|
|
920
938
|
/// String-scan hoist: inside a `for (i; i < s.length; …)` loop over an invariant native `String`
|
|
921
939
|
/// `s`, the loop body's `s.charCodeAt/charAt/at(i)` route to O(1) `Vec<char>` slice ops instead
|
|
922
940
|
/// of `str_char_code_at`'s `chars().nth(i)` = O(i). Maps the source ident → the hoisted `_scN`
|
|
@@ -1143,6 +1161,7 @@ impl Codegen {
|
|
|
1143
1161
|
module_const_f64_cum: std::collections::HashMap::new(),
|
|
1144
1162
|
module_const_f64_aliases: std::collections::HashMap::new(),
|
|
1145
1163
|
map_instance_locals: std::collections::HashSet::new(),
|
|
1164
|
+
collection_instance_locals: std::collections::HashSet::new(),
|
|
1146
1165
|
hoisted_string_chars: std::collections::HashMap::new(),
|
|
1147
1166
|
native_fns: std::collections::HashSet::new(),
|
|
1148
1167
|
native_fn_body_emit: false,
|
|
@@ -1308,6 +1327,7 @@ impl Codegen {
|
|
|
1308
1327
|
}
|
|
1309
1328
|
Statement::For { body, .. }
|
|
1310
1329
|
| Statement::ForOf { body, .. }
|
|
1330
|
+
| Statement::ForIn { body, .. }
|
|
1311
1331
|
| Statement::While { body, .. }
|
|
1312
1332
|
| Statement::DoWhile { body, .. } => {
|
|
1313
1333
|
Self::walk_type_aliases(std::slice::from_ref(body.as_ref()), out);
|
|
@@ -1590,13 +1610,59 @@ impl Codegen {
|
|
|
1590
1610
|
fn builtin_native_module_rust_init(&self, spec: &str, export_name: &str) -> Option<String> {
|
|
1591
1611
|
let init = match spec {
|
|
1592
1612
|
"tish:fs" if self.has_feature("fs") => match export_name {
|
|
1593
|
-
|
|
1594
|
-
"
|
|
1613
|
+
// tish names → the dual fs_ext functions (sync, or Node callback if a fn is passed)
|
|
1614
|
+
"readFile" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::read_file(args))"),
|
|
1615
|
+
"writeFile" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::write_file(args))"),
|
|
1595
1616
|
"fileExists" => Some("Value::native(|args: &[Value]| tish_file_exists(args))"),
|
|
1596
1617
|
"isDir" => Some("Value::native(|args: &[Value]| tish_is_dir(args))"),
|
|
1597
|
-
"readDir" => Some("Value::native(|args: &[Value]|
|
|
1598
|
-
"readFileBytes" => Some("Value::native(|args: &[Value]|
|
|
1599
|
-
|
|
1618
|
+
"readDir" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::readdir(args))"),
|
|
1619
|
+
"readFileBytes" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::read_file_bytes(args))"),
|
|
1620
|
+
// Node-compatible names + new methods (sync) — issue #122
|
|
1621
|
+
"readFileSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::read_file(args))"),
|
|
1622
|
+
"readFileBytesSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::read_file_bytes(args))"),
|
|
1623
|
+
"writeFileSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::write_file(args))"),
|
|
1624
|
+
"appendFile" | "appendFileSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::append_file(args))"),
|
|
1625
|
+
"existsSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::exists(args))"),
|
|
1626
|
+
"mkdir" | "mkdirSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::mkdir(args))"),
|
|
1627
|
+
"readdir" | "readdirSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::readdir(args))"),
|
|
1628
|
+
"stat" | "statSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::stat(args))"),
|
|
1629
|
+
"lstat" | "lstatSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::lstat(args))"),
|
|
1630
|
+
"rm" | "rmSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::rm(args))"),
|
|
1631
|
+
"rmdir" | "rmdirSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::rmdir(args))"),
|
|
1632
|
+
"unlink" | "unlinkSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::unlink(args))"),
|
|
1633
|
+
"rename" | "renameSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::rename(args))"),
|
|
1634
|
+
"copyFile" | "copyFileSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::copy_file(args))"),
|
|
1635
|
+
"realpath" | "realpathSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::realpath(args))"),
|
|
1636
|
+
"readlink" | "readlinkSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::readlink(args))"),
|
|
1637
|
+
"truncate" | "truncateSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::truncate(args))"),
|
|
1638
|
+
"mkdtemp" | "mkdtempSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::mkdtemp(args))"),
|
|
1639
|
+
"cp" | "cpSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::cp(args))"),
|
|
1640
|
+
"access" | "accessSync" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::access(args))"),
|
|
1641
|
+
"constants" => Some("tishlang_runtime::fs_ext::constants()"),
|
|
1642
|
+
_ => None,
|
|
1643
|
+
},
|
|
1644
|
+
"tish:fs/promises" if self.has_feature("fs") => match export_name {
|
|
1645
|
+
"readFile" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::read_file_promise(args))"),
|
|
1646
|
+
"readFileBytes" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::read_file_bytes_promise(args))"),
|
|
1647
|
+
"writeFile" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::write_file_promise(args))"),
|
|
1648
|
+
"appendFile" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::append_file_promise(args))"),
|
|
1649
|
+
"mkdir" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::mkdir_promise(args))"),
|
|
1650
|
+
"readdir" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::readdir_promise(args))"),
|
|
1651
|
+
"stat" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::stat_promise(args))"),
|
|
1652
|
+
"lstat" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::lstat_promise(args))"),
|
|
1653
|
+
"rm" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::rm_promise(args))"),
|
|
1654
|
+
"rmdir" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::rmdir_promise(args))"),
|
|
1655
|
+
"unlink" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::unlink_promise(args))"),
|
|
1656
|
+
"rename" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::rename_promise(args))"),
|
|
1657
|
+
"copyFile" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::copy_file_promise(args))"),
|
|
1658
|
+
"realpath" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::realpath_promise(args))"),
|
|
1659
|
+
"readlink" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::readlink_promise(args))"),
|
|
1660
|
+
"truncate" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::truncate_promise(args))"),
|
|
1661
|
+
"mkdtemp" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::mkdtemp_promise(args))"),
|
|
1662
|
+
"cp" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::cp_promise(args))"),
|
|
1663
|
+
"access" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::access_promise(args))"),
|
|
1664
|
+
"exists" => Some("Value::native(|args: &[Value]| tishlang_runtime::fs_ext::exists_promise(args))"),
|
|
1665
|
+
"constants" => Some("tishlang_runtime::fs_ext::constants()"),
|
|
1600
1666
|
_ => None,
|
|
1601
1667
|
},
|
|
1602
1668
|
"tish:http" if self.has_feature("http") => match export_name {
|
|
@@ -1624,9 +1690,11 @@ impl Codegen {
|
|
|
1624
1690
|
"cwd" => Some("Value::native(|args: &[Value]| tish_process_cwd(args))"),
|
|
1625
1691
|
"exec" => Some("Value::native(|args: &[Value]| tish_process_exec(args))"),
|
|
1626
1692
|
"execFile" => Some("Value::native(|args: &[Value]| tish_process_exec_file(args))"),
|
|
1693
|
+
"execCapture" => Some("Value::native(|args: &[Value]| tish_process_exec_capture(args))"),
|
|
1694
|
+
"execFileCapture" => Some("Value::native(|args: &[Value]| tish_process_exec_file_capture(args))"),
|
|
1627
1695
|
"argv" => Some("Value::Array(VmRef::new(std::env::args().map(|s| Value::String(s.into())).collect()))"),
|
|
1628
1696
|
"env" => Some("Value::object(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect())"),
|
|
1629
|
-
"process" => Some("{ let mut m = ObjectMap::default(); m.insert(Arc::from(\"exit\"), Value::native(|args: &[Value]| tish_process_exit(args))); m.insert(Arc::from(\"cwd\"), Value::native(|args: &[Value]| tish_process_cwd(args))); m.insert(Arc::from(\"exec\"), Value::native(|args: &[Value]| tish_process_exec(args))); m.insert(Arc::from(\"execFile\"), Value::native(|args: &[Value]| tish_process_exec_file(args))); m.insert(Arc::from(\"argv\"), Value::Array(VmRef::new(std::env::args().map(|s| Value::String(s.into())).collect()))); m.insert(Arc::from(\"env\"), Value::object(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect::<ObjectMap>())); Value::object(m) }"),
|
|
1697
|
+
"process" => Some("{ let mut m = ObjectMap::default(); m.insert(Arc::from(\"exit\"), Value::native(|args: &[Value]| tish_process_exit(args))); m.insert(Arc::from(\"cwd\"), Value::native(|args: &[Value]| tish_process_cwd(args))); m.insert(Arc::from(\"exec\"), Value::native(|args: &[Value]| tish_process_exec(args))); m.insert(Arc::from(\"execFile\"), Value::native(|args: &[Value]| tish_process_exec_file(args))); m.insert(Arc::from(\"execCapture\"), Value::native(|args: &[Value]| tish_process_exec_capture(args))); m.insert(Arc::from(\"execFileCapture\"), Value::native(|args: &[Value]| tish_process_exec_file_capture(args))); m.insert(Arc::from(\"argv\"), Value::Array(VmRef::new(std::env::args().map(|s| Value::String(s.into())).collect()))); m.insert(Arc::from(\"env\"), Value::object(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect::<ObjectMap>())); Value::object(m) }"),
|
|
1630
1698
|
_ => None,
|
|
1631
1699
|
},
|
|
1632
1700
|
"tish:ws" if self.has_feature("ws") => match export_name {
|
|
@@ -2175,12 +2243,12 @@ impl Codegen {
|
|
|
2175
2243
|
self.write("use std::cell::RefCell;\n");
|
|
2176
2244
|
self.write("use std::rc::Rc;\n");
|
|
2177
2245
|
self.write("use std::sync::Arc;\n");
|
|
2178
|
-
self.write("use tishlang_runtime::{console_debug as tish_console_debug, console_info as tish_console_info, console_log as tish_console_log, console_warn as tish_console_warn, console_error as tish_console_error, boolean as tish_boolean, decode_uri as tish_decode_uri, encode_uri as tish_encode_uri, string_escape_html_impl as tish_escape_html, in_operator as tish_in_operator, is_finite as tish_is_finite, is_nan as tish_is_nan, json_parse as tish_json_parse, json_stringify as tish_json_stringify, math_abs as tish_math_abs, math_ceil as tish_math_ceil, math_floor as tish_math_floor, math_max as tish_math_max, math_min as tish_math_min, math_round as tish_math_round, math_sqrt as tish_math_sqrt, parse_float as tish_parse_float, parse_int as tish_parse_int, math_random as tish_math_random, math_pow as tish_math_pow, math_sin as tish_math_sin, math_cos as tish_math_cos, math_tan as tish_math_tan, math_log as tish_math_log, math_exp as tish_math_exp, math_sign as tish_math_sign, math_trunc as tish_math_trunc, math_imul as tish_math_imul, math_sinh as tish_math_sinh, math_cosh as tish_math_cosh, math_tanh as tish_math_tanh, math_asinh as tish_math_asinh, math_acosh as tish_math_acosh, math_atanh as tish_math_atanh, math_cbrt as tish_math_cbrt, math_log2 as tish_math_log2, math_log10 as tish_math_log10, math_hypot as tish_math_hypot, math_atan2 as tish_math_atan2, math_asin as tish_math_asin, math_acos as tish_math_acos, math_atan as tish_math_atan, array_is_array as tish_array_is_array, array_construct as tish_array_construct, string_from_char_code as tish_string_from_char_code, string_convert as tish_string_convert, number_convert as tish_number_convert, object_assign as tish_object_assign, object_keys as tish_object_keys, object_values as tish_object_values, object_entries as tish_object_entries, object_from_entries as tish_object_from_entries, symbol_object as tish_symbol_object, tish_construct, tish_error_constructor, tish_date_constructor, tish_set_constructor, tish_map_constructor, map_get as tish_map_get, map_has as tish_map_has, map_set as tish_map_set, map_values as tish_map_values, tish_float64_array_constructor, tish_float32_array_constructor, tish_int8_array_constructor, tish_uint8_array_constructor, tish_uint8_clamped_array_constructor, tish_int16_array_constructor, tish_uint16_array_constructor, tish_int32_array_constructor, tish_uint32_array_constructor, tish_audio_context_constructor, ObjectMap, ObjectData, PropMap, TishError, Value, VmRef};\n");
|
|
2246
|
+
self.write("use tishlang_runtime::{console_debug as tish_console_debug, console_info as tish_console_info, console_log as tish_console_log, console_warn as tish_console_warn, console_error as tish_console_error, boolean as tish_boolean, decode_uri as tish_decode_uri, encode_uri as tish_encode_uri, encode_uri_component as tish_encode_uri_component, decode_uri_component as tish_decode_uri_component, string_escape_html_impl as tish_escape_html, in_operator as tish_in_operator, is_finite as tish_is_finite, is_nan as tish_is_nan, json_parse as tish_json_parse, json_stringify as tish_json_stringify, math_abs as tish_math_abs, math_ceil as tish_math_ceil, math_floor as tish_math_floor, math_max as tish_math_max, math_min as tish_math_min, math_round as tish_math_round, math_sqrt as tish_math_sqrt, parse_float as tish_parse_float, parse_int as tish_parse_int, math_random as tish_math_random, math_pow as tish_math_pow, math_sin as tish_math_sin, math_cos as tish_math_cos, math_tan as tish_math_tan, math_log as tish_math_log, math_exp as tish_math_exp, math_sign as tish_math_sign, math_trunc as tish_math_trunc, math_imul as tish_math_imul, math_sinh as tish_math_sinh, math_cosh as tish_math_cosh, math_tanh as tish_math_tanh, math_asinh as tish_math_asinh, math_acosh as tish_math_acosh, math_atanh as tish_math_atanh, math_cbrt as tish_math_cbrt, math_log2 as tish_math_log2, math_log10 as tish_math_log10, math_expm1 as tish_math_expm1, math_log1p as tish_math_log1p, math_clz32 as tish_math_clz32, math_fround as tish_math_fround, math_hypot as tish_math_hypot, math_atan2 as tish_math_atan2, math_asin as tish_math_asin, math_acos as tish_math_acos, math_atan as tish_math_atan, array_is_array as tish_array_is_array, array_of as tish_array_of, array_from as tish_array_from, object_is as tish_object_is, object_has_own as tish_object_has_own, structured_clone as tish_structured_clone, array_construct as tish_array_construct, string_from_char_code as tish_string_from_char_code, string_convert as tish_string_convert, number_convert as tish_number_convert, number_is_integer as tish_number_is_integer, number_is_safe_integer as tish_number_is_safe_integer, number_is_nan as tish_number_is_nan, number_is_finite as tish_number_is_finite, object_assign as tish_object_assign, object_keys as tish_object_keys, object_values as tish_object_values, object_entries as tish_object_entries, object_from_entries as tish_object_from_entries, symbol_object as tish_symbol_object, tish_construct, tish_error_constructor, tish_date_constructor, tish_set_constructor, tish_map_constructor, map_get as tish_map_get, map_has as tish_map_has, map_set as tish_map_set, map_values as tish_map_values, tish_float64_array_constructor, tish_float32_array_constructor, tish_int8_array_constructor, tish_uint8_array_constructor, tish_uint8_clamped_array_constructor, tish_int16_array_constructor, tish_uint16_array_constructor, tish_int32_array_constructor, tish_uint32_array_constructor, tish_audio_context_constructor, ObjectMap, ObjectData, PropMap, TishError, Value, VmRef};\n");
|
|
2179
2247
|
if self.program_has_jsx {
|
|
2180
2248
|
self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
|
|
2181
2249
|
}
|
|
2182
2250
|
if self.has_feature("process") {
|
|
2183
|
-
self.write("use tishlang_runtime::{process_exit as tish_process_exit, process_cwd as tish_process_cwd, process_exec as tish_process_exec, process_exec_file as tish_process_exec_file};\n");
|
|
2251
|
+
self.write("use tishlang_runtime::{process_exit as tish_process_exit, process_cwd as tish_process_cwd, process_exec as tish_process_exec, process_exec_file as tish_process_exec_file, process_exec_capture as tish_process_exec_capture, process_exec_file_capture as tish_process_exec_file_capture};\n");
|
|
2184
2252
|
}
|
|
2185
2253
|
if self.has_feature("timers") {
|
|
2186
2254
|
self.write("use tishlang_runtime::{timer_set_timeout as tish_timer_set_timeout, timer_clear_timeout as tish_timer_clear_timeout, timer_set_interval as tish_timer_set_interval, timer_clear_interval as tish_timer_clear_interval};\n");
|
|
@@ -2195,6 +2263,11 @@ impl Codegen {
|
|
|
2195
2263
|
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve};\n");
|
|
2196
2264
|
}
|
|
2197
2265
|
}
|
|
2266
|
+
// `fs/promises` (and any promise-using async program) needs the await machinery even
|
|
2267
|
+
// without http. `fs` implies the runtime `promise` feature, so these resolve.
|
|
2268
|
+
if self.is_async && !self.has_feature("http") && self.has_feature("fs") {
|
|
2269
|
+
self.write("use tishlang_runtime::{promise_object as tish_promise_object, await_promise as tish_await_promise, await_promise_throw as tish_await_promise_throw};\n");
|
|
2270
|
+
}
|
|
2198
2271
|
if self.has_feature("fs") {
|
|
2199
2272
|
self.write("use tishlang_runtime::{read_file as tish_read_file, read_file_bytes as tish_read_file_bytes, write_file as tish_write_file, file_exists as tish_file_exists, is_dir as tish_is_dir, read_dir as tish_read_dir, mkdir as tish_mkdir};\n");
|
|
2200
2273
|
}
|
|
@@ -2378,6 +2451,9 @@ impl Codegen {
|
|
|
2378
2451
|
self.writeln("let parseFloat = Value::native(|args: &[Value]| tish_parse_float(args));");
|
|
2379
2452
|
self.writeln("let decodeURI = Value::native(|args: &[Value]| tish_decode_uri(args));");
|
|
2380
2453
|
self.writeln("let encodeURI = Value::native(|args: &[Value]| tish_encode_uri(args));");
|
|
2454
|
+
self.writeln("let encodeURIComponent = Value::native(|args: &[Value]| tish_encode_uri_component(args));");
|
|
2455
|
+
self.writeln("let decodeURIComponent = Value::native(|args: &[Value]| tish_decode_uri_component(args));");
|
|
2456
|
+
self.writeln("let structuredClone = Value::native(|args: &[Value]| tish_structured_clone(args));");
|
|
2381
2457
|
// `registerStaticRoute` calls the http-gated runtime fn, so only bind it when http is linked
|
|
2382
2458
|
// (matches the conditional `use` above; otherwise non-http builds fail to resolve it).
|
|
2383
2459
|
if self.has_feature("http") {
|
|
@@ -2439,6 +2515,10 @@ impl Codegen {
|
|
|
2439
2515
|
("cbrt", "tish_math_cbrt"),
|
|
2440
2516
|
("log2", "tish_math_log2"),
|
|
2441
2517
|
("log10", "tish_math_log10"),
|
|
2518
|
+
("expm1", "tish_math_expm1"),
|
|
2519
|
+
("log1p", "tish_math_log1p"),
|
|
2520
|
+
("clz32", "tish_math_clz32"),
|
|
2521
|
+
("fround", "tish_math_fround"),
|
|
2442
2522
|
("hypot", "tish_math_hypot"),
|
|
2443
2523
|
("atan2", "tish_math_atan2"),
|
|
2444
2524
|
("asin", "tish_math_asin"),
|
|
@@ -2467,6 +2547,12 @@ impl Codegen {
|
|
|
2467
2547
|
self.writeln(
|
|
2468
2548
|
"(Arc::from(\"isArray\"), Value::native(|args: &[Value]| tish_array_is_array(args))),",
|
|
2469
2549
|
);
|
|
2550
|
+
self.writeln(
|
|
2551
|
+
"(Arc::from(\"of\"), Value::native(|args: &[Value]| tish_array_of(args))),",
|
|
2552
|
+
);
|
|
2553
|
+
self.writeln(
|
|
2554
|
+
"(Arc::from(\"from\"), Value::native(|args: &[Value]| tish_array_from(args))),",
|
|
2555
|
+
);
|
|
2470
2556
|
// `Array(n)` / `new Array(n)` constructor (issue #72); `__call` covers both forms.
|
|
2471
2557
|
self.writeln(
|
|
2472
2558
|
"(Arc::from(\"__call\"), Value::native(|args: &[Value]| tish_array_construct(args))),",
|
|
@@ -2482,10 +2568,24 @@ impl Codegen {
|
|
|
2482
2568
|
self.indent -= 1;
|
|
2483
2569
|
self.writeln("]));");
|
|
2484
2570
|
|
|
2485
|
-
// `Number(value)` coercion callable (issue #36).
|
|
2571
|
+
// `Number(value)` coercion callable (issue #36) + the `Number.*` statics.
|
|
2486
2572
|
self.writeln("let Number = Value::object(ObjectMap::from([");
|
|
2487
2573
|
self.indent += 1;
|
|
2488
2574
|
self.writeln("(Arc::from(\"__call\"), Value::native(|args: &[Value]| tish_number_convert(args))),");
|
|
2575
|
+
self.writeln("(Arc::from(\"isInteger\"), Value::native(|args: &[Value]| tish_number_is_integer(args))),");
|
|
2576
|
+
self.writeln("(Arc::from(\"isSafeInteger\"), Value::native(|args: &[Value]| tish_number_is_safe_integer(args))),");
|
|
2577
|
+
self.writeln("(Arc::from(\"isNaN\"), Value::native(|args: &[Value]| tish_number_is_nan(args))),");
|
|
2578
|
+
self.writeln("(Arc::from(\"isFinite\"), Value::native(|args: &[Value]| tish_number_is_finite(args))),");
|
|
2579
|
+
self.writeln("(Arc::from(\"parseInt\"), Value::native(|args: &[Value]| tish_parse_int(args))),");
|
|
2580
|
+
self.writeln("(Arc::from(\"parseFloat\"), Value::native(|args: &[Value]| tish_parse_float(args))),");
|
|
2581
|
+
self.writeln("(Arc::from(\"MAX_SAFE_INTEGER\"), Value::Number(9007199254740991.0)),");
|
|
2582
|
+
self.writeln("(Arc::from(\"MIN_SAFE_INTEGER\"), Value::Number(-9007199254740991.0)),");
|
|
2583
|
+
self.writeln("(Arc::from(\"EPSILON\"), Value::Number(f64::EPSILON)),");
|
|
2584
|
+
self.writeln("(Arc::from(\"MAX_VALUE\"), Value::Number(f64::MAX)),");
|
|
2585
|
+
self.writeln("(Arc::from(\"MIN_VALUE\"), Value::Number(5e-324)),");
|
|
2586
|
+
self.writeln("(Arc::from(\"POSITIVE_INFINITY\"), Value::Number(f64::INFINITY)),");
|
|
2587
|
+
self.writeln("(Arc::from(\"NEGATIVE_INFINITY\"), Value::Number(f64::NEG_INFINITY)),");
|
|
2588
|
+
self.writeln("(Arc::from(\"NaN\"), Value::Number(f64::NAN)),");
|
|
2489
2589
|
self.indent -= 1;
|
|
2490
2590
|
self.writeln("]));");
|
|
2491
2591
|
|
|
@@ -2500,6 +2600,15 @@ impl Codegen {
|
|
|
2500
2600
|
self.writeln(
|
|
2501
2601
|
"(Arc::from(\"assign\"), Value::native(|args: &[Value]| tish_object_assign(args))),",
|
|
2502
2602
|
);
|
|
2603
|
+
self.writeln(
|
|
2604
|
+
"(Arc::from(\"is\"), Value::native(|args: &[Value]| tish_object_is(args))),",
|
|
2605
|
+
);
|
|
2606
|
+
self.writeln(
|
|
2607
|
+
"(Arc::from(\"hasOwn\"), Value::native(|args: &[Value]| tish_object_has_own(args))),",
|
|
2608
|
+
);
|
|
2609
|
+
self.writeln(
|
|
2610
|
+
"(Arc::from(\"getOwnPropertyNames\"), Value::native(|args: &[Value]| tish_object_keys(args))),",
|
|
2611
|
+
);
|
|
2503
2612
|
self.writeln(
|
|
2504
2613
|
"(Arc::from(\"keys\"), Value::native(|args: &[Value]| tish_object_keys(args))),",
|
|
2505
2614
|
);
|
|
@@ -2544,6 +2653,8 @@ impl Codegen {
|
|
|
2544
2653
|
self.writeln("p.insert(Arc::from(\"cwd\"), Value::native(|args: &[Value]| tish_process_cwd(args)));");
|
|
2545
2654
|
self.writeln("p.insert(Arc::from(\"exec\"), Value::native(|args: &[Value]| tish_process_exec(args)));");
|
|
2546
2655
|
self.writeln("p.insert(Arc::from(\"execFile\"), Value::native(|args: &[Value]| tish_process_exec_file(args)));");
|
|
2656
|
+
self.writeln("p.insert(Arc::from(\"execCapture\"), Value::native(|args: &[Value]| tish_process_exec_capture(args)));");
|
|
2657
|
+
self.writeln("p.insert(Arc::from(\"execFileCapture\"), Value::native(|args: &[Value]| tish_process_exec_file_capture(args)));");
|
|
2547
2658
|
self.writeln("let argv: Vec<Value> = std::env::args().map(|s| Value::String(s.into())).collect();");
|
|
2548
2659
|
self.writeln("p.insert(Arc::from(\"argv\"), Value::Array(VmRef::new(argv)));");
|
|
2549
2660
|
self.writeln("let mut env_obj = ObjectMap::default();");
|
|
@@ -4590,6 +4701,11 @@ impl Codegen {
|
|
|
4590
4701
|
if Self::expr_is_map_construct(init_e) {
|
|
4591
4702
|
self.map_instance_locals.insert(name.to_string());
|
|
4592
4703
|
}
|
|
4704
|
+
// #456 regression: a `new Map()`/`new Set()` local must not have its
|
|
4705
|
+
// keys/values/entries hijacked by the array fast-path.
|
|
4706
|
+
if Self::expr_is_map_construct(init_e) || Self::expr_is_set_construct(init_e) {
|
|
4707
|
+
self.collection_instance_locals.insert(name.to_string());
|
|
4708
|
+
}
|
|
4593
4709
|
}
|
|
4594
4710
|
|
|
4595
4711
|
// `let cum = cumulative_nv(&probs)` inherits `probs`' fixed length for in-bounds reads.
|
|
@@ -4805,6 +4921,46 @@ impl Codegen {
|
|
|
4805
4921
|
self.indent -= 1;
|
|
4806
4922
|
self.writeln("}");
|
|
4807
4923
|
}
|
|
4924
|
+
Statement::ForIn {
|
|
4925
|
+
name,
|
|
4926
|
+
name_span,
|
|
4927
|
+
object,
|
|
4928
|
+
body,
|
|
4929
|
+
span,
|
|
4930
|
+
} => {
|
|
4931
|
+
// Lower `for (let k in obj)` to `for (let k of Object.keys(obj))` (#413) and reuse the
|
|
4932
|
+
// native for-of emission. Object.keys yields the own enumerable keys the interpreter
|
|
4933
|
+
// enumerates (insertion order for objects, index strings for arrays, `[]` for a
|
|
4934
|
+
// non-object → `for (k in null)` no-ops).
|
|
4935
|
+
let dummy = Span {
|
|
4936
|
+
start: (0, 0),
|
|
4937
|
+
end: (0, 0),
|
|
4938
|
+
};
|
|
4939
|
+
let keys_call = Expr::Call {
|
|
4940
|
+
callee: Box::new(Expr::Member {
|
|
4941
|
+
object: Box::new(Expr::Ident {
|
|
4942
|
+
name: Arc::from("Object"),
|
|
4943
|
+
span: dummy,
|
|
4944
|
+
}),
|
|
4945
|
+
prop: MemberProp::Name {
|
|
4946
|
+
name: Arc::from("keys"),
|
|
4947
|
+
span: dummy,
|
|
4948
|
+
},
|
|
4949
|
+
optional: false,
|
|
4950
|
+
span: dummy,
|
|
4951
|
+
}),
|
|
4952
|
+
args: vec![CallArg::Expr(object.clone())],
|
|
4953
|
+
span: dummy,
|
|
4954
|
+
};
|
|
4955
|
+
let lowered = Statement::ForOf {
|
|
4956
|
+
name: Arc::clone(name),
|
|
4957
|
+
name_span: *name_span,
|
|
4958
|
+
iterable: keys_call,
|
|
4959
|
+
body: body.clone(),
|
|
4960
|
+
span: *span,
|
|
4961
|
+
};
|
|
4962
|
+
self.emit_statement(&lowered)?;
|
|
4963
|
+
}
|
|
4808
4964
|
Statement::ForOf {
|
|
4809
4965
|
name,
|
|
4810
4966
|
iterable,
|
|
@@ -5591,6 +5747,9 @@ impl Codegen {
|
|
|
5591
5747
|
"isFinite",
|
|
5592
5748
|
"encodeURI",
|
|
5593
5749
|
"decodeURI",
|
|
5750
|
+
"encodeURIComponent",
|
|
5751
|
+
"decodeURIComponent",
|
|
5752
|
+
"structuredClone",
|
|
5594
5753
|
"htmlEscape",
|
|
5595
5754
|
"registerStaticRoute",
|
|
5596
5755
|
"String",
|
|
@@ -6728,9 +6887,15 @@ impl Codegen {
|
|
|
6728
6887
|
return Ok(format!("tish_map_get({}, {})", map_ref, key_ref));
|
|
6729
6888
|
}
|
|
6730
6889
|
"set" if args.len() == 2 => {
|
|
6890
|
+
// Borrow the key/val places (like `get`/`has`) so a keyed local
|
|
6891
|
+
// isn't MOVED into the call and can still be used afterward — the
|
|
6892
|
+
// runtime `map_set` clones on insert (#442). `&Value` args also
|
|
6893
|
+
// route native-typed operands through the boxed path correctly.
|
|
6894
|
+
let key_ref = self.emit_borrowed_arg(&args[0])?;
|
|
6895
|
+
let val_ref = self.emit_borrowed_arg(&args[1])?;
|
|
6731
6896
|
return Ok(format!(
|
|
6732
6897
|
"tish_map_set({}, {}, {})",
|
|
6733
|
-
map_ref,
|
|
6898
|
+
map_ref, key_ref, val_ref
|
|
6734
6899
|
));
|
|
6735
6900
|
}
|
|
6736
6901
|
"values" if args.is_empty() => {
|
|
@@ -6786,9 +6951,14 @@ impl Codegen {
|
|
|
6786
6951
|
"indexOf" => {
|
|
6787
6952
|
let search = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6788
6953
|
let from = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6954
|
+
// Array `indexOf` takes `Option<&Value>` fromIndex; string keeps the &Value/Null form.
|
|
6955
|
+
let from_opt = match arg_exprs.get(1) {
|
|
6956
|
+
Some(e) => format!("Some(&{})", e),
|
|
6957
|
+
None => "None".to_string(),
|
|
6958
|
+
};
|
|
6789
6959
|
return Ok(format!(
|
|
6790
|
-
"{{ let _obj = ({}).clone(); match &_obj {{ Value::Array(_) => tishlang_runtime::array_index_of(&_obj, &{}), Value::String(_) => tishlang_runtime::string_index_of(&_obj, &{}, &{}), _ => Value::Number(-1.0) }} }}",
|
|
6791
|
-
obj_expr, search, search, from
|
|
6960
|
+
"{{ let _obj = ({}).clone(); match &_obj {{ Value::Array(_) => tishlang_runtime::array_index_of(&_obj, &{}, {}), Value::String(_) => tishlang_runtime::string_index_of(&_obj, &{}, &{}), _ => Value::Number(-1.0) }} }}",
|
|
6961
|
+
obj_expr, search, from_opt, search, from
|
|
6792
6962
|
));
|
|
6793
6963
|
}
|
|
6794
6964
|
"lastIndexOf" => {
|
|
@@ -6798,9 +6968,23 @@ impl Codegen {
|
|
|
6798
6968
|
} else {
|
|
6799
6969
|
"Value::Number(f64::INFINITY)".to_string()
|
|
6800
6970
|
};
|
|
6971
|
+
// Array lastIndexOf takes Option<&Value> fromIndex; string keeps &Value.
|
|
6972
|
+
let from_opt = match arg_exprs.get(1) {
|
|
6973
|
+
Some(e) => format!("Some(&{})", e),
|
|
6974
|
+
None => "None".to_string(),
|
|
6975
|
+
};
|
|
6976
|
+
return Ok(format!(
|
|
6977
|
+
"{{ let _obj = ({}).clone(); match &_obj {{ Value::Array(_) => tishlang_runtime::array_last_index_of(&_obj, &{}, {}), Value::String(_) => tishlang_runtime::string_last_index_of(&_obj, &{}, &{}), _ => Value::Number(-1.0) }} }}",
|
|
6978
|
+
obj_expr, search, from_opt, search, position
|
|
6979
|
+
));
|
|
6980
|
+
}
|
|
6981
|
+
"copyWithin" => {
|
|
6982
|
+
let t = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6983
|
+
let s = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6984
|
+
let e = arg_exprs.get(2).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6801
6985
|
return Ok(format!(
|
|
6802
|
-
"
|
|
6803
|
-
obj_expr,
|
|
6986
|
+
"tishlang_runtime::array_copy_within(&{}, &{}, &{}, &{})",
|
|
6987
|
+
obj_expr, t, s, e
|
|
6804
6988
|
));
|
|
6805
6989
|
}
|
|
6806
6990
|
"includes" => {
|
|
@@ -6821,6 +7005,9 @@ impl Codegen {
|
|
|
6821
7005
|
"reverse" => {
|
|
6822
7006
|
return Ok(format!("tishlang_runtime::array_reverse(&{})", obj_expr));
|
|
6823
7007
|
}
|
|
7008
|
+
"toReversed" => {
|
|
7009
|
+
return Ok(format!("tishlang_runtime::array_to_reversed(&{})", obj_expr));
|
|
7010
|
+
}
|
|
6824
7011
|
"fill" => {
|
|
6825
7012
|
let value = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6826
7013
|
let start = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
@@ -6879,6 +7066,26 @@ impl Codegen {
|
|
|
6879
7066
|
"trim" => {
|
|
6880
7067
|
return Ok(format!("tishlang_runtime::string_trim(&{})", obj_expr));
|
|
6881
7068
|
}
|
|
7069
|
+
"trimStart" => {
|
|
7070
|
+
return Ok(format!("tishlang_runtime::string_trim_start(&{})", obj_expr));
|
|
7071
|
+
}
|
|
7072
|
+
"trimEnd" => {
|
|
7073
|
+
return Ok(format!("tishlang_runtime::string_trim_end(&{})", obj_expr));
|
|
7074
|
+
}
|
|
7075
|
+
"normalize" => {
|
|
7076
|
+
let form = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7077
|
+
return Ok(format!(
|
|
7078
|
+
"tishlang_runtime::string_normalize(&{}, &{})",
|
|
7079
|
+
obj_expr, form
|
|
7080
|
+
));
|
|
7081
|
+
}
|
|
7082
|
+
"codePointAt" => {
|
|
7083
|
+
let idx = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7084
|
+
return Ok(format!(
|
|
7085
|
+
"tishlang_runtime::string_code_point_at(&{}, &{})",
|
|
7086
|
+
obj_expr, idx
|
|
7087
|
+
));
|
|
7088
|
+
}
|
|
6882
7089
|
"toUpperCase" => {
|
|
6883
7090
|
return Ok(format!("tishlang_runtime::string_to_upper_case(&{})", obj_expr));
|
|
6884
7091
|
}
|
|
@@ -6887,16 +7094,24 @@ impl Codegen {
|
|
|
6887
7094
|
}
|
|
6888
7095
|
"startsWith" => {
|
|
6889
7096
|
let search = arg_exprs.first().cloned().unwrap_or_else(|| "Value::String(\"\".into())".to_string());
|
|
7097
|
+
let pos = match arg_exprs.get(1) {
|
|
7098
|
+
Some(e) => format!("Some(&{})", e),
|
|
7099
|
+
None => "None".to_string(),
|
|
7100
|
+
};
|
|
6890
7101
|
return Ok(format!(
|
|
6891
|
-
"tishlang_runtime::string_starts_with(&{}, &{})",
|
|
6892
|
-
obj_expr, search
|
|
7102
|
+
"tishlang_runtime::string_starts_with(&{}, &{}, {})",
|
|
7103
|
+
obj_expr, search, pos
|
|
6893
7104
|
));
|
|
6894
7105
|
}
|
|
6895
7106
|
"endsWith" => {
|
|
6896
7107
|
let search = arg_exprs.first().cloned().unwrap_or_else(|| "Value::String(\"\".into())".to_string());
|
|
7108
|
+
let pos = match arg_exprs.get(1) {
|
|
7109
|
+
Some(e) => format!("Some(&{})", e),
|
|
7110
|
+
None => "None".to_string(),
|
|
7111
|
+
};
|
|
6897
7112
|
return Ok(format!(
|
|
6898
|
-
"tishlang_runtime::string_ends_with(&{}, &{})",
|
|
6899
|
-
obj_expr, search
|
|
7113
|
+
"tishlang_runtime::string_ends_with(&{}, &{}, {})",
|
|
7114
|
+
obj_expr, search, pos
|
|
6900
7115
|
));
|
|
6901
7116
|
}
|
|
6902
7117
|
"replace" => {
|
|
@@ -6926,6 +7141,13 @@ impl Codegen {
|
|
|
6926
7141
|
obj_expr, regexp
|
|
6927
7142
|
));
|
|
6928
7143
|
}
|
|
7144
|
+
"matchAll" if self.has_feature("regex") => {
|
|
7145
|
+
let regexp = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7146
|
+
return Ok(format!(
|
|
7147
|
+
"tishlang_runtime::string_match_all_regex(&{}, &{})",
|
|
7148
|
+
obj_expr, regexp
|
|
7149
|
+
));
|
|
7150
|
+
}
|
|
6929
7151
|
"search" if self.has_feature("regex") => {
|
|
6930
7152
|
let regexp = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6931
7153
|
return Ok(format!(
|
|
@@ -6987,6 +7209,20 @@ impl Codegen {
|
|
|
6987
7209
|
obj_expr, digits
|
|
6988
7210
|
));
|
|
6989
7211
|
}
|
|
7212
|
+
"toExponential" => {
|
|
7213
|
+
let digits = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7214
|
+
return Ok(format!(
|
|
7215
|
+
"tishlang_runtime::number_to_exponential(&{}, &{})",
|
|
7216
|
+
obj_expr, digits
|
|
7217
|
+
));
|
|
7218
|
+
}
|
|
7219
|
+
"toPrecision" => {
|
|
7220
|
+
let precision = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7221
|
+
return Ok(format!(
|
|
7222
|
+
"tishlang_runtime::number_to_precision(&{}, &{})",
|
|
7223
|
+
obj_expr, precision
|
|
7224
|
+
));
|
|
7225
|
+
}
|
|
6990
7226
|
"toString" => {
|
|
6991
7227
|
let radix = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
6992
7228
|
return Ok(format!(
|
|
@@ -7024,11 +7260,41 @@ impl Codegen {
|
|
|
7024
7260
|
}
|
|
7025
7261
|
}
|
|
7026
7262
|
let callback = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7263
|
+
// `array_reduce` takes `Option<&Value>` init: `None` = no initial value, so an
|
|
7264
|
+
// empty array with no init throws (per JS) instead of returning `null`.
|
|
7265
|
+
let init_arg = match arg_exprs.get(1) {
|
|
7266
|
+
Some(e) => format!("Some(&{})", e),
|
|
7267
|
+
None => "None".to_string(),
|
|
7268
|
+
};
|
|
7269
|
+
return Ok(format!(
|
|
7270
|
+
"tishlang_runtime::array_reduce(&{}, &{}, {})",
|
|
7271
|
+
obj_expr, callback, init_arg
|
|
7272
|
+
));
|
|
7273
|
+
}
|
|
7274
|
+
"reduceRight" => {
|
|
7275
|
+
let callback = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7276
|
+
let init_arg = match arg_exprs.get(1) {
|
|
7277
|
+
Some(e) => format!("Some(&{})", e),
|
|
7278
|
+
None => "None".to_string(),
|
|
7279
|
+
};
|
|
7027
7280
|
return Ok(format!(
|
|
7028
|
-
"tishlang_runtime::
|
|
7029
|
-
obj_expr, callback,
|
|
7281
|
+
"tishlang_runtime::array_reduce_right(&{}, &{}, {})",
|
|
7282
|
+
obj_expr, callback, init_arg
|
|
7030
7283
|
));
|
|
7031
7284
|
}
|
|
7285
|
+
// keys/values/entries exist on BOTH arrays and Map/Set. Arrays have no runtime
|
|
7286
|
+
// method table, so they need this fast-path; a known Map/Set local instead
|
|
7287
|
+
// falls through to the generic bound-method call (its get_prop resolves them).
|
|
7288
|
+
// (#456 regression — the unguarded arms hijacked `m.keys()`/`s.values()`.)
|
|
7289
|
+
"keys" if !self.kve_defers_to_generic(object.as_ref()) => {
|
|
7290
|
+
return Ok(format!("tishlang_runtime::array_keys(&{})", obj_expr));
|
|
7291
|
+
}
|
|
7292
|
+
"values" if !self.kve_defers_to_generic(object.as_ref()) => {
|
|
7293
|
+
return Ok(format!("tishlang_runtime::array_values(&{})", obj_expr));
|
|
7294
|
+
}
|
|
7295
|
+
"entries" if !self.kve_defers_to_generic(object.as_ref()) => {
|
|
7296
|
+
return Ok(format!("tishlang_runtime::array_entries(&{})", obj_expr));
|
|
7297
|
+
}
|
|
7032
7298
|
"forEach" => {
|
|
7033
7299
|
let callback = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7034
7300
|
return Ok(format!(
|
|
@@ -7133,6 +7399,38 @@ impl Codegen {
|
|
|
7133
7399
|
obj_expr, start, delete_count, items
|
|
7134
7400
|
));
|
|
7135
7401
|
}
|
|
7402
|
+
"toSpliced" => {
|
|
7403
|
+
let start = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Number(0.0)".to_string());
|
|
7404
|
+
let delete_count = arg_exprs.get(1).map(|d| format!("Some(&{})", d)).unwrap_or_else(|| "None".to_string());
|
|
7405
|
+
let items = if arg_exprs.len() > 2 {
|
|
7406
|
+
let items_vec = arg_exprs[2..].iter()
|
|
7407
|
+
.map(|a| format!("{}.clone()", a))
|
|
7408
|
+
.collect::<Vec<_>>()
|
|
7409
|
+
.join(", ");
|
|
7410
|
+
format!("&[{}]", items_vec)
|
|
7411
|
+
} else {
|
|
7412
|
+
"&[]".to_string()
|
|
7413
|
+
};
|
|
7414
|
+
return Ok(format!(
|
|
7415
|
+
"tishlang_runtime::array_to_spliced(&{}, &{}, {}, {})",
|
|
7416
|
+
obj_expr, start, delete_count, items
|
|
7417
|
+
));
|
|
7418
|
+
}
|
|
7419
|
+
"toSorted" => {
|
|
7420
|
+
let comparator = arg_exprs.first().map(|c| format!("Some(&{})", c)).unwrap_or_else(|| "None".to_string());
|
|
7421
|
+
return Ok(format!(
|
|
7422
|
+
"tishlang_runtime::array_to_sorted(&{}, {})",
|
|
7423
|
+
obj_expr, comparator
|
|
7424
|
+
));
|
|
7425
|
+
}
|
|
7426
|
+
"with" => {
|
|
7427
|
+
let index = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7428
|
+
let value = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7429
|
+
return Ok(format!(
|
|
7430
|
+
"tishlang_runtime::array_with(&{}, &{}, &{})",
|
|
7431
|
+
obj_expr, index, value
|
|
7432
|
+
));
|
|
7433
|
+
}
|
|
7136
7434
|
"flat" => {
|
|
7137
7435
|
let depth = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Number(1.0)".to_string());
|
|
7138
7436
|
return Ok(format!(
|
|
@@ -8175,7 +8473,12 @@ impl Codegen {
|
|
|
8175
8473
|
}
|
|
8176
8474
|
Self::collect_assigned_idents_in_stmt(body, names);
|
|
8177
8475
|
}
|
|
8178
|
-
Statement::ForOf { iterable, body, .. }
|
|
8476
|
+
Statement::ForOf { iterable, body, .. }
|
|
8477
|
+
| Statement::ForIn {
|
|
8478
|
+
object: iterable,
|
|
8479
|
+
body,
|
|
8480
|
+
..
|
|
8481
|
+
} => {
|
|
8179
8482
|
Self::collect_assigned_idents_in_expr(iterable, names);
|
|
8180
8483
|
Self::collect_assigned_idents_in_stmt(body, names);
|
|
8181
8484
|
}
|
|
@@ -8311,7 +8614,12 @@ impl Codegen {
|
|
|
8311
8614
|
|| update.as_ref().is_some_and(|u| Self::expr_has_length_changer(u))
|
|
8312
8615
|
|| Self::stmt_has_length_changer(body)
|
|
8313
8616
|
}
|
|
8314
|
-
Statement::ForOf { iterable, body, .. }
|
|
8617
|
+
Statement::ForOf { iterable, body, .. }
|
|
8618
|
+
| Statement::ForIn {
|
|
8619
|
+
object: iterable,
|
|
8620
|
+
body,
|
|
8621
|
+
..
|
|
8622
|
+
} => {
|
|
8315
8623
|
Self::expr_has_length_changer(iterable) || Self::stmt_has_length_changer(body)
|
|
8316
8624
|
}
|
|
8317
8625
|
Statement::While { cond, body, .. } | Statement::DoWhile { body, cond, .. } => {
|
|
@@ -8338,6 +8646,7 @@ impl Codegen {
|
|
|
8338
8646
|
| "normalize" | "localeCompare" | "map" | "filter" | "reduce" | "reduceRight"
|
|
8339
8647
|
| "forEach" | "find" | "findIndex" | "findLast" | "findLastIndex" | "some" | "every"
|
|
8340
8648
|
| "join" | "flat" | "flatMap" | "keys" | "values" | "entries"
|
|
8649
|
+
| "toReversed" | "toSorted" | "with" | "toSpliced"
|
|
8341
8650
|
)
|
|
8342
8651
|
}
|
|
8343
8652
|
|
|
@@ -8448,7 +8757,12 @@ impl Codegen {
|
|
|
8448
8757
|
|| update.as_ref().is_some_and(|u| Self::expr_changes_base_len(u, base))
|
|
8449
8758
|
|| Self::stmt_changes_base_len(body, base)
|
|
8450
8759
|
}
|
|
8451
|
-
Statement::ForOf { iterable, body, .. }
|
|
8760
|
+
Statement::ForOf { iterable, body, .. }
|
|
8761
|
+
| Statement::ForIn {
|
|
8762
|
+
object: iterable,
|
|
8763
|
+
body,
|
|
8764
|
+
..
|
|
8765
|
+
} => {
|
|
8452
8766
|
Self::expr_changes_base_len(iterable, base) || Self::stmt_changes_base_len(body, base)
|
|
8453
8767
|
}
|
|
8454
8768
|
Statement::While { cond, body, .. } | Statement::DoWhile { body, cond, .. } => {
|
|
@@ -8857,7 +9171,12 @@ impl Codegen {
|
|
|
8857
9171
|
}
|
|
8858
9172
|
Self::collect_captured_block_vars_from_statements(body, block_vars, result);
|
|
8859
9173
|
}
|
|
8860
|
-
Statement::ForOf { iterable, body, .. }
|
|
9174
|
+
Statement::ForOf { iterable, body, .. }
|
|
9175
|
+
| Statement::ForIn {
|
|
9176
|
+
object: iterable,
|
|
9177
|
+
body,
|
|
9178
|
+
..
|
|
9179
|
+
} => {
|
|
8861
9180
|
Self::collect_captured_block_vars_from_expr(iterable, block_vars, result);
|
|
8862
9181
|
Self::collect_captured_block_vars_from_statements(body, block_vars, result);
|
|
8863
9182
|
}
|
|
@@ -8995,7 +9314,8 @@ impl Codegen {
|
|
|
8995
9314
|
}
|
|
8996
9315
|
Self::collect_local_var_names(body, names);
|
|
8997
9316
|
}
|
|
8998
|
-
Statement::ForOf { body, .. }
|
|
9317
|
+
Statement::ForOf { body, .. }
|
|
9318
|
+
| Statement::ForIn { body, .. } => Self::collect_local_var_names(body, names),
|
|
8999
9319
|
Statement::While { body, .. } | Statement::DoWhile { body, .. } => {
|
|
9000
9320
|
Self::collect_local_var_names(body, names);
|
|
9001
9321
|
}
|
|
@@ -9261,7 +9581,12 @@ impl Codegen {
|
|
|
9261
9581
|
}
|
|
9262
9582
|
Self::collect_stmt_idents(body, idents);
|
|
9263
9583
|
}
|
|
9264
|
-
Statement::ForOf { iterable, body, .. }
|
|
9584
|
+
Statement::ForOf { iterable, body, .. }
|
|
9585
|
+
| Statement::ForIn {
|
|
9586
|
+
object: iterable,
|
|
9587
|
+
body,
|
|
9588
|
+
..
|
|
9589
|
+
} => {
|
|
9265
9590
|
Self::collect_expr_idents(iterable, idents);
|
|
9266
9591
|
Self::collect_stmt_idents(body, idents);
|
|
9267
9592
|
}
|
|
@@ -12150,6 +12475,18 @@ impl Codegen {
|
|
|
12150
12475
|
}
|
|
12151
12476
|
_ => false,
|
|
12152
12477
|
},
|
|
12478
|
+
// `Math.imul(a, b)` — an exact int32-domain node (emits an i32 `wrapping_mul`, see
|
|
12479
|
+
// `emit_int32_operand`), so it participates in an int32 chain when both operands are
|
|
12480
|
+
// themselves int32-lowerable. Lets `h = Math.imul(h, K) >>> 0` keep `h` in a register
|
|
12481
|
+
// across the loop instead of narrowing/widening `h` on every op.
|
|
12482
|
+
Expr::Call { callee, args, .. } => {
|
|
12483
|
+
matches!(callee.as_ref(),
|
|
12484
|
+
Expr::Member { object, prop: MemberProp::Name { name: m, .. }, .. }
|
|
12485
|
+
if m.as_ref() == "imul"
|
|
12486
|
+
&& matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Math"))
|
|
12487
|
+
&& matches!(args.as_slice(), [CallArg::Expr(a0), CallArg::Expr(a1)]
|
|
12488
|
+
if self.i32_chain_lowerable(a0, var) && self.i32_chain_lowerable(a1, var))
|
|
12489
|
+
}
|
|
12153
12490
|
_ => false,
|
|
12154
12491
|
}
|
|
12155
12492
|
}
|
|
@@ -12391,6 +12728,7 @@ impl Codegen {
|
|
|
12391
12728
|
}
|
|
12392
12729
|
Statement::For { body, .. }
|
|
12393
12730
|
| Statement::ForOf { body, .. }
|
|
12731
|
+
| Statement::ForIn { body, .. }
|
|
12394
12732
|
| Statement::While { body, .. }
|
|
12395
12733
|
| Statement::DoWhile { body, .. } => {
|
|
12396
12734
|
Self::collect_ordered_assigns_to(body, var, out)
|
|
@@ -12415,6 +12753,7 @@ impl Codegen {
|
|
|
12415
12753
|
}
|
|
12416
12754
|
Statement::For { body, .. }
|
|
12417
12755
|
| Statement::ForOf { body, .. }
|
|
12756
|
+
| Statement::ForIn { body, .. }
|
|
12418
12757
|
| Statement::While { body, .. }
|
|
12419
12758
|
| Statement::DoWhile { body, .. }
|
|
12420
12759
|
| Statement::FunDecl { body, .. } => f(std::slice::from_ref(body)),
|
|
@@ -12480,7 +12819,12 @@ impl Codegen {
|
|
|
12480
12819
|
}
|
|
12481
12820
|
Self::for_each_stmt_expr(body, f);
|
|
12482
12821
|
}
|
|
12483
|
-
Statement::ForOf { iterable, body, .. }
|
|
12822
|
+
Statement::ForOf { iterable, body, .. }
|
|
12823
|
+
| Statement::ForIn {
|
|
12824
|
+
object: iterable,
|
|
12825
|
+
body,
|
|
12826
|
+
..
|
|
12827
|
+
} => {
|
|
12484
12828
|
f(iterable);
|
|
12485
12829
|
Self::for_each_stmt_expr(body, f);
|
|
12486
12830
|
}
|
|
@@ -12893,7 +13237,11 @@ impl Codegen {
|
|
|
12893
13237
|
Self::collect_annotated_types(std::slice::from_ref(e), aliases, env);
|
|
12894
13238
|
}
|
|
12895
13239
|
}
|
|
12896
|
-
|
|
13240
|
+
// for-in's loop var is always a string key, so it gets no Vec-element type binding —
|
|
13241
|
+
// it behaves like while/do-while here (recurse the body only).
|
|
13242
|
+
Statement::While { body, .. }
|
|
13243
|
+
| Statement::DoWhile { body, .. }
|
|
13244
|
+
| Statement::ForIn { body, .. } => {
|
|
12897
13245
|
Self::collect_annotated_types(std::slice::from_ref(body), aliases, env)
|
|
12898
13246
|
}
|
|
12899
13247
|
Statement::ForOf {
|
|
@@ -13037,7 +13385,12 @@ impl Codegen {
|
|
|
13037
13385
|
}
|
|
13038
13386
|
Self::collect_reassignments_stmt(body, out);
|
|
13039
13387
|
}
|
|
13040
|
-
Statement::ForOf { iterable, body, .. }
|
|
13388
|
+
Statement::ForOf { iterable, body, .. }
|
|
13389
|
+
| Statement::ForIn {
|
|
13390
|
+
object: iterable,
|
|
13391
|
+
body,
|
|
13392
|
+
..
|
|
13393
|
+
} => {
|
|
13041
13394
|
Self::collect_reassignments_expr(iterable, out);
|
|
13042
13395
|
Self::collect_reassignments_stmt(body, out);
|
|
13043
13396
|
}
|
|
@@ -13299,6 +13652,22 @@ impl Codegen {
|
|
|
13299
13652
|
}
|
|
13300
13653
|
}
|
|
13301
13654
|
}
|
|
13655
|
+
// Two-arg `Math.imul(a, b)` → an exact 32-bit product (a number). Modelled so a value
|
|
13656
|
+
// fed by it (`h = Math.imul(h, K) >>> 0`) is not demoted to a boxed `Value`.
|
|
13657
|
+
if let [CallArg::Expr(_), CallArg::Expr(_)] = args.as_slice() {
|
|
13658
|
+
if let Expr::Member {
|
|
13659
|
+
object,
|
|
13660
|
+
prop: MemberProp::Name { name: method, .. },
|
|
13661
|
+
..
|
|
13662
|
+
} = callee.as_ref()
|
|
13663
|
+
{
|
|
13664
|
+
if method.as_ref() == "imul"
|
|
13665
|
+
&& matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Math")
|
|
13666
|
+
{
|
|
13667
|
+
return RustType::F64;
|
|
13668
|
+
}
|
|
13669
|
+
}
|
|
13670
|
+
}
|
|
13302
13671
|
// #169: a fused native `Vec<f64>` reduce produces an `f64` (the emitter lowers it via
|
|
13303
13672
|
// `native_vec_hof_for_call`). Model it here so an accumulator fed by `xs.reduce(...)`
|
|
13304
13673
|
// is not wrongly demoted to a boxed `Value`. Conservative: any miss → `Value`.
|
|
@@ -13984,7 +14353,16 @@ impl Codegen {
|
|
|
13984
14353
|
self.emit_native_fn_body(&statements[i])?;
|
|
13985
14354
|
} else {
|
|
13986
14355
|
self.emit_statement(&statements[i])?;
|
|
13987
|
-
|
|
14356
|
+
// Same guard as the primary emit site above: a native-typed frame
|
|
14357
|
+
// (`-> f64`/`-> Vec<..>`/`-> ()`) can't type the `Err`/`Value::Null` check, so
|
|
14358
|
+
// suppress it there — the parked throw still surfaces at the first Value/Result
|
|
14359
|
+
// frame up the chain. Without this guard the strict-`<` fast-path emitted an
|
|
14360
|
+
// untypeable checkpoint after any may-throw stmt (e.g. an index read post-#425),
|
|
14361
|
+
// breaking pure-numeric fixtures like fannkuch.
|
|
14362
|
+
if Self::stmt_may_throw(&statements[i])
|
|
14363
|
+
&& !Self::is_terminator_stmt(&statements[i])
|
|
14364
|
+
&& (self.try_closure_depth > 0 || !self.in_native_typed_frame())
|
|
14365
|
+
{
|
|
13988
14366
|
self.emit_pending_throw_check();
|
|
13989
14367
|
}
|
|
13990
14368
|
}
|
|
@@ -14115,7 +14493,8 @@ impl Codegen {
|
|
|
14115
14493
|
}
|
|
14116
14494
|
Self::walk_stmt_for_module_const_disqualifiers(body, g, bad);
|
|
14117
14495
|
}
|
|
14118
|
-
Statement::ForOf { body, .. }
|
|
14496
|
+
Statement::ForOf { body, .. }
|
|
14497
|
+
| Statement::ForIn { body, .. } => {
|
|
14119
14498
|
Self::walk_stmt_for_module_const_disqualifiers(body, g, bad);
|
|
14120
14499
|
}
|
|
14121
14500
|
Statement::Switch {
|
|
@@ -14536,6 +14915,32 @@ impl Codegen {
|
|
|
14536
14915
|
)
|
|
14537
14916
|
}
|
|
14538
14917
|
|
|
14918
|
+
fn expr_is_set_construct(expr: &Expr) -> bool {
|
|
14919
|
+
matches!(
|
|
14920
|
+
expr,
|
|
14921
|
+
Expr::New {
|
|
14922
|
+
callee,
|
|
14923
|
+
..
|
|
14924
|
+
} if matches!(callee.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Set")
|
|
14925
|
+
)
|
|
14926
|
+
}
|
|
14927
|
+
|
|
14928
|
+
/// #456 regression guard: should `<object>.keys()/.values()/.entries()` DEFER to the generic
|
|
14929
|
+
/// bound-method call instead of the array fast-path? True when `object` is not an array:
|
|
14930
|
+
/// - the `Object` global (`Object.keys/values/entries` are STATICS → `tish_object_*`),
|
|
14931
|
+
/// - a known `new Map()`/`new Set()` local, or a direct `new Map(…)`/`new Set(…)` receiver
|
|
14932
|
+
/// (their keys/values/entries resolve as bound methods via `get_prop`).
|
|
14933
|
+
/// Arrays (everything else) keep the fast-path — they have no runtime method table.
|
|
14934
|
+
fn kve_defers_to_generic(&self, object: &Expr) -> bool {
|
|
14935
|
+
match object {
|
|
14936
|
+
Expr::Ident { name, .. } => {
|
|
14937
|
+
let n = name.as_ref();
|
|
14938
|
+
n == "Object" || self.collection_instance_locals.contains(n)
|
|
14939
|
+
}
|
|
14940
|
+
_ => Self::expr_is_map_construct(object) || Self::expr_is_set_construct(object),
|
|
14941
|
+
}
|
|
14942
|
+
}
|
|
14943
|
+
|
|
14539
14944
|
/// #176 — classify top-level `let x = <number-literal>` bindings whose every use is a numeric
|
|
14540
14945
|
/// read or a whole-binding assign with a numeric-shaped RHS (fasta `seed`).
|
|
14541
14946
|
fn collect_native_numeric_globals(stmts: &[Statement]) -> HashMap<String, f64> {
|
|
@@ -14865,7 +15270,8 @@ impl Codegen {
|
|
|
14865
15270
|
Statement::While { body, .. }
|
|
14866
15271
|
| Statement::DoWhile { body, .. }
|
|
14867
15272
|
| Statement::For { body, .. }
|
|
14868
|
-
| Statement::ForOf { body, .. }
|
|
15273
|
+
| Statement::ForOf { body, .. }
|
|
15274
|
+
| Statement::ForIn { body, .. } => {
|
|
14869
15275
|
Self::walk_stmt_for_global_disqualifiers(body, g, bad);
|
|
14870
15276
|
}
|
|
14871
15277
|
Statement::Switch {
|
|
@@ -15105,7 +15511,12 @@ impl Codegen {
|
|
|
15105
15511
|
f(cond);
|
|
15106
15512
|
Self::for_each_expr_root_stmt(body, f);
|
|
15107
15513
|
}
|
|
15108
|
-
Statement::ForOf { iterable, body, .. }
|
|
15514
|
+
Statement::ForOf { iterable, body, .. }
|
|
15515
|
+
| Statement::ForIn {
|
|
15516
|
+
object: iterable,
|
|
15517
|
+
body,
|
|
15518
|
+
..
|
|
15519
|
+
} => {
|
|
15109
15520
|
f(iterable);
|
|
15110
15521
|
Self::for_each_expr_root_stmt(body, f);
|
|
15111
15522
|
}
|
|
@@ -15717,12 +16128,16 @@ impl Codegen {
|
|
|
15717
16128
|
}
|
|
15718
16129
|
Expr::Member { object, prop: MemberProp::Name { name: m, .. }, .. } => {
|
|
15719
16130
|
matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Math")
|
|
15720
|
-
&& args.len() == 1
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
16131
|
+
&& ((args.len() == 1
|
|
16132
|
+
&& matches!(
|
|
16133
|
+
m.as_ref(),
|
|
16134
|
+
"sqrt" | "sin" | "cos" | "tan" | "abs" | "floor" | "ceil"
|
|
16135
|
+
| "exp" | "trunc" | "log"
|
|
16136
|
+
))
|
|
16137
|
+
// 2-arg `Math.imul` inlines to an exact i32 wrapping multiply — a
|
|
16138
|
+
// native-safe numeric primitive (no dispatch), so a fn using it (FNV
|
|
16139
|
+
// with imul, xmur3/mulberry32 PRNGs) stays eligible for native typing.
|
|
16140
|
+
|| (args.len() == 2 && m.as_ref() == "imul"))
|
|
15726
16141
|
}
|
|
15727
16142
|
_ => false,
|
|
15728
16143
|
}
|
|
@@ -18325,7 +18740,8 @@ impl Codegen {
|
|
|
18325
18740
|
Statement::While { body, .. }
|
|
18326
18741
|
| Statement::DoWhile { body, .. }
|
|
18327
18742
|
| Statement::For { body, .. }
|
|
18328
|
-
| Statement::ForOf { body, .. }
|
|
18743
|
+
| Statement::ForOf { body, .. }
|
|
18744
|
+
| Statement::ForIn { body, .. } => Self::rec_collect_returns(body, out),
|
|
18329
18745
|
_ => {}
|
|
18330
18746
|
}
|
|
18331
18747
|
}
|
|
@@ -18469,7 +18885,8 @@ impl Codegen {
|
|
|
18469
18885
|
}
|
|
18470
18886
|
Statement::While { body, .. }
|
|
18471
18887
|
| Statement::DoWhile { body, .. }
|
|
18472
|
-
| Statement::ForOf { body, .. }
|
|
18888
|
+
| Statement::ForOf { body, .. }
|
|
18889
|
+
| Statement::ForIn { body, .. } => Self::rec_orch_binds_node(body, builders),
|
|
18473
18890
|
Statement::For { init, body, .. } => {
|
|
18474
18891
|
init.as_ref()
|
|
18475
18892
|
.is_some_and(|i| Self::rec_orch_binds_node(i, builders))
|
|
@@ -20553,7 +20970,8 @@ impl Codegen {
|
|
|
20553
20970
|
}
|
|
20554
20971
|
Statement::While { body, .. }
|
|
20555
20972
|
| Statement::DoWhile { body, .. }
|
|
20556
|
-
| Statement::ForOf { body, .. }
|
|
20973
|
+
| Statement::ForOf { body, .. }
|
|
20974
|
+
| Statement::ForIn { body, .. } => Self::agg_collect_aliases(body, p, out),
|
|
20557
20975
|
_ => {}
|
|
20558
20976
|
}
|
|
20559
20977
|
}
|
|
@@ -20581,7 +20999,8 @@ impl Codegen {
|
|
|
20581
20999
|
Statement::For { body, .. }
|
|
20582
21000
|
| Statement::While { body, .. }
|
|
20583
21001
|
| Statement::DoWhile { body, .. }
|
|
20584
|
-
| Statement::ForOf { body, .. }
|
|
21002
|
+
| Statement::ForOf { body, .. }
|
|
21003
|
+
| Statement::ForIn { body, .. } => Self::agg_stmt_writes(body, p, aliases),
|
|
20585
21004
|
_ => false,
|
|
20586
21005
|
}
|
|
20587
21006
|
}
|
|
@@ -20705,7 +21124,8 @@ impl Codegen {
|
|
|
20705
21124
|
Statement::For { body, .. }
|
|
20706
21125
|
| Statement::While { body, .. }
|
|
20707
21126
|
| Statement::DoWhile { body, .. }
|
|
20708
|
-
| Statement::ForOf { body, .. }
|
|
21127
|
+
| Statement::ForOf { body, .. }
|
|
21128
|
+
| Statement::ForIn { body, .. } => Self::stmt_returns_value(body),
|
|
20709
21129
|
_ => false,
|
|
20710
21130
|
}
|
|
20711
21131
|
}
|
|
@@ -20764,6 +21184,30 @@ impl Codegen {
|
|
|
20764
21184
|
return Ok(Some(code));
|
|
20765
21185
|
}
|
|
20766
21186
|
}
|
|
21187
|
+
// `Math.imul(a, b)` in an int32 context → the raw i32 `wrapping_mul` (no f64 excursion, no
|
|
21188
|
+
// `to_int32` round-trip on the product), when both operands are themselves int32-lowerable.
|
|
21189
|
+
// JS ES6 semantics (ToInt32 both operands, wrapping 32-bit multiply, signed result) are carried
|
|
21190
|
+
// by the operands' own int32 lowering. Keeps `h = Math.imul(h, K) >>> 0` fully register-domain.
|
|
21191
|
+
if let Expr::Call { callee, args, .. } = e {
|
|
21192
|
+
if let [CallArg::Expr(a0), CallArg::Expr(a1)] = args.as_slice() {
|
|
21193
|
+
if let Expr::Member {
|
|
21194
|
+
object,
|
|
21195
|
+
prop: MemberProp::Name { name: m, .. },
|
|
21196
|
+
..
|
|
21197
|
+
} = callee.as_ref()
|
|
21198
|
+
{
|
|
21199
|
+
if m.as_ref() == "imul"
|
|
21200
|
+
&& matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Math")
|
|
21201
|
+
{
|
|
21202
|
+
let ai = self.emit_int32_operand(a0)?;
|
|
21203
|
+
let bi = self.emit_int32_operand(a1)?;
|
|
21204
|
+
if let (Some(ai), Some(bi)) = (ai, bi) {
|
|
21205
|
+
return Ok(Some(format!("(({}).wrapping_mul({}))", ai, bi)));
|
|
21206
|
+
}
|
|
21207
|
+
}
|
|
21208
|
+
}
|
|
21209
|
+
}
|
|
21210
|
+
}
|
|
20767
21211
|
// Integer-literal leaf: `ToInt32(<int literal>)` is a compile-time constant — emit it
|
|
20768
21212
|
// directly (`v as i32` = modulo 2^32 = JS ToInt32 of the integer) instead of a runtime
|
|
20769
21213
|
// `to_int32(1_f64)` call. Removes the constant round-trip in masks/shift-counts
|
|
@@ -21334,6 +21778,35 @@ impl Codegen {
|
|
|
21334
21778
|
}
|
|
21335
21779
|
}
|
|
21336
21780
|
}
|
|
21781
|
+
// `Math.imul(a, b)` → an inline exact 32-bit wrapping multiply (ES6: ToInt32 both
|
|
21782
|
+
// operands, wrapping i32 multiply, signed int32 result). Both args go through
|
|
21783
|
+
// `emit_int32_operand` — the SAME ToInt32 lowering the bitwise path uses — so this is
|
|
21784
|
+
// bit-for-bit identical to the boxed `tish_math_imul` and to V8: typed==boxed==node
|
|
21785
|
+
// holds. It eliminates the per-call arg boxing + `value_call` a hot hashing/PRNG loop
|
|
21786
|
+
// (xmur3, mulberry32, FNV-with-imul) would otherwise pay. A general primitive (fires
|
|
21787
|
+
// for any `Math.imul`), not a fixture-specific kernel emission (cf. #317). Falls
|
|
21788
|
+
// through to the boxed call when an operand isn't int32-lowerable.
|
|
21789
|
+
if let [CallArg::Expr(a0), CallArg::Expr(a1)] = args.as_slice() {
|
|
21790
|
+
if let Expr::Member {
|
|
21791
|
+
object,
|
|
21792
|
+
prop: MemberProp::Name { name: method, .. },
|
|
21793
|
+
..
|
|
21794
|
+
} = callee.as_ref()
|
|
21795
|
+
{
|
|
21796
|
+
if method.as_ref() == "imul"
|
|
21797
|
+
&& matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Math")
|
|
21798
|
+
{
|
|
21799
|
+
let ai = self.emit_int32_operand(a0)?;
|
|
21800
|
+
let bi = self.emit_int32_operand(a1)?;
|
|
21801
|
+
if let (Some(ai), Some(bi)) = (ai, bi) {
|
|
21802
|
+
return Ok((
|
|
21803
|
+
format!("(({}).wrapping_mul({}) as f64)", ai, bi),
|
|
21804
|
+
RustType::F64,
|
|
21805
|
+
));
|
|
21806
|
+
}
|
|
21807
|
+
}
|
|
21808
|
+
}
|
|
21809
|
+
}
|
|
21337
21810
|
if let [CallArg::Expr(arg_expr)] = args.as_slice() {
|
|
21338
21811
|
if let Expr::Member {
|
|
21339
21812
|
object,
|