@tishlang/tish 1.1.3 → 1.3.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/README.md +4 -4
- package/bin/tish.js +1 -1
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/main.rs +117 -40
- package/crates/tish/tests/integration_test.rs +12 -12
- package/crates/tish_builtins/src/construct.rs +1 -1
- package/crates/tish_compile/src/lib.rs +1 -1
- package/crates/tish_compile/src/resolve.rs +53 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +1 -1
- package/crates/tish_eval/src/eval.rs +1 -1
- package/justfile +9 -9
- package/package.json +2 -2
- 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/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @tishlang/tish
|
|
2
2
|
|
|
3
|
-
[Tish](https://github.com/tishlang/tish) is a minimal TypeScript/JavaScript–compatible language: run with an interpreter, use a REPL, or
|
|
3
|
+
[Tish](https://github.com/tishlang/tish) is a minimal TypeScript/JavaScript–compatible language: run with an interpreter, use a REPL, or build native binaries and other targets.
|
|
4
4
|
|
|
5
5
|
This npm package ships the **Tish CLI** for Node.js **22+**. It includes platform-specific native binaries; the `tish` command picks the right one for your OS and CPU.
|
|
6
6
|
|
|
@@ -25,14 +25,14 @@ npx @tishlang/tish hello.tish
|
|
|
25
25
|
npx @tishlang/tish run src/main.tish
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Build a native executable:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
npx @tishlang/tish
|
|
31
|
+
npx @tishlang/tish build app.tish -o app
|
|
32
32
|
./app
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Native
|
|
35
|
+
Native builds use the Rust backend by default (requires [Rust](https://rustup.rs) and `cargo` on your PATH). The package includes the Tish workspace source (`Cargo.toml`, `crates/`, `justfile`) so `tish build` can run `cargo build` for your program. For pure Tish without native imports, use `--native-backend cranelift` (no Rust toolchain needed).
|
|
36
36
|
|
|
37
37
|
Start the REPL:
|
|
38
38
|
|
package/bin/tish.js
CHANGED
|
@@ -19,7 +19,7 @@ if (!fs.existsSync(binPath)) {
|
|
|
19
19
|
|
|
20
20
|
// Convenience: "npx @tishlang/tish FILE [args]" → "tish run FILE [args]"
|
|
21
21
|
const args = process.argv.slice(2);
|
|
22
|
-
const subcommands = ['run', 'repl', '
|
|
22
|
+
const subcommands = ['run', 'repl', 'build', 'dump-ast'];
|
|
23
23
|
const first = args[0];
|
|
24
24
|
const looksLikeFile = first && !first.startsWith('-') && !subcommands.includes(first);
|
|
25
25
|
|
package/crates/tish/Cargo.toml
CHANGED
package/crates/tish/src/main.rs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
//! Tish CLI - run, REPL,
|
|
1
|
+
//! Tish CLI - run, REPL, build to native or other targets.
|
|
2
2
|
|
|
3
3
|
mod repl_completion;
|
|
4
4
|
|
|
5
5
|
use std::cell::RefCell;
|
|
6
6
|
use std::fs;
|
|
7
|
-
use std::io::{self, IsTerminal, Write};
|
|
7
|
+
use std::io::{self, IsTerminal, Read, Write};
|
|
8
8
|
use std::path::{Path, PathBuf};
|
|
9
9
|
use std::rc::Rc;
|
|
10
10
|
|
|
@@ -23,7 +23,8 @@ pub(crate) struct Cli {
|
|
|
23
23
|
|
|
24
24
|
#[derive(Parser)]
|
|
25
25
|
struct RunArgs {
|
|
26
|
-
|
|
26
|
+
/// Path to a `.tish` file, or `-` to read the program from stdin (like `node -`).
|
|
27
|
+
#[arg(required = true, allow_hyphen_values = true)]
|
|
27
28
|
file: String,
|
|
28
29
|
#[arg(long, default_value = "vm")]
|
|
29
30
|
backend: String,
|
|
@@ -45,7 +46,7 @@ struct ReplArgs {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
#[derive(Parser)]
|
|
48
|
-
struct
|
|
49
|
+
struct BuildArgs {
|
|
49
50
|
#[arg(short, long, default_value = "tish_out")]
|
|
50
51
|
output: String,
|
|
51
52
|
#[arg(long, default_value = "native")]
|
|
@@ -66,8 +67,8 @@ pub(crate) enum Commands {
|
|
|
66
67
|
Run(RunArgs),
|
|
67
68
|
/// Interactive REPL
|
|
68
69
|
Repl(ReplArgs),
|
|
69
|
-
///
|
|
70
|
-
|
|
70
|
+
/// Build native binary, wasm, wasi, or JavaScript output
|
|
71
|
+
Build(BuildArgs),
|
|
71
72
|
/// Parse and dump AST
|
|
72
73
|
#[command(name = "dump-ast")]
|
|
73
74
|
DumpAst {
|
|
@@ -77,14 +78,26 @@ pub(crate) enum Commands {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
fn main() {
|
|
80
|
-
let cli = Cli::parse();
|
|
81
81
|
let no_opt_env = std::env::var_os("TISH_NO_OPTIMIZE")
|
|
82
82
|
.map(|v| v == "1" || v == "true" || v == "yes")
|
|
83
83
|
.unwrap_or(false);
|
|
84
|
+
|
|
85
|
+
// `tish -` (like `node -` / `bun -`); clap would treat `-` as an invalid subcommand.
|
|
86
|
+
let argv: Vec<String> = std::env::args().collect();
|
|
87
|
+
if argv.len() == 2 && argv[1] == "-" {
|
|
88
|
+
let result = run_stdin_pipe("vm", &[], no_opt_env, true);
|
|
89
|
+
if let Err(e) = result {
|
|
90
|
+
eprintln!("Error: {}", e);
|
|
91
|
+
std::process::exit(1);
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let cli = Cli::parse();
|
|
84
97
|
let result = match cli.command {
|
|
85
98
|
Some(Commands::Run(a)) => run_file(&a.file, &a.backend, &a.features, a.no_optimize || no_opt_env),
|
|
86
99
|
Some(Commands::Repl(a)) => run_repl(&a.backend, a.no_optimize || no_opt_env),
|
|
87
|
-
Some(Commands::
|
|
100
|
+
Some(Commands::Build(a)) => build_file(
|
|
88
101
|
&a.file,
|
|
89
102
|
&a.output,
|
|
90
103
|
&a.target,
|
|
@@ -93,7 +106,14 @@ fn main() {
|
|
|
93
106
|
a.no_optimize || no_opt_env,
|
|
94
107
|
),
|
|
95
108
|
Some(Commands::DumpAst { file }) => dump_ast(&file),
|
|
96
|
-
None =>
|
|
109
|
+
None => {
|
|
110
|
+
if io::stdin().is_terminal() {
|
|
111
|
+
run_repl("vm", no_opt_env)
|
|
112
|
+
} else {
|
|
113
|
+
// `echo '...' | tish` — run script from stdin (Bun-style)
|
|
114
|
+
run_stdin_pipe("vm", &[], no_opt_env, false)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
97
117
|
};
|
|
98
118
|
|
|
99
119
|
if let Err(e) = result {
|
|
@@ -102,49 +122,97 @@ fn main() {
|
|
|
102
122
|
}
|
|
103
123
|
}
|
|
104
124
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
125
|
+
/// Read stdin and run as Tish. If `fail_on_empty`, `tish run -` / `tish -` get an error; if false, empty stdin exits 0.
|
|
126
|
+
fn run_stdin_pipe(
|
|
127
|
+
backend: &str,
|
|
128
|
+
features: &[String],
|
|
129
|
+
no_optimize: bool,
|
|
130
|
+
fail_on_empty: bool,
|
|
131
|
+
) -> Result<(), String> {
|
|
132
|
+
let mut source = String::new();
|
|
133
|
+
io::stdin()
|
|
134
|
+
.read_to_string(&mut source)
|
|
135
|
+
.map_err(|e| format!("Cannot read stdin: {}", e))?;
|
|
136
|
+
if source.trim().is_empty() {
|
|
137
|
+
if fail_on_empty {
|
|
138
|
+
return Err(
|
|
139
|
+
"No source on stdin. Example: echo 'console.log(1)' | tish or tish run -".into(),
|
|
140
|
+
);
|
|
112
141
|
}
|
|
113
|
-
|
|
142
|
+
return Ok(());
|
|
143
|
+
}
|
|
144
|
+
run_stdin_source(&source, backend, features, no_optimize)
|
|
145
|
+
}
|
|
114
146
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
147
|
+
fn run_stdin_source(
|
|
148
|
+
source: &str,
|
|
149
|
+
backend: &str,
|
|
150
|
+
_features: &[String],
|
|
151
|
+
no_optimize: bool,
|
|
152
|
+
) -> Result<(), String> {
|
|
153
|
+
let cwd = std::env::current_dir().map_err(|e| e.to_string())?;
|
|
154
|
+
let modules = tishlang_compile::resolve_project_from_stdin(source, &cwd)?;
|
|
155
|
+
tishlang_compile::detect_cycles(&modules)?;
|
|
156
|
+
let prog = tishlang_compile::merge_modules(modules)?;
|
|
157
|
+
let program = if no_optimize {
|
|
158
|
+
prog
|
|
159
|
+
} else {
|
|
160
|
+
tishlang_opt::optimize(&prog)
|
|
161
|
+
};
|
|
162
|
+
run_program(&program, backend, no_optimize)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fn run_file(path: &str, backend: &str, features: &[String], no_optimize: bool) -> Result<(), String> {
|
|
166
|
+
let program = if path == "-" {
|
|
167
|
+
return run_stdin_pipe(backend, features, no_optimize, true);
|
|
123
168
|
} else {
|
|
124
|
-
let
|
|
125
|
-
|
|
126
|
-
let
|
|
127
|
-
|
|
128
|
-
|
|
169
|
+
let path =
|
|
170
|
+
Path::new(path).canonicalize().map_err(|e| format!("Cannot resolve {}: {}", path, e))?;
|
|
171
|
+
let project_root = path.parent().and_then(|p| {
|
|
172
|
+
if p.file_name().and_then(|n| n.to_str()) == Some("src") {
|
|
173
|
+
p.parent()
|
|
174
|
+
} else {
|
|
175
|
+
Some(p)
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if path.extension().map(|e| e == "js") == Some(true) {
|
|
180
|
+
let prog = tishlang_js_to_tish::convert(&fs::read_to_string(&path).map_err(|e| format!("{}", e))?)
|
|
181
|
+
.map_err(|e| format!("{}", e))?;
|
|
182
|
+
if no_optimize {
|
|
183
|
+
prog
|
|
184
|
+
} else {
|
|
185
|
+
tishlang_opt::optimize(&prog)
|
|
186
|
+
}
|
|
129
187
|
} else {
|
|
130
|
-
|
|
188
|
+
let modules = tishlang_compile::resolve_project(&path, project_root)?;
|
|
189
|
+
tishlang_compile::detect_cycles(&modules)?;
|
|
190
|
+
let prog = tishlang_compile::merge_modules(modules)?;
|
|
191
|
+
if no_optimize {
|
|
192
|
+
prog
|
|
193
|
+
} else {
|
|
194
|
+
tishlang_opt::optimize(&prog)
|
|
195
|
+
}
|
|
131
196
|
}
|
|
132
197
|
};
|
|
133
198
|
|
|
199
|
+
run_program(&program, backend, no_optimize)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fn run_program(program: &tishlang_ast::Program, backend: &str, no_optimize: bool) -> Result<(), String> {
|
|
134
203
|
if backend == "interp" {
|
|
135
204
|
let mut eval = tishlang_eval::Evaluator::new();
|
|
136
|
-
let value = eval.eval_program(
|
|
205
|
+
let value = eval.eval_program(program)?;
|
|
137
206
|
if !matches!(value, tishlang_eval::Value::Null) {
|
|
138
207
|
println!("{}", tishlang_eval::format_value_for_console(&value, tishlang_core::use_console_colors()));
|
|
139
208
|
}
|
|
140
209
|
return Ok(());
|
|
141
210
|
}
|
|
142
211
|
|
|
143
|
-
// VM backend (bytecode) - supports native imports when built with fs/http/process features
|
|
144
212
|
let chunk = if no_optimize {
|
|
145
|
-
tishlang_bytecode::compile_unoptimized(
|
|
213
|
+
tishlang_bytecode::compile_unoptimized(program).map_err(|e| e.to_string())?
|
|
146
214
|
} else {
|
|
147
|
-
tishlang_bytecode::compile(
|
|
215
|
+
tishlang_bytecode::compile(program).map_err(|e| e.to_string())?
|
|
148
216
|
};
|
|
149
217
|
let value = tishlang_vm::run(&chunk)?;
|
|
150
218
|
if !matches!(value, tishlang_core::Value::Null) {
|
|
@@ -385,7 +453,7 @@ fn compile_to_js(input_path: &Path, output_path: &str, optimize: bool) -> Result
|
|
|
385
453
|
}
|
|
386
454
|
|
|
387
455
|
#[allow(clippy::vec_init_then_push)]
|
|
388
|
-
fn
|
|
456
|
+
fn build_file(
|
|
389
457
|
input_path: &str,
|
|
390
458
|
output_path: &str,
|
|
391
459
|
target: &str,
|
|
@@ -512,10 +580,10 @@ mod cli_tests {
|
|
|
512
580
|
use super::{Cli, Commands};
|
|
513
581
|
|
|
514
582
|
#[test]
|
|
515
|
-
fn
|
|
583
|
+
fn build_js_target_parses() {
|
|
516
584
|
let cli = Cli::try_parse_from([
|
|
517
585
|
"tish",
|
|
518
|
-
"
|
|
586
|
+
"build",
|
|
519
587
|
"m.tish",
|
|
520
588
|
"--target",
|
|
521
589
|
"js",
|
|
@@ -524,8 +592,17 @@ mod cli_tests {
|
|
|
524
592
|
])
|
|
525
593
|
.unwrap();
|
|
526
594
|
match cli.command {
|
|
527
|
-
Some(Commands::
|
|
528
|
-
_ => panic!("expected
|
|
595
|
+
Some(Commands::Build(a)) => assert_eq!(a.file, "m.tish"),
|
|
596
|
+
_ => panic!("expected Build"),
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
#[test]
|
|
601
|
+
fn run_stdin_marker_parses_as_file() {
|
|
602
|
+
let cli = Cli::try_parse_from(["tish", "run", "-"]).unwrap();
|
|
603
|
+
match cli.command {
|
|
604
|
+
Some(Commands::Run(a)) => assert_eq!(a.file, "-"),
|
|
605
|
+
_ => panic!("expected Run"),
|
|
529
606
|
}
|
|
530
607
|
}
|
|
531
608
|
}
|
|
@@ -40,7 +40,7 @@ fn target_dir() -> PathBuf {
|
|
|
40
40
|
.unwrap_or_else(|_| workspace_root().join("target"))
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/// Cache dir for tish
|
|
43
|
+
/// Cache dir for tish build outputs (under target/ so CI rust-cache restores it).
|
|
44
44
|
fn integration_compile_cache_dir() -> PathBuf {
|
|
45
45
|
target_dir().join("integration_compile_cache")
|
|
46
46
|
}
|
|
@@ -56,7 +56,7 @@ fn file_content_hash(path: &Path) -> u64 {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/// Compile a .tish file with the given backend, using a persistent cache so we only run
|
|
59
|
-
/// `tish
|
|
59
|
+
/// `tish build` when the file or backend changed. Returns path to the compiled artifact
|
|
60
60
|
/// (binary, .js, or .wasm) in a temp dir; caller may run it and then delete it.
|
|
61
61
|
///
|
|
62
62
|
/// Cache is keyed by backend (native, cranelift, js, wasi) so e.g. cranelift and wasi
|
|
@@ -73,7 +73,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
73
73
|
let ext = if cfg!(target_os = "windows") { ".exe" } else { "" };
|
|
74
74
|
let cached = cache_base.join(format!("{}_{}{}", stem, hash8, ext));
|
|
75
75
|
let args = vec![
|
|
76
|
-
OsString::from("
|
|
76
|
+
OsString::from("build"),
|
|
77
77
|
OsString::from(path),
|
|
78
78
|
OsString::from("-o"),
|
|
79
79
|
OsString::from(&cached),
|
|
@@ -84,7 +84,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
84
84
|
let ext = if cfg!(target_os = "windows") { ".exe" } else { "" };
|
|
85
85
|
let cached = cache_base.join(format!("{}_{}{}", stem, hash8, ext));
|
|
86
86
|
let args = vec![
|
|
87
|
-
OsString::from("
|
|
87
|
+
OsString::from("build"),
|
|
88
88
|
OsString::from(path),
|
|
89
89
|
OsString::from("-o"),
|
|
90
90
|
OsString::from(&cached),
|
|
@@ -96,7 +96,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
96
96
|
"js" => {
|
|
97
97
|
let cached = cache_base.join(format!("{}_{}.js", stem, hash8));
|
|
98
98
|
let args = vec![
|
|
99
|
-
OsString::from("
|
|
99
|
+
OsString::from("build"),
|
|
100
100
|
OsString::from(path),
|
|
101
101
|
OsString::from("--target"),
|
|
102
102
|
OsString::from("js"),
|
|
@@ -109,7 +109,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
109
109
|
let out_base = cache_base.join(format!("{}_{}", stem, hash8));
|
|
110
110
|
let artifact = out_base.with_extension("wasm");
|
|
111
111
|
let args = vec![
|
|
112
|
-
OsString::from("
|
|
112
|
+
OsString::from("build"),
|
|
113
113
|
OsString::from(path),
|
|
114
114
|
OsString::from("-o"),
|
|
115
115
|
OsString::from(&out_base),
|
|
@@ -126,7 +126,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
126
126
|
.args(compile_args)
|
|
127
127
|
.current_dir(workspace_root())
|
|
128
128
|
.output()
|
|
129
|
-
.expect("run tish
|
|
129
|
+
.expect("run tish build");
|
|
130
130
|
assert!(
|
|
131
131
|
out.status.success(),
|
|
132
132
|
"Compile failed for {} ({}): {}",
|
|
@@ -203,13 +203,13 @@ fn test_async_await_compile_via_binary() {
|
|
|
203
203
|
if path.exists() && bin.exists() {
|
|
204
204
|
let out = std::env::temp_dir().join("tish_async_test_out");
|
|
205
205
|
let compile_result = Command::new(&bin)
|
|
206
|
-
.args(["
|
|
206
|
+
.args(["build", path.to_string_lossy().as_ref(), "-o", out.to_string_lossy().as_ref()])
|
|
207
207
|
.current_dir(workspace_root())
|
|
208
208
|
.output();
|
|
209
|
-
let compile_out = compile_result.expect("run tish
|
|
209
|
+
let compile_out = compile_result.expect("run tish build");
|
|
210
210
|
assert!(
|
|
211
211
|
compile_out.status.success(),
|
|
212
|
-
"tish
|
|
212
|
+
"tish build failed: {}",
|
|
213
213
|
String::from_utf8_lossy(&compile_out.stderr)
|
|
214
214
|
);
|
|
215
215
|
// Run compiled binary to validate non-blocking fetchAll executes correctly
|
|
@@ -245,13 +245,13 @@ fn test_async_parallel_vs_sequential_timing() {
|
|
|
245
245
|
|
|
246
246
|
// Compile both
|
|
247
247
|
let compile_par = Command::new(&bin)
|
|
248
|
-
.args(["
|
|
248
|
+
.args(["build", parallel_src.to_string_lossy().as_ref(), "-o", out_parallel.to_string_lossy().as_ref()])
|
|
249
249
|
.current_dir(workspace_root())
|
|
250
250
|
.output();
|
|
251
251
|
assert!(compile_par.as_ref().unwrap().status.success(), "compile parallel: {}", String::from_utf8_lossy(&compile_par.as_ref().unwrap().stderr));
|
|
252
252
|
|
|
253
253
|
let compile_seq = Command::new(&bin)
|
|
254
|
-
.args(["
|
|
254
|
+
.args(["build", sequential_src.to_string_lossy().as_ref(), "-o", out_sequential.to_string_lossy().as_ref()])
|
|
255
255
|
.current_dir(workspace_root())
|
|
256
256
|
.output();
|
|
257
257
|
assert!(compile_seq.as_ref().unwrap().status.success(), "compile sequential: {}", String::from_utf8_lossy(&compile_seq.as_ref().unwrap().stderr));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//! `new` lowering for non-JS targets: `construct(callee, args)` approximates JS `[[Construct]]`.
|
|
2
|
-
//! Browser-exact behavior remains on `tish
|
|
2
|
+
//! Browser-exact behavior remains on `tish build --target js`.
|
|
3
3
|
|
|
4
4
|
use std::cell::RefCell;
|
|
5
5
|
use std::rc::Rc;
|
|
@@ -15,7 +15,7 @@ pub use codegen::CompileError;
|
|
|
15
15
|
pub use resolve::{
|
|
16
16
|
detect_cycles, extract_native_import_features, has_external_native_imports, has_native_imports,
|
|
17
17
|
is_builtin_native_spec, merge_modules, resolve_native_modules, resolve_project,
|
|
18
|
-
ResolvedNativeModule,
|
|
18
|
+
resolve_project_from_stdin, ResolvedNativeModule,
|
|
19
19
|
};
|
|
20
20
|
pub use types::{RustType, TypeContext};
|
|
21
21
|
|
|
@@ -242,6 +242,59 @@ pub fn resolve_project(
|
|
|
242
242
|
.collect())
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
/// Resolve modules when the entry program is read from stdin (`tish run -`).
|
|
246
|
+
/// Relative file imports resolve from `project_root` (typically [`std::env::current_dir()`]).
|
|
247
|
+
/// The synthetic entry path `<stdin>` is not a real file; dependencies load from disk as usual.
|
|
248
|
+
pub fn resolve_project_from_stdin(
|
|
249
|
+
source: &str,
|
|
250
|
+
project_root: &Path,
|
|
251
|
+
) -> Result<Vec<ResolvedModule>, String> {
|
|
252
|
+
let root_canon = project_root
|
|
253
|
+
.canonicalize()
|
|
254
|
+
.map_err(|e| format!("Cannot canonicalize project root {}: {}", project_root.display(), e))?;
|
|
255
|
+
|
|
256
|
+
let stdin_path = root_canon.join("<stdin>");
|
|
257
|
+
let program = tishlang_parser::parse(source)
|
|
258
|
+
.map_err(|e| format!("Parse error (stdin): {}", e))?;
|
|
259
|
+
|
|
260
|
+
let mut visited = HashSet::new();
|
|
261
|
+
let mut path_to_module: HashMap<PathBuf, Program> = HashMap::new();
|
|
262
|
+
let mut load_order: Vec<PathBuf> = Vec::new();
|
|
263
|
+
|
|
264
|
+
let from_dir = stdin_path
|
|
265
|
+
.parent()
|
|
266
|
+
.unwrap_or_else(|| Path::new("."));
|
|
267
|
+
|
|
268
|
+
for stmt in &program.statements {
|
|
269
|
+
if let Statement::Import { from, .. } = stmt {
|
|
270
|
+
if is_native_import(from) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
let dep_path = resolve_import_path(from, from_dir, &root_canon)?;
|
|
274
|
+
if !path_to_module.contains_key(&dep_path) {
|
|
275
|
+
load_module_recursive(
|
|
276
|
+
&dep_path,
|
|
277
|
+
&root_canon,
|
|
278
|
+
&mut visited,
|
|
279
|
+
&mut path_to_module,
|
|
280
|
+
&mut load_order,
|
|
281
|
+
)?;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
path_to_module.insert(stdin_path.clone(), program);
|
|
287
|
+
load_order.push(stdin_path);
|
|
288
|
+
|
|
289
|
+
Ok(load_order
|
|
290
|
+
.into_iter()
|
|
291
|
+
.map(|p| {
|
|
292
|
+
let program = path_to_module.remove(&p).unwrap();
|
|
293
|
+
ResolvedModule { path: p, program }
|
|
294
|
+
})
|
|
295
|
+
.collect())
|
|
296
|
+
}
|
|
297
|
+
|
|
245
298
|
fn load_module_recursive(
|
|
246
299
|
module_path: &Path,
|
|
247
300
|
project_root: &Path,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Runtime linked into the `tish
|
|
1
|
+
//! Runtime linked into the `tish build --native-backend cranelift` executable.
|
|
2
2
|
//!
|
|
3
3
|
//! **`tish_run_chunk`** deserializes embedded bytecode and runs **`tishlang_vm`** — the same
|
|
4
4
|
//! execution engine as `tish run --backend vm`. The crate name is historical; this is not
|
|
@@ -1593,7 +1593,7 @@ impl Evaluator {
|
|
|
1593
1593
|
self.construct_value(&c, &arg_vals)
|
|
1594
1594
|
}
|
|
1595
1595
|
Expr::JsxElement { .. } | Expr::JsxFragment { .. } => Err(EvalError::Error(
|
|
1596
|
-
"JSX is not supported in the interpreter. Use 'tish
|
|
1596
|
+
"JSX is not supported in the interpreter. Use 'tish build --target js' to compile to JavaScript.".to_string(),
|
|
1597
1597
|
)),
|
|
1598
1598
|
Expr::NativeModuleLoad { spec, export_name, .. } => {
|
|
1599
1599
|
self.load_builtin_export(spec.as_ref(), export_name.as_ref())
|
package/justfile
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
# just run run hello.tish # runs via interpreter
|
|
10
10
|
# tish run hello.tish # if tish is in PATH
|
|
11
11
|
#
|
|
12
|
-
# 2. BUILD (
|
|
13
|
-
# just run
|
|
12
|
+
# 2. BUILD (native binary) - Create standalone executable:
|
|
13
|
+
# just run build hello.tish -o hello # builds native binary
|
|
14
14
|
# ./hello # run the standalone binary
|
|
15
15
|
#
|
|
16
16
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -72,17 +72,17 @@ run-regex *ARGS:
|
|
|
72
72
|
cargo run --release --no-default-features --features regex -- {{ARGS}}
|
|
73
73
|
|
|
74
74
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
75
|
-
#
|
|
75
|
+
# BUILD TISH PROGRAMS TO NATIVE BINARIES (just recipe name: compile → invokes `tish build`)
|
|
76
76
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
77
77
|
|
|
78
|
-
#
|
|
78
|
+
# Build a .tish file to native binary (with all features)
|
|
79
79
|
# Usage: just compile hello.tish hello
|
|
80
80
|
compile INPUT OUTPUT:
|
|
81
|
-
cargo run --release --features full --
|
|
81
|
+
cargo run --release --features full -- build {{INPUT}} -o {{OUTPUT}}
|
|
82
82
|
|
|
83
83
|
# Compile with secure mode (no dangerous features)
|
|
84
84
|
compile-secure INPUT OUTPUT:
|
|
85
|
-
cargo run --release --no-default-features --
|
|
85
|
+
cargo run --release --no-default-features -- build {{INPUT}} -o {{OUTPUT}}
|
|
86
86
|
|
|
87
87
|
# Build compiler WASM (for playground, REPL, try-it). Output: tish_compiler.js, tish_compiler_bg.wasm
|
|
88
88
|
# Requires: rustup target add wasm32-unknown-unknown, cargo install wasm-bindgen-cli
|
|
@@ -94,17 +94,17 @@ build-compiler-wasm OUT_DIR:
|
|
|
94
94
|
# Compile to WebAssembly (browser) - produces .wasm, .js, .html
|
|
95
95
|
# Requires: rustup target add wasm32-unknown-unknown, cargo install wasm-bindgen-cli
|
|
96
96
|
compile-wasm INPUT OUTPUT:
|
|
97
|
-
cargo run --release --
|
|
97
|
+
cargo run --release -- build {{INPUT}} -o {{OUTPUT}} --target wasm
|
|
98
98
|
|
|
99
99
|
# Compile to WebAssembly (Wasmtime/WASI) - single .wasm, run with: wasmtime OUTPUT.wasm
|
|
100
100
|
# Requires: rustup target add wasm32-wasip1, wasmtime (curl -sSf https://wasmtime.dev/install.sh | bash)
|
|
101
101
|
compile-wasi INPUT OUTPUT:
|
|
102
|
-
cargo run --release --
|
|
102
|
+
cargo run --release -- build {{INPUT}} -o {{OUTPUT}} --target wasi
|
|
103
103
|
|
|
104
104
|
# Compile with specific features
|
|
105
105
|
# Usage: just compile-with "http fs" hello.tish hello
|
|
106
106
|
compile-with FEATURES INPUT OUTPUT:
|
|
107
|
-
cargo run --release --no-default-features --features "{{FEATURES}}" --
|
|
107
|
+
cargo run --release --no-default-features --features "{{FEATURES}}" -- build {{INPUT}} -o {{OUTPUT}}
|
|
108
108
|
|
|
109
109
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
110
110
|
# TESTS
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tishlang/tish",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Tish - minimal TS/JS-compatible language. Run, REPL,
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Tish - minimal TS/JS-compatible language. Run, REPL, build to native or other targets.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
Binary file
|
package/platform/darwin-x64/tish
CHANGED
|
Binary file
|
|
Binary file
|
package/platform/linux-x64/tish
CHANGED
|
Binary file
|
|
Binary file
|