@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.
Files changed (164) hide show
  1. package/Cargo.toml +49 -0
  2. package/LICENSE +13 -0
  3. package/README.md +138 -0
  4. package/bin/tish-format +0 -0
  5. package/crates/js_to_tish/Cargo.toml +11 -0
  6. package/crates/js_to_tish/README.md +18 -0
  7. package/crates/js_to_tish/src/error.rs +55 -0
  8. package/crates/js_to_tish/src/lib.rs +11 -0
  9. package/crates/js_to_tish/src/span_util.rs +35 -0
  10. package/crates/js_to_tish/src/transform/expr.rs +610 -0
  11. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  12. package/crates/js_to_tish/src/transform.rs +60 -0
  13. package/crates/tish/Cargo.toml +54 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +565 -0
  16. package/crates/tish/src/main.rs +781 -0
  17. package/crates/tish/src/repl_completion.rs +200 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  24. package/crates/tish/tests/integration_test.rs +1095 -0
  25. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  26. package/crates/tish/tests/shortcircuit.rs +65 -0
  27. package/crates/tish_ast/Cargo.toml +9 -0
  28. package/crates/tish_ast/src/ast.rs +620 -0
  29. package/crates/tish_ast/src/lib.rs +5 -0
  30. package/crates/tish_build_utils/Cargo.toml +11 -0
  31. package/crates/tish_build_utils/src/lib.rs +577 -0
  32. package/crates/tish_builtins/Cargo.toml +20 -0
  33. package/crates/tish_builtins/src/array.rs +441 -0
  34. package/crates/tish_builtins/src/construct.rs +159 -0
  35. package/crates/tish_builtins/src/globals.rs +213 -0
  36. package/crates/tish_builtins/src/helpers.rs +35 -0
  37. package/crates/tish_builtins/src/lib.rs +16 -0
  38. package/crates/tish_builtins/src/math.rs +89 -0
  39. package/crates/tish_builtins/src/object.rs +36 -0
  40. package/crates/tish_builtins/src/string.rs +647 -0
  41. package/crates/tish_builtins/src/symbol.rs +83 -0
  42. package/crates/tish_bytecode/Cargo.toml +17 -0
  43. package/crates/tish_bytecode/src/chunk.rs +96 -0
  44. package/crates/tish_bytecode/src/compiler.rs +1760 -0
  45. package/crates/tish_bytecode/src/encoding.rs +100 -0
  46. package/crates/tish_bytecode/src/lib.rs +19 -0
  47. package/crates/tish_bytecode/src/opcode.rs +142 -0
  48. package/crates/tish_bytecode/src/peephole.rs +189 -0
  49. package/crates/tish_bytecode/src/serialize.rs +163 -0
  50. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  51. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  52. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  53. package/crates/tish_compile/Cargo.toml +26 -0
  54. package/crates/tish_compile/src/codegen.rs +5332 -0
  55. package/crates/tish_compile/src/infer.rs +292 -0
  56. package/crates/tish_compile/src/lib.rs +164 -0
  57. package/crates/tish_compile/src/resolve.rs +1388 -0
  58. package/crates/tish_compile/src/types.rs +501 -0
  59. package/crates/tish_compile_js/Cargo.toml +18 -0
  60. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  61. package/crates/tish_compile_js/src/codegen.rs +871 -0
  62. package/crates/tish_compile_js/src/error.rs +20 -0
  63. package/crates/tish_compile_js/src/lib.rs +26 -0
  64. package/crates/tish_compile_js/src/tests_jsx.rs +350 -0
  65. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  66. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  67. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  68. package/crates/tish_core/Cargo.toml +26 -0
  69. package/crates/tish_core/src/console_style.rs +160 -0
  70. package/crates/tish_core/src/json.rs +387 -0
  71. package/crates/tish_core/src/lib.rs +17 -0
  72. package/crates/tish_core/src/macros.rs +36 -0
  73. package/crates/tish_core/src/uri.rs +118 -0
  74. package/crates/tish_core/src/value.rs +696 -0
  75. package/crates/tish_core/src/vmref.rs +178 -0
  76. package/crates/tish_cranelift/Cargo.toml +19 -0
  77. package/crates/tish_cranelift/src/lib.rs +43 -0
  78. package/crates/tish_cranelift/src/link.rs +117 -0
  79. package/crates/tish_cranelift/src/lower.rs +85 -0
  80. package/crates/tish_cranelift_runtime/Cargo.toml +25 -0
  81. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  82. package/crates/tish_eval/Cargo.toml +45 -0
  83. package/crates/tish_eval/src/eval.rs +3717 -0
  84. package/crates/tish_eval/src/http.rs +188 -0
  85. package/crates/tish_eval/src/lib.rs +99 -0
  86. package/crates/tish_eval/src/natives.rs +399 -0
  87. package/crates/tish_eval/src/promise.rs +179 -0
  88. package/crates/tish_eval/src/regex.rs +299 -0
  89. package/crates/tish_eval/src/timers.rs +120 -0
  90. package/crates/tish_eval/src/value.rs +318 -0
  91. package/crates/tish_eval/src/value_convert.rs +111 -0
  92. package/crates/tish_fmt/Cargo.toml +16 -0
  93. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  94. package/crates/tish_fmt/src/lib.rs +2101 -0
  95. package/crates/tish_jsx_web/Cargo.toml +9 -0
  96. package/crates/tish_jsx_web/README.md +5 -0
  97. package/crates/tish_jsx_web/src/lib.rs +2 -0
  98. package/crates/tish_lexer/Cargo.toml +9 -0
  99. package/crates/tish_lexer/src/lib.rs +716 -0
  100. package/crates/tish_lexer/src/token.rs +163 -0
  101. package/crates/tish_lint/Cargo.toml +18 -0
  102. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  103. package/crates/tish_lint/src/lib.rs +289 -0
  104. package/crates/tish_llvm/Cargo.toml +13 -0
  105. package/crates/tish_llvm/src/lib.rs +115 -0
  106. package/crates/tish_lsp/Cargo.toml +25 -0
  107. package/crates/tish_lsp/README.md +26 -0
  108. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  109. package/crates/tish_lsp/src/import_goto.rs +562 -0
  110. package/crates/tish_lsp/src/main.rs +1046 -0
  111. package/crates/tish_native/Cargo.toml +16 -0
  112. package/crates/tish_native/src/build.rs +427 -0
  113. package/crates/tish_native/src/config.rs +48 -0
  114. package/crates/tish_native/src/lib.rs +416 -0
  115. package/crates/tish_opt/Cargo.toml +13 -0
  116. package/crates/tish_opt/src/lib.rs +943 -0
  117. package/crates/tish_parser/Cargo.toml +11 -0
  118. package/crates/tish_parser/src/lib.rs +332 -0
  119. package/crates/tish_parser/src/parser.rs +2304 -0
  120. package/crates/tish_pg/Cargo.toml +34 -0
  121. package/crates/tish_pg/README.md +38 -0
  122. package/crates/tish_pg/src/error.rs +52 -0
  123. package/crates/tish_pg/src/lib.rs +955 -0
  124. package/crates/tish_resolve/Cargo.toml +13 -0
  125. package/crates/tish_resolve/src/lib.rs +3561 -0
  126. package/crates/tish_resolve/src/pos.rs +141 -0
  127. package/crates/tish_runtime/Cargo.toml +96 -0
  128. package/crates/tish_runtime/src/http.rs +1298 -0
  129. package/crates/tish_runtime/src/http_fetch.rs +471 -0
  130. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  131. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  132. package/crates/tish_runtime/src/lib.rs +1192 -0
  133. package/crates/tish_runtime/src/native_promise.rs +15 -0
  134. package/crates/tish_runtime/src/promise.rs +248 -0
  135. package/crates/tish_runtime/src/promise_io.rs +38 -0
  136. package/crates/tish_runtime/src/timers.rs +166 -0
  137. package/crates/tish_runtime/src/ws.rs +761 -0
  138. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  139. package/crates/tish_ui/Cargo.toml +17 -0
  140. package/crates/tish_ui/src/jsx.rs +682 -0
  141. package/crates/tish_ui/src/lib.rs +20 -0
  142. package/crates/tish_ui/src/runtime/hooks.rs +569 -0
  143. package/crates/tish_ui/src/runtime/mod.rs +180 -0
  144. package/crates/tish_vm/Cargo.toml +47 -0
  145. package/crates/tish_vm/src/lib.rs +39 -0
  146. package/crates/tish_vm/src/vm.rs +2192 -0
  147. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  148. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  149. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  150. package/crates/tish_wasm/Cargo.toml +15 -0
  151. package/crates/tish_wasm/src/lib.rs +424 -0
  152. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  153. package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
  154. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  155. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  156. package/crates/tishlang_cargo_bindgen/src/classify.rs +263 -0
  157. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  158. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  159. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  160. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  161. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  162. package/justfile +268 -0
  163. package/package.json +1 -1
  164. 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
+ }