@tishlang/tish 1.7.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 (95) hide show
  1. package/Cargo.toml +1 -0
  2. package/README.md +2 -0
  3. package/bin/tish +0 -0
  4. package/crates/js_to_tish/src/transform/expr.rs +28 -8
  5. package/crates/js_to_tish/src/transform/stmt.rs +49 -22
  6. package/crates/tish/Cargo.toml +15 -5
  7. package/crates/tish/src/cargo_native_registry.rs +29 -0
  8. package/crates/tish/src/cli_help.rs +16 -10
  9. package/crates/tish/src/main.rs +87 -32
  10. package/crates/tish/src/repl_completion.rs +3 -3
  11. package/crates/tish/tests/cargo_example_compile.rs +1 -1
  12. package/crates/tish/tests/integration_test.rs +19 -7
  13. package/crates/tish/tests/shortcircuit.rs +1 -1
  14. package/crates/tish_ast/src/ast.rs +80 -9
  15. package/crates/tish_build_utils/Cargo.toml +4 -0
  16. package/crates/tish_build_utils/src/lib.rs +105 -2
  17. package/crates/tish_builtins/Cargo.toml +5 -1
  18. package/crates/tish_builtins/src/array.rs +13 -12
  19. package/crates/tish_builtins/src/construct.rs +34 -33
  20. package/crates/tish_builtins/src/globals.rs +12 -11
  21. package/crates/tish_builtins/src/helpers.rs +2 -1
  22. package/crates/tish_builtins/src/object.rs +3 -2
  23. package/crates/tish_builtins/src/string.rs +73 -3
  24. package/crates/tish_bytecode/src/compiler.rs +12 -14
  25. package/crates/tish_bytecode/src/opcode.rs +12 -3
  26. package/crates/tish_compile/Cargo.toml +1 -0
  27. package/crates/tish_compile/src/codegen.rs +745 -199
  28. package/crates/tish_compile/src/infer.rs +6 -0
  29. package/crates/tish_compile/src/lib.rs +4 -3
  30. package/crates/tish_compile/src/resolve.rs +180 -82
  31. package/crates/tish_compile/src/types.rs +175 -11
  32. package/crates/tish_compile_js/Cargo.toml +1 -0
  33. package/crates/tish_compile_js/src/codegen.rs +152 -29
  34. package/crates/tish_compile_js/src/lib.rs +3 -1
  35. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +31 -12
  36. package/crates/tish_core/Cargo.toml +8 -0
  37. package/crates/tish_core/src/json.rs +102 -53
  38. package/crates/tish_core/src/lib.rs +3 -1
  39. package/crates/tish_core/src/macros.rs +5 -5
  40. package/crates/tish_core/src/value.rs +53 -15
  41. package/crates/tish_core/src/vmref.rs +178 -0
  42. package/crates/tish_eval/Cargo.toml +17 -2
  43. package/crates/tish_eval/src/eval.rs +90 -28
  44. package/crates/tish_eval/src/http.rs +61 -0
  45. package/crates/tish_eval/src/lib.rs +3 -3
  46. package/crates/tish_eval/src/natives.rs +41 -0
  47. package/crates/tish_eval/src/value.rs +7 -3
  48. package/crates/tish_eval/src/value_convert.rs +13 -5
  49. package/crates/tish_fmt/src/lib.rs +120 -30
  50. package/crates/tish_lexer/src/lib.rs +20 -5
  51. package/crates/tish_lexer/src/token.rs +4 -0
  52. package/crates/tish_llvm/src/lib.rs +3 -1
  53. package/crates/tish_lsp/Cargo.toml +4 -1
  54. package/crates/tish_lsp/README.md +1 -1
  55. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  56. package/crates/tish_lsp/src/import_goto.rs +549 -0
  57. package/crates/tish_lsp/src/main.rs +502 -102
  58. package/crates/tish_native/src/build.rs +3 -2
  59. package/crates/tish_native/src/lib.rs +6 -2
  60. package/crates/tish_opt/src/lib.rs +17 -2
  61. package/crates/tish_parser/src/lib.rs +10 -3
  62. package/crates/tish_parser/src/parser.rs +346 -56
  63. package/crates/tish_resolve/Cargo.toml +13 -0
  64. package/crates/tish_resolve/src/lib.rs +3436 -0
  65. package/crates/tish_resolve/src/pos.rs +133 -0
  66. package/crates/tish_runtime/Cargo.toml +68 -3
  67. package/crates/tish_runtime/src/http.rs +1123 -141
  68. package/crates/tish_runtime/src/http_fetch.rs +15 -14
  69. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  70. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  71. package/crates/tish_runtime/src/lib.rs +159 -29
  72. package/crates/tish_runtime/src/promise.rs +199 -36
  73. package/crates/tish_runtime/src/promise_io.rs +2 -1
  74. package/crates/tish_runtime/src/timers.rs +37 -1
  75. package/crates/tish_runtime/src/ws.rs +26 -28
  76. package/crates/tish_ui/src/jsx.rs +279 -8
  77. package/crates/tish_ui/src/lib.rs +5 -2
  78. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  79. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  80. package/crates/tish_vm/Cargo.toml +15 -5
  81. package/crates/tish_vm/src/vm.rs +506 -259
  82. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +3 -1
  83. package/crates/tish_wasm/src/lib.rs +17 -14
  84. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  85. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  86. package/crates/tishlang_cargo_bindgen/Cargo.toml +1 -0
  87. package/crates/tishlang_cargo_bindgen/src/discover.rs +68 -0
  88. package/crates/tishlang_cargo_bindgen/src/lib.rs +5 -4
  89. package/justfile +8 -0
  90. package/package.json +1 -1
  91. package/platform/darwin-arm64/tish +0 -0
  92. package/platform/darwin-x64/tish +0 -0
  93. package/platform/linux-arm64/tish +0 -0
  94. package/platform/linux-x64/tish +0 -0
  95. package/platform/win32-x64/tish.exe +0 -0
package/Cargo.toml CHANGED
@@ -28,6 +28,7 @@ members = [
28
28
  "crates/tish_fmt",
29
29
  "crates/tish_lint",
30
30
  "crates/tish_lsp",
31
+ "crates/tish_resolve",
31
32
  "crates/tishlang_cargo_bindgen",
32
33
  ]
33
34
  resolver = "2"
package/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  This npm package ships the **Tish CLI**. It includes platform-specific native binaries under `platform/`; **`npm install`** runs `postinstall`, which copies the correct binary to `bin/tish`. That file is what runs when you invoke `tish` — the CLI itself is native, not Node. Node **22+** is required for install scripts and tooling (e.g. semantic-release in this repo); the `tish` binary has no Node runtime dependency.
6
6
 
7
+ Those prebuilt binaries are built with **`--features full`** (http, fs, process, regex, ws) so `tish run` and the bytecode VM match a full developer toolchain. For a minimal CLI, build `tishlang` from the repo with `--no-default-features` (see the workspace `justfile`).
8
+
7
9
  ## Install
8
10
 
9
11
  ```bash
package/bin/tish CHANGED
Binary file
@@ -89,9 +89,14 @@ pub fn convert_expr(expr: &OxcExpr<'_>, ctx: &Ctx<'_>) -> Result<Expr, ConvertEr
89
89
  }
90
90
  OxcExpr::StaticMemberExpression(s) => {
91
91
  let object = Box::new(convert_expr(&s.object, ctx)?);
92
+ let name = Arc::from(s.property.name.as_str());
93
+ let name_span = span_util::oxc_span_to_tish(ctx.1, &s.property);
92
94
  Ok(Expr::Member {
93
95
  object,
94
- prop: MemberProp::Name(Arc::from(s.property.name.as_str())),
96
+ prop: MemberProp::Name {
97
+ name,
98
+ span: name_span,
99
+ },
95
100
  optional: s.optional,
96
101
  span,
97
102
  })
@@ -241,9 +246,14 @@ fn convert_chain_element(
241
246
  match ce {
242
247
  oxc::ast::ast::ChainElement::StaticMemberExpression(m) => {
243
248
  let object = Box::new(convert_expr(&m.object, ctx)?);
249
+ let name = Arc::from(m.property.name.as_str());
250
+ let name_span = span_util::oxc_span_to_tish(ctx.1, &m.property);
244
251
  Ok(Expr::Member {
245
252
  object,
246
- prop: MemberProp::Name(Arc::from(m.property.name.as_str())),
253
+ prop: MemberProp::Name {
254
+ name,
255
+ span: name_span,
256
+ },
247
257
  optional: m.optional,
248
258
  span,
249
259
  })
@@ -504,8 +514,11 @@ pub fn convert_params(
504
514
  for (i, p) in params.items.iter().enumerate() {
505
515
  if params.rest.is_some() && i == params.items.len() - 1 {
506
516
  if let Some(rest) = &params.rest {
507
- let rest_name = match &rest.rest.argument {
508
- oxc::ast::ast::BindingPattern::BindingIdentifier(b) => b.name.as_str(),
517
+ let (rest_name, rest_name_span) = match &rest.rest.argument {
518
+ oxc::ast::ast::BindingPattern::BindingIdentifier(b) => (
519
+ b.name.as_str(),
520
+ crate::span_util::oxc_span_to_tish(ctx.1, b.as_ref()),
521
+ ),
509
522
  _ => {
510
523
  return Err(ConvertError::new(ConvertErrorKind::Unsupported {
511
524
  what: "rest param with non-identifier".into(),
@@ -515,6 +528,7 @@ pub fn convert_params(
515
528
  };
516
529
  rest_param = Some(TypedParam {
517
530
  name: Arc::from(rest_name),
531
+ name_span: rest_name_span,
518
532
  type_ann: None,
519
533
  default: None,
520
534
  });
@@ -524,8 +538,10 @@ pub fn convert_params(
524
538
  // params.items contains FormalParameter structs (not enum)
525
539
  let fp = p;
526
540
  {
527
- let name = match &fp.pattern {
528
- oxc::ast::ast::BindingPattern::BindingIdentifier(b) => b.name.as_str(),
541
+ let (name, name_span) = match &fp.pattern {
542
+ oxc::ast::ast::BindingPattern::BindingIdentifier(b) => {
543
+ (b.name.as_str(), crate::span_util::oxc_span_to_tish(ctx.1, b.as_ref()))
544
+ }
529
545
  _ => {
530
546
  return Err(ConvertError::new(ConvertErrorKind::Unsupported {
531
547
  what: "destructuring in params".into(),
@@ -540,6 +556,7 @@ pub fn convert_params(
540
556
  .transpose()?;
541
557
  typed_params.push(FunParam::Simple(TypedParam {
542
558
  name: Arc::from(name),
559
+ name_span,
543
560
  type_ann: None,
544
561
  default,
545
562
  }));
@@ -547,8 +564,10 @@ pub fn convert_params(
547
564
  }
548
565
  if rest_param.is_none() {
549
566
  if let Some(rest) = &params.rest {
550
- let rest_name = match &rest.rest.argument {
551
- oxc::ast::ast::BindingPattern::BindingIdentifier(b) => b.name.as_str(),
567
+ let (rest_name, rest_name_span) = match &rest.rest.argument {
568
+ oxc::ast::ast::BindingPattern::BindingIdentifier(b) => {
569
+ (b.name.as_str(), crate::span_util::oxc_span_to_tish(ctx.1, b.as_ref()))
570
+ }
552
571
  _ => {
553
572
  return Err(ConvertError::new(ConvertErrorKind::Unsupported {
554
573
  what: "rest param with non-identifier".into(),
@@ -558,6 +577,7 @@ pub fn convert_params(
558
577
  };
559
578
  rest_param = Some(TypedParam {
560
579
  name: Arc::from(rest_name),
580
+ name_span: rest_name_span,
561
581
  type_ann: None,
562
582
  default: None,
563
583
  });
@@ -149,8 +149,10 @@ fn convert_var_decl(
149
149
  match id {
150
150
  oxc::ast::ast::BindingPattern::BindingIdentifier(b) => {
151
151
  let name: Arc<str> = Arc::from(b.name.as_str());
152
+ let name_span = span_util::oxc_span_to_tish(ctx.1, b.as_ref());
152
153
  Ok(Statement::VarDecl {
153
154
  name,
155
+ name_span,
154
156
  mutable,
155
157
  type_ann: None,
156
158
  init,
@@ -240,12 +242,14 @@ fn convert_for_of_statement(
240
242
  ctx: &Ctx<'_>,
241
243
  span: Span,
242
244
  ) -> Result<Statement, ConvertError> {
243
- let name = match &f.left {
245
+ let (name, name_span) = match &f.left {
244
246
  oxc::ast::ast::ForStatementLeft::VariableDeclaration(v) => {
245
247
  if v.declarations.len() == 1 {
246
248
  let d = &v.declarations[0];
247
249
  match &d.id {
248
- oxc::ast::ast::BindingPattern::BindingIdentifier(b) => b.name.as_str(),
250
+ oxc::ast::ast::BindingPattern::BindingIdentifier(b) => {
251
+ (b.name.as_str(), span_util::oxc_span_to_tish(ctx.1, b.as_ref()))
252
+ }
249
253
  _ => {
250
254
  return Err(ConvertError::new(ConvertErrorKind::Incompatible {
251
255
  what: "for-of with destructuring".into(),
@@ -271,6 +275,7 @@ fn convert_for_of_statement(
271
275
  let body = Box::new(convert_statement(&f.body, ctx)?);
272
276
  Ok(Statement::ForOf {
273
277
  name: Arc::from(name),
278
+ name_span,
274
279
  iterable,
275
280
  body,
276
281
  span,
@@ -311,26 +316,30 @@ fn convert_try_statement(
311
316
  statements: body_stmts,
312
317
  span: span_util::oxc_span_to_tish(ctx.1, &*t.block),
313
318
  });
314
- let (catch_param, catch_body) = match &t.handler {
319
+ let (catch_param, catch_param_span, catch_body) = match &t.handler {
315
320
  Some(h) => {
316
- let param = h
321
+ let (param, pspan) = h
317
322
  .param
318
323
  .as_ref()
319
324
  .and_then(|cp: &oxc::ast::ast::CatchParameter<'_>| {
320
325
  if let oxc::ast::ast::BindingPattern::BindingIdentifier(b) = &cp.pattern {
321
- Some(Arc::from(b.name.as_str()))
326
+ Some((
327
+ Arc::from(b.name.as_str()),
328
+ span_util::oxc_span_to_tish(ctx.1, b.as_ref()),
329
+ ))
322
330
  } else {
323
331
  None
324
332
  }
325
- });
333
+ })
334
+ .map_or((None, None), |(n, s)| (Some(n), Some(s)));
326
335
  let catch_stmts = convert_statements(&h.body.body, ctx.0, ctx.1)?;
327
336
  let cb = Box::new(Statement::Block {
328
337
  statements: catch_stmts,
329
338
  span: span_util::oxc_span_to_tish(ctx.1, &*h.body),
330
339
  });
331
- (param, Some(cb))
340
+ (param, pspan, Some(cb))
332
341
  }
333
- None => (None, None),
342
+ None => (None, None, None),
334
343
  };
335
344
  let finally_body = t
336
345
  .finalizer
@@ -346,6 +355,7 @@ fn convert_try_statement(
346
355
  Ok(Statement::Try {
347
356
  body,
348
357
  catch_param,
358
+ catch_param_span,
349
359
  catch_body,
350
360
  finally_body,
351
361
  span,
@@ -358,10 +368,16 @@ fn convert_function_decl(
358
368
  span: Span,
359
369
  ) -> Result<Statement, ConvertError> {
360
370
  let async_ = f.r#async;
361
- let name: Arc<str> =
362
- f.id.as_ref()
363
- .map(|id| Arc::from(id.name.as_str()))
364
- .unwrap_or_else(|| Arc::from(""));
371
+ let name: Arc<str> = f
372
+ .id
373
+ .as_ref()
374
+ .map(|id| Arc::from(id.name.as_str()))
375
+ .unwrap_or_else(|| Arc::from(""));
376
+ let name_span = f
377
+ .id
378
+ .as_ref()
379
+ .map(|id| span_util::oxc_span_to_tish(ctx.1, id))
380
+ .unwrap_or_else(span_util::stub_span);
365
381
  let (params, rest_param) = expr::convert_params(&f.params, ctx)?;
366
382
  let body = match &f.body {
367
383
  Some(fb) => {
@@ -381,6 +397,7 @@ fn convert_function_decl(
381
397
  Ok(Statement::FunDecl {
382
398
  async_,
383
399
  name,
400
+ name_span,
384
401
  params,
385
402
  rest_param,
386
403
  return_type: None,
@@ -391,7 +408,7 @@ fn convert_function_decl(
391
408
 
392
409
  fn convert_import(
393
410
  i: &oxc::ast::ast::ImportDeclaration<'_>,
394
- _ctx: &Ctx<'_>,
411
+ ctx: &Ctx<'_>,
395
412
  span: Span,
396
413
  ) -> Result<Statement, ConvertError> {
397
414
  let from: Arc<str> = Arc::from(i.source.value.as_str());
@@ -402,25 +419,35 @@ fn convert_import(
402
419
  oxc::ast::ast::ImportDeclarationSpecifier::ImportSpecifier(is) => {
403
420
  let imported_name = is.imported.name().as_str();
404
421
  let local_name = is.local.name.as_str();
405
- let alias = if imported_name == local_name {
406
- None
422
+ let name_span = crate::span_util::oxc_span_to_tish(ctx.1, &is.imported);
423
+ let (alias, alias_span) = if imported_name == local_name {
424
+ (None, None)
407
425
  } else {
408
- Some(Arc::from(local_name))
426
+ (
427
+ Some(Arc::from(local_name)),
428
+ Some(crate::span_util::oxc_span_to_tish(ctx.1, &is.local)),
429
+ )
409
430
  };
410
431
  specifiers.push(tishlang_ast::ImportSpecifier::Named {
411
432
  name: Arc::from(imported_name),
433
+ name_span,
412
434
  alias,
435
+ alias_span,
413
436
  });
414
437
  }
415
438
  oxc::ast::ast::ImportDeclarationSpecifier::ImportDefaultSpecifier(ds) => {
416
- specifiers.push(tishlang_ast::ImportSpecifier::Default(Arc::from(
417
- ds.local.name.as_str(),
418
- )));
439
+ let name_span = crate::span_util::oxc_span_to_tish(ctx.1, &ds.local);
440
+ specifiers.push(tishlang_ast::ImportSpecifier::Default {
441
+ name: Arc::from(ds.local.name.as_str()),
442
+ name_span,
443
+ });
419
444
  }
420
445
  oxc::ast::ast::ImportDeclarationSpecifier::ImportNamespaceSpecifier(ns) => {
421
- specifiers.push(tishlang_ast::ImportSpecifier::Namespace(Arc::from(
422
- ns.local.name.as_str(),
423
- )));
446
+ let name_span = crate::span_util::oxc_span_to_tish(ctx.1, &ns.local);
447
+ specifiers.push(tishlang_ast::ImportSpecifier::Namespace {
448
+ name: Arc::from(ns.local.name.as_str()),
449
+ name_span,
450
+ });
424
451
  }
425
452
  }
426
453
  }
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tishlang"
3
- version = "1.7.0"
3
+ version = "1.8.0"
4
4
  edition = "2021"
5
5
  description = "Tish CLI - run, REPL, compile to native"
6
6
  license-file = { workspace = true }
@@ -11,12 +11,21 @@ name = "tish"
11
11
  path = "src/main.rs"
12
12
 
13
13
  [features]
14
- # Default: secure mode with no dangerous capabilities
15
- default = []
16
- # Full: all capabilities enabled (for development/trusted environments)
17
- full = ["http", "fs", "process", "regex", "ws"]
14
+ # The published `tish` CLI always includes every optional runtime (http, timers, fs, process, regex, ws).
15
+ # Use `cargo build -p tishlang --no-default-features` when you need a locked-down toolchain
16
+ # (see workspace `justfile`). `tish run --feature …` / `tish build --feature …` gate what runs
17
+ # or what gets linked into a *native output* binary — they are not the primary way to ship a
18
+ # “full” vs “empty” CLI.
19
+ default = ["full"]
20
+ # Alias: same dependency edges as `default` (kept for `--features full` in CI and docs).
21
+ # Includes `pg` so `tish run` can resolve `import … from 'tish-pg'` (`cargo:tish_pg`) on the bytecode VM.
22
+ # Requires `tish-pg` next to this workspace root (`…/tish/tish-pg`); CI runs `scripts/ci/ensure-tish-pg.sh`.
23
+ full = ["http", "fs", "process", "regex", "ws", "timers", "pg"]
24
+ # Opt-out: build without Postgres (`cargo build -p tishlang --no-default-features --features http,...`).
25
+ pg = ["dep:tish_pg"]
18
26
  # Individual capability flags
19
27
  http = ["tishlang_eval/http", "tishlang_runtime/http", "tishlang_compile/http", "tishlang_vm/http"]
28
+ timers = ["tishlang_eval/timers", "tishlang_compile/timers", "tishlang_vm/timers"]
20
29
  fs = ["tishlang_eval/fs", "tishlang_runtime/fs", "tishlang_compile/fs", "tishlang_vm/fs"]
21
30
  process = ["tishlang_eval/process", "tishlang_runtime/process", "tishlang_compile/process", "tishlang_vm/process"]
22
31
  regex = ["tishlang_eval/regex", "tishlang_runtime/regex", "tishlang_compile/regex", "tishlang_vm/regex"]
@@ -40,6 +49,7 @@ tishlang_runtime = { path = "../tish_runtime", version = ">=0.1" }
40
49
  tishlang_core = { path = "../tish_core", version = ">=0.1" }
41
50
  tishlang_js_to_tish = { path = "../js_to_tish", version = ">=0.1" }
42
51
  clap = { version = "4.6.0", features = ["derive", "color"] }
52
+ tish_pg = { path = "../../../tish-pg", package = "tish-pg", optional = true }
43
53
 
44
54
  [dev-dependencies]
45
55
  rayon = "1.11"
@@ -0,0 +1,29 @@
1
+ //! Registers `cargo:*` Rust shims on the bytecode VM (`tish run`, REPL).
2
+ //!
3
+ //! Native `tish build` outputs register their own crates at link time; the
4
+ //! interpreter path must populate [`tishlang_vm::Vm::register_native_module`].
5
+
6
+ #[cfg(feature = "pg")]
7
+ pub(crate) fn register_bytecode_native_modules(vm: &mut tishlang_vm::Vm) {
8
+ use std::sync::Arc;
9
+ use tishlang_core::{ObjectMap, Value};
10
+
11
+ let mut om = ObjectMap::with_capacity(8);
12
+ om.insert(
13
+ Arc::from("per_worker_client"),
14
+ Value::native(tish_pg::per_worker_client),
15
+ );
16
+ om.insert(Arc::from("connect"), Value::native(tish_pg::connect));
17
+ om.insert(Arc::from("prepare"), Value::native(tish_pg::prepare));
18
+ om.insert(
19
+ Arc::from("query_prepared"),
20
+ Value::native(tish_pg::query_prepared),
21
+ );
22
+ om.insert(Arc::from("query_all"), Value::native(tish_pg::query_all));
23
+ om.insert(Arc::from("migrate"), Value::native(tish_pg::migrate));
24
+ om.insert(Arc::from("close"), Value::native(tish_pg::close));
25
+ vm.register_native_module("cargo:tish_pg", om);
26
+ }
27
+
28
+ #[cfg(not(feature = "pg"))]
29
+ pub(crate) fn register_bytecode_native_modules(_vm: &mut tishlang_vm::Vm) {}
@@ -337,7 +337,9 @@ fn capabilities_section(oh: &str, t: &str, r: &str) -> String {
337
337
 
338
338
  {oh}Capabilities{r} (--feature, repeatable; comma-separated values are split):
339
339
  {t}http{r}
340
- Network: fetch, serve, Promise / timers (native async)
340
+ Network: fetch, fetchAll, serve, Promise (and `await`); enabling http also enables timers
341
+ {t}timers{r}
342
+ setTimeout, setInterval, clearTimeout, clearInterval (global + `import from \"timers\"` / tish:timers)
341
343
  {t}fs{r}
342
344
  Filesystem: readFile, writeFile, fileExists, isDir, readDir, mkdir
343
345
  {t}process{r}
@@ -347,9 +349,9 @@ fn capabilities_section(oh: &str, t: &str, r: &str) -> String {
347
349
  {t}ws{r}
348
350
  WebSocket client / server
349
351
  {t}full{r}
350
- All of the above (http, fs, process, regex, ws)
352
+ All of the above (http, timers, fs, process, regex, ws)
351
353
 
352
- Omit --feature to use every capability linked into this binary."
354
+ Omit --feature to allow every capability compiled into this `tish` binary; pass flags to restrict what scripts may use. The CLI is normally built with all of them (Cargo default on `tishlang`)."
353
355
  )
354
356
  }
355
357
 
@@ -414,7 +416,9 @@ pub fn build_after_help() -> String {
414
416
 
415
417
  {oh}Capabilities{r} (--feature, repeatable; comma-separated values are split):
416
418
  {t}http{r}
417
- Network: fetch, serve, Promise / timers (native async)
419
+ Network: fetch, fetchAll, serve, Promise (and `await`); enabling http also enables timers
420
+ {t}timers{r}
421
+ setTimeout, setInterval, clearTimeout, clearInterval (global + `import from \"timers\"` / tish:timers)
418
422
  {t}fs{r}
419
423
  Filesystem: readFile, writeFile, fileExists, isDir, readDir, mkdir
420
424
  {t}process{r}
@@ -424,10 +428,9 @@ pub fn build_after_help() -> String {
424
428
  {t}ws{r}
425
429
  WebSocket client / server
426
430
  {t}full{r}
427
- All of the above (http, fs, process, regex, ws)
431
+ All of the above (http, timers, fs, process, regex, ws)
428
432
 
429
- Omit --feature to use every capability linked into this binary.
430
- Build `tish` with matching Cargo features (e.g. cargo build -p tishlang --features full)."
433
+ For `--target native`, these choose what is linked into the **output** executable (omit = same set as this `tish` binary was built with). Minimal native outputs still use a full `tish` CLI unless you built it with `cargo build -p tishlang --no-default-features`."
431
434
  )
432
435
  }
433
436
 
@@ -458,7 +461,7 @@ pub(crate) struct RunArgs {
458
461
  help_heading = "Options"
459
462
  )]
460
463
  pub backend: String,
461
- /// Subset of capabilities (see `tish --help` for the full list).
464
+ /// Restrict which platform APIs the script may use (omit = all capabilities compiled into this `tish`).
462
465
  #[arg(
463
466
  long = "feature",
464
467
  value_name = "NAME",
@@ -481,7 +484,7 @@ pub(crate) struct ReplArgs {
481
484
  help_heading = "Options"
482
485
  )]
483
486
  pub backend: String,
484
- /// Subset of capabilities (see `tish --help` for the full list).
487
+ /// Restrict which platform APIs the REPL may use (omit = all capabilities compiled into this `tish`).
485
488
  #[arg(
486
489
  long = "feature",
487
490
  value_name = "NAME",
@@ -519,7 +522,7 @@ pub(crate) struct BuildArgs {
519
522
  help_heading = "Options"
520
523
  )]
521
524
  pub native_backend: String,
522
- /// Capability subset for native output (see long help below).
525
+ /// For `--target native`: which capabilities to link into the produced binary (omit = same as this `tish`; see long help).
523
526
  #[arg(
524
527
  long = "feature",
525
528
  value_name = "NAME",
@@ -529,6 +532,9 @@ pub(crate) struct BuildArgs {
529
532
  pub features: Vec<String>,
530
533
  #[arg(long, help_heading = "Options")]
531
534
  pub no_optimize: bool,
535
+ /// For `--target js` project builds: emit `OUTPUT.js.map` and `//# sourceMappingURL=…` so JS/TS tools can jump to original `.tish` (implies `--no-optimize` for that build).
536
+ #[arg(long, help_heading = "Options")]
537
+ pub source_map: bool,
532
538
  /// Entry `.tish` file (or `.js` where supported).
533
539
  #[arg(required = true, value_name = "FILE", help_heading = "Arguments")]
534
540
  pub file: String,
@@ -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 {
@@ -110,6 +112,7 @@ fn main() {
110
112
  &a.native_backend,
111
113
  &a.features,
112
114
  a.no_optimize || no_opt_env,
115
+ a.source_map,
113
116
  ),
114
117
  Some(Commands::DumpAst { file }) => dump_ast(&file),
115
118
  None => {
@@ -160,7 +163,7 @@ fn run_stdin_source(
160
163
  let cwd = std::env::current_dir().map_err(|e| e.to_string())?;
161
164
  let modules = tishlang_compile::resolve_project_from_stdin(source, &cwd)?;
162
165
  tishlang_compile::detect_cycles(&modules)?;
163
- let prog = tishlang_compile::merge_modules(modules)?;
166
+ let prog = tishlang_compile::merge_modules(modules)?.program;
164
167
  let program = if no_optimize {
165
168
  prog
166
169
  } else {
@@ -202,7 +205,7 @@ fn run_file(
202
205
  } else {
203
206
  let modules = tishlang_compile::resolve_project(&path, project_root)?;
204
207
  tishlang_compile::detect_cycles(&modules)?;
205
- let prog = tishlang_compile::merge_modules(modules)?;
208
+ let prog = tishlang_compile::merge_modules(modules)?.program;
206
209
  if no_optimize {
207
210
  prog
208
211
  } else {
@@ -223,6 +226,10 @@ fn run_program(
223
226
  if backend == "interp" {
224
227
  let mut eval = tishlang_eval::Evaluator::new();
225
228
  let value = eval.eval_program(program)?;
229
+ #[cfg(feature = "timers")]
230
+ {
231
+ let _ = eval.run_timer_phase();
232
+ }
226
233
  if !matches!(value, tishlang_eval::Value::Null) {
227
234
  println!(
228
235
  "{}",
@@ -241,13 +248,9 @@ fn run_program(
241
248
  tishlang_bytecode::compile(program).map_err(|e| e.to_string())?
242
249
  };
243
250
  let caps = vm_capabilities_for_cli_run(features);
244
- let value = tishlang_vm::run_with_options(
245
- &chunk,
246
- tishlang_vm::VmRunOptions {
247
- repl_mode: false,
248
- capabilities: caps,
249
- },
250
- )?;
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)?;
251
254
  if !matches!(value, tishlang_core::Value::Null) {
252
255
  println!(
253
256
  "{}",
@@ -294,6 +297,10 @@ fn run_repl(backend: &str, no_optimize: bool, features: &[String]) -> Result<(),
294
297
  Ok(program) => {
295
298
  match eval.eval_program(&program) {
296
299
  Ok(v) => {
300
+ #[cfg(feature = "timers")]
301
+ {
302
+ let _ = eval.run_timer_phase();
303
+ }
297
304
  if !matches!(v, tishlang_eval::Value::Null) {
298
305
  println!(
299
306
  "{}",
@@ -325,11 +332,11 @@ fn run_repl(backend: &str, no_optimize: bool, features: &[String]) -> Result<(),
325
332
  if !std::io::stdin().is_terminal() {
326
333
  eprintln!("Note: Tab completion and grey preview require an interactive terminal (TTY).");
327
334
  }
328
- let vm = Rc::new(RefCell::new(tishlang_vm::Vm::with_capabilities(
329
- vm_capabilities_for_cli_run(features),
330
- )));
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);
331
338
  let completer = repl_completion::ReplCompleter {
332
- vm: Rc::clone(&vm),
339
+ vm: vm.clone(),
333
340
  no_optimize,
334
341
  };
335
342
  let config = Config::builder()
@@ -455,7 +462,27 @@ fn tish_history_path() -> Option<PathBuf> {
455
462
  home.map(|h| PathBuf::from(h).join(".tish_history"))
456
463
  }
457
464
 
458
- 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
+ }
459
486
  let project_root = input_path.parent().and_then(|p| {
460
487
  if p.file_name().and_then(|n| n.to_str()) == Some("src") {
461
488
  p.parent()
@@ -463,7 +490,20 @@ fn compile_to_js(input_path: &Path, output_path: &str, optimize: bool) -> Result
463
490
  Some(p)
464
491
  }
465
492
  });
466
- 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) {
467
507
  let source = fs::read_to_string(input_path).map_err(|e| format!("{}", e))?;
468
508
  let wrapped = format!(
469
509
  "export fn __TishJsxRoot() {{\n return (\n{}\n )\n}}",
@@ -476,30 +516,44 @@ fn compile_to_js(input_path: &Path, output_path: &str, optimize: bool) -> Result
476
516
  } else {
477
517
  program
478
518
  };
479
- 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)
480
521
  } else if input_path.extension().map(|e| e == "js") == Some(true) {
481
522
  let source = fs::read_to_string(input_path).map_err(|e| format!("{}", e))?;
482
523
  let program = tishlang_js_to_tish::convert(&source).map_err(|e| format!("{}", e))?;
483
- tishlang_compile_js::compile_with_jsx(&program, optimize).map_err(|e| format!("{}", e))?
484
- } else {
485
- tishlang_compile_js::compile_project_with_jsx(input_path, project_root, optimize)
486
- .map_err(|e| format!("{}", e))?
487
- };
488
-
489
- let out_path = Path::new(output_path);
490
- let out_path = if out_path.extension().is_none()
491
- || out_path.extension() == Some(std::ffi::OsStr::new(""))
492
- {
493
- 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)
494
535
  } else {
495
- 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)
496
539
  };
497
540
 
498
541
  if let Some(parent) = out_path.parent() {
499
542
  fs::create_dir_all(parent)
500
543
  .map_err(|e| format!("Cannot create output directory {}: {}", parent.display(), e))?;
501
544
  }
502
- 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))?;
503
557
  println!("Built: {}", out_path.display());
504
558
  Ok(())
505
559
  }
@@ -512,6 +566,7 @@ fn build_file(
512
566
  native_backend: &str,
513
567
  cli_features: &[String],
514
568
  no_optimize: bool,
569
+ source_map: bool,
515
570
  ) -> Result<(), String> {
516
571
  let optimize = !no_optimize;
517
572
  let input_path = Path::new(input_path)
@@ -521,7 +576,7 @@ fn build_file(
521
576
  let is_js = input_path.extension().map(|e| e == "js") == Some(true);
522
577
 
523
578
  if target == "js" {
524
- return compile_to_js(&input_path, output_path, optimize);
579
+ return compile_to_js(&input_path, output_path, optimize, source_map);
525
580
  }
526
581
 
527
582
  if target == "wasm" && is_js {