@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
|
@@ -7,7 +7,8 @@ use std::collections::{HashMap, HashSet};
|
|
|
7
7
|
use std::path::Path;
|
|
8
8
|
use tishlang_ast::{
|
|
9
9
|
ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern, Expr,
|
|
10
|
-
FunParam, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement,
|
|
10
|
+
FunParam, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement,
|
|
11
|
+
TypeAnnotation, UnaryOp,
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
/// Tracks variable usage for move/clone optimization.
|
|
@@ -119,7 +120,11 @@ impl UsageAnalyzer {
|
|
|
119
120
|
self.analyze_statement(body);
|
|
120
121
|
}
|
|
121
122
|
Statement::Break { .. } | Statement::Continue { .. } => {}
|
|
122
|
-
Statement::Import { .. }
|
|
123
|
+
Statement::Import { .. }
|
|
124
|
+
| Statement::Export { .. }
|
|
125
|
+
| Statement::TypeAlias { .. }
|
|
126
|
+
| Statement::DeclareVar { .. }
|
|
127
|
+
| Statement::DeclareFun { .. } => {
|
|
123
128
|
// Import/Export should be resolved by merge_modules before compilation
|
|
124
129
|
}
|
|
125
130
|
}
|
|
@@ -551,23 +556,27 @@ pub fn compile_project_full(
|
|
|
551
556
|
span: None,
|
|
552
557
|
})?;
|
|
553
558
|
let native_modules =
|
|
554
|
-
resolve::resolve_native_modules(&merged, root).map_err(|e| CompileError {
|
|
559
|
+
resolve::resolve_native_modules(&merged.program, root).map_err(|e| CompileError {
|
|
555
560
|
message: e,
|
|
556
561
|
span: None,
|
|
557
562
|
})?;
|
|
558
|
-
let native_build = resolve::compute_native_build_artifacts(
|
|
563
|
+
let native_build = resolve::compute_native_build_artifacts(
|
|
564
|
+
&merged.program,
|
|
565
|
+
root,
|
|
566
|
+
&native_modules,
|
|
567
|
+
)
|
|
559
568
|
.map_err(|e| CompileError {
|
|
560
569
|
message: e,
|
|
561
570
|
span: None,
|
|
562
571
|
})?;
|
|
563
572
|
let mut all_features: Vec<String> = features.to_vec();
|
|
564
|
-
for f in resolve::extract_native_import_features(&merged) {
|
|
573
|
+
for f in resolve::extract_native_import_features(&merged.program) {
|
|
565
574
|
if !all_features.contains(&f) {
|
|
566
575
|
all_features.push(f);
|
|
567
576
|
}
|
|
568
577
|
}
|
|
569
578
|
let rust = compile_with_native_modules(
|
|
570
|
-
&merged,
|
|
579
|
+
&merged.program,
|
|
571
580
|
project_root,
|
|
572
581
|
&all_features,
|
|
573
582
|
&native_modules,
|
|
@@ -659,8 +668,20 @@ struct Codegen {
|
|
|
659
668
|
usage_analyzer: Option<UsageAnalyzer>,
|
|
660
669
|
/// Type context for tracking variable types (for static typing)
|
|
661
670
|
type_context: TypeContext,
|
|
671
|
+
/// Registry of `type Foo = { ... }` declarations seen in the program.
|
|
672
|
+
/// Populated in a pre-pass so that any later `let x: Foo = ...` or
|
|
673
|
+
/// `fn f(x: Foo)` resolves to a `RustType::Named { name: "Foo", ... }`
|
|
674
|
+
/// and the codegen can emit a Rust struct + direct field access for
|
|
675
|
+
/// values of that type.
|
|
676
|
+
type_aliases: std::collections::HashMap<String, crate::types::RustType>,
|
|
662
677
|
/// Program uses JSX; emit `tishlang_ui` imports and `h` / `Fragment` globals.
|
|
663
678
|
program_has_jsx: bool,
|
|
679
|
+
/// `fn` names for Rust JSX: PascalCase tags matching these use a value binding; others are string intrinsics.
|
|
680
|
+
program_fun_decl_names: std::collections::HashSet<String>,
|
|
681
|
+
/// Nesting depth inside `Value::native(move |args| {{ ... }})` user functions / arrows.
|
|
682
|
+
/// `try`/`throw` lowering uses `return Err` only at depth 0 (e.g. `run()`); inside native
|
|
683
|
+
/// closures it must not return a `Result` from a `Value`-returning closure.
|
|
684
|
+
value_fn_depth: u32,
|
|
664
685
|
}
|
|
665
686
|
|
|
666
687
|
impl Codegen {
|
|
@@ -687,7 +708,191 @@ impl Codegen {
|
|
|
687
708
|
rc_cell_storage_scopes: vec![std::collections::HashSet::new()],
|
|
688
709
|
usage_analyzer: None,
|
|
689
710
|
type_context: TypeContext::new(),
|
|
711
|
+
type_aliases: std::collections::HashMap::new(),
|
|
690
712
|
program_has_jsx: false,
|
|
713
|
+
program_fun_decl_names: std::collections::HashSet::new(),
|
|
714
|
+
value_fn_depth: 0,
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/// Walk every `Statement::TypeAlias` in the program (including nested
|
|
719
|
+
/// ones inside blocks, ifs, loops, function bodies, and exports) and
|
|
720
|
+
/// register the resolved `RustType` under its alias name. Forward
|
|
721
|
+
/// references are handled by running this pass *before* any other
|
|
722
|
+
/// codegen step.
|
|
723
|
+
fn collect_type_aliases(&mut self, statements: &[Statement]) {
|
|
724
|
+
// Two passes so an alias `type B = A` can resolve `A` even if
|
|
725
|
+
// `A` is declared after `B` in source order.
|
|
726
|
+
let mut raw: Vec<(String, &TypeAnnotation)> = Vec::new();
|
|
727
|
+
Self::walk_type_aliases(statements, &mut raw);
|
|
728
|
+
// First-fixpoint resolution: keep iterating until no more aliases
|
|
729
|
+
// change shape. In practice 1–2 passes; capped to prevent infinite
|
|
730
|
+
// loops on (already rejected) self-referential aliases.
|
|
731
|
+
for _ in 0..8 {
|
|
732
|
+
let mut changed = false;
|
|
733
|
+
for (name, ann) in &raw {
|
|
734
|
+
let resolved = crate::types::RustType::from_annotation_with_aliases(
|
|
735
|
+
ann,
|
|
736
|
+
&self.type_aliases,
|
|
737
|
+
);
|
|
738
|
+
let prev: Option<crate::types::RustType> =
|
|
739
|
+
self.type_aliases.get(name).cloned();
|
|
740
|
+
if prev.as_ref() != Some(&resolved) {
|
|
741
|
+
self.type_aliases.insert(name.clone(), resolved);
|
|
742
|
+
changed = true;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if !changed {
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
fn walk_type_aliases<'p>(
|
|
752
|
+
statements: &'p [Statement],
|
|
753
|
+
out: &mut Vec<(String, &'p TypeAnnotation)>,
|
|
754
|
+
) {
|
|
755
|
+
for s in statements {
|
|
756
|
+
match s {
|
|
757
|
+
Statement::TypeAlias { name, ty, .. } => {
|
|
758
|
+
out.push((name.to_string(), ty));
|
|
759
|
+
}
|
|
760
|
+
Statement::Block { statements, .. } => Self::walk_type_aliases(statements, out),
|
|
761
|
+
Statement::If {
|
|
762
|
+
then_branch,
|
|
763
|
+
else_branch,
|
|
764
|
+
..
|
|
765
|
+
} => {
|
|
766
|
+
Self::walk_type_aliases(std::slice::from_ref(then_branch.as_ref()), out);
|
|
767
|
+
if let Some(e) = else_branch {
|
|
768
|
+
Self::walk_type_aliases(std::slice::from_ref(e.as_ref()), out);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
Statement::For { body, .. }
|
|
772
|
+
| Statement::ForOf { body, .. }
|
|
773
|
+
| Statement::While { body, .. }
|
|
774
|
+
| Statement::DoWhile { body, .. } => {
|
|
775
|
+
Self::walk_type_aliases(std::slice::from_ref(body.as_ref()), out);
|
|
776
|
+
}
|
|
777
|
+
Statement::Export { declaration, .. } => {
|
|
778
|
+
if let tishlang_ast::ExportDeclaration::Named(s) = declaration.as_ref() {
|
|
779
|
+
Self::walk_type_aliases(std::slice::from_ref(s.as_ref()), out);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
_ => {}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/// Emit a Rust `struct` definition for every type alias whose RHS is
|
|
788
|
+
/// an object shape. Each generated struct derives `Clone` + `Debug`
|
|
789
|
+
/// (cheap; field types are all `Copy`-or-cheap-clone in practice) and
|
|
790
|
+
/// is named `TishStruct_<TishAlias>`.
|
|
791
|
+
fn emit_named_struct_decls(&mut self) {
|
|
792
|
+
// Snapshot keys + values so we can mutate `self` (writing the
|
|
793
|
+
// emitted source) inside the loop.
|
|
794
|
+
let mut entries: Vec<(String, crate::types::RustType)> =
|
|
795
|
+
self.type_aliases.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
|
|
796
|
+
entries.sort_by(|a, b| a.0.cmp(&b.0));
|
|
797
|
+
let mut emitted_any = false;
|
|
798
|
+
for (name, ty) in entries {
|
|
799
|
+
if let crate::types::RustType::Named { fields, .. }
|
|
800
|
+
| crate::types::RustType::Object(fields)
|
|
801
|
+
// ^^ also accept inline shapes registered as aliases — though
|
|
802
|
+
// `from_annotation_with_aliases` should always have lifted
|
|
803
|
+
// them to `Named` by now.
|
|
804
|
+
= &ty
|
|
805
|
+
{
|
|
806
|
+
let struct_name = crate::types::named_struct_ident(&name);
|
|
807
|
+
self.write(&format!("#[derive(Clone, Debug, Default)]\n"));
|
|
808
|
+
self.write("#[allow(non_snake_case, non_camel_case_types)]\n");
|
|
809
|
+
self.write(&format!("pub struct {} {{\n", struct_name));
|
|
810
|
+
for (k, t) in fields {
|
|
811
|
+
self.write(&format!(
|
|
812
|
+
" pub {}: {},\n",
|
|
813
|
+
crate::types::field_ident(k),
|
|
814
|
+
t.to_rust_type_str()
|
|
815
|
+
));
|
|
816
|
+
}
|
|
817
|
+
self.write("}\n\n");
|
|
818
|
+
|
|
819
|
+
// Emit a hand-rolled JSON serialiser per struct so
|
|
820
|
+
// `JSON.stringify(typed_value)` (and `Vec<TishStruct_X>`)
|
|
821
|
+
// can bypass the `Value::Object` allocation entirely —
|
|
822
|
+
// we walk the struct's fields by name and write directly
|
|
823
|
+
// into the response buffer. ASCII-fast string escape is
|
|
824
|
+
// shared with the `Value` path via the
|
|
825
|
+
// `escape_json_string_into` helper that the runtime
|
|
826
|
+
// re-exports from `tishlang_core::json`.
|
|
827
|
+
self.write(&format!("impl {} {{\n", struct_name));
|
|
828
|
+
self.write(" pub fn _tish_write_json(&self, buf: &mut String) {\n");
|
|
829
|
+
self.write(" use std::fmt::Write as _;\n");
|
|
830
|
+
self.write(" buf.push('{');\n");
|
|
831
|
+
for (i, (k, t)) in fields.iter().enumerate() {
|
|
832
|
+
let sep = if i == 0 { "{" } else { ",{" };
|
|
833
|
+
let prefix = if i == 0 {
|
|
834
|
+
format!("\"\\\"{}\\\":\"", k.as_ref())
|
|
835
|
+
} else {
|
|
836
|
+
format!("\",\\\"{}\\\":\"", k.as_ref())
|
|
837
|
+
};
|
|
838
|
+
let _ = sep; // (lint silence; we built `prefix` above directly)
|
|
839
|
+
self.write(&format!(" buf.push_str({});\n", prefix));
|
|
840
|
+
let access = format!("self.{}", crate::types::field_ident(k));
|
|
841
|
+
match t {
|
|
842
|
+
crate::types::RustType::F64 => {
|
|
843
|
+
self.write(&format!(
|
|
844
|
+
" if {a}.is_nan() || {a}.is_infinite() {{ buf.push_str(\"null\"); }} else {{ let _ = write!(buf, \"{{}}\", {a}); }}\n",
|
|
845
|
+
a = access
|
|
846
|
+
));
|
|
847
|
+
}
|
|
848
|
+
crate::types::RustType::Bool => {
|
|
849
|
+
self.write(&format!(
|
|
850
|
+
" buf.push_str(if {} {{ \"true\" }} else {{ \"false\" }});\n",
|
|
851
|
+
access
|
|
852
|
+
));
|
|
853
|
+
}
|
|
854
|
+
crate::types::RustType::String => {
|
|
855
|
+
self.write(&format!(
|
|
856
|
+
" buf.push('\"'); tishlang_runtime::json::escape_into(buf, {}.as_str()); buf.push('\"');\n",
|
|
857
|
+
access
|
|
858
|
+
));
|
|
859
|
+
}
|
|
860
|
+
crate::types::RustType::Named { .. } => {
|
|
861
|
+
self.write(&format!(
|
|
862
|
+
" {}._tish_write_json(buf);\n",
|
|
863
|
+
access
|
|
864
|
+
));
|
|
865
|
+
}
|
|
866
|
+
crate::types::RustType::Vec(inner) if matches!(
|
|
867
|
+
inner.as_ref(),
|
|
868
|
+
crate::types::RustType::Named { .. }
|
|
869
|
+
) => {
|
|
870
|
+
self.write(" buf.push('[');\n");
|
|
871
|
+
self.write(&format!(
|
|
872
|
+
" for (i, item) in {}.iter().enumerate() {{ if i > 0 {{ buf.push(','); }} item._tish_write_json(buf); }}\n",
|
|
873
|
+
access
|
|
874
|
+
));
|
|
875
|
+
self.write(" buf.push(']');\n");
|
|
876
|
+
}
|
|
877
|
+
_ => {
|
|
878
|
+
// Fallback: convert the field to a Value and
|
|
879
|
+
// delegate to the dynamic stringifier.
|
|
880
|
+
let v_expr = t.to_value_expr(&access);
|
|
881
|
+
self.write(&format!(
|
|
882
|
+
" let _v: Value = {}; tishlang_runtime::json::stringify_into(buf, &_v);\n",
|
|
883
|
+
v_expr
|
|
884
|
+
));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
self.write(" buf.push('}');\n");
|
|
889
|
+
self.write(" }\n");
|
|
890
|
+
self.write("}\n\n");
|
|
891
|
+
emitted_any = true;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if emitted_any {
|
|
895
|
+
self.write("\n");
|
|
691
896
|
}
|
|
692
897
|
}
|
|
693
898
|
|
|
@@ -733,39 +938,47 @@ impl Codegen {
|
|
|
733
938
|
fn builtin_native_module_rust_init(&self, spec: &str, export_name: &str) -> Option<String> {
|
|
734
939
|
let init = match spec {
|
|
735
940
|
"tish:fs" if self.has_feature("fs") => match export_name {
|
|
736
|
-
"readFile" => Some("Value::
|
|
737
|
-
"writeFile" => Some("Value::
|
|
738
|
-
"fileExists" => Some("Value::
|
|
739
|
-
"isDir" => Some("Value::
|
|
740
|
-
"readDir" => Some("Value::
|
|
741
|
-
"mkdir" => Some("Value::
|
|
941
|
+
"readFile" => Some("Value::native(|args: &[Value]| tish_read_file(args))"),
|
|
942
|
+
"writeFile" => Some("Value::native(|args: &[Value]| tish_write_file(args))"),
|
|
943
|
+
"fileExists" => Some("Value::native(|args: &[Value]| tish_file_exists(args))"),
|
|
944
|
+
"isDir" => Some("Value::native(|args: &[Value]| tish_is_dir(args))"),
|
|
945
|
+
"readDir" => Some("Value::native(|args: &[Value]| tish_read_dir(args))"),
|
|
946
|
+
"mkdir" => Some("Value::native(|args: &[Value]| tish_mkdir(args))"),
|
|
742
947
|
_ => None,
|
|
743
948
|
},
|
|
744
949
|
"tish:http" if self.has_feature("http") => match export_name {
|
|
745
|
-
"fetch" => Some("Value::
|
|
746
|
-
"fetchAll" => Some("Value::
|
|
747
|
-
|
|
950
|
+
"fetch" => Some("Value::native(|args: &[Value]| tish_fetch_promise(args.to_vec()))"),
|
|
951
|
+
"fetchAll" => Some("Value::native(|args: &[Value]| tish_fetch_all_promise(args.to_vec()))"),
|
|
952
|
+
// `serve(port, handler)` (single shared handler) or
|
|
953
|
+
// `serve(port, { onWorker })` (per-worker factory). The
|
|
954
|
+
// latter dispatches into `http_serve_per_worker`, which
|
|
955
|
+
// calls onWorker once per accept thread to build that
|
|
956
|
+
// thread's handler.
|
|
957
|
+
"serve" => Some("Value::native(|args: &[Value]| { let handler = args.get(1).cloned().unwrap_or(Value::Null); match handler { Value::Function(f) => tish_http_serve(args, move |req_args| f(req_args)), Value::Object(ref opts) => { let factory = opts.borrow().get(&Arc::from(\"onWorker\")).cloned().unwrap_or(Value::Null); tishlang_runtime::http_serve_per_worker(args, factory) }, _ => Value::Null } })"),
|
|
748
958
|
"Promise" => Some("tish_promise_object()"),
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
"
|
|
959
|
+
_ => None,
|
|
960
|
+
},
|
|
961
|
+
"tish:timers" if self.has_feature("timers") => match export_name {
|
|
962
|
+
"setTimeout" => Some("Value::native(|args: &[Value]| tish_timer_set_timeout(args))"),
|
|
963
|
+
"setInterval" => Some("Value::native(|args: &[Value]| tish_timer_set_interval(args))"),
|
|
964
|
+
"clearTimeout" => Some("Value::native(|args: &[Value]| tish_timer_clear_timeout(args))"),
|
|
965
|
+
"clearInterval" => Some("Value::native(|args: &[Value]| tish_timer_clear_interval(args))"),
|
|
753
966
|
_ => None,
|
|
754
967
|
},
|
|
755
968
|
"tish:process" if self.has_feature("process") => match export_name {
|
|
756
|
-
"exit" => Some("Value::
|
|
757
|
-
"cwd" => Some("Value::
|
|
758
|
-
"exec" => Some("Value::
|
|
759
|
-
"argv" => Some("Value::Array(
|
|
760
|
-
"env" => Some("Value::Object(
|
|
761
|
-
"process" => Some("{ let mut m = ObjectMap::default(); m.insert(Arc::from(\"exit\"), Value::
|
|
969
|
+
"exit" => Some("Value::native(|args: &[Value]| tish_process_exit(args))"),
|
|
970
|
+
"cwd" => Some("Value::native(|args: &[Value]| tish_process_cwd(args))"),
|
|
971
|
+
"exec" => Some("Value::native(|args: &[Value]| tish_process_exec(args))"),
|
|
972
|
+
"argv" => Some("Value::Array(VmRef::new(std::env::args().map(|s| Value::String(s.into())).collect()))"),
|
|
973
|
+
"env" => Some("Value::Object(VmRef::new(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect()))"),
|
|
974
|
+
"process" => Some("{ let mut m = ObjectMap::default(); m.insert(Arc::from(\"exit\"), Value::native(|args: &[Value]| tish_process_exit(args))); m.insert(Arc::from(\"cwd\"), Value::native(|args: &[Value]| tish_process_cwd(args))); m.insert(Arc::from(\"exec\"), Value::native(|args: &[Value]| tish_process_exec(args))); m.insert(Arc::from(\"argv\"), Value::Array(VmRef::new(std::env::args().map(|s| Value::String(s.into())).collect()))); m.insert(Arc::from(\"env\"), Value::Object(VmRef::new(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect::<ObjectMap>()))); Value::Object(VmRef::new(m)) }"),
|
|
762
975
|
_ => None,
|
|
763
976
|
},
|
|
764
977
|
"tish:ws" if self.has_feature("ws") => match export_name {
|
|
765
|
-
"WebSocket" => Some("Value::
|
|
766
|
-
"Server" => Some("Value::
|
|
767
|
-
"wsSend" => Some("Value::
|
|
768
|
-
"wsBroadcast" => Some("Value::
|
|
978
|
+
"WebSocket" => Some("Value::native(|args: &[Value]| tish_ws_client(args))"),
|
|
979
|
+
"Server" => Some("Value::native(|args: &[Value]| tish_ws_server_construct(args))"),
|
|
980
|
+
"wsSend" => Some("Value::native(|args: &[Value]| Value::Bool(tishlang_runtime::ws_send_native(args.first().unwrap_or(&Value::Null), &args.get(1).map(|v| v.to_display_string()).unwrap_or_default())))"),
|
|
981
|
+
"wsBroadcast" => Some("Value::native(|args: &[Value]| tishlang_runtime::ws_broadcast_native(args))"),
|
|
769
982
|
_ => None,
|
|
770
983
|
},
|
|
771
984
|
_ => return None,
|
|
@@ -775,7 +988,7 @@ impl Codegen {
|
|
|
775
988
|
|
|
776
989
|
fn has_feature(&self, name: &str) -> bool {
|
|
777
990
|
if self.features.contains("full") {
|
|
778
|
-
matches!(name, "http" | "fs" | "process" | "regex" | "ws")
|
|
991
|
+
matches!(name, "http" | "timers" | "fs" | "process" | "regex" | "ws")
|
|
779
992
|
} else {
|
|
780
993
|
self.features.contains(name)
|
|
781
994
|
}
|
|
@@ -1008,22 +1221,26 @@ impl Codegen {
|
|
|
1008
1221
|
fn emit_program(&mut self, program: &Program) -> Result<(), CompileError> {
|
|
1009
1222
|
self.is_async = program_uses_async(program);
|
|
1010
1223
|
self.program_has_jsx = tishlang_ui::jsx::program_contains_jsx(program);
|
|
1224
|
+
self.program_fun_decl_names = tishlang_ui::jsx::collect_fun_decl_names(program);
|
|
1011
1225
|
self.write("#![allow(unused, non_snake_case)]\n\n");
|
|
1012
1226
|
self.write("use std::cell::RefCell;\n");
|
|
1013
1227
|
self.write("use std::rc::Rc;\n");
|
|
1014
1228
|
self.write("use std::sync::Arc;\n");
|
|
1015
|
-
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, 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, tish_construct, tish_uint8_array_constructor, tish_audio_context_constructor, ObjectMap, TishError, Value};\n");
|
|
1229
|
+
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, tish_construct, tish_uint8_array_constructor, tish_audio_context_constructor, register_static_route as tish_register_static_route, ObjectMap, TishError, Value, VmRef};\n");
|
|
1016
1230
|
if self.program_has_jsx {
|
|
1017
1231
|
self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
|
|
1018
1232
|
}
|
|
1019
1233
|
if self.has_feature("process") {
|
|
1020
1234
|
self.write("use tishlang_runtime::{process_exit as tish_process_exit, process_cwd as tish_process_cwd, process_exec as tish_process_exec};\n");
|
|
1021
1235
|
}
|
|
1236
|
+
if self.has_feature("timers") {
|
|
1237
|
+
self.write("use tishlang_runtime::{timer_set_timeout as tish_timer_set_timeout, timer_clear_timeout as tish_timer_clear_timeout, timer_set_interval as tish_timer_set_interval, timer_clear_interval as tish_timer_clear_interval};\n");
|
|
1238
|
+
}
|
|
1022
1239
|
if self.has_feature("http") {
|
|
1023
1240
|
if self.is_async {
|
|
1024
|
-
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve,
|
|
1241
|
+
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve, promise_object as tish_promise_object, await_promise as tish_await_promise};\n");
|
|
1025
1242
|
} else {
|
|
1026
|
-
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve
|
|
1243
|
+
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve};\n");
|
|
1027
1244
|
}
|
|
1028
1245
|
}
|
|
1029
1246
|
if self.has_feature("fs") {
|
|
@@ -1037,6 +1254,19 @@ impl Codegen {
|
|
|
1037
1254
|
}
|
|
1038
1255
|
self.write("\n");
|
|
1039
1256
|
|
|
1257
|
+
// Collect every `type Foo = { ... }` declaration in the program
|
|
1258
|
+
// (recursive, so they can also live inside blocks / branches) and
|
|
1259
|
+
// canonicalise each into a `RustType::Named` with its field list.
|
|
1260
|
+
// Aliases that resolve to a non-Object shape (e.g. `type N = number`)
|
|
1261
|
+
// are stored too, so later annotations like `let x: N = 0` still
|
|
1262
|
+
// pick up the right native type.
|
|
1263
|
+
self.collect_type_aliases(&program.statements);
|
|
1264
|
+
// Emit a Rust `struct` for every alias whose RHS is an object
|
|
1265
|
+
// shape. Subsequent `let x: Foo = ...` literals lower to plain
|
|
1266
|
+
// struct moves (no `VmRef::new(ObjectMap::from(..))` allocation),
|
|
1267
|
+
// and `x.field` becomes a direct field access.
|
|
1268
|
+
self.emit_named_struct_decls();
|
|
1269
|
+
|
|
1040
1270
|
if self.is_async {
|
|
1041
1271
|
self.writeln("#[tokio::main]");
|
|
1042
1272
|
self.writeln("async fn main() {");
|
|
@@ -1065,126 +1295,132 @@ impl Codegen {
|
|
|
1065
1295
|
self.indent += 1;
|
|
1066
1296
|
|
|
1067
1297
|
// Initialize builtins
|
|
1068
|
-
self.writeln("let mut console = Value::Object(
|
|
1298
|
+
self.writeln("let mut console = Value::Object(VmRef::new(ObjectMap::from([");
|
|
1069
1299
|
self.indent += 1;
|
|
1070
|
-
self.writeln("(Arc::from(\"debug\"), Value::
|
|
1071
|
-
self.writeln("(Arc::from(\"info\"), Value::
|
|
1072
|
-
self.writeln("(Arc::from(\"log\"), Value::
|
|
1073
|
-
self.writeln("(Arc::from(\"warn\"), Value::
|
|
1074
|
-
self.writeln("(Arc::from(\"error\"), Value::
|
|
1300
|
+
self.writeln("(Arc::from(\"debug\"), Value::native(|args: &[Value]| { tish_console_debug(args); Value::Null })),");
|
|
1301
|
+
self.writeln("(Arc::from(\"info\"), Value::native(|args: &[Value]| { tish_console_info(args); Value::Null })),");
|
|
1302
|
+
self.writeln("(Arc::from(\"log\"), Value::native(|args: &[Value]| { tish_console_log(args); Value::Null })),");
|
|
1303
|
+
self.writeln("(Arc::from(\"warn\"), Value::native(|args: &[Value]| { tish_console_warn(args); Value::Null })),");
|
|
1304
|
+
self.writeln("(Arc::from(\"error\"), Value::native(|args: &[Value]| { tish_console_error(args); Value::Null })),");
|
|
1075
1305
|
self.indent -= 1;
|
|
1076
|
-
self.writeln("])))
|
|
1306
|
+
self.writeln("])));");
|
|
1077
1307
|
self.writeln(
|
|
1078
|
-
"let Boolean = Value::
|
|
1308
|
+
"let Boolean = Value::native(|args: &[Value]| tish_boolean(args));",
|
|
1079
1309
|
);
|
|
1080
1310
|
self.writeln(
|
|
1081
|
-
"let parseInt = Value::
|
|
1311
|
+
"let parseInt = Value::native(|args: &[Value]| tish_parse_int(args));",
|
|
1082
1312
|
);
|
|
1083
1313
|
self.writeln(
|
|
1084
|
-
"let parseFloat = Value::
|
|
1314
|
+
"let parseFloat = Value::native(|args: &[Value]| tish_parse_float(args));",
|
|
1085
1315
|
);
|
|
1086
1316
|
self.writeln(
|
|
1087
|
-
"let decodeURI = Value::
|
|
1317
|
+
"let decodeURI = Value::native(|args: &[Value]| tish_decode_uri(args));",
|
|
1088
1318
|
);
|
|
1089
1319
|
self.writeln(
|
|
1090
|
-
"let encodeURI = Value::
|
|
1320
|
+
"let encodeURI = Value::native(|args: &[Value]| tish_encode_uri(args));",
|
|
1091
1321
|
);
|
|
1092
1322
|
self.writeln(
|
|
1093
|
-
"let
|
|
1323
|
+
r#"let registerStaticRoute = Value::native(|args: &[Value]| { let path = match args.get(0) { Some(Value::String(s)) => s.to_string(), _ => return Value::Null }; let body = match args.get(1) { Some(Value::String(s)) => s.as_bytes().to_vec(), _ => return Value::Null }; let ct = match args.get(2) { Some(Value::String(s)) => s.to_string(), _ => "application/octet-stream".to_string() }; tish_register_static_route(&path, &body, &ct); Value::Null });"#,
|
|
1094
1324
|
);
|
|
1095
|
-
self.writeln(
|
|
1325
|
+
self.writeln(
|
|
1326
|
+
"let htmlEscape = Value::native(|args: &[Value]| tish_escape_html(args.first().unwrap_or(&Value::Null)));",
|
|
1327
|
+
);
|
|
1328
|
+
self.writeln(
|
|
1329
|
+
"let isFinite = Value::native(|args: &[Value]| tish_is_finite(args));",
|
|
1330
|
+
);
|
|
1331
|
+
self.writeln("let isNaN = Value::native(|args: &[Value]| tish_is_nan(args));");
|
|
1096
1332
|
self.writeln("let Infinity = Value::Number(f64::INFINITY);");
|
|
1097
1333
|
self.writeln("let NaN = Value::Number(f64::NAN);");
|
|
1098
|
-
self.writeln("let Math = Value::Object(
|
|
1334
|
+
self.writeln("let Math = Value::Object(VmRef::new(ObjectMap::from([");
|
|
1099
1335
|
self.indent += 1;
|
|
1100
1336
|
self.writeln(
|
|
1101
|
-
"(Arc::from(\"abs\"), Value::
|
|
1337
|
+
"(Arc::from(\"abs\"), Value::native(|args: &[Value]| tish_math_abs(args))),",
|
|
1102
1338
|
);
|
|
1103
|
-
self.writeln("(Arc::from(\"sqrt\"), Value::
|
|
1339
|
+
self.writeln("(Arc::from(\"sqrt\"), Value::native(|args: &[Value]| tish_math_sqrt(args))),");
|
|
1104
1340
|
self.writeln(
|
|
1105
|
-
"(Arc::from(\"min\"), Value::
|
|
1341
|
+
"(Arc::from(\"min\"), Value::native(|args: &[Value]| tish_math_min(args))),",
|
|
1106
1342
|
);
|
|
1107
1343
|
self.writeln(
|
|
1108
|
-
"(Arc::from(\"max\"), Value::
|
|
1344
|
+
"(Arc::from(\"max\"), Value::native(|args: &[Value]| tish_math_max(args))),",
|
|
1109
1345
|
);
|
|
1110
|
-
self.writeln("(Arc::from(\"floor\"), Value::
|
|
1111
|
-
self.writeln("(Arc::from(\"ceil\"), Value::
|
|
1112
|
-
self.writeln("(Arc::from(\"round\"), Value::
|
|
1113
|
-
self.writeln("(Arc::from(\"random\"), Value::
|
|
1346
|
+
self.writeln("(Arc::from(\"floor\"), Value::native(|args: &[Value]| tish_math_floor(args))),");
|
|
1347
|
+
self.writeln("(Arc::from(\"ceil\"), Value::native(|args: &[Value]| tish_math_ceil(args))),");
|
|
1348
|
+
self.writeln("(Arc::from(\"round\"), Value::native(|args: &[Value]| tish_math_round(args))),");
|
|
1349
|
+
self.writeln("(Arc::from(\"random\"), Value::native(|args: &[Value]| tish_math_random(args))),");
|
|
1114
1350
|
self.writeln(
|
|
1115
|
-
"(Arc::from(\"pow\"), Value::
|
|
1351
|
+
"(Arc::from(\"pow\"), Value::native(|args: &[Value]| tish_math_pow(args))),",
|
|
1116
1352
|
);
|
|
1117
1353
|
self.writeln(
|
|
1118
|
-
"(Arc::from(\"sin\"), Value::
|
|
1354
|
+
"(Arc::from(\"sin\"), Value::native(|args: &[Value]| tish_math_sin(args))),",
|
|
1119
1355
|
);
|
|
1120
1356
|
self.writeln(
|
|
1121
|
-
"(Arc::from(\"cos\"), Value::
|
|
1357
|
+
"(Arc::from(\"cos\"), Value::native(|args: &[Value]| tish_math_cos(args))),",
|
|
1122
1358
|
);
|
|
1123
1359
|
self.writeln(
|
|
1124
|
-
"(Arc::from(\"tan\"), Value::
|
|
1360
|
+
"(Arc::from(\"tan\"), Value::native(|args: &[Value]| tish_math_tan(args))),",
|
|
1125
1361
|
);
|
|
1126
1362
|
self.writeln(
|
|
1127
|
-
"(Arc::from(\"log\"), Value::
|
|
1363
|
+
"(Arc::from(\"log\"), Value::native(|args: &[Value]| tish_math_log(args))),",
|
|
1128
1364
|
);
|
|
1129
1365
|
self.writeln(
|
|
1130
|
-
"(Arc::from(\"exp\"), Value::
|
|
1366
|
+
"(Arc::from(\"exp\"), Value::native(|args: &[Value]| tish_math_exp(args))),",
|
|
1131
1367
|
);
|
|
1132
|
-
self.writeln("(Arc::from(\"sign\"), Value::
|
|
1133
|
-
self.writeln("(Arc::from(\"trunc\"), Value::
|
|
1368
|
+
self.writeln("(Arc::from(\"sign\"), Value::native(|args: &[Value]| tish_math_sign(args))),");
|
|
1369
|
+
self.writeln("(Arc::from(\"trunc\"), Value::native(|args: &[Value]| tish_math_trunc(args))),");
|
|
1134
1370
|
self.writeln("(Arc::from(\"PI\"), Value::Number(std::f64::consts::PI)),");
|
|
1135
1371
|
self.writeln("(Arc::from(\"E\"), Value::Number(std::f64::consts::E)),");
|
|
1136
1372
|
self.indent -= 1;
|
|
1137
|
-
self.writeln("])))
|
|
1138
|
-
self.writeln("let JSON = Value::Object(
|
|
1373
|
+
self.writeln("])));");
|
|
1374
|
+
self.writeln("let JSON = Value::Object(VmRef::new(ObjectMap::from([");
|
|
1139
1375
|
self.indent += 1;
|
|
1140
|
-
self.writeln("(Arc::from(\"parse\"), Value::
|
|
1141
|
-
self.writeln("(Arc::from(\"stringify\"), Value::
|
|
1376
|
+
self.writeln("(Arc::from(\"parse\"), Value::native(|args: &[Value]| tish_json_parse(args))),");
|
|
1377
|
+
self.writeln("(Arc::from(\"stringify\"), Value::native(|args: &[Value]| tish_json_stringify(args))),");
|
|
1142
1378
|
self.indent -= 1;
|
|
1143
|
-
self.writeln("])))
|
|
1379
|
+
self.writeln("])));");
|
|
1144
1380
|
|
|
1145
|
-
self.writeln("let Array = Value::Object(
|
|
1381
|
+
self.writeln("let Array = Value::Object(VmRef::new(ObjectMap::from([");
|
|
1146
1382
|
self.indent += 1;
|
|
1147
|
-
self.writeln("(Arc::from(\"isArray\"), Value::
|
|
1383
|
+
self.writeln("(Arc::from(\"isArray\"), Value::native(|args: &[Value]| tish_array_is_array(args))),");
|
|
1148
1384
|
self.indent -= 1;
|
|
1149
|
-
self.writeln("])))
|
|
1385
|
+
self.writeln("])));");
|
|
1150
1386
|
|
|
1151
|
-
self.writeln("let String = Value::Object(
|
|
1387
|
+
self.writeln("let String = Value::Object(VmRef::new(ObjectMap::from([");
|
|
1152
1388
|
self.indent += 1;
|
|
1153
|
-
self.writeln("(Arc::from(\"fromCharCode\"), Value::
|
|
1389
|
+
self.writeln("(Arc::from(\"fromCharCode\"), Value::native(|args: &[Value]| tish_string_from_char_code(args))),");
|
|
1154
1390
|
self.indent -= 1;
|
|
1155
|
-
self.writeln("])))
|
|
1391
|
+
self.writeln("])));");
|
|
1156
1392
|
|
|
1157
|
-
self.writeln("let Date = Value::Object(
|
|
1393
|
+
self.writeln("let Date = Value::Object(VmRef::new(ObjectMap::from([");
|
|
1158
1394
|
self.indent += 1;
|
|
1159
1395
|
self.writeln(
|
|
1160
|
-
"(Arc::from(\"now\"), Value::
|
|
1396
|
+
"(Arc::from(\"now\"), Value::native(|args: &[Value]| tish_date_now(args))),",
|
|
1161
1397
|
);
|
|
1162
1398
|
self.indent -= 1;
|
|
1163
|
-
self.writeln("])))
|
|
1399
|
+
self.writeln("])));");
|
|
1164
1400
|
|
|
1165
|
-
self.writeln("let Object = Value::Object(
|
|
1401
|
+
self.writeln("let Object = Value::Object(VmRef::new(ObjectMap::from([");
|
|
1166
1402
|
self.indent += 1;
|
|
1167
|
-
self.writeln("(Arc::from(\"assign\"), Value::
|
|
1168
|
-
self.writeln("(Arc::from(\"keys\"), Value::
|
|
1169
|
-
self.writeln("(Arc::from(\"values\"), Value::
|
|
1170
|
-
self.writeln("(Arc::from(\"entries\"), Value::
|
|
1171
|
-
self.writeln("(Arc::from(\"fromEntries\"), Value::
|
|
1403
|
+
self.writeln("(Arc::from(\"assign\"), Value::native(|args: &[Value]| tish_object_assign(args))),");
|
|
1404
|
+
self.writeln("(Arc::from(\"keys\"), Value::native(|args: &[Value]| tish_object_keys(args))),");
|
|
1405
|
+
self.writeln("(Arc::from(\"values\"), Value::native(|args: &[Value]| tish_object_values(args))),");
|
|
1406
|
+
self.writeln("(Arc::from(\"entries\"), Value::native(|args: &[Value]| tish_object_entries(args))),");
|
|
1407
|
+
self.writeln("(Arc::from(\"fromEntries\"), Value::native(|args: &[Value]| tish_object_from_entries(args))),");
|
|
1172
1408
|
self.indent -= 1;
|
|
1173
|
-
self.writeln("])))
|
|
1409
|
+
self.writeln("])));");
|
|
1174
1410
|
|
|
1175
1411
|
self.writeln("let Uint8Array = tish_uint8_array_constructor();");
|
|
1176
1412
|
self.writeln("let AudioContext = tish_audio_context_constructor();");
|
|
1177
1413
|
|
|
1178
1414
|
if self.has_feature("process") {
|
|
1179
|
-
self.writeln("let process = Value::Object(
|
|
1415
|
+
self.writeln("let process = Value::Object(VmRef::new({");
|
|
1180
1416
|
self.indent += 1;
|
|
1181
1417
|
self.writeln("let mut p = ObjectMap::default();");
|
|
1182
|
-
self.writeln("p.insert(Arc::from(\"exit\"), Value::
|
|
1183
|
-
self.writeln("p.insert(Arc::from(\"cwd\"), Value::
|
|
1184
|
-
self.writeln("p.insert(Arc::from(\"exec\"), Value::
|
|
1418
|
+
self.writeln("p.insert(Arc::from(\"exit\"), Value::native(|args: &[Value]| tish_process_exit(args)));");
|
|
1419
|
+
self.writeln("p.insert(Arc::from(\"cwd\"), Value::native(|args: &[Value]| tish_process_cwd(args)));");
|
|
1420
|
+
self.writeln("p.insert(Arc::from(\"exec\"), Value::native(|args: &[Value]| tish_process_exec(args)));");
|
|
1185
1421
|
self.writeln("let argv: Vec<Value> = std::env::args().map(|s| Value::String(s.into())).collect();");
|
|
1186
1422
|
self.writeln(
|
|
1187
|
-
"p.insert(Arc::from(\"argv\"), Value::Array(
|
|
1423
|
+
"p.insert(Arc::from(\"argv\"), Value::Array(VmRef::new(argv)));",
|
|
1188
1424
|
);
|
|
1189
1425
|
self.writeln("let mut env_obj = ObjectMap::default();");
|
|
1190
1426
|
self.writeln("for (key, value) in std::env::vars() {");
|
|
@@ -1193,72 +1429,86 @@ impl Codegen {
|
|
|
1193
1429
|
self.indent -= 1;
|
|
1194
1430
|
self.writeln("}");
|
|
1195
1431
|
self.writeln(
|
|
1196
|
-
"p.insert(Arc::from(\"env\"), Value::Object(
|
|
1432
|
+
"p.insert(Arc::from(\"env\"), Value::Object(VmRef::new(env_obj)));",
|
|
1197
1433
|
);
|
|
1198
1434
|
self.writeln("p");
|
|
1199
1435
|
self.indent -= 1;
|
|
1200
|
-
self.writeln("}))
|
|
1436
|
+
self.writeln("}));");
|
|
1201
1437
|
}
|
|
1202
1438
|
|
|
1439
|
+
if self.has_feature("timers") {
|
|
1440
|
+
self.writeln("let setTimeout = Value::native(|args: &[Value]| tish_timer_set_timeout(args));");
|
|
1441
|
+
self.writeln("let clearTimeout = Value::native(|args: &[Value]| tish_timer_clear_timeout(args));");
|
|
1442
|
+
self.writeln("let setInterval = Value::native(|args: &[Value]| tish_timer_set_interval(args));");
|
|
1443
|
+
self.writeln("let clearInterval = Value::native(|args: &[Value]| tish_timer_clear_interval(args));");
|
|
1444
|
+
}
|
|
1203
1445
|
if self.has_feature("http") {
|
|
1204
|
-
self.writeln("let fetch = Value::
|
|
1205
|
-
self.writeln("let fetchAll = Value::
|
|
1206
|
-
self.writeln("let setTimeout = Value::Function(Rc::new(|args: &[Value]| tish_timer_set_timeout(args)));");
|
|
1207
|
-
self.writeln("let clearTimeout = Value::Function(Rc::new(|args: &[Value]| tish_timer_clear_timeout(args)));");
|
|
1446
|
+
self.writeln("let fetch = Value::native(|args: &[Value]| tish_fetch_promise(args.to_vec()));");
|
|
1447
|
+
self.writeln("let fetchAll = Value::native(|args: &[Value]| tish_fetch_all_promise(args.to_vec()));");
|
|
1208
1448
|
if self.is_async {
|
|
1209
1449
|
self.writeln("let Promise = tish_promise_object();");
|
|
1210
1450
|
}
|
|
1211
|
-
|
|
1451
|
+
// `serve` supports two shapes:
|
|
1452
|
+
// 1. serve(port, handler) // single shared handler
|
|
1453
|
+
// 2. serve(port, { onWorker: (workerId) => handler, ... })
|
|
1454
|
+
//
|
|
1455
|
+
// Shape (2) lets users build per-worker state (DB connection,
|
|
1456
|
+
// cache, counter, ...) without a global mutex. The runtime
|
|
1457
|
+
// dispatches each accept thread to its own handler, all in
|
|
1458
|
+
// parallel under `send-values`.
|
|
1459
|
+
self.writeln("let serve = Value::native(|args: &[Value]| {");
|
|
1212
1460
|
self.indent += 1;
|
|
1213
|
-
self.writeln("let port = args.first().cloned().unwrap_or(Value::Null);");
|
|
1214
1461
|
self.writeln("let handler = args.get(1).cloned().unwrap_or(Value::Null);");
|
|
1215
|
-
self.writeln("
|
|
1462
|
+
self.writeln("match handler {");
|
|
1216
1463
|
self.indent += 1;
|
|
1217
|
-
self.writeln("tish_http_serve(args, move |req_args| f(req_args))");
|
|
1218
|
-
self.
|
|
1219
|
-
self.writeln("} else {");
|
|
1464
|
+
self.writeln("Value::Function(f) => tish_http_serve(args, move |req_args| f(req_args)),");
|
|
1465
|
+
self.writeln("Value::Object(ref opts) => {");
|
|
1220
1466
|
self.indent += 1;
|
|
1221
|
-
self.writeln("Value::Null");
|
|
1467
|
+
self.writeln("let factory = opts.borrow().get(&Arc::from(\"onWorker\")).cloned().unwrap_or(Value::Null);");
|
|
1468
|
+
self.writeln("tishlang_runtime::http_serve_per_worker(args, factory)");
|
|
1469
|
+
self.indent -= 1;
|
|
1470
|
+
self.writeln("},");
|
|
1471
|
+
self.writeln("_ => Value::Null,");
|
|
1222
1472
|
self.indent -= 1;
|
|
1223
1473
|
self.writeln("}");
|
|
1224
1474
|
self.indent -= 1;
|
|
1225
|
-
self.writeln("})
|
|
1475
|
+
self.writeln("});");
|
|
1226
1476
|
}
|
|
1227
1477
|
|
|
1228
1478
|
if self.has_feature("fs") {
|
|
1229
1479
|
self.writeln(
|
|
1230
|
-
"let readFile = Value::
|
|
1480
|
+
"let readFile = Value::native(|args: &[Value]| tish_read_file(args));",
|
|
1231
1481
|
);
|
|
1232
1482
|
self.writeln(
|
|
1233
|
-
"let writeFile = Value::
|
|
1483
|
+
"let writeFile = Value::native(|args: &[Value]| tish_write_file(args));",
|
|
1234
1484
|
);
|
|
1235
|
-
self.writeln("let fileExists = Value::
|
|
1485
|
+
self.writeln("let fileExists = Value::native(|args: &[Value]| tish_file_exists(args));");
|
|
1236
1486
|
self.writeln(
|
|
1237
|
-
"let isDir = Value::
|
|
1487
|
+
"let isDir = Value::native(|args: &[Value]| tish_is_dir(args));",
|
|
1238
1488
|
);
|
|
1239
1489
|
self.writeln(
|
|
1240
|
-
"let readDir = Value::
|
|
1490
|
+
"let readDir = Value::native(|args: &[Value]| tish_read_dir(args));",
|
|
1241
1491
|
);
|
|
1242
1492
|
self.writeln(
|
|
1243
|
-
"let mkdir = Value::
|
|
1493
|
+
"let mkdir = Value::native(|args: &[Value]| tish_mkdir(args));",
|
|
1244
1494
|
);
|
|
1245
1495
|
}
|
|
1246
1496
|
|
|
1247
1497
|
if self.has_feature("regex") {
|
|
1248
1498
|
self.writeln(
|
|
1249
|
-
"let RegExp = Value::
|
|
1499
|
+
"let RegExp = Value::native(|args: &[Value]| regexp_new(args));",
|
|
1250
1500
|
);
|
|
1251
1501
|
}
|
|
1252
1502
|
|
|
1253
1503
|
if self.program_has_jsx {
|
|
1254
1504
|
self.writeln("install_thread_local_host(Box::new(HeadlessHost::default()));");
|
|
1255
1505
|
self.writeln("let Fragment = fragment_value();");
|
|
1256
|
-
self.writeln("let h = Value::
|
|
1257
|
-
self.writeln("let text = Value::
|
|
1506
|
+
self.writeln("let h = Value::native(|args: &[Value]| ui_h(args));");
|
|
1507
|
+
self.writeln("let text = Value::native(|args: &[Value]| ui_text(args));");
|
|
1258
1508
|
self.writeln(
|
|
1259
|
-
"let useState = Value::
|
|
1509
|
+
"let useState = Value::native(|args: &[Value]| native_use_state(args));",
|
|
1260
1510
|
);
|
|
1261
|
-
self.writeln("let createRoot = Value::
|
|
1511
|
+
self.writeln("let createRoot = Value::native(|args: &[Value]| native_create_root(args));");
|
|
1262
1512
|
}
|
|
1263
1513
|
|
|
1264
1514
|
// Polars, Egui etc. are emitted via VarDecl from import { X } from 'tish:...'
|
|
@@ -1269,7 +1519,7 @@ impl Codegen {
|
|
|
1269
1519
|
for func_name in &top_level_funcs {
|
|
1270
1520
|
let escaped = Self::escape_ident(func_name);
|
|
1271
1521
|
self.writeln(&format!(
|
|
1272
|
-
"let {}_cell:
|
|
1522
|
+
"let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
|
|
1273
1523
|
escaped
|
|
1274
1524
|
));
|
|
1275
1525
|
}
|
|
@@ -1323,7 +1573,7 @@ impl Codegen {
|
|
|
1323
1573
|
for func_name in &func_names {
|
|
1324
1574
|
let escaped = Self::escape_ident(func_name);
|
|
1325
1575
|
self.writeln(&format!(
|
|
1326
|
-
"let {}_cell:
|
|
1576
|
+
"let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
|
|
1327
1577
|
escaped
|
|
1328
1578
|
));
|
|
1329
1579
|
}
|
|
@@ -1347,10 +1597,15 @@ impl Codegen {
|
|
|
1347
1597
|
init,
|
|
1348
1598
|
..
|
|
1349
1599
|
} => {
|
|
1350
|
-
// Determine the Rust type from annotation
|
|
1600
|
+
// Determine the Rust type from annotation, consulting the
|
|
1601
|
+
// user-declared `type` aliases so a `let x: World = ...`
|
|
1602
|
+
// resolves to `RustType::Named { name: "World", fields }`
|
|
1603
|
+
// and we can emit a struct move instead of a Value box.
|
|
1351
1604
|
let rust_type = type_ann
|
|
1352
1605
|
.as_ref()
|
|
1353
|
-
.map(
|
|
1606
|
+
.map(|t| {
|
|
1607
|
+
crate::types::RustType::from_annotation_with_aliases(t, &self.type_aliases)
|
|
1608
|
+
})
|
|
1354
1609
|
.unwrap_or(RustType::Value);
|
|
1355
1610
|
|
|
1356
1611
|
// Track the variable type
|
|
@@ -1368,7 +1623,7 @@ impl Codegen {
|
|
|
1368
1623
|
if self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
1369
1624
|
// Closure-mutated: same Rc<RefCell<T>> pattern as Value (assignments use borrow_mut)
|
|
1370
1625
|
self.writeln(&format!(
|
|
1371
|
-
"let {} =
|
|
1626
|
+
"let {} = VmRef::new({});",
|
|
1372
1627
|
escaped_name, expr_str
|
|
1373
1628
|
));
|
|
1374
1629
|
self.rc_cell_storage_define(name.as_ref());
|
|
@@ -1399,7 +1654,7 @@ impl Codegen {
|
|
|
1399
1654
|
expr_str.to_string()
|
|
1400
1655
|
};
|
|
1401
1656
|
self.writeln(&format!(
|
|
1402
|
-
"let {} =
|
|
1657
|
+
"let {} = VmRef::new({});",
|
|
1403
1658
|
escaped_name, init_val
|
|
1404
1659
|
));
|
|
1405
1660
|
self.rc_cell_storage_define(name.as_ref());
|
|
@@ -1433,6 +1688,7 @@ impl Codegen {
|
|
|
1433
1688
|
};
|
|
1434
1689
|
self.writeln(&format!("let _destruct_val = ({}){};", expr, clone_suffix));
|
|
1435
1690
|
self.emit_destruct_bindings(pattern, "_destruct_val", mutability, *span)?;
|
|
1691
|
+
self.register_destruct_pattern_outer_vars(pattern);
|
|
1436
1692
|
}
|
|
1437
1693
|
Statement::ExprStmt { expr, .. } => {
|
|
1438
1694
|
let e = self.emit_expr(expr)?;
|
|
@@ -1584,6 +1840,9 @@ impl Codegen {
|
|
|
1584
1840
|
span: None,
|
|
1585
1841
|
});
|
|
1586
1842
|
}
|
|
1843
|
+
Statement::TypeAlias { .. }
|
|
1844
|
+
| Statement::DeclareVar { .. }
|
|
1845
|
+
| Statement::DeclareFun { .. } => {}
|
|
1587
1846
|
Statement::Switch {
|
|
1588
1847
|
expr,
|
|
1589
1848
|
cases,
|
|
@@ -1637,10 +1896,17 @@ impl Codegen {
|
|
|
1637
1896
|
}
|
|
1638
1897
|
Statement::Throw { value, .. } => {
|
|
1639
1898
|
let v = self.emit_expr(value)?;
|
|
1640
|
-
self.
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1899
|
+
if self.value_fn_depth > 0 {
|
|
1900
|
+
self.writeln(&format!(
|
|
1901
|
+
"{{ let _th = {}; panic!(\"uncaught throw: {{}}\", _th.to_display_string()); }}",
|
|
1902
|
+
v
|
|
1903
|
+
));
|
|
1904
|
+
} else {
|
|
1905
|
+
self.writeln(&format!(
|
|
1906
|
+
"return Err(Box::new(tishlang_runtime::TishError::Throw({})) as Box<dyn std::error::Error>);",
|
|
1907
|
+
v
|
|
1908
|
+
));
|
|
1909
|
+
}
|
|
1644
1910
|
}
|
|
1645
1911
|
Statement::Try {
|
|
1646
1912
|
body,
|
|
@@ -1670,10 +1936,22 @@ impl Codegen {
|
|
|
1670
1936
|
Self::escape_ident(param.as_ref())
|
|
1671
1937
|
));
|
|
1672
1938
|
self.emit_statement(catch_stmt)?;
|
|
1673
|
-
self.
|
|
1939
|
+
if self.value_fn_depth > 0 {
|
|
1940
|
+
self.writeln(
|
|
1941
|
+
"} else { panic!(\"unhandled error in native Tish: {:?}\", *tish_err); }",
|
|
1942
|
+
);
|
|
1943
|
+
} else {
|
|
1944
|
+
self.writeln("} else { return Err(Box::new(tish_err)); }");
|
|
1945
|
+
}
|
|
1674
1946
|
self.indent -= 1;
|
|
1675
1947
|
self.writeln("}");
|
|
1676
|
-
self.
|
|
1948
|
+
if self.value_fn_depth > 0 {
|
|
1949
|
+
self.writeln(
|
|
1950
|
+
"Err(orig) => panic!(\"non-Tish error in native Tish: {:?}\", orig),",
|
|
1951
|
+
);
|
|
1952
|
+
} else {
|
|
1953
|
+
self.writeln("Err(orig) => return Err(orig),");
|
|
1954
|
+
}
|
|
1677
1955
|
self.indent -= 1;
|
|
1678
1956
|
self.writeln("}");
|
|
1679
1957
|
self.indent -= 1;
|
|
@@ -1710,7 +1988,7 @@ impl Codegen {
|
|
|
1710
1988
|
.unwrap_or(false);
|
|
1711
1989
|
if !cell_exists {
|
|
1712
1990
|
self.writeln(&format!(
|
|
1713
|
-
"let {}_cell:
|
|
1991
|
+
"let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
|
|
1714
1992
|
name_str
|
|
1715
1993
|
));
|
|
1716
1994
|
}
|
|
@@ -1751,9 +2029,12 @@ impl Codegen {
|
|
|
1751
2029
|
"Math",
|
|
1752
2030
|
"JSON",
|
|
1753
2031
|
"Date",
|
|
2032
|
+
"Object",
|
|
1754
2033
|
"process",
|
|
1755
2034
|
"setTimeout",
|
|
1756
2035
|
"clearTimeout",
|
|
2036
|
+
"setInterval",
|
|
2037
|
+
"clearInterval",
|
|
1757
2038
|
"Promise",
|
|
1758
2039
|
"RegExp",
|
|
1759
2040
|
"Polars",
|
|
@@ -1788,7 +2069,7 @@ impl Codegen {
|
|
|
1788
2069
|
));
|
|
1789
2070
|
} else {
|
|
1790
2071
|
self.writeln(&format!(
|
|
1791
|
-
"let {}_cell =
|
|
2072
|
+
"let {}_cell = VmRef::new({}.clone());",
|
|
1792
2073
|
var_escaped, var_escaped
|
|
1793
2074
|
));
|
|
1794
2075
|
}
|
|
@@ -1846,20 +2127,40 @@ impl Codegen {
|
|
|
1846
2127
|
"Math",
|
|
1847
2128
|
"JSON",
|
|
1848
2129
|
"Date",
|
|
2130
|
+
"Object",
|
|
1849
2131
|
"Uint8Array",
|
|
1850
2132
|
"AudioContext",
|
|
1851
2133
|
"process",
|
|
1852
2134
|
"setTimeout",
|
|
1853
2135
|
"clearTimeout",
|
|
2136
|
+
"setInterval",
|
|
2137
|
+
"clearInterval",
|
|
1854
2138
|
"Promise",
|
|
1855
2139
|
"RegExp",
|
|
1856
2140
|
"Polars",
|
|
2141
|
+
// Free-standing global functions used inside user-defined
|
|
2142
|
+
// functions also need to be cloned into the closure
|
|
2143
|
+
// capture, or the emitted Rust hits E0382 (moved value)
|
|
2144
|
+
// at the closure's defining `let`.
|
|
2145
|
+
"parseInt",
|
|
2146
|
+
"parseFloat",
|
|
2147
|
+
"isNaN",
|
|
2148
|
+
"isFinite",
|
|
2149
|
+
"encodeURI",
|
|
2150
|
+
"decodeURI",
|
|
2151
|
+
"htmlEscape",
|
|
2152
|
+
"registerStaticRoute",
|
|
2153
|
+
"String",
|
|
2154
|
+
"Infinity",
|
|
2155
|
+
"NaN",
|
|
2156
|
+
"serve",
|
|
1857
2157
|
] {
|
|
1858
2158
|
if referenced.contains(*builtin) {
|
|
1859
2159
|
self.writeln(&format!("let {} = {}.clone();", builtin, builtin));
|
|
1860
2160
|
}
|
|
1861
2161
|
}
|
|
1862
|
-
self.writeln("Value::
|
|
2162
|
+
self.writeln("Value::native(move |args: &[Value]| {");
|
|
2163
|
+
self.value_fn_depth += 1;
|
|
1863
2164
|
self.indent += 1;
|
|
1864
2165
|
// Mutable outer vars: capture the RefCell so assignments use borrow_mut
|
|
1865
2166
|
for outer_var in &mutable_outer_vars {
|
|
@@ -1920,57 +2221,81 @@ impl Codegen {
|
|
|
1920
2221
|
}
|
|
1921
2222
|
if let Some(rest) = rest_param {
|
|
1922
2223
|
self.writeln(&format!(
|
|
1923
|
-
"let {} = Value::Array(
|
|
2224
|
+
"let {} = Value::Array(VmRef::new(args[{}..].to_vec()));",
|
|
1924
2225
|
Self::escape_ident(rest.name.as_ref()),
|
|
1925
2226
|
params.len()
|
|
1926
2227
|
));
|
|
1927
2228
|
}
|
|
1928
2229
|
|
|
1929
|
-
|
|
1930
|
-
|
|
2230
|
+
self.type_context
|
|
2231
|
+
.push_fun_param_scope(params, rest_param.as_ref());
|
|
1931
2232
|
|
|
1932
|
-
|
|
1933
|
-
|
|
2233
|
+
let fun_body_res: Result<(), CompileError> = (|| -> Result<(), CompileError> {
|
|
2234
|
+
// Push current params to stack for nested functions
|
|
2235
|
+
self.outer_params_stack.push(current_param_names);
|
|
1934
2236
|
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
for v in &mutable_outer_vars {
|
|
1938
|
-
self.refcell_wrapped_vars.insert(v.clone());
|
|
1939
|
-
}
|
|
2237
|
+
// Function bodies are sync closures (even Tish async fn) - use block_on for await
|
|
2238
|
+
self.async_context_stack.push(false);
|
|
1940
2239
|
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
// Create cells for nested functions
|
|
1946
|
-
for func_name in &nested_func_names {
|
|
1947
|
-
let escaped = Self::escape_ident(func_name);
|
|
1948
|
-
self.writeln(&format!(
|
|
1949
|
-
"let {}_cell: Rc<RefCell<Value>> = Rc::new(RefCell::new(Value::Null));",
|
|
1950
|
-
escaped
|
|
1951
|
-
));
|
|
2240
|
+
// Mutable outer vars must be in refcell_wrapped_vars so Assign/CompoundAssign emit borrow_mut
|
|
2241
|
+
let saved_refcell = self.refcell_wrapped_vars.clone();
|
|
2242
|
+
for v in &mutable_outer_vars {
|
|
2243
|
+
self.refcell_wrapped_vars.insert(v.clone());
|
|
1952
2244
|
}
|
|
1953
|
-
|
|
1954
|
-
|
|
2245
|
+
|
|
2246
|
+
// Pre-scan body for nested functions (handles function body as Block)
|
|
2247
|
+
if let Statement::Block { statements, .. } = body.as_ref() {
|
|
2248
|
+
let nested_func_names = self.prescan_function_decls(statements);
|
|
2249
|
+
self.function_scope_stack.push(nested_func_names.clone());
|
|
2250
|
+
self.outer_vars_stack.push(Vec::new());
|
|
2251
|
+
self.rc_cell_storage_scopes
|
|
2252
|
+
.push(std::collections::HashSet::new());
|
|
2253
|
+
// Create cells for nested functions
|
|
2254
|
+
for func_name in &nested_func_names {
|
|
2255
|
+
let escaped = Self::escape_ident(func_name);
|
|
2256
|
+
self.writeln(&format!(
|
|
2257
|
+
"let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
|
|
2258
|
+
escaped
|
|
2259
|
+
));
|
|
2260
|
+
}
|
|
2261
|
+
for s in statements {
|
|
2262
|
+
self.emit_statement(s)?;
|
|
2263
|
+
}
|
|
2264
|
+
self.function_scope_stack.pop();
|
|
2265
|
+
self.outer_vars_stack.pop();
|
|
2266
|
+
self.rc_cell_storage_scopes.pop();
|
|
2267
|
+
} else {
|
|
2268
|
+
self.function_scope_stack.push(Vec::new());
|
|
2269
|
+
self.outer_vars_stack.push(Vec::new());
|
|
2270
|
+
self.rc_cell_storage_scopes
|
|
2271
|
+
.push(std::collections::HashSet::new());
|
|
2272
|
+
self.emit_statement(body)?;
|
|
2273
|
+
self.function_scope_stack.pop();
|
|
2274
|
+
self.outer_vars_stack.pop();
|
|
2275
|
+
self.rc_cell_storage_scopes.pop();
|
|
1955
2276
|
}
|
|
1956
|
-
self.function_scope_stack.pop();
|
|
1957
|
-
} else {
|
|
1958
|
-
self.function_scope_stack.push(Vec::new());
|
|
1959
|
-
self.emit_statement(body)?;
|
|
1960
|
-
self.function_scope_stack.pop();
|
|
1961
|
-
}
|
|
1962
2277
|
|
|
1963
|
-
|
|
2278
|
+
self.async_context_stack.pop();
|
|
1964
2279
|
|
|
1965
|
-
|
|
1966
|
-
|
|
2280
|
+
// Restore refcell_wrapped_vars (remove mutable outer vars we added)
|
|
2281
|
+
self.refcell_wrapped_vars = saved_refcell;
|
|
1967
2282
|
|
|
1968
|
-
|
|
1969
|
-
|
|
2283
|
+
// Pop params stack
|
|
2284
|
+
self.outer_params_stack.pop();
|
|
2285
|
+
|
|
2286
|
+
Ok(())
|
|
2287
|
+
})();
|
|
2288
|
+
|
|
2289
|
+
self.type_context.pop_scope();
|
|
2290
|
+
if let Err(e) = fun_body_res {
|
|
2291
|
+
self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
|
|
2292
|
+
return Err(e);
|
|
2293
|
+
}
|
|
1970
2294
|
|
|
1971
2295
|
self.writeln("Value::Null");
|
|
1972
2296
|
self.indent -= 1;
|
|
1973
|
-
self.writeln("})
|
|
2297
|
+
self.writeln("})");
|
|
2298
|
+
self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
|
|
1974
2299
|
self.indent -= 1;
|
|
1975
2300
|
self.writeln("};");
|
|
1976
2301
|
// Update the cell with the actual function value
|
|
@@ -2048,7 +2373,7 @@ impl Codegen {
|
|
|
2048
2373
|
for (i, elem) in elements.iter().enumerate() {
|
|
2049
2374
|
if let Some(el) = elem {
|
|
2050
2375
|
match el {
|
|
2051
|
-
DestructElement::Ident(name) => {
|
|
2376
|
+
DestructElement::Ident(name, _) => {
|
|
2052
2377
|
self.writeln(&format!(
|
|
2053
2378
|
"{} {} = match &({}) {{ Value::Array(ref _a) => _a.borrow().get({}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
|
|
2054
2379
|
mutability,
|
|
@@ -2065,9 +2390,9 @@ impl Codegen {
|
|
|
2065
2390
|
));
|
|
2066
2391
|
self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
|
|
2067
2392
|
}
|
|
2068
|
-
DestructElement::Rest(name) => {
|
|
2393
|
+
DestructElement::Rest(name, _) => {
|
|
2069
2394
|
self.writeln(&format!(
|
|
2070
|
-
"{} {} = match &({}) {{ Value::Array(ref _a) => {{ let _b = _a.borrow(); Value::Array(
|
|
2395
|
+
"{} {} = match &({}) {{ Value::Array(ref _a) => {{ let _b = _a.borrow(); Value::Array(VmRef::new(_b.iter().skip({}).cloned().collect())) }}, _ => Value::Array(VmRef::new(Vec::new())) }};",
|
|
2071
2396
|
mutability,
|
|
2072
2397
|
Self::escape_ident(name.as_ref()),
|
|
2073
2398
|
value_expr,
|
|
@@ -2082,7 +2407,7 @@ impl Codegen {
|
|
|
2082
2407
|
for prop in props {
|
|
2083
2408
|
let key = prop.key.as_ref();
|
|
2084
2409
|
match &prop.value {
|
|
2085
|
-
DestructElement::Ident(name) => {
|
|
2410
|
+
DestructElement::Ident(name, _) => {
|
|
2086
2411
|
self.writeln(&format!(
|
|
2087
2412
|
"{} {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
|
|
2088
2413
|
mutability,
|
|
@@ -2099,7 +2424,7 @@ impl Codegen {
|
|
|
2099
2424
|
));
|
|
2100
2425
|
self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
|
|
2101
2426
|
}
|
|
2102
|
-
DestructElement::Rest(_) => {
|
|
2427
|
+
DestructElement::Rest(_, _) => {
|
|
2103
2428
|
return Err(CompileError::new(
|
|
2104
2429
|
"Rest in object destructuring not supported",
|
|
2105
2430
|
Some(span),
|
|
@@ -2112,6 +2437,47 @@ impl Codegen {
|
|
|
2112
2437
|
Ok(())
|
|
2113
2438
|
}
|
|
2114
2439
|
|
|
2440
|
+
/// Like `VarDecl` pushing onto `outer_vars_stack`, so nested `move` closures rebind
|
|
2441
|
+
/// destructured names via `_cell` / `.clone()` instead of moving `Value` multiple times.
|
|
2442
|
+
fn register_destruct_pattern_outer_vars(&mut self, pattern: &DestructPattern) {
|
|
2443
|
+
match pattern {
|
|
2444
|
+
DestructPattern::Array(elements) => {
|
|
2445
|
+
for el in elements.iter().flatten() {
|
|
2446
|
+
match el {
|
|
2447
|
+
DestructElement::Ident(name, _) => {
|
|
2448
|
+
if let Some(scope) = self.outer_vars_stack.last_mut() {
|
|
2449
|
+
scope.push(name.to_string());
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
DestructElement::Pattern(nested) => {
|
|
2453
|
+
self.register_destruct_pattern_outer_vars(nested);
|
|
2454
|
+
}
|
|
2455
|
+
DestructElement::Rest(name, _) => {
|
|
2456
|
+
if let Some(scope) = self.outer_vars_stack.last_mut() {
|
|
2457
|
+
scope.push(name.to_string());
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
DestructPattern::Object(props) => {
|
|
2464
|
+
for prop in props {
|
|
2465
|
+
match &prop.value {
|
|
2466
|
+
DestructElement::Ident(name, _) => {
|
|
2467
|
+
if let Some(scope) = self.outer_vars_stack.last_mut() {
|
|
2468
|
+
scope.push(name.to_string());
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
DestructElement::Pattern(nested) => {
|
|
2472
|
+
self.register_destruct_pattern_outer_vars(nested);
|
|
2473
|
+
}
|
|
2474
|
+
DestructElement::Rest(_, _) => {}
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2115
2481
|
fn emit_expr(&mut self, expr: &Expr) -> Result<String, CompileError> {
|
|
2116
2482
|
Ok(match expr {
|
|
2117
2483
|
Expr::Literal { value, .. } => match value {
|
|
@@ -2165,6 +2531,51 @@ impl Codegen {
|
|
|
2165
2531
|
}
|
|
2166
2532
|
}
|
|
2167
2533
|
Expr::Call { callee, args, .. } => {
|
|
2534
|
+
// Typed-struct shortcut for `JSON.stringify(typedValue)`.
|
|
2535
|
+
// When the single arg has a known native type that owns a
|
|
2536
|
+
// hand-rolled `_tish_write_json` (struct or `Vec<struct>`),
|
|
2537
|
+
// emit a direct write into a String buffer and skip the
|
|
2538
|
+
// entire `Value::Object` / `Value::Array` allocation
|
|
2539
|
+
// round-trip + the dynamic stringifier walk. Wraps the
|
|
2540
|
+
// result in `Value::String` for the caller, which is what
|
|
2541
|
+
// the existing `JSON.stringify` returned anyway.
|
|
2542
|
+
if let Expr::Member {
|
|
2543
|
+
object,
|
|
2544
|
+
prop: MemberProp::Name { name: method_name, .. },
|
|
2545
|
+
..
|
|
2546
|
+
} = callee.as_ref()
|
|
2547
|
+
{
|
|
2548
|
+
if method_name.as_ref() == "stringify"
|
|
2549
|
+
&& matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "JSON")
|
|
2550
|
+
{
|
|
2551
|
+
if args.len() == 1 {
|
|
2552
|
+
if let CallArg::Expr(arg) = &args[0] {
|
|
2553
|
+
let (arg_code, arg_ty) = self.emit_typed_expr(arg)?;
|
|
2554
|
+
match &arg_ty {
|
|
2555
|
+
crate::types::RustType::Named { .. } => {
|
|
2556
|
+
return Ok(format!(
|
|
2557
|
+
"{{ let mut _buf = String::with_capacity(128); ({})._tish_write_json(&mut _buf); Value::String(_buf.into()) }}",
|
|
2558
|
+
arg_code
|
|
2559
|
+
));
|
|
2560
|
+
}
|
|
2561
|
+
crate::types::RustType::Vec(inner)
|
|
2562
|
+
if matches!(
|
|
2563
|
+
inner.as_ref(),
|
|
2564
|
+
crate::types::RustType::Named { .. }
|
|
2565
|
+
) =>
|
|
2566
|
+
{
|
|
2567
|
+
return Ok(format!(
|
|
2568
|
+
"{{ let mut _buf = String::with_capacity(256); _buf.push('['); for (i, item) in ({}).iter().enumerate() {{ if i > 0 {{ _buf.push(','); }} item._tish_write_json(&mut _buf); }} _buf.push(']'); Value::String(_buf.into()) }}",
|
|
2569
|
+
arg_code
|
|
2570
|
+
));
|
|
2571
|
+
}
|
|
2572
|
+
_ => {}
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2168
2579
|
// Compile-time embed: Polars.read_csv("<literal path>") when file exists
|
|
2169
2580
|
if let Some(init) = self.native_module_init.get("tish:polars") {
|
|
2170
2581
|
let crate_name = match init {
|
|
@@ -2180,7 +2591,7 @@ impl Codegen {
|
|
|
2180
2591
|
{
|
|
2181
2592
|
if let Expr::Member {
|
|
2182
2593
|
object,
|
|
2183
|
-
prop: MemberProp::Name
|
|
2594
|
+
prop: MemberProp::Name { name: ref method_name, .. },
|
|
2184
2595
|
..
|
|
2185
2596
|
} = callee.as_ref()
|
|
2186
2597
|
{
|
|
@@ -2211,7 +2622,12 @@ impl Codegen {
|
|
|
2211
2622
|
}
|
|
2212
2623
|
|
|
2213
2624
|
// Check for built-in method calls on arrays/strings
|
|
2214
|
-
if let Expr::Member {
|
|
2625
|
+
if let Expr::Member {
|
|
2626
|
+
object,
|
|
2627
|
+
prop: MemberProp::Name { name: method_name, .. },
|
|
2628
|
+
..
|
|
2629
|
+
} = callee.as_ref()
|
|
2630
|
+
{
|
|
2215
2631
|
// ── native Vec<T> push fast path ──────────────────────────────
|
|
2216
2632
|
if method_name.as_ref() == "push" {
|
|
2217
2633
|
if let Expr::Ident { name, .. } = object.as_ref() {
|
|
@@ -2594,9 +3010,43 @@ impl Codegen {
|
|
|
2594
3010
|
optional,
|
|
2595
3011
|
..
|
|
2596
3012
|
} => {
|
|
3013
|
+
// Fast path: typed struct member access. If `object` is
|
|
3014
|
+
// a local with `RustType::Named { fields }` and `prop` is
|
|
3015
|
+
// a literal field name of that struct, lower to a direct
|
|
3016
|
+
// Rust field access (`obj.field`), then wrap in
|
|
3017
|
+
// `Value::*` so the caller gets a `Value` as expected.
|
|
3018
|
+
if !optional {
|
|
3019
|
+
if let (Expr::Ident { name: var_name, .. }, MemberProp::Name { name: prop_name, .. }) =
|
|
3020
|
+
(object.as_ref(), prop)
|
|
3021
|
+
{
|
|
3022
|
+
let var_type = self.type_context.get_type(var_name.as_ref());
|
|
3023
|
+
if let RustType::Named { fields, .. } = &var_type {
|
|
3024
|
+
if let Some((_, field_ty)) =
|
|
3025
|
+
fields.iter().find(|(k, _)| k.as_ref() == prop_name.as_ref())
|
|
3026
|
+
{
|
|
3027
|
+
let var_esc = Self::escape_ident(var_name.as_ref()).into_owned();
|
|
3028
|
+
let access = if self.refcell_wrapped_vars.contains(var_name.as_ref()) {
|
|
3029
|
+
format!(
|
|
3030
|
+
"(*{}.borrow()).{}.clone()",
|
|
3031
|
+
var_esc,
|
|
3032
|
+
crate::types::field_ident(prop_name.as_ref())
|
|
3033
|
+
)
|
|
3034
|
+
} else {
|
|
3035
|
+
format!(
|
|
3036
|
+
"{}.{}",
|
|
3037
|
+
var_esc,
|
|
3038
|
+
crate::types::field_ident(prop_name.as_ref())
|
|
3039
|
+
)
|
|
3040
|
+
};
|
|
3041
|
+
// Caller expects a `Value`; wrap.
|
|
3042
|
+
return Ok(field_ty.to_value_expr(&access));
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
2597
3047
|
let obj = self.emit_expr(object)?;
|
|
2598
3048
|
let key = match prop {
|
|
2599
|
-
MemberProp::Name
|
|
3049
|
+
MemberProp::Name { name, .. } => format!("{:?}", name.as_ref()),
|
|
2600
3050
|
MemberProp::Expr(e) => {
|
|
2601
3051
|
let k = self.emit_expr(e)?;
|
|
2602
3052
|
format!("{}.to_display_string()", k)
|
|
@@ -2670,7 +3120,7 @@ impl Codegen {
|
|
|
2670
3120
|
}
|
|
2671
3121
|
}
|
|
2672
3122
|
}
|
|
2673
|
-
format!("{{ let mut _arr: Vec<Value> = Vec::new(); {} Value::Array(
|
|
3123
|
+
format!("{{ let mut _arr: Vec<Value> = Vec::new(); {} Value::Array(VmRef::new(_arr)) }}", parts.join(" "))
|
|
2674
3124
|
} else {
|
|
2675
3125
|
let mut els = Vec::new();
|
|
2676
3126
|
for elem in elements {
|
|
@@ -2689,7 +3139,7 @@ impl Codegen {
|
|
|
2689
3139
|
}
|
|
2690
3140
|
}
|
|
2691
3141
|
format!(
|
|
2692
|
-
"Value::Array(
|
|
3142
|
+
"Value::Array(VmRef::new(vec![{}]))",
|
|
2693
3143
|
els.join(", ")
|
|
2694
3144
|
)
|
|
2695
3145
|
}
|
|
@@ -2714,7 +3164,7 @@ impl Codegen {
|
|
|
2714
3164
|
}
|
|
2715
3165
|
}
|
|
2716
3166
|
}
|
|
2717
|
-
format!("{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::Object(
|
|
3167
|
+
format!("{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::Object(VmRef::new(_obj)) }}", parts.join(" "))
|
|
2718
3168
|
} else {
|
|
2719
3169
|
let mut parts = Vec::new();
|
|
2720
3170
|
for prop in props {
|
|
@@ -2728,7 +3178,7 @@ impl Codegen {
|
|
|
2728
3178
|
}
|
|
2729
3179
|
}
|
|
2730
3180
|
format!(
|
|
2731
|
-
"Value::Object(
|
|
3181
|
+
"Value::Object(VmRef::new(ObjectMap::from([{}])))",
|
|
2732
3182
|
parts.join(", ")
|
|
2733
3183
|
)
|
|
2734
3184
|
}
|
|
@@ -3149,9 +3599,12 @@ impl Codegen {
|
|
|
3149
3599
|
format!("Value::String([{}].concat().into())", parts.join(", "))
|
|
3150
3600
|
}
|
|
3151
3601
|
Expr::JsxElement { .. } | Expr::JsxFragment { .. } => {
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3602
|
+
let fun_decls = self.program_fun_decl_names.clone();
|
|
3603
|
+
tishlang_ui::jsx::emit_jsx_rust(
|
|
3604
|
+
expr,
|
|
3605
|
+
&mut |e| self.emit_expr(e).map_err(|ce| ce.message),
|
|
3606
|
+
&fun_decls,
|
|
3607
|
+
)
|
|
3155
3608
|
.map_err(|m| CompileError::new(m, None))?
|
|
3156
3609
|
}
|
|
3157
3610
|
Expr::New { callee, args, .. } => {
|
|
@@ -3448,7 +3901,10 @@ impl Codegen {
|
|
|
3448
3901
|
Statement::Break { .. }
|
|
3449
3902
|
| Statement::Continue { .. }
|
|
3450
3903
|
| Statement::Import { .. }
|
|
3451
|
-
| Statement::Export { .. }
|
|
3904
|
+
| Statement::Export { .. }
|
|
3905
|
+
| Statement::TypeAlias { .. }
|
|
3906
|
+
| Statement::DeclareVar { .. }
|
|
3907
|
+
| Statement::DeclareFun { .. } => {}
|
|
3452
3908
|
}
|
|
3453
3909
|
}
|
|
3454
3910
|
|
|
@@ -3964,7 +4420,7 @@ impl Codegen {
|
|
|
3964
4420
|
match pattern {
|
|
3965
4421
|
DestructPattern::Array(elements) => {
|
|
3966
4422
|
for el in elements {
|
|
3967
|
-
if let Some(DestructElement::Ident(n)) = el {
|
|
4423
|
+
if let Some(DestructElement::Ident(n, _)) = el {
|
|
3968
4424
|
names.insert(n.to_string());
|
|
3969
4425
|
}
|
|
3970
4426
|
if let Some(DestructElement::Pattern(p)) = el {
|
|
@@ -3975,11 +4431,11 @@ impl Codegen {
|
|
|
3975
4431
|
DestructPattern::Object(props) => {
|
|
3976
4432
|
for prop in props {
|
|
3977
4433
|
match &prop.value {
|
|
3978
|
-
DestructElement::Ident(n) => {
|
|
4434
|
+
DestructElement::Ident(n, _) => {
|
|
3979
4435
|
names.insert(n.to_string());
|
|
3980
4436
|
}
|
|
3981
4437
|
DestructElement::Pattern(p) => Self::collect_destruct_names(p, names),
|
|
3982
|
-
DestructElement::Rest(n) => {
|
|
4438
|
+
DestructElement::Rest(n, _) => {
|
|
3983
4439
|
names.insert(n.to_string());
|
|
3984
4440
|
}
|
|
3985
4441
|
}
|
|
@@ -4085,7 +4541,10 @@ impl Codegen {
|
|
|
4085
4541
|
Statement::Break { .. }
|
|
4086
4542
|
| Statement::Continue { .. }
|
|
4087
4543
|
| Statement::Import { .. }
|
|
4088
|
-
| Statement::Export { .. }
|
|
4544
|
+
| Statement::Export { .. }
|
|
4545
|
+
| Statement::TypeAlias { .. }
|
|
4546
|
+
| Statement::DeclareVar { .. }
|
|
4547
|
+
| Statement::DeclareFun { .. } => {}
|
|
4089
4548
|
}
|
|
4090
4549
|
}
|
|
4091
4550
|
|
|
@@ -4133,6 +4592,25 @@ impl Codegen {
|
|
|
4133
4592
|
&& !param_names.contains(name)
|
|
4134
4593
|
&& !local_var_names.contains(name)
|
|
4135
4594
|
})
|
|
4595
|
+
.filter(|name| {
|
|
4596
|
+
![
|
|
4597
|
+
"Boolean",
|
|
4598
|
+
"console",
|
|
4599
|
+
"Math",
|
|
4600
|
+
"JSON",
|
|
4601
|
+
"Date",
|
|
4602
|
+
"Object",
|
|
4603
|
+
"process",
|
|
4604
|
+
"setTimeout",
|
|
4605
|
+
"clearTimeout",
|
|
4606
|
+
"setInterval",
|
|
4607
|
+
"clearInterval",
|
|
4608
|
+
"Promise",
|
|
4609
|
+
"RegExp",
|
|
4610
|
+
"Polars",
|
|
4611
|
+
]
|
|
4612
|
+
.contains(&name.as_str())
|
|
4613
|
+
})
|
|
4136
4614
|
.collect();
|
|
4137
4615
|
|
|
4138
4616
|
// Outer vars that are assigned in the body need RefCell; read-only get Value binding
|
|
@@ -4141,14 +4619,16 @@ impl Codegen {
|
|
|
4141
4619
|
ArrowBody::Expr(e) => Self::collect_assigned_idents_in_expr(e, &mut assigned_in_body),
|
|
4142
4620
|
ArrowBody::Block(s) => Self::collect_assigned_idents_in_stmt(s, &mut assigned_in_body),
|
|
4143
4621
|
}
|
|
4144
|
-
|
|
4622
|
+
// Live cell capture: assigned here, or already `Rc<RefCell<Value>>` in a parent scope
|
|
4623
|
+
// (cleanups may only read `timer2` but must see updates from nested callbacks).
|
|
4624
|
+
let cell_capture_outer_vars: Vec<String> = outer_vars
|
|
4145
4625
|
.iter()
|
|
4146
|
-
.filter(|v| assigned_in_body.contains(*v))
|
|
4626
|
+
.filter(|v| assigned_in_body.contains(*v) || self.rc_cell_storage_contains(*v))
|
|
4147
4627
|
.cloned()
|
|
4148
4628
|
.collect();
|
|
4149
4629
|
let read_only_outer_vars: Vec<String> = outer_vars
|
|
4150
4630
|
.iter()
|
|
4151
|
-
.filter(|v| !assigned_in_body.contains(*v))
|
|
4631
|
+
.filter(|v| !assigned_in_body.contains(*v) && !self.rc_cell_storage_contains(*v))
|
|
4152
4632
|
.cloned()
|
|
4153
4633
|
.collect();
|
|
4154
4634
|
|
|
@@ -4164,7 +4644,7 @@ impl Codegen {
|
|
|
4164
4644
|
));
|
|
4165
4645
|
} else {
|
|
4166
4646
|
code.push_str(&format!(
|
|
4167
|
-
" let {} =
|
|
4647
|
+
" let {} = VmRef::new({}.clone());\n",
|
|
4168
4648
|
ref_name, param_escaped
|
|
4169
4649
|
));
|
|
4170
4650
|
}
|
|
@@ -4179,7 +4659,7 @@ impl Codegen {
|
|
|
4179
4659
|
));
|
|
4180
4660
|
} else {
|
|
4181
4661
|
code.push_str(&format!(
|
|
4182
|
-
" let {} =
|
|
4662
|
+
" let {} = VmRef::new({}.clone());\n",
|
|
4183
4663
|
ref_name, var_escaped
|
|
4184
4664
|
));
|
|
4185
4665
|
}
|
|
@@ -4190,11 +4670,14 @@ impl Codegen {
|
|
|
4190
4670
|
"Math",
|
|
4191
4671
|
"JSON",
|
|
4192
4672
|
"Date",
|
|
4673
|
+
"Object",
|
|
4193
4674
|
"Uint8Array",
|
|
4194
4675
|
"AudioContext",
|
|
4195
4676
|
"process",
|
|
4196
4677
|
"setTimeout",
|
|
4197
4678
|
"clearTimeout",
|
|
4679
|
+
"setInterval",
|
|
4680
|
+
"clearInterval",
|
|
4198
4681
|
"Promise",
|
|
4199
4682
|
"RegExp",
|
|
4200
4683
|
"Polars",
|
|
@@ -4224,7 +4707,8 @@ impl Codegen {
|
|
|
4224
4707
|
));
|
|
4225
4708
|
}
|
|
4226
4709
|
|
|
4227
|
-
code.push_str(" Value::
|
|
4710
|
+
code.push_str(" Value::native(move |args: &[Value]| {\n");
|
|
4711
|
+
self.value_fn_depth += 1;
|
|
4228
4712
|
|
|
4229
4713
|
// Make captured outer params available as plain Values (from _ref RefCells)
|
|
4230
4714
|
for outer_param in &outer_params {
|
|
@@ -4234,15 +4718,15 @@ impl Codegen {
|
|
|
4234
4718
|
param_escaped, param_escaped
|
|
4235
4719
|
));
|
|
4236
4720
|
}
|
|
4237
|
-
//
|
|
4238
|
-
for outer_var in &
|
|
4721
|
+
// Outer vars that share a RefCell with the parent: capture the cell (read + write)
|
|
4722
|
+
for outer_var in &cell_capture_outer_vars {
|
|
4239
4723
|
let var_escaped = Self::escape_ident(outer_var);
|
|
4240
4724
|
code.push_str(&format!(
|
|
4241
4725
|
" let {} = {}_ref.clone();\n",
|
|
4242
4726
|
var_escaped, var_escaped
|
|
4243
4727
|
));
|
|
4244
4728
|
}
|
|
4245
|
-
// Read-only outer vars: Value
|
|
4729
|
+
// Read-only outer vars: snapshot Value at closure creation
|
|
4246
4730
|
for outer_var in &read_only_outer_vars {
|
|
4247
4731
|
let var_escaped = Self::escape_ident(outer_var);
|
|
4248
4732
|
code.push_str(&format!(
|
|
@@ -4297,17 +4781,19 @@ impl Codegen {
|
|
|
4297
4781
|
// Push empty scope for variables declared inside this arrow function
|
|
4298
4782
|
self.outer_vars_stack.push(Vec::new());
|
|
4299
4783
|
|
|
4300
|
-
//
|
|
4784
|
+
// Cell-backed outer vars need refcell_wrapped_vars for Assign and for reads in emit_expr
|
|
4301
4785
|
let saved_refcell_vars = self.refcell_wrapped_vars.clone();
|
|
4302
|
-
for v in &
|
|
4786
|
+
for v in &cell_capture_outer_vars {
|
|
4303
4787
|
self.refcell_wrapped_vars.insert(v.clone());
|
|
4304
4788
|
}
|
|
4305
4789
|
|
|
4306
|
-
|
|
4307
|
-
|
|
4790
|
+
self.type_context.push_fun_param_scope(params, None);
|
|
4791
|
+
|
|
4792
|
+
let arrow_body_res: Result<(), CompileError> = match body {
|
|
4308
4793
|
tishlang_ast::ArrowBody::Expr(expr) => {
|
|
4309
4794
|
let expr_code = self.emit_expr(expr)?;
|
|
4310
4795
|
code.push_str(&format!(" {}\n", expr_code));
|
|
4796
|
+
Ok(())
|
|
4311
4797
|
}
|
|
4312
4798
|
tishlang_ast::ArrowBody::Block(block_stmt) => {
|
|
4313
4799
|
// For block bodies, emit the block statement
|
|
@@ -4326,15 +4812,24 @@ impl Codegen {
|
|
|
4326
4812
|
|
|
4327
4813
|
code.push_str(&body_code);
|
|
4328
4814
|
code.push_str(" Value::Null\n");
|
|
4815
|
+
Ok(())
|
|
4329
4816
|
}
|
|
4817
|
+
};
|
|
4818
|
+
|
|
4819
|
+
self.type_context.pop_scope();
|
|
4820
|
+
if let Err(e) = arrow_body_res {
|
|
4821
|
+
self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
|
|
4822
|
+
return Err(e);
|
|
4330
4823
|
}
|
|
4331
4824
|
|
|
4825
|
+
self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
|
|
4826
|
+
|
|
4332
4827
|
// Restore state
|
|
4333
4828
|
self.refcell_wrapped_vars = saved_refcell_vars;
|
|
4334
4829
|
self.outer_params_stack.pop();
|
|
4335
4830
|
self.outer_vars_stack.pop();
|
|
4336
4831
|
|
|
4337
|
-
code.push_str(" })
|
|
4832
|
+
code.push_str(" })\n");
|
|
4338
4833
|
code.push('}');
|
|
4339
4834
|
|
|
4340
4835
|
Ok(code)
|
|
@@ -4386,6 +4881,57 @@ impl Codegen {
|
|
|
4386
4881
|
return Ok(format!("vec![{}]", items.join(", ")));
|
|
4387
4882
|
}
|
|
4388
4883
|
|
|
4884
|
+
// Try to emit object literals directly as a Rust struct literal
|
|
4885
|
+
// when the target is a `RustType::Named` (a user `type Foo = {...}`
|
|
4886
|
+
// alias). Each property in source order is matched to a struct
|
|
4887
|
+
// field; missing fields fall back to `default_value()` so the
|
|
4888
|
+
// emit succeeds even on partial literals (rare, but harmless).
|
|
4889
|
+
if let (RustType::Named { name, fields }, Expr::Object { props, .. }) =
|
|
4890
|
+
(target_type, expr)
|
|
4891
|
+
{
|
|
4892
|
+
use std::collections::HashMap;
|
|
4893
|
+
let field_types: HashMap<&str, &RustType> = fields
|
|
4894
|
+
.iter()
|
|
4895
|
+
.map(|(k, t)| (k.as_ref(), t))
|
|
4896
|
+
.collect();
|
|
4897
|
+
let mut field_inits: HashMap<String, String> = HashMap::new();
|
|
4898
|
+
let mut bail = false;
|
|
4899
|
+
for prop in props {
|
|
4900
|
+
match prop {
|
|
4901
|
+
ObjectProp::KeyValue(key, value) => {
|
|
4902
|
+
if let Some(field_ty) = field_types.get(key.as_ref()) {
|
|
4903
|
+
let v = self.emit_native_expr(value, field_ty)?;
|
|
4904
|
+
field_inits.insert(crate::types::field_ident(key.as_ref()), v);
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
// Spread can't be statically matched to struct fields:
|
|
4908
|
+
// fall back to the dynamic Value path.
|
|
4909
|
+
ObjectProp::Spread(_) => {
|
|
4910
|
+
bail = true;
|
|
4911
|
+
break;
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
if !bail {
|
|
4916
|
+
let assigns = fields
|
|
4917
|
+
.iter()
|
|
4918
|
+
.map(|(k, t)| {
|
|
4919
|
+
let fid = crate::types::field_ident(k);
|
|
4920
|
+
match field_inits.remove(&fid) {
|
|
4921
|
+
Some(v) => format!("{}: {}", fid, v),
|
|
4922
|
+
None => format!("{}: {}", fid, t.default_value()),
|
|
4923
|
+
}
|
|
4924
|
+
})
|
|
4925
|
+
.collect::<Vec<_>>()
|
|
4926
|
+
.join(", ");
|
|
4927
|
+
return Ok(format!(
|
|
4928
|
+
"{} {{ {} }}",
|
|
4929
|
+
crate::types::named_struct_ident(name),
|
|
4930
|
+
assigns
|
|
4931
|
+
));
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
|
|
4389
4935
|
// Check if the identifier is already of the target type
|
|
4390
4936
|
if let Expr::Ident { name, .. } = expr {
|
|
4391
4937
|
let var_type = self.type_context.get_type(name.as_ref());
|