@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
|
@@ -344,6 +344,8 @@ impl LanguageServer for Backend {
|
|
|
344
344
|
}
|
|
345
345
|
|
|
346
346
|
let mut src_root: Option<PathBuf> = None;
|
|
347
|
+
let mut init_platform: Option<String> = None;
|
|
348
|
+
let mut init_surface: Option<String> = None;
|
|
347
349
|
if let Some(opts) = ¶ms.initialization_options {
|
|
348
350
|
if let Some(s) = opts
|
|
349
351
|
.get("tishlangSourceRoot")
|
|
@@ -354,7 +356,23 @@ impl LanguageServer for Backend {
|
|
|
354
356
|
src_root = Some(PathBuf::from(s));
|
|
355
357
|
}
|
|
356
358
|
}
|
|
359
|
+
// Platform file cascade for imports (same as `tish resolve-id` / Vite).
|
|
360
|
+
// settings.json: "tish.platform" / "tish.surface", or env TISH_PLATFORM / TISH_SURFACE.
|
|
361
|
+
if let Some(p) = opts.get("platform").and_then(|v| v.as_str()) {
|
|
362
|
+
init_platform = Some(p.to_string());
|
|
363
|
+
} else if let Some(p) = opts.get("tishPlatform").and_then(|v| v.as_str()) {
|
|
364
|
+
init_platform = Some(p.to_string());
|
|
365
|
+
}
|
|
366
|
+
if let Some(s) = opts.get("surface").and_then(|v| v.as_str()) {
|
|
367
|
+
init_surface = Some(s.to_string());
|
|
368
|
+
} else if let Some(s) = opts.get("tishSurface").and_then(|v| v.as_str()) {
|
|
369
|
+
init_surface = Some(s.to_string());
|
|
370
|
+
}
|
|
357
371
|
}
|
|
372
|
+
let _ = tishlang_compile::apply_resolve_env(
|
|
373
|
+
init_platform.as_deref(),
|
|
374
|
+
init_surface.as_deref(),
|
|
375
|
+
);
|
|
358
376
|
if src_root.is_none() {
|
|
359
377
|
if let Ok(s) = std::env::var("TISHLANG_SOURCE_ROOT") {
|
|
360
378
|
let t = s.trim();
|
|
@@ -135,6 +135,17 @@ pub fn build_via_cargo_with_config(
|
|
|
135
135
|
project_root: Option<&Path>,
|
|
136
136
|
build_config: &NativeBuildConfig,
|
|
137
137
|
) -> Result<(), String> {
|
|
138
|
+
if build_config.artifact == NativeArtifact::GbaRom {
|
|
139
|
+
return build_gba_rom(
|
|
140
|
+
rust_code,
|
|
141
|
+
native_modules,
|
|
142
|
+
output_path,
|
|
143
|
+
extra_dependencies_toml,
|
|
144
|
+
generated_native_rs,
|
|
145
|
+
project_root,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
138
149
|
let out_stem = output_path
|
|
139
150
|
.file_stem()
|
|
140
151
|
.and_then(|s| s.to_str())
|
|
@@ -299,6 +310,241 @@ tishlang_runtime = {{ path = {:?}{} }}
|
|
|
299
310
|
Ok(())
|
|
300
311
|
}
|
|
301
312
|
|
|
313
|
+
/// Read the agb version the facade pins, from `<facade>/Cargo.toml`. This keeps the generated ROM
|
|
314
|
+
/// crate's agb in lockstep with the facade's (the single source of truth) — see [`build_gba_rom`].
|
|
315
|
+
/// Matches an `agb = "X"` or `agb = { version = "X", … }` line; `None` if absent (caller falls back).
|
|
316
|
+
fn read_facade_agb_version(facade_path: &Path) -> Option<String> {
|
|
317
|
+
let toml = fs::read_to_string(facade_path.join("Cargo.toml")).ok()?;
|
|
318
|
+
for line in toml.lines() {
|
|
319
|
+
// Require the key to be exactly `agb` (not `agb_foo`): after `agb`, the next non-space
|
|
320
|
+
// token must be `=`.
|
|
321
|
+
let Some(after_key) = line.trim_start().strip_prefix("agb") else {
|
|
322
|
+
continue;
|
|
323
|
+
};
|
|
324
|
+
let Some(after_eq) = after_key.trim_start().strip_prefix('=') else {
|
|
325
|
+
continue;
|
|
326
|
+
};
|
|
327
|
+
// Take the first double-quoted string (the version, in either the bare or table form).
|
|
328
|
+
let rest = after_eq.trim_start();
|
|
329
|
+
if let Some(start) = rest.find('"') {
|
|
330
|
+
if let Some(len) = rest[start + 1..].find('"') {
|
|
331
|
+
return Some(rest[start + 1..start + 1 + len].to_string());
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
None
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/// Build a Game Boy Advance ROM from generated `#![no_std]` Rust.
|
|
339
|
+
///
|
|
340
|
+
/// Emits an agb-style cargo project (nightly + build-std + `thumbv4t-none-eabi` +
|
|
341
|
+
/// `gba.ld`) that links the `tishlang_runtime_gba` facade under the `tishlang_runtime`
|
|
342
|
+
/// name (the `package =` rename), builds it, then runs `agb-gbafix` on the ELF.
|
|
343
|
+
///
|
|
344
|
+
/// Runs its own `cargo build` (NOT `run_cargo_build`) so the `.cargo/config.toml`
|
|
345
|
+
/// rustflags (`-Tgba.ld`, `-Ctarget-cpu=arm7tdmi`) apply — `run_cargo_build` sets a
|
|
346
|
+
/// `RUSTFLAGS` env that would shadow them.
|
|
347
|
+
fn build_gba_rom(
|
|
348
|
+
rust_code: &str,
|
|
349
|
+
native_modules: Vec<ResolvedNativeModule>,
|
|
350
|
+
output_path: &Path,
|
|
351
|
+
extra_dependencies_toml: &str,
|
|
352
|
+
generated_native_rs: Option<&str>,
|
|
353
|
+
project_root: Option<&Path>,
|
|
354
|
+
) -> Result<(), String> {
|
|
355
|
+
use std::process::Command;
|
|
356
|
+
|
|
357
|
+
// Locate the facade crate (sibling of tishlang_runtime in the tish workspace).
|
|
358
|
+
let runtime_path = tishlang_build_utils::find_runtime_path_for_project(project_root)?;
|
|
359
|
+
let facade_path = Path::new(&runtime_path)
|
|
360
|
+
.parent()
|
|
361
|
+
.ok_or_else(|| "invalid tishlang_runtime path (no parent)".to_string())?
|
|
362
|
+
.join("tish_runtime_gba");
|
|
363
|
+
if !facade_path.exists() {
|
|
364
|
+
return Err(format!(
|
|
365
|
+
"GBA runtime facade not found at {} (expected the tishlang_runtime_gba crate)",
|
|
366
|
+
facade_path.display()
|
|
367
|
+
));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Single-source the agb version from the facade's own Cargo.toml — the facade is the one crate
|
|
371
|
+
// that actually links agb (its `Fixed`, entry-point, and asset arenas are agb types), so it is
|
|
372
|
+
// the source of truth. The generated ROM crate must depend on the SAME agb: a skew would pull in
|
|
373
|
+
// two agb crates and mismatch types at the sprite-registration boundary. Falls back to a known
|
|
374
|
+
// pin only if the line can't be read (never blocks the build on a parse miss).
|
|
375
|
+
let agb_version =
|
|
376
|
+
read_facade_agb_version(&facade_path).unwrap_or_else(|| "0.25.0".to_string());
|
|
377
|
+
|
|
378
|
+
let out_stem = output_path
|
|
379
|
+
.file_stem()
|
|
380
|
+
.and_then(|s| s.to_str())
|
|
381
|
+
.unwrap_or("tish_out");
|
|
382
|
+
let cargo_name = tishlang_build_utils::cargo_target_name(out_stem);
|
|
383
|
+
|
|
384
|
+
// Persistent build dir under the project so the (expensive) build-std recompile
|
|
385
|
+
// is cached across runs; the generated Rust stays inspectable.
|
|
386
|
+
let base = project_root
|
|
387
|
+
.map(|p| p.to_path_buf())
|
|
388
|
+
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
|
|
389
|
+
let build_dir = base.join(".tish").join("gba").join(out_stem);
|
|
390
|
+
fs::create_dir_all(build_dir.join("src"))
|
|
391
|
+
.map_err(|e| format!("Cannot create GBA build dir: {}", e))?;
|
|
392
|
+
fs::create_dir_all(build_dir.join(".cargo"))
|
|
393
|
+
.map_err(|e| format!("Cannot create .cargo dir: {}", e))?;
|
|
394
|
+
|
|
395
|
+
// Path dependencies for any `cargo:` native modules the program imported.
|
|
396
|
+
let native_deps: String = native_modules
|
|
397
|
+
.iter()
|
|
398
|
+
.filter(|m| m.use_path_dependency)
|
|
399
|
+
.map(|m| {
|
|
400
|
+
let path = m.crate_path.display().to_string().replace('\\', "/");
|
|
401
|
+
format!("{} = {{ path = {:?} }}\n", m.package_name, path)
|
|
402
|
+
})
|
|
403
|
+
.collect();
|
|
404
|
+
|
|
405
|
+
// `cargo:` module path deps arrive via `extra_dependencies_toml`
|
|
406
|
+
// (`tish.rustDependencies`); `native_modules` covers any that use a path dep
|
|
407
|
+
// resolved another way. Both are appended to `[dependencies]`.
|
|
408
|
+
let mut more_deps = native_deps;
|
|
409
|
+
if !extra_dependencies_toml.trim().is_empty() {
|
|
410
|
+
more_deps.push('\n');
|
|
411
|
+
more_deps.push_str(extra_dependencies_toml);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let facade = facade_path.display().to_string().replace('\\', "/");
|
|
415
|
+
let cargo_toml = format!(
|
|
416
|
+
r#"[package]
|
|
417
|
+
name = "tish_output"
|
|
418
|
+
version = "0.1.0"
|
|
419
|
+
edition = "2021"
|
|
420
|
+
|
|
421
|
+
# Standalone workspace so a parent Cargo workspace (e.g. the tish-gba repo, when
|
|
422
|
+
# the build dir lives under it) doesn't try to absorb this generated crate.
|
|
423
|
+
[workspace]
|
|
424
|
+
|
|
425
|
+
[[bin]]
|
|
426
|
+
name = "{cargo_name}"
|
|
427
|
+
path = "src/main.rs"
|
|
428
|
+
|
|
429
|
+
[dependencies]
|
|
430
|
+
tishlang_runtime = {{ package = "tishlang_runtime_gba", path = {facade:?} }}
|
|
431
|
+
agb = "{agb_version}"
|
|
432
|
+
{more_deps}
|
|
433
|
+
[profile.dev]
|
|
434
|
+
opt-level = 3
|
|
435
|
+
debug = true
|
|
436
|
+
|
|
437
|
+
[profile.dev.build-override]
|
|
438
|
+
opt-level = 3
|
|
439
|
+
|
|
440
|
+
[profile.release]
|
|
441
|
+
opt-level = 3
|
|
442
|
+
lto = "fat"
|
|
443
|
+
debug = true
|
|
444
|
+
|
|
445
|
+
[profile.release.build-override]
|
|
446
|
+
opt-level = 3
|
|
447
|
+
"#,
|
|
448
|
+
);
|
|
449
|
+
fs::write(build_dir.join("Cargo.toml"), cargo_toml)
|
|
450
|
+
.map_err(|e| format!("Cannot write Cargo.toml: {}", e))?;
|
|
451
|
+
|
|
452
|
+
fs::write(
|
|
453
|
+
build_dir.join("rust-toolchain.toml"),
|
|
454
|
+
"[toolchain]\nchannel = \"nightly\"\ncomponents = [\"rust-src\"]\n",
|
|
455
|
+
)
|
|
456
|
+
.map_err(|e| format!("Cannot write rust-toolchain.toml: {}", e))?;
|
|
457
|
+
|
|
458
|
+
// build-std + target + gba.ld rustflags (agb ships gba.ld via its build.rs).
|
|
459
|
+
fs::write(
|
|
460
|
+
build_dir.join(".cargo").join("config.toml"),
|
|
461
|
+
r#"[unstable]
|
|
462
|
+
build-std = ["core", "alloc"]
|
|
463
|
+
build-std-features = ["compiler-builtins-mem"]
|
|
464
|
+
|
|
465
|
+
[build]
|
|
466
|
+
target = "thumbv4t-none-eabi"
|
|
467
|
+
|
|
468
|
+
[target.thumbv4t-none-eabi]
|
|
469
|
+
runner = ["mgba-qt", "-C", "logToStdout=1", "-C", "logLevel.gba.debug=127"]
|
|
470
|
+
"#,
|
|
471
|
+
)
|
|
472
|
+
.map_err(|e| format!("Cannot write .cargo/config.toml: {}", e))?;
|
|
473
|
+
|
|
474
|
+
let rust_main = if generated_native_rs.is_some() {
|
|
475
|
+
inject_generated_native_mod(rust_code)
|
|
476
|
+
} else {
|
|
477
|
+
rust_code.to_string()
|
|
478
|
+
};
|
|
479
|
+
if let Some(gen) = generated_native_rs {
|
|
480
|
+
// The `cargo:` wrapper emits the std header (`std::cell/rc/sync`); map it
|
|
481
|
+
// to the no_std facade equivalents (`alloc` is crate-wide via main.rs's
|
|
482
|
+
// `extern crate alloc`). The module body is otherwise Value-only.
|
|
483
|
+
let gen = gen
|
|
484
|
+
.replace("use std::cell::RefCell;", "use core::cell::RefCell;")
|
|
485
|
+
.replace("use std::rc::Rc;", "use alloc::rc::Rc;")
|
|
486
|
+
.replace("use std::sync::Arc;", "use tishlang_runtime::Arc;");
|
|
487
|
+
fs::write(build_dir.join("src").join("generated_native.rs"), gen)
|
|
488
|
+
.map_err(|e| format!("Cannot write generated_native.rs: {}", e))?;
|
|
489
|
+
}
|
|
490
|
+
fs::write(build_dir.join("src").join("main.rs"), rust_main)
|
|
491
|
+
.map_err(|e| format!("Cannot write main.rs: {}", e))?;
|
|
492
|
+
|
|
493
|
+
// Pass the GBA linker/target rustflags via the RUSTFLAGS env (which REPLACES,
|
|
494
|
+
// not joins, any `[target.*].rustflags` from config files). This matters when
|
|
495
|
+
// the build dir sits inside a project that already has a `.cargo/config.toml`
|
|
496
|
+
// with `-Tgba.ld` — cargo *joins* rustflags arrays across config files, so a
|
|
497
|
+
// config-file approach would pass `-Tgba.ld` twice ("region already defined").
|
|
498
|
+
// build-std still comes from the generated `[unstable]` config (not rustflags).
|
|
499
|
+
let status = Command::new("cargo")
|
|
500
|
+
.arg("build")
|
|
501
|
+
.arg("--release")
|
|
502
|
+
.current_dir(&build_dir)
|
|
503
|
+
.env(
|
|
504
|
+
"RUSTFLAGS",
|
|
505
|
+
"-Clink-arg=-Tgba.ld -Ctarget-cpu=arm7tdmi -Cforce-frame-pointers=yes",
|
|
506
|
+
)
|
|
507
|
+
.env("CARGO_TERM_COLOR", "always")
|
|
508
|
+
.status()
|
|
509
|
+
.map_err(|e| format!("Failed to run cargo for GBA build: {}", e))?;
|
|
510
|
+
if !status.success() {
|
|
511
|
+
return Err(format!(
|
|
512
|
+
"GBA cargo build failed (see output above). Build dir: {}",
|
|
513
|
+
build_dir.display()
|
|
514
|
+
));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// ELF → .gba via agb-gbafix.
|
|
518
|
+
let elf = build_dir
|
|
519
|
+
.join("target")
|
|
520
|
+
.join("thumbv4t-none-eabi")
|
|
521
|
+
.join("release")
|
|
522
|
+
.join(&cargo_name);
|
|
523
|
+
if !elf.exists() {
|
|
524
|
+
return Err(format!("GBA ELF not found at {}", elf.display()));
|
|
525
|
+
}
|
|
526
|
+
let rom = if output_path.extension().is_some_and(|e| e == "gba") {
|
|
527
|
+
output_path.to_path_buf()
|
|
528
|
+
} else {
|
|
529
|
+
output_path.with_extension("gba")
|
|
530
|
+
};
|
|
531
|
+
let gbafix = Command::new("agb-gbafix")
|
|
532
|
+
.arg(&elf)
|
|
533
|
+
.arg("-o")
|
|
534
|
+
.arg(&rom)
|
|
535
|
+
.status()
|
|
536
|
+
.map_err(|e| {
|
|
537
|
+
format!(
|
|
538
|
+
"Failed to run agb-gbafix ({}). Install it with: cargo install agb-gbafix",
|
|
539
|
+
e
|
|
540
|
+
)
|
|
541
|
+
})?;
|
|
542
|
+
if !gbafix.success() {
|
|
543
|
+
return Err("agb-gbafix failed to produce the ROM".to_string());
|
|
544
|
+
}
|
|
545
|
+
Ok(())
|
|
546
|
+
}
|
|
547
|
+
|
|
302
548
|
/// Remove a finished build's per-PID source directory (#384). Called only on SUCCESS, after the
|
|
303
549
|
/// binary is copied out — the dir is then disposable (it holds only the generated crate source;
|
|
304
550
|
/// compiled artifacts live in the shared workspace `target/`). Left in place on any earlier error so a
|
|
@@ -8,6 +8,9 @@ pub enum NativeArtifact {
|
|
|
8
8
|
#[default]
|
|
9
9
|
Bin,
|
|
10
10
|
StaticLib,
|
|
11
|
+
/// A GBA ROM: cargo builds an ELF for `thumbv4t-none-eabi`, then `agb-gbafix`
|
|
12
|
+
/// converts it to a `.gba`.
|
|
13
|
+
GbaRom,
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
/// Options passed from the CLI into nested `cargo build`.
|
|
@@ -32,11 +35,28 @@ impl NativeBuildConfig {
|
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
/// Game Boy Advance ROM build (`tish build --target gba`): cross-compile to
|
|
39
|
+
/// `thumbv4t-none-eabi` with the `Gba` emit mode.
|
|
40
|
+
pub fn gba() -> Self {
|
|
41
|
+
Self {
|
|
42
|
+
artifact: NativeArtifact::GbaRom,
|
|
43
|
+
cargo_target: Some("thumbv4t-none-eabi".to_string()),
|
|
44
|
+
emit_mode: NativeEmitMode::Gba,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
pub fn is_cross_compile(&self) -> bool {
|
|
36
49
|
self.cargo_target.is_some()
|
|
37
50
|
}
|
|
38
51
|
}
|
|
39
52
|
|
|
53
|
+
/// Feature cap for GBA builds: no host-only runtime capabilities (http/fs/process/
|
|
54
|
+
/// timers/ws/tty/pty). The facade `compile_error!`-stubs them, but capping keeps
|
|
55
|
+
/// the emitted prelude from importing names that don't exist on GBA.
|
|
56
|
+
pub fn gba_runtime_features(_features: &[String]) -> Vec<String> {
|
|
57
|
+
Vec::new()
|
|
58
|
+
}
|
|
59
|
+
|
|
40
60
|
/// Filter runtime features for iOS sandbox builds.
|
|
41
61
|
pub fn ios_runtime_features(features: &[String]) -> Vec<String> {
|
|
42
62
|
const ALLOW: &[&str] = &["http", "http-hyper", "regex", "timers"];
|
|
@@ -97,13 +97,23 @@ pub fn compile_to_native_with_config(
|
|
|
97
97
|
|
|
98
98
|
match backend {
|
|
99
99
|
Backend::Rust => {
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
// Cross-target builds cap runtime features to what the target supports:
|
|
101
|
+
// GBA gets none (no OS), iOS gets its sandbox allow-list.
|
|
102
|
+
let is_gba = build_config.emit_mode == tishlang_compile::NativeEmitMode::Gba;
|
|
103
|
+
let cap = |f: &[String]| -> Vec<String> {
|
|
104
|
+
if is_gba {
|
|
105
|
+
crate::config::gba_runtime_features(f)
|
|
106
|
+
} else {
|
|
107
|
+
ios_runtime_features(f)
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
let cross_cap = build_config.cargo_target.as_ref().map(|_| {
|
|
111
|
+
cap(features)
|
|
102
112
|
.into_iter()
|
|
103
113
|
.collect::<std::collections::HashSet<_>>()
|
|
104
114
|
});
|
|
105
|
-
let compile_features = if
|
|
106
|
-
|
|
115
|
+
let compile_features = if cross_cap.is_some() {
|
|
116
|
+
cap(features)
|
|
107
117
|
} else {
|
|
108
118
|
features.to_vec()
|
|
109
119
|
};
|
|
@@ -114,14 +124,14 @@ pub fn compile_to_native_with_config(
|
|
|
114
124
|
&compile_features,
|
|
115
125
|
optimize,
|
|
116
126
|
build_config.emit_mode,
|
|
117
|
-
|
|
127
|
+
cross_cap.as_ref(),
|
|
118
128
|
)
|
|
119
129
|
.map_err(|e| NativeError {
|
|
120
130
|
message: e.to_string(),
|
|
121
131
|
})?;
|
|
122
132
|
|
|
123
133
|
let features_for_cargo = if build_config.cargo_target.is_some() {
|
|
124
|
-
|
|
134
|
+
cap(&effective_features)
|
|
125
135
|
} else {
|
|
126
136
|
effective_features
|
|
127
137
|
};
|
|
@@ -101,6 +101,34 @@ mod tests {
|
|
|
101
101
|
assert!(parse("let x = async 5").is_err());
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/// #428 (function-expression half): `async function (…) {…}` / named `async function f(…) {…}`
|
|
105
|
+
/// in expression position parse as async ArrowFunctions — anonymous, named, object-property,
|
|
106
|
+
/// and IIFE-operand forms. Non-async function expressions keep `async_: false` (#464).
|
|
107
|
+
#[test]
|
|
108
|
+
fn async_function_expressions_parse() {
|
|
109
|
+
use tishlang_ast::Expr;
|
|
110
|
+
let fn_expr_async = |src: &str| -> bool {
|
|
111
|
+
let p = parse(src).expect("parse");
|
|
112
|
+
let Some(Statement::VarDecl { init: Some(e), .. }) = p.statements.first() else {
|
|
113
|
+
panic!("expected `let x = <fn expr>`");
|
|
114
|
+
};
|
|
115
|
+
match e {
|
|
116
|
+
Expr::ArrowFunction { async_, .. } => *async_,
|
|
117
|
+
other => panic!("expected ArrowFunction, got {other:?}"),
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
assert!(fn_expr_async("let f = async function() { return 1 }"), "anonymous");
|
|
121
|
+
assert!(fn_expr_async("let f = async function named(n) { return n }"), "named");
|
|
122
|
+
assert!(!fn_expr_async("let f = function() { return 1 }"), "plain fn expr is not async");
|
|
123
|
+
// Object-property and IIFE-operand positions.
|
|
124
|
+
assert!(parse("let o = { m: async function() { return 1 } }").is_ok());
|
|
125
|
+
assert!(parse("let p = (async function() { return 1 })()").is_ok());
|
|
126
|
+
assert!(parse("let q = (async () => { return 1 })()").is_ok());
|
|
127
|
+
// `async fn => …` stays a single-param arrow with param `fn` (#55 ident-compat) —
|
|
128
|
+
// the function-expression branch requires `(` or an identifier after `function`.
|
|
129
|
+
assert!(parse("let r = async fn => fn").is_ok());
|
|
130
|
+
}
|
|
131
|
+
|
|
104
132
|
#[test]
|
|
105
133
|
fn test_async_fn_parse() {
|
|
106
134
|
let program = parse("async fn foo() { }").expect("parse async fn");
|
|
@@ -197,6 +197,11 @@ impl<'a> Parser<'a> {
|
|
|
197
197
|
self.peek().map(|t| t.kind)
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
/// Kind of the token `n` positions past the current one (`peek_ahead_kind(0)` == `peek_kind`).
|
|
201
|
+
fn peek_ahead_kind(&self, n: usize) -> Option<TokenKind> {
|
|
202
|
+
self.tokens.get(self.pos + n).map(|t| t.kind)
|
|
203
|
+
}
|
|
204
|
+
|
|
200
205
|
fn advance(&mut self) -> Option<&Token> {
|
|
201
206
|
let t = self.tokens.get(self.pos);
|
|
202
207
|
if t.is_some() {
|
|
@@ -1060,7 +1065,8 @@ impl<'a> Parser<'a> {
|
|
|
1060
1065
|
|
|
1061
1066
|
/// `interface Name { k: T, ... }` — desugared to `type Name = { ... }` so the checker
|
|
1062
1067
|
/// (structural matching) and codegen (native `TishStruct_*`) treat it exactly like an
|
|
1063
|
-
/// object-type alias.
|
|
1068
|
+
/// object-type alias. `interface Name extends P1, P2 { ... }` desugars to an intersection
|
|
1069
|
+
/// of the parents with the body (see below), so inherited fields participate too.
|
|
1064
1070
|
fn parse_interface(&mut self) -> Result<Statement, String> {
|
|
1065
1071
|
let span_start = self.expect(TokenKind::Interface)?.span.start;
|
|
1066
1072
|
let name_tok = self.expect(TokenKind::Ident)?;
|
|
@@ -2464,9 +2470,11 @@ impl<'a> Parser<'a> {
|
|
|
2464
2470
|
value: Literal::Null,
|
|
2465
2471
|
span,
|
|
2466
2472
|
}),
|
|
2467
|
-
// `async` in expression position (#428): an async arrow — `async () => …`
|
|
2468
|
-
// `async x =>
|
|
2469
|
-
//
|
|
2473
|
+
// `async` in expression position (#428): an async arrow — `async () => …` /
|
|
2474
|
+
// `async x => …` — or an async function EXPRESSION — `async function (…) {…}` /
|
|
2475
|
+
// named `async function f(…) {…}` (desugars to an async ArrowFunction exactly like
|
|
2476
|
+
// the non-async `function` expression form, #464). The body runs in async context
|
|
2477
|
+
// (`await` allowed).
|
|
2470
2478
|
TokenKind::Async => {
|
|
2471
2479
|
if matches!(self.peek_kind(), Some(TokenKind::LParen)) {
|
|
2472
2480
|
self.advance(); // consume `(`
|
|
@@ -2475,6 +2483,19 @@ impl<'a> Parser<'a> {
|
|
|
2475
2483
|
}
|
|
2476
2484
|
return Err("Expected an arrow function after `async (`".to_string());
|
|
2477
2485
|
}
|
|
2486
|
+
// `async function (…) {…}` / `async function f(…) {…}`: `function` (`Fn`)
|
|
2487
|
+
// followed by `(` or an identifier is a function expression, mirroring the
|
|
2488
|
+
// non-async arm below. Checked BEFORE the single-param-arrow branch, which
|
|
2489
|
+
// would otherwise consume `function` as a parameter name and demand `=>`.
|
|
2490
|
+
if matches!(self.peek_kind(), Some(TokenKind::Fn))
|
|
2491
|
+
&& matches!(
|
|
2492
|
+
self.peek_ahead_kind(1),
|
|
2493
|
+
Some(TokenKind::LParen | TokenKind::Ident)
|
|
2494
|
+
)
|
|
2495
|
+
{
|
|
2496
|
+
self.advance(); // consume `function`
|
|
2497
|
+
return self.parse_function_expr_tail(&span, true);
|
|
2498
|
+
}
|
|
2478
2499
|
if matches!(
|
|
2479
2500
|
self.peek_kind(),
|
|
2480
2501
|
Some(TokenKind::Ident | TokenKind::Fn | TokenKind::Type)
|
|
@@ -16,6 +16,7 @@ pub use tishlang_core::ObjectMap;
|
|
|
16
16
|
// pass) instead of materializing an intermediate `AHashMap` and rebuilding from it.
|
|
17
17
|
pub use tishlang_core::{ObjectData, PropMap};
|
|
18
18
|
pub use tishlang_core::Value;
|
|
19
|
+
pub use tishlang_core::NumArrayBacking;
|
|
19
20
|
pub use tishlang_core::ArcStr;
|
|
20
21
|
/// Used by native codegen for `f()` / `obj()` dispatch (`Value::Function` or `__call` on objects).
|
|
21
22
|
/// Wraps `tishlang_core::value_call` with a pending-throw suppression check (#381): while a thrown
|
|
@@ -79,7 +80,7 @@ pub fn normalize_for_of(v: Value) -> Value {
|
|
|
79
80
|
// single-variant `if let Value::Array` in the spread / for-of codegen binds — otherwise it spreads
|
|
80
81
|
// to zero elements.
|
|
81
82
|
if let Value::NumberArray(arr) = &v {
|
|
82
|
-
let items: Vec<Value> = arr.borrow().
|
|
83
|
+
let items: Vec<Value> = arr.borrow().to_values();
|
|
83
84
|
return Value::Array(VmRef::new(items));
|
|
84
85
|
}
|
|
85
86
|
match tishlang_core::drain_iterator(&v) {
|
|
@@ -547,7 +548,8 @@ use tishlang_builtins::globals::{
|
|
|
547
548
|
decode_uri as builtins_decode_uri, encode_uri as builtins_encode_uri,
|
|
548
549
|
is_finite as builtins_is_finite, is_nan as builtins_is_nan,
|
|
549
550
|
object_assign as builtins_object_assign, object_entries as builtins_object_entries,
|
|
550
|
-
|
|
551
|
+
object_freeze as builtins_object_freeze, object_from_entries as builtins_object_from_entries,
|
|
552
|
+
object_is_frozen as builtins_object_is_frozen, object_keys as builtins_object_keys,
|
|
551
553
|
object_values as builtins_object_values,
|
|
552
554
|
number_convert as builtins_number_convert,
|
|
553
555
|
string_convert as builtins_string_convert,
|
|
@@ -1389,7 +1391,10 @@ pub fn get_prop(obj: &Value, key: impl AsRef<str>) -> Value {
|
|
|
1389
1391
|
if key == "length" {
|
|
1390
1392
|
Value::Number(arr.borrow().len() as f64)
|
|
1391
1393
|
} else if let Ok(idx) = key.parse::<usize>() {
|
|
1392
|
-
arr.borrow().get(idx).
|
|
1394
|
+
arr.borrow().get(idx).map(|v| match v {
|
|
1395
|
+
Value::Number(n) if n.is_nan() => Value::Null,
|
|
1396
|
+
other => other,
|
|
1397
|
+
}).unwrap_or(Value::Null)
|
|
1393
1398
|
} else {
|
|
1394
1399
|
Value::Null
|
|
1395
1400
|
}
|
|
@@ -1487,7 +1492,10 @@ pub fn get_index(obj: &Value, index: &Value) -> Value {
|
|
|
1487
1492
|
},
|
|
1488
1493
|
_ => return Value::Null,
|
|
1489
1494
|
};
|
|
1490
|
-
arr.borrow().get(idx).
|
|
1495
|
+
arr.borrow().get(idx).map(|v| match v {
|
|
1496
|
+
Value::Number(n) if n.is_nan() => Value::Null,
|
|
1497
|
+
other => other,
|
|
1498
|
+
}).unwrap_or(Value::Null)
|
|
1491
1499
|
}
|
|
1492
1500
|
// `str[i]` returns the character at index `i` (issue #17) — matches the VM /
|
|
1493
1501
|
// interpreter; out-of-bounds / negative / non-integer indices yield null.
|
|
@@ -1558,6 +1566,14 @@ pub fn set_prop(obj: &Value, key: &str, val: Value) -> Value {
|
|
|
1558
1566
|
// exists we re-use the existing `Arc<str>` and skip the
|
|
1559
1567
|
// alloc. Only newly-inserted keys pay for `Arc::from(key)`.
|
|
1560
1568
|
let mut m = map.borrow_mut();
|
|
1569
|
+
if m.frozen {
|
|
1570
|
+
// #437: a write to a frozen object throws a catchable TypeError (parked in the
|
|
1571
|
+
// pending-throw slot; the generated code surfaces it at the next checkpoint).
|
|
1572
|
+
tishlang_core::set_pending_throw(tishlang_core::type_error(format!(
|
|
1573
|
+
"Cannot assign to read only property '{key}' of a frozen object"
|
|
1574
|
+
)));
|
|
1575
|
+
return val;
|
|
1576
|
+
}
|
|
1561
1577
|
if let Some(slot) = m.strings.get_mut(key) {
|
|
1562
1578
|
*slot = val.clone();
|
|
1563
1579
|
} else {
|
|
@@ -1613,14 +1629,35 @@ pub fn set_index(obj: &Value, idx: &Value, val: Value) -> Value {
|
|
|
1613
1629
|
},
|
|
1614
1630
|
_ => panic!("Array index must be number"),
|
|
1615
1631
|
};
|
|
1616
|
-
let mut
|
|
1617
|
-
|
|
1618
|
-
|
|
1632
|
+
let mut b = arr.borrow_mut();
|
|
1633
|
+
match (b.as_packed_mut(), val.as_number()) {
|
|
1634
|
+
(Some(nums), Some(n)) => {
|
|
1635
|
+
while nums.len() <= index {
|
|
1636
|
+
nums.push(f64::NAN);
|
|
1637
|
+
}
|
|
1638
|
+
nums[index] = n;
|
|
1639
|
+
}
|
|
1640
|
+
// Non-numeric (or already deopted): upgrade in place, store the real Value.
|
|
1641
|
+
_ => {
|
|
1642
|
+
let boxed = b.deopt();
|
|
1643
|
+
while boxed.len() <= index {
|
|
1644
|
+
boxed.push(Value::Null);
|
|
1645
|
+
}
|
|
1646
|
+
boxed[index] = val.clone();
|
|
1647
|
+
}
|
|
1619
1648
|
}
|
|
1620
|
-
arr_mut[index] = val.as_number().unwrap_or(f64::NAN);
|
|
1621
1649
|
val
|
|
1622
1650
|
}
|
|
1623
|
-
Value::Object(
|
|
1651
|
+
Value::Object(map) => {
|
|
1652
|
+
if map.borrow().frozen {
|
|
1653
|
+
// #437: a write to a frozen object throws a catchable TypeError (parked; surfaced at
|
|
1654
|
+
// the next assignment checkpoint, same as `set_prop`).
|
|
1655
|
+
tishlang_core::set_pending_throw(tishlang_core::type_error(format!(
|
|
1656
|
+
"Cannot assign to read only property '{}' of a frozen object",
|
|
1657
|
+
idx.to_display_string()
|
|
1658
|
+
)));
|
|
1659
|
+
return val;
|
|
1660
|
+
}
|
|
1624
1661
|
tishlang_core::object_set(obj, idx, val.clone()).expect("object set");
|
|
1625
1662
|
val
|
|
1626
1663
|
}
|
|
@@ -1673,6 +1710,14 @@ pub fn object_keys(args: &[Value]) -> Value {
|
|
|
1673
1710
|
builtins_object_keys(args)
|
|
1674
1711
|
}
|
|
1675
1712
|
|
|
1713
|
+
pub fn object_freeze(args: &[Value]) -> Value {
|
|
1714
|
+
builtins_object_freeze(args)
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
pub fn object_is_frozen(args: &[Value]) -> Value {
|
|
1718
|
+
builtins_object_is_frozen(args)
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1676
1721
|
pub fn object_values(args: &[Value]) -> Value {
|
|
1677
1722
|
builtins_object_values(args)
|
|
1678
1723
|
}
|