@tishlang/tish 1.0.29 → 1.0.34
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/crates/js_to_tish/src/transform/expr.rs +15 -6
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/main.rs +1 -1
- package/crates/tish/tests/integration_test.rs +4 -3
- package/crates/tish_ast/src/ast.rs +65 -2
- package/crates/tish_build_utils/src/lib.rs +10 -2
- package/crates/tish_builtins/src/construct.rs +177 -0
- package/crates/tish_builtins/src/globals.rs +3 -5
- package/crates/tish_builtins/src/helpers.rs +2 -3
- package/crates/tish_builtins/src/lib.rs +1 -0
- package/crates/tish_builtins/src/object.rs +3 -4
- package/crates/tish_bytecode/src/compiler.rs +85 -11
- package/crates/tish_bytecode/src/opcode.rs +7 -3
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +604 -106
- package/crates/tish_compile/src/infer.rs +236 -0
- package/crates/tish_compile/src/lib.rs +52 -5
- package/crates/tish_compile/src/types.rs +42 -5
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +38 -94
- package/crates/tish_compile_js/src/lib.rs +0 -1
- package/crates/tish_compile_js/src/tests_jsx.rs +68 -0
- package/crates/tish_core/Cargo.toml +4 -0
- package/crates/tish_core/src/console_style.rs +7 -1
- package/crates/tish_core/src/json.rs +1 -2
- package/crates/tish_core/src/macros.rs +2 -3
- package/crates/tish_core/src/value.rs +13 -5
- package/crates/tish_cranelift/src/lib.rs +6 -4
- package/crates/tish_cranelift/src/lower.rs +5 -3
- package/crates/tish_cranelift_runtime/src/lib.rs +4 -2
- package/crates/tish_eval/Cargo.toml +2 -0
- package/crates/tish_eval/src/eval.rs +172 -79
- package/crates/tish_eval/src/http.rs +3 -4
- package/crates/tish_eval/src/lib.rs +7 -0
- package/crates/tish_eval/src/regex.rs +3 -2
- package/crates/tish_eval/src/value.rs +11 -13
- package/crates/tish_eval/src/value_convert.rs +4 -8
- package/crates/tish_fmt/src/lib.rs +49 -10
- package/crates/tish_lexer/src/token.rs +2 -0
- package/crates/tish_lint/src/lib.rs +9 -0
- package/crates/tish_llvm/src/lib.rs +4 -4
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_native/src/build.rs +16 -2
- package/crates/tish_native/src/lib.rs +10 -11
- package/crates/tish_opt/src/lib.rs +15 -0
- package/crates/tish_parser/src/lib.rs +101 -1
- package/crates/tish_parser/src/parser.rs +168 -51
- package/crates/tish_runtime/src/http.rs +4 -5
- package/crates/tish_runtime/src/http_fetch.rs +17 -10
- package/crates/tish_runtime/src/lib.rs +9 -2
- package/crates/tish_runtime/src/promise.rs +2 -3
- package/crates/tish_runtime/src/promise_io.rs +2 -3
- package/crates/tish_runtime/src/ws.rs +7 -7
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +390 -0
- package/crates/tish_ui/src/lib.rs +16 -0
- package/crates/tish_ui/src/runtime/hooks.rs +122 -0
- package/crates/tish_ui/src/runtime/mod.rs +173 -0
- package/crates/tish_vm/src/vm.rs +121 -27
- package/justfile +3 -3
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
- package/crates/tish_compile_js/src/js_intrinsics.rs +0 -82
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
use std::borrow::Cow;
|
|
4
4
|
use std::collections::{HashMap, HashSet};
|
|
5
5
|
use std::path::Path;
|
|
6
|
-
use tishlang_ast::{ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern, Expr, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement, UnaryOp};
|
|
6
|
+
use tishlang_ast::{ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern, Expr, FunParam, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement, UnaryOp};
|
|
7
7
|
use crate::resolve::is_builtin_native_spec;
|
|
8
8
|
use crate::types::{RustType, TypeContext};
|
|
9
9
|
|
|
@@ -120,6 +120,14 @@ impl UsageAnalyzer {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
Expr::New { callee, args, .. } => {
|
|
124
|
+
self.analyze_expr(callee);
|
|
125
|
+
for arg in args {
|
|
126
|
+
match arg {
|
|
127
|
+
CallArg::Expr(e) | CallArg::Spread(e) => self.analyze_expr(e),
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
123
131
|
Expr::Member { object, prop, .. } => {
|
|
124
132
|
self.analyze_expr(object);
|
|
125
133
|
if let MemberProp::Expr(e) = prop {
|
|
@@ -283,6 +291,11 @@ fn program_uses_async(program: &Program) -> bool {
|
|
|
283
291
|
CallArg::Expr(e) | CallArg::Spread(e) => expr_has_await(e),
|
|
284
292
|
})
|
|
285
293
|
}
|
|
294
|
+
Expr::New { callee, args, .. } => {
|
|
295
|
+
expr_has_await(callee) || args.iter().any(|a| match a {
|
|
296
|
+
CallArg::Expr(e) | CallArg::Spread(e) => expr_has_await(e),
|
|
297
|
+
})
|
|
298
|
+
}
|
|
286
299
|
Expr::Member { object, prop, .. } => {
|
|
287
300
|
expr_has_await(object)
|
|
288
301
|
|| if let MemberProp::Expr(e) = prop {
|
|
@@ -430,6 +443,9 @@ pub fn compile_with_native_modules(
|
|
|
430
443
|
optimize: bool,
|
|
431
444
|
) -> Result<String, CompileError> {
|
|
432
445
|
let program = if optimize { tishlang_opt::optimize(program) } else { program.clone() };
|
|
446
|
+
// Type-inference pass: fills in `type_ann` on unannotated VarDecl nodes where
|
|
447
|
+
// the type is unambiguous (literals, arithmetic of typed vars, etc.).
|
|
448
|
+
let program = crate::infer::infer_program(&program);
|
|
433
449
|
let map: std::collections::HashMap<String, (String, String)> = native_modules
|
|
434
450
|
.iter()
|
|
435
451
|
.map(|m| (m.spec.clone(), (m.crate_name.clone(), m.export_fn.clone())))
|
|
@@ -468,6 +484,8 @@ struct Codegen {
|
|
|
468
484
|
usage_analyzer: Option<UsageAnalyzer>,
|
|
469
485
|
/// Type context for tracking variable types (for static typing)
|
|
470
486
|
type_context: TypeContext,
|
|
487
|
+
/// Program uses JSX; emit `tishlang_ui` imports and `h` / `Fragment` globals.
|
|
488
|
+
program_has_jsx: bool,
|
|
471
489
|
}
|
|
472
490
|
|
|
473
491
|
impl Codegen {
|
|
@@ -493,6 +511,7 @@ impl Codegen {
|
|
|
493
511
|
refcell_wrapped_vars: std::collections::HashSet::new(),
|
|
494
512
|
usage_analyzer: None,
|
|
495
513
|
type_context: TypeContext::new(),
|
|
514
|
+
program_has_jsx: false,
|
|
496
515
|
}
|
|
497
516
|
}
|
|
498
517
|
|
|
@@ -503,7 +522,12 @@ impl Codegen {
|
|
|
503
522
|
return self.builtin_native_module_rust_init(spec, export_name);
|
|
504
523
|
}
|
|
505
524
|
self.native_module_map.get(spec).map(|(crate_name, export_fn)| {
|
|
506
|
-
|
|
525
|
+
// Native modules return a namespace object (like an ES module).
|
|
526
|
+
// Named imports extract the field from that namespace: `import { foo } from "pkg"` → `ns.foo`.
|
|
527
|
+
format!(
|
|
528
|
+
"{{ let _ns = {}::{}(); match _ns {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }} }}",
|
|
529
|
+
crate_name, export_fn, export_name
|
|
530
|
+
)
|
|
507
531
|
})
|
|
508
532
|
}
|
|
509
533
|
|
|
@@ -536,7 +560,7 @@ impl Codegen {
|
|
|
536
560
|
"exec" => Some("Value::Function(Rc::new(|args: &[Value]| tish_process_exec(args)))"),
|
|
537
561
|
"argv" => Some("Value::Array(Rc::new(RefCell::new(std::env::args().map(|s| Value::String(s.into())).collect())))"),
|
|
538
562
|
"env" => Some("Value::Object(Rc::new(RefCell::new(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect())))"),
|
|
539
|
-
"process" => Some("{ let mut m =
|
|
563
|
+
"process" => Some("{ let mut m = ObjectMap::default(); m.insert(Arc::from(\"exit\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exit(args)))); m.insert(Arc::from(\"cwd\"), Value::Function(Rc::new(|args: &[Value]| tish_process_cwd(args)))); m.insert(Arc::from(\"exec\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exec(args)))); m.insert(Arc::from(\"argv\"), Value::Array(Rc::new(RefCell::new(std::env::args().map(|s| Value::String(s.into())).collect())))); m.insert(Arc::from(\"env\"), Value::Object(Rc::new(RefCell::new(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect::<ObjectMap>())))); Value::Object(Rc::new(RefCell::new(m))) }"),
|
|
540
564
|
_ => None,
|
|
541
565
|
},
|
|
542
566
|
"tish:ws" if self.has_feature("ws") => match export_name {
|
|
@@ -634,6 +658,7 @@ impl Codegen {
|
|
|
634
658
|
| Expr::Array { .. }
|
|
635
659
|
| Expr::Object { .. }
|
|
636
660
|
| Expr::Call { .. }
|
|
661
|
+
| Expr::New { .. }
|
|
637
662
|
| Expr::Await { .. }
|
|
638
663
|
| Expr::ArrowFunction { .. }
|
|
639
664
|
| Expr::Binary { .. }
|
|
@@ -684,7 +709,18 @@ impl Codegen {
|
|
|
684
709
|
fn emit_inc_dec(&self, name: &str, is_prefix: bool, delta: &str, op_name: &str) -> String {
|
|
685
710
|
let n = Self::escape_ident(name);
|
|
686
711
|
let is_wrapped = self.refcell_wrapped_vars.contains(name);
|
|
687
|
-
|
|
712
|
+
let var_type = self.type_context.get_type(name);
|
|
713
|
+
|
|
714
|
+
// Native fast path: f64 variable → avoid boxing/unboxing.
|
|
715
|
+
if !is_wrapped && var_type == RustType::F64 {
|
|
716
|
+
let op_assign = if delta.contains('+') { "+=" } else { "-=" };
|
|
717
|
+
return if is_prefix {
|
|
718
|
+
format!("{{ {n} {op_assign} 1.0_f64; Value::Number({n}) }}")
|
|
719
|
+
} else {
|
|
720
|
+
format!("{{ let _prev = {n}; {n} {op_assign} 1.0_f64; Value::Number(_prev) }}")
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
688
724
|
if is_prefix {
|
|
689
725
|
if is_wrapped {
|
|
690
726
|
format!(
|
|
@@ -695,16 +731,14 @@ impl Codegen {
|
|
|
695
731
|
"{{ {n} = Value::Number(match &{n} {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); {n}.clone() }}"
|
|
696
732
|
)
|
|
697
733
|
}
|
|
734
|
+
} else if is_wrapped {
|
|
735
|
+
format!(
|
|
736
|
+
"{{ let _v = (*{n}.borrow()).clone(); *{n}.borrow_mut() = Value::Number(match &_v {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); _v }}"
|
|
737
|
+
)
|
|
698
738
|
} else {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
)
|
|
703
|
-
} else {
|
|
704
|
-
format!(
|
|
705
|
-
"{{ let _v = {n}.clone(); {n} = Value::Number(match &_v {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); _v }}"
|
|
706
|
-
)
|
|
707
|
-
}
|
|
739
|
+
format!(
|
|
740
|
+
"{{ let _v = {n}.clone(); {n} = Value::Number(match &_v {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); _v }}"
|
|
741
|
+
)
|
|
708
742
|
}
|
|
709
743
|
}
|
|
710
744
|
|
|
@@ -727,12 +761,17 @@ impl Codegen {
|
|
|
727
761
|
use tishlang_ast::ArrowBody;
|
|
728
762
|
|
|
729
763
|
if let Expr::ArrowFunction { params, body, .. } = expr {
|
|
730
|
-
// Must have exactly 2 params
|
|
731
764
|
if params.len() != 2 {
|
|
732
765
|
return None;
|
|
733
766
|
}
|
|
734
|
-
let param_a = params[0]
|
|
735
|
-
|
|
767
|
+
let (param_a, param_b) = match (¶ms[0], ¶ms[1]) {
|
|
768
|
+
(FunParam::Simple(a), FunParam::Simple(b))
|
|
769
|
+
if a.default.is_none() && b.default.is_none() =>
|
|
770
|
+
{
|
|
771
|
+
(a.name.as_ref(), b.name.as_ref())
|
|
772
|
+
}
|
|
773
|
+
_ => return None,
|
|
774
|
+
};
|
|
736
775
|
|
|
737
776
|
// Body must be a single expression that's a subtraction
|
|
738
777
|
let body_expr = match body {
|
|
@@ -763,12 +802,15 @@ impl Codegen {
|
|
|
763
802
|
|
|
764
803
|
fn emit_program(&mut self, program: &Program) -> Result<(), CompileError> {
|
|
765
804
|
self.is_async = program_uses_async(program);
|
|
805
|
+
self.program_has_jsx = tishlang_ui::jsx::program_contains_jsx(program);
|
|
766
806
|
self.write("#![allow(unused, non_snake_case)]\n\n");
|
|
767
807
|
self.write("use std::cell::RefCell;\n");
|
|
768
|
-
self.write("use std::collections::HashMap;\n");
|
|
769
808
|
self.write("use std::rc::Rc;\n");
|
|
770
809
|
self.write("use std::sync::Arc;\n");
|
|
771
|
-
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, TishError, Value};\n");
|
|
810
|
+
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");
|
|
811
|
+
if self.program_has_jsx {
|
|
812
|
+
self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
|
|
813
|
+
}
|
|
772
814
|
if self.has_feature("process") {
|
|
773
815
|
self.write("use tishlang_runtime::{process_exit as tish_process_exit, process_cwd as tish_process_cwd, process_exec as tish_process_exec};\n");
|
|
774
816
|
}
|
|
@@ -818,7 +860,7 @@ impl Codegen {
|
|
|
818
860
|
self.indent += 1;
|
|
819
861
|
|
|
820
862
|
// Initialize builtins
|
|
821
|
-
self.writeln("let mut console = Value::Object(Rc::new(RefCell::new(
|
|
863
|
+
self.writeln("let mut console = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
|
|
822
864
|
self.indent += 1;
|
|
823
865
|
self.writeln("(Arc::from(\"debug\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_debug(args); Value::Null }))),");
|
|
824
866
|
self.writeln("(Arc::from(\"info\"), Value::Function(Rc::new(|args: &[Value]| { tish_console_info(args); Value::Null }))),");
|
|
@@ -836,7 +878,7 @@ impl Codegen {
|
|
|
836
878
|
self.writeln("let isNaN = Value::Function(Rc::new(|args: &[Value]| tish_is_nan(args)));");
|
|
837
879
|
self.writeln("let Infinity = Value::Number(f64::INFINITY);");
|
|
838
880
|
self.writeln("let NaN = Value::Number(f64::NAN);");
|
|
839
|
-
self.writeln("let Math = Value::Object(Rc::new(RefCell::new(
|
|
881
|
+
self.writeln("let Math = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
|
|
840
882
|
self.indent += 1;
|
|
841
883
|
self.writeln("(Arc::from(\"abs\"), Value::Function(Rc::new(|args: &[Value]| tish_math_abs(args)))),");
|
|
842
884
|
self.writeln("(Arc::from(\"sqrt\"), Value::Function(Rc::new(|args: &[Value]| tish_math_sqrt(args)))),");
|
|
@@ -858,32 +900,32 @@ impl Codegen {
|
|
|
858
900
|
self.writeln("(Arc::from(\"E\"), Value::Number(std::f64::consts::E)),");
|
|
859
901
|
self.indent -= 1;
|
|
860
902
|
self.writeln("]))));");
|
|
861
|
-
self.writeln("let JSON = Value::Object(Rc::new(RefCell::new(
|
|
903
|
+
self.writeln("let JSON = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
|
|
862
904
|
self.indent += 1;
|
|
863
905
|
self.writeln("(Arc::from(\"parse\"), Value::Function(Rc::new(|args: &[Value]| tish_json_parse(args)))),");
|
|
864
906
|
self.writeln("(Arc::from(\"stringify\"), Value::Function(Rc::new(|args: &[Value]| tish_json_stringify(args)))),");
|
|
865
907
|
self.indent -= 1;
|
|
866
908
|
self.writeln("]))));");
|
|
867
909
|
|
|
868
|
-
self.writeln("let Array = Value::Object(Rc::new(RefCell::new(
|
|
910
|
+
self.writeln("let Array = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
|
|
869
911
|
self.indent += 1;
|
|
870
912
|
self.writeln("(Arc::from(\"isArray\"), Value::Function(Rc::new(|args: &[Value]| tish_array_is_array(args)))),");
|
|
871
913
|
self.indent -= 1;
|
|
872
914
|
self.writeln("]))));");
|
|
873
915
|
|
|
874
|
-
self.writeln("let String = Value::Object(Rc::new(RefCell::new(
|
|
916
|
+
self.writeln("let String = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
|
|
875
917
|
self.indent += 1;
|
|
876
918
|
self.writeln("(Arc::from(\"fromCharCode\"), Value::Function(Rc::new(|args: &[Value]| tish_string_from_char_code(args)))),");
|
|
877
919
|
self.indent -= 1;
|
|
878
920
|
self.writeln("]))));");
|
|
879
921
|
|
|
880
|
-
self.writeln("let Date = Value::Object(Rc::new(RefCell::new(
|
|
922
|
+
self.writeln("let Date = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
|
|
881
923
|
self.indent += 1;
|
|
882
924
|
self.writeln("(Arc::from(\"now\"), Value::Function(Rc::new(|args: &[Value]| tish_date_now(args)))),");
|
|
883
925
|
self.indent -= 1;
|
|
884
926
|
self.writeln("]))));");
|
|
885
927
|
|
|
886
|
-
self.writeln("let Object = Value::Object(Rc::new(RefCell::new(
|
|
928
|
+
self.writeln("let Object = Value::Object(Rc::new(RefCell::new(ObjectMap::from([");
|
|
887
929
|
self.indent += 1;
|
|
888
930
|
self.writeln("(Arc::from(\"assign\"), Value::Function(Rc::new(|args: &[Value]| tish_object_assign(args)))),");
|
|
889
931
|
self.writeln("(Arc::from(\"keys\"), Value::Function(Rc::new(|args: &[Value]| tish_object_keys(args)))),");
|
|
@@ -893,16 +935,19 @@ impl Codegen {
|
|
|
893
935
|
self.indent -= 1;
|
|
894
936
|
self.writeln("]))));");
|
|
895
937
|
|
|
938
|
+
self.writeln("let Uint8Array = tish_uint8_array_constructor();");
|
|
939
|
+
self.writeln("let AudioContext = tish_audio_context_constructor();");
|
|
940
|
+
|
|
896
941
|
if self.has_feature("process") {
|
|
897
942
|
self.writeln("let process = Value::Object(Rc::new(RefCell::new({");
|
|
898
943
|
self.indent += 1;
|
|
899
|
-
self.writeln("let mut p =
|
|
944
|
+
self.writeln("let mut p = ObjectMap::default();");
|
|
900
945
|
self.writeln("p.insert(Arc::from(\"exit\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exit(args))));");
|
|
901
946
|
self.writeln("p.insert(Arc::from(\"cwd\"), Value::Function(Rc::new(|args: &[Value]| tish_process_cwd(args))));");
|
|
902
947
|
self.writeln("p.insert(Arc::from(\"exec\"), Value::Function(Rc::new(|args: &[Value]| tish_process_exec(args))));");
|
|
903
948
|
self.writeln("let argv: Vec<Value> = std::env::args().map(|s| Value::String(s.into())).collect();");
|
|
904
949
|
self.writeln("p.insert(Arc::from(\"argv\"), Value::Array(Rc::new(RefCell::new(argv))));");
|
|
905
|
-
self.writeln("let mut env_obj =
|
|
950
|
+
self.writeln("let mut env_obj = ObjectMap::default();");
|
|
906
951
|
self.writeln("for (key, value) in std::env::vars() {");
|
|
907
952
|
self.indent += 1;
|
|
908
953
|
self.writeln("env_obj.insert(Arc::from(key.as_str()), Value::String(value.into()));");
|
|
@@ -952,6 +997,15 @@ impl Codegen {
|
|
|
952
997
|
self.writeln("let RegExp = Value::Function(Rc::new(|args: &[Value]| regexp_new(args)));");
|
|
953
998
|
}
|
|
954
999
|
|
|
1000
|
+
if self.program_has_jsx {
|
|
1001
|
+
self.writeln("install_thread_local_host(Box::new(HeadlessHost::default()));");
|
|
1002
|
+
self.writeln("let Fragment = fragment_value();");
|
|
1003
|
+
self.writeln("let h = Value::Function(Rc::new(|args: &[Value]| ui_h(args)));");
|
|
1004
|
+
self.writeln("let text = Value::Function(Rc::new(|args: &[Value]| ui_text(args)));");
|
|
1005
|
+
self.writeln("let useState = Value::Function(Rc::new(|args: &[Value]| native_use_state(args)));");
|
|
1006
|
+
self.writeln("let createRoot = Value::Function(Rc::new(|args: &[Value]| native_create_root(args)));");
|
|
1007
|
+
}
|
|
1008
|
+
|
|
955
1009
|
// Polars, Egui etc. are emitted via VarDecl from import { X } from 'tish:...'
|
|
956
1010
|
|
|
957
1011
|
// Pre-scan for top-level function declarations and create cells (for mutual recursion)
|
|
@@ -1078,11 +1132,8 @@ impl Codegen {
|
|
|
1078
1132
|
let expr = self.emit_expr(init)?;
|
|
1079
1133
|
let mutability = if *mutable { "let mut" } else { "let" };
|
|
1080
1134
|
let clone_suffix = if Self::needs_clone(init) { ".clone()" } else { "" };
|
|
1081
|
-
self.writeln(&format!("
|
|
1082
|
-
self.indent += 1;
|
|
1135
|
+
self.writeln(&format!("let _destruct_val = ({}){};", expr, clone_suffix));
|
|
1083
1136
|
self.emit_destruct_bindings(pattern, "_destruct_val", mutability, *span)?;
|
|
1084
|
-
self.indent -= 1;
|
|
1085
|
-
self.writeln("}");
|
|
1086
1137
|
}
|
|
1087
1138
|
Statement::ExprStmt { expr, .. } => {
|
|
1088
1139
|
let e = self.emit_expr(expr)?;
|
|
@@ -1094,8 +1145,8 @@ impl Codegen {
|
|
|
1094
1145
|
else_branch,
|
|
1095
1146
|
..
|
|
1096
1147
|
} => {
|
|
1097
|
-
let c = self.
|
|
1098
|
-
self.write(&format!("if {}
|
|
1148
|
+
let c = self.emit_cond_expr(cond)?;
|
|
1149
|
+
self.write(&format!("if {} {{\n", c));
|
|
1099
1150
|
self.indent += 1;
|
|
1100
1151
|
self.emit_statement(then_branch)?;
|
|
1101
1152
|
self.indent -= 1;
|
|
@@ -1108,11 +1159,11 @@ impl Codegen {
|
|
|
1108
1159
|
self.writeln("}");
|
|
1109
1160
|
}
|
|
1110
1161
|
Statement::While { cond, body, .. } => {
|
|
1111
|
-
let c = self.
|
|
1162
|
+
let c = self.emit_cond_expr(cond)?;
|
|
1112
1163
|
let label = format!("'while_loop_{}", self.loop_label_index);
|
|
1113
1164
|
self.loop_label_index += 1;
|
|
1114
1165
|
self.loop_stack.push((label.clone(), None));
|
|
1115
|
-
self.write(&format!("{}: while {}
|
|
1166
|
+
self.write(&format!("{}: while {} {{\n", label, c));
|
|
1116
1167
|
self.indent += 1;
|
|
1117
1168
|
self.emit_statement(body)?;
|
|
1118
1169
|
self.loop_stack.pop();
|
|
@@ -1170,7 +1221,7 @@ impl Codegen {
|
|
|
1170
1221
|
self.loop_label_index += 1;
|
|
1171
1222
|
let cond_expr = cond
|
|
1172
1223
|
.as_ref()
|
|
1173
|
-
.map(|c|
|
|
1224
|
+
.map(|c| self.emit_cond_expr(c).unwrap())
|
|
1174
1225
|
.unwrap_or_else(|| "true".to_string());
|
|
1175
1226
|
let update_code = update.as_ref().map(|u| {
|
|
1176
1227
|
let ue = self.emit_expr(u).unwrap();
|
|
@@ -1262,14 +1313,14 @@ impl Codegen {
|
|
|
1262
1313
|
self.writeln("}");
|
|
1263
1314
|
}
|
|
1264
1315
|
Statement::DoWhile { body, cond, .. } => {
|
|
1265
|
-
let c = self.
|
|
1316
|
+
let c = self.emit_cond_expr(cond)?;
|
|
1266
1317
|
let label = format!("'dowhile_loop_{}", self.loop_label_index);
|
|
1267
1318
|
self.loop_label_index += 1;
|
|
1268
1319
|
self.loop_stack.push((label.clone(), None));
|
|
1269
1320
|
self.write(&format!("{}: loop {{\n", label));
|
|
1270
1321
|
self.indent += 1;
|
|
1271
1322
|
self.emit_statement(body)?;
|
|
1272
|
-
self.write(&format!("if !{}
|
|
1323
|
+
self.write(&format!("if !{} {{ break; }}\n", c));
|
|
1273
1324
|
self.loop_stack.pop();
|
|
1274
1325
|
self.indent -= 1;
|
|
1275
1326
|
self.writeln("}");
|
|
@@ -1326,7 +1377,7 @@ impl Codegen {
|
|
|
1326
1377
|
self.emit_statement(finally_stmt)?;
|
|
1327
1378
|
}
|
|
1328
1379
|
}
|
|
1329
|
-
Statement::FunDecl { name, params, rest_param, body, .. } => {
|
|
1380
|
+
Statement::FunDecl { name, params, rest_param, body, span, .. } => {
|
|
1330
1381
|
// Use Rc<RefCell<>> pattern to allow recursive function calls
|
|
1331
1382
|
// The function can reference itself through the cell
|
|
1332
1383
|
let name_raw = name.as_ref();
|
|
@@ -1343,7 +1394,11 @@ impl Codegen {
|
|
|
1343
1394
|
// Analyze body to find which identifiers are actually referenced
|
|
1344
1395
|
let mut referenced = HashSet::new();
|
|
1345
1396
|
Self::collect_stmt_idents(body, &mut referenced);
|
|
1346
|
-
let param_names: HashSet<String> = params
|
|
1397
|
+
let param_names: HashSet<String> = params
|
|
1398
|
+
.iter()
|
|
1399
|
+
.flat_map(|p| p.bound_names())
|
|
1400
|
+
.map(|n| n.to_string())
|
|
1401
|
+
.collect();
|
|
1347
1402
|
|
|
1348
1403
|
// Collect all outer parameters that need to be captured (only those referenced)
|
|
1349
1404
|
let outer_params: Vec<String> = self.outer_params_stack
|
|
@@ -1418,7 +1473,7 @@ impl Codegen {
|
|
|
1418
1473
|
self.writeln(&format!("let {} = {}.clone();", param_escaped, param_escaped));
|
|
1419
1474
|
}
|
|
1420
1475
|
// Only clone builtins that are actually referenced (clone so outer scope can still use them, e.g. process for PORT before serve)
|
|
1421
|
-
for builtin in &["Boolean", "console", "Math", "JSON", "Date", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"] {
|
|
1476
|
+
for builtin in &["Boolean", "console", "Math", "JSON", "Date", "Uint8Array", "AudioContext", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"] {
|
|
1422
1477
|
if referenced.contains(*builtin) {
|
|
1423
1478
|
self.writeln(&format!("let {} = {}.clone();", builtin, builtin));
|
|
1424
1479
|
}
|
|
@@ -1445,13 +1500,30 @@ impl Codegen {
|
|
|
1445
1500
|
self.writeln(&format!("let {} = (*{}_ref.borrow()).clone();", sibling_escaped, sibling_escaped));
|
|
1446
1501
|
}
|
|
1447
1502
|
// Extract just the parameter names (type annotations are parsed but not used in codegen yet)
|
|
1448
|
-
let current_param_names: Vec<String> = params
|
|
1503
|
+
let current_param_names: Vec<String> = params
|
|
1504
|
+
.iter()
|
|
1505
|
+
.flat_map(|p| p.bound_names())
|
|
1506
|
+
.map(|n| n.to_string())
|
|
1507
|
+
.collect();
|
|
1508
|
+
let formal_span = *span;
|
|
1449
1509
|
for (i, p) in params.iter().enumerate() {
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1510
|
+
match p {
|
|
1511
|
+
FunParam::Simple(tp) => {
|
|
1512
|
+
self.writeln(&format!(
|
|
1513
|
+
"let mut {} = args.get({}).cloned().unwrap_or(Value::Null);",
|
|
1514
|
+
Self::escape_ident(tp.name.as_ref()),
|
|
1515
|
+
i
|
|
1516
|
+
));
|
|
1517
|
+
}
|
|
1518
|
+
FunParam::Destructure { pattern, .. } => {
|
|
1519
|
+
let tmp = format!("_formal_{}", i);
|
|
1520
|
+
self.writeln(&format!(
|
|
1521
|
+
"let {} = args.get({}).cloned().unwrap_or(Value::Null);",
|
|
1522
|
+
tmp, i
|
|
1523
|
+
));
|
|
1524
|
+
self.emit_destruct_bindings(pattern, &tmp, "let mut", formal_span)?;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1455
1527
|
}
|
|
1456
1528
|
if let Some(rest) = rest_param {
|
|
1457
1529
|
self.writeln(&format!(
|
|
@@ -1562,58 +1634,71 @@ impl Codegen {
|
|
|
1562
1634
|
}
|
|
1563
1635
|
|
|
1564
1636
|
fn emit_destruct_bindings(&mut self, pattern: &DestructPattern, value_expr: &str, mutability: &str, span: Span) -> Result<(), CompileError> {
|
|
1637
|
+
// Flat `let` bindings so names stay in scope for the rest of the function (e.g. JSX).
|
|
1565
1638
|
match pattern {
|
|
1566
1639
|
DestructPattern::Array(elements) => {
|
|
1567
|
-
self.writeln(&format!("if let Value::Array(ref _arr) = {} {{", value_expr));
|
|
1568
|
-
self.indent += 1;
|
|
1569
|
-
self.writeln("let _arr_borrow = _arr.borrow();");
|
|
1570
1640
|
for (i, elem) in elements.iter().enumerate() {
|
|
1571
1641
|
if let Some(el) = elem {
|
|
1572
1642
|
match el {
|
|
1573
1643
|
DestructElement::Ident(name) => {
|
|
1574
|
-
self.writeln(&format!(
|
|
1575
|
-
|
|
1644
|
+
self.writeln(&format!(
|
|
1645
|
+
"{} {} = match &({}) {{ Value::Array(ref _a) => _a.borrow().get({}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
|
|
1646
|
+
mutability,
|
|
1647
|
+
Self::escape_ident(name.as_ref()),
|
|
1648
|
+
value_expr,
|
|
1649
|
+
i
|
|
1650
|
+
));
|
|
1576
1651
|
}
|
|
1577
1652
|
DestructElement::Pattern(nested) => {
|
|
1578
|
-
let nested_var = format!("
|
|
1579
|
-
self.writeln(&format!(
|
|
1580
|
-
|
|
1653
|
+
let nested_var = format!("_nested_arr_{}", i);
|
|
1654
|
+
self.writeln(&format!(
|
|
1655
|
+
"let {} = match &({}) {{ Value::Array(ref _a) => _a.borrow().get({}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
|
|
1656
|
+
nested_var, value_expr, i
|
|
1657
|
+
));
|
|
1581
1658
|
self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
|
|
1582
1659
|
}
|
|
1583
1660
|
DestructElement::Rest(name) => {
|
|
1584
|
-
self.writeln(&format!(
|
|
1585
|
-
|
|
1661
|
+
self.writeln(&format!(
|
|
1662
|
+
"{} {} = match &({}) {{ Value::Array(ref _a) => {{ let _b = _a.borrow(); Value::Array(Rc::new(RefCell::new(_b.iter().skip({}).cloned().collect()))) }}, _ => Value::Array(Rc::new(RefCell::new(Vec::new()))) }};",
|
|
1663
|
+
mutability,
|
|
1664
|
+
Self::escape_ident(name.as_ref()),
|
|
1665
|
+
value_expr,
|
|
1666
|
+
i
|
|
1667
|
+
));
|
|
1586
1668
|
}
|
|
1587
1669
|
}
|
|
1588
1670
|
}
|
|
1589
1671
|
}
|
|
1590
|
-
self.indent -= 1;
|
|
1591
|
-
self.writeln("}");
|
|
1592
1672
|
}
|
|
1593
1673
|
DestructPattern::Object(props) => {
|
|
1594
|
-
self.writeln(&format!("if let Value::Object(ref _obj) = {} {{", value_expr));
|
|
1595
|
-
self.indent += 1;
|
|
1596
|
-
self.writeln("let _obj_borrow = _obj.borrow();");
|
|
1597
1674
|
for prop in props {
|
|
1598
1675
|
let key = prop.key.as_ref();
|
|
1599
1676
|
match &prop.value {
|
|
1600
1677
|
DestructElement::Ident(name) => {
|
|
1601
|
-
self.writeln(&format!(
|
|
1602
|
-
|
|
1678
|
+
self.writeln(&format!(
|
|
1679
|
+
"{} {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
|
|
1680
|
+
mutability,
|
|
1681
|
+
Self::escape_ident(name.as_ref()),
|
|
1682
|
+
value_expr,
|
|
1683
|
+
key
|
|
1684
|
+
));
|
|
1603
1685
|
}
|
|
1604
1686
|
DestructElement::Pattern(nested) => {
|
|
1605
|
-
let nested_var = format!("
|
|
1606
|
-
self.writeln(&format!(
|
|
1607
|
-
|
|
1687
|
+
let nested_var = format!("_nested_obj_{}", key);
|
|
1688
|
+
self.writeln(&format!(
|
|
1689
|
+
"let {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
|
|
1690
|
+
nested_var, value_expr, key
|
|
1691
|
+
));
|
|
1608
1692
|
self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
|
|
1609
1693
|
}
|
|
1610
1694
|
DestructElement::Rest(_) => {
|
|
1611
|
-
return Err(CompileError::new(
|
|
1695
|
+
return Err(CompileError::new(
|
|
1696
|
+
"Rest in object destructuring not supported",
|
|
1697
|
+
Some(span),
|
|
1698
|
+
));
|
|
1612
1699
|
}
|
|
1613
1700
|
}
|
|
1614
1701
|
}
|
|
1615
|
-
self.indent -= 1;
|
|
1616
|
-
self.writeln("}");
|
|
1617
1702
|
}
|
|
1618
1703
|
}
|
|
1619
1704
|
Ok(())
|
|
@@ -1642,10 +1727,10 @@ impl Codegen {
|
|
|
1642
1727
|
}
|
|
1643
1728
|
}
|
|
1644
1729
|
}
|
|
1645
|
-
Expr::Binary {
|
|
1646
|
-
|
|
1647
|
-
let
|
|
1648
|
-
|
|
1730
|
+
Expr::Binary { .. } => {
|
|
1731
|
+
// Delegate to emit_typed_expr; wrap the native result in Value.
|
|
1732
|
+
let (code, ty) = self.emit_typed_expr(expr)?;
|
|
1733
|
+
if ty.is_native() { ty.to_value_expr(&code) } else { code }
|
|
1649
1734
|
}
|
|
1650
1735
|
Expr::Unary { op, operand, .. } => {
|
|
1651
1736
|
let o = self.emit_expr(operand)?;
|
|
@@ -1706,6 +1791,37 @@ impl Codegen {
|
|
|
1706
1791
|
|
|
1707
1792
|
// Check for built-in method calls on arrays/strings
|
|
1708
1793
|
if let Expr::Member { object, prop: MemberProp::Name(method_name), .. } = callee.as_ref() {
|
|
1794
|
+
// ── native Vec<T> push fast path ──────────────────────────────
|
|
1795
|
+
if method_name.as_ref() == "push" {
|
|
1796
|
+
if let Expr::Ident { name, .. } = object.as_ref() {
|
|
1797
|
+
if !self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
1798
|
+
let obj_type = self.type_context.get_type(name.as_ref());
|
|
1799
|
+
if let RustType::Vec(elem_type) = obj_type {
|
|
1800
|
+
let esc_obj = Self::escape_ident(name.as_ref()).into_owned();
|
|
1801
|
+
// Collect push arguments as native values.
|
|
1802
|
+
let mut push_stmts: Vec<String> = Vec::new();
|
|
1803
|
+
for a in args {
|
|
1804
|
+
if let CallArg::Expr(e) = a {
|
|
1805
|
+
let (val_code, val_ty) = self.emit_typed_expr(e)?;
|
|
1806
|
+
let native_val = if val_ty == *elem_type {
|
|
1807
|
+
val_code
|
|
1808
|
+
} else if val_ty == RustType::Value {
|
|
1809
|
+
elem_type.from_value_expr(&val_code)
|
|
1810
|
+
} else {
|
|
1811
|
+
val_code
|
|
1812
|
+
};
|
|
1813
|
+
push_stmts.push(format!("{}.push({});", esc_obj, native_val));
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
return Ok(format!(
|
|
1817
|
+
"{{ {} Value::Null }}",
|
|
1818
|
+
push_stmts.join(" ")
|
|
1819
|
+
));
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1709
1825
|
let obj_expr = self.emit_expr(object)?;
|
|
1710
1826
|
let arg_exprs: Result<Vec<_>, _> =
|
|
1711
1827
|
args.iter().map(|a| self.emit_call_arg(a)).collect();
|
|
@@ -2063,23 +2179,24 @@ impl Codegen {
|
|
|
2063
2179
|
format!("tishlang_runtime::get_prop(&{}, {})", obj, key)
|
|
2064
2180
|
}
|
|
2065
2181
|
}
|
|
2182
|
+
Expr::Index { optional, .. } if !optional => {
|
|
2183
|
+
// Try native Vec<T> fast path via emit_typed_expr; wrap result.
|
|
2184
|
+
let (code, ty) = self.emit_typed_expr(expr)?;
|
|
2185
|
+
if ty.is_native() { ty.to_value_expr(&code) } else { code }
|
|
2186
|
+
}
|
|
2066
2187
|
Expr::Index {
|
|
2067
2188
|
object,
|
|
2068
2189
|
index,
|
|
2069
|
-
optional,
|
|
2070
2190
|
..
|
|
2071
2191
|
} => {
|
|
2192
|
+
// optional chaining: always use runtime path
|
|
2072
2193
|
let obj = self.emit_expr(object)?;
|
|
2073
2194
|
let idx = self.emit_expr(index)?;
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
)
|
|
2080
|
-
} else {
|
|
2081
|
-
format!("tishlang_runtime::get_index(&{}, &{})", obj, idx)
|
|
2082
|
-
}
|
|
2195
|
+
format!(
|
|
2196
|
+
"{{ let o = {}.clone(); if matches!(o, Value::Null) {{ Value::Null }} else {{ \
|
|
2197
|
+
tishlang_runtime::get_index(&o, &{}) }} }}",
|
|
2198
|
+
obj, idx
|
|
2199
|
+
)
|
|
2083
2200
|
}
|
|
2084
2201
|
Expr::Conditional {
|
|
2085
2202
|
cond,
|
|
@@ -2164,7 +2281,7 @@ impl Codegen {
|
|
|
2164
2281
|
}
|
|
2165
2282
|
}
|
|
2166
2283
|
}
|
|
2167
|
-
format!("{{ let mut _obj:
|
|
2284
|
+
format!("{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::Object(Rc::new(RefCell::new(_obj))) }}", parts.join(" "))
|
|
2168
2285
|
} else {
|
|
2169
2286
|
let mut parts = Vec::new();
|
|
2170
2287
|
for prop in props {
|
|
@@ -2178,14 +2295,35 @@ impl Codegen {
|
|
|
2178
2295
|
}
|
|
2179
2296
|
}
|
|
2180
2297
|
format!(
|
|
2181
|
-
"Value::Object(Rc::new(RefCell::new(
|
|
2298
|
+
"Value::Object(Rc::new(RefCell::new(ObjectMap::from([{}]))))",
|
|
2182
2299
|
parts.join(", ")
|
|
2183
2300
|
)
|
|
2184
2301
|
}
|
|
2185
2302
|
}
|
|
2186
2303
|
Expr::Assign { name, value, .. } => {
|
|
2187
|
-
let val = self.emit_expr(value)?;
|
|
2188
2304
|
let escaped = Self::escape_ident(name.as_ref());
|
|
2305
|
+
// Native fast path: if the target is a scalar native type, emit
|
|
2306
|
+
// a direct assignment without boxing/unboxing through Value.
|
|
2307
|
+
if !self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
2308
|
+
let rust_type = self.type_context.get_type(name.as_ref());
|
|
2309
|
+
if rust_type.is_native() && matches!(rust_type, RustType::F64 | RustType::Bool | RustType::String) {
|
|
2310
|
+
let (val_code, val_ty) = self.emit_typed_expr(value)?;
|
|
2311
|
+
let native_val = if val_ty == rust_type {
|
|
2312
|
+
val_code
|
|
2313
|
+
} else if val_ty == RustType::Value {
|
|
2314
|
+
rust_type.from_value_expr(&val_code)
|
|
2315
|
+
} else {
|
|
2316
|
+
val_code
|
|
2317
|
+
};
|
|
2318
|
+
let return_val = rust_type.to_value_expr(&escaped);
|
|
2319
|
+
return Ok(format!(
|
|
2320
|
+
"{{ {} = {}; {} }}",
|
|
2321
|
+
escaped, native_val, return_val
|
|
2322
|
+
));
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
// Fallback: Value path
|
|
2326
|
+
let val = self.emit_expr(value)?;
|
|
2189
2327
|
let needs_outer_clone = self.should_clone(value);
|
|
2190
2328
|
if self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
2191
2329
|
if needs_outer_clone {
|
|
@@ -2194,7 +2332,6 @@ impl Codegen {
|
|
|
2194
2332
|
format!("{{ let _v = {}; *{}.borrow_mut() = _v.clone(); _v }}", val, escaped)
|
|
2195
2333
|
}
|
|
2196
2334
|
} else {
|
|
2197
|
-
// Use type_context: typed vars need from_value_expr; Value needs .clone() (we return _v)
|
|
2198
2335
|
let rust_type = self.type_context.get_type(name.as_ref());
|
|
2199
2336
|
let assign_rhs = if matches!(rust_type, RustType::Value) {
|
|
2200
2337
|
"_v.clone()".to_string()
|
|
@@ -2253,8 +2390,65 @@ impl Codegen {
|
|
|
2253
2390
|
Expr::PrefixInc { name, .. } => self.emit_inc_dec(name.as_ref(), true, "+ 1.0", "++"),
|
|
2254
2391
|
Expr::PrefixDec { name, .. } => self.emit_inc_dec(name.as_ref(), true, "- 1.0", "--"),
|
|
2255
2392
|
Expr::CompoundAssign { name, op, value, .. } => {
|
|
2256
|
-
let val = self.emit_expr(value)?;
|
|
2257
2393
|
let n = Self::escape_ident(name.as_ref());
|
|
2394
|
+
let is_refcell = self.refcell_wrapped_vars.contains(name.as_ref());
|
|
2395
|
+
let var_type = self.type_context.get_type(name.as_ref());
|
|
2396
|
+
|
|
2397
|
+
// ── native f64 fast path: direct arithmetic operators ─────────
|
|
2398
|
+
// emit_expr must return a Value expression; wrap the result back.
|
|
2399
|
+
if !is_refcell && var_type == RustType::F64 {
|
|
2400
|
+
let (rhs_code, rhs_ty) = self.emit_typed_expr(value)?;
|
|
2401
|
+
let rhs_f64 = if rhs_ty == RustType::F64 {
|
|
2402
|
+
rhs_code
|
|
2403
|
+
} else {
|
|
2404
|
+
// rhs is Value or another native: unbox to f64
|
|
2405
|
+
let rhs_val = if rhs_ty.is_native() {
|
|
2406
|
+
rhs_ty.to_value_expr(&rhs_code)
|
|
2407
|
+
} else {
|
|
2408
|
+
rhs_code
|
|
2409
|
+
};
|
|
2410
|
+
format!("(match &({}) {{ Value::Number(n) => *n, v => panic!(\"compound assign: expected number, got {{:?}}\", v) }})", rhs_val)
|
|
2411
|
+
};
|
|
2412
|
+
let op_str = match op {
|
|
2413
|
+
CompoundOp::Add => "+=",
|
|
2414
|
+
CompoundOp::Sub => "-=",
|
|
2415
|
+
CompoundOp::Mul => "*=",
|
|
2416
|
+
CompoundOp::Div => "/=",
|
|
2417
|
+
CompoundOp::Mod => "%=",
|
|
2418
|
+
};
|
|
2419
|
+
// Wrap in Value::Number so the expression is a valid Value
|
|
2420
|
+
return Ok(format!("{{ {} {} {}; Value::Number({}) }}", n, op_str, rhs_f64, n));
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
// ── native String += fast path: push_str ─────────────────────
|
|
2424
|
+
if !is_refcell && var_type == RustType::String && matches!(op, CompoundOp::Add) {
|
|
2425
|
+
let (rhs_code, rhs_ty) = self.emit_typed_expr(value)?;
|
|
2426
|
+
let rhs_str = if rhs_ty == RustType::String {
|
|
2427
|
+
rhs_code
|
|
2428
|
+
} else {
|
|
2429
|
+
// Convert rhs Value to display string inline
|
|
2430
|
+
let rhs_val = if rhs_ty.is_native() {
|
|
2431
|
+
rhs_ty.to_value_expr(&rhs_code)
|
|
2432
|
+
} else {
|
|
2433
|
+
rhs_code
|
|
2434
|
+
};
|
|
2435
|
+
format!(
|
|
2436
|
+
"match &({}) {{ \
|
|
2437
|
+
Value::String(s) => s.to_string(), \
|
|
2438
|
+
Value::Number(n) => {{ let i = *n as i64; if (*n - i as f64).abs() < f64::EPSILON {{ i.to_string() }} else {{ n.to_string() }} }}, \
|
|
2439
|
+
Value::Bool(b) => b.to_string(), \
|
|
2440
|
+
Value::Null => \"null\".to_string(), \
|
|
2441
|
+
other => format!(\"{{:?}}\", other) }}",
|
|
2442
|
+
rhs_val
|
|
2443
|
+
)
|
|
2444
|
+
};
|
|
2445
|
+
// Wrap in Value::String so the expression is a valid Value
|
|
2446
|
+
return Ok(format!("{{ {}.push_str(&({})); Value::String({}.clone().into()) }}", n, rhs_str, n));
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// ── fallback: Value path ──────────────────────────────────────
|
|
2450
|
+
// If the variable is native, wrap it as Value before calling ops::
|
|
2451
|
+
let val = self.emit_expr(value)?;
|
|
2258
2452
|
let op_fn = match op {
|
|
2259
2453
|
CompoundOp::Add => "add",
|
|
2260
2454
|
CompoundOp::Sub => "sub",
|
|
@@ -2262,11 +2456,20 @@ impl Codegen {
|
|
|
2262
2456
|
CompoundOp::Div => "div",
|
|
2263
2457
|
CompoundOp::Mod => "modulo",
|
|
2264
2458
|
};
|
|
2265
|
-
if
|
|
2459
|
+
if is_refcell {
|
|
2266
2460
|
format!(
|
|
2267
2461
|
"{{ let _rhs = ({}).clone(); *{}.borrow_mut() = tishlang_runtime::ops::{}(&*{}.borrow(), &_rhs)?; (*{}.borrow()).clone() }}",
|
|
2268
2462
|
val, n, op_fn, n, n
|
|
2269
2463
|
)
|
|
2464
|
+
} else if var_type.is_native() {
|
|
2465
|
+
// Wrap native lhs as Value, run ops::, unbox result back to native
|
|
2466
|
+
let n_as_value = var_type.to_value_expr(&n);
|
|
2467
|
+
let result_native = var_type.from_value_expr("_result");
|
|
2468
|
+
let n_as_value2 = var_type.to_value_expr(&n);
|
|
2469
|
+
format!(
|
|
2470
|
+
"{{ let _lhs = {}; let _rhs = ({}).clone(); let _result = tishlang_runtime::ops::{}(&_lhs, &_rhs)?; {} = {}; {} }}",
|
|
2471
|
+
n_as_value, val, op_fn, n, result_native, n_as_value2
|
|
2472
|
+
)
|
|
2270
2473
|
} else {
|
|
2271
2474
|
format!(
|
|
2272
2475
|
"{{ let _rhs = ({}).clone(); {} = tishlang_runtime::ops::{}(&{}, &_rhs)?; {}.clone() }}",
|
|
@@ -2278,6 +2481,35 @@ impl Codegen {
|
|
|
2278
2481
|
let val = self.emit_expr(value)?;
|
|
2279
2482
|
let n = Self::escape_ident(name.as_ref()).into_owned();
|
|
2280
2483
|
let is_refcell = self.refcell_wrapped_vars.contains(name.as_ref());
|
|
2484
|
+
let var_type = self.type_context.get_type(name.as_ref());
|
|
2485
|
+
|
|
2486
|
+
// ── native type: wrap for condition, unbox for assignment ──────
|
|
2487
|
+
if !is_refcell && var_type.is_native() {
|
|
2488
|
+
// n_as_value uses .clone() for String so we don't consume n
|
|
2489
|
+
let n_as_value = var_type.to_value_expr(&n);
|
|
2490
|
+
let val_as_native = var_type.from_value_expr("_v");
|
|
2491
|
+
let (cond, assign_and_return, else_expr) = match op {
|
|
2492
|
+
LogicalAssignOp::AndAnd => (
|
|
2493
|
+
format!("{{ let __chk = {}; __chk.is_truthy() }}", n_as_value),
|
|
2494
|
+
format!("{{ let _v = ({}).clone(); {} = {}; {} }}", val, n, val_as_native, var_type.to_value_expr(&n)),
|
|
2495
|
+
var_type.to_value_expr(&n),
|
|
2496
|
+
),
|
|
2497
|
+
LogicalAssignOp::OrOr => (
|
|
2498
|
+
format!("!{{ let __chk = {}; __chk.is_truthy() }}", n_as_value),
|
|
2499
|
+
format!("{{ let _v = ({}).clone(); {} = {}; {} }}", val, n, val_as_native, var_type.to_value_expr(&n)),
|
|
2500
|
+
var_type.to_value_expr(&n),
|
|
2501
|
+
),
|
|
2502
|
+
// Native types (f64, String, bool) are never null — ??= is a no-op
|
|
2503
|
+
LogicalAssignOp::Nullish => (
|
|
2504
|
+
"false".to_string(),
|
|
2505
|
+
var_type.to_value_expr(&n), // unreachable but must type-check
|
|
2506
|
+
var_type.to_value_expr(&n),
|
|
2507
|
+
),
|
|
2508
|
+
};
|
|
2509
|
+
return Ok(format!("{{ if {} {{ {} }} else {{ {} }} }}", cond, assign_and_return, else_expr));
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
// ── Value / refcell path ──────────────────────────────────────
|
|
2281
2513
|
let (cond, assign_and_return, else_expr) = if is_refcell {
|
|
2282
2514
|
match op {
|
|
2283
2515
|
LogicalAssignOp::AndAnd => (
|
|
@@ -2328,6 +2560,43 @@ impl Codegen {
|
|
|
2328
2560
|
)
|
|
2329
2561
|
}
|
|
2330
2562
|
Expr::IndexAssign { object, index, value, .. } => {
|
|
2563
|
+
// Native fast path: Vec<T>[i] = v
|
|
2564
|
+
if let Expr::Ident { name, .. } = object.as_ref() {
|
|
2565
|
+
if !self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
2566
|
+
let obj_type = self.type_context.get_type(name.as_ref());
|
|
2567
|
+
if let RustType::Vec(elem_type) = obj_type {
|
|
2568
|
+
let esc_obj = Self::escape_ident(name.as_ref()).into_owned();
|
|
2569
|
+
let (idx_code, idx_ty) = self.emit_typed_expr(index)?;
|
|
2570
|
+
let idx_usize = if idx_ty == RustType::F64 {
|
|
2571
|
+
format!("({}) as usize", idx_code)
|
|
2572
|
+
} else {
|
|
2573
|
+
let iv = if idx_ty.is_native() {
|
|
2574
|
+
idx_ty.to_value_expr(&idx_code)
|
|
2575
|
+
} else {
|
|
2576
|
+
idx_code
|
|
2577
|
+
};
|
|
2578
|
+
format!(
|
|
2579
|
+
"{{ let _i = &{}; if let Value::Number(n) = _i {{ *n as usize }} else {{ panic!(\"array index must be a number\") }} }}",
|
|
2580
|
+
iv
|
|
2581
|
+
)
|
|
2582
|
+
};
|
|
2583
|
+
let (val_code, val_ty) = self.emit_typed_expr(value)?;
|
|
2584
|
+
let native_val = if val_ty == *elem_type {
|
|
2585
|
+
val_code
|
|
2586
|
+
} else if val_ty == RustType::Value {
|
|
2587
|
+
elem_type.from_value_expr(&val_code)
|
|
2588
|
+
} else {
|
|
2589
|
+
// both native but different type — best effort
|
|
2590
|
+
val_code
|
|
2591
|
+
};
|
|
2592
|
+
return Ok(format!(
|
|
2593
|
+
"{{ {}[{}] = {}; Value::Null }}",
|
|
2594
|
+
esc_obj, idx_usize, native_val
|
|
2595
|
+
));
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
// Fallback: runtime set_index
|
|
2331
2600
|
let obj = self.emit_expr(object)?;
|
|
2332
2601
|
let idx = self.emit_expr(index)?;
|
|
2333
2602
|
let val = self.emit_expr(value)?;
|
|
@@ -2338,8 +2607,8 @@ impl Codegen {
|
|
|
2338
2607
|
val
|
|
2339
2608
|
)
|
|
2340
2609
|
}
|
|
2341
|
-
Expr::ArrowFunction { params, body, .. } => {
|
|
2342
|
-
self.emit_arrow_function(params, body)?
|
|
2610
|
+
Expr::ArrowFunction { params, body, span, .. } => {
|
|
2611
|
+
self.emit_arrow_function(params, body, *span)?
|
|
2343
2612
|
}
|
|
2344
2613
|
Expr::TemplateLiteral { quasis, exprs, .. } => {
|
|
2345
2614
|
// Build the template string
|
|
@@ -2356,7 +2625,33 @@ impl Codegen {
|
|
|
2356
2625
|
format!("Value::String([{}].concat().into())", parts.join(", "))
|
|
2357
2626
|
}
|
|
2358
2627
|
Expr::JsxElement { .. } | Expr::JsxFragment { .. } => {
|
|
2359
|
-
|
|
2628
|
+
tishlang_ui::jsx::emit_jsx_rust(expr, &mut |e| {
|
|
2629
|
+
self.emit_expr(e).map_err(|ce| ce.message)
|
|
2630
|
+
})
|
|
2631
|
+
.map_err(|m| CompileError::new(m, None))?
|
|
2632
|
+
}
|
|
2633
|
+
Expr::New { callee, args, .. } => {
|
|
2634
|
+
let callee_expr = self.emit_expr(callee)?;
|
|
2635
|
+
let has_spread = args.iter().any(|a| matches!(a, CallArg::Spread(_)));
|
|
2636
|
+
if has_spread {
|
|
2637
|
+
let args_code = self.emit_call_args(args)?;
|
|
2638
|
+
return Ok(format!(
|
|
2639
|
+
"{{ let _callee = ({}).clone(); let _spread_args = {}; tish_construct(&_callee, &_spread_args[..]) }}",
|
|
2640
|
+
callee_expr, args_code
|
|
2641
|
+
));
|
|
2642
|
+
}
|
|
2643
|
+
let arg_exprs: Result<Vec<_>, _> =
|
|
2644
|
+
args.iter().map(|a| self.emit_call_arg(a)).collect();
|
|
2645
|
+
let arg_exprs = arg_exprs?;
|
|
2646
|
+
let args_vec = arg_exprs
|
|
2647
|
+
.iter()
|
|
2648
|
+
.map(|a| format!("{}.clone()", a))
|
|
2649
|
+
.collect::<Vec<_>>()
|
|
2650
|
+
.join(", ");
|
|
2651
|
+
format!(
|
|
2652
|
+
"tish_construct(&({}).clone(), &[{}])",
|
|
2653
|
+
callee_expr, args_vec
|
|
2654
|
+
)
|
|
2360
2655
|
}
|
|
2361
2656
|
Expr::NativeModuleLoad { spec, export_name, .. } => {
|
|
2362
2657
|
self.native_module_rust_init(spec.as_ref(), export_name.as_ref())
|
|
@@ -2410,6 +2705,14 @@ impl Codegen {
|
|
|
2410
2705
|
}
|
|
2411
2706
|
}
|
|
2412
2707
|
}
|
|
2708
|
+
Expr::New { callee, args, .. } => {
|
|
2709
|
+
Self::collect_expr_idents(callee, idents);
|
|
2710
|
+
for arg in args {
|
|
2711
|
+
match arg {
|
|
2712
|
+
CallArg::Expr(e) | CallArg::Spread(e) => Self::collect_expr_idents(e, idents),
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2413
2716
|
Expr::Member { object, prop, .. } => {
|
|
2414
2717
|
Self::collect_expr_idents(object, idents);
|
|
2415
2718
|
if let MemberProp::Expr(e) = prop { Self::collect_expr_idents(e, idents); }
|
|
@@ -2613,6 +2916,16 @@ impl Codegen {
|
|
|
2613
2916
|
}
|
|
2614
2917
|
}
|
|
2615
2918
|
}
|
|
2919
|
+
Expr::New { callee, args, .. } => {
|
|
2920
|
+
Self::collect_assigned_idents_in_expr(callee, names);
|
|
2921
|
+
for arg in args {
|
|
2922
|
+
match arg {
|
|
2923
|
+
CallArg::Expr(e) | CallArg::Spread(e) => {
|
|
2924
|
+
Self::collect_assigned_idents_in_expr(e, names);
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2616
2929
|
Expr::Member { object, prop, .. } => {
|
|
2617
2930
|
Self::collect_assigned_idents_in_expr(object, names);
|
|
2618
2931
|
if let MemberProp::Expr(e) = prop {
|
|
@@ -2725,12 +3038,16 @@ impl Codegen {
|
|
|
2725
3038
|
/// Collect variable names that are both captured and mutated by a closure body.
|
|
2726
3039
|
/// block_vars: vars declared in the enclosing block (candidates for mutation).
|
|
2727
3040
|
fn collect_mutated_captures_from_closure(
|
|
2728
|
-
params: &[
|
|
3041
|
+
params: &[FunParam],
|
|
2729
3042
|
body: &Statement,
|
|
2730
3043
|
block_vars: &HashSet<String>,
|
|
2731
3044
|
result: &mut HashSet<String>,
|
|
2732
3045
|
) {
|
|
2733
|
-
let param_names: HashSet<String> = params
|
|
3046
|
+
let param_names: HashSet<String> = params
|
|
3047
|
+
.iter()
|
|
3048
|
+
.flat_map(|p| p.bound_names())
|
|
3049
|
+
.map(|n| n.to_string())
|
|
3050
|
+
.collect();
|
|
2734
3051
|
let mut local_var_names = HashSet::new();
|
|
2735
3052
|
Self::collect_local_var_names(body, &mut local_var_names);
|
|
2736
3053
|
let mut referenced = HashSet::new();
|
|
@@ -2754,12 +3071,16 @@ impl Codegen {
|
|
|
2754
3071
|
}
|
|
2755
3072
|
|
|
2756
3073
|
fn collect_mutated_captures_from_arrow(
|
|
2757
|
-
params: &[
|
|
3074
|
+
params: &[FunParam],
|
|
2758
3075
|
body: &ArrowBody,
|
|
2759
3076
|
block_vars: &HashSet<String>,
|
|
2760
3077
|
result: &mut HashSet<String>,
|
|
2761
3078
|
) {
|
|
2762
|
-
let param_names: HashSet<String> = params
|
|
3079
|
+
let param_names: HashSet<String> = params
|
|
3080
|
+
.iter()
|
|
3081
|
+
.flat_map(|p| p.bound_names())
|
|
3082
|
+
.map(|n| n.to_string())
|
|
3083
|
+
.collect();
|
|
2763
3084
|
let mut local_var_names = HashSet::new();
|
|
2764
3085
|
match body {
|
|
2765
3086
|
ArrowBody::Expr(_) => {}
|
|
@@ -2808,6 +3129,16 @@ impl Codegen {
|
|
|
2808
3129
|
}
|
|
2809
3130
|
}
|
|
2810
3131
|
}
|
|
3132
|
+
Expr::New { callee, args, .. } => {
|
|
3133
|
+
Self::collect_mutated_captures_from_expr(callee, block_vars, result);
|
|
3134
|
+
for arg in args {
|
|
3135
|
+
match arg {
|
|
3136
|
+
CallArg::Expr(e) | CallArg::Spread(e) => {
|
|
3137
|
+
Self::collect_mutated_captures_from_expr(e, block_vars, result);
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
2811
3142
|
Expr::Member { object, prop, .. } => {
|
|
2812
3143
|
Self::collect_mutated_captures_from_expr(object, block_vars, result);
|
|
2813
3144
|
if let MemberProp::Expr(e) = prop {
|
|
@@ -3055,8 +3386,9 @@ impl Codegen {
|
|
|
3055
3386
|
|
|
3056
3387
|
fn emit_arrow_function(
|
|
3057
3388
|
&mut self,
|
|
3058
|
-
params: &[
|
|
3389
|
+
params: &[FunParam],
|
|
3059
3390
|
body: &tishlang_ast::ArrowBody,
|
|
3391
|
+
span: Span,
|
|
3060
3392
|
) -> Result<String, CompileError> {
|
|
3061
3393
|
// Build the arrow function as a Value::Function closure
|
|
3062
3394
|
let mut code = String::new();
|
|
@@ -3065,7 +3397,11 @@ impl Codegen {
|
|
|
3065
3397
|
// Find which identifiers are actually referenced in the body
|
|
3066
3398
|
let referenced = Self::collect_referenced_idents(body);
|
|
3067
3399
|
// Exclude the arrow's own parameters - they're not outer captures
|
|
3068
|
-
let param_names: HashSet<String> = params
|
|
3400
|
+
let param_names: HashSet<String> = params
|
|
3401
|
+
.iter()
|
|
3402
|
+
.flat_map(|p| p.bound_names())
|
|
3403
|
+
.map(|n| n.to_string())
|
|
3404
|
+
.collect();
|
|
3069
3405
|
|
|
3070
3406
|
// Exclude variables declared inside the arrow body (locals)
|
|
3071
3407
|
let mut local_var_names = HashSet::new();
|
|
@@ -3120,7 +3456,7 @@ impl Codegen {
|
|
|
3120
3456
|
}
|
|
3121
3457
|
}
|
|
3122
3458
|
// Only clone builtins that are actually referenced (clone so outer scope can still use, e.g. process for PORT)
|
|
3123
|
-
for builtin in &["console", "Math", "JSON", "Date", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"] {
|
|
3459
|
+
for builtin in &["console", "Math", "JSON", "Date", "Uint8Array", "AudioContext", "process", "setTimeout", "clearTimeout", "Promise", "RegExp", "Polars"] {
|
|
3124
3460
|
if referenced.contains(*builtin) {
|
|
3125
3461
|
code.push_str(&format!(" let {} = {}.clone();\n", builtin, builtin));
|
|
3126
3462
|
}
|
|
@@ -3164,13 +3500,35 @@ impl Codegen {
|
|
|
3164
3500
|
}
|
|
3165
3501
|
|
|
3166
3502
|
// Extract parameters from args
|
|
3167
|
-
let current_param_names: Vec<String> = params
|
|
3503
|
+
let current_param_names: Vec<String> = params
|
|
3504
|
+
.iter()
|
|
3505
|
+
.flat_map(|p| p.bound_names())
|
|
3506
|
+
.map(|n| n.to_string())
|
|
3507
|
+
.collect();
|
|
3168
3508
|
for (i, p) in params.iter().enumerate() {
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3509
|
+
match p {
|
|
3510
|
+
FunParam::Simple(tp) => {
|
|
3511
|
+
code.push_str(&format!(
|
|
3512
|
+
" let mut {} = args.get({}).cloned().unwrap_or(Value::Null);\n",
|
|
3513
|
+
Self::escape_ident(tp.name.as_ref()),
|
|
3514
|
+
i
|
|
3515
|
+
));
|
|
3516
|
+
}
|
|
3517
|
+
FunParam::Destructure { pattern, .. } => {
|
|
3518
|
+
let tmp = format!("_formal_{}", i);
|
|
3519
|
+
code.push_str(&format!(
|
|
3520
|
+
" let {} = args.get({}).cloned().unwrap_or(Value::Null);\n",
|
|
3521
|
+
tmp, i
|
|
3522
|
+
));
|
|
3523
|
+
let saved = std::mem::take(&mut self.output);
|
|
3524
|
+
let saved_indent = self.indent;
|
|
3525
|
+
self.indent = 8;
|
|
3526
|
+
self.emit_destruct_bindings(pattern, &tmp, "let mut", span)?;
|
|
3527
|
+
let frag = std::mem::replace(&mut self.output, saved);
|
|
3528
|
+
self.indent = saved_indent;
|
|
3529
|
+
code.push_str(&frag);
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3174
3532
|
}
|
|
3175
3533
|
|
|
3176
3534
|
// Push current params for potential nested arrows
|
|
@@ -3276,6 +3634,146 @@ impl Codegen {
|
|
|
3276
3634
|
Ok(target_type.from_value_expr(&value_expr))
|
|
3277
3635
|
}
|
|
3278
3636
|
|
|
3637
|
+
/// Emit an expression and return `(code, type)`.
|
|
3638
|
+
///
|
|
3639
|
+
/// When `type` is a native type (`F64`, `Bool`, `String`, `Vec<T>`, …), `code`
|
|
3640
|
+
/// evaluates to a Rust value of that type directly — **not** a `Value`.
|
|
3641
|
+
/// When `type` is `RustType::Value`, `code` evaluates to a `Value`.
|
|
3642
|
+
///
|
|
3643
|
+
/// This is the fast-path used by callers that want to propagate native types
|
|
3644
|
+
/// through arithmetic, indexing, and assignments. For any expression this
|
|
3645
|
+
/// function cannot handle natively, it falls back to `emit_expr` and returns
|
|
3646
|
+
/// `RustType::Value`.
|
|
3647
|
+
fn emit_typed_expr(&mut self, expr: &Expr) -> Result<(String, RustType), CompileError> {
|
|
3648
|
+
match expr {
|
|
3649
|
+
// ── literals ─────────────────────────────────────────────────────────
|
|
3650
|
+
Expr::Literal { value, .. } => match value {
|
|
3651
|
+
Literal::Number(n) => Ok((format!("{}_f64", n), RustType::F64)),
|
|
3652
|
+
Literal::String(s) => Ok((format!("{:?}.to_string()", s.as_ref()), RustType::String)),
|
|
3653
|
+
Literal::Bool(b) => Ok((format!("{}", b), RustType::Bool)),
|
|
3654
|
+
Literal::Null => Ok(("Value::Null".to_string(), RustType::Value)),
|
|
3655
|
+
},
|
|
3656
|
+
|
|
3657
|
+
// ── identifiers ──────────────────────────────────────────────────────
|
|
3658
|
+
Expr::Ident { name, .. } => {
|
|
3659
|
+
let escaped = Self::escape_ident(name.as_ref());
|
|
3660
|
+
if self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
3661
|
+
// RefCell-wrapped: unwrap via borrow and return Value
|
|
3662
|
+
Ok((format!("(*{}.borrow()).clone()", escaped), RustType::Value))
|
|
3663
|
+
} else {
|
|
3664
|
+
let var_type = self.type_context.get_type(name.as_ref());
|
|
3665
|
+
if var_type.is_native() {
|
|
3666
|
+
Ok((escaped.into_owned(), var_type))
|
|
3667
|
+
} else {
|
|
3668
|
+
Ok((escaped.into_owned(), RustType::Value))
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
// ── binary expressions ───────────────────────────────────────────────
|
|
3674
|
+
Expr::Binary { left, op, right, span, .. } => {
|
|
3675
|
+
let (l, lt) = self.emit_typed_expr(left)?;
|
|
3676
|
+
let (r, rt) = self.emit_typed_expr(right)?;
|
|
3677
|
+
|
|
3678
|
+
if let Some(result_ty) = RustType::result_type_of_binop(*op, <, &rt) {
|
|
3679
|
+
// Both sides are compatible native types → emit native op.
|
|
3680
|
+
let code = match op {
|
|
3681
|
+
BinOp::Add => format!("({} + {})", l, r),
|
|
3682
|
+
BinOp::Sub => format!("({} - {})", l, r),
|
|
3683
|
+
BinOp::Mul => format!("({} * {})", l, r),
|
|
3684
|
+
BinOp::Div => format!("({} / {})", l, r),
|
|
3685
|
+
BinOp::Mod => format!("({} % {})", l, r),
|
|
3686
|
+
BinOp::Pow => format!("({}).powf({})", l, r),
|
|
3687
|
+
BinOp::Lt => format!("({} < {})", l, r),
|
|
3688
|
+
BinOp::Le => format!("({} <= {})", l, r),
|
|
3689
|
+
BinOp::Gt => format!("({} > {})", l, r),
|
|
3690
|
+
BinOp::Ge => format!("({} >= {})", l, r),
|
|
3691
|
+
BinOp::StrictEq => format!("({} == {})", l, r),
|
|
3692
|
+
BinOp::StrictNe => format!("({} != {})", l, r),
|
|
3693
|
+
BinOp::And => format!("({} && {})", l, r),
|
|
3694
|
+
BinOp::Or => format!("({} || {})", l, r),
|
|
3695
|
+
_ => unreachable!("result_type_of_binop covers all handled ops"),
|
|
3696
|
+
};
|
|
3697
|
+
return Ok((code, result_ty));
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
// Fall back: convert both sides to Value and use the runtime.
|
|
3701
|
+
let lv = if lt.is_native() { lt.to_value_expr(&l) } else { l };
|
|
3702
|
+
let rv = if rt.is_native() { rt.to_value_expr(&r) } else { r };
|
|
3703
|
+
let result = self.emit_binop(&lv, *op, &rv, *span)?;
|
|
3704
|
+
Ok((result, RustType::Value))
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
// ── array indexing ───────────────────────────────────────────────────
|
|
3708
|
+
Expr::Index { object, index, optional, .. } => {
|
|
3709
|
+
// Native fast path: `vec[i]` where vec is Vec<T> and i is numeric.
|
|
3710
|
+
if !optional {
|
|
3711
|
+
if let Expr::Ident { name, .. } = object.as_ref() {
|
|
3712
|
+
if !self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
3713
|
+
let obj_type = self.type_context.get_type(name.as_ref());
|
|
3714
|
+
if let RustType::Vec(elem_type) = &obj_type {
|
|
3715
|
+
let esc_obj = Self::escape_ident(name.as_ref()).into_owned();
|
|
3716
|
+
let (idx_code, idx_ty) = self.emit_typed_expr(index)?;
|
|
3717
|
+
let idx_usize = if idx_ty == RustType::F64 {
|
|
3718
|
+
format!("({}) as usize", idx_code)
|
|
3719
|
+
} else {
|
|
3720
|
+
let iv = if idx_ty.is_native() {
|
|
3721
|
+
idx_ty.to_value_expr(&idx_code)
|
|
3722
|
+
} else {
|
|
3723
|
+
idx_code
|
|
3724
|
+
};
|
|
3725
|
+
format!(
|
|
3726
|
+
"{{ let _i = &{}; if let Value::Number(n) = _i {{ *n as usize }} else {{ panic!(\"array index must be a number\") }} }}",
|
|
3727
|
+
iv
|
|
3728
|
+
)
|
|
3729
|
+
};
|
|
3730
|
+
let elem_ty = *elem_type.clone();
|
|
3731
|
+
return Ok((format!("{}[{}]", esc_obj, idx_usize), elem_ty));
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
// Value fallback: emit runtime code directly to avoid cycles
|
|
3737
|
+
// (emit_expr for !optional Index delegates here, so we must not call emit_expr(expr))
|
|
3738
|
+
let obj = self.emit_expr(object)?;
|
|
3739
|
+
let idx = self.emit_expr(index)?;
|
|
3740
|
+
let result = if *optional {
|
|
3741
|
+
format!(
|
|
3742
|
+
"{{ let o = {}.clone(); if matches!(o, Value::Null) {{ Value::Null }} else {{ \
|
|
3743
|
+
tishlang_runtime::get_index(&o, &{}) }} }}",
|
|
3744
|
+
obj, idx
|
|
3745
|
+
)
|
|
3746
|
+
} else {
|
|
3747
|
+
format!("tishlang_runtime::get_index(&{}, &{})", obj, idx)
|
|
3748
|
+
};
|
|
3749
|
+
Ok((result, RustType::Value))
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
// ── everything else: delegate to emit_expr ───────────────────────────
|
|
3753
|
+
_ => {
|
|
3754
|
+
let result = self.emit_expr(expr)?;
|
|
3755
|
+
Ok((result, RustType::Value))
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
/// Emit a condition expression as a Rust `bool`.
|
|
3761
|
+
///
|
|
3762
|
+
/// Returns a `bool`-typed Rust expression when the condition can be
|
|
3763
|
+
/// determined to be native (e.g. `i < N` where both are `f64`), otherwise
|
|
3764
|
+
/// falls back to `{value}.is_truthy()`.
|
|
3765
|
+
fn emit_cond_expr(&mut self, expr: &Expr) -> Result<String, CompileError> {
|
|
3766
|
+
let (code, ty) = self.emit_typed_expr(expr)?;
|
|
3767
|
+
if ty == RustType::Bool {
|
|
3768
|
+
Ok(code)
|
|
3769
|
+
} else if ty.is_native() {
|
|
3770
|
+
// Non-bool native type: convert to Value and use is_truthy
|
|
3771
|
+
Ok(format!("{}.is_truthy()", ty.to_value_expr(&code)))
|
|
3772
|
+
} else {
|
|
3773
|
+
Ok(format!("{}.is_truthy()", code))
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3279
3777
|
fn emit_binop(
|
|
3280
3778
|
&self,
|
|
3281
3779
|
l: &str,
|