@tishlang/tish 1.7.0 → 1.9.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/Cargo.toml +2 -0
- package/README.md +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +28 -8
- package/crates/js_to_tish/src/transform/stmt.rs +49 -22
- package/crates/tish/Cargo.toml +14 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +16 -10
- package/crates/tish/src/main.rs +87 -32
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +1 -1
- package/crates/tish/tests/integration_test.rs +19 -7
- package/crates/tish/tests/shortcircuit.rs +1 -1
- package/crates/tish_ast/src/ast.rs +80 -9
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +105 -2
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +13 -12
- package/crates/tish_builtins/src/construct.rs +34 -33
- package/crates/tish_builtins/src/globals.rs +12 -11
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +73 -3
- package/crates/tish_bytecode/src/compiler.rs +12 -14
- package/crates/tish_bytecode/src/opcode.rs +12 -3
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +745 -199
- package/crates/tish_compile/src/infer.rs +6 -0
- package/crates/tish_compile/src/lib.rs +4 -3
- package/crates/tish_compile/src/resolve.rs +180 -82
- package/crates/tish_compile/src/types.rs +175 -11
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +152 -29
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +31 -12
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +102 -53
- package/crates/tish_core/src/lib.rs +3 -1
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/value.rs +53 -15
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +90 -28
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +3 -3
- package/crates/tish_eval/src/natives.rs +41 -0
- package/crates/tish_eval/src/value.rs +7 -3
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +120 -30
- package/crates/tish_lexer/src/lib.rs +20 -5
- package/crates/tish_lexer/src/token.rs +4 -0
- package/crates/tish_llvm/src/lib.rs +3 -1
- package/crates/tish_lsp/Cargo.toml +4 -1
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_lsp/src/builtin_goto.rs +261 -0
- package/crates/tish_lsp/src/import_goto.rs +549 -0
- package/crates/tish_lsp/src/main.rs +502 -102
- package/crates/tish_native/src/build.rs +3 -2
- package/crates/tish_native/src/lib.rs +6 -2
- package/crates/tish_opt/src/lib.rs +17 -2
- package/crates/tish_parser/src/lib.rs +10 -3
- package/crates/tish_parser/src/parser.rs +346 -56
- package/crates/tish_pg/Cargo.toml +34 -0
- package/crates/tish_pg/README.md +38 -0
- package/crates/tish_pg/src/error.rs +52 -0
- package/crates/tish_pg/src/lib.rs +967 -0
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3436 -0
- package/crates/tish_resolve/src/pos.rs +133 -0
- package/crates/tish_runtime/Cargo.toml +68 -3
- package/crates/tish_runtime/src/http.rs +1123 -141
- package/crates/tish_runtime/src/http_fetch.rs +15 -14
- package/crates/tish_runtime/src/http_hyper.rs +418 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +159 -29
- package/crates/tish_runtime/src/promise.rs +199 -36
- package/crates/tish_runtime/src/promise_io.rs +2 -1
- package/crates/tish_runtime/src/timers.rs +37 -1
- package/crates/tish_runtime/src/ws.rs +26 -28
- package/crates/tish_ui/src/jsx.rs +279 -8
- package/crates/tish_ui/src/lib.rs +5 -2
- package/crates/tish_ui/src/runtime/hooks.rs +406 -45
- package/crates/tish_ui/src/runtime/mod.rs +36 -9
- package/crates/tish_vm/Cargo.toml +15 -5
- package/crates/tish_vm/src/vm.rs +506 -259
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +3 -1
- package/crates/tish_wasm/src/lib.rs +17 -14
- package/crates/tish_wasm_runtime/Cargo.toml +2 -1
- package/crates/tish_wasm_runtime/src/lib.rs +1 -1
- package/crates/tishlang_cargo_bindgen/Cargo.toml +1 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +68 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +5 -4
- package/justfile +8 -0
- 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/crates/tish/src/main.rs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
//! Tish CLI - run, REPL, build to native or other targets.
|
|
2
2
|
|
|
3
|
+
mod cargo_native_registry;
|
|
3
4
|
mod cli_help;
|
|
4
5
|
mod repl_completion;
|
|
5
6
|
|
|
6
7
|
use std::cell::RefCell;
|
|
8
|
+
use tishlang_core::VmRef;
|
|
7
9
|
use std::collections::HashSet;
|
|
8
10
|
use std::fs;
|
|
9
11
|
use std::io::{self, IsTerminal, Read, Write};
|
|
@@ -15,13 +17,13 @@ use rustyline::{Behavior, ColorMode, CompletionType, Config, Editor};
|
|
|
15
17
|
|
|
16
18
|
use cli_help::{Cli, Commands};
|
|
17
19
|
|
|
18
|
-
/// Normalize `--feature` / `--feature http,fs` / `--feature full` for VM runs and native builds.
|
|
20
|
+
/// Normalize `--feature` / `--feature http,timers,fs` / `--feature full` for VM runs and native builds.
|
|
19
21
|
fn normalize_capability_flags(features: &[String]) -> HashSet<String> {
|
|
20
22
|
let mut out = HashSet::new();
|
|
21
23
|
for s in features {
|
|
22
24
|
for part in s.split(',').map(str::trim).filter(|p| !p.is_empty()) {
|
|
23
25
|
if part == "full" {
|
|
24
|
-
for name in ["http", "fs", "process", "regex", "ws"] {
|
|
26
|
+
for name in ["http", "timers", "fs", "process", "regex", "ws"] {
|
|
25
27
|
out.insert(name.to_string());
|
|
26
28
|
}
|
|
27
29
|
} else {
|
|
@@ -110,6 +112,7 @@ fn main() {
|
|
|
110
112
|
&a.native_backend,
|
|
111
113
|
&a.features,
|
|
112
114
|
a.no_optimize || no_opt_env,
|
|
115
|
+
a.source_map,
|
|
113
116
|
),
|
|
114
117
|
Some(Commands::DumpAst { file }) => dump_ast(&file),
|
|
115
118
|
None => {
|
|
@@ -160,7 +163,7 @@ fn run_stdin_source(
|
|
|
160
163
|
let cwd = std::env::current_dir().map_err(|e| e.to_string())?;
|
|
161
164
|
let modules = tishlang_compile::resolve_project_from_stdin(source, &cwd)?;
|
|
162
165
|
tishlang_compile::detect_cycles(&modules)?;
|
|
163
|
-
let prog = tishlang_compile::merge_modules(modules)
|
|
166
|
+
let prog = tishlang_compile::merge_modules(modules)?.program;
|
|
164
167
|
let program = if no_optimize {
|
|
165
168
|
prog
|
|
166
169
|
} else {
|
|
@@ -202,7 +205,7 @@ fn run_file(
|
|
|
202
205
|
} else {
|
|
203
206
|
let modules = tishlang_compile::resolve_project(&path, project_root)?;
|
|
204
207
|
tishlang_compile::detect_cycles(&modules)?;
|
|
205
|
-
let prog = tishlang_compile::merge_modules(modules)
|
|
208
|
+
let prog = tishlang_compile::merge_modules(modules)?.program;
|
|
206
209
|
if no_optimize {
|
|
207
210
|
prog
|
|
208
211
|
} else {
|
|
@@ -223,6 +226,10 @@ fn run_program(
|
|
|
223
226
|
if backend == "interp" {
|
|
224
227
|
let mut eval = tishlang_eval::Evaluator::new();
|
|
225
228
|
let value = eval.eval_program(program)?;
|
|
229
|
+
#[cfg(feature = "timers")]
|
|
230
|
+
{
|
|
231
|
+
let _ = eval.run_timer_phase();
|
|
232
|
+
}
|
|
226
233
|
if !matches!(value, tishlang_eval::Value::Null) {
|
|
227
234
|
println!(
|
|
228
235
|
"{}",
|
|
@@ -241,13 +248,9 @@ fn run_program(
|
|
|
241
248
|
tishlang_bytecode::compile(program).map_err(|e| e.to_string())?
|
|
242
249
|
};
|
|
243
250
|
let caps = vm_capabilities_for_cli_run(features);
|
|
244
|
-
let
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
repl_mode: false,
|
|
248
|
-
capabilities: caps,
|
|
249
|
-
},
|
|
250
|
-
)?;
|
|
251
|
+
let mut vm = tishlang_vm::Vm::with_capabilities(caps);
|
|
252
|
+
cargo_native_registry::register_bytecode_native_modules(&mut vm);
|
|
253
|
+
let value = vm.run_with_options(&chunk, false)?;
|
|
251
254
|
if !matches!(value, tishlang_core::Value::Null) {
|
|
252
255
|
println!(
|
|
253
256
|
"{}",
|
|
@@ -294,6 +297,10 @@ fn run_repl(backend: &str, no_optimize: bool, features: &[String]) -> Result<(),
|
|
|
294
297
|
Ok(program) => {
|
|
295
298
|
match eval.eval_program(&program) {
|
|
296
299
|
Ok(v) => {
|
|
300
|
+
#[cfg(feature = "timers")]
|
|
301
|
+
{
|
|
302
|
+
let _ = eval.run_timer_phase();
|
|
303
|
+
}
|
|
297
304
|
if !matches!(v, tishlang_eval::Value::Null) {
|
|
298
305
|
println!(
|
|
299
306
|
"{}",
|
|
@@ -325,11 +332,11 @@ fn run_repl(backend: &str, no_optimize: bool, features: &[String]) -> Result<(),
|
|
|
325
332
|
if !std::io::stdin().is_terminal() {
|
|
326
333
|
eprintln!("Note: Tab completion and grey preview require an interactive terminal (TTY).");
|
|
327
334
|
}
|
|
328
|
-
let
|
|
329
|
-
|
|
330
|
-
)
|
|
335
|
+
let mut vm0 = tishlang_vm::Vm::with_capabilities(vm_capabilities_for_cli_run(features));
|
|
336
|
+
cargo_native_registry::register_bytecode_native_modules(&mut vm0);
|
|
337
|
+
let vm = VmRef::new(vm0);
|
|
331
338
|
let completer = repl_completion::ReplCompleter {
|
|
332
|
-
vm:
|
|
339
|
+
vm: vm.clone(),
|
|
333
340
|
no_optimize,
|
|
334
341
|
};
|
|
335
342
|
let config = Config::builder()
|
|
@@ -455,7 +462,27 @@ fn tish_history_path() -> Option<PathBuf> {
|
|
|
455
462
|
home.map(|h| PathBuf::from(h).join(".tish_history"))
|
|
456
463
|
}
|
|
457
464
|
|
|
458
|
-
fn compile_to_js(
|
|
465
|
+
fn compile_to_js(
|
|
466
|
+
input_path: &Path,
|
|
467
|
+
output_path: &str,
|
|
468
|
+
optimize: bool,
|
|
469
|
+
source_map: bool,
|
|
470
|
+
) -> Result<(), String> {
|
|
471
|
+
if source_map && optimize {
|
|
472
|
+
return Err(
|
|
473
|
+
"tish build --target js --source-map requires --no-optimize (mappings follow unmerged statement order)."
|
|
474
|
+
.into(),
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
if source_map
|
|
478
|
+
&& (input_path.extension().map(|e| e == "jsx") == Some(true)
|
|
479
|
+
|| input_path.extension().map(|e| e == "js") == Some(true))
|
|
480
|
+
{
|
|
481
|
+
return Err(
|
|
482
|
+
"tish build --target js --source-map is only supported for .tish project builds (not single-file .jsx / .js inputs)."
|
|
483
|
+
.into(),
|
|
484
|
+
);
|
|
485
|
+
}
|
|
459
486
|
let project_root = input_path.parent().and_then(|p| {
|
|
460
487
|
if p.file_name().and_then(|n| n.to_str()) == Some("src") {
|
|
461
488
|
p.parent()
|
|
@@ -463,7 +490,20 @@ fn compile_to_js(input_path: &Path, output_path: &str, optimize: bool) -> Result
|
|
|
463
490
|
Some(p)
|
|
464
491
|
}
|
|
465
492
|
});
|
|
466
|
-
let
|
|
493
|
+
let out_path = Path::new(output_path);
|
|
494
|
+
let out_path = if out_path.extension().is_none()
|
|
495
|
+
|| out_path.extension() == Some(std::ffi::OsStr::new(""))
|
|
496
|
+
{
|
|
497
|
+
out_path.with_extension("js")
|
|
498
|
+
} else {
|
|
499
|
+
out_path.to_path_buf()
|
|
500
|
+
};
|
|
501
|
+
let out_js_name = out_path
|
|
502
|
+
.file_name()
|
|
503
|
+
.and_then(|s| s.to_str())
|
|
504
|
+
.unwrap_or("out.js");
|
|
505
|
+
|
|
506
|
+
let (js, map_json) = if input_path.extension().map(|e| e == "jsx") == Some(true) {
|
|
467
507
|
let source = fs::read_to_string(input_path).map_err(|e| format!("{}", e))?;
|
|
468
508
|
let wrapped = format!(
|
|
469
509
|
"export fn __TishJsxRoot() {{\n return (\n{}\n )\n}}",
|
|
@@ -476,30 +516,44 @@ fn compile_to_js(input_path: &Path, output_path: &str, optimize: bool) -> Result
|
|
|
476
516
|
} else {
|
|
477
517
|
program
|
|
478
518
|
};
|
|
479
|
-
tishlang_compile_js::compile_with_jsx(&p, optimize).map_err(|e| format!("{}", e))
|
|
519
|
+
let js = tishlang_compile_js::compile_with_jsx(&p, optimize).map_err(|e| format!("{}", e))?;
|
|
520
|
+
(js, None)
|
|
480
521
|
} else if input_path.extension().map(|e| e == "js") == Some(true) {
|
|
481
522
|
let source = fs::read_to_string(input_path).map_err(|e| format!("{}", e))?;
|
|
482
523
|
let program = tishlang_js_to_tish::convert(&source).map_err(|e| format!("{}", e))?;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
524
|
+
let js =
|
|
525
|
+
tishlang_compile_js::compile_with_jsx(&program, optimize).map_err(|e| format!("{}", e))?;
|
|
526
|
+
(js, None)
|
|
527
|
+
} else if source_map {
|
|
528
|
+
let bundle = tishlang_compile_js::compile_project_with_jsx_and_source_map(
|
|
529
|
+
input_path,
|
|
530
|
+
project_root,
|
|
531
|
+
out_js_name,
|
|
532
|
+
)
|
|
533
|
+
.map_err(|e| format!("{}", e))?;
|
|
534
|
+
(bundle.js, bundle.source_map_json)
|
|
494
535
|
} else {
|
|
495
|
-
|
|
536
|
+
let js = tishlang_compile_js::compile_project_with_jsx(input_path, project_root, optimize)
|
|
537
|
+
.map_err(|e| format!("{}", e))?;
|
|
538
|
+
(js, None)
|
|
496
539
|
};
|
|
497
540
|
|
|
498
541
|
if let Some(parent) = out_path.parent() {
|
|
499
542
|
fs::create_dir_all(parent)
|
|
500
543
|
.map_err(|e| format!("Cannot create output directory {}: {}", parent.display(), e))?;
|
|
501
544
|
}
|
|
502
|
-
|
|
545
|
+
let mut js_out = js;
|
|
546
|
+
if let Some(map) = &map_json {
|
|
547
|
+
let map_path = out_path.with_extension("js.map");
|
|
548
|
+
fs::write(&map_path, map).map_err(|e| format!("Cannot write {}: {}", map_path.display(), e))?;
|
|
549
|
+
let map_url = map_path
|
|
550
|
+
.file_name()
|
|
551
|
+
.and_then(|s| s.to_str())
|
|
552
|
+
.unwrap_or("out.js.map");
|
|
553
|
+
js_out.push_str(&format!("\n//# sourceMappingURL={map_url}\n"));
|
|
554
|
+
println!("Built: {}", map_path.display());
|
|
555
|
+
}
|
|
556
|
+
fs::write(&out_path, js_out).map_err(|e| format!("Cannot write {}: {}", out_path.display(), e))?;
|
|
503
557
|
println!("Built: {}", out_path.display());
|
|
504
558
|
Ok(())
|
|
505
559
|
}
|
|
@@ -512,6 +566,7 @@ fn build_file(
|
|
|
512
566
|
native_backend: &str,
|
|
513
567
|
cli_features: &[String],
|
|
514
568
|
no_optimize: bool,
|
|
569
|
+
source_map: bool,
|
|
515
570
|
) -> Result<(), String> {
|
|
516
571
|
let optimize = !no_optimize;
|
|
517
572
|
let input_path = Path::new(input_path)
|
|
@@ -521,7 +576,7 @@ fn build_file(
|
|
|
521
576
|
let is_js = input_path.extension().map(|e| e == "js") == Some(true);
|
|
522
577
|
|
|
523
578
|
if target == "js" {
|
|
524
|
-
return compile_to_js(&input_path, output_path, optimize);
|
|
579
|
+
return compile_to_js(&input_path, output_path, optimize, source_map);
|
|
525
580
|
}
|
|
526
581
|
|
|
527
582
|
if target == "wasm" && is_js {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
//! Grey preview hint below the line (like Node) and Tab for full list.
|
|
3
3
|
|
|
4
4
|
use std::borrow::Cow;
|
|
5
|
-
|
|
6
|
-
use
|
|
5
|
+
|
|
6
|
+
use tishlang_core::VmRef;
|
|
7
7
|
|
|
8
8
|
use rustyline::completion::{Completer, Pair};
|
|
9
9
|
use rustyline::highlight::Highlighter;
|
|
@@ -29,7 +29,7 @@ const ANSI_RESET: &str = "\x1b[0m";
|
|
|
29
29
|
|
|
30
30
|
/// Tab completer that evaluates the expression before the last `.` and suggests property/method names.
|
|
31
31
|
pub struct ReplCompleter {
|
|
32
|
-
pub vm:
|
|
32
|
+
pub vm: VmRef<Vm>,
|
|
33
33
|
pub no_optimize: bool,
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -66,12 +66,20 @@ fn file_content_hash(path: &Path) -> u64 {
|
|
|
66
66
|
///
|
|
67
67
|
/// Cache is keyed by backend (native, cranelift, js, wasi) so e.g. cranelift and wasi
|
|
68
68
|
/// compiles of the same file do not overwrite each other: .../cranelift/<stem>_<hash> vs .../wasi/<stem>_<hash>.wasm.
|
|
69
|
+
///
|
|
70
|
+
/// The artifact **basename** must be unique per `(stem, hash, backend)`: nested `tish build`
|
|
71
|
+
/// uses it as the Cargo binary name under the workspace `target/release/`. If native and
|
|
72
|
+
/// cranelift both used `strict_equality_<hash>`, parallel `cargo nextest` could run those
|
|
73
|
+
/// tests concurrently and corrupt the same `target/release/...` output (Linux: ETXTBSY when
|
|
74
|
+
/// executing a binary still being written).
|
|
69
75
|
fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
70
76
|
let stem = path.file_stem().unwrap().to_string_lossy();
|
|
71
77
|
let hash = file_content_hash(path);
|
|
72
78
|
let hash8 = &format!("{:016x}", hash)[..8];
|
|
73
79
|
let cache_base = integration_compile_cache_dir().join(backend);
|
|
74
80
|
let _ = std::fs::create_dir_all(&cache_base);
|
|
81
|
+
// Include `backend` in the leaf name so nested cargo bin names never collide across backends.
|
|
82
|
+
let leaf = format!("{}__{}__{}", stem, backend, hash8);
|
|
75
83
|
|
|
76
84
|
let (artifact_path, compile_args): (PathBuf, Vec<OsString>) = match backend {
|
|
77
85
|
"native" => {
|
|
@@ -80,7 +88,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
80
88
|
} else {
|
|
81
89
|
""
|
|
82
90
|
};
|
|
83
|
-
let cached = cache_base.join(format!("{}
|
|
91
|
+
let cached = cache_base.join(format!("{}{}", leaf, ext));
|
|
84
92
|
let args = vec![
|
|
85
93
|
OsString::from("build"),
|
|
86
94
|
OsString::from(path),
|
|
@@ -95,7 +103,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
95
103
|
} else {
|
|
96
104
|
""
|
|
97
105
|
};
|
|
98
|
-
let cached = cache_base.join(format!("{}
|
|
106
|
+
let cached = cache_base.join(format!("{}{}", leaf, ext));
|
|
99
107
|
let args = vec![
|
|
100
108
|
OsString::from("build"),
|
|
101
109
|
OsString::from(path),
|
|
@@ -107,7 +115,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
107
115
|
(cached, args)
|
|
108
116
|
}
|
|
109
117
|
"js" => {
|
|
110
|
-
let cached = cache_base.join(format!("{}
|
|
118
|
+
let cached = cache_base.join(format!("{}.js", leaf));
|
|
111
119
|
let args = vec![
|
|
112
120
|
OsString::from("build"),
|
|
113
121
|
OsString::from(path),
|
|
@@ -119,7 +127,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
119
127
|
(cached, args)
|
|
120
128
|
}
|
|
121
129
|
"wasi" => {
|
|
122
|
-
let out_base = cache_base.join(
|
|
130
|
+
let out_base = cache_base.join(&leaf);
|
|
123
131
|
let artifact = out_base.with_extension("wasm");
|
|
124
132
|
let args = vec![
|
|
125
133
|
OsString::from("build"),
|
|
@@ -397,7 +405,7 @@ fn test_async_await_run() {
|
|
|
397
405
|
}
|
|
398
406
|
}
|
|
399
407
|
|
|
400
|
-
/// Run Promise and setTimeout module tests (
|
|
408
|
+
/// Run Promise and setTimeout module tests (`promise` needs `http`; `settimeout` needs `timers`, which `http` enables).
|
|
401
409
|
/// Ignored: tishlang_eval::run() does not run the event loop.
|
|
402
410
|
#[test]
|
|
403
411
|
#[cfg(feature = "http")]
|
|
@@ -455,7 +463,9 @@ fn test_vm_date_now() {
|
|
|
455
463
|
// Library path
|
|
456
464
|
let modules = tishlang_compile::resolve_project(&path, path.parent()).expect("resolve");
|
|
457
465
|
tishlang_compile::detect_cycles(&modules).expect("cycles");
|
|
458
|
-
let program = tishlang_compile::merge_modules(modules)
|
|
466
|
+
let program = tishlang_compile::merge_modules(modules)
|
|
467
|
+
.expect("merge")
|
|
468
|
+
.program;
|
|
459
469
|
let chunk = tishlang_bytecode::compile(&program).expect("compile");
|
|
460
470
|
let result = tishlang_vm::run(&chunk);
|
|
461
471
|
assert!(
|
|
@@ -499,7 +509,9 @@ fn test_vm_index_assign_via_resolve() {
|
|
|
499
509
|
.join("array_sort_minimal.tish");
|
|
500
510
|
let modules = tishlang_compile::resolve_project(&path, path.parent()).expect("resolve");
|
|
501
511
|
tishlang_compile::detect_cycles(&modules).expect("cycles");
|
|
502
|
-
let program = tishlang_compile::merge_modules(modules)
|
|
512
|
+
let program = tishlang_compile::merge_modules(modules)
|
|
513
|
+
.expect("merge")
|
|
514
|
+
.program;
|
|
503
515
|
let chunk = tishlang_bytecode::compile(&program).expect("compile");
|
|
504
516
|
let result = tishlang_vm::run(&chunk);
|
|
505
517
|
assert!(
|
|
@@ -53,7 +53,7 @@ fn test_and_shortcircuit_via_resolve_project() {
|
|
|
53
53
|
let path = path.canonicalize().expect("path");
|
|
54
54
|
let project_root = path.parent().unwrap();
|
|
55
55
|
let modules = resolve_project(&path, Some(project_root)).expect("resolve");
|
|
56
|
-
let program = merge_modules(modules).expect("merge");
|
|
56
|
+
let program = merge_modules(modules).expect("merge").program;
|
|
57
57
|
let program = tishlang_opt::optimize(&program); // Mirror CLI
|
|
58
58
|
let chunk = compile(&program).expect("compile");
|
|
59
59
|
let result = tishlang_vm::run(&chunk);
|
|
@@ -30,6 +30,7 @@ pub enum TypeAnnotation {
|
|
|
30
30
|
#[derive(Debug, Clone, PartialEq)]
|
|
31
31
|
pub struct TypedParam {
|
|
32
32
|
pub name: Arc<str>,
|
|
33
|
+
pub name_span: Span,
|
|
33
34
|
pub type_ann: Option<TypeAnnotation>,
|
|
34
35
|
pub default: Option<Expr>,
|
|
35
36
|
}
|
|
@@ -64,11 +65,11 @@ impl FunParam {
|
|
|
64
65
|
for el in elements {
|
|
65
66
|
if let Some(el) = el {
|
|
66
67
|
match el {
|
|
67
|
-
DestructElement::Ident(n) => out.push(Arc::clone(n)),
|
|
68
|
+
DestructElement::Ident(n, _) => out.push(Arc::clone(n)),
|
|
68
69
|
DestructElement::Pattern(p) => {
|
|
69
70
|
Self::collect_pattern_binding_names(p, out);
|
|
70
71
|
}
|
|
71
|
-
DestructElement::Rest(n) => out.push(Arc::clone(n)),
|
|
72
|
+
DestructElement::Rest(n, _) => out.push(Arc::clone(n)),
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
}
|
|
@@ -76,11 +77,11 @@ impl FunParam {
|
|
|
76
77
|
DestructPattern::Object(props) => {
|
|
77
78
|
for prop in props {
|
|
78
79
|
match &prop.value {
|
|
79
|
-
DestructElement::Ident(n) => out.push(Arc::clone(n)),
|
|
80
|
+
DestructElement::Ident(n, _) => out.push(Arc::clone(n)),
|
|
80
81
|
DestructElement::Pattern(p) => {
|
|
81
82
|
Self::collect_pattern_binding_names(p, out);
|
|
82
83
|
}
|
|
83
|
-
DestructElement::Rest(n) => out.push(Arc::clone(n)),
|
|
84
|
+
DestructElement::Rest(n, _) => out.push(Arc::clone(n)),
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
}
|
|
@@ -101,11 +102,11 @@ pub enum DestructPattern {
|
|
|
101
102
|
#[derive(Debug, Clone, PartialEq)]
|
|
102
103
|
pub enum DestructElement {
|
|
103
104
|
/// Simple binding: a
|
|
104
|
-
Ident(Arc<str
|
|
105
|
+
Ident(Arc<str>, Span),
|
|
105
106
|
/// Nested pattern: [a, b] or { x, y }
|
|
106
107
|
Pattern(Box<DestructPattern>),
|
|
107
108
|
/// Rest element: ...rest
|
|
108
|
-
Rest(Arc<str
|
|
109
|
+
Rest(Arc<str>, Span),
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
/// Property in object destructuring pattern
|
|
@@ -123,12 +124,20 @@ pub enum ImportSpecifier {
|
|
|
123
124
|
/// Named: { foo } or { foo as bar }
|
|
124
125
|
Named {
|
|
125
126
|
name: Arc<str>,
|
|
127
|
+
name_span: Span,
|
|
126
128
|
alias: Option<Arc<str>>,
|
|
129
|
+
alias_span: Option<Span>,
|
|
127
130
|
},
|
|
128
131
|
/// Namespace: * as M
|
|
129
|
-
Namespace
|
|
132
|
+
Namespace {
|
|
133
|
+
name: Arc<str>,
|
|
134
|
+
name_span: Span,
|
|
135
|
+
},
|
|
130
136
|
/// Default: import X from "..."
|
|
131
|
-
Default
|
|
137
|
+
Default {
|
|
138
|
+
name: Arc<str>,
|
|
139
|
+
name_span: Span,
|
|
140
|
+
},
|
|
132
141
|
}
|
|
133
142
|
|
|
134
143
|
/// Export declaration: named (const/let/fn) or default
|
|
@@ -153,6 +162,7 @@ pub enum Statement {
|
|
|
153
162
|
},
|
|
154
163
|
VarDecl {
|
|
155
164
|
name: Arc<str>,
|
|
165
|
+
name_span: Span,
|
|
156
166
|
mutable: bool, // true for `let`, false for `const`
|
|
157
167
|
type_ann: Option<TypeAnnotation>,
|
|
158
168
|
init: Option<Expr>,
|
|
@@ -189,6 +199,7 @@ pub enum Statement {
|
|
|
189
199
|
},
|
|
190
200
|
ForOf {
|
|
191
201
|
name: Arc<str>,
|
|
202
|
+
name_span: Span,
|
|
192
203
|
iterable: Expr,
|
|
193
204
|
body: Box<Statement>,
|
|
194
205
|
span: Span,
|
|
@@ -206,6 +217,7 @@ pub enum Statement {
|
|
|
206
217
|
FunDecl {
|
|
207
218
|
async_: bool,
|
|
208
219
|
name: Arc<str>,
|
|
220
|
+
name_span: Span,
|
|
209
221
|
params: Vec<FunParam>,
|
|
210
222
|
rest_param: Option<TypedParam>,
|
|
211
223
|
return_type: Option<TypeAnnotation>,
|
|
@@ -230,6 +242,7 @@ pub enum Statement {
|
|
|
230
242
|
Try {
|
|
231
243
|
body: Box<Statement>,
|
|
232
244
|
catch_param: Option<Arc<str>>,
|
|
245
|
+
catch_param_span: Option<Span>,
|
|
233
246
|
catch_body: Option<Box<Statement>>,
|
|
234
247
|
finally_body: Option<Box<Statement>>,
|
|
235
248
|
span: Span,
|
|
@@ -243,6 +256,31 @@ pub enum Statement {
|
|
|
243
256
|
declaration: Box<ExportDeclaration>,
|
|
244
257
|
span: Span,
|
|
245
258
|
},
|
|
259
|
+
/// `type Name = Type` (erased at runtime; for checker / declaration files).
|
|
260
|
+
TypeAlias {
|
|
261
|
+
name: Arc<str>,
|
|
262
|
+
name_span: Span,
|
|
263
|
+
ty: TypeAnnotation,
|
|
264
|
+
span: Span,
|
|
265
|
+
},
|
|
266
|
+
/// `declare let name: T` or `declare const name: T`
|
|
267
|
+
DeclareVar {
|
|
268
|
+
name: Arc<str>,
|
|
269
|
+
name_span: Span,
|
|
270
|
+
type_ann: Option<TypeAnnotation>,
|
|
271
|
+
const_: bool,
|
|
272
|
+
span: Span,
|
|
273
|
+
},
|
|
274
|
+
/// `declare [async] function name(...): R` (no body).
|
|
275
|
+
DeclareFun {
|
|
276
|
+
async_: bool,
|
|
277
|
+
name: Arc<str>,
|
|
278
|
+
name_span: Span,
|
|
279
|
+
params: Vec<FunParam>,
|
|
280
|
+
rest_param: Option<TypedParam>,
|
|
281
|
+
return_type: Option<TypeAnnotation>,
|
|
282
|
+
span: Span,
|
|
283
|
+
},
|
|
246
284
|
}
|
|
247
285
|
|
|
248
286
|
#[derive(Debug, Clone, PartialEq)]
|
|
@@ -550,6 +588,39 @@ pub enum UnaryOp {
|
|
|
550
588
|
|
|
551
589
|
#[derive(Debug, Clone, PartialEq)]
|
|
552
590
|
pub enum MemberProp {
|
|
553
|
-
|
|
591
|
+
/// Property name in `obj.prop` / `obj?.prop` (span covers **prop** only).
|
|
592
|
+
Name {
|
|
593
|
+
name: Arc<str>,
|
|
594
|
+
span: Span,
|
|
595
|
+
},
|
|
554
596
|
Expr(Box<Expr>), // for computed property
|
|
555
597
|
}
|
|
598
|
+
|
|
599
|
+
impl Statement {
|
|
600
|
+
/// Source span covering this statement (including nested bodies where applicable).
|
|
601
|
+
pub fn span(&self) -> Span {
|
|
602
|
+
match self {
|
|
603
|
+
Statement::Block { span, .. }
|
|
604
|
+
| Statement::VarDecl { span, .. }
|
|
605
|
+
| Statement::VarDeclDestructure { span, .. }
|
|
606
|
+
| Statement::ExprStmt { span, .. }
|
|
607
|
+
| Statement::If { span, .. }
|
|
608
|
+
| Statement::While { span, .. }
|
|
609
|
+
| Statement::For { span, .. }
|
|
610
|
+
| Statement::ForOf { span, .. }
|
|
611
|
+
| Statement::Return { span, .. }
|
|
612
|
+
| Statement::Break { span, .. }
|
|
613
|
+
| Statement::Continue { span, .. }
|
|
614
|
+
| Statement::FunDecl { span, .. }
|
|
615
|
+
| Statement::Switch { span, .. }
|
|
616
|
+
| Statement::DoWhile { span, .. }
|
|
617
|
+
| Statement::Throw { span, .. }
|
|
618
|
+
| Statement::Try { span, .. }
|
|
619
|
+
| Statement::Import { span, .. }
|
|
620
|
+
| Statement::Export { span, .. }
|
|
621
|
+
| Statement::TypeAlias { span, .. }
|
|
622
|
+
| Statement::DeclareVar { span, .. }
|
|
623
|
+
| Statement::DeclareFun { span, .. } => *span,
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
@@ -5,3 +5,7 @@ edition = "2021"
|
|
|
5
5
|
description = "Shared build utilities for Tish (workspace discovery, path resolution)"
|
|
6
6
|
license-file = { workspace = true }
|
|
7
7
|
repository = { workspace = true }
|
|
8
|
+
|
|
9
|
+
[dependencies]
|
|
10
|
+
# Bundled `protoc` for nested `cargo build` (Lance / prost-build) when none is on PATH.
|
|
11
|
+
protoc-bin-vendored = "3"
|
|
@@ -137,6 +137,39 @@ fn tish_root_from_project_cargo_files(mut start: PathBuf) -> Option<PathBuf> {
|
|
|
137
137
|
/// Returns the directory containing the workspace Cargo.toml (with [workspace]).
|
|
138
138
|
/// Used when building native binaries, WASM, or locating runtime crates.
|
|
139
139
|
pub fn find_workspace_root() -> Result<PathBuf, String> {
|
|
140
|
+
// Strategy 0: explicit checkout (e.g. tish-hub `npm run dev` / native build from a JS-only tree)
|
|
141
|
+
if let Ok(root) = std::env::var("TISHLANG_WORKSPACE") {
|
|
142
|
+
let trimmed = root.trim();
|
|
143
|
+
if !trimmed.is_empty() {
|
|
144
|
+
let p = PathBuf::from(trimmed);
|
|
145
|
+
if p.is_dir() && is_tish_workspace_root(&p) {
|
|
146
|
+
return p.canonicalize().map_err(|e| {
|
|
147
|
+
format!(
|
|
148
|
+
"TISHLANG_WORKSPACE is set but not a usable directory: {}",
|
|
149
|
+
e
|
|
150
|
+
)
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// Non-empty but invalid: fall through (sibling `tish/` discovery may still apply).
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Strategy 0b: monorepo layout `…/<parent>/tish-hub` (cwd) next to `…/<parent>/tish` (language repo).
|
|
158
|
+
// Works without `TISHLANG_WORKSPACE` so older `tish` binaries still find the compiler tree.
|
|
159
|
+
if let Ok(mut dir) = std::env::current_dir() {
|
|
160
|
+
for _ in 0..24 {
|
|
161
|
+
let candidate = dir.join("tish");
|
|
162
|
+
if is_tish_workspace_root(&candidate) {
|
|
163
|
+
return candidate.canonicalize().map_err(|e| {
|
|
164
|
+
format!("Cannot canonicalize Tish workspace {}: {}", candidate.display(), e)
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if !dir.pop() {
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
140
173
|
// Strategy 1: CARGO_MANIFEST_DIR (works during cargo build/run from workspace)
|
|
141
174
|
if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
|
|
142
175
|
let path = PathBuf::from(&manifest_dir);
|
|
@@ -301,18 +334,70 @@ pub fn create_build_dir(prefix: &str, out_name: &str) -> Result<PathBuf, String>
|
|
|
301
334
|
Ok(build_dir)
|
|
302
335
|
}
|
|
303
336
|
|
|
337
|
+
/// `PROTOC` for prost-build in transitive crates (e.g. lance-encoding) during nested `cargo build`.
|
|
338
|
+
/// Respects an existing `PROTOC`, then `protoc` on `PATH`, then `protoc-bin-vendored`.
|
|
339
|
+
fn protoc_for_nested_cargo() -> Option<PathBuf> {
|
|
340
|
+
// Empty or non-file PROTOC must not short-circuit: prost-build treats "" like a missing binary.
|
|
341
|
+
if let Some(v) = std::env::var_os("PROTOC") {
|
|
342
|
+
if !v.is_empty() {
|
|
343
|
+
let p = PathBuf::from(&v);
|
|
344
|
+
if p.is_file() {
|
|
345
|
+
return None;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Prefer vendored protoc over PATH: a broken or non-executable `protoc` on PATH still passes `is_file()`.
|
|
350
|
+
if let Ok(p) = protoc_bin_vendored::protoc_bin_path() {
|
|
351
|
+
return Some(p);
|
|
352
|
+
}
|
|
353
|
+
let ext = if cfg!(windows) { ".exe" } else { "" };
|
|
354
|
+
let name = format!("protoc{}", ext);
|
|
355
|
+
if let Some(paths) = std::env::var_os("PATH") {
|
|
356
|
+
for dir in std::env::split_paths(&paths) {
|
|
357
|
+
let candidate = dir.join(&name);
|
|
358
|
+
if candidate.is_file() {
|
|
359
|
+
return Some(candidate);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
None
|
|
364
|
+
}
|
|
365
|
+
|
|
304
366
|
/// Run cargo build in the given directory.
|
|
305
367
|
/// If target_dir is Some, use that for --target-dir (e.g. workspace target for caching).
|
|
306
368
|
pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<(), String> {
|
|
307
369
|
let target_dir = target_dir
|
|
308
370
|
.map(|p| p.to_path_buf())
|
|
309
371
|
.unwrap_or_else(|| build_dir.join("target"));
|
|
310
|
-
|
|
311
|
-
|
|
372
|
+
// Default to target-cpu=native so the emitted binary uses every SIMD / ISA
|
|
373
|
+
// extension the build host supports. Callers can override by pre-setting
|
|
374
|
+
// RUSTFLAGS in the environment.
|
|
375
|
+
let existing_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
|
|
376
|
+
let merged_rustflags = if existing_rustflags.is_empty() {
|
|
377
|
+
"-C target-cpu=native".to_string()
|
|
378
|
+
} else if existing_rustflags.contains("target-cpu") {
|
|
379
|
+
existing_rustflags
|
|
380
|
+
} else {
|
|
381
|
+
format!("{} -C target-cpu=native", existing_rustflags)
|
|
382
|
+
};
|
|
383
|
+
// Nested `cargo build` (e.g. `tish build --native-backend rust`) inherits the parent
|
|
384
|
+
// environment. CI often sets `RUSTC_WRAPPER=sccache`; wrapping this inner compile too can
|
|
385
|
+
// cause flaky or failed builds (LTO / temp-crate paths). Use plain rustc here; the main
|
|
386
|
+
// workspace build still benefits from the wrapper.
|
|
387
|
+
let mut cmd = Command::new("cargo");
|
|
388
|
+
cmd.args(["build", "--release", "--target-dir"])
|
|
312
389
|
.arg(&target_dir)
|
|
313
390
|
.current_dir(build_dir)
|
|
314
391
|
.env_remove("CARGO_TARGET_DIR")
|
|
392
|
+
.env_remove("RUSTC_WRAPPER")
|
|
393
|
+
.env_remove("RUSTC_WORKSPACE_WRAPPER")
|
|
394
|
+
.env_remove("CARGO_BUILD_RUSTC_WRAPPER")
|
|
315
395
|
.env("CARGO_TERM_PROGRESS", "always")
|
|
396
|
+
.env("RUSTFLAGS", &merged_rustflags);
|
|
397
|
+
if let Some(protoc) = protoc_for_nested_cargo() {
|
|
398
|
+
cmd.env("PROTOC", protoc);
|
|
399
|
+
}
|
|
400
|
+
let output = cmd
|
|
316
401
|
.output()
|
|
317
402
|
.map_err(|e| format!("Failed to run cargo: {}", e))?;
|
|
318
403
|
|
|
@@ -327,6 +412,24 @@ pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<()
|
|
|
327
412
|
Ok(())
|
|
328
413
|
}
|
|
329
414
|
|
|
415
|
+
#[cfg(test)]
|
|
416
|
+
mod protoc_tests {
|
|
417
|
+
use super::*;
|
|
418
|
+
|
|
419
|
+
#[test]
|
|
420
|
+
fn protoc_for_nested_cargo_without_env_uses_vendored_or_path() {
|
|
421
|
+
let _lock = std::sync::Mutex::new(());
|
|
422
|
+
let _guard = _lock.lock().unwrap();
|
|
423
|
+
std::env::remove_var("PROTOC");
|
|
424
|
+
let p = protoc_for_nested_cargo().expect("expected vendored or PATH protoc");
|
|
425
|
+
assert!(
|
|
426
|
+
p.exists(),
|
|
427
|
+
"resolved protoc should exist: {}",
|
|
428
|
+
p.display()
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
330
433
|
/// Find the built binary in target/release.
|
|
331
434
|
pub fn find_release_binary(binary_dir: &Path, bin_name: &str) -> Result<PathBuf, String> {
|
|
332
435
|
let binary_no_ext = binary_dir.join(bin_name);
|