@tishlang/tish-format 1.0.12 → 2.0.1
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 +51 -0
- package/LICENSE +13 -0
- package/bin/tish-format +0 -0
- package/crates/js_to_tish/Cargo.toml +11 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +55 -0
- package/crates/js_to_tish/src/lib.rs +11 -0
- package/crates/js_to_tish/src/span_util.rs +35 -0
- package/crates/js_to_tish/src/transform/expr.rs +611 -0
- package/crates/js_to_tish/src/transform/stmt.rs +503 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +62 -0
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +576 -0
- package/crates/tish/src/main.rs +853 -0
- package/crates/tish/src/repl_completion.rs +199 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/error_source_location.rs +36 -0
- package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
- package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -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 +1406 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +649 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +11 -0
- package/crates/tish_build_utils/src/lib.rs +577 -0
- package/crates/tish_builtins/Cargo.toml +22 -0
- package/crates/tish_builtins/src/array.rs +803 -0
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +199 -0
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +293 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +21 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +646 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +164 -0
- package/crates/tish_bytecode/src/compiler.rs +2604 -0
- package/crates/tish_bytecode/src/encoding.rs +102 -0
- package/crates/tish_bytecode/src/lib.rs +20 -0
- package/crates/tish_bytecode/src/opcode.rs +185 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +193 -0
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +27 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +7317 -0
- package/crates/tish_compile/src/infer.rs +1681 -0
- package/crates/tish_compile/src/lib.rs +206 -0
- package/crates/tish_compile/src/resolve.rs +1951 -0
- package/crates/tish_compile/src/types.rs +605 -0
- package/crates/tish_compile_js/Cargo.toml +18 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +938 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/lib.rs +26 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
- package/crates/tish_compiler_wasm/Cargo.toml +21 -0
- package/crates/tish_compiler_wasm/src/lib.rs +57 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
- package/crates/tish_core/Cargo.toml +32 -0
- package/crates/tish_core/src/console_style.rs +170 -0
- package/crates/tish_core/src/json.rs +430 -0
- package/crates/tish_core/src/lib.rs +20 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +1350 -0
- package/crates/tish_core/src/vmref.rs +183 -0
- package/crates/tish_cranelift/Cargo.toml +19 -0
- package/crates/tish_cranelift/src/lib.rs +43 -0
- package/crates/tish_cranelift/src/link.rs +130 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +51 -0
- package/crates/tish_eval/src/eval.rs +4265 -0
- package/crates/tish_eval/src/http.rs +191 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +551 -0
- package/crates/tish_eval/src/promise.rs +179 -0
- package/crates/tish_eval/src/regex.rs +299 -0
- package/crates/tish_eval/src/timers.rs +120 -0
- package/crates/tish_eval/src/value.rs +336 -0
- package/crates/tish_eval/src/value_convert.rs +117 -0
- 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/Cargo.toml +16 -0
- package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
- package/crates/tish_fmt/src/lib.rs +2157 -0
- package/crates/tish_jsx_web/Cargo.toml +9 -0
- package/crates/tish_jsx_web/README.md +5 -0
- package/crates/tish_jsx_web/src/lib.rs +2 -0
- package/crates/tish_lexer/Cargo.toml +9 -0
- package/crates/tish_lexer/src/lib.rs +1104 -0
- package/crates/tish_lexer/src/token.rs +170 -0
- package/crates/tish_lint/Cargo.toml +18 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
- package/crates/tish_lint/src/lib.rs +281 -0
- package/crates/tish_llvm/Cargo.toml +13 -0
- package/crates/tish_llvm/src/lib.rs +115 -0
- package/crates/tish_lsp/Cargo.toml +25 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/builtin_goto.rs +362 -0
- package/crates/tish_lsp/src/import_goto.rs +564 -0
- package/crates/tish_lsp/src/main.rs +1459 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +481 -0
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +416 -0
- package/crates/tish_opt/Cargo.toml +13 -0
- package/crates/tish_opt/src/lib.rs +1046 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +386 -0
- package/crates/tish_parser/src/parser.rs +2726 -0
- 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 +955 -0
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3601 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +100 -0
- package/crates/tish_runtime/src/http.rs +1347 -0
- package/crates/tish_runtime/src/http_fetch.rs +492 -0
- package/crates/tish_runtime/src/http_hyper.rs +441 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +1447 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +558 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +172 -0
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +778 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +692 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +573 -0
- package/crates/tish_ui/src/runtime/mod.rs +183 -0
- package/crates/tish_vm/Cargo.toml +60 -0
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +41 -0
- package/crates/tish_vm/src/vm.rs +3536 -0
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
- package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
- package/crates/tish_wasm/Cargo.toml +15 -0
- package/crates/tish_wasm/src/lib.rs +428 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
- package/crates/tish_wasm_runtime/src/lib.rs +42 -0
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
- package/justfile +276 -0
- package/package.json +2 -2
- package/platform/darwin-arm64/tish-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- package/platform/win32-x64/tish-fmt.exe +0 -0
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
//! Tish CLI - run, REPL, build to native or other targets.
|
|
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
|
+
|
|
12
|
+
mod cargo_native_registry;
|
|
13
|
+
mod cli_help;
|
|
14
|
+
mod repl_completion;
|
|
15
|
+
|
|
16
|
+
use std::collections::HashSet;
|
|
17
|
+
use std::fs;
|
|
18
|
+
use std::io::{self, IsTerminal, Read, Write};
|
|
19
|
+
use std::path::{Path, PathBuf};
|
|
20
|
+
use tishlang_core::VmRef;
|
|
21
|
+
|
|
22
|
+
use clap::FromArgMatches;
|
|
23
|
+
use rustyline::{Behavior, ColorMode, CompletionType, Config, Editor};
|
|
24
|
+
|
|
25
|
+
use cli_help::{Cli, Commands};
|
|
26
|
+
|
|
27
|
+
/// Normalize `--feature` / `--feature http,timers,fs` / `--feature full` for VM runs and native builds.
|
|
28
|
+
fn normalize_capability_flags(features: &[String]) -> HashSet<String> {
|
|
29
|
+
let mut out = HashSet::new();
|
|
30
|
+
for s in features {
|
|
31
|
+
for part in s.split(',').map(str::trim).filter(|p| !p.is_empty()) {
|
|
32
|
+
if part == "full" {
|
|
33
|
+
for name in ["http", "timers", "fs", "process", "regex", "ws", "tty"] {
|
|
34
|
+
out.insert(name.to_string());
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
out.insert(part.to_string());
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
out
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// VM capabilities for `run` / `repl` / stdin with the bytecode VM.
|
|
45
|
+
///
|
|
46
|
+
/// If the user passes no `--feature`, enable **everything linked into this `tish` binary**
|
|
47
|
+
/// (so `cargo run --bin tish --features full -- script.tish` does not need `--feature full`).
|
|
48
|
+
/// If they pass `--feature …`, use **only** that set (e.g. restrict a full build to `http` only).
|
|
49
|
+
fn vm_capabilities_for_cli_run(cli_features: &[String]) -> HashSet<String> {
|
|
50
|
+
if cli_features.is_empty() {
|
|
51
|
+
tishlang_vm::all_compiled_capabilities()
|
|
52
|
+
} else {
|
|
53
|
+
normalize_capability_flags(cli_features)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// `--feature` list for `tish build --target native`: same default as `tish run` (all linked-in caps).
|
|
58
|
+
fn native_build_features_from_cli(cli_features: &[String]) -> Vec<String> {
|
|
59
|
+
if cli_features.is_empty() {
|
|
60
|
+
let mut v: Vec<String> = tishlang_vm::all_compiled_capabilities()
|
|
61
|
+
.into_iter()
|
|
62
|
+
.collect();
|
|
63
|
+
v.sort();
|
|
64
|
+
v
|
|
65
|
+
} else {
|
|
66
|
+
cli_features.to_vec()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// `tish script.tish` → insert `run` so it matches `tish run script.tish` (npx / npm UX).
|
|
71
|
+
fn argv_with_implicit_run(mut argv: Vec<String>) -> Vec<String> {
|
|
72
|
+
if argv.len() >= 2 {
|
|
73
|
+
let first = argv[1].as_str();
|
|
74
|
+
const SUBCOMMANDS: &[&str] = &["run", "repl", "build", "dump-ast"];
|
|
75
|
+
let looks_like_file = !first.starts_with('-') && !SUBCOMMANDS.contains(&first);
|
|
76
|
+
if looks_like_file {
|
|
77
|
+
argv.insert(1, "run".to_string());
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
argv
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fn main() {
|
|
84
|
+
let no_opt_env = std::env::var_os("TISH_NO_OPTIMIZE")
|
|
85
|
+
.map(|v| v == "1" || v == "true" || v == "yes")
|
|
86
|
+
.unwrap_or(false);
|
|
87
|
+
|
|
88
|
+
// `tish -` (like `node -` / `bun -`); clap would treat `-` as an invalid subcommand.
|
|
89
|
+
let argv: Vec<String> = std::env::args().collect();
|
|
90
|
+
if argv.len() == 2 && argv[1] == "-" {
|
|
91
|
+
let result = run_stdin_pipe("vm", &[], no_opt_env, true);
|
|
92
|
+
if let Err(e) = result {
|
|
93
|
+
eprintln!("Error: {}", e);
|
|
94
|
+
std::process::exit(1);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if cli_help::argv_requests_help(&argv) {
|
|
100
|
+
cli_help::print_banner_with_help(&argv);
|
|
101
|
+
std::process::exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let argv = argv_with_implicit_run(argv);
|
|
105
|
+
let matches = cli_help::build_command().get_matches_from(&argv);
|
|
106
|
+
let cli = Cli::from_arg_matches(&matches).unwrap_or_else(|e| e.exit());
|
|
107
|
+
let result = match cli.command {
|
|
108
|
+
Some(Commands::Run(a)) => run_file(
|
|
109
|
+
&a.file,
|
|
110
|
+
&a.backend,
|
|
111
|
+
&a.features,
|
|
112
|
+
a.no_optimize || no_opt_env,
|
|
113
|
+
),
|
|
114
|
+
Some(Commands::Repl(a)) => run_repl(&a.backend, a.no_optimize || no_opt_env, &a.features),
|
|
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),
|
|
137
|
+
None => {
|
|
138
|
+
if io::stdin().is_terminal() {
|
|
139
|
+
run_repl("vm", no_opt_env, &[])
|
|
140
|
+
} else {
|
|
141
|
+
// `echo '...' | tish` — run script from stdin (Bun-style)
|
|
142
|
+
run_stdin_pipe("vm", &[], no_opt_env, false)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if let Err(e) = result {
|
|
148
|
+
eprintln!("Error: {}", e);
|
|
149
|
+
std::process::exit(1);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Read stdin and run as Tish. If `fail_on_empty`, `tish run -` / `tish -` get an error; if false, empty stdin exits 0.
|
|
154
|
+
fn run_stdin_pipe(
|
|
155
|
+
backend: &str,
|
|
156
|
+
features: &[String],
|
|
157
|
+
no_optimize: bool,
|
|
158
|
+
fail_on_empty: bool,
|
|
159
|
+
) -> Result<(), String> {
|
|
160
|
+
let mut source = String::new();
|
|
161
|
+
io::stdin()
|
|
162
|
+
.read_to_string(&mut source)
|
|
163
|
+
.map_err(|e| format!("Cannot read stdin: {}", e))?;
|
|
164
|
+
if source.trim().is_empty() {
|
|
165
|
+
if fail_on_empty {
|
|
166
|
+
return Err(
|
|
167
|
+
"No source on stdin. Example: echo 'console.log(1)' | tish or tish run -"
|
|
168
|
+
.into(),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return Ok(());
|
|
172
|
+
}
|
|
173
|
+
run_stdin_source(&source, backend, features, no_optimize)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fn run_stdin_source(
|
|
177
|
+
source: &str,
|
|
178
|
+
backend: &str,
|
|
179
|
+
features: &[String],
|
|
180
|
+
no_optimize: bool,
|
|
181
|
+
) -> Result<(), String> {
|
|
182
|
+
let cwd = std::env::current_dir().map_err(|e| e.to_string())?;
|
|
183
|
+
let modules = tishlang_compile::resolve_project_from_stdin(source, &cwd)?;
|
|
184
|
+
tishlang_compile::detect_cycles(&modules)?;
|
|
185
|
+
let prog = tishlang_compile::merge_modules(modules)?.program;
|
|
186
|
+
let program = if no_optimize {
|
|
187
|
+
prog
|
|
188
|
+
} else {
|
|
189
|
+
tishlang_opt::optimize(&prog)
|
|
190
|
+
};
|
|
191
|
+
run_program(&program, &cwd, backend, no_optimize, features, None)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fn run_file(
|
|
195
|
+
path: &str,
|
|
196
|
+
backend: &str,
|
|
197
|
+
features: &[String],
|
|
198
|
+
no_optimize: bool,
|
|
199
|
+
) -> Result<(), String> {
|
|
200
|
+
let program = if path == "-" {
|
|
201
|
+
return run_stdin_pipe(backend, features, no_optimize, true);
|
|
202
|
+
} else {
|
|
203
|
+
let path = Path::new(path)
|
|
204
|
+
.canonicalize()
|
|
205
|
+
.map_err(|e| format!("Cannot resolve {}: {}", path, e))?;
|
|
206
|
+
let project_root = path.parent().and_then(|p| {
|
|
207
|
+
if p.file_name().and_then(|n| n.to_str()) == Some("src") {
|
|
208
|
+
p.parent()
|
|
209
|
+
} else {
|
|
210
|
+
Some(p)
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if path.extension().map(|e| e == "js") == Some(true) {
|
|
215
|
+
let prog = tishlang_js_to_tish::convert(
|
|
216
|
+
&fs::read_to_string(&path).map_err(|e| format!("{}", e))?,
|
|
217
|
+
)
|
|
218
|
+
.map_err(|e| format!("{}", e))?;
|
|
219
|
+
if no_optimize {
|
|
220
|
+
prog
|
|
221
|
+
} else {
|
|
222
|
+
tishlang_opt::optimize(&prog)
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
let modules = tishlang_compile::resolve_project(&path, project_root)?;
|
|
226
|
+
tishlang_compile::detect_cycles(&modules)?;
|
|
227
|
+
let prog = tishlang_compile::merge_modules(modules)?.program;
|
|
228
|
+
if no_optimize {
|
|
229
|
+
prog
|
|
230
|
+
} else {
|
|
231
|
+
tishlang_opt::optimize(&prog)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
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)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
fn run_program(
|
|
264
|
+
program: &tishlang_ast::Program,
|
|
265
|
+
base_dir: &Path,
|
|
266
|
+
backend: &str,
|
|
267
|
+
no_optimize: bool,
|
|
268
|
+
features: &[String],
|
|
269
|
+
source_name: Option<&str>,
|
|
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
|
+
|
|
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
|
+
}
|
|
285
|
+
let mut eval = tishlang_eval::Evaluator::new();
|
|
286
|
+
let value = eval.eval_program(program)?;
|
|
287
|
+
#[cfg(feature = "timers")]
|
|
288
|
+
{
|
|
289
|
+
let _ = eval.run_timer_phase();
|
|
290
|
+
}
|
|
291
|
+
if !matches!(value, tishlang_eval::Value::Null) {
|
|
292
|
+
println!(
|
|
293
|
+
"{}",
|
|
294
|
+
tishlang_eval::format_value_for_console(
|
|
295
|
+
&value,
|
|
296
|
+
tishlang_core::use_console_colors()
|
|
297
|
+
)
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
return Ok(());
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let source_arc: Option<std::sync::Arc<str>> = source_name.map(std::sync::Arc::from);
|
|
304
|
+
let chunk = if no_optimize {
|
|
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
|
|
309
|
+
} else {
|
|
310
|
+
tishlang_bytecode::compile_with_source(program, source_arc.clone())
|
|
311
|
+
.map_err(|e| e.to_string())?
|
|
312
|
+
};
|
|
313
|
+
let caps = vm_capabilities_for_cli_run(features);
|
|
314
|
+
let mut vm = tishlang_vm::Vm::with_capabilities(caps);
|
|
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
|
+
}
|
|
319
|
+
let value = vm.run_with_options(&chunk, false)?;
|
|
320
|
+
if !matches!(value, tishlang_core::Value::Null) {
|
|
321
|
+
println!(
|
|
322
|
+
"{}",
|
|
323
|
+
tishlang_core::format_value_styled(&value, tishlang_core::use_console_colors())
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
Ok(())
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
fn run_repl(backend: &str, no_optimize: bool, features: &[String]) -> Result<(), String> {
|
|
330
|
+
cli_help::print_tish_banner();
|
|
331
|
+
println!("Tish REPL (Ctrl-D to exit)");
|
|
332
|
+
let mut buffer = String::new();
|
|
333
|
+
|
|
334
|
+
if backend == "interp" {
|
|
335
|
+
let mut eval = tishlang_eval::Evaluator::new();
|
|
336
|
+
let mut multiline = String::new();
|
|
337
|
+
loop {
|
|
338
|
+
let prompt = repl_prompt(multiline.is_empty());
|
|
339
|
+
print!("{}", prompt);
|
|
340
|
+
io::stdout().flush().map_err(|e| e.to_string())?;
|
|
341
|
+
buffer.clear();
|
|
342
|
+
if io::stdin()
|
|
343
|
+
.read_line(&mut buffer)
|
|
344
|
+
.map_err(|e| e.to_string())?
|
|
345
|
+
== 0
|
|
346
|
+
{
|
|
347
|
+
if !multiline.is_empty() {
|
|
348
|
+
let _ = tishlang_parser::parse(multiline.trim());
|
|
349
|
+
}
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
let line = buffer.trim_end();
|
|
353
|
+
if multiline.is_empty() && line.is_empty() {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if multiline.is_empty() {
|
|
357
|
+
multiline = line.to_string();
|
|
358
|
+
} else {
|
|
359
|
+
multiline.push('\n');
|
|
360
|
+
multiline.push_str(line);
|
|
361
|
+
}
|
|
362
|
+
match tishlang_parser::parse(multiline.trim()) {
|
|
363
|
+
Ok(program) => {
|
|
364
|
+
match eval.eval_program(&program) {
|
|
365
|
+
Ok(v) => {
|
|
366
|
+
#[cfg(feature = "timers")]
|
|
367
|
+
{
|
|
368
|
+
let _ = eval.run_timer_phase();
|
|
369
|
+
}
|
|
370
|
+
if !matches!(v, tishlang_eval::Value::Null) {
|
|
371
|
+
println!(
|
|
372
|
+
"{}",
|
|
373
|
+
tishlang_eval::format_value_for_console(
|
|
374
|
+
&v,
|
|
375
|
+
tishlang_core::use_console_colors()
|
|
376
|
+
)
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
Err(e) => eprintln!("{}", e),
|
|
381
|
+
}
|
|
382
|
+
multiline.clear();
|
|
383
|
+
}
|
|
384
|
+
Err(e) => {
|
|
385
|
+
if e.to_lowercase().contains("eof") {
|
|
386
|
+
// Incomplete: keep reading
|
|
387
|
+
} else {
|
|
388
|
+
eprintln!("Parse error: {}", e);
|
|
389
|
+
multiline.clear();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return Ok(());
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// VM backend with tab completion (e.g. a. -> properties/methods)
|
|
398
|
+
if !std::io::stdin().is_terminal() {
|
|
399
|
+
eprintln!("Note: Tab completion and grey preview require an interactive terminal (TTY).");
|
|
400
|
+
}
|
|
401
|
+
let mut vm0 = tishlang_vm::Vm::with_capabilities(vm_capabilities_for_cli_run(features));
|
|
402
|
+
cargo_native_registry::register_bytecode_native_modules(&mut vm0);
|
|
403
|
+
let vm = VmRef::new(vm0);
|
|
404
|
+
let completer = repl_completion::ReplCompleter {
|
|
405
|
+
vm: vm.clone(),
|
|
406
|
+
no_optimize,
|
|
407
|
+
};
|
|
408
|
+
let config = Config::builder()
|
|
409
|
+
.completion_type(CompletionType::List)
|
|
410
|
+
.completion_show_all_if_ambiguous(true)
|
|
411
|
+
.color_mode(ColorMode::Forced)
|
|
412
|
+
.behavior(Behavior::PreferTerm)
|
|
413
|
+
.build();
|
|
414
|
+
let mut rl: Editor<repl_completion::ReplCompleter, _> =
|
|
415
|
+
Editor::with_config(config).map_err(|e| e.to_string())?;
|
|
416
|
+
rl.set_helper(Some(completer));
|
|
417
|
+
|
|
418
|
+
if let Some(ref path) = tish_history_path() {
|
|
419
|
+
let _ = rl.load_history(path);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
println!("Tab after 'obj.' for completions (grey preview); press Tab again for full list.");
|
|
423
|
+
println!("Multi-line: type until the statement is complete; use ... continuation prompt.");
|
|
424
|
+
|
|
425
|
+
let mut buffer = String::new();
|
|
426
|
+
|
|
427
|
+
loop {
|
|
428
|
+
let prompt = repl_prompt(buffer.is_empty());
|
|
429
|
+
let line = match rl.readline(&prompt) {
|
|
430
|
+
Ok(l) => l,
|
|
431
|
+
Err(rustyline::error::ReadlineError::Eof) => {
|
|
432
|
+
if buffer.is_empty() {
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
match tishlang_parser::parse(buffer.trim()) {
|
|
436
|
+
Ok(program) => {
|
|
437
|
+
let compile_fn = if no_optimize {
|
|
438
|
+
tishlang_bytecode::compile_for_repl_unoptimized
|
|
439
|
+
} else {
|
|
440
|
+
tishlang_bytecode::compile_for_repl
|
|
441
|
+
};
|
|
442
|
+
if let Ok(chunk) = compile_fn(&program) {
|
|
443
|
+
let _ = vm.borrow_mut().run_with_options(&chunk, true);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
Err(e) => eprintln!("Parse error: {}", e),
|
|
447
|
+
}
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
Err(rustyline::error::ReadlineError::Interrupted) => {
|
|
451
|
+
buffer.clear();
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
Err(e) => return Err(e.to_string()),
|
|
455
|
+
};
|
|
456
|
+
let line = line.trim_end();
|
|
457
|
+
if buffer.is_empty() && line.is_empty() {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if buffer.is_empty() {
|
|
461
|
+
buffer = line.to_string();
|
|
462
|
+
} else {
|
|
463
|
+
buffer.push('\n');
|
|
464
|
+
buffer.push_str(line);
|
|
465
|
+
}
|
|
466
|
+
match tishlang_parser::parse(buffer.trim()) {
|
|
467
|
+
Ok(program) => {
|
|
468
|
+
let compile_fn = if no_optimize {
|
|
469
|
+
tishlang_bytecode::compile_for_repl_unoptimized
|
|
470
|
+
} else {
|
|
471
|
+
tishlang_bytecode::compile_for_repl
|
|
472
|
+
};
|
|
473
|
+
match compile_fn(&program) {
|
|
474
|
+
Ok(chunk) => match vm.borrow_mut().run_with_options(&chunk, true) {
|
|
475
|
+
Ok(v) => {
|
|
476
|
+
if !matches!(v, tishlang_core::Value::Null) {
|
|
477
|
+
println!(
|
|
478
|
+
"{}",
|
|
479
|
+
tishlang_core::format_value_styled(
|
|
480
|
+
&v,
|
|
481
|
+
tishlang_core::use_console_colors()
|
|
482
|
+
)
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
Err(e) => eprintln!("{}", e),
|
|
487
|
+
},
|
|
488
|
+
Err(e) => eprintln!("Compile error: {}", e),
|
|
489
|
+
}
|
|
490
|
+
let _ = rl.add_history_entry(buffer.trim());
|
|
491
|
+
buffer.clear();
|
|
492
|
+
}
|
|
493
|
+
Err(e) => {
|
|
494
|
+
if e.to_lowercase().contains("eof") {
|
|
495
|
+
// Incomplete: keep accumulating (Python-style ... prompt)
|
|
496
|
+
} else {
|
|
497
|
+
eprintln!("Parse error: {}", e);
|
|
498
|
+
buffer.clear();
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if let Some(ref path) = tish_history_path() {
|
|
505
|
+
let _ = rl.save_history(path);
|
|
506
|
+
}
|
|
507
|
+
Ok(())
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/// REPL prompt with green caret when stdout is a TTY (platform-style).
|
|
511
|
+
fn repl_prompt(primary: bool) -> String {
|
|
512
|
+
if tishlang_core::use_console_colors() {
|
|
513
|
+
if primary {
|
|
514
|
+
"\x1b[32m> \x1b[0m".to_string()
|
|
515
|
+
} else {
|
|
516
|
+
"\x1b[32m... \x1b[0m".to_string()
|
|
517
|
+
}
|
|
518
|
+
} else if primary {
|
|
519
|
+
"> ".to_string()
|
|
520
|
+
} else {
|
|
521
|
+
"... ".to_string()
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/// Path to REPL history file (Python-style: ~/.tish_history).
|
|
526
|
+
fn tish_history_path() -> Option<PathBuf> {
|
|
527
|
+
let home = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE"));
|
|
528
|
+
home.map(|h| PathBuf::from(h).join(".tish_history"))
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
fn compile_to_js(
|
|
532
|
+
input_path: &Path,
|
|
533
|
+
output_path: &str,
|
|
534
|
+
optimize: bool,
|
|
535
|
+
source_map: bool,
|
|
536
|
+
) -> Result<(), String> {
|
|
537
|
+
if source_map && optimize {
|
|
538
|
+
return Err(
|
|
539
|
+
"tish build --target js --source-map requires --no-optimize (mappings follow unmerged statement order)."
|
|
540
|
+
.into(),
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
if source_map
|
|
544
|
+
&& (input_path.extension().map(|e| e == "jsx") == Some(true)
|
|
545
|
+
|| input_path.extension().map(|e| e == "js") == Some(true))
|
|
546
|
+
{
|
|
547
|
+
return Err(
|
|
548
|
+
"tish build --target js --source-map is only supported for .tish project builds (not single-file .jsx / .js inputs)."
|
|
549
|
+
.into(),
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
let project_root = input_path.parent().and_then(|p| {
|
|
553
|
+
if p.file_name().and_then(|n| n.to_str()) == Some("src") {
|
|
554
|
+
p.parent()
|
|
555
|
+
} else {
|
|
556
|
+
Some(p)
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
let out_path = Path::new(output_path);
|
|
560
|
+
let out_path = if out_path.extension().is_none()
|
|
561
|
+
|| out_path.extension() == Some(std::ffi::OsStr::new(""))
|
|
562
|
+
{
|
|
563
|
+
out_path.with_extension("js")
|
|
564
|
+
} else {
|
|
565
|
+
out_path.to_path_buf()
|
|
566
|
+
};
|
|
567
|
+
let out_js_name = out_path
|
|
568
|
+
.file_name()
|
|
569
|
+
.and_then(|s| s.to_str())
|
|
570
|
+
.unwrap_or("out.js");
|
|
571
|
+
|
|
572
|
+
let (js, map_json) = if input_path.extension().map(|e| e == "jsx") == Some(true) {
|
|
573
|
+
let source = fs::read_to_string(input_path).map_err(|e| format!("{}", e))?;
|
|
574
|
+
let wrapped = format!(
|
|
575
|
+
"export fn __TishJsxRoot() {{\n return (\n{}\n )\n}}",
|
|
576
|
+
source.trim()
|
|
577
|
+
);
|
|
578
|
+
let program =
|
|
579
|
+
tishlang_parser::parse(&wrapped).map_err(|e| format!("JSX wrapper parse: {}", e))?;
|
|
580
|
+
let p = if optimize {
|
|
581
|
+
tishlang_opt::optimize(&program)
|
|
582
|
+
} else {
|
|
583
|
+
program
|
|
584
|
+
};
|
|
585
|
+
let js =
|
|
586
|
+
tishlang_compile_js::compile_with_jsx(&p, optimize).map_err(|e| format!("{}", e))?;
|
|
587
|
+
(js, None)
|
|
588
|
+
} else if input_path.extension().map(|e| e == "js") == Some(true) {
|
|
589
|
+
let source = fs::read_to_string(input_path).map_err(|e| format!("{}", e))?;
|
|
590
|
+
let program = tishlang_js_to_tish::convert(&source).map_err(|e| format!("{}", e))?;
|
|
591
|
+
let js = tishlang_compile_js::compile_with_jsx(&program, optimize)
|
|
592
|
+
.map_err(|e| format!("{}", e))?;
|
|
593
|
+
(js, None)
|
|
594
|
+
} else if source_map {
|
|
595
|
+
let bundle = tishlang_compile_js::compile_project_with_jsx_and_source_map(
|
|
596
|
+
input_path,
|
|
597
|
+
project_root,
|
|
598
|
+
out_js_name,
|
|
599
|
+
)
|
|
600
|
+
.map_err(|e| format!("{}", e))?;
|
|
601
|
+
(bundle.js, bundle.source_map_json)
|
|
602
|
+
} else {
|
|
603
|
+
let js = tishlang_compile_js::compile_project_with_jsx(input_path, project_root, optimize)
|
|
604
|
+
.map_err(|e| format!("{}", e))?;
|
|
605
|
+
(js, None)
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
if let Some(parent) = out_path.parent() {
|
|
609
|
+
fs::create_dir_all(parent)
|
|
610
|
+
.map_err(|e| format!("Cannot create output directory {}: {}", parent.display(), e))?;
|
|
611
|
+
}
|
|
612
|
+
let mut js_out = js;
|
|
613
|
+
if let Some(map) = &map_json {
|
|
614
|
+
let map_path = out_path.with_extension("js.map");
|
|
615
|
+
fs::write(&map_path, map)
|
|
616
|
+
.map_err(|e| format!("Cannot write {}: {}", map_path.display(), e))?;
|
|
617
|
+
let map_url = map_path
|
|
618
|
+
.file_name()
|
|
619
|
+
.and_then(|s| s.to_str())
|
|
620
|
+
.unwrap_or("out.js.map");
|
|
621
|
+
js_out.push_str(&format!("\n//# sourceMappingURL={map_url}\n"));
|
|
622
|
+
println!("Built: {}", map_path.display());
|
|
623
|
+
}
|
|
624
|
+
fs::write(&out_path, js_out)
|
|
625
|
+
.map_err(|e| format!("Cannot write {}: {}", out_path.display(), e))?;
|
|
626
|
+
println!("Built: {}", out_path.display());
|
|
627
|
+
Ok(())
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
#[allow(clippy::vec_init_then_push, clippy::too_many_arguments)] // build_file maps CLI build flags 1:1
|
|
631
|
+
fn build_file(
|
|
632
|
+
input_path: &str,
|
|
633
|
+
output_path: &str,
|
|
634
|
+
target: &str,
|
|
635
|
+
native_backend: &str,
|
|
636
|
+
cli_features: &[String],
|
|
637
|
+
no_optimize: bool,
|
|
638
|
+
source_map: bool,
|
|
639
|
+
ios_triple: Option<&str>,
|
|
640
|
+
crate_type: &str,
|
|
641
|
+
) -> Result<(), String> {
|
|
642
|
+
let optimize = !no_optimize;
|
|
643
|
+
let input_path = Path::new(input_path)
|
|
644
|
+
.canonicalize()
|
|
645
|
+
.map_err(|e| format!("Cannot resolve {}: {}", input_path, e))?;
|
|
646
|
+
|
|
647
|
+
let is_js = input_path.extension().map(|e| e == "js") == Some(true);
|
|
648
|
+
|
|
649
|
+
if target == "js" {
|
|
650
|
+
return compile_to_js(&input_path, output_path, optimize, source_map);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if target == "wasm" && is_js {
|
|
654
|
+
let source = fs::read_to_string(&input_path).map_err(|e| format!("{}", e))?;
|
|
655
|
+
let program = tishlang_js_to_tish::convert(&source).map_err(|e| format!("{}", e))?;
|
|
656
|
+
return tishlang_wasm::compile_program_to_wasm(&program, Path::new(output_path), optimize)
|
|
657
|
+
.map_err(|e| format!("{}", e));
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if target == "wasm" {
|
|
661
|
+
let project_root = input_path.parent().and_then(|p| {
|
|
662
|
+
if p.file_name().and_then(|n| n.to_str()) == Some("src") {
|
|
663
|
+
p.parent()
|
|
664
|
+
} else {
|
|
665
|
+
Some(p)
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
return tishlang_wasm::compile_to_wasm(
|
|
669
|
+
&input_path,
|
|
670
|
+
project_root,
|
|
671
|
+
Path::new(output_path),
|
|
672
|
+
optimize,
|
|
673
|
+
)
|
|
674
|
+
.map_err(|e| e.to_string());
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if target == "wasi" {
|
|
678
|
+
let project_root = input_path.parent().and_then(|p| {
|
|
679
|
+
if p.file_name().and_then(|n| n.to_str()) == Some("src") {
|
|
680
|
+
p.parent()
|
|
681
|
+
} else {
|
|
682
|
+
Some(p)
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
let features = native_build_features_from_cli(cli_features);
|
|
686
|
+
return tishlang_wasm::compile_to_wasi(
|
|
687
|
+
&input_path,
|
|
688
|
+
project_root,
|
|
689
|
+
Path::new(output_path),
|
|
690
|
+
optimize,
|
|
691
|
+
&features,
|
|
692
|
+
)
|
|
693
|
+
.map_err(|e| e.to_string());
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if target == "bytecode" {
|
|
697
|
+
let project_root = input_path.parent().and_then(|p| {
|
|
698
|
+
if p.file_name().and_then(|n| n.to_str()) == Some("src") {
|
|
699
|
+
p.parent()
|
|
700
|
+
} else {
|
|
701
|
+
Some(p)
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
return tishlang_wasm::compile_to_bytecode(
|
|
705
|
+
&input_path,
|
|
706
|
+
project_root,
|
|
707
|
+
Path::new(output_path),
|
|
708
|
+
optimize,
|
|
709
|
+
)
|
|
710
|
+
.map_err(|e| e.to_string());
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if target != "native" {
|
|
714
|
+
return Err(format!(
|
|
715
|
+
"Unknown target: {}. Use 'native', 'js', 'wasm', 'wasi', or 'bytecode'.",
|
|
716
|
+
target
|
|
717
|
+
));
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
let project_root = input_path.parent().map(|p| {
|
|
721
|
+
if p.file_name().and_then(|n| n.to_str()) == Some("src") {
|
|
722
|
+
p.parent().unwrap_or(p)
|
|
723
|
+
} else {
|
|
724
|
+
p
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
let features: Vec<String> = native_build_features_from_cli(cli_features);
|
|
728
|
+
|
|
729
|
+
let build_config = if let Some(triple) = ios_triple {
|
|
730
|
+
tishlang_native::NativeBuildConfig::ios_staticlib(triple)
|
|
731
|
+
} else if crate_type == "staticlib" {
|
|
732
|
+
tishlang_native::NativeBuildConfig {
|
|
733
|
+
artifact: tishlang_native::NativeArtifact::StaticLib,
|
|
734
|
+
cargo_target: None,
|
|
735
|
+
emit_mode: tishlang_compile::NativeEmitMode::EmbeddedLib,
|
|
736
|
+
}
|
|
737
|
+
} else if crate_type != "bin" {
|
|
738
|
+
return Err(format!(
|
|
739
|
+
"Unknown --crate-type: {}. Use 'bin' or 'staticlib'.",
|
|
740
|
+
crate_type
|
|
741
|
+
));
|
|
742
|
+
} else {
|
|
743
|
+
tishlang_native::NativeBuildConfig::desktop()
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
if is_js {
|
|
747
|
+
let source = fs::read_to_string(&input_path).map_err(|e| format!("{}", e))?;
|
|
748
|
+
let program = tishlang_js_to_tish::convert(&source).map_err(|e| format!("{}", e))?;
|
|
749
|
+
if build_config.artifact != tishlang_native::NativeArtifact::Bin {
|
|
750
|
+
return Err(
|
|
751
|
+
"--crate-type staticlib / --ios-triple require a .tish entry file.".to_string(),
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
tishlang_native::compile_program_to_native(
|
|
755
|
+
&program,
|
|
756
|
+
project_root,
|
|
757
|
+
Path::new(output_path),
|
|
758
|
+
&features,
|
|
759
|
+
native_backend,
|
|
760
|
+
optimize,
|
|
761
|
+
)
|
|
762
|
+
.map_err(|e| e.to_string())?;
|
|
763
|
+
} else {
|
|
764
|
+
tishlang_native::compile_to_native_with_config(
|
|
765
|
+
&input_path,
|
|
766
|
+
project_root,
|
|
767
|
+
Path::new(output_path),
|
|
768
|
+
&features,
|
|
769
|
+
native_backend,
|
|
770
|
+
optimize,
|
|
771
|
+
&build_config,
|
|
772
|
+
)
|
|
773
|
+
.map_err(|e| e.to_string())?;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
let out_name = Path::new(output_path)
|
|
777
|
+
.file_stem()
|
|
778
|
+
.and_then(|s| s.to_str())
|
|
779
|
+
.unwrap_or("tish_out");
|
|
780
|
+
let built_path = if build_config.artifact == tishlang_native::NativeArtifact::StaticLib {
|
|
781
|
+
if output_path.ends_with('/') || Path::new(output_path).is_dir() {
|
|
782
|
+
Path::new(output_path).join(format!("lib{out_name}.a"))
|
|
783
|
+
} else if output_path.ends_with(".a") {
|
|
784
|
+
Path::new(output_path).to_path_buf()
|
|
785
|
+
} else {
|
|
786
|
+
Path::new(output_path).with_extension("a")
|
|
787
|
+
}
|
|
788
|
+
} else if output_path.ends_with('/') || Path::new(output_path).is_dir() {
|
|
789
|
+
Path::new(output_path).join(out_name)
|
|
790
|
+
} else {
|
|
791
|
+
Path::new(output_path).to_path_buf()
|
|
792
|
+
};
|
|
793
|
+
println!("Built: {}", built_path.display());
|
|
794
|
+
Ok(())
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
#[cfg(test)]
|
|
798
|
+
mod cli_tests {
|
|
799
|
+
use clap::Parser;
|
|
800
|
+
|
|
801
|
+
use crate::cli_help::{Cli, Commands};
|
|
802
|
+
|
|
803
|
+
use super::argv_with_implicit_run;
|
|
804
|
+
|
|
805
|
+
#[test]
|
|
806
|
+
fn implicit_run_inserts_run_before_file() {
|
|
807
|
+
let argv = argv_with_implicit_run(vec!["tish".to_string(), "hello.tish".to_string()]);
|
|
808
|
+
let cli = Cli::try_parse_from(argv).unwrap();
|
|
809
|
+
match cli.command {
|
|
810
|
+
Some(Commands::Run(a)) => assert_eq!(a.file, "hello.tish"),
|
|
811
|
+
_ => panic!("expected Run"),
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
#[test]
|
|
816
|
+
fn explicit_subcommand_not_treated_as_file() {
|
|
817
|
+
let argv = argv_with_implicit_run(vec!["tish".to_string(), "repl".to_string()]);
|
|
818
|
+
let cli = Cli::try_parse_from(argv).unwrap();
|
|
819
|
+
assert!(matches!(cli.command, Some(Commands::Repl(_))));
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
#[test]
|
|
823
|
+
fn build_js_target_parses() {
|
|
824
|
+
let cli = Cli::try_parse_from(["tish", "build", "m.tish", "--target", "js", "-o", "x.js"])
|
|
825
|
+
.unwrap();
|
|
826
|
+
match cli.command {
|
|
827
|
+
Some(Commands::Build(a)) => assert_eq!(a.file, "m.tish"),
|
|
828
|
+
_ => panic!("expected Build"),
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
#[test]
|
|
833
|
+
fn run_stdin_marker_parses_as_file() {
|
|
834
|
+
let cli = Cli::try_parse_from(["tish", "run", "-"]).unwrap();
|
|
835
|
+
match cli.command {
|
|
836
|
+
Some(Commands::Run(a)) => assert_eq!(a.file, "-"),
|
|
837
|
+
_ => panic!("expected Run"),
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
fn dump_ast(path: &str, ignore_indent: bool) -> Result<(), String> {
|
|
843
|
+
let source = fs::read_to_string(path).map_err(|e| format!("Cannot read {}: {}", path, e))?;
|
|
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
|
+
)?;
|
|
851
|
+
println!("{:#?}", program);
|
|
852
|
+
Ok(())
|
|
853
|
+
}
|