@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,473 @@
|
|
|
1
|
+
//! Virtual module resolution for browser/playground. No filesystem.
|
|
2
|
+
//! Resolves imports from an in-memory file map, merges modules into a single Program.
|
|
3
|
+
|
|
4
|
+
use std::collections::{HashMap, HashSet};
|
|
5
|
+
use std::sync::Arc;
|
|
6
|
+
|
|
7
|
+
use tishlang_ast::{ExportDeclaration, Expr, ImportSpecifier, Program, Statement};
|
|
8
|
+
|
|
9
|
+
/// A resolved module: virtual path and its parsed program.
|
|
10
|
+
#[derive(Debug, Clone)]
|
|
11
|
+
pub struct VirtualModule {
|
|
12
|
+
pub path: String,
|
|
13
|
+
pub program: Program,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Node-compatible aliases for built-in modules (fs -> tish:fs, etc.).
|
|
17
|
+
const BUILTIN_ALIASES: &[(&str, &str)] = &[
|
|
18
|
+
("fs", "tish:fs"),
|
|
19
|
+
("http", "tish:http"),
|
|
20
|
+
("timers", "tish:timers"),
|
|
21
|
+
("process", "tish:process"),
|
|
22
|
+
("ws", "tish:ws"),
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
fn normalize_builtin_spec(spec: &str) -> Option<String> {
|
|
26
|
+
if spec.starts_with("tish:") {
|
|
27
|
+
return Some(spec.to_string());
|
|
28
|
+
}
|
|
29
|
+
BUILTIN_ALIASES
|
|
30
|
+
.iter()
|
|
31
|
+
.find(|(alias, _)| *alias == spec)
|
|
32
|
+
.map(|(_, canonical)| (*canonical).to_string())
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn is_native_import(spec: &str) -> bool {
|
|
36
|
+
spec.starts_with("tish:")
|
|
37
|
+
|| spec.starts_with("cargo:")
|
|
38
|
+
|| spec.starts_with('@')
|
|
39
|
+
|| matches!(spec, "fs" | "http" | "timers" | "process" | "ws")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Normalize a virtual path: resolve . and .. components.
|
|
43
|
+
/// e.g. "sub/../lib.tish" -> "lib.tish", "./foo.tish" -> "foo.tish"
|
|
44
|
+
fn normalize_virtual_path(from_dir: &str, spec: &str) -> Result<String, String> {
|
|
45
|
+
if !spec.starts_with("./") && !spec.starts_with("../") {
|
|
46
|
+
return Err(format!(
|
|
47
|
+
"Only relative imports (./, ../) or native imports (tish:*, @scope/pkg) are supported. Got: {}",
|
|
48
|
+
spec
|
|
49
|
+
));
|
|
50
|
+
}
|
|
51
|
+
let combined = if from_dir.is_empty() {
|
|
52
|
+
spec.to_string()
|
|
53
|
+
} else {
|
|
54
|
+
format!("{}/{}", from_dir, spec)
|
|
55
|
+
};
|
|
56
|
+
let parts: Vec<&str> = combined.split('/').collect();
|
|
57
|
+
let mut stack: Vec<&str> = Vec::new();
|
|
58
|
+
for p in parts {
|
|
59
|
+
match p {
|
|
60
|
+
"" | "." => {}
|
|
61
|
+
".." => {
|
|
62
|
+
stack.pop();
|
|
63
|
+
}
|
|
64
|
+
_ => stack.push(p),
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
Ok(stack.join("/"))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Parent directory of a virtual path. "main.tish" -> "", "sub/foo.tish" -> "sub"
|
|
71
|
+
fn parent_dir(path: &str) -> &str {
|
|
72
|
+
if let Some(slash) = path.rfind('/') {
|
|
73
|
+
&path[..slash]
|
|
74
|
+
} else {
|
|
75
|
+
""
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Resolve import spec to a key for the files map. Tries .tish extension if missing.
|
|
80
|
+
fn resolve_import_to_key(
|
|
81
|
+
spec: &str,
|
|
82
|
+
from_dir: &str,
|
|
83
|
+
files: &HashMap<String, String>,
|
|
84
|
+
) -> Result<String, String> {
|
|
85
|
+
let normalized = normalize_virtual_path(from_dir, spec)?;
|
|
86
|
+
if files.contains_key(&normalized) {
|
|
87
|
+
return Ok(normalized);
|
|
88
|
+
}
|
|
89
|
+
if !normalized.ends_with(".tish") && !normalized.contains('.') {
|
|
90
|
+
let with_ext = format!("{}.tish", normalized);
|
|
91
|
+
if files.contains_key(&with_ext) {
|
|
92
|
+
return Ok(with_ext);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
Err(format!(
|
|
96
|
+
"Cannot resolve import '{}' from {}: file not in virtual file map",
|
|
97
|
+
spec, from_dir
|
|
98
|
+
))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Resolve all modules starting from the entry file. Returns modules in dependency order.
|
|
102
|
+
pub fn resolve_virtual(
|
|
103
|
+
entry_path: &str,
|
|
104
|
+
files: &HashMap<String, String>,
|
|
105
|
+
) -> Result<Vec<VirtualModule>, String> {
|
|
106
|
+
let entry_key = if files.contains_key(entry_path) {
|
|
107
|
+
entry_path.to_string()
|
|
108
|
+
} else if !entry_path.ends_with(".tish") {
|
|
109
|
+
let with_ext = format!("{}.tish", entry_path);
|
|
110
|
+
if files.contains_key(&with_ext) {
|
|
111
|
+
with_ext
|
|
112
|
+
} else {
|
|
113
|
+
return Err(format!(
|
|
114
|
+
"Entry file '{}' not in virtual file map",
|
|
115
|
+
entry_path
|
|
116
|
+
));
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
return Err(format!(
|
|
120
|
+
"Entry file '{}' not in virtual file map",
|
|
121
|
+
entry_path
|
|
122
|
+
));
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
let mut visited = HashSet::new();
|
|
126
|
+
let mut path_to_module: HashMap<String, Program> = HashMap::new();
|
|
127
|
+
let mut load_order: Vec<String> = Vec::new();
|
|
128
|
+
|
|
129
|
+
load_module_recursive(
|
|
130
|
+
&entry_key,
|
|
131
|
+
files,
|
|
132
|
+
&mut visited,
|
|
133
|
+
&mut path_to_module,
|
|
134
|
+
&mut load_order,
|
|
135
|
+
)?;
|
|
136
|
+
|
|
137
|
+
Ok(load_order
|
|
138
|
+
.into_iter()
|
|
139
|
+
.map(|p| {
|
|
140
|
+
let program = path_to_module.remove(&p).unwrap();
|
|
141
|
+
VirtualModule { path: p, program }
|
|
142
|
+
})
|
|
143
|
+
.collect())
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fn load_module_recursive(
|
|
147
|
+
module_path: &str,
|
|
148
|
+
files: &HashMap<String, String>,
|
|
149
|
+
visited: &mut HashSet<String>,
|
|
150
|
+
path_to_module: &mut HashMap<String, Program>,
|
|
151
|
+
load_order: &mut Vec<String>,
|
|
152
|
+
) -> Result<(), String> {
|
|
153
|
+
if visited.contains(module_path) {
|
|
154
|
+
return Ok(());
|
|
155
|
+
}
|
|
156
|
+
visited.insert(module_path.to_string());
|
|
157
|
+
|
|
158
|
+
let source = files
|
|
159
|
+
.get(module_path)
|
|
160
|
+
.ok_or_else(|| format!("Module '{}' not in virtual file map", module_path))?;
|
|
161
|
+
let program = tishlang_parser::parse(source.trim())
|
|
162
|
+
.map_err(|e| format!("Parse error in {}: {}", module_path, e))?;
|
|
163
|
+
|
|
164
|
+
let from_dir = parent_dir(module_path);
|
|
165
|
+
for stmt in &program.statements {
|
|
166
|
+
if let Statement::Import { from, .. } = stmt {
|
|
167
|
+
if is_native_import(from) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
let dep_key = resolve_import_to_key(from, from_dir, files)?;
|
|
171
|
+
if !path_to_module.contains_key(&dep_key) {
|
|
172
|
+
load_module_recursive(&dep_key, files, visited, path_to_module, load_order)?;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
path_to_module.insert(module_path.to_string(), program);
|
|
178
|
+
load_order.push(module_path.to_string());
|
|
179
|
+
Ok(())
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// Check for cyclic imports.
|
|
183
|
+
pub fn detect_cycles_virtual(modules: &[VirtualModule]) -> Result<(), String> {
|
|
184
|
+
let path_to_idx: HashMap<_, _> = modules
|
|
185
|
+
.iter()
|
|
186
|
+
.enumerate()
|
|
187
|
+
.map(|(i, m)| (m.path.clone(), i))
|
|
188
|
+
.collect();
|
|
189
|
+
|
|
190
|
+
for (idx, module) in modules.iter().enumerate() {
|
|
191
|
+
let dir = parent_dir(&module.path);
|
|
192
|
+
let mut stack = vec![idx];
|
|
193
|
+
if has_cycle_from(
|
|
194
|
+
dir,
|
|
195
|
+
&module.program,
|
|
196
|
+
&path_to_idx,
|
|
197
|
+
modules,
|
|
198
|
+
&mut stack,
|
|
199
|
+
&mut HashSet::new(),
|
|
200
|
+
)? {
|
|
201
|
+
let path_names: Vec<_> = stack.iter().map(|&i| modules[i].path.clone()).collect();
|
|
202
|
+
return Err(format!(
|
|
203
|
+
"Circular import detected: {}",
|
|
204
|
+
path_names.join(" -> ")
|
|
205
|
+
));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
Ok(())
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
fn has_cycle_from(
|
|
212
|
+
from_dir: &str,
|
|
213
|
+
program: &Program,
|
|
214
|
+
path_to_idx: &HashMap<String, usize>,
|
|
215
|
+
modules: &[VirtualModule],
|
|
216
|
+
stack: &mut Vec<usize>,
|
|
217
|
+
visiting: &mut HashSet<usize>,
|
|
218
|
+
) -> Result<bool, String> {
|
|
219
|
+
for stmt in &program.statements {
|
|
220
|
+
if let Statement::Import { from, .. } = stmt {
|
|
221
|
+
if is_native_import(from) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
let dep_key = resolve_import_to_key_for_cycle(from, from_dir, path_to_idx)?;
|
|
225
|
+
if let Some(&dep_idx) = path_to_idx.get(&dep_key) {
|
|
226
|
+
if stack.contains(&dep_idx) {
|
|
227
|
+
stack.push(dep_idx);
|
|
228
|
+
return Ok(true);
|
|
229
|
+
}
|
|
230
|
+
if !visiting.contains(&dep_idx) {
|
|
231
|
+
visiting.insert(dep_idx);
|
|
232
|
+
stack.push(dep_idx);
|
|
233
|
+
let dep = &modules[dep_idx];
|
|
234
|
+
let dep_dir = parent_dir(&dep.path);
|
|
235
|
+
if has_cycle_from(dep_dir, &dep.program, path_to_idx, modules, stack, visiting)?
|
|
236
|
+
{
|
|
237
|
+
return Ok(true);
|
|
238
|
+
}
|
|
239
|
+
stack.pop();
|
|
240
|
+
visiting.remove(&dep_idx);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
Ok(false)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
fn resolve_import_to_key_for_cycle(
|
|
249
|
+
spec: &str,
|
|
250
|
+
from_dir: &str,
|
|
251
|
+
path_to_idx: &HashMap<String, usize>,
|
|
252
|
+
) -> Result<String, String> {
|
|
253
|
+
let normalized = normalize_virtual_path(from_dir, spec)?;
|
|
254
|
+
if path_to_idx.contains_key(&normalized) {
|
|
255
|
+
return Ok(normalized);
|
|
256
|
+
}
|
|
257
|
+
if !normalized.ends_with(".tish") && !normalized.contains('.') {
|
|
258
|
+
let with_ext = format!("{}.tish", normalized);
|
|
259
|
+
if path_to_idx.contains_key(&with_ext) {
|
|
260
|
+
return Ok(with_ext);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
Err(format!(
|
|
264
|
+
"Cannot resolve import '{}' from {}: module not in resolved set",
|
|
265
|
+
spec, from_dir
|
|
266
|
+
))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/// Merge all resolved modules into a single program. Dependencies are emitted first.
|
|
270
|
+
pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, String> {
|
|
271
|
+
let path_to_idx: HashMap<String, usize> = modules
|
|
272
|
+
.iter()
|
|
273
|
+
.enumerate()
|
|
274
|
+
.map(|(i, m)| (m.path.clone(), i))
|
|
275
|
+
.collect();
|
|
276
|
+
|
|
277
|
+
let mut module_exports: Vec<HashMap<String, String>> = vec![HashMap::new(); modules.len()];
|
|
278
|
+
for (idx, module) in modules.iter().enumerate() {
|
|
279
|
+
for stmt in &module.program.statements {
|
|
280
|
+
if let Statement::Export { declaration, .. } = stmt {
|
|
281
|
+
match declaration.as_ref() {
|
|
282
|
+
ExportDeclaration::Named(s) => {
|
|
283
|
+
let name = match s.as_ref() {
|
|
284
|
+
Statement::VarDecl { name, .. } | Statement::FunDecl { name, .. } => {
|
|
285
|
+
name.to_string()
|
|
286
|
+
}
|
|
287
|
+
_ => continue,
|
|
288
|
+
};
|
|
289
|
+
module_exports[idx].insert(name.clone(), name);
|
|
290
|
+
}
|
|
291
|
+
ExportDeclaration::Default(_) => {
|
|
292
|
+
let default_name = format!("__default_{}", idx);
|
|
293
|
+
module_exports[idx].insert("default".to_string(), default_name);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let mut statements = Vec::new();
|
|
301
|
+
for (idx, module) in modules.iter().enumerate() {
|
|
302
|
+
let dir = parent_dir(&module.path);
|
|
303
|
+
for stmt in &module.program.statements {
|
|
304
|
+
match stmt {
|
|
305
|
+
Statement::Import {
|
|
306
|
+
specifiers,
|
|
307
|
+
from,
|
|
308
|
+
span,
|
|
309
|
+
} => {
|
|
310
|
+
if is_native_import(from) {
|
|
311
|
+
let canonical_spec =
|
|
312
|
+
normalize_builtin_spec(from).unwrap_or_else(|| from.to_string());
|
|
313
|
+
for spec in specifiers {
|
|
314
|
+
match spec {
|
|
315
|
+
ImportSpecifier::Named {
|
|
316
|
+
name,
|
|
317
|
+
name_span,
|
|
318
|
+
alias,
|
|
319
|
+
alias_span,
|
|
320
|
+
} => {
|
|
321
|
+
let bind = alias.as_deref().unwrap_or(name.as_ref());
|
|
322
|
+
let decl_name_span = alias_span.as_ref().unwrap_or(name_span);
|
|
323
|
+
let init = Expr::NativeModuleLoad {
|
|
324
|
+
spec: Arc::from(canonical_spec.clone()),
|
|
325
|
+
export_name: name.clone(),
|
|
326
|
+
span: *span,
|
|
327
|
+
};
|
|
328
|
+
statements.push(Statement::VarDecl {
|
|
329
|
+
name: Arc::from(bind),
|
|
330
|
+
name_span: *decl_name_span,
|
|
331
|
+
mutable: false,
|
|
332
|
+
type_ann: None,
|
|
333
|
+
init: Some(init),
|
|
334
|
+
span: *span,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
ImportSpecifier::Namespace { name, .. } => {
|
|
338
|
+
return Err(format!(
|
|
339
|
+
"Namespace import (* as {}) not supported for native module '{}'",
|
|
340
|
+
name.as_ref(),
|
|
341
|
+
from.as_ref()
|
|
342
|
+
));
|
|
343
|
+
}
|
|
344
|
+
ImportSpecifier::Default { name, .. } => {
|
|
345
|
+
return Err(format!(
|
|
346
|
+
"Default import '{}' not supported for native module '{}'. Use named import.",
|
|
347
|
+
name.as_ref(),
|
|
348
|
+
from.as_ref()
|
|
349
|
+
));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
let dep_key = resolve_import_to_key_for_cycle(from, dir, &path_to_idx)?;
|
|
356
|
+
let dep_idx = *path_to_idx
|
|
357
|
+
.get(&dep_key)
|
|
358
|
+
.ok_or_else(|| format!("Resolved import '{}' not in module list", from))?;
|
|
359
|
+
let dep_exports = &module_exports[dep_idx];
|
|
360
|
+
for spec in specifiers {
|
|
361
|
+
match spec {
|
|
362
|
+
ImportSpecifier::Named {
|
|
363
|
+
name,
|
|
364
|
+
name_span,
|
|
365
|
+
alias,
|
|
366
|
+
alias_span,
|
|
367
|
+
} => {
|
|
368
|
+
let source = dep_exports
|
|
369
|
+
.get(name.as_ref())
|
|
370
|
+
.cloned()
|
|
371
|
+
.unwrap_or_else(|| name.to_string());
|
|
372
|
+
let bind = alias.as_deref().unwrap_or(name.as_ref());
|
|
373
|
+
if bind != source {
|
|
374
|
+
let decl_name_span = alias_span.as_ref().unwrap_or(name_span);
|
|
375
|
+
statements.push(Statement::VarDecl {
|
|
376
|
+
name: Arc::from(bind),
|
|
377
|
+
name_span: *decl_name_span,
|
|
378
|
+
mutable: false,
|
|
379
|
+
type_ann: None,
|
|
380
|
+
init: Some(Expr::Ident {
|
|
381
|
+
name: Arc::from(source),
|
|
382
|
+
span: *span,
|
|
383
|
+
}),
|
|
384
|
+
span: *span,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
ImportSpecifier::Namespace { name, name_span } => {
|
|
389
|
+
let mut props = Vec::new();
|
|
390
|
+
for (k, v) in dep_exports {
|
|
391
|
+
props.push(tishlang_ast::ObjectProp::KeyValue(
|
|
392
|
+
Arc::from(k.clone()),
|
|
393
|
+
Expr::Ident {
|
|
394
|
+
name: Arc::from(v.clone()),
|
|
395
|
+
span: *span,
|
|
396
|
+
},
|
|
397
|
+
));
|
|
398
|
+
}
|
|
399
|
+
statements.push(Statement::VarDecl {
|
|
400
|
+
name: name.clone(),
|
|
401
|
+
name_span: *name_span,
|
|
402
|
+
mutable: false,
|
|
403
|
+
type_ann: None,
|
|
404
|
+
init: Some(Expr::Object { props, span: *span }),
|
|
405
|
+
span: *span,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
ImportSpecifier::Default { name, name_span } => {
|
|
409
|
+
let source =
|
|
410
|
+
dep_exports.get("default").cloned().ok_or_else(|| {
|
|
411
|
+
format!("Module '{}' has no default export", from)
|
|
412
|
+
})?;
|
|
413
|
+
statements.push(Statement::VarDecl {
|
|
414
|
+
name: name.clone(),
|
|
415
|
+
name_span: *name_span,
|
|
416
|
+
mutable: false,
|
|
417
|
+
type_ann: None,
|
|
418
|
+
init: Some(Expr::Ident {
|
|
419
|
+
name: Arc::from(source),
|
|
420
|
+
span: *span,
|
|
421
|
+
}),
|
|
422
|
+
span: *span,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
429
|
+
ExportDeclaration::Named(s) => statements.push(*s.clone()),
|
|
430
|
+
ExportDeclaration::Default(e) => {
|
|
431
|
+
let default_name = format!("__default_{}", idx);
|
|
432
|
+
let espan = e.span();
|
|
433
|
+
statements.push(Statement::VarDecl {
|
|
434
|
+
name: Arc::from(default_name),
|
|
435
|
+
name_span: espan,
|
|
436
|
+
mutable: false,
|
|
437
|
+
type_ann: None,
|
|
438
|
+
init: Some((*e).clone()),
|
|
439
|
+
span: espan,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
_ => statements.push(stmt.clone()),
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
Ok(Program { statements })
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
#[cfg(test)]
|
|
451
|
+
mod tests {
|
|
452
|
+
use super::*;
|
|
453
|
+
|
|
454
|
+
#[test]
|
|
455
|
+
fn test_resolve_virtual_simple_import() {
|
|
456
|
+
let mut files = HashMap::new();
|
|
457
|
+
files.insert(
|
|
458
|
+
"lib.tish".to_string(),
|
|
459
|
+
"export fn add(a, b) { return a + b }".to_string(),
|
|
460
|
+
);
|
|
461
|
+
files.insert(
|
|
462
|
+
"main.tish".to_string(),
|
|
463
|
+
"import { add } from \"./lib.tish\"\nconsole.log(add(1, 2))".to_string(),
|
|
464
|
+
);
|
|
465
|
+
let modules = resolve_virtual("main.tish", &files).unwrap();
|
|
466
|
+
assert_eq!(modules.len(), 2);
|
|
467
|
+
assert_eq!(modules[0].path, "lib.tish");
|
|
468
|
+
assert_eq!(modules[1].path, "main.tish");
|
|
469
|
+
detect_cycles_virtual(&modules).unwrap();
|
|
470
|
+
let program = merge_modules_virtual(modules).unwrap();
|
|
471
|
+
assert!(!program.statements.is_empty());
|
|
472
|
+
}
|
|
473
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "tishlang_core"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "Shared Value type and utilities for Tish (used by interpreter and runtime)"
|
|
6
|
+
|
|
7
|
+
license-file = { workspace = true }
|
|
8
|
+
repository = { workspace = true }
|
|
9
|
+
[features]
|
|
10
|
+
default = []
|
|
11
|
+
regex = ["dep:fancy-regex"]
|
|
12
|
+
# Make `Value` (and its array / object / regex payloads) `Send + Sync` by
|
|
13
|
+
# switching the interior `Rc<RefCell<T>>` to `Arc<Mutex<T>>` and the native
|
|
14
|
+
# function type from `Rc<dyn Fn>` to `Arc<dyn Fn + Send + Sync>`. Enabled
|
|
15
|
+
# transitively by any crate that needs to pass `Value`s across threads —
|
|
16
|
+
# most notably `tishlang_runtime/http`, which dispatches HTTP handlers
|
|
17
|
+
# across a worker pool. Off by default so wasm / wasi / cranelift / llvm /
|
|
18
|
+
# interpreter builds pay no atomic-ref-count or mutex overhead.
|
|
19
|
+
send-values = []
|
|
20
|
+
|
|
21
|
+
[dependencies]
|
|
22
|
+
ahash = "0.8.11"
|
|
23
|
+
fancy-regex = { version = "0.17.0", optional = true }
|
|
24
|
+
|
|
25
|
+
[target.wasm32-unknown-unknown.dependencies]
|
|
26
|
+
getrandom = { version = "0.3", features = ["wasm_js"] }
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
//! Console styling for values (Node/Bun-style colors).
|
|
2
|
+
//!
|
|
3
|
+
//! Use for REPL, console.log, and any terminal output so numbers, strings,
|
|
4
|
+
//! booleans, null, and object structure are easier to scan.
|
|
5
|
+
//!
|
|
6
|
+
//! `console.log` prints string arguments without surrounding quotes (like Node); nested
|
|
7
|
+
//! strings inside arrays/objects stay quoted. The REPL still quotes string results for clarity.
|
|
8
|
+
|
|
9
|
+
use std::io::IsTerminal;
|
|
10
|
+
use std::sync::OnceLock;
|
|
11
|
+
|
|
12
|
+
use crate::Value;
|
|
13
|
+
|
|
14
|
+
static CONSOLE_USES_COLORS: OnceLock<bool> = OnceLock::new();
|
|
15
|
+
|
|
16
|
+
/// ANSI escape codes (standard 4-bit + bright black for dim).
|
|
17
|
+
const RESET: &str = "\x1b[0m";
|
|
18
|
+
/// Number: yellow (Node-style)
|
|
19
|
+
const NUMBER: &str = "\x1b[33m";
|
|
20
|
+
/// String: green
|
|
21
|
+
const STRING: &str = "\x1b[32m";
|
|
22
|
+
/// Boolean: blue
|
|
23
|
+
const BOOLEAN: &str = "\x1b[34m";
|
|
24
|
+
/// Null: dim grey
|
|
25
|
+
const NULL: &str = "\x1b[90m";
|
|
26
|
+
/// Object keys: cyan
|
|
27
|
+
const KEY: &str = "\x1b[36m";
|
|
28
|
+
/// Punctuation (brackets, commas): dim
|
|
29
|
+
const PUNCT: &str = "\x1b[90m";
|
|
30
|
+
/// Function / special (e.g. [Function]): dim
|
|
31
|
+
const SPECIAL: &str = "\x1b[90m";
|
|
32
|
+
|
|
33
|
+
/// Returns whether console output should use colors (stdout is a TTY).
|
|
34
|
+
///
|
|
35
|
+
/// Cached for the process lifetime. `is_terminal()` is a syscall; benchmarks and
|
|
36
|
+
/// scripts with many `console.log` calls must not pay it on every line.
|
|
37
|
+
pub fn use_console_colors() -> bool {
|
|
38
|
+
*CONSOLE_USES_COLORS.get_or_init(|| std::io::stdout().is_terminal())
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Format a single value for console with optional ANSI colors (Node/Bun-style).
|
|
42
|
+
pub fn format_value_styled(value: &Value, colors: bool) -> String {
|
|
43
|
+
if !colors {
|
|
44
|
+
return value.to_display_string();
|
|
45
|
+
}
|
|
46
|
+
format_value_styled_inner(value, colors, true)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// `quote_strings`: when true (REPL / inspect), strings render as quoted literals. When false
|
|
50
|
+
/// (top-level `console.log` arguments), strings render raw like Node.
|
|
51
|
+
fn format_value_styled_inner(value: &Value, colors: bool, quote_strings: bool) -> String {
|
|
52
|
+
match value {
|
|
53
|
+
Value::Number(n) => {
|
|
54
|
+
let s = if n.is_nan() {
|
|
55
|
+
"NaN".to_string()
|
|
56
|
+
} else if *n == f64::INFINITY {
|
|
57
|
+
"Infinity".to_string()
|
|
58
|
+
} else if *n == f64::NEG_INFINITY {
|
|
59
|
+
"-Infinity".to_string()
|
|
60
|
+
} else {
|
|
61
|
+
n.to_string()
|
|
62
|
+
};
|
|
63
|
+
format!("{NUMBER}{s}{RESET}")
|
|
64
|
+
}
|
|
65
|
+
Value::String(s) => {
|
|
66
|
+
if quote_strings {
|
|
67
|
+
let escaped = escape_string_for_display(s);
|
|
68
|
+
format!("{STRING}\"{escaped}\"{RESET}")
|
|
69
|
+
} else {
|
|
70
|
+
format!("{STRING}{}{RESET}", s.as_ref())
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
Value::Bool(b) => format!("{BOOLEAN}{b}{RESET}"),
|
|
74
|
+
Value::Null => format!("{NULL}null{RESET}"),
|
|
75
|
+
Value::Array(arr) => {
|
|
76
|
+
let inner: Vec<String> = arr
|
|
77
|
+
.borrow()
|
|
78
|
+
.iter()
|
|
79
|
+
.map(|v| format_value_styled_inner(v, colors, true))
|
|
80
|
+
.collect();
|
|
81
|
+
let sep = format!("{PUNCT}, {RESET}");
|
|
82
|
+
format!("{PUNCT}[{RESET}{}{PUNCT}]{RESET}", inner.join(&sep))
|
|
83
|
+
}
|
|
84
|
+
Value::Object(obj) => {
|
|
85
|
+
let inner: Vec<String> = obj
|
|
86
|
+
.borrow()
|
|
87
|
+
.strings
|
|
88
|
+
.iter()
|
|
89
|
+
.map(|(k, v)| {
|
|
90
|
+
format!(
|
|
91
|
+
"{KEY}{}{RESET}{PUNCT}: {RESET}{}",
|
|
92
|
+
k.as_ref(),
|
|
93
|
+
format_value_styled_inner(v, colors, true)
|
|
94
|
+
)
|
|
95
|
+
})
|
|
96
|
+
.collect();
|
|
97
|
+
let sep = format!("{PUNCT}, {RESET}");
|
|
98
|
+
format!("{PUNCT}{{{RESET} {} {PUNCT}}}{RESET}", inner.join(&sep))
|
|
99
|
+
}
|
|
100
|
+
Value::Symbol(s) => {
|
|
101
|
+
let body = s
|
|
102
|
+
.description
|
|
103
|
+
.as_ref()
|
|
104
|
+
.map(|d| d.as_ref())
|
|
105
|
+
.unwrap_or("");
|
|
106
|
+
format!("{SPECIAL}Symbol({body}){RESET}")
|
|
107
|
+
}
|
|
108
|
+
Value::Function(_) => format!("{SPECIAL}[Function]{RESET}"),
|
|
109
|
+
Value::Promise(_) => format!("{SPECIAL}[object Promise]{RESET}"),
|
|
110
|
+
Value::Opaque(o) => format!("{SPECIAL}[object {}]{RESET}", o.type_name()),
|
|
111
|
+
#[cfg(feature = "regex")]
|
|
112
|
+
Value::RegExp(re) => {
|
|
113
|
+
let re = re.borrow();
|
|
114
|
+
format!(
|
|
115
|
+
"{PUNCT}/{KEY}{}{RESET}{PUNCT}/{}{RESET}",
|
|
116
|
+
re.source,
|
|
117
|
+
re.flags_string()
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fn escape_string_for_display(s: &str) -> String {
|
|
124
|
+
let mut out = String::with_capacity(s.len());
|
|
125
|
+
for c in s.chars() {
|
|
126
|
+
match c {
|
|
127
|
+
'\\' => out.push_str("\\\\"),
|
|
128
|
+
'\n' => out.push_str("\\n"),
|
|
129
|
+
'\r' => out.push_str("\\r"),
|
|
130
|
+
'\t' => out.push_str("\\t"),
|
|
131
|
+
'"' => out.push_str("\\\""),
|
|
132
|
+
c => out.push(c),
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
out
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Format multiple values for console (e.g. console.log(a, b, c)) with optional colors.
|
|
139
|
+
pub fn format_values_for_console(values: &[Value], colors: bool) -> String {
|
|
140
|
+
let mut iter = values.iter();
|
|
141
|
+
match iter.next() {
|
|
142
|
+
None => String::new(),
|
|
143
|
+
Some(first) => {
|
|
144
|
+
let mut result = if colors {
|
|
145
|
+
format_value_styled_inner(first, colors, false)
|
|
146
|
+
} else {
|
|
147
|
+
first.to_display_string()
|
|
148
|
+
};
|
|
149
|
+
for v in iter {
|
|
150
|
+
result.push(' ');
|
|
151
|
+
if colors {
|
|
152
|
+
result.push_str(&format_value_styled_inner(v, colors, false));
|
|
153
|
+
} else {
|
|
154
|
+
result.push_str(&v.to_display_string());
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
result
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|