@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.
- package/Cargo.toml +1 -0
- package/README.md +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +28 -8
- package/crates/js_to_tish/src/transform/stmt.rs +49 -22
- package/crates/tish/Cargo.toml +15 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +16 -10
- package/crates/tish/src/main.rs +87 -32
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +1 -1
- package/crates/tish/tests/integration_test.rs +19 -7
- package/crates/tish/tests/shortcircuit.rs +1 -1
- package/crates/tish_ast/src/ast.rs +80 -9
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +105 -2
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +13 -12
- package/crates/tish_builtins/src/construct.rs +34 -33
- package/crates/tish_builtins/src/globals.rs +12 -11
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +73 -3
- package/crates/tish_bytecode/src/compiler.rs +12 -14
- package/crates/tish_bytecode/src/opcode.rs +12 -3
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +745 -199
- package/crates/tish_compile/src/infer.rs +6 -0
- package/crates/tish_compile/src/lib.rs +4 -3
- package/crates/tish_compile/src/resolve.rs +180 -82
- package/crates/tish_compile/src/types.rs +175 -11
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +152 -29
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +31 -12
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +102 -53
- package/crates/tish_core/src/lib.rs +3 -1
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/value.rs +53 -15
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +90 -28
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +3 -3
- package/crates/tish_eval/src/natives.rs +41 -0
- package/crates/tish_eval/src/value.rs +7 -3
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +120 -30
- package/crates/tish_lexer/src/lib.rs +20 -5
- package/crates/tish_lexer/src/token.rs +4 -0
- package/crates/tish_llvm/src/lib.rs +3 -1
- package/crates/tish_lsp/Cargo.toml +4 -1
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_lsp/src/builtin_goto.rs +261 -0
- package/crates/tish_lsp/src/import_goto.rs +549 -0
- package/crates/tish_lsp/src/main.rs +502 -102
- package/crates/tish_native/src/build.rs +3 -2
- package/crates/tish_native/src/lib.rs +6 -2
- package/crates/tish_opt/src/lib.rs +17 -2
- package/crates/tish_parser/src/lib.rs +10 -3
- package/crates/tish_parser/src/parser.rs +346 -56
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3436 -0
- package/crates/tish_resolve/src/pos.rs +133 -0
- package/crates/tish_runtime/Cargo.toml +68 -3
- package/crates/tish_runtime/src/http.rs +1123 -141
- package/crates/tish_runtime/src/http_fetch.rs +15 -14
- 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 +159 -29
- package/crates/tish_runtime/src/promise.rs +199 -36
- package/crates/tish_runtime/src/promise_io.rs +2 -1
- package/crates/tish_runtime/src/timers.rs +37 -1
- package/crates/tish_runtime/src/ws.rs +26 -28
- package/crates/tish_ui/src/jsx.rs +279 -8
- package/crates/tish_ui/src/lib.rs +5 -2
- package/crates/tish_ui/src/runtime/hooks.rs +406 -45
- package/crates/tish_ui/src/runtime/mod.rs +36 -9
- package/crates/tish_vm/Cargo.toml +15 -5
- package/crates/tish_vm/src/vm.rs +506 -259
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +3 -1
- package/crates/tish_wasm/src/lib.rs +17 -14
- package/crates/tish_wasm_runtime/Cargo.toml +2 -1
- package/crates/tish_wasm_runtime/src/lib.rs +1 -1
- package/crates/tishlang_cargo_bindgen/Cargo.toml +1 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +68 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +5 -4
- package/justfile +8 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
package/Cargo.toml
CHANGED
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
|
|
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
|
|
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) = ¶ms.rest {
|
|
507
|
-
let rest_name = match &rest.rest.argument {
|
|
508
|
-
oxc::ast::ast::BindingPattern::BindingIdentifier(b) =>
|
|
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) =>
|
|
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) = ¶ms.rest {
|
|
550
|
-
let rest_name = match &rest.rest.argument {
|
|
551
|
-
oxc::ast::ast::BindingPattern::BindingIdentifier(b) =>
|
|
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) =>
|
|
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(
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
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
|
|
406
|
-
|
|
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
|
-
|
|
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
|
-
|
|
417
|
-
|
|
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
|
-
|
|
422
|
-
|
|
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
|
}
|
package/crates/tish/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "tishlang"
|
|
3
|
-
version = "1.
|
|
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
|
-
#
|
|
15
|
-
default
|
|
16
|
-
#
|
|
17
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
///
|
|
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
|
-
///
|
|
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
|
-
///
|
|
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,
|
package/crates/tish/src/main.rs
CHANGED
|
@@ -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
|
|
245
|
-
|
|
246
|
-
|
|
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
|
|
329
|
-
|
|
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:
|
|
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(
|
|
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
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|