@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.
- package/Cargo.toml +2 -0
- package/README.md +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/error.rs +2 -8
- package/crates/js_to_tish/src/transform/expr.rs +128 -137
- package/crates/js_to_tish/src/transform/stmt.rs +62 -32
- package/crates/tish/Cargo.toml +15 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +92 -39
- package/crates/tish/src/main.rs +172 -86
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +4 -2
- package/crates/tish/tests/integration_test.rs +216 -54
- package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
- package/crates/tish/tests/shortcircuit.rs +20 -5
- package/crates/tish_ast/src/ast.rs +92 -23
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +136 -8
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +65 -33
- package/crates/tish_builtins/src/construct.rs +34 -39
- package/crates/tish_builtins/src/globals.rs +42 -26
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/lib.rs +5 -5
- package/crates/tish_builtins/src/math.rs +5 -3
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +144 -22
- package/crates/tish_bytecode/src/chunk.rs +0 -1
- package/crates/tish_bytecode/src/compiler.rs +173 -71
- package/crates/tish_bytecode/src/opcode.rs +24 -6
- package/crates/tish_bytecode/src/peephole.rs +2 -2
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +1621 -453
- package/crates/tish_compile/src/infer.rs +75 -19
- package/crates/tish_compile/src/lib.rs +19 -8
- package/crates/tish_compile/src/resolve.rs +278 -137
- package/crates/tish_compile/src/types.rs +184 -24
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +181 -37
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
- package/crates/tish_compiler_wasm/src/lib.rs +16 -13
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +107 -56
- package/crates/tish_core/src/lib.rs +4 -2
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/uri.rs +9 -6
- package/crates/tish_core/src/value.rs +145 -43
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_cranelift/src/link.rs +6 -9
- package/crates/tish_cranelift/src/lower.rs +14 -8
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +474 -165
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +12 -8
- package/crates/tish_eval/src/natives.rs +136 -38
- package/crates/tish_eval/src/promise.rs +14 -8
- package/crates/tish_eval/src/timers.rs +28 -19
- package/crates/tish_eval/src/value.rs +17 -6
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +149 -43
- package/crates/tish_lexer/src/lib.rs +232 -63
- package/crates/tish_lexer/src/token.rs +10 -6
- package/crates/tish_llvm/src/lib.rs +17 -8
- package/crates/tish_lsp/Cargo.toml +4 -1
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_lsp/src/builtin_goto.rs +261 -0
- package/crates/tish_lsp/src/import_goto.rs +549 -0
- package/crates/tish_lsp/src/main.rs +504 -106
- package/crates/tish_native/src/build.rs +4 -8
- package/crates/tish_native/src/lib.rs +54 -21
- package/crates/tish_opt/src/lib.rs +84 -52
- package/crates/tish_parser/src/lib.rs +45 -13
- package/crates/tish_parser/src/parser.rs +505 -130
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3436 -0
- package/crates/tish_resolve/src/pos.rs +133 -0
- package/crates/tish_runtime/Cargo.toml +68 -3
- package/crates/tish_runtime/src/http.rs +1136 -145
- package/crates/tish_runtime/src/http_fetch.rs +38 -27
- 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 +375 -189
- package/crates/tish_runtime/src/promise.rs +199 -40
- package/crates/tish_runtime/src/promise_io.rs +2 -1
- package/crates/tish_runtime/src/timers.rs +37 -1
- package/crates/tish_runtime/src/ws.rs +65 -42
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
- package/crates/tish_ui/src/jsx.rs +317 -27
- package/crates/tish_ui/src/lib.rs +5 -2
- package/crates/tish_ui/src/runtime/hooks.rs +406 -45
- package/crates/tish_ui/src/runtime/mod.rs +36 -9
- package/crates/tish_vm/Cargo.toml +15 -5
- package/crates/tish_vm/src/vm.rs +725 -281
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
- package/crates/tish_wasm/src/lib.rs +55 -42
- package/crates/tish_wasm_runtime/Cargo.toml +2 -1
- package/crates/tish_wasm_runtime/src/lib.rs +1 -1
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
- package/justfile +8 -0
- package/package.json +1 -1
- 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
|
@@ -49,6 +49,7 @@ pub struct NativeBuildArtifacts {
|
|
|
49
49
|
const BUILTIN_ALIASES: &[(&str, &str)] = &[
|
|
50
50
|
("fs", "tish:fs"),
|
|
51
51
|
("http", "tish:http"),
|
|
52
|
+
("timers", "tish:timers"),
|
|
52
53
|
("process", "tish:process"),
|
|
53
54
|
("ws", "tish:ws"),
|
|
54
55
|
];
|
|
@@ -66,14 +67,17 @@ pub fn normalize_builtin_spec(spec: &str) -> Option<String> {
|
|
|
66
67
|
|
|
67
68
|
/// Built-in modules that come from tishlang_runtime, not from package.json.
|
|
68
69
|
pub fn is_builtin_native_spec(spec: &str) -> bool {
|
|
69
|
-
matches!(spec, "tish:fs" | "tish:http" | "tish:process" | "tish:ws")
|
|
70
|
-
|| matches!(spec, "fs" | "http" | "process" | "ws")
|
|
70
|
+
matches!(spec, "tish:fs" | "tish:http" | "tish:timers" | "tish:process" | "tish:ws")
|
|
71
|
+
|| matches!(spec, "fs" | "http" | "timers" | "process" | "ws")
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
/// Resolve all native imports in a merged program via package.json lookup.
|
|
74
75
|
/// Built-in modules (tish:fs, tish:http, tish:process) are skipped - they use tishlang_runtime directly.
|
|
75
76
|
/// Handles both lowered `NativeModuleLoad` (merged modules) and raw `import { … } from 'tish:…'`.
|
|
76
|
-
pub fn resolve_native_modules(
|
|
77
|
+
pub fn resolve_native_modules(
|
|
78
|
+
program: &Program,
|
|
79
|
+
project_root: &Path,
|
|
80
|
+
) -> Result<Vec<ResolvedNativeModule>, String> {
|
|
77
81
|
let root_canon = project_root
|
|
78
82
|
.canonicalize()
|
|
79
83
|
.map_err(|e| format!("Cannot canonicalize project root: {}", e))?;
|
|
@@ -131,12 +135,17 @@ pub fn cargo_export_fn_name(spec: &str) -> String {
|
|
|
131
135
|
out
|
|
132
136
|
}
|
|
133
137
|
|
|
134
|
-
fn resolve_cargo_native_module(
|
|
138
|
+
fn resolve_cargo_native_module(
|
|
139
|
+
spec: &str,
|
|
140
|
+
project_root: &Path,
|
|
141
|
+
) -> Result<ResolvedNativeModule, String> {
|
|
135
142
|
let tail = spec
|
|
136
143
|
.strip_prefix("cargo:")
|
|
137
144
|
.ok_or_else(|| format!("Invalid cargo native spec: {}", spec))?;
|
|
138
145
|
if tail.is_empty() {
|
|
139
|
-
return Err(
|
|
146
|
+
return Err(
|
|
147
|
+
"cargo: import needs a dependency name, e.g. import { x } from 'cargo:my_crate'".into(),
|
|
148
|
+
);
|
|
140
149
|
}
|
|
141
150
|
let dep_key = tail.to_string();
|
|
142
151
|
let tish = read_project_tish_config(project_root);
|
|
@@ -179,14 +188,26 @@ fn resolve_native_module(spec: &str, project_root: &Path) -> Result<ResolvedNati
|
|
|
179
188
|
let pkg_json = pkg_dir.join("package.json");
|
|
180
189
|
let content = std::fs::read_to_string(&pkg_json)
|
|
181
190
|
.map_err(|e| format!("Cannot read {}: {}", pkg_json.display(), e))?;
|
|
182
|
-
let json: serde_json::Value =
|
|
183
|
-
|
|
191
|
+
let json: serde_json::Value = serde_json::from_str(&content)
|
|
192
|
+
.map_err(|e| format!("Invalid JSON in {}: {}", pkg_json.display(), e))?;
|
|
184
193
|
let tish = json
|
|
185
194
|
.get("tish")
|
|
186
195
|
.and_then(|v| v.as_object())
|
|
187
|
-
.ok_or_else(||
|
|
188
|
-
|
|
189
|
-
|
|
196
|
+
.ok_or_else(|| {
|
|
197
|
+
format!(
|
|
198
|
+
"Package {} has no \"tish\" config in package.json",
|
|
199
|
+
package_name
|
|
200
|
+
)
|
|
201
|
+
})?;
|
|
202
|
+
if !tish
|
|
203
|
+
.get("module")
|
|
204
|
+
.and_then(|v| v.as_bool())
|
|
205
|
+
.unwrap_or(false)
|
|
206
|
+
{
|
|
207
|
+
return Err(format!(
|
|
208
|
+
"Package {} is not a Tish native module (tish.module must be true)",
|
|
209
|
+
package_name
|
|
210
|
+
));
|
|
190
211
|
}
|
|
191
212
|
let raw_crate = tish
|
|
192
213
|
.get("crate")
|
|
@@ -219,7 +240,9 @@ pub fn read_project_tish_config(project_root: &Path) -> serde_json::Value {
|
|
|
219
240
|
let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) else {
|
|
220
241
|
return serde_json::json!({});
|
|
221
242
|
};
|
|
222
|
-
json.get("tish")
|
|
243
|
+
json.get("tish")
|
|
244
|
+
.cloned()
|
|
245
|
+
.unwrap_or_else(|| serde_json::json!({}))
|
|
223
246
|
}
|
|
224
247
|
|
|
225
248
|
fn resolve_cargo_path_for_toml(project_root: &Path, raw: &str) -> String {
|
|
@@ -233,7 +256,10 @@ fn resolve_cargo_path_for_toml(project_root: &Path, raw: &str) -> String {
|
|
|
233
256
|
resolved.display().to_string().replace('\\', "/")
|
|
234
257
|
}
|
|
235
258
|
|
|
236
|
-
fn json_to_cargo_inline_value(
|
|
259
|
+
fn json_to_cargo_inline_value(
|
|
260
|
+
v: &serde_json::Value,
|
|
261
|
+
project_root: &Path,
|
|
262
|
+
) -> Result<String, String> {
|
|
237
263
|
match v {
|
|
238
264
|
serde_json::Value::String(s) => Ok(format!("{:?}", s.as_str())),
|
|
239
265
|
serde_json::Value::Bool(b) => Ok(b.to_string()),
|
|
@@ -264,7 +290,10 @@ fn json_to_cargo_inline_value(v: &serde_json::Value, project_root: &Path) -> Res
|
|
|
264
290
|
|
|
265
291
|
/// Serialize `tish.rustDependencies` from project `package.json` into Cargo.toml `[dependencies]` lines.
|
|
266
292
|
/// Relative `path = "…"` entries in inline tables are resolved against `project_root` so the temp build crate can find them.
|
|
267
|
-
pub fn format_rust_dependencies_toml(
|
|
293
|
+
pub fn format_rust_dependencies_toml(
|
|
294
|
+
tish: &serde_json::Value,
|
|
295
|
+
project_root: &Path,
|
|
296
|
+
) -> Result<String, String> {
|
|
268
297
|
let Some(obj) = tish.get("rustDependencies").and_then(|v| v.as_object()) else {
|
|
269
298
|
return Ok(String::new());
|
|
270
299
|
};
|
|
@@ -313,7 +342,10 @@ pub fn infer_native_module_exports(program: &Program) -> HashMap<String, HashSet
|
|
|
313
342
|
for stmt in &program.statements {
|
|
314
343
|
match stmt {
|
|
315
344
|
Statement::VarDecl {
|
|
316
|
-
init:
|
|
345
|
+
init:
|
|
346
|
+
Some(Expr::NativeModuleLoad {
|
|
347
|
+
spec, export_name, ..
|
|
348
|
+
}),
|
|
317
349
|
..
|
|
318
350
|
} => {
|
|
319
351
|
let s = spec.as_ref();
|
|
@@ -324,8 +356,11 @@ pub fn infer_native_module_exports(program: &Program) -> HashMap<String, HashSet
|
|
|
324
356
|
.or_default()
|
|
325
357
|
.insert(export_name.to_string());
|
|
326
358
|
}
|
|
327
|
-
Statement::Import {
|
|
328
|
-
|
|
359
|
+
Statement::Import {
|
|
360
|
+
specifiers, from, ..
|
|
361
|
+
} if is_native_import(from.as_ref()) => {
|
|
362
|
+
let spec =
|
|
363
|
+
normalize_builtin_spec(from.as_ref()).unwrap_or_else(|| from.to_string());
|
|
329
364
|
if is_builtin_native_spec(&spec) {
|
|
330
365
|
continue;
|
|
331
366
|
}
|
|
@@ -354,11 +389,15 @@ pub fn generate_native_wrapper_rs(
|
|
|
354
389
|
use std::cell::RefCell;\n\
|
|
355
390
|
use std::rc::Rc;\n\
|
|
356
391
|
use std::sync::Arc;\n\
|
|
357
|
-
use tishlang_runtime::{ObjectMap, Value};\n\n",
|
|
392
|
+
use tishlang_runtime::{ObjectMap, Value, VmRef};\n\n",
|
|
358
393
|
);
|
|
359
394
|
let mut any = false;
|
|
360
395
|
for m in modules {
|
|
361
|
-
let Some(NativeModuleInit::Generated {
|
|
396
|
+
let Some(NativeModuleInit::Generated {
|
|
397
|
+
shim_crate,
|
|
398
|
+
export_fn,
|
|
399
|
+
}) = init_by_spec.get(&m.spec)
|
|
400
|
+
else {
|
|
362
401
|
continue;
|
|
363
402
|
};
|
|
364
403
|
let Some(names) = inferred.get(&m.spec) else {
|
|
@@ -376,11 +415,11 @@ pub fn generate_native_wrapper_rs(
|
|
|
376
415
|
let rust_fn = export_name_to_rust_ident(&export_name);
|
|
377
416
|
let key_lit = format!("{:?}", export_name);
|
|
378
417
|
file.push_str(&format!(
|
|
379
|
-
" m.insert(Arc::from({}), Value::
|
|
418
|
+
" m.insert(Arc::from({}), Value::native(|args: &[Value]| {{\n {}::{}(args)\n }}));\n",
|
|
380
419
|
key_lit, shim_crate, rust_fn
|
|
381
420
|
));
|
|
382
421
|
}
|
|
383
|
-
file.push_str(" Value::Object(
|
|
422
|
+
file.push_str(" Value::Object(VmRef::new(m))\n}\n\n");
|
|
384
423
|
}
|
|
385
424
|
if !any {
|
|
386
425
|
return String::new();
|
|
@@ -405,9 +444,16 @@ pub fn compute_native_build_artifacts(
|
|
|
405
444
|
let mut native_init: HashMap<String, NativeModuleInit> = HashMap::new();
|
|
406
445
|
for m in native_modules {
|
|
407
446
|
let use_gen = if is_cargo_native_spec(&m.spec) {
|
|
408
|
-
inferred
|
|
447
|
+
inferred
|
|
448
|
+
.get(&m.spec)
|
|
449
|
+
.map(|s| !s.is_empty())
|
|
450
|
+
.unwrap_or(false)
|
|
409
451
|
} else {
|
|
410
|
-
gen_tish
|
|
452
|
+
gen_tish
|
|
453
|
+
&& inferred
|
|
454
|
+
.get(&m.spec)
|
|
455
|
+
.map(|s| !s.is_empty())
|
|
456
|
+
.unwrap_or(false)
|
|
411
457
|
};
|
|
412
458
|
let init = if use_gen {
|
|
413
459
|
NativeModuleInit::Generated {
|
|
@@ -538,13 +584,18 @@ pub fn resolve_project(
|
|
|
538
584
|
entry_path: &Path,
|
|
539
585
|
project_root: Option<&Path>,
|
|
540
586
|
) -> Result<Vec<ResolvedModule>, String> {
|
|
541
|
-
let project_root =
|
|
587
|
+
let project_root =
|
|
588
|
+
project_root.unwrap_or_else(|| entry_path.parent().unwrap_or(Path::new(".")));
|
|
542
589
|
let entry_canon = entry_path
|
|
543
590
|
.canonicalize()
|
|
544
591
|
.map_err(|e| format!("Cannot canonicalize entry {}: {}", entry_path.display(), e))?;
|
|
545
|
-
let root_canon = project_root
|
|
546
|
-
|
|
547
|
-
|
|
592
|
+
let root_canon = project_root.canonicalize().map_err(|e| {
|
|
593
|
+
format!(
|
|
594
|
+
"Cannot canonicalize project root {}: {}",
|
|
595
|
+
project_root.display(),
|
|
596
|
+
e
|
|
597
|
+
)
|
|
598
|
+
})?;
|
|
548
599
|
|
|
549
600
|
let mut visited = HashSet::new();
|
|
550
601
|
let mut path_to_module: HashMap<PathBuf, Program> = HashMap::new();
|
|
@@ -574,21 +625,23 @@ pub fn resolve_project_from_stdin(
|
|
|
574
625
|
source: &str,
|
|
575
626
|
project_root: &Path,
|
|
576
627
|
) -> Result<Vec<ResolvedModule>, String> {
|
|
577
|
-
let root_canon = project_root
|
|
578
|
-
|
|
579
|
-
|
|
628
|
+
let root_canon = project_root.canonicalize().map_err(|e| {
|
|
629
|
+
format!(
|
|
630
|
+
"Cannot canonicalize project root {}: {}",
|
|
631
|
+
project_root.display(),
|
|
632
|
+
e
|
|
633
|
+
)
|
|
634
|
+
})?;
|
|
580
635
|
|
|
581
636
|
let stdin_path = root_canon.join("<stdin>");
|
|
582
|
-
let program =
|
|
583
|
-
.map_err(|e| format!("Parse error (stdin): {}", e))?;
|
|
637
|
+
let program =
|
|
638
|
+
tishlang_parser::parse(source).map_err(|e| format!("Parse error (stdin): {}", e))?;
|
|
584
639
|
|
|
585
640
|
let mut visited = HashSet::new();
|
|
586
641
|
let mut path_to_module: HashMap<PathBuf, Program> = HashMap::new();
|
|
587
642
|
let mut load_order: Vec<PathBuf> = Vec::new();
|
|
588
643
|
|
|
589
|
-
let from_dir = stdin_path
|
|
590
|
-
.parent()
|
|
591
|
-
.unwrap_or_else(|| Path::new("."));
|
|
644
|
+
let from_dir = stdin_path.parent().unwrap_or_else(|| Path::new("."));
|
|
592
645
|
|
|
593
646
|
for stmt in &program.statements {
|
|
594
647
|
if let Statement::Import { from, .. } = stmt {
|
|
@@ -667,7 +720,7 @@ fn load_module_recursive(
|
|
|
667
720
|
}
|
|
668
721
|
|
|
669
722
|
/// Returns true for native module imports that don't resolve to files.
|
|
670
|
-
/// - fs, http, process, ws (Node-compatible aliases for tish
|
|
723
|
+
/// - fs, http, timers, process, ws (Node-compatible aliases for tish:*)
|
|
671
724
|
/// - tish:egui, tish:polars, etc.
|
|
672
725
|
/// - cargo:… (Cargo `rustDependencies` + generated wrapper; Rust native backend)
|
|
673
726
|
/// - @scope/package (npm-style)
|
|
@@ -675,7 +728,7 @@ pub fn is_native_import(spec: &str) -> bool {
|
|
|
675
728
|
spec.starts_with("tish:")
|
|
676
729
|
|| spec.starts_with("cargo:")
|
|
677
730
|
|| spec.starts_with('@')
|
|
678
|
-
|| matches!(spec, "fs" | "http" | "process" | "ws")
|
|
731
|
+
|| matches!(spec, "fs" | "http" | "timers" | "process" | "ws")
|
|
679
732
|
}
|
|
680
733
|
|
|
681
734
|
/// Map native spec to Cargo feature name for built-in tish:* modules.
|
|
@@ -684,30 +737,52 @@ pub fn native_spec_to_feature(spec: &str) -> Option<String> {
|
|
|
684
737
|
canonical.strip_prefix("tish:").map(|s| s.to_string())
|
|
685
738
|
}
|
|
686
739
|
|
|
687
|
-
/// Resolve
|
|
688
|
-
fn
|
|
740
|
+
/// Resolve `package.json` at `pkg_root` to the package's main `.tish` entry, if `name` matches `spec`.
|
|
741
|
+
fn resolve_package_root_to_entry(pkg_root: &Path, spec: &str) -> Option<PathBuf> {
|
|
742
|
+
let pkg_json = pkg_root.join("package.json");
|
|
743
|
+
if !pkg_json.exists() {
|
|
744
|
+
return None;
|
|
745
|
+
}
|
|
746
|
+
if read_package_name(&pkg_json).as_deref() != Some(spec) {
|
|
747
|
+
return None;
|
|
748
|
+
}
|
|
749
|
+
let content = std::fs::read_to_string(&pkg_json).ok()?;
|
|
750
|
+
let json: serde_json::Value = serde_json::from_str(&content).ok()?;
|
|
751
|
+
let entry = json
|
|
752
|
+
.get("tish")
|
|
753
|
+
.and_then(|t| t.get("module"))
|
|
754
|
+
.and_then(|m| m.as_str())
|
|
755
|
+
.or_else(|| json.get("main").and_then(|m| m.as_str()))
|
|
756
|
+
.unwrap_or("index.tish");
|
|
757
|
+
let entry_clean = entry.trim_start_matches("./");
|
|
758
|
+
let resolved = pkg_root.join(entry_clean);
|
|
759
|
+
if !resolved.exists() {
|
|
760
|
+
return None;
|
|
761
|
+
}
|
|
762
|
+
match resolved.canonicalize() {
|
|
763
|
+
Ok(p) => Some(p),
|
|
764
|
+
Err(_) => Some(resolved),
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/// Resolve a bare specifier (e.g. "lattish") to the package entry `.tish` file.
|
|
769
|
+
///
|
|
770
|
+
/// Walks upward from `from_dir` and, at each level, checks (same order as native [`find_package_dir`]):
|
|
771
|
+
/// - `node_modules/<spec>/`
|
|
772
|
+
/// - `<spec>/` as a sibling directory (monorepo: `…/tish/tish-candle` next to `…/tish/tish-hub`)
|
|
773
|
+
/// - the search directory itself if its `package.json` name matches `spec`
|
|
774
|
+
pub fn resolve_bare_spec(spec: &str, from_dir: &Path, _project_root: &Path) -> Option<PathBuf> {
|
|
689
775
|
let mut search = from_dir.to_path_buf();
|
|
690
776
|
loop {
|
|
691
|
-
let
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
.and_then(|t| t.get("module"))
|
|
701
|
-
.and_then(|m| m.as_str())
|
|
702
|
-
.or_else(|| json.get("main").and_then(|m| m.as_str()));
|
|
703
|
-
let entry = entry.unwrap_or("index.tish");
|
|
704
|
-
let entry_clean = entry.trim_start_matches("./");
|
|
705
|
-
let resolved = node_mod.join(entry_clean);
|
|
706
|
-
if resolved.exists() {
|
|
707
|
-
return resolved.canonicalize().ok();
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
}
|
|
777
|
+
if let Some(p) = resolve_package_root_to_entry(&search.join("node_modules").join(spec), spec)
|
|
778
|
+
{
|
|
779
|
+
return Some(p);
|
|
780
|
+
}
|
|
781
|
+
if let Some(p) = resolve_package_root_to_entry(&search.join(spec), spec) {
|
|
782
|
+
return Some(p);
|
|
783
|
+
}
|
|
784
|
+
if let Some(p) = resolve_package_root_to_entry(&search, spec) {
|
|
785
|
+
return Some(p);
|
|
711
786
|
}
|
|
712
787
|
if let Some(parent) = search.parent() {
|
|
713
788
|
if parent == search {
|
|
@@ -738,7 +813,7 @@ fn resolve_import_path(
|
|
|
738
813
|
return Ok(path);
|
|
739
814
|
}
|
|
740
815
|
return Err(format!(
|
|
741
|
-
"Package '{}' not found
|
|
816
|
+
"Package '{}' not found. Install with `npm install {}`, or place the package under node_modules/ or as a sibling directory (same layout as native `find_package_dir`).",
|
|
742
817
|
spec, spec
|
|
743
818
|
));
|
|
744
819
|
}
|
|
@@ -787,7 +862,10 @@ pub fn detect_cycles(modules: &[ResolvedModule]) -> Result<(), String> {
|
|
|
787
862
|
.iter()
|
|
788
863
|
.map(|&i| modules[i].path.display().to_string())
|
|
789
864
|
.collect();
|
|
790
|
-
return Err(format!(
|
|
865
|
+
return Err(format!(
|
|
866
|
+
"Circular import detected: {}",
|
|
867
|
+
path_names.join(" -> ")
|
|
868
|
+
));
|
|
791
869
|
}
|
|
792
870
|
}
|
|
793
871
|
Ok(())
|
|
@@ -817,14 +895,8 @@ fn has_cycle_from(
|
|
|
817
895
|
stack.push(dep_idx);
|
|
818
896
|
let dep = &modules[dep_idx];
|
|
819
897
|
let dep_dir = dep.path.parent().unwrap_or(Path::new("."));
|
|
820
|
-
if has_cycle_from(
|
|
821
|
-
|
|
822
|
-
&dep.program,
|
|
823
|
-
path_to_idx,
|
|
824
|
-
modules,
|
|
825
|
-
stack,
|
|
826
|
-
visiting,
|
|
827
|
-
)? {
|
|
898
|
+
if has_cycle_from(dep_dir, &dep.program, path_to_idx, modules, stack, visiting)?
|
|
899
|
+
{
|
|
828
900
|
return Ok(true);
|
|
829
901
|
}
|
|
830
902
|
stack.pop();
|
|
@@ -836,10 +908,27 @@ fn has_cycle_from(
|
|
|
836
908
|
Ok(false)
|
|
837
909
|
}
|
|
838
910
|
|
|
911
|
+
/// Result of [`merge_modules`]: merged AST plus, per top-level statement, the originating `.tish` file.
|
|
912
|
+
#[derive(Debug)]
|
|
913
|
+
pub struct MergedProgram {
|
|
914
|
+
pub program: Program,
|
|
915
|
+
pub statement_sources: Vec<PathBuf>,
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
fn merge_push(
|
|
919
|
+
statements: &mut Vec<Statement>,
|
|
920
|
+
statement_sources: &mut Vec<PathBuf>,
|
|
921
|
+
stmt: Statement,
|
|
922
|
+
source: PathBuf,
|
|
923
|
+
) {
|
|
924
|
+
statements.push(stmt);
|
|
925
|
+
statement_sources.push(source);
|
|
926
|
+
}
|
|
927
|
+
|
|
839
928
|
/// Merge all resolved modules into a single program. Dependencies are emitted first.
|
|
840
929
|
/// Import statements are rewritten as bindings from already-emitted dep exports.
|
|
841
930
|
/// Export statements are unwrapped (the inner declaration is emitted).
|
|
842
|
-
pub fn merge_modules(modules: Vec<ResolvedModule>) -> Result<
|
|
931
|
+
pub fn merge_modules(modules: Vec<ResolvedModule>) -> Result<MergedProgram, String> {
|
|
843
932
|
let path_to_idx: HashMap<PathBuf, usize> = modules
|
|
844
933
|
.iter()
|
|
845
934
|
.enumerate()
|
|
@@ -870,45 +959,62 @@ pub fn merge_modules(modules: Vec<ResolvedModule>) -> Result<Program, String> {
|
|
|
870
959
|
}
|
|
871
960
|
|
|
872
961
|
let mut statements = Vec::new();
|
|
962
|
+
let mut statement_sources = Vec::new();
|
|
873
963
|
for (idx, module) in modules.iter().enumerate() {
|
|
964
|
+
let src_path = module.path.clone();
|
|
874
965
|
let dir = module.path.parent().unwrap_or(Path::new("."));
|
|
875
966
|
for stmt in &module.program.statements {
|
|
876
967
|
match stmt {
|
|
877
|
-
Statement::Import {
|
|
968
|
+
Statement::Import {
|
|
969
|
+
specifiers,
|
|
970
|
+
from,
|
|
971
|
+
span,
|
|
972
|
+
} => {
|
|
878
973
|
if is_native_import(from.as_ref()) {
|
|
879
974
|
// Normalize fs/http/process -> tish:fs etc. for Node compatibility
|
|
880
|
-
let canonical_spec =
|
|
881
|
-
|
|
882
|
-
.unwrap_or_else(|| from.to_string());
|
|
975
|
+
let canonical_spec = normalize_builtin_spec(from.as_ref())
|
|
976
|
+
.unwrap_or_else(|| from.to_string());
|
|
883
977
|
// Emit VarDecl with NativeModuleLoad for each specifier
|
|
884
978
|
for spec in specifiers {
|
|
885
979
|
match spec {
|
|
886
|
-
ImportSpecifier::Named {
|
|
980
|
+
ImportSpecifier::Named {
|
|
981
|
+
name,
|
|
982
|
+
name_span,
|
|
983
|
+
alias,
|
|
984
|
+
alias_span,
|
|
985
|
+
} => {
|
|
887
986
|
let bind = alias.as_deref().unwrap_or(name.as_ref());
|
|
987
|
+
let decl_name_span = alias_span.as_ref().unwrap_or(name_span);
|
|
888
988
|
let init = Expr::NativeModuleLoad {
|
|
889
989
|
spec: Arc::from(canonical_spec.clone()),
|
|
890
990
|
export_name: name.clone(),
|
|
891
991
|
span: *span,
|
|
892
992
|
};
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
993
|
+
merge_push(
|
|
994
|
+
&mut statements,
|
|
995
|
+
&mut statement_sources,
|
|
996
|
+
Statement::VarDecl {
|
|
997
|
+
name: Arc::from(bind),
|
|
998
|
+
name_span: *decl_name_span,
|
|
999
|
+
mutable: false,
|
|
1000
|
+
type_ann: None,
|
|
1001
|
+
init: Some(init),
|
|
1002
|
+
span: *span,
|
|
1003
|
+
},
|
|
1004
|
+
src_path.clone(),
|
|
1005
|
+
);
|
|
900
1006
|
}
|
|
901
|
-
ImportSpecifier::Namespace
|
|
1007
|
+
ImportSpecifier::Namespace { name, .. } => {
|
|
902
1008
|
return Err(format!(
|
|
903
1009
|
"Namespace import (* as {}) not supported for native module '{}'",
|
|
904
|
-
|
|
1010
|
+
name.as_ref(),
|
|
905
1011
|
from.as_ref()
|
|
906
1012
|
));
|
|
907
1013
|
}
|
|
908
|
-
ImportSpecifier::Default
|
|
1014
|
+
ImportSpecifier::Default { name, .. } => {
|
|
909
1015
|
return Err(format!(
|
|
910
1016
|
"Default import '{}' not supported for native module '{}'. Use named import, e.g. import {{ egui }} from '{}'",
|
|
911
|
-
|
|
1017
|
+
name.as_ref(),
|
|
912
1018
|
from.as_ref(),
|
|
913
1019
|
from.as_ref()
|
|
914
1020
|
));
|
|
@@ -918,35 +1024,45 @@ pub fn merge_modules(modules: Vec<ResolvedModule>) -> Result<Program, String> {
|
|
|
918
1024
|
continue;
|
|
919
1025
|
}
|
|
920
1026
|
let dep_path = resolve_import_path(from.as_ref(), dir, Path::new("."))?;
|
|
921
|
-
let dep_path = dep_path
|
|
922
|
-
.canonicalize()
|
|
923
|
-
.unwrap_or(dep_path);
|
|
1027
|
+
let dep_path = dep_path.canonicalize().unwrap_or(dep_path);
|
|
924
1028
|
let dep_idx = *path_to_idx
|
|
925
1029
|
.get(&dep_path)
|
|
926
1030
|
.ok_or_else(|| format!("Resolved import '{}' not in module list", from))?;
|
|
927
1031
|
let dep_exports = &module_exports[dep_idx];
|
|
928
1032
|
for spec in specifiers {
|
|
929
1033
|
match spec {
|
|
930
|
-
ImportSpecifier::Named {
|
|
1034
|
+
ImportSpecifier::Named {
|
|
1035
|
+
name,
|
|
1036
|
+
name_span,
|
|
1037
|
+
alias,
|
|
1038
|
+
alias_span,
|
|
1039
|
+
} => {
|
|
931
1040
|
let source = dep_exports
|
|
932
1041
|
.get(name.as_ref())
|
|
933
1042
|
.cloned()
|
|
934
1043
|
.unwrap_or_else(|| name.to_string());
|
|
935
1044
|
let bind = alias.as_deref().unwrap_or(name.as_ref());
|
|
936
1045
|
if bind != source {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
name: Arc::from(
|
|
1046
|
+
let decl_name_span = alias_span.as_ref().unwrap_or(name_span);
|
|
1047
|
+
merge_push(
|
|
1048
|
+
&mut statements,
|
|
1049
|
+
&mut statement_sources,
|
|
1050
|
+
Statement::VarDecl {
|
|
1051
|
+
name: Arc::from(bind),
|
|
1052
|
+
name_span: *decl_name_span,
|
|
1053
|
+
mutable: false,
|
|
1054
|
+
type_ann: None,
|
|
1055
|
+
init: Some(Expr::Ident {
|
|
1056
|
+
name: Arc::from(source),
|
|
1057
|
+
span: *span,
|
|
1058
|
+
}),
|
|
943
1059
|
span: *span,
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
|
|
1060
|
+
},
|
|
1061
|
+
src_path.clone(),
|
|
1062
|
+
);
|
|
947
1063
|
}
|
|
948
1064
|
}
|
|
949
|
-
ImportSpecifier::Namespace
|
|
1065
|
+
ImportSpecifier::Namespace { name, name_span } => {
|
|
950
1066
|
let mut props = Vec::new();
|
|
951
1067
|
for (k, v) in dep_exports {
|
|
952
1068
|
props.push(tishlang_ast::ObjectProp::KeyValue(
|
|
@@ -957,58 +1073,83 @@ pub fn merge_modules(modules: Vec<ResolvedModule>) -> Result<Program, String> {
|
|
|
957
1073
|
},
|
|
958
1074
|
));
|
|
959
1075
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1076
|
+
merge_push(
|
|
1077
|
+
&mut statements,
|
|
1078
|
+
&mut statement_sources,
|
|
1079
|
+
Statement::VarDecl {
|
|
1080
|
+
name: name.clone(),
|
|
1081
|
+
name_span: *name_span,
|
|
1082
|
+
mutable: false,
|
|
1083
|
+
type_ann: None,
|
|
1084
|
+
init: Some(Expr::Object { props, span: *span }),
|
|
966
1085
|
span: *span,
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
|
|
1086
|
+
},
|
|
1087
|
+
src_path.clone(),
|
|
1088
|
+
);
|
|
970
1089
|
}
|
|
971
|
-
ImportSpecifier::Default
|
|
972
|
-
let source =
|
|
973
|
-
.get("default")
|
|
974
|
-
.cloned()
|
|
975
|
-
.ok_or_else(|| {
|
|
1090
|
+
ImportSpecifier::Default { name, name_span } => {
|
|
1091
|
+
let source =
|
|
1092
|
+
dep_exports.get("default").cloned().ok_or_else(|| {
|
|
976
1093
|
format!("Module '{}' has no default export", from)
|
|
977
1094
|
})?;
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1095
|
+
merge_push(
|
|
1096
|
+
&mut statements,
|
|
1097
|
+
&mut statement_sources,
|
|
1098
|
+
Statement::VarDecl {
|
|
1099
|
+
name: name.clone(),
|
|
1100
|
+
name_span: *name_span,
|
|
1101
|
+
mutable: false,
|
|
1102
|
+
type_ann: None,
|
|
1103
|
+
init: Some(Expr::Ident {
|
|
1104
|
+
name: Arc::from(source),
|
|
1105
|
+
span: *span,
|
|
1106
|
+
}),
|
|
984
1107
|
span: *span,
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
|
|
1108
|
+
},
|
|
1109
|
+
src_path.clone(),
|
|
1110
|
+
);
|
|
988
1111
|
}
|
|
989
1112
|
}
|
|
990
1113
|
}
|
|
991
1114
|
}
|
|
992
|
-
Statement::Export { declaration, .. } => {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1115
|
+
Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
1116
|
+
ExportDeclaration::Named(s) => merge_push(
|
|
1117
|
+
&mut statements,
|
|
1118
|
+
&mut statement_sources,
|
|
1119
|
+
*s.clone(),
|
|
1120
|
+
src_path.clone(),
|
|
1121
|
+
),
|
|
1122
|
+
ExportDeclaration::Default(e) => {
|
|
1123
|
+
let default_name = format!("__default_{}", idx);
|
|
1124
|
+
let espan = e.span();
|
|
1125
|
+
merge_push(
|
|
1126
|
+
&mut statements,
|
|
1127
|
+
&mut statement_sources,
|
|
1128
|
+
Statement::VarDecl {
|
|
998
1129
|
name: Arc::from(default_name),
|
|
1130
|
+
name_span: espan,
|
|
999
1131
|
mutable: false,
|
|
1000
1132
|
type_ann: None,
|
|
1001
1133
|
init: Some((*e).clone()),
|
|
1002
|
-
span:
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1134
|
+
span: espan,
|
|
1135
|
+
},
|
|
1136
|
+
src_path.clone(),
|
|
1137
|
+
);
|
|
1005
1138
|
}
|
|
1006
|
-
}
|
|
1007
|
-
_ =>
|
|
1139
|
+
},
|
|
1140
|
+
_ => merge_push(
|
|
1141
|
+
&mut statements,
|
|
1142
|
+
&mut statement_sources,
|
|
1143
|
+
stmt.clone(),
|
|
1144
|
+
src_path.clone(),
|
|
1145
|
+
),
|
|
1008
1146
|
}
|
|
1009
1147
|
}
|
|
1010
1148
|
}
|
|
1011
|
-
Ok(
|
|
1149
|
+
Ok(MergedProgram {
|
|
1150
|
+
program: Program { statements },
|
|
1151
|
+
statement_sources,
|
|
1152
|
+
})
|
|
1012
1153
|
}
|
|
1013
1154
|
|
|
1014
1155
|
#[cfg(test)]
|
|
@@ -1045,14 +1186,14 @@ mod cargo_spec_tests {
|
|
|
1045
1186
|
std::fs::write(&p, src).unwrap();
|
|
1046
1187
|
let root = dir.path();
|
|
1047
1188
|
let modules = resolve_project(&p, Some(root)).unwrap();
|
|
1048
|
-
merge_modules(modules).unwrap();
|
|
1189
|
+
let _ = merge_modules(modules).unwrap();
|
|
1049
1190
|
}
|
|
1050
1191
|
|
|
1051
1192
|
#[test]
|
|
1052
1193
|
fn cargo_export_fn_name_sanitizes() {
|
|
1053
1194
|
assert_eq!(
|
|
1054
|
-
cargo_export_fn_name("cargo:
|
|
1055
|
-
"
|
|
1195
|
+
cargo_export_fn_name("cargo:tish_serde_json"),
|
|
1196
|
+
"cargo_native_tish_serde_json_object"
|
|
1056
1197
|
);
|
|
1057
1198
|
assert_eq!(
|
|
1058
1199
|
cargo_export_fn_name("cargo:my-crate"),
|