@tishlang/tish 1.5.0 → 1.7.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 (85) hide show
  1. package/Cargo.toml +1 -0
  2. package/bin/tish +0 -0
  3. package/crates/js_to_tish/src/error.rs +2 -8
  4. package/crates/js_to_tish/src/transform/expr.rs +101 -130
  5. package/crates/js_to_tish/src/transform/stmt.rs +25 -22
  6. package/crates/tish/Cargo.toml +1 -1
  7. package/crates/tish/src/cli_help.rs +76 -29
  8. package/crates/tish/src/main.rs +85 -54
  9. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  10. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  11. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  12. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  13. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  14. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  15. package/crates/tish/tests/integration_test.rs +197 -47
  16. package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
  17. package/crates/tish/tests/shortcircuit.rs +19 -4
  18. package/crates/tish_ast/src/ast.rs +12 -14
  19. package/crates/tish_build_utils/src/lib.rs +64 -6
  20. package/crates/tish_builtins/src/array.rs +52 -21
  21. package/crates/tish_builtins/src/construct.rs +2 -8
  22. package/crates/tish_builtins/src/globals.rs +30 -15
  23. package/crates/tish_builtins/src/lib.rs +5 -5
  24. package/crates/tish_builtins/src/math.rs +5 -3
  25. package/crates/tish_builtins/src/string.rs +71 -19
  26. package/crates/tish_bytecode/src/chunk.rs +0 -1
  27. package/crates/tish_bytecode/src/compiler.rs +164 -60
  28. package/crates/tish_bytecode/src/opcode.rs +13 -4
  29. package/crates/tish_bytecode/src/peephole.rs +2 -2
  30. package/crates/tish_compile/Cargo.toml +1 -0
  31. package/crates/tish_compile/src/codegen.rs +989 -318
  32. package/crates/tish_compile/src/infer.rs +69 -19
  33. package/crates/tish_compile/src/lib.rs +21 -8
  34. package/crates/tish_compile/src/resolve.rs +515 -94
  35. package/crates/tish_compile/src/types.rs +10 -14
  36. package/crates/tish_compile_js/src/codegen.rs +34 -13
  37. package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
  38. package/crates/tish_compiler_wasm/src/lib.rs +16 -13
  39. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +40 -48
  40. package/crates/tish_core/src/json.rs +5 -3
  41. package/crates/tish_core/src/lib.rs +1 -1
  42. package/crates/tish_core/src/uri.rs +9 -6
  43. package/crates/tish_core/src/value.rs +92 -28
  44. package/crates/tish_cranelift/src/link.rs +6 -9
  45. package/crates/tish_cranelift/src/lower.rs +14 -8
  46. package/crates/tish_eval/src/eval.rs +398 -141
  47. package/crates/tish_eval/src/lib.rs +10 -6
  48. package/crates/tish_eval/src/natives.rs +95 -38
  49. package/crates/tish_eval/src/promise.rs +14 -8
  50. package/crates/tish_eval/src/timers.rs +28 -19
  51. package/crates/tish_eval/src/value.rs +10 -3
  52. package/crates/tish_fmt/src/lib.rs +29 -13
  53. package/crates/tish_lexer/src/lib.rs +217 -63
  54. package/crates/tish_lexer/src/token.rs +6 -6
  55. package/crates/tish_llvm/src/lib.rs +15 -8
  56. package/crates/tish_lsp/src/main.rs +41 -43
  57. package/crates/tish_native/src/build.rs +38 -15
  58. package/crates/tish_native/src/lib.rs +76 -32
  59. package/crates/tish_opt/src/lib.rs +67 -50
  60. package/crates/tish_parser/src/lib.rs +36 -11
  61. package/crates/tish_parser/src/parser.rs +172 -87
  62. package/crates/tish_runtime/src/http.rs +15 -6
  63. package/crates/tish_runtime/src/http_fetch.rs +24 -14
  64. package/crates/tish_runtime/src/lib.rs +224 -168
  65. package/crates/tish_runtime/src/promise.rs +1 -5
  66. package/crates/tish_runtime/src/ws.rs +45 -20
  67. package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
  68. package/crates/tish_ui/src/jsx.rs +41 -22
  69. package/crates/tish_ui/src/lib.rs +2 -2
  70. package/crates/tish_vm/src/vm.rs +320 -116
  71. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +8 -3
  72. package/crates/tish_wasm/src/lib.rs +38 -28
  73. package/crates/tishlang_cargo_bindgen/Cargo.toml +25 -0
  74. package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
  75. package/crates/tishlang_cargo_bindgen/src/discover.rs +52 -0
  76. package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
  77. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  78. package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
  79. package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
  80. package/package.json +1 -1
  81. package/platform/darwin-arm64/tish +0 -0
  82. package/platform/darwin-x64/tish +0 -0
  83. package/platform/linux-arm64/tish +0 -0
  84. package/platform/linux-x64/tish +0 -0
  85. package/platform/win32-x64/tish.exe +0 -0
@@ -0,0 +1,372 @@
1
+ //! Infer bindgen targets from project `package.json` (`tish.rustDependencies`) and glue `Cargo.toml`.
2
+
3
+ use std::fs;
4
+ use std::path::{Path, PathBuf};
5
+
6
+ use serde_json::Value;
7
+
8
+ /// Glue crate identity + upstream Cargo dependency to wrap.
9
+ #[derive(Debug, Clone)]
10
+ pub struct InferredProjectBindgen {
11
+ /// `tish.rustDependencies` key and `[package].name` for the generated crate.
12
+ pub output_crate_name: String,
13
+ pub out_dir: PathBuf,
14
+ pub dependency_name: String,
15
+ pub dependency_version_req: String,
16
+ }
17
+
18
+ /// Pick the path-based glue crate from `package.json` → `tish.rustDependencies`.
19
+ pub fn select_glue_crate_from_package_json(
20
+ project_root: &Path,
21
+ rust_dep_key: Option<&str>,
22
+ ) -> Result<(String, PathBuf), String> {
23
+ let pkg_json_path = project_root.join("package.json");
24
+ let raw = fs::read_to_string(&pkg_json_path).map_err(|e| {
25
+ format!(
26
+ "could not read {}: {} (for automatic mode, run from the Tish project root or pass --project-root)",
27
+ pkg_json_path.display(),
28
+ e
29
+ )
30
+ })?;
31
+ let j: Value = serde_json::from_str(&raw).map_err(|e| format!("{}: {}", pkg_json_path.display(), e))?;
32
+ let rust_deps = j
33
+ .get("tish")
34
+ .and_then(|t| t.get("rustDependencies"))
35
+ .and_then(|v| v.as_object())
36
+ .ok_or_else(|| {
37
+ format!(
38
+ "{} must contain `tish.rustDependencies` (object) to infer glue paths",
39
+ pkg_json_path.display()
40
+ )
41
+ })?;
42
+
43
+ let mut path_entries: Vec<(String, PathBuf)> = Vec::new();
44
+ for (key, val) in rust_deps {
45
+ let rel = match val {
46
+ Value::Object(o) => o.get("path").and_then(|p| p.as_str()),
47
+ _ => None,
48
+ };
49
+ if let Some(p) = rel {
50
+ let abs = project_root.join(p);
51
+ path_entries.push((key.clone(), abs));
52
+ }
53
+ }
54
+
55
+ path_entries.sort_by(|a, b| a.0.cmp(&b.0));
56
+
57
+ if path_entries.is_empty() {
58
+ return Err(
59
+ "no path-based entries in tish.rustDependencies (only version strings? bindgen updates local path crates)"
60
+ .into(),
61
+ );
62
+ }
63
+
64
+ match rust_dep_key {
65
+ Some(k) => {
66
+ let found = path_entries
67
+ .iter()
68
+ .find(|(key, _)| key == k)
69
+ .ok_or_else(|| {
70
+ format!(
71
+ "tish.rustDependencies has no path entry for key `{}` (have: {})",
72
+ k,
73
+ path_entries
74
+ .iter()
75
+ .map(|(k, _)| k.as_str())
76
+ .collect::<Vec<_>>()
77
+ .join(", ")
78
+ )
79
+ })?;
80
+ Ok(found.clone())
81
+ }
82
+ None => {
83
+ if path_entries.len() != 1 {
84
+ return Err(format!(
85
+ "multiple path rustDependencies — pass --crate-name <key> (keys: {})",
86
+ path_entries
87
+ .iter()
88
+ .map(|(k, _)| k.as_str())
89
+ .collect::<Vec<_>>()
90
+ .join(", ")
91
+ ));
92
+ }
93
+ Ok(path_entries[0].clone())
94
+ }
95
+ }
96
+ }
97
+
98
+ fn dep_version_req_string_glue(spec: &toml::Value) -> Result<String, String> {
99
+ match spec {
100
+ toml::Value::String(s) => Ok(s.clone()),
101
+ toml::Value::Table(t) => {
102
+ if t.get("workspace").and_then(|v| v.as_bool()) == Some(true) {
103
+ return Err(
104
+ "glue Cargo.toml uses `workspace = true` for an upstream dep; set an explicit version in the glue crate or use --dependency with --out-dir"
105
+ .into(),
106
+ );
107
+ }
108
+ if t.get("path").is_some() || t.get("git").is_some() {
109
+ return Err(
110
+ "expected a registry-style upstream dep with a semver (string or version = \"…\"); path/git deps are not supported as bindgen upstream"
111
+ .into(),
112
+ );
113
+ }
114
+ t.get("version")
115
+ .and_then(|v| v.as_str())
116
+ .map(|s| s.to_string())
117
+ .ok_or_else(|| {
118
+ "dependency entry needs a version string or `version = \"…\"` in a table".into()
119
+ })
120
+ }
121
+ _ => Err("unsupported dependency entry in glue Cargo.toml".into()),
122
+ }
123
+ }
124
+
125
+ /// Crates we ignore when inferring bindgen **upstream** from the **app** `Cargo.toml` (not the glue crate).
126
+ const ROOT_MANIFEST_SKIP_DEPS: &[&str] = &["tishlang_runtime", "tishlang_core", "tishlang_build_utils"];
127
+
128
+ /// Registry semver for bindgen metadata probe; skips path/git/workspace-only entries (project root fallback).
129
+ fn dep_version_req_string_root(spec: &toml::Value) -> Option<String> {
130
+ match spec {
131
+ toml::Value::String(s) => Some(s.clone()),
132
+ toml::Value::Table(t) => {
133
+ if t.get("workspace").and_then(|v| v.as_bool()) == Some(true) {
134
+ return None;
135
+ }
136
+ if t.get("path").is_some() || t.get("git").is_some() {
137
+ return None;
138
+ }
139
+ t.get("version").and_then(|v| v.as_str()).map(|s| s.to_string())
140
+ }
141
+ _ => None,
142
+ }
143
+ }
144
+
145
+ fn collect_upstream_candidates_from_table(
146
+ deps: &toml::Table,
147
+ skip_names: &[&str],
148
+ version_fn: fn(&toml::Value) -> Result<String, String>,
149
+ ) -> Result<Vec<(String, String)>, String> {
150
+ let mut candidates: Vec<(String, String)> = Vec::new();
151
+ for (name, spec) in deps {
152
+ if name == "tishlang_runtime" || skip_names.iter().any(|s| *s == name) {
153
+ continue;
154
+ }
155
+ let ver = version_fn(spec)?;
156
+ candidates.push((name.clone(), ver));
157
+ }
158
+ Ok(candidates)
159
+ }
160
+
161
+ fn collect_upstream_candidates_from_table_root(deps: &toml::Table) -> Vec<(String, String)> {
162
+ let mut candidates: Vec<(String, String)> = Vec::new();
163
+ for (name, spec) in deps {
164
+ if name == "tishlang_runtime" || ROOT_MANIFEST_SKIP_DEPS.iter().any(|s| *s == name) {
165
+ continue;
166
+ }
167
+ if let Some(ver) = dep_version_req_string_root(spec) {
168
+ candidates.push((name.clone(), ver));
169
+ }
170
+ }
171
+ candidates
172
+ }
173
+
174
+ fn disambiguate_upstream_candidates(
175
+ candidates: Vec<(String, String)>,
176
+ manifest_path: &Path,
177
+ hint: &str,
178
+ ) -> Result<(String, String), String> {
179
+ match candidates.len() {
180
+ 0 => Err(format!(
181
+ "{}: no registry semver dependency to use as bindgen upstream{}",
182
+ manifest_path.display(),
183
+ hint
184
+ )),
185
+ 1 => Ok(candidates[0].clone()),
186
+ 2 => {
187
+ let non_sj: Vec<_> = candidates
188
+ .iter()
189
+ .filter(|(n, _)| n != "serde_json")
190
+ .cloned()
191
+ .collect();
192
+ if non_sj.len() == 1 {
193
+ Ok(non_sj[0].clone())
194
+ } else {
195
+ Err(format!(
196
+ "{}: ambiguous dependencies (expected one upstream, or one non-serde_json + serde_json); use explicit --dependency",
197
+ manifest_path.display()
198
+ ))
199
+ }
200
+ }
201
+ _ => Err(format!(
202
+ "{}: too many candidate upstream crates; narrow [dependencies] or use explicit --dependency",
203
+ manifest_path.display()
204
+ )),
205
+ }
206
+ }
207
+
208
+ /// Read `[dependencies]` from the glue crate: upstream is everything except `tishlang_runtime`,
209
+ /// disambiguating when `serde_json` is present as a JSON-bridge helper alongside another crate.
210
+ pub fn parse_upstream_from_glue_cargo(cargo_toml_path: &Path) -> Result<(String, String), String> {
211
+ let s = fs::read_to_string(cargo_toml_path).map_err(|e| e.to_string())?;
212
+ let root: toml::Value = toml::from_str(&s).map_err(|e| format!("{}: {}", cargo_toml_path.display(), e))?;
213
+ let deps = root
214
+ .get("dependencies")
215
+ .and_then(|d| d.as_table())
216
+ .ok_or_else(|| format!("{} has no [dependencies]", cargo_toml_path.display()))?;
217
+
218
+ let candidates = collect_upstream_candidates_from_table(deps, &[], dep_version_req_string_glue)?;
219
+ disambiguate_upstream_candidates(
220
+ candidates,
221
+ cargo_toml_path,
222
+ " (only tishlang_runtime?) — add e.g. `serde_json = \"1\"`",
223
+ )
224
+ }
225
+
226
+ /// Infer upstream from the **project** `Cargo.toml` when the glue crate does not exist yet.
227
+ /// Uses `[dependencies]` and `[dev-dependencies]`, skips Tish toolchain path crates (`tishlang_core`, …),
228
+ /// and only keeps entries with a registry semver (skips bare `path =` / `git` deps).
229
+ pub fn parse_upstream_from_root_package_cargo(cargo_toml_path: &Path) -> Result<(String, String), String> {
230
+ let s = fs::read_to_string(cargo_toml_path).map_err(|e| e.to_string())?;
231
+ let root: toml::Value = toml::from_str(&s).map_err(|e| format!("{}: {}", cargo_toml_path.display(), e))?;
232
+
233
+ let mut candidates = root
234
+ .get("dependencies")
235
+ .and_then(|d| d.as_table())
236
+ .map(collect_upstream_candidates_from_table_root)
237
+ .unwrap_or_default();
238
+ if let Some(dev) = root.get("dev-dependencies").and_then(|d| d.as_table()) {
239
+ candidates.extend(collect_upstream_candidates_from_table_root(dev));
240
+ }
241
+
242
+ // Same name in both tables: prefer first occurrence (dependencies before dev-dependencies).
243
+ let mut seen = std::collections::HashSet::new();
244
+ candidates.retain(|(n, _)| seen.insert(n.clone()));
245
+
246
+ disambiguate_upstream_candidates(
247
+ candidates,
248
+ cargo_toml_path,
249
+ " — add a registry dep for the crate to wrap (e.g. serde_json = \"1\") or use --dependency",
250
+ )
251
+ }
252
+
253
+ /// Full inference: `rustDependencies` path + glue `Cargo.toml`, or project-root `Cargo.toml` if glue is missing.
254
+ pub fn infer_from_project_root(
255
+ project_root: &Path,
256
+ rust_dep_key: Option<&str>,
257
+ ) -> Result<InferredProjectBindgen, String> {
258
+ let (output_crate_name, out_dir) = select_glue_crate_from_package_json(project_root, rust_dep_key)?;
259
+ let glue_cargo = out_dir.join("Cargo.toml");
260
+ let root_cargo = project_root.join("Cargo.toml");
261
+
262
+ let (dependency_name, dependency_version_req) = if glue_cargo.is_file() {
263
+ parse_upstream_from_glue_cargo(&glue_cargo)?
264
+ } else if root_cargo.is_file() {
265
+ parse_upstream_from_root_package_cargo(&root_cargo)?
266
+ } else {
267
+ return Err(format!(
268
+ "no Cargo.toml at glue path {} and none at project root {} — add the upstream crate to the app Cargo.toml ([dependencies] or [dev-dependencies]) or bootstrap with:\n --project-root {} --dependency <upstream_crate> --dependency-version 1.0",
269
+ glue_cargo.display(),
270
+ root_cargo.display(),
271
+ project_root.display()
272
+ ));
273
+ };
274
+
275
+ Ok(InferredProjectBindgen {
276
+ output_crate_name,
277
+ out_dir,
278
+ dependency_name,
279
+ dependency_version_req,
280
+ })
281
+ }
282
+
283
+ /// Paths from `package.json` only (bootstrap before a glue `Cargo.toml` exists).
284
+ pub fn infer_glue_paths_only(
285
+ project_root: &Path,
286
+ rust_dep_key: Option<&str>,
287
+ ) -> Result<(String, PathBuf), String> {
288
+ select_glue_crate_from_package_json(project_root, rust_dep_key)
289
+ }
290
+
291
+ #[cfg(test)]
292
+ mod tests {
293
+ use super::*;
294
+
295
+ #[test]
296
+ fn parse_upstream_single_serde_json() {
297
+ let dir = tempfile::tempdir().unwrap();
298
+ let p = dir.path().join("Cargo.toml");
299
+ fs::write(
300
+ &p,
301
+ r#"[package]
302
+ name = "glue"
303
+ version = "0.1.0"
304
+ edition = "2021"
305
+ [dependencies]
306
+ tishlang_runtime = { path = "../rt" }
307
+ serde_json = "1.0.149"
308
+ "#,
309
+ )
310
+ .unwrap();
311
+ let (n, v) = parse_upstream_from_glue_cargo(&p).unwrap();
312
+ assert_eq!(n, "serde_json");
313
+ assert_eq!(v, "1.0.149");
314
+ }
315
+
316
+ #[test]
317
+ fn parse_upstream_two_deps_prefers_non_serde_json() {
318
+ let dir = tempfile::tempdir().unwrap();
319
+ let p = dir.path().join("Cargo.toml");
320
+ fs::write(
321
+ &p,
322
+ r#"[dependencies]
323
+ tishlang_runtime = "0.1"
324
+ mylib = "2"
325
+ serde_json = "1"
326
+ "#,
327
+ )
328
+ .unwrap();
329
+ let (n, _) = parse_upstream_from_glue_cargo(&p).unwrap();
330
+ assert_eq!(n, "mylib");
331
+ }
332
+
333
+ #[test]
334
+ fn parse_upstream_from_root_skips_tishlang_core_path() {
335
+ let dir = tempfile::tempdir().unwrap();
336
+ let p = dir.path().join("Cargo.toml");
337
+ fs::write(
338
+ &p,
339
+ r#"[package]
340
+ name = "app"
341
+ version = "0.1.0"
342
+ edition = "2021"
343
+ [dependencies]
344
+ tishlang_core = { path = "../tish/crates/tish_core" }
345
+ serde_json = "1.0.200"
346
+ "#,
347
+ )
348
+ .unwrap();
349
+ let (n, v) = parse_upstream_from_root_package_cargo(&p).unwrap();
350
+ assert_eq!(n, "serde_json");
351
+ assert_eq!(v, "1.0.200");
352
+ }
353
+
354
+ #[test]
355
+ fn parse_upstream_from_root_merges_dev_dependencies() {
356
+ let dir = tempfile::tempdir().unwrap();
357
+ let p = dir.path().join("Cargo.toml");
358
+ fs::write(
359
+ &p,
360
+ r#"[package]
361
+ name = "app"
362
+ version = "0.1.0"
363
+ edition = "2021"
364
+ [dev-dependencies]
365
+ serde_json = "1"
366
+ "#,
367
+ )
368
+ .unwrap();
369
+ let (n, _) = parse_upstream_from_root_package_cargo(&p).unwrap();
370
+ assert_eq!(n, "serde_json");
371
+ }
372
+ }