@tishlang/tish 1.13.1 → 2.0.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/bin/tish +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +1 -0
- package/crates/tish/Cargo.toml +11 -3
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cli_help.rs +15 -4
- package/crates/tish/src/main.rs +93 -21
- package/crates/tish/src/repl_completion.rs +0 -1
- package/crates/tish/tests/error_source_location.rs +36 -0
- package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
- package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
- package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
- package/crates/tish/tests/integration_test.rs +402 -91
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/src/ast.rs +37 -8
- package/crates/tish_builtins/Cargo.toml +2 -0
- package/crates/tish_builtins/src/array.rs +375 -13
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +59 -19
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +86 -6
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +5 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +2 -2
- package/crates/tish_builtins/src/string.rs +19 -20
- package/crates/tish_builtins/src/symbol.rs +1 -1
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/src/chunk.rs +69 -1
- package/crates/tish_bytecode/src/compiler.rs +933 -89
- package/crates/tish_bytecode/src/encoding.rs +2 -0
- package/crates/tish_bytecode/src/lib.rs +2 -1
- package/crates/tish_bytecode/src/opcode.rs +47 -4
- package/crates/tish_bytecode/src/serialize.rs +31 -1
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +2334 -349
- package/crates/tish_compile/src/infer.rs +1395 -6
- package/crates/tish_compile/src/lib.rs +50 -8
- package/crates/tish_compile/src/resolve.rs +584 -21
- package/crates/tish_compile/src/types.rs +106 -2
- package/crates/tish_compile_js/src/codegen.rs +67 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +64 -0
- package/crates/tish_core/Cargo.toml +7 -1
- package/crates/tish_core/src/console_style.rs +11 -1
- package/crates/tish_core/src/json.rs +81 -38
- package/crates/tish_core/src/lib.rs +3 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/value.rs +679 -25
- package/crates/tish_core/src/vmref.rs +13 -8
- package/crates/tish_cranelift/src/link.rs +17 -4
- package/crates/tish_cranelift_runtime/Cargo.toml +1 -0
- package/crates/tish_eval/Cargo.toml +6 -0
- package/crates/tish_eval/src/eval.rs +665 -117
- package/crates/tish_eval/src/http.rs +4 -1
- package/crates/tish_eval/src/natives.rs +165 -13
- package/crates/tish_eval/src/value.rs +31 -13
- package/crates/tish_eval/src/value_convert.rs +10 -4
- package/crates/tish_ffi/Cargo.toml +26 -0
- package/crates/tish_ffi/src/lib.rs +518 -0
- package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
- package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
- package/crates/tish_ffi/tests/loader.rs +65 -0
- package/crates/tish_fmt/src/lib.rs +43 -5
- package/crates/tish_lexer/src/lib.rs +397 -9
- package/crates/tish_lexer/src/token.rs +7 -0
- package/crates/tish_lint/src/lib.rs +2 -10
- package/crates/tish_lsp/src/import_goto.rs +2 -0
- package/crates/tish_lsp/src/main.rs +439 -26
- package/crates/tish_native/src/build.rs +55 -1
- package/crates/tish_opt/src/lib.rs +126 -23
- package/crates/tish_parser/src/lib.rs +55 -1
- package/crates/tish_parser/src/parser.rs +456 -34
- package/crates/tish_pg/src/lib.rs +3 -3
- package/crates/tish_resolve/src/lib.rs +99 -59
- package/crates/tish_runtime/Cargo.toml +4 -0
- package/crates/tish_runtime/src/http.rs +66 -17
- package/crates/tish_runtime/src/http_fetch.rs +29 -8
- package/crates/tish_runtime/src/http_hyper.rs +25 -2
- package/crates/tish_runtime/src/lib.rs +299 -44
- package/crates/tish_runtime/src/promise.rs +328 -18
- package/crates/tish_runtime/src/timers.rs +13 -7
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +35 -18
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +2 -2
- package/crates/tish_ui/src/jsx.rs +10 -0
- package/crates/tish_ui/src/runtime/hooks.rs +19 -15
- package/crates/tish_ui/src/runtime/mod.rs +15 -12
- package/crates/tish_vm/Cargo.toml +14 -1
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +2 -0
- package/crates/tish_vm/src/vm.rs +1546 -202
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
- package/crates/tish_wasm/src/lib.rs +6 -2
- package/crates/tish_wasm_runtime/src/gpu.rs +17 -1
- package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
- package/crates/tishlang_cargo_bindgen/src/lib.rs +2 -2
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +1 -1
- 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/Cargo.toml
CHANGED
package/bin/tish
CHANGED
|
Binary file
|
|
@@ -477,6 +477,7 @@ fn convert_bin_op(op: &oxc::ast::ast::BinaryOperator) -> Result<BinOp, ConvertEr
|
|
|
477
477
|
oxc::ast::ast::BinaryOperator::BitwiseXOR => BinOp::BitXor,
|
|
478
478
|
oxc::ast::ast::BinaryOperator::ShiftLeft => BinOp::Shl,
|
|
479
479
|
oxc::ast::ast::BinaryOperator::ShiftRight => BinOp::Shr,
|
|
480
|
+
oxc::ast::ast::BinaryOperator::ShiftRightZeroFill => BinOp::UShr,
|
|
480
481
|
_ => {
|
|
481
482
|
return Err(ConvertError::new(ConvertErrorKind::Unsupported {
|
|
482
483
|
what: format!("binary operator: {op:?}"),
|
package/crates/tish/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "tishlang"
|
|
3
|
-
version = "
|
|
3
|
+
version = "2.0.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "Tish CLI - run, REPL, compile to native"
|
|
6
6
|
license-file = { workspace = true }
|
|
@@ -16,10 +16,15 @@ path = "src/main.rs"
|
|
|
16
16
|
# (see workspace `justfile`). `tish run --feature …` / `tish build --feature …` gate what runs
|
|
17
17
|
# or what gets linked into a *native output* binary — they are not the primary way to ship a
|
|
18
18
|
# “full” vs “empty” CLI.
|
|
19
|
-
default = ["full"]
|
|
19
|
+
default = ["full", "fast-alloc"]
|
|
20
20
|
# Alias: same dependency edges as `default` (kept for `--features full` in CI and docs).
|
|
21
21
|
# Includes `pg` so `tish run` can resolve `import … from '@tishlang/pg'` (`cargo:tish_pg`) on the bytecode VM.
|
|
22
|
-
full = ["http", "fs", "process", "regex", "ws", "timers", "pg"]
|
|
22
|
+
full = ["http", "fs", "process", "regex", "ws", "timers", "tty", "pg"]
|
|
23
|
+
# Fast allocator (mimalloc) as the process `#[global_allocator]`. tish's object/array/string workloads
|
|
24
|
+
# are allocation-bound (a sampling profile of object-heavy code spends ~14%+ in system malloc/free);
|
|
25
|
+
# mimalloc is markedly faster for the many-small-allocations pattern — the same reason JSC ships bmalloc.
|
|
26
|
+
# Semantically transparent (just a faster malloc); `--no-default-features` drops it for the system allocator.
|
|
27
|
+
fast-alloc = ["dep:mimalloc"]
|
|
23
28
|
# Opt-out: build without Postgres (`cargo build -p tishlang --no-default-features --features http,...`).
|
|
24
29
|
pg = ["dep:tishlang_pg"]
|
|
25
30
|
# Individual capability flags
|
|
@@ -29,6 +34,7 @@ fs = ["tishlang_eval/fs", "tishlang_runtime/fs", "tishlang_compil
|
|
|
29
34
|
process = ["tishlang_eval/process", "tishlang_runtime/process", "tishlang_compile/process", "tishlang_vm/process"]
|
|
30
35
|
regex = ["tishlang_eval/regex", "tishlang_runtime/regex", "tishlang_compile/regex", "tishlang_vm/regex"]
|
|
31
36
|
ws = ["tishlang_eval/ws", "tishlang_runtime/ws", "tishlang_compile/ws", "tishlang_vm/ws"]
|
|
37
|
+
tty = ["tishlang_eval/tty", "tishlang_runtime/tty", "tishlang_compile/tty", "tishlang_vm/tty"]
|
|
32
38
|
# GPU compute — Apple Silicon only
|
|
33
39
|
[dependencies]
|
|
34
40
|
rustyline = { version = "17", features = ["with-file-history"] }
|
|
@@ -41,6 +47,7 @@ tishlang_compile_js = { path = "../tish_compile_js", version = ">=0.1" }
|
|
|
41
47
|
tishlang_bytecode = { path = "../tish_bytecode", version = ">=0.1" }
|
|
42
48
|
tishlang_opt = { path = "../tish_opt", version = ">=0.1" }
|
|
43
49
|
tishlang_vm = { path = "../tish_vm", version = ">=0.1" }
|
|
50
|
+
tishlang_ffi = { path = "../tish_ffi", version = ">=0.1" }
|
|
44
51
|
tishlang_native = { path = "../tish_native", version = ">=0.1" }
|
|
45
52
|
tishlang_llvm = { path = "../tish_llvm", version = ">=0.1" }
|
|
46
53
|
tishlang_wasm = { path = "../tish_wasm", version = ">=0.1" }
|
|
@@ -49,6 +56,7 @@ tishlang_core = { path = "../tish_core", version = ">=0.1" }
|
|
|
49
56
|
tishlang_js_to_tish = { path = "../js_to_tish", version = ">=0.1" }
|
|
50
57
|
clap = { version = "4.6.0", features = ["derive", "color"] }
|
|
51
58
|
tishlang_pg = { path = "../tish_pg", version = ">=0.1", optional = true }
|
|
59
|
+
mimalloc = { version = "0.1", optional = true }
|
|
52
60
|
|
|
53
61
|
[dev-dependencies]
|
|
54
62
|
rayon = "1.11"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//! Export the `tish_value_*` C-ABI accessors (from `tishlang_ffi`) in the `tish` binary's dynamic
|
|
2
|
+
//! symbol table, so a `dlopen`'d native extension (`ffi:`) can resolve them at load time. This is
|
|
3
|
+
//! the **decoupled** FFI model: the extension declares the accessors `extern "C"` and does NOT link
|
|
4
|
+
//! `tish_core`, so there is a single value representation and no host/extension layout matching.
|
|
5
|
+
//! Without this flag the host's accessor symbols aren't visible to the loaded library.
|
|
6
|
+
|
|
7
|
+
fn main() {
|
|
8
|
+
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
|
9
|
+
match target_os.as_str() {
|
|
10
|
+
// macOS/iOS: export all global symbols (so the two-level-namespace binary exposes them).
|
|
11
|
+
"macos" | "ios" => {
|
|
12
|
+
println!("cargo:rustc-link-arg-bins=-Wl,-export_dynamic");
|
|
13
|
+
}
|
|
14
|
+
// Windows PE export tables work differently; the host-export FFI model isn't wired there yet.
|
|
15
|
+
"windows" => {}
|
|
16
|
+
// Linux/BSD: -rdynamic puts all symbols in the dynamic table.
|
|
17
|
+
_ => {
|
|
18
|
+
println!("cargo:rustc-link-arg-bins=-rdynamic");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -72,8 +72,7 @@ fn write_tish_banner_frame(out: &mut impl Write, reveal_t: f32, color_frame: usi
|
|
|
72
72
|
let visible = ((len as f32) * reveal_t).round() as usize;
|
|
73
73
|
let visible = visible.min(len);
|
|
74
74
|
|
|
75
|
-
for col in
|
|
76
|
-
let ch = chars[col];
|
|
75
|
+
for (col, &ch) in chars.iter().enumerate() {
|
|
77
76
|
if col >= visible || ch == ' ' {
|
|
78
77
|
let _ = write!(out, " ");
|
|
79
78
|
} else {
|
|
@@ -321,6 +320,8 @@ pub fn cli_after_help() -> String {
|
|
|
321
320
|
{oh}Environment variables:{r}
|
|
322
321
|
{t}TISH_NO_OPTIMIZE=1{r}
|
|
323
322
|
Disable AST and bytecode optimizations for run/build
|
|
323
|
+
{t}TISH_IGNORE_INDENT=1{r}
|
|
324
|
+
Ignore indentation syntax: parse blocks by braces only (debug nested-block transpilation)
|
|
324
325
|
|
|
325
326
|
See {t}tish run --help{r} and {t}tish build --help{r} for backend and feature options."
|
|
326
327
|
)
|
|
@@ -348,8 +349,10 @@ fn capabilities_section(oh: &str, t: &str, r: &str) -> String {
|
|
|
348
349
|
RegExp
|
|
349
350
|
{t}ws{r}
|
|
350
351
|
WebSocket client / server
|
|
352
|
+
{t}tty{r}
|
|
353
|
+
Interactive terminal: raw mode, key/resize events, size, alt screen (`import from \"tish:tty\"`)
|
|
351
354
|
{t}full{r}
|
|
352
|
-
All of the above (http, timers, fs, process, regex, ws)
|
|
355
|
+
All of the above (http, timers, fs, process, regex, ws, tty)
|
|
353
356
|
|
|
354
357
|
Omit --feature to allow every capability compiled into this `tish` binary; pass flags to restrict what scripts may use. The CLI is normally built with all of them (Cargo default on `tishlang`)."
|
|
355
358
|
)
|
|
@@ -429,8 +432,10 @@ pub fn build_after_help() -> String {
|
|
|
429
432
|
RegExp
|
|
430
433
|
{t}ws{r}
|
|
431
434
|
WebSocket client / server
|
|
435
|
+
{t}tty{r}
|
|
436
|
+
Interactive terminal: raw mode, key/resize events, size, alt screen (`import from \"tish:tty\"`)
|
|
432
437
|
{t}full{r}
|
|
433
|
-
All of the above (http, timers, fs, process, regex, ws)
|
|
438
|
+
All of the above (http, timers, fs, process, regex, ws, tty)
|
|
434
439
|
|
|
435
440
|
For `--target native`, these choose what is linked into the **output** executable (omit = same set as this `tish` binary was built with). Minimal native outputs still use a full `tish` CLI unless you built it with `cargo build -p tishlang --no-default-features`."
|
|
436
441
|
)
|
|
@@ -543,6 +548,9 @@ pub(crate) struct BuildArgs {
|
|
|
543
548
|
/// 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).
|
|
544
549
|
#[arg(long, help_heading = "Options")]
|
|
545
550
|
pub source_map: bool,
|
|
551
|
+
/// Run the gradual type checker: `warn` prints `line:col` type diagnostics; `error` also fails the build on them. (Equivalent to setting `TISH_CHECK`.)
|
|
552
|
+
#[arg(long, value_name = "MODE", help_heading = "Options")]
|
|
553
|
+
pub check: Option<String>,
|
|
546
554
|
/// Entry `.tish` file (or `.js` where supported).
|
|
547
555
|
#[arg(required = true, value_name = "FILE", help_heading = "Arguments")]
|
|
548
556
|
pub file: String,
|
|
@@ -561,5 +569,8 @@ pub(crate) enum Commands {
|
|
|
561
569
|
DumpAst {
|
|
562
570
|
#[arg(required = true, value_name = "FILE", help_heading = "Arguments")]
|
|
563
571
|
file: String,
|
|
572
|
+
/// Ignore indentation syntax: parse blocks by braces only (same as TISH_IGNORE_INDENT=1).
|
|
573
|
+
#[arg(long, help_heading = "Options")]
|
|
574
|
+
ignore_indent: bool,
|
|
564
575
|
},
|
|
565
576
|
}
|
package/crates/tish/src/main.rs
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
//! Tish CLI - run, REPL, build to native or other targets.
|
|
2
2
|
|
|
3
|
+
// Fast allocator for the whole process. tish's object/array/string workloads are allocation-bound
|
|
4
|
+
// (sampling profiles spend a large fraction in system malloc/free + Arc drops); mimalloc is much
|
|
5
|
+
// faster for the many-small-allocations pattern — the technique JSC uses with bmalloc. Transparent:
|
|
6
|
+
// it only changes WHICH malloc backs every allocation, never program behaviour. `fast-alloc` is in
|
|
7
|
+
// `default`; `--no-default-features` falls back to the system allocator.
|
|
8
|
+
#[cfg(feature = "fast-alloc")]
|
|
9
|
+
#[global_allocator]
|
|
10
|
+
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
|
11
|
+
|
|
3
12
|
mod cargo_native_registry;
|
|
4
13
|
mod cli_help;
|
|
5
14
|
mod repl_completion;
|
|
@@ -21,7 +30,7 @@ fn normalize_capability_flags(features: &[String]) -> HashSet<String> {
|
|
|
21
30
|
for s in features {
|
|
22
31
|
for part in s.split(',').map(str::trim).filter(|p| !p.is_empty()) {
|
|
23
32
|
if part == "full" {
|
|
24
|
-
for name in ["http", "timers", "fs", "process", "regex", "ws"] {
|
|
33
|
+
for name in ["http", "timers", "fs", "process", "regex", "ws", "tty"] {
|
|
25
34
|
out.insert(name.to_string());
|
|
26
35
|
}
|
|
27
36
|
} else {
|
|
@@ -63,7 +72,7 @@ fn argv_with_implicit_run(mut argv: Vec<String>) -> Vec<String> {
|
|
|
63
72
|
if argv.len() >= 2 {
|
|
64
73
|
let first = argv[1].as_str();
|
|
65
74
|
const SUBCOMMANDS: &[&str] = &["run", "repl", "build", "dump-ast"];
|
|
66
|
-
let looks_like_file = !first.starts_with('-') && !SUBCOMMANDS.
|
|
75
|
+
let looks_like_file = !first.starts_with('-') && !SUBCOMMANDS.contains(&first);
|
|
67
76
|
if looks_like_file {
|
|
68
77
|
argv.insert(1, "run".to_string());
|
|
69
78
|
}
|
|
@@ -103,18 +112,28 @@ fn main() {
|
|
|
103
112
|
a.no_optimize || no_opt_env,
|
|
104
113
|
),
|
|
105
114
|
Some(Commands::Repl(a)) => run_repl(&a.backend, a.no_optimize || no_opt_env, &a.features),
|
|
106
|
-
Some(Commands::Build(a)) =>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
&a.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
Some(Commands::Build(a)) => {
|
|
116
|
+
// `--check warn|error` drives the gradual type checker via the same channel as the
|
|
117
|
+
// `TISH_CHECK` env var that `tishlang_compile::run_type_check` reads.
|
|
118
|
+
if let Some(mode) = &a.check {
|
|
119
|
+
std::env::set_var("TISH_CHECK", mode);
|
|
120
|
+
}
|
|
121
|
+
build_file(
|
|
122
|
+
&a.file,
|
|
123
|
+
&a.output,
|
|
124
|
+
&a.target,
|
|
125
|
+
&a.native_backend,
|
|
126
|
+
&a.features,
|
|
127
|
+
a.no_optimize || no_opt_env,
|
|
128
|
+
a.source_map,
|
|
129
|
+
a.ios_triple.as_deref(),
|
|
130
|
+
&a.crate_type,
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
Some(Commands::DumpAst {
|
|
134
|
+
file,
|
|
135
|
+
ignore_indent,
|
|
136
|
+
}) => dump_ast(&file, ignore_indent),
|
|
118
137
|
None => {
|
|
119
138
|
if io::stdin().is_terminal() {
|
|
120
139
|
run_repl("vm", no_opt_env, &[])
|
|
@@ -169,7 +188,7 @@ fn run_stdin_source(
|
|
|
169
188
|
} else {
|
|
170
189
|
tishlang_opt::optimize(&prog)
|
|
171
190
|
};
|
|
172
|
-
run_program(&program, backend, no_optimize, features)
|
|
191
|
+
run_program(&program, &cwd, backend, no_optimize, features, None)
|
|
173
192
|
}
|
|
174
193
|
|
|
175
194
|
fn run_file(
|
|
@@ -214,16 +233,55 @@ fn run_file(
|
|
|
214
233
|
}
|
|
215
234
|
};
|
|
216
235
|
|
|
217
|
-
|
|
236
|
+
let ffi_base = Path::new(path)
|
|
237
|
+
.parent()
|
|
238
|
+
.map(|p| p.to_path_buf())
|
|
239
|
+
.unwrap_or_else(|| std::path::PathBuf::from("."));
|
|
240
|
+
run_program(&program, &ffi_base, backend, no_optimize, features, Some(path))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/// Load every `ffi:<path>` cdylib the program imports, resolving each path relative to `base_dir`
|
|
244
|
+
/// (the importing file's directory). Returns `(spec, exports)` pairs the backends register as
|
|
245
|
+
/// native modules so `import { f } from "ffi:./lib.dylib"` resolves to the extension's exports.
|
|
246
|
+
fn load_ffi_modules(
|
|
247
|
+
program: &tishlang_ast::Program,
|
|
248
|
+
base_dir: &Path,
|
|
249
|
+
) -> Result<Vec<(String, tishlang_core::ObjectMap)>, String> {
|
|
250
|
+
let mut out = Vec::new();
|
|
251
|
+
for spec in tishlang_compile::ffi_native_specs(program) {
|
|
252
|
+
let rel = spec.strip_prefix("ffi:").unwrap_or(spec.as_str());
|
|
253
|
+
let lib_path = base_dir.join(rel);
|
|
254
|
+
let path_str = lib_path
|
|
255
|
+
.to_str()
|
|
256
|
+
.ok_or_else(|| format!("ffi: non-UTF-8 path: {}", lib_path.display()))?;
|
|
257
|
+
let exports = tishlang_ffi::load_module(path_str)?;
|
|
258
|
+
out.push((spec, exports));
|
|
259
|
+
}
|
|
260
|
+
Ok(out)
|
|
218
261
|
}
|
|
219
262
|
|
|
220
263
|
fn run_program(
|
|
221
264
|
program: &tishlang_ast::Program,
|
|
265
|
+
base_dir: &Path,
|
|
222
266
|
backend: &str,
|
|
223
267
|
no_optimize: bool,
|
|
224
268
|
features: &[String],
|
|
269
|
+
source_name: Option<&str>,
|
|
225
270
|
) -> Result<(), String> {
|
|
271
|
+
// FFI: load each `ffi:<path>` cdylib (resolved relative to the importing file) into a
|
|
272
|
+
// name→export map the backends register as a native module. Built once, shared across backends.
|
|
273
|
+
let ffi_modules = load_ffi_modules(program, base_dir)?;
|
|
274
|
+
|
|
226
275
|
if backend == "interp" {
|
|
276
|
+
if !ffi_modules.is_empty() {
|
|
277
|
+
// The interpreter runs on `EvalValue`, but the FFI shim is built over core `Value`;
|
|
278
|
+
// bridging the two is a follow-up. `ffi:` works on the default VM backend today.
|
|
279
|
+
return Err(
|
|
280
|
+
"ffi: native extensions currently require the default (VM) backend; \
|
|
281
|
+
run without `--backend interp`"
|
|
282
|
+
.to_string(),
|
|
283
|
+
);
|
|
284
|
+
}
|
|
227
285
|
let mut eval = tishlang_eval::Evaluator::new();
|
|
228
286
|
let value = eval.eval_program(program)?;
|
|
229
287
|
#[cfg(feature = "timers")]
|
|
@@ -242,14 +300,22 @@ fn run_program(
|
|
|
242
300
|
return Ok(());
|
|
243
301
|
}
|
|
244
302
|
|
|
303
|
+
let source_arc: Option<std::sync::Arc<str>> = source_name.map(std::sync::Arc::from);
|
|
245
304
|
let chunk = if no_optimize {
|
|
246
|
-
|
|
305
|
+
// `--no-optimize` keeps the simpler path; line info is most useful with optimization on.
|
|
306
|
+
let mut c = tishlang_bytecode::compile_unoptimized(program).map_err(|e| e.to_string())?;
|
|
307
|
+
c.source = source_arc.clone();
|
|
308
|
+
c
|
|
247
309
|
} else {
|
|
248
|
-
tishlang_bytecode::
|
|
310
|
+
tishlang_bytecode::compile_with_source(program, source_arc.clone())
|
|
311
|
+
.map_err(|e| e.to_string())?
|
|
249
312
|
};
|
|
250
313
|
let caps = vm_capabilities_for_cli_run(features);
|
|
251
314
|
let mut vm = tishlang_vm::Vm::with_capabilities(caps);
|
|
252
315
|
cargo_native_registry::register_bytecode_native_modules(&mut vm);
|
|
316
|
+
for (spec, exports) in ffi_modules {
|
|
317
|
+
vm.register_native_module(spec, exports);
|
|
318
|
+
}
|
|
253
319
|
let value = vm.run_with_options(&chunk, false)?;
|
|
254
320
|
if !matches!(value, tishlang_core::Value::Null) {
|
|
255
321
|
println!(
|
|
@@ -561,7 +627,7 @@ fn compile_to_js(
|
|
|
561
627
|
Ok(())
|
|
562
628
|
}
|
|
563
629
|
|
|
564
|
-
#[allow(clippy::vec_init_then_push)]
|
|
630
|
+
#[allow(clippy::vec_init_then_push, clippy::too_many_arguments)] // build_file maps CLI build flags 1:1
|
|
565
631
|
fn build_file(
|
|
566
632
|
input_path: &str,
|
|
567
633
|
output_path: &str,
|
|
@@ -773,9 +839,15 @@ mod cli_tests {
|
|
|
773
839
|
}
|
|
774
840
|
}
|
|
775
841
|
|
|
776
|
-
fn dump_ast(path: &str) -> Result<(), String> {
|
|
842
|
+
fn dump_ast(path: &str, ignore_indent: bool) -> Result<(), String> {
|
|
777
843
|
let source = fs::read_to_string(path).map_err(|e| format!("Cannot read {}: {}", path, e))?;
|
|
778
|
-
|
|
844
|
+
// The `--ignore-indent` flag ORs with the `TISH_IGNORE_INDENT` env var, mirroring how
|
|
845
|
+
// `--no-optimize` combines with `TISH_NO_OPTIMIZE`.
|
|
846
|
+
let ignore_indent = ignore_indent || tishlang_parser::LexerOptions::from_env().ignore_indent;
|
|
847
|
+
let program = tishlang_parser::parse_with_options(
|
|
848
|
+
&source,
|
|
849
|
+
tishlang_parser::LexerOptions { ignore_indent },
|
|
850
|
+
)?;
|
|
779
851
|
println!("{:#?}", program);
|
|
780
852
|
Ok(())
|
|
781
853
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//! Issue #74: a runtime error from the bytecode VM carries its source location (`file:line`)
|
|
2
|
+
//! instead of a bare message, so embedders/users can find where it came from.
|
|
3
|
+
|
|
4
|
+
use std::path::PathBuf;
|
|
5
|
+
use std::process::Command;
|
|
6
|
+
|
|
7
|
+
#[test]
|
|
8
|
+
fn runtime_error_reports_source_file_and_line() {
|
|
9
|
+
let tish = PathBuf::from(env!("CARGO_BIN_EXE_tish"));
|
|
10
|
+
let fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
11
|
+
.join("tests")
|
|
12
|
+
.join("fixtures")
|
|
13
|
+
.join("runtime_error_location.tish");
|
|
14
|
+
assert!(fixture.is_file(), "missing fixture {}", fixture.display());
|
|
15
|
+
|
|
16
|
+
// `obj.field.deep` reads `.field` of `null` on line 4 of the fixture.
|
|
17
|
+
let out = Command::new(&tish)
|
|
18
|
+
.args(["run", "--backend", "vm", fixture.to_str().unwrap()])
|
|
19
|
+
.output()
|
|
20
|
+
.expect("spawn tish run");
|
|
21
|
+
assert!(!out.status.success(), "expected a runtime error");
|
|
22
|
+
let stderr = String::from_utf8_lossy(&out.stderr);
|
|
23
|
+
|
|
24
|
+
assert!(
|
|
25
|
+
stderr.contains(":4"),
|
|
26
|
+
"error should report the source line (4):\n{stderr}"
|
|
27
|
+
);
|
|
28
|
+
assert!(
|
|
29
|
+
stderr.contains("runtime_error_location.tish"),
|
|
30
|
+
"error should report the source file:\n{stderr}"
|
|
31
|
+
);
|
|
32
|
+
assert!(
|
|
33
|
+
stderr.contains("Cannot read property 'field' of null"),
|
|
34
|
+
"error should keep the original message:\n{stderr}"
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// try/catch catches runtime errors (null property/method access, "not a function")
|
|
2
|
+
// AND throws that cross function-call frames, with the thrown value preserved (issue #60).
|
|
3
|
+
try { let x = null; x.foo() } catch (e) { console.log("A caught") }
|
|
4
|
+
function f() { throw new Error("boom") }
|
|
5
|
+
try { f() } catch (e) { console.log("B caught:", e.message) }
|
|
6
|
+
function g() { let y = null; y.baz() }
|
|
7
|
+
try { g() } catch (e) { console.log("C caught") }
|
|
8
|
+
console.log("D done")
|
|
9
|
+
try { try { throw "inner" } catch (e) { throw "rethrown" } } catch (e) { console.log("E caught:", e) }
|
|
10
|
+
try { console.log("F ok") } catch (e) { console.log("F should not happen") }
|
|
11
|
+
let total = 0
|
|
12
|
+
function risky(n) { if (n > 3) { throw n }; return n }
|
|
13
|
+
let i = 0
|
|
14
|
+
while (i < 6) { try { total = total + risky(i) } catch (e) { total = total + 100 }; i = i + 1 }
|
|
15
|
+
console.log("G total:", total)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// tish:tty capability (issue #101). Asserts the module loads and degrades gracefully when
|
|
2
|
+
// stdout/stdin is not a terminal (CI), so the output is deterministic without a real TTY.
|
|
3
|
+
import { isTTY, size, setRawMode, enterAltScreen, leaveAltScreen, read, readLine } from 'tish:tty'
|
|
4
|
+
console.log("isTTY:", typeof isTTY())
|
|
5
|
+
console.log("size:", size() ? "object" : "null")
|
|
6
|
+
console.log("setRawMode:", typeof setRawMode)
|
|
7
|
+
console.log("read:", typeof read)
|
|
8
|
+
console.log("readLine:", typeof readLine)
|
|
9
|
+
console.log("alt:", typeof enterAltScreen, typeof leaveAltScreen)
|