@tishlang/tish 1.6.0 → 1.8.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.
Files changed (113) hide show
  1. package/Cargo.toml +2 -0
  2. package/README.md +2 -0
  3. package/bin/tish +0 -0
  4. package/crates/js_to_tish/src/error.rs +2 -8
  5. package/crates/js_to_tish/src/transform/expr.rs +128 -137
  6. package/crates/js_to_tish/src/transform/stmt.rs +62 -32
  7. package/crates/tish/Cargo.toml +15 -5
  8. package/crates/tish/src/cargo_native_registry.rs +29 -0
  9. package/crates/tish/src/cli_help.rs +92 -39
  10. package/crates/tish/src/main.rs +172 -86
  11. package/crates/tish/src/repl_completion.rs +3 -3
  12. package/crates/tish/tests/cargo_example_compile.rs +4 -2
  13. package/crates/tish/tests/integration_test.rs +216 -54
  14. package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
  15. package/crates/tish/tests/shortcircuit.rs +20 -5
  16. package/crates/tish_ast/src/ast.rs +92 -23
  17. package/crates/tish_build_utils/Cargo.toml +4 -0
  18. package/crates/tish_build_utils/src/lib.rs +136 -8
  19. package/crates/tish_builtins/Cargo.toml +5 -1
  20. package/crates/tish_builtins/src/array.rs +65 -33
  21. package/crates/tish_builtins/src/construct.rs +34 -39
  22. package/crates/tish_builtins/src/globals.rs +42 -26
  23. package/crates/tish_builtins/src/helpers.rs +2 -1
  24. package/crates/tish_builtins/src/lib.rs +5 -5
  25. package/crates/tish_builtins/src/math.rs +5 -3
  26. package/crates/tish_builtins/src/object.rs +3 -2
  27. package/crates/tish_builtins/src/string.rs +144 -22
  28. package/crates/tish_bytecode/src/chunk.rs +0 -1
  29. package/crates/tish_bytecode/src/compiler.rs +173 -71
  30. package/crates/tish_bytecode/src/opcode.rs +24 -6
  31. package/crates/tish_bytecode/src/peephole.rs +2 -2
  32. package/crates/tish_compile/Cargo.toml +1 -0
  33. package/crates/tish_compile/src/codegen.rs +1621 -453
  34. package/crates/tish_compile/src/infer.rs +75 -19
  35. package/crates/tish_compile/src/lib.rs +19 -8
  36. package/crates/tish_compile/src/resolve.rs +278 -137
  37. package/crates/tish_compile/src/types.rs +184 -24
  38. package/crates/tish_compile_js/Cargo.toml +1 -0
  39. package/crates/tish_compile_js/src/codegen.rs +181 -37
  40. package/crates/tish_compile_js/src/lib.rs +3 -1
  41. package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
  42. package/crates/tish_compiler_wasm/src/lib.rs +16 -13
  43. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
  44. package/crates/tish_core/Cargo.toml +8 -0
  45. package/crates/tish_core/src/json.rs +107 -56
  46. package/crates/tish_core/src/lib.rs +4 -2
  47. package/crates/tish_core/src/macros.rs +5 -5
  48. package/crates/tish_core/src/uri.rs +9 -6
  49. package/crates/tish_core/src/value.rs +145 -43
  50. package/crates/tish_core/src/vmref.rs +178 -0
  51. package/crates/tish_cranelift/src/link.rs +6 -9
  52. package/crates/tish_cranelift/src/lower.rs +14 -8
  53. package/crates/tish_eval/Cargo.toml +17 -2
  54. package/crates/tish_eval/src/eval.rs +474 -165
  55. package/crates/tish_eval/src/http.rs +61 -0
  56. package/crates/tish_eval/src/lib.rs +12 -8
  57. package/crates/tish_eval/src/natives.rs +136 -38
  58. package/crates/tish_eval/src/promise.rs +14 -8
  59. package/crates/tish_eval/src/timers.rs +28 -19
  60. package/crates/tish_eval/src/value.rs +17 -6
  61. package/crates/tish_eval/src/value_convert.rs +13 -5
  62. package/crates/tish_fmt/src/lib.rs +149 -43
  63. package/crates/tish_lexer/src/lib.rs +232 -63
  64. package/crates/tish_lexer/src/token.rs +10 -6
  65. package/crates/tish_llvm/src/lib.rs +17 -8
  66. package/crates/tish_lsp/Cargo.toml +4 -1
  67. package/crates/tish_lsp/README.md +1 -1
  68. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  69. package/crates/tish_lsp/src/import_goto.rs +549 -0
  70. package/crates/tish_lsp/src/main.rs +504 -106
  71. package/crates/tish_native/src/build.rs +4 -8
  72. package/crates/tish_native/src/lib.rs +54 -21
  73. package/crates/tish_opt/src/lib.rs +84 -52
  74. package/crates/tish_parser/src/lib.rs +45 -13
  75. package/crates/tish_parser/src/parser.rs +505 -130
  76. package/crates/tish_resolve/Cargo.toml +13 -0
  77. package/crates/tish_resolve/src/lib.rs +3436 -0
  78. package/crates/tish_resolve/src/pos.rs +133 -0
  79. package/crates/tish_runtime/Cargo.toml +68 -3
  80. package/crates/tish_runtime/src/http.rs +1136 -145
  81. package/crates/tish_runtime/src/http_fetch.rs +38 -27
  82. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  83. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  84. package/crates/tish_runtime/src/lib.rs +375 -189
  85. package/crates/tish_runtime/src/promise.rs +199 -40
  86. package/crates/tish_runtime/src/promise_io.rs +2 -1
  87. package/crates/tish_runtime/src/timers.rs +37 -1
  88. package/crates/tish_runtime/src/ws.rs +65 -42
  89. package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
  90. package/crates/tish_ui/src/jsx.rs +317 -27
  91. package/crates/tish_ui/src/lib.rs +5 -2
  92. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  93. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  94. package/crates/tish_vm/Cargo.toml +15 -5
  95. package/crates/tish_vm/src/vm.rs +725 -281
  96. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
  97. package/crates/tish_wasm/src/lib.rs +55 -42
  98. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  99. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  100. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  101. package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
  102. package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
  103. package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
  104. package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
  105. package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
  106. package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
  107. package/justfile +8 -0
  108. package/package.json +1 -1
  109. package/platform/darwin-arm64/tish +0 -0
  110. package/platform/darwin-x64/tish +0 -0
  111. package/platform/linux-arm64/tish +0 -0
  112. package/platform/linux-x64/tish +0 -0
  113. package/platform/win32-x64/tish.exe +0 -0
@@ -0,0 +1,350 @@
1
+ //! Generate Rust glue for Tish `cargo:` imports by **reading the dependency crate’s source**
2
+ //! (via `cargo metadata` + `syn`), classifying each `pub fn` by **signature shape**, then emitting
3
+ //! `pub fn …(args: &[Value]) -> Value` shims.
4
+ //!
5
+ //! This avoids a fixed catalog of crate names: behavior follows **discovered** `pub fn` signatures
6
+ //! (e.g. `Serialize` + `Result<String, _>`, or `&str` + `Deserialize` + `Result`).
7
+ //!
8
+ //! **Standalone use:** the CLI does not link `tishlang_runtime` (it only emits source). For generated
9
+ //! crates, pass **`--tishlang-runtime-version`** so `Cargo.toml` uses a crates.io semver requirement
10
+ //! instead of a `path` into the Tish repo (no workspace checkout required to **build** the glue crate).
11
+ //!
12
+ //! **Project mode (default):** with **`--project-root`** and **no** `--dependency`, the tool reads
13
+ //! **`package.json` → `tish.rustDependencies`**, picks the path-based glue crate, and reads the
14
+ //! **upstream** crate + semver from that glue crate’s **`Cargo.toml`** `[dependencies]` if it
15
+ //! exists; otherwise from the **project root** **`Cargo.toml`** (`[dependencies]` and
16
+ //! **`[dev-dependencies]`**), skipping **`tishlang_core`** / **`tishlang_runtime`** and path-only entries.
17
+
18
+ mod classify;
19
+ mod discover;
20
+ pub mod infer;
21
+ mod metadata;
22
+
23
+ pub use classify::SignatureClass;
24
+ pub use discover::rust_public_fn_location;
25
+ pub use metadata::{resolve_dependency_from_manifest, resolve_registry_dependency, ResolvedDependency};
26
+
27
+ use std::fs;
28
+ use std::io;
29
+ use std::path::Path;
30
+
31
+ use classify::classify_public_fn;
32
+
33
+ /// How the generated crate depends on `tishlang_runtime`.
34
+ #[derive(Debug, Clone)]
35
+ pub enum TishlangRuntimeDep {
36
+ /// `tishlang_runtime = { path = "..." }` (relative to `--out-dir`).
37
+ Path(String),
38
+ /// `tishlang_runtime = "1.0"` style crates.io requirement (standalone builds; publish `tishlang_runtime` first).
39
+ Version(String),
40
+ }
41
+
42
+ /// Configuration for a generated wrapper crate on disk.
43
+ #[derive(Debug, Clone)]
44
+ pub struct BindgenConfig {
45
+ /// Cargo `[package].name` of the **generated** crate (must match `cargo:` / `rustDependencies`).
46
+ pub output_crate_name: String,
47
+ pub tishlang_runtime: TishlangRuntimeDep,
48
+ pub out_dir: std::path::PathBuf,
49
+ /// Registry dependency name (e.g. `serde_json`) and semver req for the probe + generated dep.
50
+ pub dependency_name: String,
51
+ pub dependency_version_req: String,
52
+ /// Tish export names to wrap (must match a `pub fn` in the dependency).
53
+ pub exports: Vec<String>,
54
+ }
55
+
56
+ /// Full generation: resolve dependency, scan sources, classify, write crate.
57
+ pub fn generate_from_registry_dependency(cfg: &BindgenConfig) -> Result<(), String> {
58
+ let resolved = resolve_registry_dependency(&cfg.dependency_name, &cfg.dependency_version_req)?;
59
+ generate_from_resolved(cfg, &resolved)
60
+ }
61
+
62
+ /// Same as [`generate_from_registry_dependency`] but dependency is already in `manifest_path`’s workspace.
63
+ pub fn generate_from_manifest(
64
+ cfg: &BindgenConfig,
65
+ manifest_path: &Path,
66
+ dependency_package_name: &str,
67
+ ) -> Result<(), String> {
68
+ let resolved = resolve_dependency_from_manifest(manifest_path, dependency_package_name)?;
69
+ generate_from_resolved(cfg, &resolved)
70
+ }
71
+
72
+ fn generate_from_resolved(cfg: &BindgenConfig, resolved: &ResolvedDependency) -> Result<(), String> {
73
+ let root = resolved.source_root();
74
+ let fns = discover::discover_public_functions(&root)?;
75
+
76
+ let mut need_json_helpers = false;
77
+ let mut emitted = Vec::new();
78
+
79
+ for export in &cfg.exports {
80
+ let item = fns.get(export).ok_or_else(|| {
81
+ format!(
82
+ "no public fn `{}` found under {}/src (exports must match a `pub fn` in the dependency sources)",
83
+ export,
84
+ root.display()
85
+ )
86
+ })?;
87
+
88
+ let class = classify_public_fn(item).ok_or_else(|| {
89
+ format!(
90
+ "public fn `{}` has no supported signature for automatic binding (need `&[Value] -> Value`, or `&T where T: Serialize` with `Result<String, _>`, or `&str` with `Deserialize` and `Result`)",
91
+ export
92
+ )
93
+ })?;
94
+
95
+ match class {
96
+ SignatureClass::SerializeRefToResultString | SignatureClass::DeserializeStrToResult => {
97
+ need_json_helpers = true;
98
+ }
99
+ SignatureClass::TishValueAbi => {}
100
+ }
101
+
102
+ emitted.push((export.clone(), class));
103
+ }
104
+
105
+ let lib_rs = render_generated_lib(
106
+ &cfg.dependency_name,
107
+ &emitted,
108
+ need_json_helpers,
109
+ )?;
110
+
111
+ let cargo_toml = render_output_cargo_toml(
112
+ cfg,
113
+ resolved.version(),
114
+ need_json_helpers,
115
+ )?;
116
+
117
+ fs::create_dir_all(cfg.out_dir.join("src")).map_err(|e| e.to_string())?;
118
+ fs::write(cfg.out_dir.join("Cargo.toml"), cargo_toml).map_err(|e| e.to_string())?;
119
+ fs::write(cfg.out_dir.join("src").join("lib.rs"), lib_rs).map_err(|e| e.to_string())?;
120
+
121
+ Ok(())
122
+ }
123
+
124
+ impl BindgenConfig {
125
+ /// Write using [`generate_from_registry_dependency`].
126
+ pub fn write_files(&self) -> io::Result<()> {
127
+ generate_from_registry_dependency(self).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
128
+ }
129
+ }
130
+
131
+ fn render_output_cargo_toml(cfg: &BindgenConfig, dep_exact_version: &str, need_serde_json: bool) -> Result<String, String> {
132
+ let rt_line = match &cfg.tishlang_runtime {
133
+ TishlangRuntimeDep::Path(p) => format!(
134
+ "tishlang_runtime = {{ path = {} }}\n",
135
+ toml_string_value(p)
136
+ ),
137
+ TishlangRuntimeDep::Version(req) => format!(
138
+ "tishlang_runtime = {}\n",
139
+ toml_string_value(req)
140
+ ),
141
+ };
142
+ let dep_line = format!(
143
+ "{} = {}\n",
144
+ cfg.dependency_name,
145
+ toml_string_value(dep_exact_version)
146
+ );
147
+ // JSON bridge helpers use `serde_json::Value`; only add a second dep when the upstream crate is not serde_json.
148
+ let extra_serde = if need_serde_json && cfg.dependency_name != "serde_json" {
149
+ "serde_json = \"1.0\"\n"
150
+ } else {
151
+ ""
152
+ };
153
+
154
+ Ok(format!(
155
+ r#"[package]
156
+ name = "{name}"
157
+ version = "0.1.0"
158
+ edition = "2021"
159
+ publish = false
160
+
161
+ [lib]
162
+ path = "src/lib.rs"
163
+
164
+ [dependencies]
165
+ {rt_line}{dep_line}{extra_serde}"#,
166
+ name = cfg.output_crate_name,
167
+ rt_line = rt_line,
168
+ dep_line = dep_line,
169
+ extra_serde = extra_serde
170
+ ))
171
+ }
172
+
173
+ fn render_generated_lib(
174
+ dependency_cargo_name: &str,
175
+ exports: &[(String, SignatureClass)],
176
+ need_json_helpers: bool,
177
+ ) -> Result<String, String> {
178
+ let crate_ident = dependency_cargo_name.replace('-', "_");
179
+ let mut out = String::from(
180
+ "//! Generated by `tishlang-cargo-bindgen` — do not edit.\n\
181
+ //! Bindings inferred from the dependency crate’s `pub fn` signatures (syn + cargo metadata).\n\n\
182
+ use std::cell::RefCell;\n\
183
+ use std::rc::Rc;\n\
184
+ use std::sync::Arc;\n\
185
+ use tishlang_runtime::{ObjectMap, Value, VmRef};\n\n",
186
+ );
187
+
188
+ out.push_str(&format!(
189
+ "use {} as _tish_upstream;\n\n",
190
+ crate_ident
191
+ ));
192
+
193
+ if need_json_helpers {
194
+ out.push_str(JSON_HELPERS);
195
+ }
196
+
197
+ for (name, class) in exports {
198
+ let rust_fn = syn::parse_str::<syn::Ident>(name)
199
+ .map_err(|e| format!("invalid export name {}: {}", name, e))?;
200
+ let block = match class {
201
+ SignatureClass::TishValueAbi => format!(
202
+ "pub fn {name}(args: &[Value]) -> Value {{\n _tish_upstream::{name}(args)\n}}\n\n",
203
+ name = rust_fn
204
+ ),
205
+ SignatureClass::SerializeRefToResultString => format!(
206
+ "pub fn {name}(args: &[Value]) -> Value {{\n let Some(v) = args.first() else {{ return Value::Null }};\n match _tish_upstream::{name}(&tish_to_json(v)) {{\n Ok(s) => Value::String(Arc::from(s)),\n Err(_) => Value::Null,\n }}\n}}\n\n",
207
+ name = rust_fn
208
+ ),
209
+ SignatureClass::DeserializeStrToResult => format!(
210
+ "pub fn {name}(args: &[Value]) -> Value {{\n let s = match args.first() {{\n Some(Value::String(x)) => x.as_ref(),\n _ => return Value::Null,\n }};\n match _tish_upstream::{name}::<serde_json::Value>(s) {{\n Ok(j) => json_to_tish(j),\n Err(_) => Value::Null,\n }}\n}}\n\n",
211
+ name = rust_fn
212
+ ),
213
+ };
214
+ out.push_str(&block);
215
+ }
216
+
217
+ Ok(out)
218
+ }
219
+
220
+ const JSON_HELPERS: &str = r#"fn tish_to_json(v: &Value) -> serde_json::Value {
221
+ match v {
222
+ Value::Null => serde_json::Value::Null,
223
+ Value::Bool(b) => serde_json::Value::Bool(*b),
224
+ Value::Number(n) => serde_json::Number::from_f64(*n)
225
+ .map(serde_json::Value::Number)
226
+ .unwrap_or(serde_json::Value::Null),
227
+ Value::String(s) => serde_json::Value::String(s.to_string()),
228
+ Value::Array(a) => {
229
+ serde_json::Value::Array(a.borrow().iter().map(|x| tish_to_json(x)).collect())
230
+ }
231
+ Value::Object(o) => {
232
+ let mut m = serde_json::Map::new();
233
+ for (k, v) in o.borrow().iter() {
234
+ m.insert(k.to_string(), tish_to_json(v));
235
+ }
236
+ serde_json::Value::Object(m)
237
+ }
238
+ _ => serde_json::Value::Null,
239
+ }
240
+ }
241
+
242
+ fn json_to_tish(v: serde_json::Value) -> Value {
243
+ match v {
244
+ serde_json::Value::Null => Value::Null,
245
+ serde_json::Value::Bool(b) => Value::Bool(b),
246
+ serde_json::Value::Number(n) => Value::Number(n.as_f64().unwrap_or(0.0)),
247
+ serde_json::Value::String(s) => Value::String(s.into()),
248
+ serde_json::Value::Array(a) => Value::Array(VmRef::new(
249
+ a.into_iter().map(json_to_tish).collect(),
250
+ )),
251
+ serde_json::Value::Object(m) => {
252
+ let mut om = ObjectMap::default();
253
+ for (k, v) in m {
254
+ om.insert(Arc::from(k), json_to_tish(v));
255
+ }
256
+ Value::Object(VmRef::new(om))
257
+ }
258
+ }
259
+ }
260
+
261
+ "#;
262
+
263
+ fn toml_string_value(s: &str) -> String {
264
+ let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
265
+ format!("\"{}\"", escaped)
266
+ }
267
+
268
+ /// Resolve a path to `tishlang_runtime` for writing into generated `Cargo.toml`.
269
+ pub fn resolve_runtime_path_for_output(start: &Path) -> Result<String, String> {
270
+ if let Ok(p) = std::env::var("TISHLANG_RUNTIME_PATH") {
271
+ let pb = Path::new(&p);
272
+ if pb.join("Cargo.toml").is_file() {
273
+ return canonical_path_string(pb);
274
+ }
275
+ return Err(format!(
276
+ "TISHLANG_RUNTIME_PATH does not point to tishlang_runtime: {}",
277
+ p
278
+ ));
279
+ }
280
+
281
+ let mut dir = if start.is_file() {
282
+ start.parent().unwrap_or(start).to_path_buf()
283
+ } else {
284
+ start.to_path_buf()
285
+ };
286
+
287
+ for _ in 0..64 {
288
+ let npm_rt = dir
289
+ .join("node_modules")
290
+ .join("@tishlang")
291
+ .join("tish")
292
+ .join("crates")
293
+ .join("tish_runtime");
294
+ if npm_rt.join("Cargo.toml").is_file() {
295
+ return canonical_path_string(&npm_rt);
296
+ }
297
+
298
+ let ws_rt = dir.join("crates").join("tish_runtime");
299
+ if ws_rt.join("Cargo.toml").is_file() {
300
+ return canonical_path_string(&ws_rt);
301
+ }
302
+
303
+ if !dir.pop() {
304
+ break;
305
+ }
306
+ }
307
+
308
+ Err(
309
+ "Could not find tishlang_runtime (set TISHLANG_RUNTIME_PATH or run from a project with node_modules/@tishlang/tish or a Tish repo checkout)"
310
+ .into(),
311
+ )
312
+ }
313
+
314
+ fn canonical_path_string(p: &Path) -> Result<String, String> {
315
+ p.canonicalize()
316
+ .map_err(|e| format!("Cannot canonicalize {}: {}", p.display(), e))
317
+ .map(|p| p.display().to_string().replace('\\', "/"))
318
+ }
319
+
320
+ /// Relative path from `out_dir` to `runtime` for Cargo.toml `path =`.
321
+ pub fn runtime_path_relative_to_out_dir(out_dir: &Path, runtime: impl AsRef<Path>) -> Result<String, String> {
322
+ let abs_rt = runtime
323
+ .as_ref()
324
+ .canonicalize()
325
+ .map_err(|e| format!("Cannot canonicalize runtime path: {}", e))?;
326
+ fs::create_dir_all(out_dir).map_err(|e| e.to_string())?;
327
+ let abs_out = out_dir
328
+ .canonicalize()
329
+ .map_err(|e| format!("Cannot canonicalize out dir: {}", e))?;
330
+
331
+ pathdiff::diff_paths(&abs_rt, &abs_out)
332
+ .ok_or_else(|| "Could not compute relative path from out_dir to runtime".to_string())
333
+ .map(|p| p.display().to_string().replace('\\', "/"))
334
+ }
335
+
336
+ #[cfg(test)]
337
+ mod tests {
338
+ use super::*;
339
+
340
+ #[test]
341
+ fn generated_lib_contains_tish_abi_forward() {
342
+ let s = render_generated_lib(
343
+ "demo",
344
+ &[("greet".into(), SignatureClass::TishValueAbi)],
345
+ false,
346
+ )
347
+ .unwrap();
348
+ assert!(s.contains("_tish_upstream::greet"));
349
+ }
350
+ }
@@ -0,0 +1,164 @@
1
+ //! CLI for pre-generating `cargo:` binding crates (bindgen-style).
2
+ //!
3
+ //! Resolves the dependency with `cargo metadata`, scans its `src/**/*.rs` with `syn`, classifies
4
+ //! each requested `pub fn` by signature, and emits a wrapper crate.
5
+
6
+ use std::path::PathBuf;
7
+
8
+ use clap::Parser;
9
+ use tishlang_cargo_bindgen::{
10
+ generate_from_manifest, generate_from_registry_dependency, infer,
11
+ resolve_runtime_path_for_output, runtime_path_relative_to_out_dir, BindgenConfig, TishlangRuntimeDep,
12
+ };
13
+
14
+ #[derive(Parser, Debug)]
15
+ #[command(name = "tishlang-cargo-bindgen")]
16
+ #[command(about = "Generate Rust glue for Tish `cargo:` imports from dependency source + metadata")]
17
+ struct Args {
18
+ /// Upstream Cargo package to wrap (e.g. serde_json). Omit to read `tish.rustDependencies` + glue `Cargo.toml`.
19
+ #[arg(long)]
20
+ dependency: Option<String>,
21
+
22
+ /// Semver for the upstream crate when using `--dependency` (ignored in full project auto mode).
23
+ #[arg(long, default_value = "1.0")]
24
+ dependency_version: String,
25
+
26
+ /// Generated crate `[package].name` when using `--dependency` with `--out-dir` (default: tish_serde_json).
27
+ #[arg(long)]
28
+ crate_name: Option<String>,
29
+
30
+ /// Output directory. Required with `--dependency` unless `--project-root` selects a path from `package.json`.
31
+ #[arg(long)]
32
+ out_dir: Option<PathBuf>,
33
+
34
+ /// Comma-separated export names to bind (must match `pub fn` names in the dependency).
35
+ #[arg(long, default_value = "to_string,from_str")]
36
+ exports: String,
37
+
38
+ /// If set, resolve `dependency` from this workspace instead of a temporary probe crate.
39
+ #[arg(long)]
40
+ manifest_path: Option<PathBuf>,
41
+
42
+ /// Directory containing `package.json` (default: current directory). Drives automatic glue discovery.
43
+ #[arg(long)]
44
+ project_root: Option<PathBuf>,
45
+
46
+ /// Crates.io semver for `tishlang_runtime` in the generated `Cargo.toml` (no path into the Tish repo).
47
+ /// Mutually exclusive with `--tishlang-runtime-path`.
48
+ #[arg(long, conflicts_with = "tishlang_runtime_path")]
49
+ tishlang_runtime_version: Option<String>,
50
+
51
+ /// Absolute path to the `tishlang_runtime` crate root for generated `path = ...` (overrides search).
52
+ /// Mutually exclusive with `--tishlang-runtime-version`.
53
+ #[arg(long, conflicts_with = "tishlang_runtime_version")]
54
+ tishlang_runtime_path: Option<PathBuf>,
55
+ }
56
+
57
+ fn main() {
58
+ if let Err(e) = run() {
59
+ eprintln!("tishlang-cargo-bindgen: error: {}", e);
60
+ std::process::exit(1);
61
+ }
62
+ }
63
+
64
+ fn run() -> Result<(), String> {
65
+ let args = Args::parse();
66
+
67
+ let exports: Vec<String> = args
68
+ .exports
69
+ .split(',')
70
+ .map(|s| s.trim().to_string())
71
+ .filter(|s| !s.is_empty())
72
+ .collect();
73
+ if exports.is_empty() {
74
+ return Err("no exports given".into());
75
+ }
76
+
77
+ if args.dependency.is_none() && args.out_dir.is_some() {
78
+ return Err(
79
+ "--out-dir is only used with --dependency (without --dependency, output path comes from package.json tish.rustDependencies)"
80
+ .into(),
81
+ );
82
+ }
83
+
84
+ let current_dir = std::env::current_dir().map_err(|e| e.to_string())?;
85
+
86
+ let (
87
+ output_crate_name,
88
+ out_dir,
89
+ dependency_name,
90
+ dependency_version_req,
91
+ search_root,
92
+ ) = if let Some(dep) = args.dependency.as_ref() {
93
+ if let Some(out) = args.out_dir.as_ref() {
94
+ let root = args
95
+ .project_root
96
+ .clone()
97
+ .unwrap_or_else(|| out.parent().unwrap_or(out).to_path_buf());
98
+ (
99
+ args.crate_name
100
+ .clone()
101
+ .unwrap_or_else(|| "tish_serde_json".into()),
102
+ out.clone(),
103
+ dep.clone(),
104
+ args.dependency_version.clone(),
105
+ root,
106
+ )
107
+ } else {
108
+ let root = args.project_root.clone().unwrap_or_else(|| current_dir.clone());
109
+ let (crate_key, od) = infer::infer_glue_paths_only(&root, args.crate_name.as_deref())?;
110
+ (
111
+ crate_key,
112
+ od,
113
+ dep.clone(),
114
+ args.dependency_version.clone(),
115
+ root,
116
+ )
117
+ }
118
+ } else {
119
+ let root = args.project_root.clone().unwrap_or_else(|| current_dir.clone());
120
+ let inf = infer::infer_from_project_root(&root, args.crate_name.as_deref())?;
121
+ (
122
+ inf.output_crate_name,
123
+ inf.out_dir,
124
+ inf.dependency_name,
125
+ inf.dependency_version_req,
126
+ root,
127
+ )
128
+ };
129
+
130
+ let tishlang_runtime = if let Some(req) = args.tishlang_runtime_version.clone() {
131
+ TishlangRuntimeDep::Version(req)
132
+ } else if let Some(p) = args.tishlang_runtime_path.as_ref() {
133
+ let abs = p
134
+ .canonicalize()
135
+ .map_err(|e| format!("tishlang_runtime path {}: {}", p.display(), e))?;
136
+ TishlangRuntimeDep::Path(runtime_path_relative_to_out_dir(&out_dir, &abs)?)
137
+ } else {
138
+ let runtime_abs = resolve_runtime_path_for_output(&search_root)?;
139
+ let rt_rel = runtime_path_relative_to_out_dir(&out_dir, &runtime_abs)?;
140
+ TishlangRuntimeDep::Path(rt_rel)
141
+ };
142
+
143
+ let cfg = BindgenConfig {
144
+ output_crate_name,
145
+ tishlang_runtime,
146
+ out_dir: out_dir.clone(),
147
+ dependency_name,
148
+ dependency_version_req,
149
+ exports,
150
+ };
151
+
152
+ if let Some(manifest) = args.manifest_path {
153
+ generate_from_manifest(&cfg, &manifest, &cfg.dependency_name)?;
154
+ } else {
155
+ generate_from_registry_dependency(&cfg)?;
156
+ }
157
+
158
+ println!(
159
+ "Wrote {} (upstream `{}`, metadata + syn)",
160
+ out_dir.display(),
161
+ cfg.dependency_name
162
+ );
163
+ Ok(())
164
+ }
@@ -0,0 +1,114 @@
1
+ //! Resolve a crates.io (or registry) dependency to its on-disk source tree via `cargo metadata`.
2
+
3
+ use std::fs;
4
+ use std::path::{Path, PathBuf};
5
+
6
+ use cargo_metadata::{MetadataCommand, Package, TargetKind};
7
+
8
+ /// Root directory of the resolved dependency (contains `Cargo.toml` and usually `src/`).
9
+ #[derive(Debug, Clone)]
10
+ pub struct ResolvedDependency {
11
+ pub name: String,
12
+ pub version: String,
13
+ pub manifest_path: PathBuf,
14
+ }
15
+
16
+ impl ResolvedDependency {
17
+ pub fn source_root(&self) -> PathBuf {
18
+ self.manifest_path
19
+ .parent()
20
+ .expect("manifest has parent")
21
+ .to_path_buf()
22
+ }
23
+
24
+ pub fn version(&self) -> &str {
25
+ &self.version
26
+ }
27
+ }
28
+
29
+ fn from_package(pkg: &Package) -> ResolvedDependency {
30
+ ResolvedDependency {
31
+ name: pkg.name.clone(),
32
+ version: pkg.version.to_string(),
33
+ manifest_path: pkg.manifest_path.as_std_path().to_path_buf(),
34
+ }
35
+ }
36
+
37
+ /// Build a tiny probe crate, run `cargo metadata`, and return the named package.
38
+ pub fn resolve_registry_dependency(name: &str, version_req: &str) -> Result<ResolvedDependency, String> {
39
+ let probe_dir = tempfile::tempdir().map_err(|e| format!("tempdir: {}", e))?;
40
+ let probe_toml = format!(
41
+ r#"[package]
42
+ name = "_tishlang_bindgen_probe"
43
+ version = "0.0.0"
44
+ edition = "2021"
45
+ [dependencies]
46
+ {} = "{}"
47
+ "#,
48
+ name, version_req
49
+ );
50
+ let manifest = probe_dir.path().join("Cargo.toml");
51
+ fs::write(&manifest, probe_toml).map_err(|e| format!("write probe Cargo.toml: {}", e))?;
52
+ // `cargo metadata` requires at least one target in the root package.
53
+ let src_dir = probe_dir.path().join("src");
54
+ fs::create_dir_all(&src_dir).map_err(|e| format!("probe src dir: {}", e))?;
55
+ fs::write(src_dir.join("lib.rs"), "// probe only\n")
56
+ .map_err(|e| format!("write probe lib.rs: {}", e))?;
57
+
58
+ let meta = MetadataCommand::new()
59
+ .manifest_path(&manifest)
60
+ .exec()
61
+ .map_err(|e| format!("cargo metadata failed: {} (is `cargo` on PATH?)", e))?;
62
+
63
+ let pkg = meta
64
+ .packages
65
+ .iter()
66
+ .find(|p| p.name == name)
67
+ .ok_or_else(|| {
68
+ format!(
69
+ "dependency `{}` not found in metadata (check name and version req `{}`)",
70
+ name, version_req
71
+ )
72
+ })?;
73
+
74
+ let root = pkg.manifest_path.as_std_path().parent().unwrap();
75
+ if !root.join("src").is_dir()
76
+ && !pkg
77
+ .targets
78
+ .iter()
79
+ .any(|t| t.kind.iter().any(|k| *k == TargetKind::Lib))
80
+ {
81
+ return Err(format!(
82
+ "package `{}` has no src/ or lib target at {}",
83
+ name,
84
+ root.display()
85
+ ));
86
+ }
87
+
88
+ Ok(from_package(pkg))
89
+ }
90
+
91
+ /// Resolve using an existing workspace manifest (no temporary probe).
92
+ pub fn resolve_dependency_from_manifest(
93
+ manifest_path: &Path,
94
+ package_name: &str,
95
+ ) -> Result<ResolvedDependency, String> {
96
+ let meta = MetadataCommand::new()
97
+ .manifest_path(manifest_path)
98
+ .exec()
99
+ .map_err(|e| format!("cargo metadata: {}", e))?;
100
+
101
+ let pkg = meta
102
+ .packages
103
+ .iter()
104
+ .find(|p| p.name == package_name)
105
+ .ok_or_else(|| {
106
+ format!(
107
+ "package `{}` not found in metadata for {}",
108
+ package_name,
109
+ manifest_path.display()
110
+ )
111
+ })?;
112
+
113
+ Ok(from_package(pkg))
114
+ }
package/justfile CHANGED
@@ -250,6 +250,14 @@ optional-chaining-profile:
250
250
  perf *ARGS:
251
251
  ./scripts/run_performance_manual.sh {{ARGS}}
252
252
 
253
+ # Bundled perf suite: per-test table + whole-program timings (tests/main.tish); add --verbose for full stdout
254
+ perf-suite *ARGS:
255
+ ./scripts/run_performance_suite.sh {{ARGS}}
256
+
257
+ # Regenerate tests/main.tish + tests/main.js after changing paired pure perf tests
258
+ perf-suite-gen:
259
+ ./scripts/generate_perf_ci_main.sh
260
+
253
261
  # Show binary sizes for different builds
254
262
  sizes:
255
263
  @echo "Building secure binary..."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tishlang/tish",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Tish - minimal TS/JS-compatible language. Run, REPL, build to native or other targets.",
5
5
  "license": "PIF",
6
6
  "repository": {
Binary file
Binary file
Binary file
Binary file
Binary file