@tishlang/tish-format 3.1.0 → 3.3.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-format +0 -0
- package/crates/tish/src/cli_help.rs +4 -0
- package/crates/tish/src/main.rs +13 -2
- package/crates/tish/tests/rust_lib_crate.rs +140 -0
- package/crates/tish_compile/src/codegen.rs +1395 -107
- package/crates/tish_compile/src/lib.rs +14 -1
- package/crates/tish_compile/src/types.rs +6 -0
- package/crates/tish_compile/tests/perf_codegen_i32_native_abi.rs +113 -0
- package/crates/tish_compile/tests/regr_andor_rhs_move.rs +85 -0
- package/crates/tish_compile/tests/regr_gba_lowered_call_sites.rs +487 -0
- package/crates/tish_compile/tests/regr_rust_lib_emit.rs +150 -0
- package/crates/tish_fmt/Cargo.toml +1 -1
- package/crates/tish_fmt/src/lib.rs +54 -1
- package/crates/tish_native/src/build.rs +151 -6
- package/crates/tish_native/src/config.rs +14 -0
- package/crates/tish_runtime_gba/src/gba.rs +26 -6
- package/crates/tish_runtime_gba/src/lib.rs +46 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- package/platform/win32-x64/tish-fmt.exe +0 -0
|
@@ -1706,6 +1706,12 @@ impl Scanner {
|
|
|
1706
1706
|
self.chars.get(self.i + 1).copied()
|
|
1707
1707
|
}
|
|
1708
1708
|
|
|
1709
|
+
/// True when the char immediately before the cursor is `:`, i.e. the `//` about to
|
|
1710
|
+
/// be scanned is the scheme separator of a URL (`http://`) rather than a comment.
|
|
1711
|
+
fn prev_is_colon(&self) -> bool {
|
|
1712
|
+
self.i > 0 && self.chars.get(self.i - 1) == Some(&':')
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1709
1715
|
/// Consume one char, tracking line/col like the lexer (col resets after `\n`).
|
|
1710
1716
|
fn bump(&mut self) -> Option<char> {
|
|
1711
1717
|
let c = *self.chars.get(self.i)?;
|
|
@@ -1765,7 +1771,23 @@ impl Scanner {
|
|
|
1765
1771
|
self.seen_nonws = true;
|
|
1766
1772
|
self.scan_template();
|
|
1767
1773
|
}
|
|
1768
|
-
|
|
1774
|
+
// `://` is a URL scheme separator, never a comment. This scanner has no JSX
|
|
1775
|
+
// awareness, so JSX TEXT — which, unlike a string or an attribute value, is bare
|
|
1776
|
+
// source — used to have its URLs read as comments:
|
|
1777
|
+
//
|
|
1778
|
+
// <div>Redirect URI: http://127.0.0.1/callback</div>
|
|
1779
|
+
//
|
|
1780
|
+
// scanned `//127.0.0.1/callback</div>` as a line comment, and the printer then
|
|
1781
|
+
// re-emitted it after the element. Worse, the re-emitted text is a REAL comment, so
|
|
1782
|
+
// every subsequent run found one more and the file grew without converging — and
|
|
1783
|
+
// `--check` could never pass on such a file. See tishlang/tish#578.
|
|
1784
|
+
//
|
|
1785
|
+
// Guarding on the preceding `:` fixes the whole class (http://, file://, dune://)
|
|
1786
|
+
// without teaching this scanner to parse JSX, which would mean distinguishing
|
|
1787
|
+
// `<div>` from `a < b` and risks breaking comment scanning in ordinary code.
|
|
1788
|
+
'/' if self.peek2() == Some('/') && !self.prev_is_colon() => {
|
|
1789
|
+
self.scan_line_comment()
|
|
1790
|
+
}
|
|
1769
1791
|
'/' if self.peek2() == Some('*') => self.scan_block_comment(),
|
|
1770
1792
|
' ' | '\t' | '\r' | '\n' => {
|
|
1771
1793
|
self.bump();
|
|
@@ -1884,6 +1906,37 @@ mod tests {
|
|
|
1884
1906
|
let _ = tishlang_parser::parse(&out).unwrap();
|
|
1885
1907
|
}
|
|
1886
1908
|
|
|
1909
|
+
/// tishlang/tish#578 — a URL in JSX TEXT had its `//` scanned as a line comment, so the
|
|
1910
|
+
/// printer re-emitted everything after it as a trailing comment. The re-emitted text was
|
|
1911
|
+
/// itself a real comment, so each run appended another copy and the file never converged.
|
|
1912
|
+
#[test]
|
|
1913
|
+
fn url_in_jsx_text_is_not_a_comment() {
|
|
1914
|
+
let src = "/** @jsxImportSource lattish */\n\nexport fn Demo() {\n return <div>Redirect URI: http://127.0.0.1/callback for details</div>\n}\n";
|
|
1915
|
+
let once = format_source(src).unwrap();
|
|
1916
|
+
// Not `!contains("//127.0.0.1")` — that substring is part of the legitimate URL. The
|
|
1917
|
+
// corruption signature is the text appearing TWICE: once in the JSX, once hoisted out.
|
|
1918
|
+
assert_eq!(
|
|
1919
|
+
once.matches("127.0.0.1").count(),
|
|
1920
|
+
1,
|
|
1921
|
+
"URL text was duplicated into a comment:\n{once}"
|
|
1922
|
+
);
|
|
1923
|
+
// Idempotence is the property that actually broke: formatting must reach a fixed point,
|
|
1924
|
+
// otherwise `--check` can never pass no matter how many times the file is formatted.
|
|
1925
|
+
let twice = format_source(&once).unwrap();
|
|
1926
|
+
assert_eq!(once, twice, "formatting is not idempotent");
|
|
1927
|
+
let thrice = format_source(&twice).unwrap();
|
|
1928
|
+
assert_eq!(twice, thrice, "formatting is not idempotent");
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
/// The guard keys off the `:` immediately before `//`, so ordinary trailing comments —
|
|
1932
|
+
/// including ones right after a colon-bearing construct — must still be recognised.
|
|
1933
|
+
#[test]
|
|
1934
|
+
fn ordinary_comments_still_scanned() {
|
|
1935
|
+
let src = "fn f() {\n let o = { a: 1 } // trailing\n return o\n}\n";
|
|
1936
|
+
let out = format_source(src).unwrap();
|
|
1937
|
+
assert!(out.contains("// trailing"), "comment was dropped:\n{out}");
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1887
1940
|
/// Debug-render the parsed AST with all `Span { … }` contents removed, so two programs compare
|
|
1888
1941
|
/// equal iff they are structurally identical (reformatting legitimately changes spans).
|
|
1889
1942
|
fn structure(src: &str) -> String {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//! Build native binary via cargo (interim path until Cranelift backend is ready).
|
|
2
2
|
|
|
3
3
|
use std::fs;
|
|
4
|
-
use std::path::Path;
|
|
4
|
+
use std::path::{Path, PathBuf};
|
|
5
5
|
|
|
6
6
|
use tishlang_compile::ResolvedNativeModule;
|
|
7
7
|
|
|
@@ -149,6 +149,17 @@ pub fn build_via_cargo_with_config(
|
|
|
149
149
|
project_root,
|
|
150
150
|
);
|
|
151
151
|
}
|
|
152
|
+
if build_config.artifact == NativeArtifact::RustLib {
|
|
153
|
+
return emit_rust_lib_crate(
|
|
154
|
+
rust_code,
|
|
155
|
+
native_modules,
|
|
156
|
+
output_path,
|
|
157
|
+
features,
|
|
158
|
+
extra_dependencies_toml,
|
|
159
|
+
generated_native_rs,
|
|
160
|
+
project_root,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
152
163
|
|
|
153
164
|
let out_stem = output_path
|
|
154
165
|
.file_stem()
|
|
@@ -348,6 +359,122 @@ fn read_facade_agb_version(facade_path: &Path) -> Option<String> {
|
|
|
348
359
|
/// Runs its own `cargo build` (NOT `run_cargo_build`) so the `.cargo/config.toml`
|
|
349
360
|
/// rustflags (`-Tgba.ld`, `-Ctarget-cpu=arm7tdmi`) apply — `run_cargo_build` sets a
|
|
350
361
|
/// `RUSTFLAGS` env that would shadow them.
|
|
362
|
+
/// Read `version = "…"` from the `[package]` section of a crate's `Cargo.toml`.
|
|
363
|
+
fn read_crate_version(crate_dir: &Path) -> Option<String> {
|
|
364
|
+
let toml = fs::read_to_string(crate_dir.join("Cargo.toml")).ok()?;
|
|
365
|
+
for line in toml.lines() {
|
|
366
|
+
let line = line.trim();
|
|
367
|
+
if let Some(rest) = line.strip_prefix("version") {
|
|
368
|
+
let rest = rest.trim_start();
|
|
369
|
+
if let Some(rest) = rest.strip_prefix('=') {
|
|
370
|
+
return Some(rest.trim().trim_matches('"').to_string());
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// Only the first (package) table — stop before dependency versions.
|
|
374
|
+
if line.starts_with('[') && !line.starts_with("[package]") {
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
None
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/// `--target rust --emit lib`: write a Cargo **library source crate** to `output_path` and stop.
|
|
382
|
+
///
|
|
383
|
+
/// This is the one artifact that does not run cargo. The consumer decides what to build it for and
|
|
384
|
+
/// how to depend on it, exactly as they would with any other crate — which is the point: it lets a
|
|
385
|
+
/// Tish library be consumed from Rust the way `--target js` lets it be consumed from JS.
|
|
386
|
+
///
|
|
387
|
+
/// The `tishlang_runtime` dependency carries **both** `path` and `version`. Cargo prefers the path
|
|
388
|
+
/// locally and falls back to the registry version when the crate is published, so the emitted crate
|
|
389
|
+
/// is publishable without a post-processing step.
|
|
390
|
+
fn emit_rust_lib_crate(
|
|
391
|
+
rust_code: &str,
|
|
392
|
+
native_modules: Vec<ResolvedNativeModule>,
|
|
393
|
+
output_path: &Path,
|
|
394
|
+
features: &[String],
|
|
395
|
+
extra_dependencies_toml: &str,
|
|
396
|
+
generated_native_rs: Option<&str>,
|
|
397
|
+
project_root: Option<&Path>,
|
|
398
|
+
) -> Result<(), String> {
|
|
399
|
+
let crate_name = tishlang_build_utils::cargo_target_name(
|
|
400
|
+
output_path
|
|
401
|
+
.file_name()
|
|
402
|
+
.and_then(|s| s.to_str())
|
|
403
|
+
.unwrap_or("tish_lib"),
|
|
404
|
+
);
|
|
405
|
+
let runtime_path = tishlang_build_utils::find_runtime_path_for_project(project_root)?;
|
|
406
|
+
let runtime_version = read_crate_version(Path::new(&runtime_path))
|
|
407
|
+
.map(|v| format!(", version = {:?}", v))
|
|
408
|
+
.unwrap_or_default();
|
|
409
|
+
|
|
410
|
+
let runtime_features = runtime_features_for_cargo(features);
|
|
411
|
+
let runtime_refs: Vec<&str> = runtime_features.iter().map(String::as_str).collect();
|
|
412
|
+
let features_str = if runtime_refs.is_empty() {
|
|
413
|
+
String::new()
|
|
414
|
+
} else {
|
|
415
|
+
format!(", features = {:?}", runtime_refs)
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
let native_deps: String = native_modules
|
|
419
|
+
.iter()
|
|
420
|
+
.filter(|m| m.use_path_dependency)
|
|
421
|
+
.map(|m| {
|
|
422
|
+
let path = m.crate_path.display().to_string().replace('\\', "/");
|
|
423
|
+
format!("{} = {{ path = {:?} }}\n", m.package_name, path)
|
|
424
|
+
})
|
|
425
|
+
.collect();
|
|
426
|
+
|
|
427
|
+
let mut more_deps = String::new();
|
|
428
|
+
if !native_deps.is_empty() {
|
|
429
|
+
more_deps.push_str(&format!("\n{}", native_deps));
|
|
430
|
+
}
|
|
431
|
+
if !extra_dependencies_toml.trim().is_empty() {
|
|
432
|
+
more_deps.push_str(&format!("\n{}", extra_dependencies_toml));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
let lib_rs = if generated_native_rs.is_some() {
|
|
436
|
+
inject_generated_native_mod(rust_code)
|
|
437
|
+
} else {
|
|
438
|
+
rust_code.to_string()
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
let cargo_toml = format!(
|
|
442
|
+
r#"[package]
|
|
443
|
+
name = "{name}"
|
|
444
|
+
version = "0.1.0"
|
|
445
|
+
edition = "2021"
|
|
446
|
+
|
|
447
|
+
[lib]
|
|
448
|
+
name = "{name}"
|
|
449
|
+
crate-type = ["rlib"]
|
|
450
|
+
path = "src/lib.rs"
|
|
451
|
+
|
|
452
|
+
[dependencies]
|
|
453
|
+
tishlang_runtime = {{ path = {runtime:?}{version}{features} }}
|
|
454
|
+
{more}"#,
|
|
455
|
+
name = crate_name,
|
|
456
|
+
runtime = runtime_path,
|
|
457
|
+
version = runtime_version,
|
|
458
|
+
features = features_str,
|
|
459
|
+
more = more_deps,
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
let src_dir = output_path.join("src");
|
|
463
|
+
fs::create_dir_all(&src_dir)
|
|
464
|
+
.map_err(|e| format!("Cannot create {}: {}", src_dir.display(), e))?;
|
|
465
|
+
fs::write(output_path.join("Cargo.toml"), cargo_toml)
|
|
466
|
+
.map_err(|e| format!("Cannot write Cargo.toml: {}", e))?;
|
|
467
|
+
if let Some(gen) = generated_native_rs {
|
|
468
|
+
fs::write(src_dir.join("generated_native.rs"), gen)
|
|
469
|
+
.map_err(|e| format!("Cannot write generated_native.rs: {}", e))?;
|
|
470
|
+
}
|
|
471
|
+
fs::write(src_dir.join("lib.rs"), lib_rs)
|
|
472
|
+
.map_err(|e| format!("Cannot write lib.rs: {}", e))?;
|
|
473
|
+
|
|
474
|
+
println!("Emitted Rust library crate: {}", output_path.display());
|
|
475
|
+
Ok(())
|
|
476
|
+
}
|
|
477
|
+
|
|
351
478
|
fn build_gba_rom(
|
|
352
479
|
rust_code: &str,
|
|
353
480
|
native_modules: Vec<ResolvedNativeModule>,
|
|
@@ -500,15 +627,34 @@ runner = ["mgba-qt", "-C", "logToStdout=1", "-C", "logLevel.gba.debug=127"]
|
|
|
500
627
|
// with `-Tgba.ld` — cargo *joins* rustflags arrays across config files, so a
|
|
501
628
|
// config-file approach would pass `-Tgba.ld` twice ("region already defined").
|
|
502
629
|
// build-std still comes from the generated `[unstable]` config (not rustflags).
|
|
503
|
-
|
|
630
|
+
//
|
|
631
|
+
// TISH_GBA_TARGET_DIR (opt-in, explicit — deliberately NOT the ambient
|
|
632
|
+
// CARGO_TARGET_DIR, which the desktop path scrubs for good reason): share ONE
|
|
633
|
+
// cargo target dir across every example's ROM build. Each example otherwise
|
|
634
|
+
// carries a full private dependency tree — measured at ~45 GB across the
|
|
635
|
+
// tish-gba fleet, and it filled the disk mid-build once. An inherited
|
|
636
|
+
// CARGO_TARGET_DIR meanwhile made cargo write where the ELF lookup below
|
|
637
|
+
// never looked ("GBA ELF not found" on a SUCCESSFUL compile), so that env is
|
|
638
|
+
// scrubbed here and only the explicit opt-in steers both paths together.
|
|
639
|
+
let shared_target = std::env::var_os("TISH_GBA_TARGET_DIR").map(PathBuf::from);
|
|
640
|
+
let target_root = shared_target
|
|
641
|
+
.clone()
|
|
642
|
+
.unwrap_or_else(|| build_dir.join("target"));
|
|
643
|
+
let mut cargo = Command::new("cargo");
|
|
644
|
+
cargo
|
|
504
645
|
.arg("build")
|
|
505
646
|
.arg("--release")
|
|
506
647
|
.current_dir(&build_dir)
|
|
648
|
+
.env_remove("CARGO_TARGET_DIR")
|
|
507
649
|
.env(
|
|
508
650
|
"RUSTFLAGS",
|
|
509
651
|
"-Clink-arg=-Tgba.ld -Ctarget-cpu=arm7tdmi -Cforce-frame-pointers=yes",
|
|
510
652
|
)
|
|
511
|
-
.env("CARGO_TERM_COLOR", "always")
|
|
653
|
+
.env("CARGO_TERM_COLOR", "always");
|
|
654
|
+
if let Some(dir) = &shared_target {
|
|
655
|
+
cargo.env("CARGO_TARGET_DIR", dir);
|
|
656
|
+
}
|
|
657
|
+
let status = cargo
|
|
512
658
|
.status()
|
|
513
659
|
.map_err(|e| format!("Failed to run cargo for GBA build: {}", e))?;
|
|
514
660
|
if !status.success() {
|
|
@@ -518,9 +664,8 @@ runner = ["mgba-qt", "-C", "logToStdout=1", "-C", "logLevel.gba.debug=127"]
|
|
|
518
664
|
));
|
|
519
665
|
}
|
|
520
666
|
|
|
521
|
-
// ELF → .gba via agb-gbafix.
|
|
522
|
-
let elf =
|
|
523
|
-
.join("target")
|
|
667
|
+
// ELF → .gba via agb-gbafix. Resolved from the SAME root the build wrote to.
|
|
668
|
+
let elf = target_root
|
|
524
669
|
.join("thumbv4t-none-eabi")
|
|
525
670
|
.join("release")
|
|
526
671
|
.join(&cargo_name);
|
|
@@ -11,6 +11,10 @@ pub enum NativeArtifact {
|
|
|
11
11
|
/// A GBA ROM: cargo builds an ELF for `thumbv4t-none-eabi`, then `agb-gbafix`
|
|
12
12
|
/// converts it to a `.gba`.
|
|
13
13
|
GbaRom,
|
|
14
|
+
/// A Rust *library source crate* (`crate-type = ["rlib"]`) whose public API is the entry
|
|
15
|
+
/// module's `export fn`s. Unlike every other artifact, cargo is **not** run — the emitted
|
|
16
|
+
/// crate directory is itself the output, for the consumer to add as a path/registry dependency.
|
|
17
|
+
RustLib,
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
/// Options passed from the CLI into nested `cargo build`.
|
|
@@ -45,6 +49,16 @@ impl NativeBuildConfig {
|
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
|
|
52
|
+
/// `tish build --target rust --emit lib`: emit a Cargo library crate and stop. Host target —
|
|
53
|
+
/// the consumer decides what to build it for.
|
|
54
|
+
pub fn rust_lib() -> Self {
|
|
55
|
+
Self {
|
|
56
|
+
artifact: NativeArtifact::RustLib,
|
|
57
|
+
cargo_target: None,
|
|
58
|
+
emit_mode: NativeEmitMode::RustLib,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
pub fn is_cross_compile(&self) -> bool {
|
|
49
63
|
self.cargo_target.is_some()
|
|
50
64
|
}
|
|
@@ -101,21 +101,41 @@ type BgAsset = (
|
|
|
101
101
|
&'static agb::display::tile_data::TileData,
|
|
102
102
|
);
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
/// ⚠️ A FIXED TABLE, not a `Vec`, because every entry is `&'static` cartridge data and the registry
|
|
105
|
+
/// never needed to own one.
|
|
106
|
+
///
|
|
107
|
+
/// As a `Vec` it did: registering N backgrounds ran N pushes with log2(N) reallocs, all during
|
|
108
|
+
/// module init before the program body, and the freed buffers left the heap too fragmented for the
|
|
109
|
+
/// one big contiguous block a GBA UI canvas wants. A game with a handful of backgrounds booted; a
|
|
110
|
+
/// game with 162 died in the allocator with plenty of total heap free.
|
|
111
|
+
///
|
|
112
|
+
/// This costs `MAX_BGS * size_of::<Option<BgAsset>>()` of STATIC memory and allocates nothing.
|
|
113
|
+
/// ⚠️ Not a `Vec` with `reserve` — that charges every small game for the largest one.
|
|
114
|
+
const MAX_BGS: usize = 256;
|
|
115
|
+
static ASSET_BGS: SingleCore<RefCell<[Option<BgAsset>; MAX_BGS]>> =
|
|
116
|
+
SingleCore::new(RefCell::new([None; MAX_BGS]));
|
|
117
|
+
static ASSET_BGS_N: SingleCore<RefCell<usize>> = SingleCore::new(RefCell::new(0));
|
|
105
118
|
|
|
106
119
|
/// Register a background (palettes + tile data), returning its i32 handle.
|
|
107
120
|
pub fn __asset_register_bg(bg: BgAsset) -> i32 {
|
|
108
|
-
|
|
109
|
-
let mut
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
ASSET_BGS_N.with(|n| {
|
|
122
|
+
let mut n = n.borrow_mut();
|
|
123
|
+
if *n >= MAX_BGS {
|
|
124
|
+
return -1;
|
|
125
|
+
}
|
|
126
|
+
let idx = *n as i32;
|
|
127
|
+
ASSET_BGS.with(|c| c.borrow_mut()[*n] = Some(bg));
|
|
128
|
+
*n += 1;
|
|
112
129
|
idx
|
|
113
130
|
})
|
|
114
131
|
}
|
|
115
132
|
|
|
116
133
|
/// Look up a registered background by handle. `None` if out of range.
|
|
117
134
|
pub fn asset_bg(handle: i32) -> Option<BgAsset> {
|
|
118
|
-
|
|
135
|
+
if handle < 0 || handle as usize >= MAX_BGS {
|
|
136
|
+
return None;
|
|
137
|
+
}
|
|
138
|
+
ASSET_BGS.with(|c| c.borrow()[handle as usize])
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
/// Registered fonts (`font<N>:` imports) — a compile-time-baked `Font`. All `font<N>:` schemes share
|
|
@@ -266,6 +266,52 @@ pub fn get_index(obj: &Value, index: &Value) -> Value {
|
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
/// `&str` `s[i]` — char at a non-negative integer `idx`; non-int / negative / OOB → null.
|
|
270
|
+
///
|
|
271
|
+
/// Mirrors `tish_runtime::str_index`, and exists here because codegen emits a call to it whenever
|
|
272
|
+
/// the indexed expression is statically a `&str` rather than a `Value` — which is what a module
|
|
273
|
+
/// `const` string lowers to. Without it, `const D = "0123456789"` followed by `D[i]` is a hard
|
|
274
|
+
/// E0425 on the GBA target only, while the identical code compiles and runs everywhere else.
|
|
275
|
+
#[inline]
|
|
276
|
+
pub fn str_index(s: &str, idx: &Value) -> Value {
|
|
277
|
+
match idx {
|
|
278
|
+
Value::Number(n) if *n >= 0.0 && n.fract() == 0.0 => match s.chars().nth(*n as usize) {
|
|
279
|
+
Some(c) => Value::String(c.to_string().into()),
|
|
280
|
+
None => Value::Null,
|
|
281
|
+
},
|
|
282
|
+
_ => Value::Null,
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/// `&str` charCodeAt — the code unit at `idx`, NaN when out of range. Mirrors
|
|
287
|
+
/// `tish_runtime::str_char_code_at`, and missing here for the same reason `str_index` was: codegen
|
|
288
|
+
/// emits it whenever the receiver is statically a `&str`, so `"abc".charCodeAt(i)` was a hard E0425
|
|
289
|
+
/// on the GBA target only while compiling everywhere else. Found by a ROM that hashes its own trace.
|
|
290
|
+
#[inline]
|
|
291
|
+
pub fn str_char_code_at(s: &str, idx: &Value) -> Value {
|
|
292
|
+
let i = match idx {
|
|
293
|
+
Value::Number(n) if *n >= 0.0 && n.fract() == 0.0 => *n as usize,
|
|
294
|
+
_ => return Value::Number(f64::NAN),
|
|
295
|
+
};
|
|
296
|
+
match s.chars().nth(i) {
|
|
297
|
+
Some(c) => Value::Number(c as u32 as f64),
|
|
298
|
+
None => Value::Number(f64::NAN),
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/// `&str` charAt — the 1-character string at `idx`, `""` out of range. Same reason as above.
|
|
303
|
+
#[inline]
|
|
304
|
+
pub fn str_char_at(s: &str, idx: &Value) -> Value {
|
|
305
|
+
let i = match idx {
|
|
306
|
+
Value::Number(n) if *n >= 0.0 && n.fract() == 0.0 => *n as usize,
|
|
307
|
+
_ => return Value::String("".into()),
|
|
308
|
+
};
|
|
309
|
+
match s.chars().nth(i) {
|
|
310
|
+
Some(c) => Value::String(c.to_string().into()),
|
|
311
|
+
None => Value::String("".into()),
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
269
315
|
pub fn delete_property(obj: &Value, key: &Value) -> Value {
|
|
270
316
|
match obj {
|
|
271
317
|
Value::Object(m) => {
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|