@tishlang/tish 1.9.2 → 1.12.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 +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +8 -6
- package/crates/js_to_tish/src/transform/stmt.rs +12 -13
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/cargo_native_registry.rs +4 -1
- package/crates/tish/src/cli_help.rs +9 -1
- package/crates/tish/src/main.rs +66 -11
- package/crates/tish/tests/integration_test.rs +145 -7
- package/crates/tish_ast/src/ast.rs +3 -9
- package/crates/tish_build_utils/src/lib.rs +74 -23
- package/crates/tish_builtins/src/array.rs +2 -3
- package/crates/tish_builtins/src/construct.rs +15 -28
- package/crates/tish_builtins/src/globals.rs +18 -16
- package/crates/tish_builtins/src/helpers.rs +1 -4
- package/crates/tish_builtins/src/lib.rs +1 -0
- package/crates/tish_builtins/src/math.rs +7 -0
- package/crates/tish_builtins/src/object.rs +10 -10
- package/crates/tish_builtins/src/string.rs +27 -3
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_compile/src/codegen.rs +324 -158
- package/crates/tish_compile/src/lib.rs +39 -7
- package/crates/tish_compile/src/resolve.rs +191 -6
- package/crates/tish_compile/src/types.rs +6 -6
- package/crates/tish_compile_js/src/codegen.rs +8 -5
- package/crates/tish_core/src/console_style.rs +9 -0
- package/crates/tish_core/src/json.rs +17 -7
- package/crates/tish_core/src/macros.rs +2 -2
- package/crates/tish_core/src/value.rs +213 -4
- package/crates/tish_cranelift/src/link.rs +1 -1
- package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
- package/crates/tish_eval/src/eval.rs +135 -73
- package/crates/tish_eval/src/http.rs +18 -12
- package/crates/tish_eval/src/lib.rs +29 -0
- package/crates/tish_eval/src/regex.rs +1 -1
- package/crates/tish_eval/src/value.rs +89 -4
- package/crates/tish_eval/src/value_convert.rs +30 -8
- package/crates/tish_fmt/src/lib.rs +4 -1
- package/crates/tish_lexer/src/lib.rs +7 -2
- package/crates/tish_llvm/src/lib.rs +2 -2
- package/crates/tish_lsp/src/builtin_goto.rs +111 -10
- package/crates/tish_lsp/src/import_goto.rs +35 -22
- package/crates/tish_lsp/src/main.rs +118 -85
- package/crates/tish_native/src/build.rs +270 -24
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +139 -12
- package/crates/tish_parser/src/lib.rs +5 -2
- package/crates/tish_parser/src/parser.rs +45 -75
- package/crates/tish_pg/src/error.rs +1 -1
- package/crates/tish_pg/src/lib.rs +61 -73
- package/crates/tish_resolve/src/lib.rs +283 -158
- package/crates/tish_resolve/src/pos.rs +10 -2
- package/crates/tish_runtime/Cargo.toml +3 -0
- package/crates/tish_runtime/src/http.rs +39 -39
- package/crates/tish_runtime/src/http_fetch.rs +12 -12
- package/crates/tish_runtime/src/lib.rs +35 -44
- package/crates/tish_runtime/src/native_promise.rs +0 -11
- package/crates/tish_runtime/src/promise.rs +14 -1
- package/crates/tish_runtime/src/promise_io.rs +1 -4
- package/crates/tish_runtime/src/timers.rs +12 -7
- package/crates/tish_runtime/src/ws.rs +40 -27
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
- package/crates/tish_ui/src/jsx.rs +6 -4
- package/crates/tish_ui/src/lib.rs +5 -4
- package/crates/tish_ui/src/runtime/hooks.rs +123 -37
- package/crates/tish_ui/src/runtime/mod.rs +21 -41
- package/crates/tish_vm/Cargo.toml +2 -0
- package/crates/tish_vm/src/vm.rs +258 -153
- package/crates/tish_wasm/src/lib.rs +60 -7
- package/crates/tish_wasm_runtime/Cargo.toml +10 -1
- package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
- package/crates/tish_wasm_runtime/src/lib.rs +7 -1
- package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
- package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
- package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
- package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
- package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
- package/justfile +3 -3
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
package/bin/tish
CHANGED
|
Binary file
|
|
@@ -539,9 +539,10 @@ pub fn convert_params(
|
|
|
539
539
|
let fp = p;
|
|
540
540
|
{
|
|
541
541
|
let (name, name_span) = match &fp.pattern {
|
|
542
|
-
oxc::ast::ast::BindingPattern::BindingIdentifier(b) =>
|
|
543
|
-
|
|
544
|
-
|
|
542
|
+
oxc::ast::ast::BindingPattern::BindingIdentifier(b) => (
|
|
543
|
+
b.name.as_str(),
|
|
544
|
+
crate::span_util::oxc_span_to_tish(ctx.1, b.as_ref()),
|
|
545
|
+
),
|
|
545
546
|
_ => {
|
|
546
547
|
return Err(ConvertError::new(ConvertErrorKind::Unsupported {
|
|
547
548
|
what: "destructuring in params".into(),
|
|
@@ -565,9 +566,10 @@ pub fn convert_params(
|
|
|
565
566
|
if rest_param.is_none() {
|
|
566
567
|
if let Some(rest) = ¶ms.rest {
|
|
567
568
|
let (rest_name, rest_name_span) = match &rest.rest.argument {
|
|
568
|
-
oxc::ast::ast::BindingPattern::BindingIdentifier(b) =>
|
|
569
|
-
|
|
570
|
-
|
|
569
|
+
oxc::ast::ast::BindingPattern::BindingIdentifier(b) => (
|
|
570
|
+
b.name.as_str(),
|
|
571
|
+
crate::span_util::oxc_span_to_tish(ctx.1, b.as_ref()),
|
|
572
|
+
),
|
|
571
573
|
_ => {
|
|
572
574
|
return Err(ConvertError::new(ConvertErrorKind::Unsupported {
|
|
573
575
|
what: "rest param with non-identifier".into(),
|
|
@@ -247,9 +247,10 @@ fn convert_for_of_statement(
|
|
|
247
247
|
if v.declarations.len() == 1 {
|
|
248
248
|
let d = &v.declarations[0];
|
|
249
249
|
match &d.id {
|
|
250
|
-
oxc::ast::ast::BindingPattern::BindingIdentifier(b) =>
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
oxc::ast::ast::BindingPattern::BindingIdentifier(b) => (
|
|
251
|
+
b.name.as_str(),
|
|
252
|
+
span_util::oxc_span_to_tish(ctx.1, b.as_ref()),
|
|
253
|
+
),
|
|
253
254
|
_ => {
|
|
254
255
|
return Err(ConvertError::new(ConvertErrorKind::Incompatible {
|
|
255
256
|
what: "for-of with destructuring".into(),
|
|
@@ -368,16 +369,14 @@ fn convert_function_decl(
|
|
|
368
369
|
span: Span,
|
|
369
370
|
) -> Result<Statement, ConvertError> {
|
|
370
371
|
let async_ = f.r#async;
|
|
371
|
-
let name: Arc<str> =
|
|
372
|
-
.id
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
.map(|id| span_util::oxc_span_to_tish(ctx.1, id))
|
|
380
|
-
.unwrap_or_else(span_util::stub_span);
|
|
372
|
+
let name: Arc<str> =
|
|
373
|
+
f.id.as_ref()
|
|
374
|
+
.map(|id| Arc::from(id.name.as_str()))
|
|
375
|
+
.unwrap_or_else(|| Arc::from(""));
|
|
376
|
+
let name_span =
|
|
377
|
+
f.id.as_ref()
|
|
378
|
+
.map(|id| span_util::oxc_span_to_tish(ctx.1, id))
|
|
379
|
+
.unwrap_or_else(span_util::stub_span);
|
|
381
380
|
let (params, rest_param) = expr::convert_params(&f.params, ctx)?;
|
|
382
381
|
let body = match &f.body {
|
|
383
382
|
Some(fb) => {
|
package/crates/tish/Cargo.toml
CHANGED
|
@@ -19,7 +19,10 @@ pub(crate) fn register_bytecode_native_modules(vm: &mut tishlang_vm::Vm) {
|
|
|
19
19
|
Arc::from("query_prepared"),
|
|
20
20
|
Value::native(tishlang_pg::query_prepared),
|
|
21
21
|
);
|
|
22
|
-
om.insert(
|
|
22
|
+
om.insert(
|
|
23
|
+
Arc::from("query_all"),
|
|
24
|
+
Value::native(tishlang_pg::query_all),
|
|
25
|
+
);
|
|
23
26
|
om.insert(Arc::from("migrate"), Value::native(tishlang_pg::migrate));
|
|
24
27
|
om.insert(Arc::from("close"), Value::native(tishlang_pg::close));
|
|
25
28
|
vm.register_native_module("cargo:tish_pg", om);
|
|
@@ -405,6 +405,8 @@ pub fn build_after_help() -> String {
|
|
|
405
405
|
WebAssembly (.tish project; .js source supported on some paths)
|
|
406
406
|
{t}wasi{r}
|
|
407
407
|
WASI WebAssembly
|
|
408
|
+
{t}bytecode{r}
|
|
409
|
+
Raw serialized bytecode chunk (no VM binary/JS/HTML); for hosts that already ship the runtime
|
|
408
410
|
|
|
409
411
|
{oh}Native backends{r} (--native-backend, only with --target native, default: rust):
|
|
410
412
|
{t}rust{r}
|
|
@@ -506,7 +508,7 @@ pub(crate) struct BuildArgs {
|
|
|
506
508
|
help_heading = "Options"
|
|
507
509
|
)]
|
|
508
510
|
pub output: String,
|
|
509
|
-
/// `native`, `js`, `wasm`, or `
|
|
511
|
+
/// `native`, `js`, `wasm`, `wasi`, or `bytecode` (see long help below).
|
|
510
512
|
#[arg(
|
|
511
513
|
long,
|
|
512
514
|
default_value = "native",
|
|
@@ -530,6 +532,12 @@ pub(crate) struct BuildArgs {
|
|
|
530
532
|
help_heading = "Options"
|
|
531
533
|
)]
|
|
532
534
|
pub features: Vec<String>,
|
|
535
|
+
/// Cross-compile to an Apple iOS triple (e.g. `aarch64-apple-ios-sim`). Implies `--crate-type staticlib`.
|
|
536
|
+
#[arg(long, value_name = "TRIPLE", help_heading = "Options")]
|
|
537
|
+
pub ios_triple: Option<String>,
|
|
538
|
+
/// Output artifact for `--target native` (default: `bin`; use `staticlib` for embedded iOS).
|
|
539
|
+
#[arg(long, value_name = "TYPE", default_value = "bin", help_heading = "Options")]
|
|
540
|
+
pub crate_type: String,
|
|
533
541
|
#[arg(long, help_heading = "Options")]
|
|
534
542
|
pub no_optimize: bool,
|
|
535
543
|
/// For `--target js` project builds: emit `OUTPUT.js.map` and `//# sourceMappingURL=…` so JS/TS tools can jump to original `.tish` (implies `--no-optimize` for that build).
|
package/crates/tish/src/main.rs
CHANGED
|
@@ -4,13 +4,11 @@ mod cargo_native_registry;
|
|
|
4
4
|
mod cli_help;
|
|
5
5
|
mod repl_completion;
|
|
6
6
|
|
|
7
|
-
use std::cell::RefCell;
|
|
8
|
-
use tishlang_core::VmRef;
|
|
9
7
|
use std::collections::HashSet;
|
|
10
8
|
use std::fs;
|
|
11
9
|
use std::io::{self, IsTerminal, Read, Write};
|
|
12
10
|
use std::path::{Path, PathBuf};
|
|
13
|
-
use
|
|
11
|
+
use tishlang_core::VmRef;
|
|
14
12
|
|
|
15
13
|
use clap::FromArgMatches;
|
|
16
14
|
use rustyline::{Behavior, ColorMode, CompletionType, Config, Editor};
|
|
@@ -113,6 +111,8 @@ fn main() {
|
|
|
113
111
|
&a.features,
|
|
114
112
|
a.no_optimize || no_opt_env,
|
|
115
113
|
a.source_map,
|
|
114
|
+
a.ios_triple.as_deref(),
|
|
115
|
+
&a.crate_type,
|
|
116
116
|
),
|
|
117
117
|
Some(Commands::DumpAst { file }) => dump_ast(&file),
|
|
118
118
|
None => {
|
|
@@ -516,13 +516,14 @@ fn compile_to_js(
|
|
|
516
516
|
} else {
|
|
517
517
|
program
|
|
518
518
|
};
|
|
519
|
-
let js =
|
|
519
|
+
let js =
|
|
520
|
+
tishlang_compile_js::compile_with_jsx(&p, optimize).map_err(|e| format!("{}", e))?;
|
|
520
521
|
(js, None)
|
|
521
522
|
} else if input_path.extension().map(|e| e == "js") == Some(true) {
|
|
522
523
|
let source = fs::read_to_string(input_path).map_err(|e| format!("{}", e))?;
|
|
523
524
|
let program = tishlang_js_to_tish::convert(&source).map_err(|e| format!("{}", e))?;
|
|
524
|
-
let js =
|
|
525
|
-
|
|
525
|
+
let js = tishlang_compile_js::compile_with_jsx(&program, optimize)
|
|
526
|
+
.map_err(|e| format!("{}", e))?;
|
|
526
527
|
(js, None)
|
|
527
528
|
} else if source_map {
|
|
528
529
|
let bundle = tishlang_compile_js::compile_project_with_jsx_and_source_map(
|
|
@@ -545,7 +546,8 @@ fn compile_to_js(
|
|
|
545
546
|
let mut js_out = js;
|
|
546
547
|
if let Some(map) = &map_json {
|
|
547
548
|
let map_path = out_path.with_extension("js.map");
|
|
548
|
-
fs::write(&map_path, map)
|
|
549
|
+
fs::write(&map_path, map)
|
|
550
|
+
.map_err(|e| format!("Cannot write {}: {}", map_path.display(), e))?;
|
|
549
551
|
let map_url = map_path
|
|
550
552
|
.file_name()
|
|
551
553
|
.and_then(|s| s.to_str())
|
|
@@ -553,7 +555,8 @@ fn compile_to_js(
|
|
|
553
555
|
js_out.push_str(&format!("\n//# sourceMappingURL={map_url}\n"));
|
|
554
556
|
println!("Built: {}", map_path.display());
|
|
555
557
|
}
|
|
556
|
-
fs::write(&out_path, js_out)
|
|
558
|
+
fs::write(&out_path, js_out)
|
|
559
|
+
.map_err(|e| format!("Cannot write {}: {}", out_path.display(), e))?;
|
|
557
560
|
println!("Built: {}", out_path.display());
|
|
558
561
|
Ok(())
|
|
559
562
|
}
|
|
@@ -567,6 +570,8 @@ fn build_file(
|
|
|
567
570
|
cli_features: &[String],
|
|
568
571
|
no_optimize: bool,
|
|
569
572
|
source_map: bool,
|
|
573
|
+
ios_triple: Option<&str>,
|
|
574
|
+
crate_type: &str,
|
|
570
575
|
) -> Result<(), String> {
|
|
571
576
|
let optimize = !no_optimize;
|
|
572
577
|
let input_path = Path::new(input_path)
|
|
@@ -611,18 +616,37 @@ fn build_file(
|
|
|
611
616
|
Some(p)
|
|
612
617
|
}
|
|
613
618
|
});
|
|
619
|
+
let features = native_build_features_from_cli(cli_features);
|
|
614
620
|
return tishlang_wasm::compile_to_wasi(
|
|
615
621
|
&input_path,
|
|
616
622
|
project_root,
|
|
617
623
|
Path::new(output_path),
|
|
618
624
|
optimize,
|
|
625
|
+
&features,
|
|
626
|
+
)
|
|
627
|
+
.map_err(|e| e.to_string());
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if target == "bytecode" {
|
|
631
|
+
let project_root = input_path.parent().and_then(|p| {
|
|
632
|
+
if p.file_name().and_then(|n| n.to_str()) == Some("src") {
|
|
633
|
+
p.parent()
|
|
634
|
+
} else {
|
|
635
|
+
Some(p)
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
return tishlang_wasm::compile_to_bytecode(
|
|
639
|
+
&input_path,
|
|
640
|
+
project_root,
|
|
641
|
+
Path::new(output_path),
|
|
642
|
+
optimize,
|
|
619
643
|
)
|
|
620
644
|
.map_err(|e| e.to_string());
|
|
621
645
|
}
|
|
622
646
|
|
|
623
647
|
if target != "native" {
|
|
624
648
|
return Err(format!(
|
|
625
|
-
"Unknown target: {}. Use 'native', 'js', 'wasm', or '
|
|
649
|
+
"Unknown target: {}. Use 'native', 'js', 'wasm', 'wasi', or 'bytecode'.",
|
|
626
650
|
target
|
|
627
651
|
));
|
|
628
652
|
}
|
|
@@ -636,9 +660,31 @@ fn build_file(
|
|
|
636
660
|
});
|
|
637
661
|
let features: Vec<String> = native_build_features_from_cli(cli_features);
|
|
638
662
|
|
|
663
|
+
let build_config = if let Some(triple) = ios_triple {
|
|
664
|
+
tishlang_native::NativeBuildConfig::ios_staticlib(triple)
|
|
665
|
+
} else if crate_type == "staticlib" {
|
|
666
|
+
tishlang_native::NativeBuildConfig {
|
|
667
|
+
artifact: tishlang_native::NativeArtifact::StaticLib,
|
|
668
|
+
cargo_target: None,
|
|
669
|
+
emit_mode: tishlang_compile::NativeEmitMode::EmbeddedLib,
|
|
670
|
+
}
|
|
671
|
+
} else if crate_type != "bin" {
|
|
672
|
+
return Err(format!(
|
|
673
|
+
"Unknown --crate-type: {}. Use 'bin' or 'staticlib'.",
|
|
674
|
+
crate_type
|
|
675
|
+
));
|
|
676
|
+
} else {
|
|
677
|
+
tishlang_native::NativeBuildConfig::desktop()
|
|
678
|
+
};
|
|
679
|
+
|
|
639
680
|
if is_js {
|
|
640
681
|
let source = fs::read_to_string(&input_path).map_err(|e| format!("{}", e))?;
|
|
641
682
|
let program = tishlang_js_to_tish::convert(&source).map_err(|e| format!("{}", e))?;
|
|
683
|
+
if build_config.artifact != tishlang_native::NativeArtifact::Bin {
|
|
684
|
+
return Err(
|
|
685
|
+
"--crate-type staticlib / --ios-triple require a .tish entry file.".to_string(),
|
|
686
|
+
);
|
|
687
|
+
}
|
|
642
688
|
tishlang_native::compile_program_to_native(
|
|
643
689
|
&program,
|
|
644
690
|
project_root,
|
|
@@ -649,13 +695,14 @@ fn build_file(
|
|
|
649
695
|
)
|
|
650
696
|
.map_err(|e| e.to_string())?;
|
|
651
697
|
} else {
|
|
652
|
-
tishlang_native::
|
|
698
|
+
tishlang_native::compile_to_native_with_config(
|
|
653
699
|
&input_path,
|
|
654
700
|
project_root,
|
|
655
701
|
Path::new(output_path),
|
|
656
702
|
&features,
|
|
657
703
|
native_backend,
|
|
658
704
|
optimize,
|
|
705
|
+
&build_config,
|
|
659
706
|
)
|
|
660
707
|
.map_err(|e| e.to_string())?;
|
|
661
708
|
}
|
|
@@ -664,7 +711,15 @@ fn build_file(
|
|
|
664
711
|
.file_stem()
|
|
665
712
|
.and_then(|s| s.to_str())
|
|
666
713
|
.unwrap_or("tish_out");
|
|
667
|
-
let built_path = if
|
|
714
|
+
let built_path = if build_config.artifact == tishlang_native::NativeArtifact::StaticLib {
|
|
715
|
+
if output_path.ends_with('/') || Path::new(output_path).is_dir() {
|
|
716
|
+
Path::new(output_path).join(format!("lib{out_name}.a"))
|
|
717
|
+
} else if output_path.ends_with(".a") {
|
|
718
|
+
Path::new(output_path).to_path_buf()
|
|
719
|
+
} else {
|
|
720
|
+
Path::new(output_path).with_extension("a")
|
|
721
|
+
}
|
|
722
|
+
} else if output_path.ends_with('/') || Path::new(output_path).is_dir() {
|
|
668
723
|
Path::new(output_path).join(out_name)
|
|
669
724
|
} else {
|
|
670
725
|
Path::new(output_path).to_path_buf()
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
//! - Generate/update expected files: `REGENERATE_EXPECTED=1 cargo test -p tishlangtest_mvp_programs_interpreter`
|
|
6
6
|
//! then commit the new/updated `tests/core/*.tish.expected` files.
|
|
7
7
|
//! - Compiled outputs are cached under `target/integration_compile_cache/` per backend.
|
|
8
|
+
//! MVP native tests use `native_many/<hash>/` plus one batched nested Cargo build.
|
|
8
9
|
|
|
9
10
|
use std::collections::hash_map::DefaultHasher;
|
|
10
11
|
use std::ffi::OsString;
|
|
@@ -14,6 +15,7 @@ use std::path::{Path, PathBuf};
|
|
|
14
15
|
use std::process::Command;
|
|
15
16
|
|
|
16
17
|
use rayon::prelude::*;
|
|
18
|
+
use tishlang_native::compile_many_to_native;
|
|
17
19
|
|
|
18
20
|
fn workspace_root() -> PathBuf {
|
|
19
21
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
@@ -50,6 +52,72 @@ fn integration_compile_cache_dir() -> PathBuf {
|
|
|
50
52
|
target_dir().join("integration_compile_cache")
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
/// Match `tish build` with no `--feature`: link every capability compiled into this `tish` binary.
|
|
56
|
+
fn native_build_features_for_integration_test() -> Vec<String> {
|
|
57
|
+
let mut v: Vec<String> = tishlang_vm::all_compiled_capabilities()
|
|
58
|
+
.into_iter()
|
|
59
|
+
.collect();
|
|
60
|
+
v.sort();
|
|
61
|
+
v
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn combined_mvp_native_inputs_hash(paths: &[PathBuf]) -> u64 {
|
|
65
|
+
let mut h = DefaultHasher::new();
|
|
66
|
+
let feats = native_build_features_for_integration_test();
|
|
67
|
+
feats.len().hash(&mut h);
|
|
68
|
+
for f in &feats {
|
|
69
|
+
f.hash(&mut h);
|
|
70
|
+
}
|
|
71
|
+
paths.len().hash(&mut h);
|
|
72
|
+
for p in paths {
|
|
73
|
+
p.file_name()
|
|
74
|
+
.unwrap_or_default()
|
|
75
|
+
.to_string_lossy()
|
|
76
|
+
.hash(&mut h);
|
|
77
|
+
file_content_hash(p).hash(&mut h);
|
|
78
|
+
}
|
|
79
|
+
// Native batch cache must invalidate when the emitter or `value_call` changes — not only
|
|
80
|
+
// when `.tish` sources change; otherwise CI/rust-cache can keep stale nested binaries.
|
|
81
|
+
let codegen_rs = workspace_root().join("crates/tish_compile/src/codegen.rs");
|
|
82
|
+
if codegen_rs.is_file() {
|
|
83
|
+
file_content_hash(&codegen_rs).hash(&mut h);
|
|
84
|
+
}
|
|
85
|
+
let value_rs = workspace_root().join("crates/tish_core/src/value.rs");
|
|
86
|
+
if value_rs.is_file() {
|
|
87
|
+
file_content_hash(&value_rs).hash(&mut h);
|
|
88
|
+
}
|
|
89
|
+
h.finish()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fn mvp_native_batch_cache_dir(combined: u64) -> PathBuf {
|
|
93
|
+
integration_compile_cache_dir()
|
|
94
|
+
.join("native_many")
|
|
95
|
+
.join(format!("{:016x}", combined))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Restores the previous process env when dropped (for `TISH_FAST_NATIVE_BUILD` in batch tests).
|
|
99
|
+
struct EnvVarGuard {
|
|
100
|
+
key: &'static str,
|
|
101
|
+
previous: Option<std::ffi::OsString>,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
impl EnvVarGuard {
|
|
105
|
+
fn set(key: &'static str, value: &str) -> Self {
|
|
106
|
+
let previous = std::env::var_os(key);
|
|
107
|
+
std::env::set_var(key, value);
|
|
108
|
+
Self { key, previous }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
impl Drop for EnvVarGuard {
|
|
113
|
+
fn drop(&mut self) {
|
|
114
|
+
match &self.previous {
|
|
115
|
+
None => std::env::remove_var(self.key),
|
|
116
|
+
Some(v) => std::env::set_var(self.key, v),
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
53
121
|
fn file_content_hash(path: &Path) -> u64 {
|
|
54
122
|
let mut f = std::fs::File::open(path).expect("open file for hash");
|
|
55
123
|
let mut content = Vec::new();
|
|
@@ -583,6 +651,7 @@ const MVP_TEST_FILES: &[&str] = &[
|
|
|
583
651
|
"break_continue.tish",
|
|
584
652
|
"length.tish",
|
|
585
653
|
"objects.tish",
|
|
654
|
+
"symbol.tish",
|
|
586
655
|
"conditional.tish",
|
|
587
656
|
"switch.tish",
|
|
588
657
|
"do_while.tish",
|
|
@@ -714,6 +783,7 @@ fn test_mvp_programs_interp_vm_stdout_parity() {
|
|
|
714
783
|
/// Compile each .tish file to native, run, and compare stdout to static expected (parallelized).
|
|
715
784
|
#[test]
|
|
716
785
|
fn test_mvp_programs_native() {
|
|
786
|
+
let _fast_native = EnvVarGuard::set("TISH_FAST_NATIVE_BUILD", "1");
|
|
717
787
|
let core_dir = core_dir();
|
|
718
788
|
let bin = tish_bin();
|
|
719
789
|
assert!(
|
|
@@ -721,18 +791,86 @@ fn test_mvp_programs_native() {
|
|
|
721
791
|
"tish binary not found at {}. Run `cargo build -p tishlang` first.",
|
|
722
792
|
bin.display()
|
|
723
793
|
);
|
|
724
|
-
|
|
725
|
-
|
|
794
|
+
|
|
795
|
+
let mut paths: Vec<PathBuf> = MVP_TEST_FILES
|
|
796
|
+
.iter()
|
|
726
797
|
.filter_map(|name| {
|
|
727
|
-
let
|
|
728
|
-
if
|
|
729
|
-
|
|
798
|
+
let p = core_dir.join(name);
|
|
799
|
+
if p.exists() {
|
|
800
|
+
Some(p)
|
|
801
|
+
} else {
|
|
802
|
+
None
|
|
730
803
|
}
|
|
731
|
-
|
|
804
|
+
})
|
|
805
|
+
.collect();
|
|
806
|
+
paths.sort();
|
|
807
|
+
|
|
808
|
+
if paths.is_empty() {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
let combined = combined_mvp_native_inputs_hash(&paths);
|
|
813
|
+
let cache_dir = mvp_native_batch_cache_dir(combined);
|
|
814
|
+
let _ = std::fs::create_dir_all(&cache_dir);
|
|
815
|
+
|
|
816
|
+
let ext = if cfg!(target_os = "windows") {
|
|
817
|
+
".exe"
|
|
818
|
+
} else {
|
|
819
|
+
""
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
let entries_owned: Vec<(PathBuf, PathBuf)> = paths
|
|
823
|
+
.iter()
|
|
824
|
+
.map(|p| {
|
|
825
|
+
let stem = p.file_stem().unwrap().to_string_lossy();
|
|
826
|
+
let cached = cache_dir.join(format!("{}{}", stem, ext));
|
|
827
|
+
(p.clone(), cached)
|
|
828
|
+
})
|
|
829
|
+
.collect();
|
|
830
|
+
|
|
831
|
+
let need_build = entries_owned.iter().any(|(_, o)| !o.exists());
|
|
832
|
+
if need_build {
|
|
833
|
+
let refs: Vec<(&Path, &Path)> = entries_owned
|
|
834
|
+
.iter()
|
|
835
|
+
.map(|(a, b)| (a.as_path(), b.as_path()))
|
|
836
|
+
.collect();
|
|
837
|
+
let feats = native_build_features_for_integration_test();
|
|
838
|
+
compile_many_to_native(&refs, Some(workspace_root().as_path()), &feats, true)
|
|
839
|
+
.unwrap_or_else(|e| panic!("compile_many_to_native: {}", e.message));
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Run each binary sequentially. Parallel `fs::copy` + `exec` caused Linux ETXTBSY (errno 26)
|
|
843
|
+
// in CI when several threads replaced/ran temp executables under load.
|
|
844
|
+
let errors: Vec<String> = entries_owned
|
|
845
|
+
.iter()
|
|
846
|
+
.enumerate()
|
|
847
|
+
.filter_map(|(run_index, (path, cached_bin))| {
|
|
848
|
+
let expected = match get_expected(path) {
|
|
732
849
|
Some(e) => e,
|
|
733
850
|
None => return Some(format!("missing expected: {}", path.display())),
|
|
734
851
|
};
|
|
735
|
-
|
|
852
|
+
if !cached_bin.exists() {
|
|
853
|
+
return Some(format!("missing cached binary: {}", cached_bin.display()));
|
|
854
|
+
}
|
|
855
|
+
let stem = path.file_stem().unwrap().to_string_lossy();
|
|
856
|
+
let ext_bin = cached_bin
|
|
857
|
+
.extension()
|
|
858
|
+
.map(|e| e.to_string_lossy().to_string())
|
|
859
|
+
.unwrap_or_default();
|
|
860
|
+
let temp_dest = std::env::temp_dir().join(format!(
|
|
861
|
+
"tish_mvp_native_{}_{:x}_{}_{}",
|
|
862
|
+
stem,
|
|
863
|
+
file_content_hash(path),
|
|
864
|
+
std::process::id(),
|
|
865
|
+
run_index
|
|
866
|
+
));
|
|
867
|
+
let temp_dest = if ext_bin.is_empty() {
|
|
868
|
+
temp_dest
|
|
869
|
+
} else {
|
|
870
|
+
temp_dest.with_extension(&ext_bin)
|
|
871
|
+
};
|
|
872
|
+
std::fs::copy(cached_bin, &temp_dest).expect("copy cached native bin to temp");
|
|
873
|
+
let out_bin = temp_dest;
|
|
736
874
|
let out = match Command::new(&out_bin)
|
|
737
875
|
.current_dir(workspace_root())
|
|
738
876
|
.output()
|
|
@@ -129,15 +129,9 @@ pub enum ImportSpecifier {
|
|
|
129
129
|
alias_span: Option<Span>,
|
|
130
130
|
},
|
|
131
131
|
/// Namespace: * as M
|
|
132
|
-
Namespace {
|
|
133
|
-
name: Arc<str>,
|
|
134
|
-
name_span: Span,
|
|
135
|
-
},
|
|
132
|
+
Namespace { name: Arc<str>, name_span: Span },
|
|
136
133
|
/// Default: import X from "..."
|
|
137
|
-
Default {
|
|
138
|
-
name: Arc<str>,
|
|
139
|
-
name_span: Span,
|
|
140
|
-
},
|
|
134
|
+
Default { name: Arc<str>, name_span: Span },
|
|
141
135
|
}
|
|
142
136
|
|
|
143
137
|
/// Export declaration: named (const/let/fn) or default
|
|
@@ -616,7 +610,7 @@ impl Statement {
|
|
|
616
610
|
| Statement::DoWhile { span, .. }
|
|
617
611
|
| Statement::Throw { span, .. }
|
|
618
612
|
| Statement::Try { span, .. }
|
|
619
|
-
|
|
|
613
|
+
| Statement::Import { span, .. }
|
|
620
614
|
| Statement::Export { span, .. }
|
|
621
615
|
| Statement::TypeAlias { span, .. }
|
|
622
616
|
| Statement::DeclareVar { span, .. }
|
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
use std::fs;
|
|
7
7
|
use std::path::{Path, PathBuf};
|
|
8
8
|
use std::process::Command;
|
|
9
|
+
use std::sync::Mutex;
|
|
10
|
+
|
|
11
|
+
/// Serialize nested `cargo build` calls that share the workspace `target/` dir (tests + `tish build`).
|
|
12
|
+
static NESTED_CARGO_MUTEX: Mutex<()> = Mutex::new(());
|
|
13
|
+
|
|
14
|
+
fn mold_available() -> bool {
|
|
15
|
+
Command::new("mold")
|
|
16
|
+
.arg("--version")
|
|
17
|
+
.output()
|
|
18
|
+
.map(|o| o.status.success())
|
|
19
|
+
.unwrap_or(false)
|
|
20
|
+
}
|
|
9
21
|
|
|
10
22
|
/// True if `root` looks like the Tish language repo (has `crates/tish_runtime`).
|
|
11
23
|
///
|
|
@@ -161,7 +173,11 @@ pub fn find_workspace_root() -> Result<PathBuf, String> {
|
|
|
161
173
|
let candidate = dir.join("tish");
|
|
162
174
|
if is_tish_workspace_root(&candidate) {
|
|
163
175
|
return candidate.canonicalize().map_err(|e| {
|
|
164
|
-
format!(
|
|
176
|
+
format!(
|
|
177
|
+
"Cannot canonicalize Tish workspace {}: {}",
|
|
178
|
+
candidate.display(),
|
|
179
|
+
e
|
|
180
|
+
)
|
|
165
181
|
});
|
|
166
182
|
}
|
|
167
183
|
if !dir.pop() {
|
|
@@ -322,6 +338,11 @@ pub fn find_crate_path(crate_name: &str) -> Result<PathBuf, String> {
|
|
|
322
338
|
Ok(crate_path)
|
|
323
339
|
}
|
|
324
340
|
|
|
341
|
+
/// Sanitize a user-chosen output stem for Cargo `[lib]` / `[[bin]]` target names.
|
|
342
|
+
pub fn cargo_target_name(stem: &str) -> String {
|
|
343
|
+
stem.replace('-', "_")
|
|
344
|
+
}
|
|
345
|
+
|
|
325
346
|
/// Create a temp build directory with src subdir.
|
|
326
347
|
pub fn create_build_dir(prefix: &str, out_name: &str) -> Result<PathBuf, String> {
|
|
327
348
|
let build_dir =
|
|
@@ -364,26 +385,36 @@ fn protoc_for_nested_cargo() -> Option<PathBuf> {
|
|
|
364
385
|
}
|
|
365
386
|
|
|
366
387
|
/// Run cargo build in the given directory.
|
|
367
|
-
/// If
|
|
368
|
-
pub fn run_cargo_build(
|
|
388
|
+
/// If `cross_target` is Some, passes `--target` and skips `-C target-cpu=native`.
|
|
389
|
+
pub fn run_cargo_build(
|
|
390
|
+
build_dir: &Path,
|
|
391
|
+
target_dir: Option<&Path>,
|
|
392
|
+
cross_target: Option<&str>,
|
|
393
|
+
) -> Result<(), String> {
|
|
394
|
+
let _nested_guard = NESTED_CARGO_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
|
|
395
|
+
|
|
369
396
|
let target_dir = target_dir
|
|
370
397
|
.map(|p| p.to_path_buf())
|
|
371
398
|
.unwrap_or_else(|| build_dir.join("target"));
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
399
|
+
let fast_native = std::env::var("TISH_FAST_NATIVE_BUILD").as_deref() == Ok("1");
|
|
400
|
+
|
|
401
|
+
let mut merged_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
|
|
402
|
+
if cross_target.is_some() {
|
|
403
|
+
// Cross-compiling (e.g. iOS): do not use host target-cpu.
|
|
404
|
+
} else if fast_native {
|
|
405
|
+
if cfg!(target_os = "linux")
|
|
406
|
+
&& mold_available()
|
|
407
|
+
&& !merged_rustflags.contains("fuse-ld=mold")
|
|
408
|
+
{
|
|
409
|
+
merged_rustflags = format!("{} -C link-arg=-fuse-ld=mold", merged_rustflags.trim());
|
|
410
|
+
merged_rustflags = merged_rustflags.trim().to_string();
|
|
411
|
+
}
|
|
412
|
+
} else if merged_rustflags.is_empty() {
|
|
413
|
+
merged_rustflags = "-C target-cpu=native".to_string();
|
|
414
|
+
} else if !merged_rustflags.contains("target-cpu") {
|
|
415
|
+
merged_rustflags = format!("{} -C target-cpu=native", merged_rustflags);
|
|
416
|
+
}
|
|
417
|
+
|
|
387
418
|
let mut cmd = Command::new("cargo");
|
|
388
419
|
cmd.args(["build", "--release", "--target-dir"])
|
|
389
420
|
.arg(&target_dir)
|
|
@@ -394,6 +425,14 @@ pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<()
|
|
|
394
425
|
.env_remove("CARGO_BUILD_RUSTC_WRAPPER")
|
|
395
426
|
.env("CARGO_TERM_PROGRESS", "always")
|
|
396
427
|
.env("RUSTFLAGS", &merged_rustflags);
|
|
428
|
+
if let Some(triple) = cross_target {
|
|
429
|
+
cmd.arg("--target").arg(triple);
|
|
430
|
+
}
|
|
431
|
+
if fast_native {
|
|
432
|
+
cmd.env("CARGO_INCREMENTAL", "1");
|
|
433
|
+
} else {
|
|
434
|
+
cmd.env_remove("CARGO_INCREMENTAL");
|
|
435
|
+
}
|
|
397
436
|
if let Some(protoc) = protoc_for_nested_cargo() {
|
|
398
437
|
cmd.env("PROTOC", protoc);
|
|
399
438
|
}
|
|
@@ -416,17 +455,29 @@ pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<()
|
|
|
416
455
|
mod protoc_tests {
|
|
417
456
|
use super::*;
|
|
418
457
|
|
|
458
|
+
#[test]
|
|
459
|
+
fn cargo_target_name_replaces_hyphens() {
|
|
460
|
+
assert_eq!(cargo_target_name("hello-ios"), "hello_ios");
|
|
461
|
+
assert_eq!(cargo_target_name("tish_out"), "tish_out");
|
|
462
|
+
}
|
|
463
|
+
|
|
419
464
|
#[test]
|
|
420
465
|
fn protoc_for_nested_cargo_without_env_uses_vendored_or_path() {
|
|
421
466
|
let _lock = std::sync::Mutex::new(());
|
|
422
467
|
let _guard = _lock.lock().unwrap();
|
|
423
468
|
std::env::remove_var("PROTOC");
|
|
424
469
|
let p = protoc_for_nested_cargo().expect("expected vendored or PATH protoc");
|
|
425
|
-
assert!(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
470
|
+
assert!(p.exists(), "resolved protoc should exist: {}", p.display());
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/// Find the built static library in target/release (or target/$TRIPLE/release).
|
|
475
|
+
pub fn find_release_staticlib(binary_dir: &Path, lib_name: &str) -> Result<PathBuf, String> {
|
|
476
|
+
let path = binary_dir.join(format!("lib{lib_name}.a"));
|
|
477
|
+
if path.exists() {
|
|
478
|
+
Ok(path)
|
|
479
|
+
} else {
|
|
480
|
+
Err(format!("Static library not found at {}", path.display()))
|
|
430
481
|
}
|
|
431
482
|
}
|
|
432
483
|
|