@tishlang/tish-format 1.0.12 → 1.0.13
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 +49 -0
- package/LICENSE +13 -0
- package/README.md +138 -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 +610 -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 +54 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +565 -0
- package/crates/tish/src/main.rs +781 -0
- package/crates/tish/src/repl_completion.rs +200 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -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/integration_test.rs +1095 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +620 -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 +20 -0
- package/crates/tish_builtins/src/array.rs +441 -0
- package/crates/tish_builtins/src/construct.rs +159 -0
- package/crates/tish_builtins/src/globals.rs +213 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/lib.rs +16 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +647 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +96 -0
- package/crates/tish_bytecode/src/compiler.rs +1760 -0
- package/crates/tish_bytecode/src/encoding.rs +100 -0
- package/crates/tish_bytecode/src/lib.rs +19 -0
- package/crates/tish_bytecode/src/opcode.rs +142 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +163 -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 +26 -0
- package/crates/tish_compile/src/codegen.rs +5332 -0
- package/crates/tish_compile/src/infer.rs +292 -0
- package/crates/tish_compile/src/lib.rs +164 -0
- package/crates/tish_compile/src/resolve.rs +1388 -0
- package/crates/tish_compile/src/types.rs +501 -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 +871 -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 +350 -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 +26 -0
- package/crates/tish_core/src/console_style.rs +160 -0
- package/crates/tish_core/src/json.rs +387 -0
- package/crates/tish_core/src/lib.rs +17 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +696 -0
- package/crates/tish_core/src/vmref.rs +178 -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 +117 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +25 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +45 -0
- package/crates/tish_eval/src/eval.rs +3717 -0
- package/crates/tish_eval/src/http.rs +188 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +399 -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 +318 -0
- package/crates/tish_eval/src/value_convert.rs +111 -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 +2101 -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 +716 -0
- package/crates/tish_lexer/src/token.rs +163 -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 +289 -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 +562 -0
- package/crates/tish_lsp/src/main.rs +1046 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +427 -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 +943 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +332 -0
- package/crates/tish_parser/src/parser.rs +2304 -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 +3561 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +96 -0
- package/crates/tish_runtime/src/http.rs +1298 -0
- package/crates/tish_runtime/src/http_fetch.rs +471 -0
- package/crates/tish_runtime/src/http_hyper.rs +418 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +1192 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +248 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +166 -0
- package/crates/tish_runtime/src/ws.rs +761 -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 +682 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +569 -0
- package/crates/tish_ui/src/runtime/mod.rs +180 -0
- package/crates/tish_vm/Cargo.toml +47 -0
- package/crates/tish_vm/src/lib.rs +39 -0
- package/crates/tish_vm/src/vm.rs +2192 -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 +424 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +413 -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 +263 -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 +268 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-fmt +0 -0
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
//! Shared build utilities for Tish.
|
|
2
|
+
//!
|
|
3
|
+
//! Provides workspace discovery, path resolution, and Cargo build orchestration
|
|
4
|
+
//! used by tishlang_wasm, tishlang_cranelift, tishlang_native, and the tish CLI.
|
|
5
|
+
|
|
6
|
+
use std::fs;
|
|
7
|
+
use std::path::{Path, PathBuf};
|
|
8
|
+
use std::process::Command;
|
|
9
|
+
use std::sync::Mutex;
|
|
10
|
+
|
|
11
|
+
/// Serialize nested `cargo build` calls that share the workspace `target/` dir (tests + `tish build`).
|
|
12
|
+
static NESTED_CARGO_MUTEX: Mutex<()> = Mutex::new(());
|
|
13
|
+
|
|
14
|
+
fn mold_available() -> bool {
|
|
15
|
+
Command::new("mold")
|
|
16
|
+
.arg("--version")
|
|
17
|
+
.output()
|
|
18
|
+
.map(|o| o.status.success())
|
|
19
|
+
.unwrap_or(false)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// True if `root` looks like the Tish language repo (has `crates/tish_runtime`).
|
|
23
|
+
///
|
|
24
|
+
/// Used so we do not treat unrelated workspaces (e.g. a parent `zectre-platform` repo) as Tish
|
|
25
|
+
/// when `CARGO_MANIFEST_DIR` or cwd points at another Rust workspace.
|
|
26
|
+
fn is_tish_workspace_root(root: &Path) -> bool {
|
|
27
|
+
root.join("crates").join("tish_runtime").is_dir()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// True if `line` (trimmed) opens a Cargo.toml table whose body may contain path dependencies.
|
|
31
|
+
fn cargo_section_may_contain_path_deps(header: &str) -> bool {
|
|
32
|
+
let h = header.trim();
|
|
33
|
+
if h == "dependencies"
|
|
34
|
+
|| h == "dev-dependencies"
|
|
35
|
+
|| h == "build-dependencies"
|
|
36
|
+
|| h == "workspace.dependencies"
|
|
37
|
+
{
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
h.starts_with("dependencies.")
|
|
41
|
+
|| h.starts_with("dev-dependencies.")
|
|
42
|
+
|| h.starts_with("build-dependencies.")
|
|
43
|
+
|| h.starts_with("workspace.dependencies.")
|
|
44
|
+
|| h.starts_with("patch.")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Collect `path = "..."` / `path = '...'` strings from lines in dependency-related sections.
|
|
48
|
+
fn path_values_from_cargo_toml(content: &str) -> Vec<String> {
|
|
49
|
+
let mut out = Vec::new();
|
|
50
|
+
let mut in_section = false;
|
|
51
|
+
for line in content.lines() {
|
|
52
|
+
let trimmed = line.trim();
|
|
53
|
+
if let Some(rest) = trimmed.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
|
|
54
|
+
in_section = cargo_section_may_contain_path_deps(rest);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if !in_section {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
extract_path_assignments_from_line(trimmed, &mut out);
|
|
61
|
+
}
|
|
62
|
+
out
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn extract_path_assignments_from_line(line: &str, out: &mut Vec<String>) {
|
|
66
|
+
let mut rest = line;
|
|
67
|
+
while let Some(idx) = rest.find("path") {
|
|
68
|
+
let after = rest[idx + 4..].trim_start();
|
|
69
|
+
let after = match after.strip_prefix('=') {
|
|
70
|
+
Some(a) => a.trim_start(),
|
|
71
|
+
None => {
|
|
72
|
+
rest = &rest[idx + 4..];
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
let quote = match after.chars().next() {
|
|
77
|
+
Some('"') => '"',
|
|
78
|
+
Some('\'') => '\'',
|
|
79
|
+
_ => {
|
|
80
|
+
rest = &rest[idx + 4..];
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
let after = &after[quote.len_utf8()..];
|
|
85
|
+
let end = after.find(quote);
|
|
86
|
+
let Some(end) = end else {
|
|
87
|
+
rest = &rest[idx + 4..];
|
|
88
|
+
continue;
|
|
89
|
+
};
|
|
90
|
+
out.push(after[..end].to_string());
|
|
91
|
+
rest = &after[end + quote.len_utf8()..];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Starting from a filesystem path (crate dir or file), walk ancestors for `crates/tish_runtime`.
|
|
96
|
+
fn tish_root_from_path_hint(start: &Path) -> Option<PathBuf> {
|
|
97
|
+
let mut dir = if start.is_file() {
|
|
98
|
+
start.parent()?.to_path_buf()
|
|
99
|
+
} else {
|
|
100
|
+
start.to_path_buf()
|
|
101
|
+
};
|
|
102
|
+
dir = fs::canonicalize(&dir).unwrap_or(dir);
|
|
103
|
+
let mut cur = dir.as_path();
|
|
104
|
+
for _ in 0..32 {
|
|
105
|
+
if is_tish_workspace_root(cur) {
|
|
106
|
+
return Some(cur.to_path_buf());
|
|
107
|
+
}
|
|
108
|
+
cur = cur.parent()?;
|
|
109
|
+
}
|
|
110
|
+
None
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Scan `dir/Cargo.toml` for path dependencies; if any resolves inside a Tish workspace, return that root.
|
|
114
|
+
fn tish_root_from_cargo_manifest_dir(dir: &Path) -> Option<PathBuf> {
|
|
115
|
+
let cargo_toml = dir.join("Cargo.toml");
|
|
116
|
+
if !cargo_toml.is_file() {
|
|
117
|
+
return None;
|
|
118
|
+
}
|
|
119
|
+
let content = fs::read_to_string(&cargo_toml).ok()?;
|
|
120
|
+
let base = dir;
|
|
121
|
+
for rel in path_values_from_cargo_toml(&content) {
|
|
122
|
+
let joined = base.join(&rel);
|
|
123
|
+
let resolved = match joined.canonicalize() {
|
|
124
|
+
Ok(p) => p,
|
|
125
|
+
Err(_) => continue,
|
|
126
|
+
};
|
|
127
|
+
if let Some(root) = tish_root_from_path_hint(&resolved) {
|
|
128
|
+
return Some(root);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
None
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Walk from `start` upward; at each directory try [`tish_root_from_cargo_manifest_dir`].
|
|
135
|
+
fn tish_root_from_project_cargo_files(mut start: PathBuf) -> Option<PathBuf> {
|
|
136
|
+
for _ in 0..32 {
|
|
137
|
+
if let Some(root) = tish_root_from_cargo_manifest_dir(&start) {
|
|
138
|
+
return Some(root);
|
|
139
|
+
}
|
|
140
|
+
if !start.pop() {
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
None
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Find the Tish workspace root using multiple strategies.
|
|
148
|
+
///
|
|
149
|
+
/// Returns the directory containing the workspace Cargo.toml (with [workspace]).
|
|
150
|
+
/// Used when building native binaries, WASM, or locating runtime crates.
|
|
151
|
+
pub fn find_workspace_root() -> Result<PathBuf, String> {
|
|
152
|
+
// Strategy 0: explicit checkout (e.g. tish-hub `npm run dev` / native build from a JS-only tree)
|
|
153
|
+
if let Ok(root) = std::env::var("TISHLANG_WORKSPACE") {
|
|
154
|
+
let trimmed = root.trim();
|
|
155
|
+
if !trimmed.is_empty() {
|
|
156
|
+
let p = PathBuf::from(trimmed);
|
|
157
|
+
if p.is_dir() && is_tish_workspace_root(&p) {
|
|
158
|
+
return p.canonicalize().map_err(|e| {
|
|
159
|
+
format!(
|
|
160
|
+
"TISHLANG_WORKSPACE is set but not a usable directory: {}",
|
|
161
|
+
e
|
|
162
|
+
)
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// Non-empty but invalid: fall through (sibling `tish/` discovery may still apply).
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Strategy 0b: monorepo layout `…/<parent>/tish-hub` (cwd) next to `…/<parent>/tish` (language repo).
|
|
170
|
+
// Works without `TISHLANG_WORKSPACE` so older `tish` binaries still find the compiler tree.
|
|
171
|
+
if let Ok(mut dir) = std::env::current_dir() {
|
|
172
|
+
for _ in 0..24 {
|
|
173
|
+
let candidate = dir.join("tish");
|
|
174
|
+
if is_tish_workspace_root(&candidate) {
|
|
175
|
+
return candidate.canonicalize().map_err(|e| {
|
|
176
|
+
format!(
|
|
177
|
+
"Cannot canonicalize Tish workspace {}: {}",
|
|
178
|
+
candidate.display(),
|
|
179
|
+
e
|
|
180
|
+
)
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if !dir.pop() {
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Strategy 1: CARGO_MANIFEST_DIR (works during cargo build/run from workspace)
|
|
190
|
+
if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
|
|
191
|
+
let path = PathBuf::from(&manifest_dir);
|
|
192
|
+
// For crates/tish_*, workspace root is parent.parent()
|
|
193
|
+
if let Some(root) = path.parent().and_then(|p| p.parent()) {
|
|
194
|
+
let root_buf = root.to_path_buf();
|
|
195
|
+
if root_buf.join("Cargo.toml").exists() && is_tish_workspace_root(&root_buf) {
|
|
196
|
+
return Ok(root_buf);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Consumer workspace: manifest is the app crate; path deps point at Tish checkout.
|
|
200
|
+
if let Some(root) = tish_root_from_project_cargo_files(path.clone()) {
|
|
201
|
+
return Ok(root);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Strategy 2: Walk from current executable (e.g. target/debug/tish)
|
|
206
|
+
if let Ok(exe) = std::env::current_exe() {
|
|
207
|
+
if let Some(mut current) = exe.parent() {
|
|
208
|
+
for _ in 0..15 {
|
|
209
|
+
let crates_dir = current.join("crates");
|
|
210
|
+
if crates_dir.join("tish_runtime").exists()
|
|
211
|
+
|| crates_dir.join("tish_cranelift_runtime").exists()
|
|
212
|
+
{
|
|
213
|
+
return Ok(current.to_path_buf());
|
|
214
|
+
}
|
|
215
|
+
if let Some(p) = current.parent() {
|
|
216
|
+
current = p;
|
|
217
|
+
} else {
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Strategy 3: Walk from current working directory (path deps on a consumer crate)
|
|
225
|
+
if let Ok(cwd) = std::env::current_dir() {
|
|
226
|
+
if let Some(root) = tish_root_from_project_cargo_files(cwd.clone()) {
|
|
227
|
+
return Ok(root);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Strategy 3b: `node_modules/@tishlang/tish` from cwd or any ancestor (package.json-only apps)
|
|
232
|
+
if let Ok(mut dir) = std::env::current_dir() {
|
|
233
|
+
for _ in 0..32 {
|
|
234
|
+
let npm_pkg = dir.join("node_modules").join("@tishlang").join("tish");
|
|
235
|
+
if is_tish_workspace_root(&npm_pkg) {
|
|
236
|
+
return Ok(npm_pkg);
|
|
237
|
+
}
|
|
238
|
+
if !dir.pop() {
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Strategy 4: Walk from current working directory
|
|
245
|
+
if let Ok(mut current) = std::env::current_dir() {
|
|
246
|
+
for _ in 0..15 {
|
|
247
|
+
let cargo_toml = current.join("Cargo.toml");
|
|
248
|
+
if cargo_toml.exists() {
|
|
249
|
+
if let Ok(content) = std::fs::read_to_string(&cargo_toml) {
|
|
250
|
+
if content.contains("[workspace]") && is_tish_workspace_root(¤t) {
|
|
251
|
+
return Ok(current);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Fallback: check for crates dir with known crates
|
|
255
|
+
let crates_dir = current.join("crates");
|
|
256
|
+
if crates_dir.join("tish_runtime").exists() {
|
|
257
|
+
return Ok(current);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if !current.pop() {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
Err("Cannot find Tish workspace root. Run from workspace root or use cargo run.".to_string())
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/// Path to `crates/tish_runtime` inside a locally installed `@tishlang/tish` npm package.
|
|
270
|
+
pub fn npm_package_runtime_path(project_root: &Path) -> Option<PathBuf> {
|
|
271
|
+
let p = project_root
|
|
272
|
+
.join("node_modules")
|
|
273
|
+
.join("@tishlang")
|
|
274
|
+
.join("tish")
|
|
275
|
+
.join("crates")
|
|
276
|
+
.join("tish_runtime");
|
|
277
|
+
if p.is_dir() {
|
|
278
|
+
Some(p)
|
|
279
|
+
} else {
|
|
280
|
+
None
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/// Find the path to the tishlang_runtime crate.
|
|
285
|
+
///
|
|
286
|
+
/// Returns a canonical path string suitable for Cargo.toml path dependencies.
|
|
287
|
+
pub fn find_runtime_path() -> Result<String, String> {
|
|
288
|
+
let workspace = find_workspace_root()?;
|
|
289
|
+
let runtime = workspace.join("crates").join("tish_runtime");
|
|
290
|
+
if !runtime.exists() {
|
|
291
|
+
return Err("tishlang_runtime crate not found".to_string());
|
|
292
|
+
}
|
|
293
|
+
runtime
|
|
294
|
+
.canonicalize()
|
|
295
|
+
.map_err(|e| format!("Cannot canonicalize tishlang_runtime: {}", e))
|
|
296
|
+
.map(|p| p.display().to_string().replace('\\', "/"))
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// Resolve `tishlang_runtime` for a Cargo build, preferring the npm install under `project_root`.
|
|
300
|
+
///
|
|
301
|
+
/// When a Tish app lives next to a checkout of the language repo (e.g. `…/tish/tish-cargo-example`),
|
|
302
|
+
/// [`find_workspace_root`] can return the checkout while `rustDependencies` point at
|
|
303
|
+
/// `node_modules/@tishlang/tish/crates/tish_core`. Using the npm tree for **both** runtime and shim
|
|
304
|
+
/// avoids Cargo lockfile "package collision" for the same crate name/version at two paths.
|
|
305
|
+
pub fn find_runtime_path_for_project(project_root: Option<&Path>) -> Result<String, String> {
|
|
306
|
+
if let Some(root) = project_root {
|
|
307
|
+
if let Some(rt) = npm_package_runtime_path(root) {
|
|
308
|
+
return rt
|
|
309
|
+
.canonicalize()
|
|
310
|
+
.map_err(|e| format!("Cannot canonicalize tishlang_runtime (npm): {}", e))
|
|
311
|
+
.map(|p| p.display().to_string().replace('\\', "/"));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
find_runtime_path()
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/// Crate package name -> directory name (directories kept as tish_* for historical reasons).
|
|
318
|
+
const CRATE_NAME_TO_DIR: &[(&str, &str)] = &[
|
|
319
|
+
("tishlang_runtime", "tish_runtime"),
|
|
320
|
+
("tishlang_cranelift_runtime", "tish_cranelift_runtime"),
|
|
321
|
+
("tishlang_wasm_runtime", "tish_wasm_runtime"),
|
|
322
|
+
]; // directory names kept as tish_* for historical reasons
|
|
323
|
+
|
|
324
|
+
/// Find the path to a crate within the workspace by name.
|
|
325
|
+
///
|
|
326
|
+
/// e.g. `find_crate_path("tishlang_cranelift_runtime")` returns the path to crates/tish_cranelift_runtime.
|
|
327
|
+
pub fn find_crate_path(crate_name: &str) -> Result<PathBuf, String> {
|
|
328
|
+
let workspace = find_workspace_root()?;
|
|
329
|
+
let dir_name = CRATE_NAME_TO_DIR
|
|
330
|
+
.iter()
|
|
331
|
+
.find(|(name, _)| *name == crate_name)
|
|
332
|
+
.map(|(_, dir)| *dir)
|
|
333
|
+
.unwrap_or(crate_name);
|
|
334
|
+
let crate_path = workspace.join("crates").join(dir_name);
|
|
335
|
+
if !crate_path.exists() {
|
|
336
|
+
return Err(format!("Crate {} not found", crate_name));
|
|
337
|
+
}
|
|
338
|
+
Ok(crate_path)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/// Sanitize a user-chosen output stem for Cargo `[lib]` / `[[bin]]` target names.
|
|
342
|
+
pub fn cargo_target_name(stem: &str) -> String {
|
|
343
|
+
stem.replace('-', "_")
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/// Create a temp build directory with src subdir.
|
|
347
|
+
pub fn create_build_dir(prefix: &str, out_name: &str) -> Result<PathBuf, String> {
|
|
348
|
+
let build_dir =
|
|
349
|
+
std::env::temp_dir()
|
|
350
|
+
.join(prefix)
|
|
351
|
+
.join(format!("{}_{}", out_name, std::process::id()));
|
|
352
|
+
fs::create_dir_all(&build_dir).map_err(|e| format!("Cannot create build dir: {}", e))?;
|
|
353
|
+
fs::create_dir_all(build_dir.join("src"))
|
|
354
|
+
.map_err(|e| format!("Cannot create src dir: {}", e))?;
|
|
355
|
+
Ok(build_dir)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/// `PROTOC` for prost-build in transitive crates (e.g. lance-encoding) during nested `cargo build`.
|
|
359
|
+
/// Respects an existing `PROTOC`, then `protoc` on `PATH`, then `protoc-bin-vendored`.
|
|
360
|
+
fn protoc_for_nested_cargo() -> Option<PathBuf> {
|
|
361
|
+
// Empty or non-file PROTOC must not short-circuit: prost-build treats "" like a missing binary.
|
|
362
|
+
if let Some(v) = std::env::var_os("PROTOC") {
|
|
363
|
+
if !v.is_empty() {
|
|
364
|
+
let p = PathBuf::from(&v);
|
|
365
|
+
if p.is_file() {
|
|
366
|
+
return None;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// Prefer vendored protoc over PATH: a broken or non-executable `protoc` on PATH still passes `is_file()`.
|
|
371
|
+
if let Ok(p) = protoc_bin_vendored::protoc_bin_path() {
|
|
372
|
+
return Some(p);
|
|
373
|
+
}
|
|
374
|
+
let ext = if cfg!(windows) { ".exe" } else { "" };
|
|
375
|
+
let name = format!("protoc{}", ext);
|
|
376
|
+
if let Some(paths) = std::env::var_os("PATH") {
|
|
377
|
+
for dir in std::env::split_paths(&paths) {
|
|
378
|
+
let candidate = dir.join(&name);
|
|
379
|
+
if candidate.is_file() {
|
|
380
|
+
return Some(candidate);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
None
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/// Run cargo build in the given directory.
|
|
388
|
+
/// If `cross_target` is Some, passes `--target` and skips `-C target-cpu=native`.
|
|
389
|
+
pub fn run_cargo_build(
|
|
390
|
+
build_dir: &Path,
|
|
391
|
+
target_dir: Option<&Path>,
|
|
392
|
+
cross_target: Option<&str>,
|
|
393
|
+
) -> Result<(), String> {
|
|
394
|
+
let _nested_guard = NESTED_CARGO_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
|
|
395
|
+
|
|
396
|
+
let target_dir = target_dir
|
|
397
|
+
.map(|p| p.to_path_buf())
|
|
398
|
+
.unwrap_or_else(|| build_dir.join("target"));
|
|
399
|
+
let fast_native = std::env::var("TISH_FAST_NATIVE_BUILD").as_deref() == Ok("1");
|
|
400
|
+
|
|
401
|
+
let mut merged_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
|
|
402
|
+
if cross_target.is_some() {
|
|
403
|
+
// Cross-compiling (e.g. iOS): do not use host target-cpu.
|
|
404
|
+
} else if fast_native {
|
|
405
|
+
if cfg!(target_os = "linux")
|
|
406
|
+
&& mold_available()
|
|
407
|
+
&& !merged_rustflags.contains("fuse-ld=mold")
|
|
408
|
+
{
|
|
409
|
+
merged_rustflags = format!("{} -C link-arg=-fuse-ld=mold", merged_rustflags.trim());
|
|
410
|
+
merged_rustflags = merged_rustflags.trim().to_string();
|
|
411
|
+
}
|
|
412
|
+
} else if merged_rustflags.is_empty() {
|
|
413
|
+
merged_rustflags = "-C target-cpu=native".to_string();
|
|
414
|
+
} else if !merged_rustflags.contains("target-cpu") {
|
|
415
|
+
merged_rustflags = format!("{} -C target-cpu=native", merged_rustflags);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
let mut cmd = Command::new("cargo");
|
|
419
|
+
cmd.args(["build", "--release", "--target-dir"])
|
|
420
|
+
.arg(&target_dir)
|
|
421
|
+
.current_dir(build_dir)
|
|
422
|
+
.env_remove("CARGO_TARGET_DIR")
|
|
423
|
+
.env_remove("RUSTC_WRAPPER")
|
|
424
|
+
.env_remove("RUSTC_WORKSPACE_WRAPPER")
|
|
425
|
+
.env_remove("CARGO_BUILD_RUSTC_WRAPPER")
|
|
426
|
+
.env("CARGO_TERM_PROGRESS", "always")
|
|
427
|
+
.env("RUSTFLAGS", &merged_rustflags);
|
|
428
|
+
if let Some(triple) = cross_target {
|
|
429
|
+
cmd.arg("--target").arg(triple);
|
|
430
|
+
}
|
|
431
|
+
if fast_native {
|
|
432
|
+
cmd.env("CARGO_INCREMENTAL", "1");
|
|
433
|
+
} else {
|
|
434
|
+
cmd.env_remove("CARGO_INCREMENTAL");
|
|
435
|
+
}
|
|
436
|
+
if let Some(protoc) = protoc_for_nested_cargo() {
|
|
437
|
+
cmd.env("PROTOC", protoc);
|
|
438
|
+
}
|
|
439
|
+
let output = cmd
|
|
440
|
+
.output()
|
|
441
|
+
.map_err(|e| format!("Failed to run cargo: {}", e))?;
|
|
442
|
+
|
|
443
|
+
if !output.status.success() {
|
|
444
|
+
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
445
|
+
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
446
|
+
return Err(format!(
|
|
447
|
+
"Compilation failed.\nstdout:\n{}\nstderr:\n{}",
|
|
448
|
+
stdout, stderr
|
|
449
|
+
));
|
|
450
|
+
}
|
|
451
|
+
Ok(())
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
#[cfg(test)]
|
|
455
|
+
mod protoc_tests {
|
|
456
|
+
use super::*;
|
|
457
|
+
|
|
458
|
+
#[test]
|
|
459
|
+
fn cargo_target_name_replaces_hyphens() {
|
|
460
|
+
assert_eq!(cargo_target_name("hello-ios"), "hello_ios");
|
|
461
|
+
assert_eq!(cargo_target_name("tish_out"), "tish_out");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
#[test]
|
|
465
|
+
fn protoc_for_nested_cargo_without_env_uses_vendored_or_path() {
|
|
466
|
+
let _lock = std::sync::Mutex::new(());
|
|
467
|
+
let _guard = _lock.lock().unwrap();
|
|
468
|
+
std::env::remove_var("PROTOC");
|
|
469
|
+
let p = protoc_for_nested_cargo().expect("expected vendored or PATH protoc");
|
|
470
|
+
assert!(p.exists(), "resolved protoc should exist: {}", p.display());
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/// Find the built static library in target/release (or target/$TRIPLE/release).
|
|
475
|
+
pub fn find_release_staticlib(binary_dir: &Path, lib_name: &str) -> Result<PathBuf, String> {
|
|
476
|
+
let path = binary_dir.join(format!("lib{lib_name}.a"));
|
|
477
|
+
if path.exists() {
|
|
478
|
+
Ok(path)
|
|
479
|
+
} else {
|
|
480
|
+
Err(format!("Static library not found at {}", path.display()))
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/// Find the built binary in target/release.
|
|
485
|
+
pub fn find_release_binary(binary_dir: &Path, bin_name: &str) -> Result<PathBuf, String> {
|
|
486
|
+
let binary_no_ext = binary_dir.join(bin_name);
|
|
487
|
+
let binary_exe = binary_dir.join(format!("{}.exe", bin_name));
|
|
488
|
+
if binary_no_ext.exists() {
|
|
489
|
+
Ok(binary_no_ext)
|
|
490
|
+
} else if binary_exe.exists() {
|
|
491
|
+
Ok(binary_exe)
|
|
492
|
+
} else {
|
|
493
|
+
Err(format!(
|
|
494
|
+
"Binary not found at {} or {}",
|
|
495
|
+
binary_no_ext.display(),
|
|
496
|
+
binary_exe.display()
|
|
497
|
+
))
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/// Resolve the output path for the binary (handles extension, directory).
|
|
502
|
+
pub fn resolve_output_path(output_path: &Path, bin_name: &str) -> PathBuf {
|
|
503
|
+
if output_path.extension().is_none()
|
|
504
|
+
|| output_path.extension() == Some(std::ffi::OsStr::new(""))
|
|
505
|
+
{
|
|
506
|
+
let mut p = output_path.to_path_buf();
|
|
507
|
+
if cfg!(windows) {
|
|
508
|
+
p.set_extension("exe");
|
|
509
|
+
}
|
|
510
|
+
return p;
|
|
511
|
+
}
|
|
512
|
+
if output_path.to_string_lossy().ends_with('/') || output_path.is_dir() {
|
|
513
|
+
let dir = if output_path.is_dir() {
|
|
514
|
+
output_path.to_path_buf()
|
|
515
|
+
} else {
|
|
516
|
+
output_path.parent().unwrap_or(Path::new(".")).to_path_buf()
|
|
517
|
+
};
|
|
518
|
+
return dir.join(if cfg!(windows) {
|
|
519
|
+
format!("{}.exe", bin_name)
|
|
520
|
+
} else {
|
|
521
|
+
bin_name.to_string()
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
output_path.to_path_buf()
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/// Copy the built binary to the output path.
|
|
528
|
+
pub fn copy_binary_to_output(binary: &Path, output_path: &Path) -> Result<(), String> {
|
|
529
|
+
if let Some(parent) = output_path.parent() {
|
|
530
|
+
fs::create_dir_all(parent).map_err(|e| format!("Cannot create output dir: {}", e))?;
|
|
531
|
+
}
|
|
532
|
+
fs::copy(binary, output_path)
|
|
533
|
+
.map_err(|e| format!("Cannot copy to {}: {}", output_path.display(), e))?;
|
|
534
|
+
Ok(())
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
#[cfg(test)]
|
|
538
|
+
mod tests {
|
|
539
|
+
use super::*;
|
|
540
|
+
|
|
541
|
+
#[test]
|
|
542
|
+
fn path_values_dependencies_section_only() {
|
|
543
|
+
let toml = r#"
|
|
544
|
+
[package]
|
|
545
|
+
name = "app"
|
|
546
|
+
path = "ignored-outside-deps"
|
|
547
|
+
|
|
548
|
+
[dependencies]
|
|
549
|
+
tishlang_runtime = { path = "../tish/crates/tish_runtime" }
|
|
550
|
+
|
|
551
|
+
[metadata]
|
|
552
|
+
path = "also-ignored"
|
|
553
|
+
"#;
|
|
554
|
+
let paths = path_values_from_cargo_toml(toml);
|
|
555
|
+
assert_eq!(paths, vec!["../tish/crates/tish_runtime"]);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
#[test]
|
|
559
|
+
fn path_values_workspace_dependencies() {
|
|
560
|
+
let toml = r#"
|
|
561
|
+
[workspace.dependencies]
|
|
562
|
+
tishlang_runtime = { path = "../../tish/tish/crates/tish_runtime" }
|
|
563
|
+
"#;
|
|
564
|
+
let paths = path_values_from_cargo_toml(toml);
|
|
565
|
+
assert_eq!(paths, vec!["../../tish/tish/crates/tish_runtime"]);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
#[test]
|
|
569
|
+
fn path_values_patch_section() {
|
|
570
|
+
let toml = r#"
|
|
571
|
+
[patch.crates-io]
|
|
572
|
+
tishlang_runtime = { path = "../vendor/tish_runtime" }
|
|
573
|
+
"#;
|
|
574
|
+
let paths = path_values_from_cargo_toml(toml);
|
|
575
|
+
assert_eq!(paths, vec!["../vendor/tish_runtime"]);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "tishlang_builtins"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "Shared builtin implementations for Tish (array, string, object, math)"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
license-file = { workspace = true }
|
|
9
|
+
repository = { workspace = true }
|
|
10
|
+
[dependencies]
|
|
11
|
+
rand = "0.10.1"
|
|
12
|
+
tishlang_core = { path = "../tish_core", version = ">=0.1" }
|
|
13
|
+
|
|
14
|
+
[features]
|
|
15
|
+
default = []
|
|
16
|
+
regex = ["tishlang_core/regex"]
|
|
17
|
+
# Propagate the `send-values` feature from `tishlang_core` so that closures
|
|
18
|
+
# handed to us via `NativeFn` pick up the `Send + Sync` bound automatically
|
|
19
|
+
# when multi-threaded `Value`s are enabled upstream.
|
|
20
|
+
send-values = ["tishlang_core/send-values"]
|