@tishlang/tish 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/Cargo.toml +2 -0
  2. package/README.md +2 -0
  3. package/bin/tish +0 -0
  4. package/crates/js_to_tish/src/error.rs +2 -8
  5. package/crates/js_to_tish/src/transform/expr.rs +128 -137
  6. package/crates/js_to_tish/src/transform/stmt.rs +62 -32
  7. package/crates/tish/Cargo.toml +15 -5
  8. package/crates/tish/src/cargo_native_registry.rs +29 -0
  9. package/crates/tish/src/cli_help.rs +92 -39
  10. package/crates/tish/src/main.rs +172 -86
  11. package/crates/tish/src/repl_completion.rs +3 -3
  12. package/crates/tish/tests/cargo_example_compile.rs +4 -2
  13. package/crates/tish/tests/integration_test.rs +216 -54
  14. package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
  15. package/crates/tish/tests/shortcircuit.rs +20 -5
  16. package/crates/tish_ast/src/ast.rs +92 -23
  17. package/crates/tish_build_utils/Cargo.toml +4 -0
  18. package/crates/tish_build_utils/src/lib.rs +136 -8
  19. package/crates/tish_builtins/Cargo.toml +5 -1
  20. package/crates/tish_builtins/src/array.rs +65 -33
  21. package/crates/tish_builtins/src/construct.rs +34 -39
  22. package/crates/tish_builtins/src/globals.rs +42 -26
  23. package/crates/tish_builtins/src/helpers.rs +2 -1
  24. package/crates/tish_builtins/src/lib.rs +5 -5
  25. package/crates/tish_builtins/src/math.rs +5 -3
  26. package/crates/tish_builtins/src/object.rs +3 -2
  27. package/crates/tish_builtins/src/string.rs +144 -22
  28. package/crates/tish_bytecode/src/chunk.rs +0 -1
  29. package/crates/tish_bytecode/src/compiler.rs +173 -71
  30. package/crates/tish_bytecode/src/opcode.rs +24 -6
  31. package/crates/tish_bytecode/src/peephole.rs +2 -2
  32. package/crates/tish_compile/Cargo.toml +1 -0
  33. package/crates/tish_compile/src/codegen.rs +1621 -453
  34. package/crates/tish_compile/src/infer.rs +75 -19
  35. package/crates/tish_compile/src/lib.rs +19 -8
  36. package/crates/tish_compile/src/resolve.rs +278 -137
  37. package/crates/tish_compile/src/types.rs +184 -24
  38. package/crates/tish_compile_js/Cargo.toml +1 -0
  39. package/crates/tish_compile_js/src/codegen.rs +181 -37
  40. package/crates/tish_compile_js/src/lib.rs +3 -1
  41. package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
  42. package/crates/tish_compiler_wasm/src/lib.rs +16 -13
  43. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
  44. package/crates/tish_core/Cargo.toml +8 -0
  45. package/crates/tish_core/src/json.rs +107 -56
  46. package/crates/tish_core/src/lib.rs +4 -2
  47. package/crates/tish_core/src/macros.rs +5 -5
  48. package/crates/tish_core/src/uri.rs +9 -6
  49. package/crates/tish_core/src/value.rs +145 -43
  50. package/crates/tish_core/src/vmref.rs +178 -0
  51. package/crates/tish_cranelift/src/link.rs +6 -9
  52. package/crates/tish_cranelift/src/lower.rs +14 -8
  53. package/crates/tish_eval/Cargo.toml +17 -2
  54. package/crates/tish_eval/src/eval.rs +474 -165
  55. package/crates/tish_eval/src/http.rs +61 -0
  56. package/crates/tish_eval/src/lib.rs +12 -8
  57. package/crates/tish_eval/src/natives.rs +136 -38
  58. package/crates/tish_eval/src/promise.rs +14 -8
  59. package/crates/tish_eval/src/timers.rs +28 -19
  60. package/crates/tish_eval/src/value.rs +17 -6
  61. package/crates/tish_eval/src/value_convert.rs +13 -5
  62. package/crates/tish_fmt/src/lib.rs +149 -43
  63. package/crates/tish_lexer/src/lib.rs +232 -63
  64. package/crates/tish_lexer/src/token.rs +10 -6
  65. package/crates/tish_llvm/src/lib.rs +17 -8
  66. package/crates/tish_lsp/Cargo.toml +4 -1
  67. package/crates/tish_lsp/README.md +1 -1
  68. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  69. package/crates/tish_lsp/src/import_goto.rs +549 -0
  70. package/crates/tish_lsp/src/main.rs +504 -106
  71. package/crates/tish_native/src/build.rs +4 -8
  72. package/crates/tish_native/src/lib.rs +54 -21
  73. package/crates/tish_opt/src/lib.rs +84 -52
  74. package/crates/tish_parser/src/lib.rs +45 -13
  75. package/crates/tish_parser/src/parser.rs +505 -130
  76. package/crates/tish_resolve/Cargo.toml +13 -0
  77. package/crates/tish_resolve/src/lib.rs +3436 -0
  78. package/crates/tish_resolve/src/pos.rs +133 -0
  79. package/crates/tish_runtime/Cargo.toml +68 -3
  80. package/crates/tish_runtime/src/http.rs +1136 -145
  81. package/crates/tish_runtime/src/http_fetch.rs +38 -27
  82. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  83. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  84. package/crates/tish_runtime/src/lib.rs +375 -189
  85. package/crates/tish_runtime/src/promise.rs +199 -40
  86. package/crates/tish_runtime/src/promise_io.rs +2 -1
  87. package/crates/tish_runtime/src/timers.rs +37 -1
  88. package/crates/tish_runtime/src/ws.rs +65 -42
  89. package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
  90. package/crates/tish_ui/src/jsx.rs +317 -27
  91. package/crates/tish_ui/src/lib.rs +5 -2
  92. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  93. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  94. package/crates/tish_vm/Cargo.toml +15 -5
  95. package/crates/tish_vm/src/vm.rs +725 -281
  96. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
  97. package/crates/tish_wasm/src/lib.rs +55 -42
  98. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  99. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  100. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  101. package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
  102. package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
  103. package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
  104. package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
  105. package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
  106. package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
  107. package/justfile +8 -0
  108. package/package.json +1 -1
  109. package/platform/darwin-arm64/tish +0 -0
  110. package/platform/darwin-x64/tish +0 -0
  111. package/platform/linux-arm64/tish +0 -0
  112. package/platform/linux-x64/tish +0 -0
  113. package/platform/win32-x64/tish.exe +0 -0
@@ -1,9 +1,11 @@
1
1
  //! Tish CLI - run, REPL, build to native or other targets.
2
2
 
3
+ mod cargo_native_registry;
3
4
  mod cli_help;
4
5
  mod repl_completion;
5
6
 
6
7
  use std::cell::RefCell;
8
+ use tishlang_core::VmRef;
7
9
  use std::collections::HashSet;
8
10
  use std::fs;
9
11
  use std::io::{self, IsTerminal, Read, Write};
@@ -15,13 +17,13 @@ use rustyline::{Behavior, ColorMode, CompletionType, Config, Editor};
15
17
 
16
18
  use cli_help::{Cli, Commands};
17
19
 
18
- /// Normalize `--feature` / `--feature http,fs` / `--feature full` for VM runs and native builds.
20
+ /// Normalize `--feature` / `--feature http,timers,fs` / `--feature full` for VM runs and native builds.
19
21
  fn normalize_capability_flags(features: &[String]) -> HashSet<String> {
20
22
  let mut out = HashSet::new();
21
23
  for s in features {
22
24
  for part in s.split(',').map(str::trim).filter(|p| !p.is_empty()) {
23
25
  if part == "full" {
24
- for name in ["http", "fs", "process", "regex", "ws"] {
26
+ for name in ["http", "timers", "fs", "process", "regex", "ws"] {
25
27
  out.insert(name.to_string());
26
28
  }
27
29
  } else {
@@ -48,7 +50,9 @@ fn vm_capabilities_for_cli_run(cli_features: &[String]) -> HashSet<String> {
48
50
  /// `--feature` list for `tish build --target native`: same default as `tish run` (all linked-in caps).
49
51
  fn native_build_features_from_cli(cli_features: &[String]) -> Vec<String> {
50
52
  if cli_features.is_empty() {
51
- let mut v: Vec<String> = tishlang_vm::all_compiled_capabilities().into_iter().collect();
53
+ let mut v: Vec<String> = tishlang_vm::all_compiled_capabilities()
54
+ .into_iter()
55
+ .collect();
52
56
  v.sort();
53
57
  v
54
58
  } else {
@@ -61,8 +65,7 @@ fn argv_with_implicit_run(mut argv: Vec<String>) -> Vec<String> {
61
65
  if argv.len() >= 2 {
62
66
  let first = argv[1].as_str();
63
67
  const SUBCOMMANDS: &[&str] = &["run", "repl", "build", "dump-ast"];
64
- let looks_like_file =
65
- !first.starts_with('-') && !SUBCOMMANDS.iter().any(|&s| s == first);
68
+ let looks_like_file = !first.starts_with('-') && !SUBCOMMANDS.iter().any(|&s| s == first);
66
69
  if looks_like_file {
67
70
  argv.insert(1, "run".to_string());
68
71
  }
@@ -95,7 +98,12 @@ fn main() {
95
98
  let matches = cli_help::build_command().get_matches_from(&argv);
96
99
  let cli = Cli::from_arg_matches(&matches).unwrap_or_else(|e| e.exit());
97
100
  let result = match cli.command {
98
- Some(Commands::Run(a)) => run_file(&a.file, &a.backend, &a.features, a.no_optimize || no_opt_env),
101
+ Some(Commands::Run(a)) => run_file(
102
+ &a.file,
103
+ &a.backend,
104
+ &a.features,
105
+ a.no_optimize || no_opt_env,
106
+ ),
99
107
  Some(Commands::Repl(a)) => run_repl(&a.backend, a.no_optimize || no_opt_env, &a.features),
100
108
  Some(Commands::Build(a)) => build_file(
101
109
  &a.file,
@@ -104,6 +112,7 @@ fn main() {
104
112
  &a.native_backend,
105
113
  &a.features,
106
114
  a.no_optimize || no_opt_env,
115
+ a.source_map,
107
116
  ),
108
117
  Some(Commands::DumpAst { file }) => dump_ast(&file),
109
118
  None => {
@@ -136,7 +145,8 @@ fn run_stdin_pipe(
136
145
  if source.trim().is_empty() {
137
146
  if fail_on_empty {
138
147
  return Err(
139
- "No source on stdin. Example: echo 'console.log(1)' | tish or tish run -".into(),
148
+ "No source on stdin. Example: echo 'console.log(1)' | tish or tish run -"
149
+ .into(),
140
150
  );
141
151
  }
142
152
  return Ok(());
@@ -153,7 +163,7 @@ fn run_stdin_source(
153
163
  let cwd = std::env::current_dir().map_err(|e| e.to_string())?;
154
164
  let modules = tishlang_compile::resolve_project_from_stdin(source, &cwd)?;
155
165
  tishlang_compile::detect_cycles(&modules)?;
156
- let prog = tishlang_compile::merge_modules(modules)?;
166
+ let prog = tishlang_compile::merge_modules(modules)?.program;
157
167
  let program = if no_optimize {
158
168
  prog
159
169
  } else {
@@ -162,12 +172,18 @@ fn run_stdin_source(
162
172
  run_program(&program, backend, no_optimize, features)
163
173
  }
164
174
 
165
- fn run_file(path: &str, backend: &str, features: &[String], no_optimize: bool) -> Result<(), String> {
175
+ fn run_file(
176
+ path: &str,
177
+ backend: &str,
178
+ features: &[String],
179
+ no_optimize: bool,
180
+ ) -> Result<(), String> {
166
181
  let program = if path == "-" {
167
182
  return run_stdin_pipe(backend, features, no_optimize, true);
168
183
  } else {
169
- let path =
170
- Path::new(path).canonicalize().map_err(|e| format!("Cannot resolve {}: {}", path, e))?;
184
+ let path = Path::new(path)
185
+ .canonicalize()
186
+ .map_err(|e| format!("Cannot resolve {}: {}", path, e))?;
171
187
  let project_root = path.parent().and_then(|p| {
172
188
  if p.file_name().and_then(|n| n.to_str()) == Some("src") {
173
189
  p.parent()
@@ -177,8 +193,10 @@ fn run_file(path: &str, backend: &str, features: &[String], no_optimize: bool) -
177
193
  });
178
194
 
179
195
  if path.extension().map(|e| e == "js") == Some(true) {
180
- let prog = tishlang_js_to_tish::convert(&fs::read_to_string(&path).map_err(|e| format!("{}", e))?)
181
- .map_err(|e| format!("{}", e))?;
196
+ let prog = tishlang_js_to_tish::convert(
197
+ &fs::read_to_string(&path).map_err(|e| format!("{}", e))?,
198
+ )
199
+ .map_err(|e| format!("{}", e))?;
182
200
  if no_optimize {
183
201
  prog
184
202
  } else {
@@ -187,7 +205,7 @@ fn run_file(path: &str, backend: &str, features: &[String], no_optimize: bool) -
187
205
  } else {
188
206
  let modules = tishlang_compile::resolve_project(&path, project_root)?;
189
207
  tishlang_compile::detect_cycles(&modules)?;
190
- let prog = tishlang_compile::merge_modules(modules)?;
208
+ let prog = tishlang_compile::merge_modules(modules)?.program;
191
209
  if no_optimize {
192
210
  prog
193
211
  } else {
@@ -208,8 +226,18 @@ fn run_program(
208
226
  if backend == "interp" {
209
227
  let mut eval = tishlang_eval::Evaluator::new();
210
228
  let value = eval.eval_program(program)?;
229
+ #[cfg(feature = "timers")]
230
+ {
231
+ let _ = eval.run_timer_phase();
232
+ }
211
233
  if !matches!(value, tishlang_eval::Value::Null) {
212
- println!("{}", tishlang_eval::format_value_for_console(&value, tishlang_core::use_console_colors()));
234
+ println!(
235
+ "{}",
236
+ tishlang_eval::format_value_for_console(
237
+ &value,
238
+ tishlang_core::use_console_colors()
239
+ )
240
+ );
213
241
  }
214
242
  return Ok(());
215
243
  }
@@ -220,15 +248,14 @@ fn run_program(
220
248
  tishlang_bytecode::compile(program).map_err(|e| e.to_string())?
221
249
  };
222
250
  let caps = vm_capabilities_for_cli_run(features);
223
- let value = tishlang_vm::run_with_options(
224
- &chunk,
225
- tishlang_vm::VmRunOptions {
226
- repl_mode: false,
227
- capabilities: caps,
228
- },
229
- )?;
251
+ let mut vm = tishlang_vm::Vm::with_capabilities(caps);
252
+ cargo_native_registry::register_bytecode_native_modules(&mut vm);
253
+ let value = vm.run_with_options(&chunk, false)?;
230
254
  if !matches!(value, tishlang_core::Value::Null) {
231
- println!("{}", tishlang_core::format_value_styled(&value, tishlang_core::use_console_colors()));
255
+ println!(
256
+ "{}",
257
+ tishlang_core::format_value_styled(&value, tishlang_core::use_console_colors())
258
+ );
232
259
  }
233
260
  Ok(())
234
261
  }
@@ -246,7 +273,11 @@ fn run_repl(backend: &str, no_optimize: bool, features: &[String]) -> Result<(),
246
273
  print!("{}", prompt);
247
274
  io::stdout().flush().map_err(|e| e.to_string())?;
248
275
  buffer.clear();
249
- if io::stdin().read_line(&mut buffer).map_err(|e| e.to_string())? == 0 {
276
+ if io::stdin()
277
+ .read_line(&mut buffer)
278
+ .map_err(|e| e.to_string())?
279
+ == 0
280
+ {
250
281
  if !multiline.is_empty() {
251
282
  let _ = tishlang_parser::parse(multiline.trim());
252
283
  }
@@ -266,8 +297,18 @@ fn run_repl(backend: &str, no_optimize: bool, features: &[String]) -> Result<(),
266
297
  Ok(program) => {
267
298
  match eval.eval_program(&program) {
268
299
  Ok(v) => {
300
+ #[cfg(feature = "timers")]
301
+ {
302
+ let _ = eval.run_timer_phase();
303
+ }
269
304
  if !matches!(v, tishlang_eval::Value::Null) {
270
- println!("{}", tishlang_eval::format_value_for_console(&v, tishlang_core::use_console_colors()));
305
+ println!(
306
+ "{}",
307
+ tishlang_eval::format_value_for_console(
308
+ &v,
309
+ tishlang_core::use_console_colors()
310
+ )
311
+ );
271
312
  }
272
313
  }
273
314
  Err(e) => eprintln!("{}", e),
@@ -291,11 +332,11 @@ fn run_repl(backend: &str, no_optimize: bool, features: &[String]) -> Result<(),
291
332
  if !std::io::stdin().is_terminal() {
292
333
  eprintln!("Note: Tab completion and grey preview require an interactive terminal (TTY).");
293
334
  }
294
- let vm = Rc::new(RefCell::new(tishlang_vm::Vm::with_capabilities(
295
- vm_capabilities_for_cli_run(features),
296
- )));
335
+ let mut vm0 = tishlang_vm::Vm::with_capabilities(vm_capabilities_for_cli_run(features));
336
+ cargo_native_registry::register_bytecode_native_modules(&mut vm0);
337
+ let vm = VmRef::new(vm0);
297
338
  let completer = repl_completion::ReplCompleter {
298
- vm: Rc::clone(&vm),
339
+ vm: vm.clone(),
299
340
  no_optimize,
300
341
  };
301
342
  let config = Config::builder()
@@ -364,16 +405,20 @@ fn run_repl(backend: &str, no_optimize: bool, features: &[String]) -> Result<(),
364
405
  tishlang_bytecode::compile_for_repl
365
406
  };
366
407
  match compile_fn(&program) {
367
- Ok(chunk) => {
368
- match vm.borrow_mut().run_with_options(&chunk, true) {
369
- Ok(v) => {
370
- if !matches!(v, tishlang_core::Value::Null) {
371
- println!("{}", tishlang_core::format_value_styled(&v, tishlang_core::use_console_colors()));
372
- }
408
+ Ok(chunk) => match vm.borrow_mut().run_with_options(&chunk, true) {
409
+ Ok(v) => {
410
+ if !matches!(v, tishlang_core::Value::Null) {
411
+ println!(
412
+ "{}",
413
+ tishlang_core::format_value_styled(
414
+ &v,
415
+ tishlang_core::use_console_colors()
416
+ )
417
+ );
373
418
  }
374
- Err(e) => eprintln!("{}", e),
375
419
  }
376
- }
420
+ Err(e) => eprintln!("{}", e),
421
+ },
377
422
  Err(e) => eprintln!("Compile error: {}", e),
378
423
  }
379
424
  let _ = rl.add_history_entry(buffer.trim());
@@ -413,12 +458,31 @@ fn repl_prompt(primary: bool) -> String {
413
458
 
414
459
  /// Path to REPL history file (Python-style: ~/.tish_history).
415
460
  fn tish_history_path() -> Option<PathBuf> {
416
- let home = std::env::var_os("HOME")
417
- .or_else(|| std::env::var_os("USERPROFILE"));
461
+ let home = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE"));
418
462
  home.map(|h| PathBuf::from(h).join(".tish_history"))
419
463
  }
420
464
 
421
- fn compile_to_js(input_path: &Path, output_path: &str, optimize: bool) -> Result<(), String> {
465
+ fn compile_to_js(
466
+ input_path: &Path,
467
+ output_path: &str,
468
+ optimize: bool,
469
+ source_map: bool,
470
+ ) -> Result<(), String> {
471
+ if source_map && optimize {
472
+ return Err(
473
+ "tish build --target js --source-map requires --no-optimize (mappings follow unmerged statement order)."
474
+ .into(),
475
+ );
476
+ }
477
+ if source_map
478
+ && (input_path.extension().map(|e| e == "jsx") == Some(true)
479
+ || input_path.extension().map(|e| e == "js") == Some(true))
480
+ {
481
+ return Err(
482
+ "tish build --target js --source-map is only supported for .tish project builds (not single-file .jsx / .js inputs)."
483
+ .into(),
484
+ );
485
+ }
422
486
  let project_root = input_path.parent().and_then(|p| {
423
487
  if p.file_name().and_then(|n| n.to_str()) == Some("src") {
424
488
  p.parent()
@@ -426,43 +490,70 @@ fn compile_to_js(input_path: &Path, output_path: &str, optimize: bool) -> Result
426
490
  Some(p)
427
491
  }
428
492
  });
429
- let js = if input_path.extension().map(|e| e == "jsx") == Some(true) {
493
+ let out_path = Path::new(output_path);
494
+ let out_path = if out_path.extension().is_none()
495
+ || out_path.extension() == Some(std::ffi::OsStr::new(""))
496
+ {
497
+ out_path.with_extension("js")
498
+ } else {
499
+ out_path.to_path_buf()
500
+ };
501
+ let out_js_name = out_path
502
+ .file_name()
503
+ .and_then(|s| s.to_str())
504
+ .unwrap_or("out.js");
505
+
506
+ let (js, map_json) = if input_path.extension().map(|e| e == "jsx") == Some(true) {
430
507
  let source = fs::read_to_string(input_path).map_err(|e| format!("{}", e))?;
431
508
  let wrapped = format!(
432
509
  "export fn __TishJsxRoot() {{\n return (\n{}\n )\n}}",
433
510
  source.trim()
434
511
  );
435
- let program = tishlang_parser::parse(&wrapped)
436
- .map_err(|e| format!("JSX wrapper parse: {}", e))?;
512
+ let program =
513
+ tishlang_parser::parse(&wrapped).map_err(|e| format!("JSX wrapper parse: {}", e))?;
437
514
  let p = if optimize {
438
515
  tishlang_opt::optimize(&program)
439
516
  } else {
440
517
  program
441
518
  };
442
- tishlang_compile_js::compile_with_jsx(&p, optimize).map_err(|e| format!("{}", e))?
519
+ let js = tishlang_compile_js::compile_with_jsx(&p, optimize).map_err(|e| format!("{}", e))?;
520
+ (js, None)
443
521
  } else if input_path.extension().map(|e| e == "js") == Some(true) {
444
522
  let source = fs::read_to_string(input_path).map_err(|e| format!("{}", e))?;
445
523
  let program = tishlang_js_to_tish::convert(&source).map_err(|e| format!("{}", e))?;
446
- tishlang_compile_js::compile_with_jsx(&program, optimize).map_err(|e| format!("{}", e))?
447
- } else {
448
- tishlang_compile_js::compile_project_with_jsx(input_path, project_root, optimize)
449
- .map_err(|e| format!("{}", e))?
450
- };
451
-
452
- let out_path = Path::new(output_path);
453
- let out_path = if out_path.extension().is_none()
454
- || out_path.extension() == Some(std::ffi::OsStr::new(""))
455
- {
456
- out_path.with_extension("js")
524
+ let js =
525
+ tishlang_compile_js::compile_with_jsx(&program, optimize).map_err(|e| format!("{}", e))?;
526
+ (js, None)
527
+ } else if source_map {
528
+ let bundle = tishlang_compile_js::compile_project_with_jsx_and_source_map(
529
+ input_path,
530
+ project_root,
531
+ out_js_name,
532
+ )
533
+ .map_err(|e| format!("{}", e))?;
534
+ (bundle.js, bundle.source_map_json)
457
535
  } else {
458
- out_path.to_path_buf()
536
+ let js = tishlang_compile_js::compile_project_with_jsx(input_path, project_root, optimize)
537
+ .map_err(|e| format!("{}", e))?;
538
+ (js, None)
459
539
  };
460
540
 
461
541
  if let Some(parent) = out_path.parent() {
462
542
  fs::create_dir_all(parent)
463
543
  .map_err(|e| format!("Cannot create output directory {}: {}", parent.display(), e))?;
464
544
  }
465
- fs::write(&out_path, js).map_err(|e| format!("Cannot write {}: {}", out_path.display(), e))?;
545
+ let mut js_out = js;
546
+ if let Some(map) = &map_json {
547
+ let map_path = out_path.with_extension("js.map");
548
+ fs::write(&map_path, map).map_err(|e| format!("Cannot write {}: {}", map_path.display(), e))?;
549
+ let map_url = map_path
550
+ .file_name()
551
+ .and_then(|s| s.to_str())
552
+ .unwrap_or("out.js.map");
553
+ js_out.push_str(&format!("\n//# sourceMappingURL={map_url}\n"));
554
+ println!("Built: {}", map_path.display());
555
+ }
556
+ fs::write(&out_path, js_out).map_err(|e| format!("Cannot write {}: {}", out_path.display(), e))?;
466
557
  println!("Built: {}", out_path.display());
467
558
  Ok(())
468
559
  }
@@ -475,15 +566,17 @@ fn build_file(
475
566
  native_backend: &str,
476
567
  cli_features: &[String],
477
568
  no_optimize: bool,
569
+ source_map: bool,
478
570
  ) -> Result<(), String> {
479
571
  let optimize = !no_optimize;
480
- let input_path =
481
- Path::new(input_path).canonicalize().map_err(|e| format!("Cannot resolve {}: {}", input_path, e))?;
572
+ let input_path = Path::new(input_path)
573
+ .canonicalize()
574
+ .map_err(|e| format!("Cannot resolve {}: {}", input_path, e))?;
482
575
 
483
576
  let is_js = input_path.extension().map(|e| e == "js") == Some(true);
484
577
 
485
578
  if target == "js" {
486
- return compile_to_js(&input_path, output_path, optimize);
579
+ return compile_to_js(&input_path, output_path, optimize, source_map);
487
580
  }
488
581
 
489
582
  if target == "wasm" && is_js {
@@ -501,8 +594,13 @@ fn build_file(
501
594
  Some(p)
502
595
  }
503
596
  });
504
- return tishlang_wasm::compile_to_wasm(&input_path, project_root, Path::new(output_path), optimize)
505
- .map_err(|e| e.to_string());
597
+ return tishlang_wasm::compile_to_wasm(
598
+ &input_path,
599
+ project_root,
600
+ Path::new(output_path),
601
+ optimize,
602
+ )
603
+ .map_err(|e| e.to_string());
506
604
  }
507
605
 
508
606
  if target == "wasi" {
@@ -513,8 +611,13 @@ fn build_file(
513
611
  Some(p)
514
612
  }
515
613
  });
516
- return tishlang_wasm::compile_to_wasi(&input_path, project_root, Path::new(output_path), optimize)
517
- .map_err(|e| e.to_string());
614
+ return tishlang_wasm::compile_to_wasi(
615
+ &input_path,
616
+ project_root,
617
+ Path::new(output_path),
618
+ optimize,
619
+ )
620
+ .map_err(|e| e.to_string());
518
621
  }
519
622
 
520
623
  if target != "native" {
@@ -570,8 +673,6 @@ fn build_file(
570
673
  Ok(())
571
674
  }
572
675
 
573
-
574
-
575
676
  #[cfg(test)]
576
677
  mod cli_tests {
577
678
  use clap::Parser;
@@ -582,10 +683,7 @@ mod cli_tests {
582
683
 
583
684
  #[test]
584
685
  fn implicit_run_inserts_run_before_file() {
585
- let argv = argv_with_implicit_run(vec![
586
- "tish".to_string(),
587
- "hello.tish".to_string(),
588
- ]);
686
+ let argv = argv_with_implicit_run(vec!["tish".to_string(), "hello.tish".to_string()]);
589
687
  let cli = Cli::try_parse_from(argv).unwrap();
590
688
  match cli.command {
591
689
  Some(Commands::Run(a)) => assert_eq!(a.file, "hello.tish"),
@@ -595,26 +693,15 @@ mod cli_tests {
595
693
 
596
694
  #[test]
597
695
  fn explicit_subcommand_not_treated_as_file() {
598
- let argv = argv_with_implicit_run(vec![
599
- "tish".to_string(),
600
- "repl".to_string(),
601
- ]);
696
+ let argv = argv_with_implicit_run(vec!["tish".to_string(), "repl".to_string()]);
602
697
  let cli = Cli::try_parse_from(argv).unwrap();
603
698
  assert!(matches!(cli.command, Some(Commands::Repl(_))));
604
699
  }
605
700
 
606
701
  #[test]
607
702
  fn build_js_target_parses() {
608
- let cli = Cli::try_parse_from([
609
- "tish",
610
- "build",
611
- "m.tish",
612
- "--target",
613
- "js",
614
- "-o",
615
- "x.js",
616
- ])
617
- .unwrap();
703
+ let cli = Cli::try_parse_from(["tish", "build", "m.tish", "--target", "js", "-o", "x.js"])
704
+ .unwrap();
618
705
  match cli.command {
619
706
  Some(Commands::Build(a)) => assert_eq!(a.file, "m.tish"),
620
707
  _ => panic!("expected Build"),
@@ -632,8 +719,7 @@ mod cli_tests {
632
719
  }
633
720
 
634
721
  fn dump_ast(path: &str) -> Result<(), String> {
635
- let source =
636
- fs::read_to_string(path).map_err(|e| format!("Cannot read {}: {}", path, e))?;
722
+ let source = fs::read_to_string(path).map_err(|e| format!("Cannot read {}: {}", path, e))?;
637
723
  let program = tishlang_parser::parse(&source)?;
638
724
  println!("{:#?}", program);
639
725
  Ok(())
@@ -2,8 +2,8 @@
2
2
  //! Grey preview hint below the line (like Node) and Tab for full list.
3
3
 
4
4
  use std::borrow::Cow;
5
- use std::cell::RefCell;
6
- use std::rc::Rc;
5
+
6
+ use tishlang_core::VmRef;
7
7
 
8
8
  use rustyline::completion::{Completer, Pair};
9
9
  use rustyline::highlight::Highlighter;
@@ -29,7 +29,7 @@ const ANSI_RESET: &str = "\x1b[0m";
29
29
 
30
30
  /// Tab completer that evaluates the expression before the last `.` and suggests property/method names.
31
31
  pub struct ReplCompleter {
32
- pub vm: Rc<RefCell<Vm>>,
32
+ pub vm: VmRef<Vm>,
33
33
  pub no_optimize: bool,
34
34
  }
35
35
 
@@ -8,7 +8,9 @@ use tishlang_compile::{compile_project_full, merge_modules, resolve_project};
8
8
 
9
9
  fn native_build_features_from_cli(cli_features: &[String]) -> Vec<String> {
10
10
  if cli_features.is_empty() {
11
- let mut v: Vec<String> = tishlang_vm::all_compiled_capabilities().into_iter().collect();
11
+ let mut v: Vec<String> = tishlang_vm::all_compiled_capabilities()
12
+ .into_iter()
13
+ .collect();
12
14
  v.sort();
13
15
  v
14
16
  } else {
@@ -37,7 +39,7 @@ fn resolve_and_merge_cargo_example_fixture() {
37
39
  panic!("expected import, got {:?}", first);
38
40
  };
39
41
  assert_eq!(from.as_ref(), "cargo:demo_shim");
40
- merge_modules(modules).unwrap();
42
+ let _ = merge_modules(modules).unwrap();
41
43
  }
42
44
 
43
45
  #[test]