@tishlang/tish 1.10.0 → 1.13.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/bin/tish +0 -0
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/cli_help.rs +9 -1
- package/crates/tish/src/main.rs +55 -3
- package/crates/tish_build_utils/src/lib.rs +33 -10
- package/crates/tish_builtins/src/math.rs +7 -0
- package/crates/tish_builtins/src/string.rs +26 -0
- package/crates/tish_compile/src/codegen.rs +207 -26
- package/crates/tish_compile/src/lib.rs +19 -9
- package/crates/tish_compile/src/resolve.rs +185 -3
- package/crates/tish_core/src/value.rs +21 -0
- package/crates/tish_cranelift/src/link.rs +1 -1
- package/crates/tish_fmt/src/lib.rs +1201 -132
- package/crates/tish_native/src/build.rs +88 -19
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +48 -5
- package/crates/tish_runtime/src/lib.rs +9 -1
- package/crates/tish_runtime/src/timers.rs +12 -7
- package/crates/tish_ui/src/lib.rs +5 -4
- package/crates/tish_ui/src/runtime/hooks.rs +119 -23
- package/crates/tish_ui/src/runtime/mod.rs +7 -26
- package/crates/tish_vm/src/vm.rs +40 -0
- package/crates/tish_wasm/src/lib.rs +27 -0
- package/crates/tish_wasm_runtime/Cargo.toml +6 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
- package/crates/tish_wasm_runtime/src/lib.rs +5 -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
|
@@ -540,6 +540,33 @@ pub fn compile_project_full(
|
|
|
540
540
|
crate::resolve::NativeBuildArtifacts,
|
|
541
541
|
),
|
|
542
542
|
CompileError,
|
|
543
|
+
> {
|
|
544
|
+
compile_project_full_emit(
|
|
545
|
+
entry_path,
|
|
546
|
+
project_root,
|
|
547
|
+
features,
|
|
548
|
+
optimize,
|
|
549
|
+
crate::NativeEmitMode::DesktopBin,
|
|
550
|
+
None,
|
|
551
|
+
)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/// Like [`compile_project_full`], with emit mode and optional feature cap (e.g. iOS sandbox).
|
|
555
|
+
pub fn compile_project_full_emit(
|
|
556
|
+
entry_path: &Path,
|
|
557
|
+
project_root: Option<&Path>,
|
|
558
|
+
features: &[String],
|
|
559
|
+
optimize: bool,
|
|
560
|
+
emit_mode: crate::NativeEmitMode,
|
|
561
|
+
feature_cap: Option<&std::collections::HashSet<String>>,
|
|
562
|
+
) -> Result<
|
|
563
|
+
(
|
|
564
|
+
String,
|
|
565
|
+
Vec<crate::resolve::ResolvedNativeModule>,
|
|
566
|
+
Vec<String>,
|
|
567
|
+
crate::resolve::NativeBuildArtifacts,
|
|
568
|
+
),
|
|
569
|
+
CompileError,
|
|
543
570
|
> {
|
|
544
571
|
use crate::resolve;
|
|
545
572
|
let root = project_root.unwrap_or_else(|| entry_path.parent().unwrap_or(Path::new(".")));
|
|
@@ -555,11 +582,17 @@ pub fn compile_project_full(
|
|
|
555
582
|
message: e,
|
|
556
583
|
span: None,
|
|
557
584
|
})?;
|
|
558
|
-
let native_modules =
|
|
585
|
+
let mut native_modules =
|
|
559
586
|
resolve::resolve_native_modules(&merged.program, root).map_err(|e| CompileError {
|
|
560
587
|
message: e,
|
|
561
588
|
span: None,
|
|
562
589
|
})?;
|
|
590
|
+
if resolve::program_uses_document(&merged.program) {
|
|
591
|
+
resolve::ensure_tish_canvas_module(&mut native_modules, root).map_err(|e| CompileError {
|
|
592
|
+
message: e,
|
|
593
|
+
span: None,
|
|
594
|
+
})?;
|
|
595
|
+
}
|
|
563
596
|
let native_build =
|
|
564
597
|
resolve::compute_native_build_artifacts(&merged.program, root, &native_modules).map_err(
|
|
565
598
|
|e| CompileError {
|
|
@@ -573,13 +606,17 @@ pub fn compile_project_full(
|
|
|
573
606
|
all_features.push(f);
|
|
574
607
|
}
|
|
575
608
|
}
|
|
576
|
-
let
|
|
609
|
+
if let Some(cap) = feature_cap {
|
|
610
|
+
all_features.retain(|f| cap.contains(f));
|
|
611
|
+
}
|
|
612
|
+
let rust = compile_with_native_modules_emit(
|
|
577
613
|
&merged.program,
|
|
578
614
|
project_root,
|
|
579
615
|
&all_features,
|
|
580
616
|
&native_modules,
|
|
581
617
|
&native_build.native_init,
|
|
582
618
|
optimize,
|
|
619
|
+
emit_mode,
|
|
583
620
|
)?;
|
|
584
621
|
Ok((rust, native_modules, all_features, native_build))
|
|
585
622
|
}
|
|
@@ -603,6 +640,26 @@ pub fn compile_with_native_modules(
|
|
|
603
640
|
native_modules: &[crate::resolve::ResolvedNativeModule],
|
|
604
641
|
native_init: &std::collections::HashMap<String, crate::resolve::NativeModuleInit>,
|
|
605
642
|
optimize: bool,
|
|
643
|
+
) -> Result<String, CompileError> {
|
|
644
|
+
compile_with_native_modules_emit(
|
|
645
|
+
program,
|
|
646
|
+
project_root,
|
|
647
|
+
features,
|
|
648
|
+
native_modules,
|
|
649
|
+
native_init,
|
|
650
|
+
optimize,
|
|
651
|
+
crate::NativeEmitMode::DesktopBin,
|
|
652
|
+
)
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
pub fn compile_with_native_modules_emit(
|
|
656
|
+
program: &Program,
|
|
657
|
+
project_root: Option<&Path>,
|
|
658
|
+
features: &[String],
|
|
659
|
+
native_modules: &[crate::resolve::ResolvedNativeModule],
|
|
660
|
+
native_init: &std::collections::HashMap<String, crate::resolve::NativeModuleInit>,
|
|
661
|
+
optimize: bool,
|
|
662
|
+
emit_mode: crate::NativeEmitMode,
|
|
606
663
|
) -> Result<String, CompileError> {
|
|
607
664
|
let program = if optimize {
|
|
608
665
|
tishlang_opt::optimize(program)
|
|
@@ -630,6 +687,13 @@ pub fn compile_with_native_modules(
|
|
|
630
687
|
native_init.clone()
|
|
631
688
|
};
|
|
632
689
|
let mut g = Codegen::new_with_native_modules(project_root, features, map);
|
|
690
|
+
g.emit_mode = emit_mode;
|
|
691
|
+
g.has_native_ui_host = native_modules.iter().any(|m| {
|
|
692
|
+
m.package_name == "tish-macos"
|
|
693
|
+
|| m.package_name == "tish-ios"
|
|
694
|
+
|| m.crate_name == "tishlang_macos"
|
|
695
|
+
|| m.crate_name == "tishlang_ios"
|
|
696
|
+
});
|
|
633
697
|
g.emit_program(&program)?;
|
|
634
698
|
Ok(g.output)
|
|
635
699
|
}
|
|
@@ -680,6 +744,11 @@ struct Codegen {
|
|
|
680
744
|
/// `try`/`throw` lowering uses `return Err` only at depth 0 (e.g. `run()`); inside native
|
|
681
745
|
/// closures it must not return a `Result` from a `Value`-returning closure.
|
|
682
746
|
value_fn_depth: u32,
|
|
747
|
+
emit_mode: crate::NativeEmitMode,
|
|
748
|
+
/// Program links `tish:macos` / `tish:ios` — skip HeadlessHost install.
|
|
749
|
+
has_native_ui_host: bool,
|
|
750
|
+
/// Program references browser global `document` — inject tish-canvas.
|
|
751
|
+
program_uses_document: bool,
|
|
683
752
|
}
|
|
684
753
|
|
|
685
754
|
impl Codegen {
|
|
@@ -710,6 +779,19 @@ impl Codegen {
|
|
|
710
779
|
program_has_jsx: false,
|
|
711
780
|
program_fun_decl_names: std::collections::HashSet::new(),
|
|
712
781
|
value_fn_depth: 0,
|
|
782
|
+
emit_mode: crate::NativeEmitMode::DesktopBin,
|
|
783
|
+
has_native_ui_host: false,
|
|
784
|
+
program_uses_document: false,
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/// In async `run()` bodies, propagate runtime op errors with `?`; in sync
|
|
789
|
+
/// `Value::native` closures use `.unwrap_or(Value::Null)`.
|
|
790
|
+
fn ops_result_suffix(&self) -> &'static str {
|
|
791
|
+
if self.is_async && self.async_context_stack.last().copied().unwrap_or(false) {
|
|
792
|
+
"?"
|
|
793
|
+
} else {
|
|
794
|
+
".unwrap_or(Value::Null)"
|
|
713
795
|
}
|
|
714
796
|
}
|
|
715
797
|
|
|
@@ -1221,11 +1303,12 @@ impl Codegen {
|
|
|
1221
1303
|
self.is_async = program_uses_async(program);
|
|
1222
1304
|
self.program_has_jsx = tishlang_ui::jsx::program_contains_jsx(program);
|
|
1223
1305
|
self.program_fun_decl_names = tishlang_ui::jsx::collect_fun_decl_names(program);
|
|
1306
|
+
self.program_uses_document = crate::resolve::program_uses_document(program);
|
|
1224
1307
|
self.write("#![allow(unused, non_snake_case)]\n\n");
|
|
1225
1308
|
self.write("use std::cell::RefCell;\n");
|
|
1226
1309
|
self.write("use std::rc::Rc;\n");
|
|
1227
1310
|
self.write("use std::sync::Arc;\n");
|
|
1228
|
-
self.write("use tishlang_runtime::{console_debug as tish_console_debug, console_info as tish_console_info, console_log as tish_console_log, console_warn as tish_console_warn, console_error as tish_console_error, boolean as tish_boolean, decode_uri as tish_decode_uri, encode_uri as tish_encode_uri, string_escape_html_impl as tish_escape_html, in_operator as tish_in_operator, is_finite as tish_is_finite, is_nan as tish_is_nan, json_parse as tish_json_parse, json_stringify as tish_json_stringify, math_abs as tish_math_abs, math_ceil as tish_math_ceil, math_floor as tish_math_floor, math_max as tish_math_max, math_min as tish_math_min, math_round as tish_math_round, math_sqrt as tish_math_sqrt, parse_float as tish_parse_float, parse_int as tish_parse_int, math_random as tish_math_random, math_pow as tish_math_pow, math_sin as tish_math_sin, math_cos as tish_math_cos, math_tan as tish_math_tan, math_log as tish_math_log, math_exp as tish_math_exp, math_sign as tish_math_sign, math_trunc as tish_math_trunc, date_now as tish_date_now, array_is_array as tish_array_is_array, string_from_char_code as tish_string_from_char_code, object_assign as tish_object_assign, object_keys as tish_object_keys, object_values as tish_object_values, object_entries as tish_object_entries, object_from_entries as tish_object_from_entries, symbol_object as tish_symbol_object, tish_construct, tish_uint8_array_constructor, tish_audio_context_constructor, register_static_route as tish_register_static_route, ObjectMap, TishError, Value, VmRef};\n");
|
|
1311
|
+
self.write("use tishlang_runtime::{console_debug as tish_console_debug, console_info as tish_console_info, console_log as tish_console_log, console_warn as tish_console_warn, console_error as tish_console_error, boolean as tish_boolean, decode_uri as tish_decode_uri, encode_uri as tish_encode_uri, string_escape_html_impl as tish_escape_html, in_operator as tish_in_operator, is_finite as tish_is_finite, is_nan as tish_is_nan, json_parse as tish_json_parse, json_stringify as tish_json_stringify, math_abs as tish_math_abs, math_ceil as tish_math_ceil, math_floor as tish_math_floor, math_max as tish_math_max, math_min as tish_math_min, math_round as tish_math_round, math_sqrt as tish_math_sqrt, parse_float as tish_parse_float, parse_int as tish_parse_int, math_random as tish_math_random, math_pow as tish_math_pow, math_sin as tish_math_sin, math_cos as tish_math_cos, math_tan as tish_math_tan, math_log as tish_math_log, math_exp as tish_math_exp, math_sign as tish_math_sign, math_trunc as tish_math_trunc, math_imul as tish_math_imul, date_now as tish_date_now, array_is_array as tish_array_is_array, string_from_char_code as tish_string_from_char_code, object_assign as tish_object_assign, object_keys as tish_object_keys, object_values as tish_object_values, object_entries as tish_object_entries, object_from_entries as tish_object_from_entries, symbol_object as tish_symbol_object, tish_construct, tish_uint8_array_constructor, tish_audio_context_constructor, register_static_route as tish_register_static_route, ObjectMap, TishError, Value, VmRef};\n");
|
|
1229
1312
|
if self.program_has_jsx {
|
|
1230
1313
|
self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
|
|
1231
1314
|
}
|
|
@@ -1251,6 +1334,9 @@ impl Codegen {
|
|
|
1251
1334
|
if self.has_feature("regex") {
|
|
1252
1335
|
self.write("use tishlang_runtime::regexp_new;\n");
|
|
1253
1336
|
}
|
|
1337
|
+
if self.program_uses_document {
|
|
1338
|
+
self.write("use tish_canvas::document_value as tish_canvas_document;\n");
|
|
1339
|
+
}
|
|
1254
1340
|
self.write("\n");
|
|
1255
1341
|
|
|
1256
1342
|
// Collect every `type Foo = { ... }` declaration in the program
|
|
@@ -1266,28 +1352,32 @@ impl Codegen {
|
|
|
1266
1352
|
// and `x.field` becomes a direct field access.
|
|
1267
1353
|
self.emit_named_struct_decls();
|
|
1268
1354
|
|
|
1269
|
-
if self.is_async {
|
|
1355
|
+
if self.is_async && self.emit_mode == crate::NativeEmitMode::DesktopBin {
|
|
1270
1356
|
self.writeln("#[tokio::main]");
|
|
1271
1357
|
self.writeln("async fn main() {");
|
|
1272
|
-
} else {
|
|
1358
|
+
} else if self.emit_mode == crate::NativeEmitMode::DesktopBin {
|
|
1273
1359
|
self.writeln("fn main() {");
|
|
1274
1360
|
}
|
|
1275
|
-
self.
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1361
|
+
if self.emit_mode == crate::NativeEmitMode::DesktopBin {
|
|
1362
|
+
self.indent += 1;
|
|
1363
|
+
if self.is_async {
|
|
1364
|
+
self.writeln("if let Err(e) = run().await {");
|
|
1365
|
+
} else {
|
|
1366
|
+
self.writeln("if let Err(e) = run() {");
|
|
1367
|
+
}
|
|
1368
|
+
self.indent += 1;
|
|
1369
|
+
self.writeln("eprintln!(\"Error: {}\", e);");
|
|
1370
|
+
self.writeln("std::process::exit(1);");
|
|
1371
|
+
self.indent -= 1;
|
|
1372
|
+
self.writeln("}");
|
|
1373
|
+
self.indent -= 1;
|
|
1374
|
+
self.writeln("}");
|
|
1375
|
+
self.writeln("");
|
|
1280
1376
|
}
|
|
1281
|
-
self.indent += 1;
|
|
1282
|
-
self.writeln("eprintln!(\"Error: {}\", e);");
|
|
1283
|
-
self.writeln("std::process::exit(1);");
|
|
1284
|
-
self.indent -= 1;
|
|
1285
|
-
self.writeln("}");
|
|
1286
|
-
self.indent -= 1;
|
|
1287
|
-
self.writeln("}");
|
|
1288
|
-
self.writeln("");
|
|
1289
1377
|
if self.is_async {
|
|
1290
1378
|
self.writeln("async fn run() -> Result<(), Box<dyn std::error::Error>> {");
|
|
1379
|
+
} else if self.emit_mode == crate::NativeEmitMode::EmbeddedLib {
|
|
1380
|
+
self.writeln("pub fn run() -> Result<(), Box<dyn std::error::Error>> {");
|
|
1291
1381
|
} else {
|
|
1292
1382
|
self.writeln("fn run() -> Result<(), Box<dyn std::error::Error>> {");
|
|
1293
1383
|
}
|
|
@@ -1350,6 +1440,9 @@ impl Codegen {
|
|
|
1350
1440
|
self.writeln(
|
|
1351
1441
|
"(Arc::from(\"trunc\"), Value::native(|args: &[Value]| tish_math_trunc(args))),",
|
|
1352
1442
|
);
|
|
1443
|
+
self.writeln(
|
|
1444
|
+
"(Arc::from(\"imul\"), Value::native(|args: &[Value]| tish_math_imul(args))),",
|
|
1445
|
+
);
|
|
1353
1446
|
self.writeln("(Arc::from(\"PI\"), Value::Number(std::f64::consts::PI)),");
|
|
1354
1447
|
self.writeln("(Arc::from(\"E\"), Value::Number(std::f64::consts::E)),");
|
|
1355
1448
|
self.indent -= 1;
|
|
@@ -1405,6 +1498,14 @@ impl Codegen {
|
|
|
1405
1498
|
|
|
1406
1499
|
self.writeln("let Uint8Array = tish_uint8_array_constructor();");
|
|
1407
1500
|
self.writeln("let AudioContext = tish_audio_context_constructor();");
|
|
1501
|
+
if self.program_uses_document {
|
|
1502
|
+
self.writeln("let document = VmRef::new(tish_canvas_document());");
|
|
1503
|
+
self.refcell_wrapped_vars.insert("document".to_string());
|
|
1504
|
+
self.rc_cell_storage_define("document");
|
|
1505
|
+
if let Some(scope) = self.outer_vars_stack.last_mut() {
|
|
1506
|
+
scope.push("document".to_string());
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1408
1509
|
|
|
1409
1510
|
if self.has_feature("process") {
|
|
1410
1511
|
self.writeln("let process = Value::object({");
|
|
@@ -1489,7 +1590,7 @@ impl Codegen {
|
|
|
1489
1590
|
self.writeln("let RegExp = Value::native(|args: &[Value]| regexp_new(args));");
|
|
1490
1591
|
}
|
|
1491
1592
|
|
|
1492
|
-
if self.program_has_jsx {
|
|
1593
|
+
if self.program_has_jsx && !self.has_native_ui_host {
|
|
1493
1594
|
self.writeln("install_thread_local_host(Box::new(HeadlessHost::default()));");
|
|
1494
1595
|
self.writeln("let Fragment = fragment_value();");
|
|
1495
1596
|
self.writeln("let h = Value::native(|args: &[Value]| ui_h(args));");
|
|
@@ -1537,6 +1638,20 @@ impl Codegen {
|
|
|
1537
1638
|
self.writeln("Ok(())");
|
|
1538
1639
|
self.indent -= 1;
|
|
1539
1640
|
self.writeln("}");
|
|
1641
|
+
if self.emit_mode == crate::NativeEmitMode::EmbeddedLib {
|
|
1642
|
+
self.writeln("");
|
|
1643
|
+
self.writeln("#[no_mangle]");
|
|
1644
|
+
self.writeln("pub extern \"C\" fn tish_ios_launch() {");
|
|
1645
|
+
self.indent += 1;
|
|
1646
|
+
if self.is_async {
|
|
1647
|
+
self.writeln("let rt = tokio::runtime::Runtime::new().expect(\"tokio runtime\");");
|
|
1648
|
+
self.writeln("let _ = rt.block_on(run());");
|
|
1649
|
+
} else {
|
|
1650
|
+
self.writeln("let _ = run();");
|
|
1651
|
+
}
|
|
1652
|
+
self.indent -= 1;
|
|
1653
|
+
self.writeln("}");
|
|
1654
|
+
}
|
|
1540
1655
|
Ok(())
|
|
1541
1656
|
}
|
|
1542
1657
|
|
|
@@ -2227,6 +2342,10 @@ impl Codegen {
|
|
|
2227
2342
|
for v in &mutable_outer_vars {
|
|
2228
2343
|
self.refcell_wrapped_vars.insert(v.clone());
|
|
2229
2344
|
}
|
|
2345
|
+
// Read-only captures are plain Value bindings inside the closure.
|
|
2346
|
+
for v in &read_only_outer_vars {
|
|
2347
|
+
self.refcell_wrapped_vars.remove(v);
|
|
2348
|
+
}
|
|
2230
2349
|
|
|
2231
2350
|
// Pre-scan body for nested functions (handles function body as Block)
|
|
2232
2351
|
if let Statement::Block { statements, .. } = body.as_ref() {
|
|
@@ -2487,7 +2606,12 @@ impl Codegen {
|
|
|
2487
2606
|
// Convert native type to Value for compatibility with existing code
|
|
2488
2607
|
var_type.to_value_expr(&escaped)
|
|
2489
2608
|
} else {
|
|
2490
|
-
escaped.into_owned()
|
|
2609
|
+
let s = escaped.into_owned();
|
|
2610
|
+
if self.value_fn_depth > 0 || !self.loop_stack.is_empty() {
|
|
2611
|
+
format!("({}).clone()", s)
|
|
2612
|
+
} else {
|
|
2613
|
+
s
|
|
2614
|
+
}
|
|
2491
2615
|
}
|
|
2492
2616
|
}
|
|
2493
2617
|
}
|
|
@@ -2745,6 +2869,14 @@ impl Codegen {
|
|
|
2745
2869
|
obj_expr, start, end
|
|
2746
2870
|
));
|
|
2747
2871
|
}
|
|
2872
|
+
"substr" => {
|
|
2873
|
+
let start = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Number(0.0)".to_string());
|
|
2874
|
+
let length = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
2875
|
+
return Ok(format!(
|
|
2876
|
+
"tishlang_runtime::string_substr(&{}, &{}, &{})",
|
|
2877
|
+
obj_expr, start, length
|
|
2878
|
+
));
|
|
2879
|
+
}
|
|
2748
2880
|
"split" => {
|
|
2749
2881
|
let sep = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
2750
2882
|
return Ok(format!(
|
|
@@ -3386,10 +3518,11 @@ impl Codegen {
|
|
|
3386
3518
|
CompoundOp::Div => "div",
|
|
3387
3519
|
CompoundOp::Mod => "modulo",
|
|
3388
3520
|
};
|
|
3521
|
+
let op_suffix = self.ops_result_suffix();
|
|
3389
3522
|
if is_refcell {
|
|
3390
3523
|
format!(
|
|
3391
|
-
"{{ let _lhs_v = (*{}.borrow()).clone(); let _rhs = ({}).clone(); let _new = tishlang_runtime::ops::{}(&_lhs_v, &_rhs)
|
|
3392
|
-
n, val, op_fn, n, n
|
|
3524
|
+
"{{ let _lhs_v = (*{}.borrow()).clone(); let _rhs = ({}).clone(); let _new = tishlang_runtime::ops::{}(&_lhs_v, &_rhs){}; *{}.borrow_mut() = _new; (*{}.borrow()).clone() }}",
|
|
3525
|
+
n, val, op_fn, op_suffix, n, n
|
|
3393
3526
|
)
|
|
3394
3527
|
} else if var_type.is_native() {
|
|
3395
3528
|
// Wrap native lhs as Value, run ops::, unbox result back to native
|
|
@@ -3397,13 +3530,13 @@ impl Codegen {
|
|
|
3397
3530
|
let result_native = var_type.from_value_expr("_result");
|
|
3398
3531
|
let n_as_value2 = var_type.to_value_expr(&n);
|
|
3399
3532
|
format!(
|
|
3400
|
-
"{{ let _lhs = {}; let _rhs = ({}).clone(); let _result = tishlang_runtime::ops::{}(&_lhs, &_rhs)
|
|
3401
|
-
n_as_value, val, op_fn, n, result_native, n_as_value2
|
|
3533
|
+
"{{ let _lhs = {}; let _rhs = ({}).clone(); let _result = tishlang_runtime::ops::{}(&_lhs, &_rhs){}; {} = {}; {} }}",
|
|
3534
|
+
n_as_value, val, op_fn, op_suffix, n, result_native, n_as_value2
|
|
3402
3535
|
)
|
|
3403
3536
|
} else {
|
|
3404
3537
|
format!(
|
|
3405
|
-
"{{ let _rhs = ({}).clone(); {} = tishlang_runtime::ops::{}(&{}, &_rhs)
|
|
3406
|
-
val, n, op_fn, n, n
|
|
3538
|
+
"{{ let _rhs = ({}).clone(); {} = tishlang_runtime::ops::{}(&{}, &_rhs){}; {}.clone() }}",
|
|
3539
|
+
val, n, op_fn, n, op_suffix, n
|
|
3407
3540
|
)
|
|
3408
3541
|
}
|
|
3409
3542
|
}
|
|
@@ -4695,6 +4828,41 @@ impl Codegen {
|
|
|
4695
4828
|
));
|
|
4696
4829
|
}
|
|
4697
4830
|
|
|
4831
|
+
// Locals from an enclosing Value::native (e.g. captured helper fns) are not on
|
|
4832
|
+
// outer_vars_stack but must not move into multiple sibling closures.
|
|
4833
|
+
const BUILTINS: &[&str] = &[
|
|
4834
|
+
"Boolean", "console", "Math", "JSON", "Date", "Object", "process",
|
|
4835
|
+
"setTimeout", "clearTimeout", "setInterval", "clearInterval", "Promise",
|
|
4836
|
+
"Symbol", "RegExp", "Polars", "Infinity", "NaN", "serve",
|
|
4837
|
+
];
|
|
4838
|
+
let mut already_captured: HashSet<String> = outer_vars
|
|
4839
|
+
.iter()
|
|
4840
|
+
.chain(outer_params.iter())
|
|
4841
|
+
.chain(referenced_funcs.iter())
|
|
4842
|
+
.cloned()
|
|
4843
|
+
.collect();
|
|
4844
|
+
already_captured.extend(BUILTINS.iter().map(|s| s.to_string()));
|
|
4845
|
+
let implicit_env_captures: Vec<String> = if self.value_fn_depth > 0 {
|
|
4846
|
+
referenced
|
|
4847
|
+
.iter()
|
|
4848
|
+
.filter(|name| {
|
|
4849
|
+
!param_names.contains(name.as_str())
|
|
4850
|
+
&& !local_var_names.contains(name.as_str())
|
|
4851
|
+
&& !already_captured.contains(name.as_str())
|
|
4852
|
+
})
|
|
4853
|
+
.cloned()
|
|
4854
|
+
.collect()
|
|
4855
|
+
} else {
|
|
4856
|
+
Vec::new()
|
|
4857
|
+
};
|
|
4858
|
+
for name in &implicit_env_captures {
|
|
4859
|
+
let escaped = Self::escape_ident(name);
|
|
4860
|
+
code.push_str(&format!(
|
|
4861
|
+
" let {}_ref = VmRef::new({}.clone());\n",
|
|
4862
|
+
escaped, escaped
|
|
4863
|
+
));
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4698
4866
|
code.push_str(" Value::native(move |args: &[Value]| {\n");
|
|
4699
4867
|
self.value_fn_depth += 1;
|
|
4700
4868
|
|
|
@@ -4722,6 +4890,13 @@ impl Codegen {
|
|
|
4722
4890
|
var_escaped, var_escaped
|
|
4723
4891
|
));
|
|
4724
4892
|
}
|
|
4893
|
+
for name in &implicit_env_captures {
|
|
4894
|
+
let escaped = Self::escape_ident(name);
|
|
4895
|
+
code.push_str(&format!(
|
|
4896
|
+
" let {} = (*{}_ref.borrow()).clone();\n",
|
|
4897
|
+
escaped, escaped
|
|
4898
|
+
));
|
|
4899
|
+
}
|
|
4725
4900
|
|
|
4726
4901
|
// Make captured functions available
|
|
4727
4902
|
for func_name in &referenced_funcs {
|
|
@@ -4774,6 +4949,12 @@ impl Codegen {
|
|
|
4774
4949
|
for v in &cell_capture_outer_vars {
|
|
4775
4950
|
self.refcell_wrapped_vars.insert(v.clone());
|
|
4776
4951
|
}
|
|
4952
|
+
for v in &read_only_outer_vars {
|
|
4953
|
+
self.refcell_wrapped_vars.remove(v);
|
|
4954
|
+
}
|
|
4955
|
+
for v in &implicit_env_captures {
|
|
4956
|
+
self.refcell_wrapped_vars.remove(v);
|
|
4957
|
+
}
|
|
4777
4958
|
|
|
4778
4959
|
self.type_context.push_fun_param_scope(params, None);
|
|
4779
4960
|
|
|
@@ -7,19 +7,29 @@ mod infer;
|
|
|
7
7
|
mod resolve;
|
|
8
8
|
mod types;
|
|
9
9
|
|
|
10
|
+
/// How generated Rust is linked (desktop binary vs embedded iOS staticlib).
|
|
11
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
12
|
+
pub enum NativeEmitMode {
|
|
13
|
+
#[default]
|
|
14
|
+
DesktopBin,
|
|
15
|
+
/// `[lib] crate-type = ["staticlib"]` — no `fn main()`, host calls `tish_ios_launch`.
|
|
16
|
+
EmbeddedLib,
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
pub use codegen::CompileError;
|
|
11
20
|
pub use codegen::{
|
|
12
|
-
compile, compile_project, compile_project_full,
|
|
13
|
-
compile_with_native_modules,
|
|
21
|
+
compile, compile_project, compile_project_full, compile_project_full_emit,
|
|
22
|
+
compile_with_features, compile_with_native_modules, compile_with_native_modules_emit,
|
|
23
|
+
compile_with_project_root,
|
|
14
24
|
};
|
|
15
25
|
pub use resolve::{
|
|
16
|
-
cargo_export_fn_name, compute_native_build_artifacts, detect_cycles,
|
|
17
|
-
extract_native_import_features, format_rust_dependencies_toml,
|
|
18
|
-
has_external_native_imports, has_native_imports,
|
|
19
|
-
is_builtin_native_spec, is_cargo_native_spec, is_native_import,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
NativeModuleInit, ResolvedNativeModule,
|
|
26
|
+
cargo_export_fn_name, compute_native_build_artifacts, detect_cycles, ensure_tish_canvas_module,
|
|
27
|
+
export_name_to_rust_ident, extract_native_import_features, format_rust_dependencies_toml,
|
|
28
|
+
generate_native_wrapper_rs, has_external_native_imports, has_native_imports,
|
|
29
|
+
infer_native_module_exports, is_builtin_native_spec, is_cargo_native_spec, is_native_import,
|
|
30
|
+
merge_modules, normalize_builtin_spec, program_uses_document, read_project_tish_config,
|
|
31
|
+
resolve_bare_spec, resolve_native_modules, resolve_project, resolve_project_from_stdin,
|
|
32
|
+
MergedProgram, NativeBuildArtifacts, NativeModuleInit, ResolvedNativeModule,
|
|
23
33
|
};
|
|
24
34
|
pub use types::{RustType, TypeContext};
|
|
25
35
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
use std::collections::{HashMap, HashSet};
|
|
5
5
|
use std::path::{Path, PathBuf};
|
|
6
6
|
use std::sync::Arc;
|
|
7
|
-
use tishlang_ast::{ExportDeclaration, Expr, ImportSpecifier, Program, Statement};
|
|
7
|
+
use tishlang_ast::{ExportDeclaration, Expr, ImportSpecifier, MemberProp, Program, Statement, CallArg};
|
|
8
8
|
|
|
9
9
|
/// Resolved native module: crate path and init expression.
|
|
10
10
|
#[derive(Debug, Clone)]
|
|
@@ -114,6 +114,188 @@ pub fn resolve_native_modules(
|
|
|
114
114
|
Ok(modules)
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/// True when merged Tish source references the browser global `document` (e.g. juke-cards).
|
|
118
|
+
pub fn program_uses_document(program: &Program) -> bool {
|
|
119
|
+
use tishlang_ast::{ArrayElement, ArrowBody, JsxAttrValue, JsxChild, JsxProp, ObjectProp};
|
|
120
|
+
|
|
121
|
+
fn expr_uses_document(e: &Expr) -> bool {
|
|
122
|
+
match e {
|
|
123
|
+
Expr::Ident { name, .. } => name.as_ref() == "document",
|
|
124
|
+
Expr::Literal { .. } | Expr::NativeModuleLoad { .. } => false,
|
|
125
|
+
Expr::Binary { left, right, .. } => {
|
|
126
|
+
expr_uses_document(left) || expr_uses_document(right)
|
|
127
|
+
}
|
|
128
|
+
Expr::Unary { operand, .. } | Expr::TypeOf { operand, .. } => {
|
|
129
|
+
expr_uses_document(operand)
|
|
130
|
+
}
|
|
131
|
+
Expr::Call { callee, args, .. } => {
|
|
132
|
+
expr_uses_document(callee)
|
|
133
|
+
|| args.iter().any(|a| match a {
|
|
134
|
+
CallArg::Expr(e) | CallArg::Spread(e) => expr_uses_document(e),
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
Expr::New { callee, args, .. } => {
|
|
138
|
+
expr_uses_document(callee)
|
|
139
|
+
|| args.iter().any(|a| match a {
|
|
140
|
+
CallArg::Expr(e) | CallArg::Spread(e) => expr_uses_document(e),
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
Expr::Member { object, prop, .. } => {
|
|
144
|
+
expr_uses_document(object)
|
|
145
|
+
|| if let MemberProp::Expr(e) = prop {
|
|
146
|
+
expr_uses_document(e)
|
|
147
|
+
} else {
|
|
148
|
+
false
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
Expr::Index { object, index, .. } => {
|
|
152
|
+
expr_uses_document(object) || expr_uses_document(index)
|
|
153
|
+
}
|
|
154
|
+
Expr::Conditional {
|
|
155
|
+
cond,
|
|
156
|
+
then_branch,
|
|
157
|
+
else_branch,
|
|
158
|
+
..
|
|
159
|
+
} => {
|
|
160
|
+
expr_uses_document(cond)
|
|
161
|
+
|| expr_uses_document(then_branch)
|
|
162
|
+
|| expr_uses_document(else_branch)
|
|
163
|
+
}
|
|
164
|
+
Expr::NullishCoalesce { left, right, .. } => {
|
|
165
|
+
expr_uses_document(left) || expr_uses_document(right)
|
|
166
|
+
}
|
|
167
|
+
Expr::Array { elements, .. } => elements.iter().any(|el| match el {
|
|
168
|
+
ArrayElement::Expr(e) | ArrayElement::Spread(e) => expr_uses_document(e),
|
|
169
|
+
}),
|
|
170
|
+
Expr::Object { props, .. } => props.iter().any(|p| match p {
|
|
171
|
+
ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => expr_uses_document(e),
|
|
172
|
+
}),
|
|
173
|
+
Expr::Assign { value, .. }
|
|
174
|
+
| Expr::CompoundAssign { value, .. }
|
|
175
|
+
| Expr::LogicalAssign { value, .. }
|
|
176
|
+
| Expr::MemberAssign { value, .. }
|
|
177
|
+
| Expr::IndexAssign { value, .. } => expr_uses_document(value),
|
|
178
|
+
Expr::PostfixInc { .. }
|
|
179
|
+
| Expr::PostfixDec { .. }
|
|
180
|
+
| Expr::PrefixInc { .. }
|
|
181
|
+
| Expr::PrefixDec { .. } => false,
|
|
182
|
+
Expr::ArrowFunction { body, .. } => match body {
|
|
183
|
+
ArrowBody::Expr(e) => expr_uses_document(e),
|
|
184
|
+
ArrowBody::Block(s) => stmt_uses_document(s),
|
|
185
|
+
},
|
|
186
|
+
Expr::TemplateLiteral { exprs, .. } => exprs.iter().any(expr_uses_document),
|
|
187
|
+
Expr::Await { operand, .. } => expr_uses_document(operand),
|
|
188
|
+
Expr::JsxElement { props, children, .. } => {
|
|
189
|
+
props.iter().any(|p| match p {
|
|
190
|
+
JsxProp::Attr { value, .. } => match value {
|
|
191
|
+
JsxAttrValue::Expr(e) => expr_uses_document(e),
|
|
192
|
+
JsxAttrValue::String(_) | JsxAttrValue::ImplicitTrue => false,
|
|
193
|
+
},
|
|
194
|
+
JsxProp::Spread(e) => expr_uses_document(e),
|
|
195
|
+
}) || children.iter().any(|c| match c {
|
|
196
|
+
JsxChild::Expr(e) => expr_uses_document(e),
|
|
197
|
+
JsxChild::Text(_) => false,
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
Expr::JsxFragment { children, .. } => children.iter().any(|c| match c {
|
|
201
|
+
JsxChild::Expr(e) => expr_uses_document(e),
|
|
202
|
+
JsxChild::Text(_) => false,
|
|
203
|
+
}),
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
fn stmt_uses_document(s: &Statement) -> bool {
|
|
208
|
+
match s {
|
|
209
|
+
Statement::VarDecl { init, .. } => init.as_ref().is_some_and(|e| expr_uses_document(e)),
|
|
210
|
+
Statement::VarDeclDestructure { init, .. } => expr_uses_document(init),
|
|
211
|
+
Statement::ExprStmt { expr, .. } => expr_uses_document(expr),
|
|
212
|
+
Statement::Return { value, .. } => value.as_ref().is_some_and(|e| expr_uses_document(e)),
|
|
213
|
+
Statement::Throw { value, .. } => expr_uses_document(value),
|
|
214
|
+
Statement::If {
|
|
215
|
+
cond,
|
|
216
|
+
then_branch,
|
|
217
|
+
else_branch,
|
|
218
|
+
..
|
|
219
|
+
} => {
|
|
220
|
+
expr_uses_document(cond)
|
|
221
|
+
|| stmt_uses_document(then_branch)
|
|
222
|
+
|| else_branch
|
|
223
|
+
.as_ref()
|
|
224
|
+
.is_some_and(|b| stmt_uses_document(b.as_ref()))
|
|
225
|
+
}
|
|
226
|
+
Statement::While { cond, body, .. }
|
|
227
|
+
| Statement::DoWhile { cond, body, .. } => {
|
|
228
|
+
expr_uses_document(cond) || stmt_uses_document(body)
|
|
229
|
+
}
|
|
230
|
+
Statement::For { init, cond, update, body, .. } => {
|
|
231
|
+
init.as_ref().is_some_and(|s| stmt_uses_document(s.as_ref()))
|
|
232
|
+
|| cond.as_ref().is_some_and(|e| expr_uses_document(e))
|
|
233
|
+
|| update.as_ref().is_some_and(|e| expr_uses_document(e))
|
|
234
|
+
|| stmt_uses_document(body)
|
|
235
|
+
}
|
|
236
|
+
Statement::ForOf { iterable, body, .. } => {
|
|
237
|
+
expr_uses_document(iterable) || stmt_uses_document(body)
|
|
238
|
+
}
|
|
239
|
+
Statement::Switch {
|
|
240
|
+
expr,
|
|
241
|
+
cases,
|
|
242
|
+
default_body,
|
|
243
|
+
..
|
|
244
|
+
} => {
|
|
245
|
+
expr_uses_document(expr)
|
|
246
|
+
|| cases.iter().any(|(e, stmts)| {
|
|
247
|
+
e.as_ref().is_some_and(|e| expr_uses_document(e))
|
|
248
|
+
|| stmts.iter().any(stmt_uses_document)
|
|
249
|
+
})
|
|
250
|
+
|| default_body
|
|
251
|
+
.as_ref()
|
|
252
|
+
.is_some_and(|stmts| stmts.iter().any(stmt_uses_document))
|
|
253
|
+
}
|
|
254
|
+
Statement::Block { statements, .. } => statements.iter().any(stmt_uses_document),
|
|
255
|
+
Statement::FunDecl { body, .. } => stmt_uses_document(body),
|
|
256
|
+
Statement::Try {
|
|
257
|
+
body,
|
|
258
|
+
catch_body,
|
|
259
|
+
finally_body,
|
|
260
|
+
..
|
|
261
|
+
} => {
|
|
262
|
+
stmt_uses_document(body)
|
|
263
|
+
|| catch_body
|
|
264
|
+
.as_ref()
|
|
265
|
+
.is_some_and(|b| stmt_uses_document(b.as_ref()))
|
|
266
|
+
|| finally_body
|
|
267
|
+
.as_ref()
|
|
268
|
+
.is_some_and(|b| stmt_uses_document(b.as_ref()))
|
|
269
|
+
}
|
|
270
|
+
Statement::Import { .. }
|
|
271
|
+
| Statement::Export { .. }
|
|
272
|
+
| Statement::Break { .. }
|
|
273
|
+
| Statement::Continue { .. }
|
|
274
|
+
| Statement::TypeAlias { .. }
|
|
275
|
+
| Statement::DeclareVar { .. }
|
|
276
|
+
| Statement::DeclareFun { .. } => false,
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
program.statements.iter().any(stmt_uses_document)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/// When Tish uses bare `document`, link `tish-canvas` even without `import from 'tish:canvas'`.
|
|
284
|
+
pub fn ensure_tish_canvas_module(
|
|
285
|
+
native_modules: &mut Vec<ResolvedNativeModule>,
|
|
286
|
+
project_root: &Path,
|
|
287
|
+
) -> Result<(), String> {
|
|
288
|
+
if native_modules
|
|
289
|
+
.iter()
|
|
290
|
+
.any(|m| m.crate_name == "tish_canvas" || m.package_name == "tish-canvas")
|
|
291
|
+
{
|
|
292
|
+
return Ok(());
|
|
293
|
+
}
|
|
294
|
+
let m = resolve_native_module("tish:canvas", project_root)?;
|
|
295
|
+
native_modules.push(m);
|
|
296
|
+
Ok(())
|
|
297
|
+
}
|
|
298
|
+
|
|
117
299
|
/// True for `cargo:…` specs (Cargo-backed imports; Rust native backend only).
|
|
118
300
|
pub fn is_cargo_native_spec(spec: &str) -> bool {
|
|
119
301
|
spec.starts_with("cargo:")
|
|
@@ -725,11 +907,11 @@ fn load_module_recursive(
|
|
|
725
907
|
/// - fs, http, timers, process, ws (Node-compatible aliases for tish:*)
|
|
726
908
|
/// - tish:egui, tish:polars, etc.
|
|
727
909
|
/// - cargo:… (Cargo `rustDependencies` + generated wrapper; Rust native backend)
|
|
728
|
-
///
|
|
910
|
+
///
|
|
911
|
+
/// Scoped npm packages (`@scope/pkg`) are merged as Tish source unless imported via `tish:…`.
|
|
729
912
|
pub fn is_native_import(spec: &str) -> bool {
|
|
730
913
|
spec.starts_with("tish:")
|
|
731
914
|
|| spec.starts_with("cargo:")
|
|
732
|
-
|| spec.starts_with('@')
|
|
733
915
|
|| matches!(spec, "fs" | "http" | "timers" | "process" | "ws")
|
|
734
916
|
}
|
|
735
917
|
|