@tishlang/tish 1.9.2 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/bin/tish +0 -0
  2. package/crates/js_to_tish/src/transform/expr.rs +8 -6
  3. package/crates/js_to_tish/src/transform/stmt.rs +12 -13
  4. package/crates/tish/Cargo.toml +1 -1
  5. package/crates/tish/src/cargo_native_registry.rs +4 -1
  6. package/crates/tish/src/cli_help.rs +9 -1
  7. package/crates/tish/src/main.rs +66 -11
  8. package/crates/tish/tests/integration_test.rs +145 -7
  9. package/crates/tish_ast/src/ast.rs +3 -9
  10. package/crates/tish_build_utils/src/lib.rs +74 -23
  11. package/crates/tish_builtins/src/array.rs +2 -3
  12. package/crates/tish_builtins/src/construct.rs +15 -28
  13. package/crates/tish_builtins/src/globals.rs +18 -16
  14. package/crates/tish_builtins/src/helpers.rs +1 -4
  15. package/crates/tish_builtins/src/lib.rs +1 -0
  16. package/crates/tish_builtins/src/math.rs +7 -0
  17. package/crates/tish_builtins/src/object.rs +10 -10
  18. package/crates/tish_builtins/src/string.rs +27 -3
  19. package/crates/tish_builtins/src/symbol.rs +83 -0
  20. package/crates/tish_compile/src/codegen.rs +324 -158
  21. package/crates/tish_compile/src/lib.rs +39 -7
  22. package/crates/tish_compile/src/resolve.rs +191 -6
  23. package/crates/tish_compile/src/types.rs +6 -6
  24. package/crates/tish_compile_js/src/codegen.rs +8 -5
  25. package/crates/tish_core/src/console_style.rs +9 -0
  26. package/crates/tish_core/src/json.rs +17 -7
  27. package/crates/tish_core/src/macros.rs +2 -2
  28. package/crates/tish_core/src/value.rs +213 -4
  29. package/crates/tish_cranelift/src/link.rs +1 -1
  30. package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
  31. package/crates/tish_eval/src/eval.rs +135 -73
  32. package/crates/tish_eval/src/http.rs +18 -12
  33. package/crates/tish_eval/src/lib.rs +29 -0
  34. package/crates/tish_eval/src/regex.rs +1 -1
  35. package/crates/tish_eval/src/value.rs +89 -4
  36. package/crates/tish_eval/src/value_convert.rs +30 -8
  37. package/crates/tish_fmt/src/lib.rs +4 -1
  38. package/crates/tish_lexer/src/lib.rs +7 -2
  39. package/crates/tish_llvm/src/lib.rs +2 -2
  40. package/crates/tish_lsp/src/builtin_goto.rs +111 -10
  41. package/crates/tish_lsp/src/import_goto.rs +35 -22
  42. package/crates/tish_lsp/src/main.rs +118 -85
  43. package/crates/tish_native/src/build.rs +270 -24
  44. package/crates/tish_native/src/config.rs +48 -0
  45. package/crates/tish_native/src/lib.rs +139 -12
  46. package/crates/tish_parser/src/lib.rs +5 -2
  47. package/crates/tish_parser/src/parser.rs +45 -75
  48. package/crates/tish_pg/src/error.rs +1 -1
  49. package/crates/tish_pg/src/lib.rs +61 -73
  50. package/crates/tish_resolve/src/lib.rs +283 -158
  51. package/crates/tish_resolve/src/pos.rs +10 -2
  52. package/crates/tish_runtime/Cargo.toml +3 -0
  53. package/crates/tish_runtime/src/http.rs +39 -39
  54. package/crates/tish_runtime/src/http_fetch.rs +12 -12
  55. package/crates/tish_runtime/src/lib.rs +35 -44
  56. package/crates/tish_runtime/src/native_promise.rs +0 -11
  57. package/crates/tish_runtime/src/promise.rs +14 -1
  58. package/crates/tish_runtime/src/promise_io.rs +1 -4
  59. package/crates/tish_runtime/src/timers.rs +12 -7
  60. package/crates/tish_runtime/src/ws.rs +40 -27
  61. package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
  62. package/crates/tish_ui/src/jsx.rs +6 -4
  63. package/crates/tish_ui/src/lib.rs +5 -4
  64. package/crates/tish_ui/src/runtime/hooks.rs +123 -37
  65. package/crates/tish_ui/src/runtime/mod.rs +21 -41
  66. package/crates/tish_vm/Cargo.toml +2 -0
  67. package/crates/tish_vm/src/vm.rs +258 -153
  68. package/crates/tish_wasm/src/lib.rs +60 -7
  69. package/crates/tish_wasm_runtime/Cargo.toml +10 -1
  70. package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
  71. package/crates/tish_wasm_runtime/src/lib.rs +7 -1
  72. package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
  73. package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
  74. package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
  75. package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
  76. package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
  77. package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
  78. package/justfile +3 -3
  79. package/package.json +1 -1
  80. package/platform/darwin-arm64/tish +0 -0
  81. package/platform/darwin-x64/tish +0 -0
  82. package/platform/linux-arm64/tish +0 -0
  83. package/platform/linux-x64/tish +0 -0
  84. package/platform/win32-x64/tish.exe +0 -0
@@ -540,6 +540,33 @@ pub fn compile_project_full(
540
540
  crate::resolve::NativeBuildArtifacts,
541
541
  ),
542
542
  CompileError,
543
+ > {
544
+ compile_project_full_emit(
545
+ entry_path,
546
+ project_root,
547
+ features,
548
+ optimize,
549
+ crate::NativeEmitMode::DesktopBin,
550
+ None,
551
+ )
552
+ }
553
+
554
+ /// Like [`compile_project_full`], with emit mode and optional feature cap (e.g. iOS sandbox).
555
+ pub fn compile_project_full_emit(
556
+ entry_path: &Path,
557
+ project_root: Option<&Path>,
558
+ features: &[String],
559
+ optimize: bool,
560
+ emit_mode: crate::NativeEmitMode,
561
+ feature_cap: Option<&std::collections::HashSet<String>>,
562
+ ) -> Result<
563
+ (
564
+ String,
565
+ Vec<crate::resolve::ResolvedNativeModule>,
566
+ Vec<String>,
567
+ crate::resolve::NativeBuildArtifacts,
568
+ ),
569
+ CompileError,
543
570
  > {
544
571
  use crate::resolve;
545
572
  let root = project_root.unwrap_or_else(|| entry_path.parent().unwrap_or(Path::new(".")));
@@ -555,33 +582,41 @@ pub fn compile_project_full(
555
582
  message: e,
556
583
  span: None,
557
584
  })?;
558
- let native_modules =
585
+ let mut native_modules =
559
586
  resolve::resolve_native_modules(&merged.program, root).map_err(|e| CompileError {
560
587
  message: e,
561
588
  span: None,
562
589
  })?;
563
- let native_build = resolve::compute_native_build_artifacts(
564
- &merged.program,
565
- root,
566
- &native_modules,
567
- )
568
- .map_err(|e| CompileError {
590
+ if resolve::program_uses_document(&merged.program) {
591
+ resolve::ensure_tish_canvas_module(&mut native_modules, root).map_err(|e| CompileError {
569
592
  message: e,
570
593
  span: None,
571
594
  })?;
595
+ }
596
+ let native_build =
597
+ resolve::compute_native_build_artifacts(&merged.program, root, &native_modules).map_err(
598
+ |e| CompileError {
599
+ message: e,
600
+ span: None,
601
+ },
602
+ )?;
572
603
  let mut all_features: Vec<String> = features.to_vec();
573
604
  for f in resolve::extract_native_import_features(&merged.program) {
574
605
  if !all_features.contains(&f) {
575
606
  all_features.push(f);
576
607
  }
577
608
  }
578
- let rust = compile_with_native_modules(
609
+ if let Some(cap) = feature_cap {
610
+ all_features.retain(|f| cap.contains(f));
611
+ }
612
+ let rust = compile_with_native_modules_emit(
579
613
  &merged.program,
580
614
  project_root,
581
615
  &all_features,
582
616
  &native_modules,
583
617
  &native_build.native_init,
584
618
  optimize,
619
+ emit_mode,
585
620
  )?;
586
621
  Ok((rust, native_modules, all_features, native_build))
587
622
  }
@@ -605,6 +640,26 @@ pub fn compile_with_native_modules(
605
640
  native_modules: &[crate::resolve::ResolvedNativeModule],
606
641
  native_init: &std::collections::HashMap<String, crate::resolve::NativeModuleInit>,
607
642
  optimize: bool,
643
+ ) -> Result<String, CompileError> {
644
+ compile_with_native_modules_emit(
645
+ program,
646
+ project_root,
647
+ features,
648
+ native_modules,
649
+ native_init,
650
+ optimize,
651
+ crate::NativeEmitMode::DesktopBin,
652
+ )
653
+ }
654
+
655
+ pub fn compile_with_native_modules_emit(
656
+ program: &Program,
657
+ project_root: Option<&Path>,
658
+ features: &[String],
659
+ native_modules: &[crate::resolve::ResolvedNativeModule],
660
+ native_init: &std::collections::HashMap<String, crate::resolve::NativeModuleInit>,
661
+ optimize: bool,
662
+ emit_mode: crate::NativeEmitMode,
608
663
  ) -> Result<String, CompileError> {
609
664
  let program = if optimize {
610
665
  tishlang_opt::optimize(program)
@@ -632,6 +687,13 @@ pub fn compile_with_native_modules(
632
687
  native_init.clone()
633
688
  };
634
689
  let mut g = Codegen::new_with_native_modules(project_root, features, map);
690
+ g.emit_mode = emit_mode;
691
+ g.has_native_ui_host = native_modules.iter().any(|m| {
692
+ m.package_name == "tish-macos"
693
+ || m.package_name == "tish-ios"
694
+ || m.crate_name == "tishlang_macos"
695
+ || m.crate_name == "tishlang_ios"
696
+ });
635
697
  g.emit_program(&program)?;
636
698
  Ok(g.output)
637
699
  }
@@ -682,6 +744,11 @@ struct Codegen {
682
744
  /// `try`/`throw` lowering uses `return Err` only at depth 0 (e.g. `run()`); inside native
683
745
  /// closures it must not return a `Result` from a `Value`-returning closure.
684
746
  value_fn_depth: u32,
747
+ emit_mode: crate::NativeEmitMode,
748
+ /// Program links `tish:macos` / `tish:ios` — skip HeadlessHost install.
749
+ has_native_ui_host: bool,
750
+ /// Program references browser global `document` — inject tish-canvas.
751
+ program_uses_document: bool,
685
752
  }
686
753
 
687
754
  impl Codegen {
@@ -712,6 +779,19 @@ impl Codegen {
712
779
  program_has_jsx: false,
713
780
  program_fun_decl_names: std::collections::HashSet::new(),
714
781
  value_fn_depth: 0,
782
+ emit_mode: crate::NativeEmitMode::DesktopBin,
783
+ has_native_ui_host: false,
784
+ program_uses_document: false,
785
+ }
786
+ }
787
+
788
+ /// In async `run()` bodies, propagate runtime op errors with `?`; in sync
789
+ /// `Value::native` closures use `.unwrap_or(Value::Null)`.
790
+ fn ops_result_suffix(&self) -> &'static str {
791
+ if self.is_async && self.async_context_stack.last().copied().unwrap_or(false) {
792
+ "?"
793
+ } else {
794
+ ".unwrap_or(Value::Null)"
715
795
  }
716
796
  }
717
797
 
@@ -731,12 +811,9 @@ impl Codegen {
731
811
  for _ in 0..8 {
732
812
  let mut changed = false;
733
813
  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();
814
+ let resolved =
815
+ crate::types::RustType::from_annotation_with_aliases(ann, &self.type_aliases);
816
+ let prev: Option<crate::types::RustType> = self.type_aliases.get(name).cloned();
740
817
  if prev.as_ref() != Some(&resolved) {
741
818
  self.type_aliases.insert(name.clone(), resolved);
742
819
  changed = true;
@@ -791,8 +868,11 @@ impl Codegen {
791
868
  fn emit_named_struct_decls(&mut self) {
792
869
  // Snapshot keys + values so we can mutate `self` (writing the
793
870
  // 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();
871
+ let mut entries: Vec<(String, crate::types::RustType)> = self
872
+ .type_aliases
873
+ .iter()
874
+ .map(|(k, v)| (k.clone(), v.clone()))
875
+ .collect();
796
876
  entries.sort_by(|a, b| a.0.cmp(&b.0));
797
877
  let mut emitted_any = false;
798
878
  for (name, ty) in entries {
@@ -928,7 +1008,7 @@ impl Codegen {
928
1008
  }
929
1009
  };
930
1010
  format!(
931
- "{{ let _ns = {}; match _ns {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }} }}",
1011
+ "{{ let _ns = {}; match _ns {{ Value::Object(ref _o) => _o.borrow().strings.get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }} }}",
932
1012
  init_expr, export_name
933
1013
  )
934
1014
  })
@@ -954,8 +1034,9 @@ impl Codegen {
954
1034
  // latter dispatches into `http_serve_per_worker`, which
955
1035
  // calls onWorker once per accept thread to build that
956
1036
  // 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 } })"),
1037
+ "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().strings.get(\"onWorker\").cloned().unwrap_or(Value::Null); tishlang_runtime::http_serve_per_worker(args, factory) }, _ => Value::Null } })"),
958
1038
  "Promise" => Some("tish_promise_object()"),
1039
+ "Symbol" => Some("tish_symbol_object()"),
959
1040
  _ => None,
960
1041
  },
961
1042
  "tish:timers" if self.has_feature("timers") => match export_name {
@@ -970,8 +1051,8 @@ impl Codegen {
970
1051
  "cwd" => Some("Value::native(|args: &[Value]| tish_process_cwd(args))"),
971
1052
  "exec" => Some("Value::native(|args: &[Value]| tish_process_exec(args))"),
972
1053
  "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)) }"),
1054
+ "env" => Some("Value::object(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect())"),
1055
+ "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(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect::<ObjectMap>())); Value::object(m) }"),
975
1056
  _ => None,
976
1057
  },
977
1058
  "tish:ws" if self.has_feature("ws") => match export_name {
@@ -1222,11 +1303,12 @@ impl Codegen {
1222
1303
  self.is_async = program_uses_async(program);
1223
1304
  self.program_has_jsx = tishlang_ui::jsx::program_contains_jsx(program);
1224
1305
  self.program_fun_decl_names = tishlang_ui::jsx::collect_fun_decl_names(program);
1306
+ self.program_uses_document = crate::resolve::program_uses_document(program);
1225
1307
  self.write("#![allow(unused, non_snake_case)]\n\n");
1226
1308
  self.write("use std::cell::RefCell;\n");
1227
1309
  self.write("use std::rc::Rc;\n");
1228
1310
  self.write("use std::sync::Arc;\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");
1311
+ self.write("use tishlang_runtime::{console_debug as tish_console_debug, console_info as tish_console_info, console_log as tish_console_log, console_warn as tish_console_warn, console_error as tish_console_error, boolean as tish_boolean, decode_uri as tish_decode_uri, encode_uri as tish_encode_uri, string_escape_html_impl as tish_escape_html, in_operator as tish_in_operator, is_finite as tish_is_finite, is_nan as tish_is_nan, json_parse as tish_json_parse, json_stringify as tish_json_stringify, math_abs as tish_math_abs, math_ceil as tish_math_ceil, math_floor as tish_math_floor, math_max as tish_math_max, math_min as tish_math_min, math_round as tish_math_round, math_sqrt as tish_math_sqrt, parse_float as tish_parse_float, parse_int as tish_parse_int, math_random as tish_math_random, math_pow as tish_math_pow, math_sin as tish_math_sin, math_cos as tish_math_cos, math_tan as tish_math_tan, math_log as tish_math_log, math_exp as tish_math_exp, math_sign as tish_math_sign, math_trunc as tish_math_trunc, math_imul as tish_math_imul, date_now as tish_date_now, array_is_array as tish_array_is_array, string_from_char_code as tish_string_from_char_code, object_assign as tish_object_assign, object_keys as tish_object_keys, object_values as tish_object_values, object_entries as tish_object_entries, object_from_entries as tish_object_from_entries, symbol_object as tish_symbol_object, tish_construct, tish_uint8_array_constructor, tish_audio_context_constructor, register_static_route as tish_register_static_route, ObjectMap, TishError, Value, VmRef};\n");
1230
1312
  if self.program_has_jsx {
1231
1313
  self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
1232
1314
  }
@@ -1252,6 +1334,9 @@ impl Codegen {
1252
1334
  if self.has_feature("regex") {
1253
1335
  self.write("use tishlang_runtime::regexp_new;\n");
1254
1336
  }
1337
+ if self.program_uses_document {
1338
+ self.write("use tish_canvas::document_value as tish_canvas_document;\n");
1339
+ }
1255
1340
  self.write("\n");
1256
1341
 
1257
1342
  // Collect every `type Foo = { ... }` declaration in the program
@@ -1267,35 +1352,39 @@ impl Codegen {
1267
1352
  // and `x.field` becomes a direct field access.
1268
1353
  self.emit_named_struct_decls();
1269
1354
 
1270
- if self.is_async {
1355
+ if self.is_async && self.emit_mode == crate::NativeEmitMode::DesktopBin {
1271
1356
  self.writeln("#[tokio::main]");
1272
1357
  self.writeln("async fn main() {");
1273
- } else {
1358
+ } else if self.emit_mode == crate::NativeEmitMode::DesktopBin {
1274
1359
  self.writeln("fn main() {");
1275
1360
  }
1276
- self.indent += 1;
1277
- if self.is_async {
1278
- self.writeln("if let Err(e) = run().await {");
1279
- } else {
1280
- self.writeln("if let Err(e) = run() {");
1361
+ if self.emit_mode == crate::NativeEmitMode::DesktopBin {
1362
+ self.indent += 1;
1363
+ if self.is_async {
1364
+ self.writeln("if let Err(e) = run().await {");
1365
+ } else {
1366
+ self.writeln("if let Err(e) = run() {");
1367
+ }
1368
+ self.indent += 1;
1369
+ self.writeln("eprintln!(\"Error: {}\", e);");
1370
+ self.writeln("std::process::exit(1);");
1371
+ self.indent -= 1;
1372
+ self.writeln("}");
1373
+ self.indent -= 1;
1374
+ self.writeln("}");
1375
+ self.writeln("");
1281
1376
  }
1282
- self.indent += 1;
1283
- self.writeln("eprintln!(\"Error: {}\", e);");
1284
- self.writeln("std::process::exit(1);");
1285
- self.indent -= 1;
1286
- self.writeln("}");
1287
- self.indent -= 1;
1288
- self.writeln("}");
1289
- self.writeln("");
1290
1377
  if self.is_async {
1291
1378
  self.writeln("async fn run() -> Result<(), Box<dyn std::error::Error>> {");
1379
+ } else if self.emit_mode == crate::NativeEmitMode::EmbeddedLib {
1380
+ self.writeln("pub fn run() -> Result<(), Box<dyn std::error::Error>> {");
1292
1381
  } else {
1293
1382
  self.writeln("fn run() -> Result<(), Box<dyn std::error::Error>> {");
1294
1383
  }
1295
1384
  self.indent += 1;
1296
1385
 
1297
1386
  // Initialize builtins
1298
- self.writeln("let mut console = Value::Object(VmRef::new(ObjectMap::from([");
1387
+ self.writeln("let mut console = Value::object(ObjectMap::from([");
1299
1388
  self.indent += 1;
1300
1389
  self.writeln("(Arc::from(\"debug\"), Value::native(|args: &[Value]| { tish_console_debug(args); Value::Null })),");
1301
1390
  self.writeln("(Arc::from(\"info\"), Value::native(|args: &[Value]| { tish_console_info(args); Value::Null })),");
@@ -1303,147 +1392,156 @@ impl Codegen {
1303
1392
  self.writeln("(Arc::from(\"warn\"), Value::native(|args: &[Value]| { tish_console_warn(args); Value::Null })),");
1304
1393
  self.writeln("(Arc::from(\"error\"), Value::native(|args: &[Value]| { tish_console_error(args); Value::Null })),");
1305
1394
  self.indent -= 1;
1306
- self.writeln("])));");
1307
- self.writeln(
1308
- "let Boolean = Value::native(|args: &[Value]| tish_boolean(args));",
1309
- );
1310
- self.writeln(
1311
- "let parseInt = Value::native(|args: &[Value]| tish_parse_int(args));",
1312
- );
1313
- self.writeln(
1314
- "let parseFloat = Value::native(|args: &[Value]| tish_parse_float(args));",
1315
- );
1316
- self.writeln(
1317
- "let decodeURI = Value::native(|args: &[Value]| tish_decode_uri(args));",
1318
- );
1319
- self.writeln(
1320
- "let encodeURI = Value::native(|args: &[Value]| tish_encode_uri(args));",
1321
- );
1395
+ self.writeln("]));");
1396
+ self.writeln("let Boolean = Value::native(|args: &[Value]| tish_boolean(args));");
1397
+ self.writeln("let parseInt = Value::native(|args: &[Value]| tish_parse_int(args));");
1398
+ self.writeln("let parseFloat = Value::native(|args: &[Value]| tish_parse_float(args));");
1399
+ self.writeln("let decodeURI = Value::native(|args: &[Value]| tish_decode_uri(args));");
1400
+ self.writeln("let encodeURI = Value::native(|args: &[Value]| tish_encode_uri(args));");
1322
1401
  self.writeln(
1323
1402
  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 });"#,
1324
1403
  );
1325
1404
  self.writeln(
1326
1405
  "let htmlEscape = Value::native(|args: &[Value]| tish_escape_html(args.first().unwrap_or(&Value::Null)));",
1327
1406
  );
1328
- self.writeln(
1329
- "let isFinite = Value::native(|args: &[Value]| tish_is_finite(args));",
1330
- );
1407
+ self.writeln("let isFinite = Value::native(|args: &[Value]| tish_is_finite(args));");
1331
1408
  self.writeln("let isNaN = Value::native(|args: &[Value]| tish_is_nan(args));");
1332
1409
  self.writeln("let Infinity = Value::Number(f64::INFINITY);");
1333
1410
  self.writeln("let NaN = Value::Number(f64::NAN);");
1334
- self.writeln("let Math = Value::Object(VmRef::new(ObjectMap::from([");
1411
+ self.writeln("let Math = Value::object(ObjectMap::from([");
1335
1412
  self.indent += 1;
1413
+ self.writeln("(Arc::from(\"abs\"), Value::native(|args: &[Value]| tish_math_abs(args))),");
1336
1414
  self.writeln(
1337
- "(Arc::from(\"abs\"), Value::native(|args: &[Value]| tish_math_abs(args))),",
1415
+ "(Arc::from(\"sqrt\"), Value::native(|args: &[Value]| tish_math_sqrt(args))),",
1338
1416
  );
1339
- self.writeln("(Arc::from(\"sqrt\"), Value::native(|args: &[Value]| tish_math_sqrt(args))),");
1417
+ self.writeln("(Arc::from(\"min\"), Value::native(|args: &[Value]| tish_math_min(args))),");
1418
+ self.writeln("(Arc::from(\"max\"), Value::native(|args: &[Value]| tish_math_max(args))),");
1340
1419
  self.writeln(
1341
- "(Arc::from(\"min\"), Value::native(|args: &[Value]| tish_math_min(args))),",
1420
+ "(Arc::from(\"floor\"), Value::native(|args: &[Value]| tish_math_floor(args))),",
1342
1421
  );
1343
1422
  self.writeln(
1344
- "(Arc::from(\"max\"), Value::native(|args: &[Value]| tish_math_max(args))),",
1423
+ "(Arc::from(\"ceil\"), Value::native(|args: &[Value]| tish_math_ceil(args))),",
1345
1424
  );
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))),");
1350
1425
  self.writeln(
1351
- "(Arc::from(\"pow\"), Value::native(|args: &[Value]| tish_math_pow(args))),",
1426
+ "(Arc::from(\"round\"), Value::native(|args: &[Value]| tish_math_round(args))),",
1352
1427
  );
1353
1428
  self.writeln(
1354
- "(Arc::from(\"sin\"), Value::native(|args: &[Value]| tish_math_sin(args))),",
1429
+ "(Arc::from(\"random\"), Value::native(|args: &[Value]| tish_math_random(args))),",
1355
1430
  );
1431
+ self.writeln("(Arc::from(\"pow\"), Value::native(|args: &[Value]| tish_math_pow(args))),");
1432
+ self.writeln("(Arc::from(\"sin\"), Value::native(|args: &[Value]| tish_math_sin(args))),");
1433
+ self.writeln("(Arc::from(\"cos\"), Value::native(|args: &[Value]| tish_math_cos(args))),");
1434
+ self.writeln("(Arc::from(\"tan\"), Value::native(|args: &[Value]| tish_math_tan(args))),");
1435
+ self.writeln("(Arc::from(\"log\"), Value::native(|args: &[Value]| tish_math_log(args))),");
1436
+ self.writeln("(Arc::from(\"exp\"), Value::native(|args: &[Value]| tish_math_exp(args))),");
1356
1437
  self.writeln(
1357
- "(Arc::from(\"cos\"), Value::native(|args: &[Value]| tish_math_cos(args))),",
1438
+ "(Arc::from(\"sign\"), Value::native(|args: &[Value]| tish_math_sign(args))),",
1358
1439
  );
1359
1440
  self.writeln(
1360
- "(Arc::from(\"tan\"), Value::native(|args: &[Value]| tish_math_tan(args))),",
1441
+ "(Arc::from(\"trunc\"), Value::native(|args: &[Value]| tish_math_trunc(args))),",
1361
1442
  );
1362
1443
  self.writeln(
1363
- "(Arc::from(\"log\"), Value::native(|args: &[Value]| tish_math_log(args))),",
1444
+ "(Arc::from(\"imul\"), Value::native(|args: &[Value]| tish_math_imul(args))),",
1364
1445
  );
1365
- self.writeln(
1366
- "(Arc::from(\"exp\"), Value::native(|args: &[Value]| tish_math_exp(args))),",
1367
- );
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))),");
1370
1446
  self.writeln("(Arc::from(\"PI\"), Value::Number(std::f64::consts::PI)),");
1371
1447
  self.writeln("(Arc::from(\"E\"), Value::Number(std::f64::consts::E)),");
1372
1448
  self.indent -= 1;
1373
- self.writeln("])));");
1374
- self.writeln("let JSON = Value::Object(VmRef::new(ObjectMap::from([");
1449
+ self.writeln("]));");
1450
+ self.writeln("let JSON = Value::object(ObjectMap::from([");
1375
1451
  self.indent += 1;
1376
- self.writeln("(Arc::from(\"parse\"), Value::native(|args: &[Value]| tish_json_parse(args))),");
1452
+ self.writeln(
1453
+ "(Arc::from(\"parse\"), Value::native(|args: &[Value]| tish_json_parse(args))),",
1454
+ );
1377
1455
  self.writeln("(Arc::from(\"stringify\"), Value::native(|args: &[Value]| tish_json_stringify(args))),");
1378
1456
  self.indent -= 1;
1379
- self.writeln("])));");
1457
+ self.writeln("]));");
1380
1458
 
1381
- self.writeln("let Array = Value::Object(VmRef::new(ObjectMap::from([");
1459
+ self.writeln("let Array = Value::object(ObjectMap::from([");
1382
1460
  self.indent += 1;
1383
- self.writeln("(Arc::from(\"isArray\"), Value::native(|args: &[Value]| tish_array_is_array(args))),");
1461
+ self.writeln(
1462
+ "(Arc::from(\"isArray\"), Value::native(|args: &[Value]| tish_array_is_array(args))),",
1463
+ );
1384
1464
  self.indent -= 1;
1385
- self.writeln("])));");
1465
+ self.writeln("]));");
1386
1466
 
1387
- self.writeln("let String = Value::Object(VmRef::new(ObjectMap::from([");
1467
+ self.writeln("let String = Value::object(ObjectMap::from([");
1388
1468
  self.indent += 1;
1389
1469
  self.writeln("(Arc::from(\"fromCharCode\"), Value::native(|args: &[Value]| tish_string_from_char_code(args))),");
1390
1470
  self.indent -= 1;
1391
- self.writeln("])));");
1471
+ self.writeln("]));");
1392
1472
 
1393
- self.writeln("let Date = Value::Object(VmRef::new(ObjectMap::from([");
1473
+ self.writeln("let Date = Value::object(ObjectMap::from([");
1394
1474
  self.indent += 1;
1395
- self.writeln(
1396
- "(Arc::from(\"now\"), Value::native(|args: &[Value]| tish_date_now(args))),",
1397
- );
1475
+ self.writeln("(Arc::from(\"now\"), Value::native(|args: &[Value]| tish_date_now(args))),");
1398
1476
  self.indent -= 1;
1399
- self.writeln("])));");
1477
+ self.writeln("]));");
1400
1478
 
1401
- self.writeln("let Object = Value::Object(VmRef::new(ObjectMap::from([");
1479
+ self.writeln("let Symbol = tish_symbol_object();");
1480
+
1481
+ self.writeln("let Object = Value::object(ObjectMap::from([");
1402
1482
  self.indent += 1;
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))),");
1483
+ self.writeln(
1484
+ "(Arc::from(\"assign\"), Value::native(|args: &[Value]| tish_object_assign(args))),",
1485
+ );
1486
+ self.writeln(
1487
+ "(Arc::from(\"keys\"), Value::native(|args: &[Value]| tish_object_keys(args))),",
1488
+ );
1489
+ self.writeln(
1490
+ "(Arc::from(\"values\"), Value::native(|args: &[Value]| tish_object_values(args))),",
1491
+ );
1492
+ self.writeln(
1493
+ "(Arc::from(\"entries\"), Value::native(|args: &[Value]| tish_object_entries(args))),",
1494
+ );
1407
1495
  self.writeln("(Arc::from(\"fromEntries\"), Value::native(|args: &[Value]| tish_object_from_entries(args))),");
1408
1496
  self.indent -= 1;
1409
- self.writeln("])));");
1497
+ self.writeln("]));");
1410
1498
 
1411
1499
  self.writeln("let Uint8Array = tish_uint8_array_constructor();");
1412
1500
  self.writeln("let AudioContext = tish_audio_context_constructor();");
1501
+ if self.program_uses_document {
1502
+ self.writeln("let document = VmRef::new(tish_canvas_document());");
1503
+ self.refcell_wrapped_vars.insert("document".to_string());
1504
+ self.rc_cell_storage_define("document");
1505
+ if let Some(scope) = self.outer_vars_stack.last_mut() {
1506
+ scope.push("document".to_string());
1507
+ }
1508
+ }
1413
1509
 
1414
1510
  if self.has_feature("process") {
1415
- self.writeln("let process = Value::Object(VmRef::new({");
1511
+ self.writeln("let process = Value::object({");
1416
1512
  self.indent += 1;
1417
1513
  self.writeln("let mut p = ObjectMap::default();");
1418
1514
  self.writeln("p.insert(Arc::from(\"exit\"), Value::native(|args: &[Value]| tish_process_exit(args)));");
1419
1515
  self.writeln("p.insert(Arc::from(\"cwd\"), Value::native(|args: &[Value]| tish_process_cwd(args)));");
1420
1516
  self.writeln("p.insert(Arc::from(\"exec\"), Value::native(|args: &[Value]| tish_process_exec(args)));");
1421
1517
  self.writeln("let argv: Vec<Value> = std::env::args().map(|s| Value::String(s.into())).collect();");
1422
- self.writeln(
1423
- "p.insert(Arc::from(\"argv\"), Value::Array(VmRef::new(argv)));",
1424
- );
1518
+ self.writeln("p.insert(Arc::from(\"argv\"), Value::Array(VmRef::new(argv)));");
1425
1519
  self.writeln("let mut env_obj = ObjectMap::default();");
1426
1520
  self.writeln("for (key, value) in std::env::vars() {");
1427
1521
  self.indent += 1;
1428
1522
  self.writeln("env_obj.insert(Arc::from(key.as_str()), Value::String(value.into()));");
1429
1523
  self.indent -= 1;
1430
1524
  self.writeln("}");
1431
- self.writeln(
1432
- "p.insert(Arc::from(\"env\"), Value::Object(VmRef::new(env_obj)));",
1433
- );
1525
+ self.writeln("p.insert(Arc::from(\"env\"), Value::object(env_obj));");
1434
1526
  self.writeln("p");
1435
1527
  self.indent -= 1;
1436
- self.writeln("}));");
1528
+ self.writeln("});");
1437
1529
  }
1438
1530
 
1439
1531
  if self.has_feature("timers") {
1440
- self.writeln("let setTimeout = Value::native(|args: &[Value]| tish_timer_set_timeout(args));");
1532
+ self.writeln(
1533
+ "let setTimeout = Value::native(|args: &[Value]| tish_timer_set_timeout(args));",
1534
+ );
1441
1535
  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));");
1536
+ self.writeln(
1537
+ "let setInterval = Value::native(|args: &[Value]| tish_timer_set_interval(args));",
1538
+ );
1443
1539
  self.writeln("let clearInterval = Value::native(|args: &[Value]| tish_timer_clear_interval(args));");
1444
1540
  }
1445
1541
  if self.has_feature("http") {
1446
- self.writeln("let fetch = Value::native(|args: &[Value]| tish_fetch_promise(args.to_vec()));");
1542
+ self.writeln(
1543
+ "let fetch = Value::native(|args: &[Value]| tish_fetch_promise(args.to_vec()));",
1544
+ );
1447
1545
  self.writeln("let fetchAll = Value::native(|args: &[Value]| tish_fetch_all_promise(args.to_vec()));");
1448
1546
  if self.is_async {
1449
1547
  self.writeln("let Promise = tish_promise_object();");
@@ -1461,10 +1559,12 @@ impl Codegen {
1461
1559
  self.writeln("let handler = args.get(1).cloned().unwrap_or(Value::Null);");
1462
1560
  self.writeln("match handler {");
1463
1561
  self.indent += 1;
1464
- self.writeln("Value::Function(f) => tish_http_serve(args, move |req_args| f(req_args)),");
1562
+ self.writeln(
1563
+ "Value::Function(f) => tish_http_serve(args, move |req_args| f(req_args)),",
1564
+ );
1465
1565
  self.writeln("Value::Object(ref opts) => {");
1466
1566
  self.indent += 1;
1467
- self.writeln("let factory = opts.borrow().get(&Arc::from(\"onWorker\")).cloned().unwrap_or(Value::Null);");
1567
+ self.writeln("let factory = opts.borrow().strings.get(\"onWorker\").cloned().unwrap_or(Value::Null);");
1468
1568
  self.writeln("tishlang_runtime::http_serve_per_worker(args, factory)");
1469
1569
  self.indent -= 1;
1470
1570
  self.writeln("},");
@@ -1476,39 +1576,29 @@ impl Codegen {
1476
1576
  }
1477
1577
 
1478
1578
  if self.has_feature("fs") {
1579
+ self.writeln("let readFile = Value::native(|args: &[Value]| tish_read_file(args));");
1580
+ self.writeln("let writeFile = Value::native(|args: &[Value]| tish_write_file(args));");
1479
1581
  self.writeln(
1480
- "let readFile = Value::native(|args: &[Value]| tish_read_file(args));",
1481
- );
1482
- self.writeln(
1483
- "let writeFile = Value::native(|args: &[Value]| tish_write_file(args));",
1484
- );
1485
- self.writeln("let fileExists = Value::native(|args: &[Value]| tish_file_exists(args));");
1486
- self.writeln(
1487
- "let isDir = Value::native(|args: &[Value]| tish_is_dir(args));",
1488
- );
1489
- self.writeln(
1490
- "let readDir = Value::native(|args: &[Value]| tish_read_dir(args));",
1491
- );
1492
- self.writeln(
1493
- "let mkdir = Value::native(|args: &[Value]| tish_mkdir(args));",
1582
+ "let fileExists = Value::native(|args: &[Value]| tish_file_exists(args));",
1494
1583
  );
1584
+ self.writeln("let isDir = Value::native(|args: &[Value]| tish_is_dir(args));");
1585
+ self.writeln("let readDir = Value::native(|args: &[Value]| tish_read_dir(args));");
1586
+ self.writeln("let mkdir = Value::native(|args: &[Value]| tish_mkdir(args));");
1495
1587
  }
1496
1588
 
1497
1589
  if self.has_feature("regex") {
1498
- self.writeln(
1499
- "let RegExp = Value::native(|args: &[Value]| regexp_new(args));",
1500
- );
1590
+ self.writeln("let RegExp = Value::native(|args: &[Value]| regexp_new(args));");
1501
1591
  }
1502
1592
 
1503
- if self.program_has_jsx {
1593
+ if self.program_has_jsx && !self.has_native_ui_host {
1504
1594
  self.writeln("install_thread_local_host(Box::new(HeadlessHost::default()));");
1505
1595
  self.writeln("let Fragment = fragment_value();");
1506
1596
  self.writeln("let h = Value::native(|args: &[Value]| ui_h(args));");
1507
1597
  self.writeln("let text = Value::native(|args: &[Value]| ui_text(args));");
1598
+ self.writeln("let useState = Value::native(|args: &[Value]| native_use_state(args));");
1508
1599
  self.writeln(
1509
- "let useState = Value::native(|args: &[Value]| native_use_state(args));",
1600
+ "let createRoot = Value::native(|args: &[Value]| native_create_root(args));",
1510
1601
  );
1511
- self.writeln("let createRoot = Value::native(|args: &[Value]| native_create_root(args));");
1512
1602
  }
1513
1603
 
1514
1604
  // Polars, Egui etc. are emitted via VarDecl from import { X } from 'tish:...'
@@ -1548,6 +1638,20 @@ impl Codegen {
1548
1638
  self.writeln("Ok(())");
1549
1639
  self.indent -= 1;
1550
1640
  self.writeln("}");
1641
+ if self.emit_mode == crate::NativeEmitMode::EmbeddedLib {
1642
+ self.writeln("");
1643
+ self.writeln("#[no_mangle]");
1644
+ self.writeln("pub extern \"C\" fn tish_ios_launch() {");
1645
+ self.indent += 1;
1646
+ if self.is_async {
1647
+ self.writeln("let rt = tokio::runtime::Runtime::new().expect(\"tokio runtime\");");
1648
+ self.writeln("let _ = rt.block_on(run());");
1649
+ } else {
1650
+ self.writeln("let _ = run();");
1651
+ }
1652
+ self.indent -= 1;
1653
+ self.writeln("}");
1654
+ }
1551
1655
  Ok(())
1552
1656
  }
1553
1657
 
@@ -1622,10 +1726,7 @@ impl Codegen {
1622
1726
  };
1623
1727
  if self.refcell_wrapped_vars.contains(name.as_ref()) {
1624
1728
  // Closure-mutated: same Rc<RefCell<T>> pattern as Value (assignments use borrow_mut)
1625
- self.writeln(&format!(
1626
- "let {} = VmRef::new({});",
1627
- escaped_name, expr_str
1628
- ));
1729
+ self.writeln(&format!("let {} = VmRef::new({});", escaped_name, expr_str));
1629
1730
  self.rc_cell_storage_define(name.as_ref());
1630
1731
  } else {
1631
1732
  let type_str = rust_type.to_rust_type_str();
@@ -1653,10 +1754,7 @@ impl Codegen {
1653
1754
  } else {
1654
1755
  expr_str.to_string()
1655
1756
  };
1656
- self.writeln(&format!(
1657
- "let {} = VmRef::new({});",
1658
- escaped_name, init_val
1659
- ));
1757
+ self.writeln(&format!("let {} = VmRef::new({});", escaped_name, init_val));
1660
1758
  self.rc_cell_storage_define(name.as_ref());
1661
1759
  } else if clone_needed {
1662
1760
  self.writeln(&format!(
@@ -2036,6 +2134,7 @@ impl Codegen {
2036
2134
  "setInterval",
2037
2135
  "clearInterval",
2038
2136
  "Promise",
2137
+ "Symbol",
2039
2138
  "RegExp",
2040
2139
  "Polars",
2041
2140
  ]
@@ -2136,6 +2235,7 @@ impl Codegen {
2136
2235
  "setInterval",
2137
2236
  "clearInterval",
2138
2237
  "Promise",
2238
+ "Symbol",
2139
2239
  "RegExp",
2140
2240
  "Polars",
2141
2241
  // Free-standing global functions used inside user-defined
@@ -2242,6 +2342,10 @@ impl Codegen {
2242
2342
  for v in &mutable_outer_vars {
2243
2343
  self.refcell_wrapped_vars.insert(v.clone());
2244
2344
  }
2345
+ // Read-only captures are plain Value bindings inside the closure.
2346
+ for v in &read_only_outer_vars {
2347
+ self.refcell_wrapped_vars.remove(v);
2348
+ }
2245
2349
 
2246
2350
  // Pre-scan body for nested functions (handles function body as Block)
2247
2351
  if let Statement::Block { statements, .. } = body.as_ref() {
@@ -2409,7 +2513,7 @@ impl Codegen {
2409
2513
  match &prop.value {
2410
2514
  DestructElement::Ident(name, _) => {
2411
2515
  self.writeln(&format!(
2412
- "{} {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
2516
+ "{} {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().strings.get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
2413
2517
  mutability,
2414
2518
  Self::escape_ident(name.as_ref()),
2415
2519
  value_expr,
@@ -2419,7 +2523,7 @@ impl Codegen {
2419
2523
  DestructElement::Pattern(nested) => {
2420
2524
  let nested_var = format!("_nested_obj_{}", key);
2421
2525
  self.writeln(&format!(
2422
- "let {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
2526
+ "let {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().strings.get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
2423
2527
  nested_var, value_expr, key
2424
2528
  ));
2425
2529
  self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
@@ -2502,7 +2606,12 @@ impl Codegen {
2502
2606
  // Convert native type to Value for compatibility with existing code
2503
2607
  var_type.to_value_expr(&escaped)
2504
2608
  } else {
2505
- escaped.into_owned()
2609
+ let s = escaped.into_owned();
2610
+ if self.value_fn_depth > 0 || !self.loop_stack.is_empty() {
2611
+ format!("({}).clone()", s)
2612
+ } else {
2613
+ s
2614
+ }
2506
2615
  }
2507
2616
  }
2508
2617
  }
@@ -2760,6 +2869,14 @@ impl Codegen {
2760
2869
  obj_expr, start, end
2761
2870
  ));
2762
2871
  }
2872
+ "substr" => {
2873
+ let start = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Number(0.0)".to_string());
2874
+ let length = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
2875
+ return Ok(format!(
2876
+ "tishlang_runtime::string_substr(&{}, &{}, &{})",
2877
+ obj_expr, start, length
2878
+ ));
2879
+ }
2763
2880
  "split" => {
2764
2881
  let sep = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
2765
2882
  return Ok(format!(
@@ -2980,7 +3097,7 @@ impl Codegen {
2980
3097
  if has_spread {
2981
3098
  let args_code = self.emit_call_args(args)?;
2982
3099
  return Ok(format!(
2983
- "{{ let _callee = &{}; let _spread_args = {}; match _callee {{ Value::Function(cb) => cb(&_spread_args), other => panic!(\"Not a function: tried to call {{:?}} as a function (e.g. method on Null when read failed)\", other) }} }}",
3100
+ "{{ let _callee = ({}).clone(); let _spread_args = {}; tishlang_runtime::value_call(&_callee, _spread_args.as_slice()) }}",
2984
3101
  callee_expr, args_code
2985
3102
  ));
2986
3103
  }
@@ -2994,8 +3111,8 @@ impl Codegen {
2994
3111
  .join(", ");
2995
3112
  format!(
2996
3113
  "({{\n\
2997
- {} let _callee = &{};\n\
2998
- {} match _callee {{ Value::Function(cb) => cb(&[{}]), other => panic!(\"Not a function: tried to call {{:?}} as a function (e.g. method on Null when read failed)\", other) }}\n\
3114
+ {} let _callee = ({}).clone();\n\
3115
+ {} tishlang_runtime::value_call(&_callee, &[{}])\n\
2999
3116
  {}}})",
3000
3117
  " ".repeat(self.indent),
3001
3118
  callee_expr,
@@ -3160,11 +3277,11 @@ impl Codegen {
3160
3277
  }
3161
3278
  ObjectProp::Spread(e) => {
3162
3279
  let val = self.emit_expr(e)?;
3163
- parts.push(format!("if let Value::Object(ref _spread) = {} {{ for (k, v) in _spread.borrow().iter() {{ _obj.insert(Arc::clone(k), v.clone()); }} }}", val));
3280
+ parts.push(format!("if let Value::Object(ref _spread) = {} {{ for (k, v) in _spread.borrow().strings.iter() {{ _obj.insert(Arc::clone(k), v.clone()); }} }}", val));
3164
3281
  }
3165
3282
  }
3166
3283
  }
3167
- format!("{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::Object(VmRef::new(_obj)) }}", parts.join(" "))
3284
+ format!("{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::object(_obj) }}", parts.join(" "))
3168
3285
  } else {
3169
3286
  let mut parts = Vec::new();
3170
3287
  for prop in props {
@@ -3178,7 +3295,7 @@ impl Codegen {
3178
3295
  }
3179
3296
  }
3180
3297
  format!(
3181
- "Value::Object(VmRef::new(ObjectMap::from([{}])))",
3298
+ "Value::object(ObjectMap::from([{}]))",
3182
3299
  parts.join(", ")
3183
3300
  )
3184
3301
  }
@@ -3274,7 +3391,8 @@ impl Codegen {
3274
3391
  Value::Number(_) => \"number\".into(), Value::String(_) => \"string\".into(), \
3275
3392
  Value::Bool(_) => \"boolean\".into(), Value::Null => \"null\".into(), \
3276
3393
  Value::Array(_) => \"object\".into(), Value::Object(_) => \"object\".into(), \
3277
- Value::Function(_) => \"function\".into(), _ => \"object\".into() }})",
3394
+ Value::Function(_) => \"function\".into(), Value::Symbol(_) => \"symbol\".into(), \
3395
+ _ => \"object\".into() }})",
3278
3396
  o
3279
3397
  )
3280
3398
  }
@@ -3400,10 +3518,11 @@ impl Codegen {
3400
3518
  CompoundOp::Div => "div",
3401
3519
  CompoundOp::Mod => "modulo",
3402
3520
  };
3521
+ let op_suffix = self.ops_result_suffix();
3403
3522
  if is_refcell {
3404
3523
  format!(
3405
- "{{ let _lhs_v = (*{}.borrow()).clone(); let _rhs = ({}).clone(); let _new = tishlang_runtime::ops::{}(&_lhs_v, &_rhs)?; *{}.borrow_mut() = _new; (*{}.borrow()).clone() }}",
3406
- n, val, op_fn, n, n
3524
+ "{{ let _lhs_v = (*{}.borrow()).clone(); let _rhs = ({}).clone(); let _new = tishlang_runtime::ops::{}(&_lhs_v, &_rhs){}; *{}.borrow_mut() = _new; (*{}.borrow()).clone() }}",
3525
+ n, val, op_fn, op_suffix, n, n
3407
3526
  )
3408
3527
  } else if var_type.is_native() {
3409
3528
  // Wrap native lhs as Value, run ops::, unbox result back to native
@@ -3411,13 +3530,13 @@ impl Codegen {
3411
3530
  let result_native = var_type.from_value_expr("_result");
3412
3531
  let n_as_value2 = var_type.to_value_expr(&n);
3413
3532
  format!(
3414
- "{{ let _lhs = {}; let _rhs = ({}).clone(); let _result = tishlang_runtime::ops::{}(&_lhs, &_rhs)?; {} = {}; {} }}",
3415
- n_as_value, val, op_fn, n, result_native, n_as_value2
3533
+ "{{ let _lhs = {}; let _rhs = ({}).clone(); let _result = tishlang_runtime::ops::{}(&_lhs, &_rhs){}; {} = {}; {} }}",
3534
+ n_as_value, val, op_fn, op_suffix, n, result_native, n_as_value2
3416
3535
  )
3417
3536
  } else {
3418
3537
  format!(
3419
- "{{ let _rhs = ({}).clone(); {} = tishlang_runtime::ops::{}(&{}, &_rhs)?; {}.clone() }}",
3420
- val, n, op_fn, n, n
3538
+ "{{ let _rhs = ({}).clone(); {} = tishlang_runtime::ops::{}(&{}, &_rhs){}; {}.clone() }}",
3539
+ val, n, op_fn, n, op_suffix, n
3421
3540
  )
3422
3541
  }
3423
3542
  }
@@ -4606,6 +4725,7 @@ impl Codegen {
4606
4725
  "setInterval",
4607
4726
  "clearInterval",
4608
4727
  "Promise",
4728
+ "Symbol",
4609
4729
  "RegExp",
4610
4730
  "Polars",
4611
4731
  ]
@@ -4679,6 +4799,7 @@ impl Codegen {
4679
4799
  "setInterval",
4680
4800
  "clearInterval",
4681
4801
  "Promise",
4802
+ "Symbol",
4682
4803
  "RegExp",
4683
4804
  "Polars",
4684
4805
  ] {
@@ -4707,6 +4828,41 @@ impl Codegen {
4707
4828
  ));
4708
4829
  }
4709
4830
 
4831
+ // Locals from an enclosing Value::native (e.g. captured helper fns) are not on
4832
+ // outer_vars_stack but must not move into multiple sibling closures.
4833
+ const BUILTINS: &[&str] = &[
4834
+ "Boolean", "console", "Math", "JSON", "Date", "Object", "process",
4835
+ "setTimeout", "clearTimeout", "setInterval", "clearInterval", "Promise",
4836
+ "Symbol", "RegExp", "Polars", "Infinity", "NaN", "serve",
4837
+ ];
4838
+ let mut already_captured: HashSet<String> = outer_vars
4839
+ .iter()
4840
+ .chain(outer_params.iter())
4841
+ .chain(referenced_funcs.iter())
4842
+ .cloned()
4843
+ .collect();
4844
+ already_captured.extend(BUILTINS.iter().map(|s| s.to_string()));
4845
+ let implicit_env_captures: Vec<String> = if self.value_fn_depth > 0 {
4846
+ referenced
4847
+ .iter()
4848
+ .filter(|name| {
4849
+ !param_names.contains(name.as_str())
4850
+ && !local_var_names.contains(name.as_str())
4851
+ && !already_captured.contains(name.as_str())
4852
+ })
4853
+ .cloned()
4854
+ .collect()
4855
+ } else {
4856
+ Vec::new()
4857
+ };
4858
+ for name in &implicit_env_captures {
4859
+ let escaped = Self::escape_ident(name);
4860
+ code.push_str(&format!(
4861
+ " let {}_ref = VmRef::new({}.clone());\n",
4862
+ escaped, escaped
4863
+ ));
4864
+ }
4865
+
4710
4866
  code.push_str(" Value::native(move |args: &[Value]| {\n");
4711
4867
  self.value_fn_depth += 1;
4712
4868
 
@@ -4734,6 +4890,13 @@ impl Codegen {
4734
4890
  var_escaped, var_escaped
4735
4891
  ));
4736
4892
  }
4893
+ for name in &implicit_env_captures {
4894
+ let escaped = Self::escape_ident(name);
4895
+ code.push_str(&format!(
4896
+ " let {} = (*{}_ref.borrow()).clone();\n",
4897
+ escaped, escaped
4898
+ ));
4899
+ }
4737
4900
 
4738
4901
  // Make captured functions available
4739
4902
  for func_name in &referenced_funcs {
@@ -4786,6 +4949,12 @@ impl Codegen {
4786
4949
  for v in &cell_capture_outer_vars {
4787
4950
  self.refcell_wrapped_vars.insert(v.clone());
4788
4951
  }
4952
+ for v in &read_only_outer_vars {
4953
+ self.refcell_wrapped_vars.remove(v);
4954
+ }
4955
+ for v in &implicit_env_captures {
4956
+ self.refcell_wrapped_vars.remove(v);
4957
+ }
4789
4958
 
4790
4959
  self.type_context.push_fun_param_scope(params, None);
4791
4960
 
@@ -4886,14 +5055,11 @@ impl Codegen {
4886
5055
  // alias). Each property in source order is matched to a struct
4887
5056
  // field; missing fields fall back to `default_value()` so the
4888
5057
  // emit succeeds even on partial literals (rare, but harmless).
4889
- if let (RustType::Named { name, fields }, Expr::Object { props, .. }) =
4890
- (target_type, expr)
5058
+ if let (RustType::Named { name, fields }, Expr::Object { props, .. }) = (target_type, expr)
4891
5059
  {
4892
5060
  use std::collections::HashMap;
4893
- let field_types: HashMap<&str, &RustType> = fields
4894
- .iter()
4895
- .map(|(k, t)| (k.as_ref(), t))
4896
- .collect();
5061
+ let field_types: HashMap<&str, &RustType> =
5062
+ fields.iter().map(|(k, t)| (k.as_ref(), t)).collect();
4897
5063
  let mut field_inits: HashMap<String, String> = HashMap::new();
4898
5064
  let mut bail = false;
4899
5065
  for prop in props {