@tishlang/tish 1.4.2 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/tish +0 -0
- package/crates/tish/Cargo.toml +2 -2
- package/crates/tish/src/cli_help.rs +504 -0
- package/crates/tish/src/main.rs +76 -90
- package/crates/tish/src/repl_completion.rs +1 -1
- package/crates/tish/tests/cargo_example_compile.rs +65 -0
- package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
- package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
- package/crates/tish/tests/integration_test.rs +48 -0
- package/crates/tish_build_utils/src/lib.rs +204 -1
- package/crates/tish_builtins/src/string.rs +248 -0
- package/crates/tish_bytecode/Cargo.toml +1 -0
- package/crates/tish_bytecode/src/compiler.rs +289 -66
- package/crates/tish_bytecode/src/opcode.rs +13 -3
- package/crates/tish_bytecode/src/peephole.rs +21 -16
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +277 -93
- package/crates/tish_compile/src/lib.rs +7 -4
- package/crates/tish_compile/src/resolve.rs +418 -40
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +1 -0
- package/crates/tish_core/src/value.rs +1 -0
- package/crates/tish_eval/src/eval.rs +49 -1
- package/crates/tish_eval/src/lib.rs +1 -1
- package/crates/tish_native/src/build.rs +86 -17
- package/crates/tish_native/src/lib.rs +36 -16
- package/crates/tish_runtime/src/lib.rs +4 -0
- package/crates/tish_vm/src/lib.rs +1 -1
- package/crates/tish_vm/src/vm.rs +165 -19
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -393,17 +393,27 @@ pub fn compile_project(
|
|
|
393
393
|
project_root: Option<&Path>,
|
|
394
394
|
features: &[String],
|
|
395
395
|
) -> Result<String, CompileError> {
|
|
396
|
-
let (rust, _) = compile_project_full(entry_path, project_root, features, true)?;
|
|
396
|
+
let (rust, _, _, _) = compile_project_full(entry_path, project_root, features, true)?;
|
|
397
397
|
Ok(rust)
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
/// Compile a project and return Rust code
|
|
400
|
+
/// Compile a project and return Rust code, resolved native modules, the **effective** feature list
|
|
401
|
+
/// (CLI features plus any inferred from `tish:fs` / `tish:http` / … imports), and native build
|
|
402
|
+
/// artifacts (Cargo dep lines, optional `generated_native.rs` source, init strategy per spec).
|
|
401
403
|
pub fn compile_project_full(
|
|
402
404
|
entry_path: &Path,
|
|
403
405
|
project_root: Option<&Path>,
|
|
404
406
|
features: &[String],
|
|
405
407
|
optimize: bool,
|
|
406
|
-
) -> Result<
|
|
408
|
+
) -> Result<
|
|
409
|
+
(
|
|
410
|
+
String,
|
|
411
|
+
Vec<crate::resolve::ResolvedNativeModule>,
|
|
412
|
+
Vec<String>,
|
|
413
|
+
crate::resolve::NativeBuildArtifacts,
|
|
414
|
+
),
|
|
415
|
+
CompileError,
|
|
416
|
+
> {
|
|
407
417
|
use crate::resolve;
|
|
408
418
|
let root = project_root.unwrap_or_else(|| entry_path.parent().unwrap_or(Path::new(".")));
|
|
409
419
|
let modules = resolve::resolve_project(entry_path, project_root)
|
|
@@ -414,14 +424,23 @@ pub fn compile_project_full(
|
|
|
414
424
|
.map_err(|e| CompileError { message: e, span: None })?;
|
|
415
425
|
let native_modules = resolve::resolve_native_modules(&merged, root)
|
|
416
426
|
.map_err(|e| CompileError { message: e, span: None })?;
|
|
427
|
+
let native_build = resolve::compute_native_build_artifacts(&merged, root, &native_modules)
|
|
428
|
+
.map_err(|e| CompileError { message: e, span: None })?;
|
|
417
429
|
let mut all_features: Vec<String> = features.to_vec();
|
|
418
430
|
for f in resolve::extract_native_import_features(&merged) {
|
|
419
431
|
if !all_features.contains(&f) {
|
|
420
432
|
all_features.push(f);
|
|
421
433
|
}
|
|
422
434
|
}
|
|
423
|
-
let rust = compile_with_native_modules(
|
|
424
|
-
|
|
435
|
+
let rust = compile_with_native_modules(
|
|
436
|
+
&merged,
|
|
437
|
+
project_root,
|
|
438
|
+
&all_features,
|
|
439
|
+
&native_modules,
|
|
440
|
+
&native_build.native_init,
|
|
441
|
+
optimize,
|
|
442
|
+
)?;
|
|
443
|
+
Ok((rust, native_modules, all_features, native_build))
|
|
425
444
|
}
|
|
426
445
|
|
|
427
446
|
/// Compile with explicit feature flags. When features are provided, codegen uses them
|
|
@@ -431,7 +450,8 @@ pub fn compile_with_features(
|
|
|
431
450
|
project_root: Option<&Path>,
|
|
432
451
|
features: &[String],
|
|
433
452
|
) -> Result<String, CompileError> {
|
|
434
|
-
|
|
453
|
+
let empty = std::collections::HashMap::new();
|
|
454
|
+
compile_with_native_modules(program, project_root, features, &[], &empty, true)
|
|
435
455
|
}
|
|
436
456
|
|
|
437
457
|
/// Compile with resolved native modules. Native imports emit calls to the module crates directly.
|
|
@@ -440,16 +460,30 @@ pub fn compile_with_native_modules(
|
|
|
440
460
|
project_root: Option<&Path>,
|
|
441
461
|
features: &[String],
|
|
442
462
|
native_modules: &[crate::resolve::ResolvedNativeModule],
|
|
463
|
+
native_init: &std::collections::HashMap<String, crate::resolve::NativeModuleInit>,
|
|
443
464
|
optimize: bool,
|
|
444
465
|
) -> Result<String, CompileError> {
|
|
445
466
|
let program = if optimize { tishlang_opt::optimize(program) } else { program.clone() };
|
|
446
467
|
// Type-inference pass: fills in `type_ann` on unannotated VarDecl nodes where
|
|
447
468
|
// the type is unambiguous (literals, arithmetic of typed vars, etc.).
|
|
448
469
|
let program = crate::infer::infer_program(&program);
|
|
449
|
-
let map: std::collections::HashMap<String,
|
|
450
|
-
.
|
|
451
|
-
|
|
452
|
-
|
|
470
|
+
let map: std::collections::HashMap<String, crate::resolve::NativeModuleInit> =
|
|
471
|
+
if native_init.is_empty() {
|
|
472
|
+
native_modules
|
|
473
|
+
.iter()
|
|
474
|
+
.map(|m| {
|
|
475
|
+
(
|
|
476
|
+
m.spec.clone(),
|
|
477
|
+
crate::resolve::NativeModuleInit::Legacy {
|
|
478
|
+
crate_name: m.crate_name.clone(),
|
|
479
|
+
export_fn: m.export_fn.clone(),
|
|
480
|
+
},
|
|
481
|
+
)
|
|
482
|
+
})
|
|
483
|
+
.collect()
|
|
484
|
+
} else {
|
|
485
|
+
native_init.clone()
|
|
486
|
+
};
|
|
453
487
|
let mut g = Codegen::new_with_native_modules(project_root, features, map);
|
|
454
488
|
g.emit_program(&program)?;
|
|
455
489
|
Ok(g.output)
|
|
@@ -463,8 +497,8 @@ struct Codegen {
|
|
|
463
497
|
project_root: Option<std::path::PathBuf>,
|
|
464
498
|
/// Requested features (http, process, fs, regex, polars). When non-empty, used instead of #[cfg].
|
|
465
499
|
features: std::collections::HashSet<String>,
|
|
466
|
-
/// spec -> (
|
|
467
|
-
|
|
500
|
+
/// spec -> native init strategy (legacy adapter object vs generated `generated_native` wrapper)
|
|
501
|
+
native_module_init: std::collections::HashMap<String, crate::resolve::NativeModuleInit>,
|
|
468
502
|
/// Stack: true = async Rust context (run body), false = sync closure (Tish fn body)
|
|
469
503
|
async_context_stack: Vec<bool>,
|
|
470
504
|
loop_stack: Vec<(String, Option<String>)>, // (break_label, continue_update) for innermost loop
|
|
@@ -480,6 +514,9 @@ struct Codegen {
|
|
|
480
514
|
/// Variables currently wrapped in Rc<RefCell<Value>> for mutable capture in closures
|
|
481
515
|
/// These need special handling: reads via .borrow().clone(), writes via *var.borrow_mut()
|
|
482
516
|
refcell_wrapped_vars: std::collections::HashSet<String>,
|
|
517
|
+
/// Scopes of names whose Rust binding is actually `Rc<RefCell<_>>` (emitted at VarDecl).
|
|
518
|
+
/// `refcell_wrapped_vars` alone is insufficient: it is set by prepasses before decl may run.
|
|
519
|
+
rc_cell_storage_scopes: Vec<std::collections::HashSet<String>>,
|
|
483
520
|
/// Usage analyzer for move/clone optimization
|
|
484
521
|
usage_analyzer: Option<UsageAnalyzer>,
|
|
485
522
|
/// Type context for tracking variable types (for static typing)
|
|
@@ -492,7 +529,7 @@ impl Codegen {
|
|
|
492
529
|
fn new_with_native_modules(
|
|
493
530
|
project_root: Option<&Path>,
|
|
494
531
|
features: &[String],
|
|
495
|
-
|
|
532
|
+
native_module_init: std::collections::HashMap<String, crate::resolve::NativeModuleInit>,
|
|
496
533
|
) -> Self {
|
|
497
534
|
let features: std::collections::HashSet<String> = features.iter().cloned().collect();
|
|
498
535
|
Self {
|
|
@@ -502,31 +539,54 @@ impl Codegen {
|
|
|
502
539
|
is_async: false,
|
|
503
540
|
project_root: project_root.map(|p| p.to_path_buf()),
|
|
504
541
|
features,
|
|
505
|
-
|
|
542
|
+
native_module_init,
|
|
506
543
|
async_context_stack: Vec::new(),
|
|
507
544
|
loop_stack: Vec::new(),
|
|
508
545
|
function_scope_stack: vec![Vec::new()], // Start with global scope
|
|
509
546
|
outer_params_stack: Vec::new(),
|
|
510
547
|
outer_vars_stack: vec![Vec::new()], // Start with module-level scope
|
|
511
548
|
refcell_wrapped_vars: std::collections::HashSet::new(),
|
|
549
|
+
rc_cell_storage_scopes: vec![std::collections::HashSet::new()],
|
|
512
550
|
usage_analyzer: None,
|
|
513
551
|
type_context: TypeContext::new(),
|
|
514
552
|
program_has_jsx: false,
|
|
515
553
|
}
|
|
516
554
|
}
|
|
517
555
|
|
|
556
|
+
fn rc_cell_storage_contains(&self, name: &str) -> bool {
|
|
557
|
+
self.rc_cell_storage_scopes
|
|
558
|
+
.iter()
|
|
559
|
+
.rev()
|
|
560
|
+
.any(|s| s.contains(name))
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
fn rc_cell_storage_define(&mut self, name: &str) {
|
|
564
|
+
if let Some(scope) = self.rc_cell_storage_scopes.last_mut() {
|
|
565
|
+
scope.insert(name.to_string());
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
518
569
|
/// Map native module spec to Rust init expression using resolved package.json modules.
|
|
519
570
|
/// For built-in modules (tish:fs, tish:http, tish:process), use builtin_native_module_rust_init.
|
|
520
571
|
fn native_module_rust_init(&self, spec: &str, export_name: &str) -> Option<String> {
|
|
521
572
|
if is_builtin_native_spec(spec) {
|
|
522
573
|
return self.builtin_native_module_rust_init(spec, export_name);
|
|
523
574
|
}
|
|
524
|
-
self.
|
|
575
|
+
self.native_module_init.get(spec).map(|init| {
|
|
525
576
|
// Native modules return a namespace object (like an ES module).
|
|
526
577
|
// Named imports extract the field from that namespace: `import { foo } from "pkg"` → `ns.foo`.
|
|
578
|
+
let init_expr = match init {
|
|
579
|
+
crate::resolve::NativeModuleInit::Legacy {
|
|
580
|
+
crate_name,
|
|
581
|
+
export_fn,
|
|
582
|
+
} => format!("{}::{}()", crate_name, export_fn),
|
|
583
|
+
crate::resolve::NativeModuleInit::Generated { export_fn, .. } => {
|
|
584
|
+
format!("crate::generated_native::{}()", export_fn)
|
|
585
|
+
}
|
|
586
|
+
};
|
|
527
587
|
format!(
|
|
528
|
-
"{{ let _ns = {}
|
|
529
|
-
|
|
588
|
+
"{{ let _ns = {}; match _ns {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }} }}",
|
|
589
|
+
init_expr, export_name
|
|
530
590
|
)
|
|
531
591
|
})
|
|
532
592
|
}
|
|
@@ -576,28 +636,8 @@ impl Codegen {
|
|
|
576
636
|
}
|
|
577
637
|
|
|
578
638
|
fn has_feature(&self, name: &str) -> bool {
|
|
579
|
-
if self.features.
|
|
580
|
-
|
|
581
|
-
if name == "process" {
|
|
582
|
-
return true;
|
|
583
|
-
}
|
|
584
|
-
#[cfg(feature = "http")]
|
|
585
|
-
if name == "http" {
|
|
586
|
-
return true;
|
|
587
|
-
}
|
|
588
|
-
#[cfg(feature = "fs")]
|
|
589
|
-
if name == "fs" {
|
|
590
|
-
return true;
|
|
591
|
-
}
|
|
592
|
-
#[cfg(feature = "regex")]
|
|
593
|
-
if name == "regex" {
|
|
594
|
-
return true;
|
|
595
|
-
}
|
|
596
|
-
#[cfg(feature = "ws")]
|
|
597
|
-
if name == "ws" {
|
|
598
|
-
return true;
|
|
599
|
-
}
|
|
600
|
-
false
|
|
639
|
+
if self.features.contains("full") {
|
|
640
|
+
matches!(name, "http" | "fs" | "process" | "regex" | "ws")
|
|
601
641
|
} else {
|
|
602
642
|
self.features.contains(name)
|
|
603
643
|
}
|
|
@@ -711,20 +751,27 @@ impl Codegen {
|
|
|
711
751
|
let is_wrapped = self.refcell_wrapped_vars.contains(name);
|
|
712
752
|
let var_type = self.type_context.get_type(name);
|
|
713
753
|
|
|
714
|
-
// Native
|
|
715
|
-
if
|
|
754
|
+
// Native f64 (plain or Rc<RefCell<f64>> for closure-mutated locals)
|
|
755
|
+
if var_type == RustType::F64 {
|
|
716
756
|
let op_assign = if delta.contains('+') { "+=" } else { "-=" };
|
|
757
|
+
if !is_wrapped {
|
|
758
|
+
return if is_prefix {
|
|
759
|
+
format!("{{ {n} {op_assign} 1.0_f64; Value::Number({n}) }}")
|
|
760
|
+
} else {
|
|
761
|
+
format!("{{ let _prev = {n}; {n} {op_assign} 1.0_f64; Value::Number(_prev) }}")
|
|
762
|
+
};
|
|
763
|
+
}
|
|
717
764
|
return if is_prefix {
|
|
718
|
-
format!("{{ {n} {op_assign} 1.0_f64; Value::Number({n}) }}")
|
|
765
|
+
format!("{{ *{n}.borrow_mut() {op_assign} 1.0_f64; Value::Number(*{n}.borrow()) }}")
|
|
719
766
|
} else {
|
|
720
|
-
format!("{{ let _prev = {n}; {n} {op_assign} 1.0_f64; Value::Number(_prev) }}")
|
|
767
|
+
format!("{{ let _prev = *{n}.borrow(); *{n}.borrow_mut() {op_assign} 1.0_f64; Value::Number(_prev) }}")
|
|
721
768
|
};
|
|
722
769
|
}
|
|
723
770
|
|
|
724
771
|
if is_prefix {
|
|
725
772
|
if is_wrapped {
|
|
726
773
|
format!(
|
|
727
|
-
"{{ *{n}.borrow_mut() = Value::Number(match
|
|
774
|
+
"{{ let _cur = (*{n}.borrow()).clone(); *{n}.borrow_mut() = Value::Number(match &_cur {{ Value::Number(n) => n {delta}, _ => panic!(\"{op_name} needs number\") }}); (*{n}.borrow()).clone() }}"
|
|
728
775
|
)
|
|
729
776
|
} else {
|
|
730
777
|
format!(
|
|
@@ -818,7 +865,7 @@ impl Codegen {
|
|
|
818
865
|
if self.is_async {
|
|
819
866
|
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve, timer_set_timeout as tish_timer_set_timeout, timer_clear_timeout as tish_timer_clear_timeout, promise_object as tish_promise_object, await_promise as tish_await_promise};\n");
|
|
820
867
|
} else {
|
|
821
|
-
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve};\n");
|
|
868
|
+
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve, timer_set_timeout as tish_timer_set_timeout, timer_clear_timeout as tish_timer_clear_timeout};\n");
|
|
822
869
|
}
|
|
823
870
|
}
|
|
824
871
|
if self.has_feature("fs") {
|
|
@@ -962,9 +1009,9 @@ impl Codegen {
|
|
|
962
1009
|
if self.has_feature("http") {
|
|
963
1010
|
self.writeln("let fetch = Value::Function(Rc::new(|args: &[Value]| tish_fetch_promise(args.to_vec())));");
|
|
964
1011
|
self.writeln("let fetchAll = Value::Function(Rc::new(|args: &[Value]| tish_fetch_all_promise(args.to_vec())));");
|
|
1012
|
+
self.writeln("let setTimeout = Value::Function(Rc::new(|args: &[Value]| tish_timer_set_timeout(args)));");
|
|
1013
|
+
self.writeln("let clearTimeout = Value::Function(Rc::new(|args: &[Value]| tish_timer_clear_timeout(args)));");
|
|
965
1014
|
if self.is_async {
|
|
966
|
-
self.writeln("let setTimeout = Value::Function(Rc::new(|args: &[Value]| tish_timer_set_timeout(args)));");
|
|
967
|
-
self.writeln("let clearTimeout = Value::Function(Rc::new(|args: &[Value]| tish_timer_clear_timeout(args)));");
|
|
968
1015
|
self.writeln("let Promise = tish_promise_object();");
|
|
969
1016
|
}
|
|
970
1017
|
self.writeln("let serve = Value::Function(Rc::new(|args: &[Value]| {");
|
|
@@ -1050,6 +1097,7 @@ impl Codegen {
|
|
|
1050
1097
|
self.indent += 1;
|
|
1051
1098
|
self.type_context.push_scope();
|
|
1052
1099
|
self.outer_vars_stack.push(Vec::new());
|
|
1100
|
+
self.rc_cell_storage_scopes.push(std::collections::HashSet::new());
|
|
1053
1101
|
// Prepass: vars that must be RefCell because nested closures capture and mutate them
|
|
1054
1102
|
let vars_mutated_by_nested = Self::collect_vars_mutated_by_nested_closures(statements);
|
|
1055
1103
|
for v in &vars_mutated_by_nested {
|
|
@@ -1068,6 +1116,7 @@ impl Codegen {
|
|
|
1068
1116
|
}
|
|
1069
1117
|
self.function_scope_stack.pop(); // Exit scope
|
|
1070
1118
|
self.outer_vars_stack.pop(); // Exit variable scope
|
|
1119
|
+
self.rc_cell_storage_scopes.pop();
|
|
1071
1120
|
for v in &vars_mutated_by_nested {
|
|
1072
1121
|
self.refcell_wrapped_vars.remove(v);
|
|
1073
1122
|
}
|
|
@@ -1090,12 +1139,24 @@ impl Codegen {
|
|
|
1090
1139
|
|
|
1091
1140
|
if rust_type.is_native() {
|
|
1092
1141
|
// Generate native typed variable
|
|
1093
|
-
let type_str = rust_type.to_rust_type_str();
|
|
1094
1142
|
let expr_str = match init.as_ref() {
|
|
1095
1143
|
Some(e) => self.emit_native_expr(e, &rust_type)?,
|
|
1096
1144
|
None => rust_type.default_value(),
|
|
1097
1145
|
};
|
|
1098
|
-
self.
|
|
1146
|
+
if self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
1147
|
+
// Closure-mutated: same Rc<RefCell<T>> pattern as Value (assignments use borrow_mut)
|
|
1148
|
+
self.writeln(&format!(
|
|
1149
|
+
"let {} = std::rc::Rc::new(RefCell::new({}));",
|
|
1150
|
+
escaped_name, expr_str
|
|
1151
|
+
));
|
|
1152
|
+
self.rc_cell_storage_define(name.as_ref());
|
|
1153
|
+
} else {
|
|
1154
|
+
let type_str = rust_type.to_rust_type_str();
|
|
1155
|
+
self.writeln(&format!(
|
|
1156
|
+
"{} {}: {} = {};",
|
|
1157
|
+
mutability, escaped_name, type_str, expr_str
|
|
1158
|
+
));
|
|
1159
|
+
}
|
|
1099
1160
|
} else {
|
|
1100
1161
|
// Original Value-based codegen
|
|
1101
1162
|
let (expr_str, clone_needed) = match init.as_ref() {
|
|
@@ -1117,6 +1178,7 @@ impl Codegen {
|
|
|
1117
1178
|
expr_str.to_string()
|
|
1118
1179
|
};
|
|
1119
1180
|
self.writeln(&format!("let {} = std::rc::Rc::new(RefCell::new({}));", escaped_name, init_val));
|
|
1181
|
+
self.rc_cell_storage_define(name.as_ref());
|
|
1120
1182
|
} else if clone_needed {
|
|
1121
1183
|
self.writeln(&format!("{} {} = ({}).clone();", mutability, escaped_name, expr_str));
|
|
1122
1184
|
} else {
|
|
@@ -1436,7 +1498,7 @@ impl Codegen {
|
|
|
1436
1498
|
// If outer scope already has the var as RefCell, just clone it.
|
|
1437
1499
|
for outer_var in &outer_vars {
|
|
1438
1500
|
let var_escaped = Self::escape_ident(outer_var);
|
|
1439
|
-
if self.
|
|
1501
|
+
if self.rc_cell_storage_contains(outer_var) {
|
|
1440
1502
|
self.writeln(&format!("let {}_cell = {}.clone();", var_escaped, var_escaped));
|
|
1441
1503
|
} else {
|
|
1442
1504
|
self.writeln(&format!("let {}_cell = std::rc::Rc::new(RefCell::new({}.clone()));", var_escaped, var_escaped));
|
|
@@ -1715,7 +1777,12 @@ impl Codegen {
|
|
|
1715
1777
|
Expr::Ident { name, .. } => {
|
|
1716
1778
|
let escaped = Self::escape_ident(name.as_ref());
|
|
1717
1779
|
if self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
1718
|
-
|
|
1780
|
+
let var_type = self.type_context.get_type(name.as_ref());
|
|
1781
|
+
if var_type.is_native() {
|
|
1782
|
+
var_type.to_value_expr(&format!("(*{}.borrow())", escaped))
|
|
1783
|
+
} else {
|
|
1784
|
+
format!("(*{}.borrow()).clone()", escaped)
|
|
1785
|
+
}
|
|
1719
1786
|
} else {
|
|
1720
1787
|
// Check if this is a typed variable that needs conversion to Value
|
|
1721
1788
|
let var_type = self.type_context.get_type(name.as_ref());
|
|
@@ -1753,7 +1820,15 @@ impl Codegen {
|
|
|
1753
1820
|
}
|
|
1754
1821
|
Expr::Call { callee, args, .. } => {
|
|
1755
1822
|
// Compile-time embed: Polars.read_csv("<literal path>") when file exists
|
|
1756
|
-
if let Some(
|
|
1823
|
+
if let Some(init) = self.native_module_init.get("tish:polars") {
|
|
1824
|
+
let crate_name = match init {
|
|
1825
|
+
crate::resolve::NativeModuleInit::Legacy { crate_name, .. } => {
|
|
1826
|
+
crate_name.as_str()
|
|
1827
|
+
}
|
|
1828
|
+
crate::resolve::NativeModuleInit::Generated { shim_crate, .. } => {
|
|
1829
|
+
shim_crate.as_str()
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1757
1832
|
if let (Some(root), Some(CallArg::Expr(first_arg))) =
|
|
1758
1833
|
(self.project_root.as_ref(), args.first())
|
|
1759
1834
|
{
|
|
@@ -1863,6 +1938,18 @@ impl Codegen {
|
|
|
1863
1938
|
obj_expr, search, search, from
|
|
1864
1939
|
));
|
|
1865
1940
|
}
|
|
1941
|
+
"lastIndexOf" => {
|
|
1942
|
+
let search = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
1943
|
+
let position = if args.len() >= 2 {
|
|
1944
|
+
arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string())
|
|
1945
|
+
} else {
|
|
1946
|
+
"Value::Number(f64::INFINITY)".to_string()
|
|
1947
|
+
};
|
|
1948
|
+
return Ok(format!(
|
|
1949
|
+
"{{ let _obj = ({}).clone(); match &_obj {{ Value::String(_) => tishlang_runtime::string_last_index_of(&_obj, &{}, &{}), _ => Value::Number(-1.0) }} }}",
|
|
1950
|
+
obj_expr, search, position
|
|
1951
|
+
));
|
|
1952
|
+
}
|
|
1866
1953
|
"includes" => {
|
|
1867
1954
|
let search = arg_exprs.first().cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
1868
1955
|
let from = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
@@ -2302,37 +2389,47 @@ impl Codegen {
|
|
|
2302
2389
|
}
|
|
2303
2390
|
Expr::Assign { name, value, .. } => {
|
|
2304
2391
|
let escaped = Self::escape_ident(name.as_ref());
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
)
|
|
2323
|
-
}
|
|
2392
|
+
let rust_type = self.type_context.get_type(name.as_ref());
|
|
2393
|
+
let is_ref = self.refcell_wrapped_vars.contains(name.as_ref());
|
|
2394
|
+
// Native fast path: direct assignment (plain or Rc<RefCell<T>> for closure capture)
|
|
2395
|
+
if rust_type.is_native()
|
|
2396
|
+
&& matches!(rust_type, RustType::F64 | RustType::Bool | RustType::String)
|
|
2397
|
+
{
|
|
2398
|
+
let (val_code, val_ty) = self.emit_typed_expr(value)?;
|
|
2399
|
+
let native_val = if val_ty == rust_type {
|
|
2400
|
+
val_code
|
|
2401
|
+
} else if val_ty == RustType::Value {
|
|
2402
|
+
rust_type.from_value_expr(&val_code)
|
|
2403
|
+
} else {
|
|
2404
|
+
val_code
|
|
2405
|
+
};
|
|
2406
|
+
let return_val = if is_ref {
|
|
2407
|
+
rust_type.to_value_expr(&format!("(*{}.borrow())", escaped))
|
|
2408
|
+
} else {
|
|
2409
|
+
rust_type.to_value_expr(&escaped)
|
|
2410
|
+
};
|
|
2411
|
+
// Rust evaluates the assignment place before the RHS; RHS must not call
|
|
2412
|
+
// `.borrow()` on the same RefCell while `borrow_mut()` is active.
|
|
2413
|
+
let assign_stmt = if is_ref {
|
|
2414
|
+
format!(
|
|
2415
|
+
"let _assign_tmp = {}; *{}.borrow_mut() = _assign_tmp",
|
|
2416
|
+
native_val, escaped
|
|
2417
|
+
)
|
|
2418
|
+
} else {
|
|
2419
|
+
format!("{} = {}", escaped, native_val)
|
|
2420
|
+
};
|
|
2421
|
+
return Ok(format!("{{ {}; {} }}", assign_stmt, return_val));
|
|
2324
2422
|
}
|
|
2325
2423
|
// Fallback: Value path
|
|
2326
2424
|
let val = self.emit_expr(value)?;
|
|
2327
2425
|
let needs_outer_clone = self.should_clone(value);
|
|
2328
|
-
if
|
|
2426
|
+
if is_ref {
|
|
2329
2427
|
if needs_outer_clone {
|
|
2330
2428
|
format!("{{ let _v = ({}).clone(); *{}.borrow_mut() = _v.clone(); _v }}", val, escaped)
|
|
2331
2429
|
} else {
|
|
2332
2430
|
format!("{{ let _v = {}; *{}.borrow_mut() = _v.clone(); _v }}", val, escaped)
|
|
2333
2431
|
}
|
|
2334
2432
|
} else {
|
|
2335
|
-
let rust_type = self.type_context.get_type(name.as_ref());
|
|
2336
2433
|
let assign_rhs = if matches!(rust_type, RustType::Value) {
|
|
2337
2434
|
"_v.clone()".to_string()
|
|
2338
2435
|
} else {
|
|
@@ -2394,6 +2491,31 @@ impl Codegen {
|
|
|
2394
2491
|
let is_refcell = self.refcell_wrapped_vars.contains(name.as_ref());
|
|
2395
2492
|
let var_type = self.type_context.get_type(name.as_ref());
|
|
2396
2493
|
|
|
2494
|
+
// ── native f64 in Rc<RefCell<f64>> (closure-mutated) ───────────
|
|
2495
|
+
if is_refcell && var_type == RustType::F64 {
|
|
2496
|
+
let (rhs_code, rhs_ty) = self.emit_typed_expr(value)?;
|
|
2497
|
+
let rhs_f64 = if rhs_ty == RustType::F64 {
|
|
2498
|
+
rhs_code
|
|
2499
|
+
} else {
|
|
2500
|
+
let rhs_val = if rhs_ty.is_native() {
|
|
2501
|
+
rhs_ty.to_value_expr(&rhs_code)
|
|
2502
|
+
} else {
|
|
2503
|
+
rhs_code
|
|
2504
|
+
};
|
|
2505
|
+
format!("(match &({}) {{ Value::Number(n) => *n, v => panic!(\"compound assign: expected number, got {{:?}}\", v) }})", rhs_val)
|
|
2506
|
+
};
|
|
2507
|
+
let op_str = match op {
|
|
2508
|
+
CompoundOp::Add => "+=",
|
|
2509
|
+
CompoundOp::Sub => "-=",
|
|
2510
|
+
CompoundOp::Mul => "*=",
|
|
2511
|
+
CompoundOp::Div => "/=",
|
|
2512
|
+
CompoundOp::Mod => "%=",
|
|
2513
|
+
};
|
|
2514
|
+
return Ok(format!(
|
|
2515
|
+
"{{ let _op_rhs = {rhs_f64}; *{n}.borrow_mut() {op_str} _op_rhs; Value::Number(*{n}.borrow()) }}"
|
|
2516
|
+
));
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2397
2519
|
// ── native f64 fast path: direct arithmetic operators ─────────
|
|
2398
2520
|
// emit_expr must return a Value expression; wrap the result back.
|
|
2399
2521
|
if !is_refcell && var_type == RustType::F64 {
|
|
@@ -2420,6 +2542,32 @@ impl Codegen {
|
|
|
2420
2542
|
return Ok(format!("{{ {} {} {}; Value::Number({}) }}", n, op_str, rhs_f64, n));
|
|
2421
2543
|
}
|
|
2422
2544
|
|
|
2545
|
+
// ── native String += in Rc<RefCell<String>> ───────────────────
|
|
2546
|
+
if is_refcell && var_type == RustType::String && matches!(op, CompoundOp::Add) {
|
|
2547
|
+
let (rhs_code, rhs_ty) = self.emit_typed_expr(value)?;
|
|
2548
|
+
let rhs_str = if rhs_ty == RustType::String {
|
|
2549
|
+
rhs_code
|
|
2550
|
+
} else {
|
|
2551
|
+
let rhs_val = if rhs_ty.is_native() {
|
|
2552
|
+
rhs_ty.to_value_expr(&rhs_code)
|
|
2553
|
+
} else {
|
|
2554
|
+
rhs_code
|
|
2555
|
+
};
|
|
2556
|
+
format!(
|
|
2557
|
+
"match &({}) {{ \
|
|
2558
|
+
Value::String(s) => s.to_string(), \
|
|
2559
|
+
Value::Number(n) => {{ let i = *n as i64; if (*n - i as f64).abs() < f64::EPSILON {{ i.to_string() }} else {{ n.to_string() }} }}, \
|
|
2560
|
+
Value::Bool(b) => b.to_string(), \
|
|
2561
|
+
Value::Null => \"null\".to_string(), \
|
|
2562
|
+
other => format!(\"{{:?}}\", other) }}",
|
|
2563
|
+
rhs_val
|
|
2564
|
+
)
|
|
2565
|
+
};
|
|
2566
|
+
return Ok(format!(
|
|
2567
|
+
"{{ let _push_rhs = {rhs_str}; (*{n}.borrow_mut()).push_str(&_push_rhs); Value::String((*{n}.borrow()).clone().into()) }}"
|
|
2568
|
+
));
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2423
2571
|
// ── native String += fast path: push_str ─────────────────────
|
|
2424
2572
|
if !is_refcell && var_type == RustType::String && matches!(op, CompoundOp::Add) {
|
|
2425
2573
|
let (rhs_code, rhs_ty) = self.emit_typed_expr(value)?;
|
|
@@ -2458,8 +2606,8 @@ impl Codegen {
|
|
|
2458
2606
|
};
|
|
2459
2607
|
if is_refcell {
|
|
2460
2608
|
format!(
|
|
2461
|
-
"{{ let
|
|
2462
|
-
|
|
2609
|
+
"{{ let _lhs_v = (*{}.borrow()).clone(); let _rhs = ({}).clone(); let _new = tishlang_runtime::ops::{}(&_lhs_v, &_rhs)?; *{}.borrow_mut() = _new; (*{}.borrow()).clone() }}",
|
|
2610
|
+
n, val, op_fn, n, n
|
|
2463
2611
|
)
|
|
2464
2612
|
} else if var_type.is_native() {
|
|
2465
2613
|
// Wrap native lhs as Value, run ops::, unbox result back to native
|
|
@@ -2484,26 +2632,56 @@ impl Codegen {
|
|
|
2484
2632
|
let var_type = self.type_context.get_type(name.as_ref());
|
|
2485
2633
|
|
|
2486
2634
|
// ── native type: wrap for condition, unbox for assignment ──────
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
let
|
|
2635
|
+
// (plain binding or Rc<RefCell<T>> when closure-mutated)
|
|
2636
|
+
if var_type.is_native() {
|
|
2637
|
+
let inner = if is_refcell {
|
|
2638
|
+
format!("(*{}.borrow())", n)
|
|
2639
|
+
} else {
|
|
2640
|
+
n.clone()
|
|
2641
|
+
};
|
|
2642
|
+
let n_as_value = var_type.to_value_expr(&inner);
|
|
2490
2643
|
let val_as_native = var_type.from_value_expr("_v");
|
|
2644
|
+
let ret_expr = if is_refcell {
|
|
2645
|
+
var_type.to_value_expr(&format!("(*{}.borrow())", n))
|
|
2646
|
+
} else {
|
|
2647
|
+
var_type.to_value_expr(&n)
|
|
2648
|
+
};
|
|
2491
2649
|
let (cond, assign_and_return, else_expr) = match op {
|
|
2492
2650
|
LogicalAssignOp::AndAnd => (
|
|
2493
2651
|
format!("{{ let __chk = {}; __chk.is_truthy() }}", n_as_value),
|
|
2494
|
-
|
|
2495
|
-
|
|
2652
|
+
if is_refcell {
|
|
2653
|
+
format!(
|
|
2654
|
+
"{{ let _v = ({}).clone(); *{}.borrow_mut() = {}; {} }}",
|
|
2655
|
+
val, n, val_as_native, ret_expr
|
|
2656
|
+
)
|
|
2657
|
+
} else {
|
|
2658
|
+
format!(
|
|
2659
|
+
"{{ let _v = ({}).clone(); {} = {}; {} }}",
|
|
2660
|
+
val, n, val_as_native, ret_expr
|
|
2661
|
+
)
|
|
2662
|
+
},
|
|
2663
|
+
ret_expr.clone(),
|
|
2496
2664
|
),
|
|
2497
2665
|
LogicalAssignOp::OrOr => (
|
|
2498
2666
|
format!("!{{ let __chk = {}; __chk.is_truthy() }}", n_as_value),
|
|
2499
|
-
|
|
2500
|
-
|
|
2667
|
+
if is_refcell {
|
|
2668
|
+
format!(
|
|
2669
|
+
"{{ let _v = ({}).clone(); *{}.borrow_mut() = {}; {} }}",
|
|
2670
|
+
val, n, val_as_native, ret_expr
|
|
2671
|
+
)
|
|
2672
|
+
} else {
|
|
2673
|
+
format!(
|
|
2674
|
+
"{{ let _v = ({}).clone(); {} = {}; {} }}",
|
|
2675
|
+
val, n, val_as_native, ret_expr
|
|
2676
|
+
)
|
|
2677
|
+
},
|
|
2678
|
+
ret_expr.clone(),
|
|
2501
2679
|
),
|
|
2502
2680
|
// Native types (f64, String, bool) are never null — ??= is a no-op
|
|
2503
2681
|
LogicalAssignOp::Nullish => (
|
|
2504
2682
|
"false".to_string(),
|
|
2505
|
-
|
|
2506
|
-
|
|
2683
|
+
ret_expr.clone(),
|
|
2684
|
+
ret_expr.clone(),
|
|
2507
2685
|
),
|
|
2508
2686
|
};
|
|
2509
2687
|
return Ok(format!("{{ if {} {{ {} }} else {{ {} }} }}", cond, assign_and_return, else_expr));
|
|
@@ -3433,14 +3611,12 @@ impl Codegen {
|
|
|
3433
3611
|
let mutable_outer_vars: Vec<String> = outer_vars.iter().filter(|v| assigned_in_body.contains(*v)).cloned().collect();
|
|
3434
3612
|
let read_only_outer_vars: Vec<String> = outer_vars.iter().filter(|v| !assigned_in_body.contains(*v)).cloned().collect();
|
|
3435
3613
|
|
|
3436
|
-
//
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
// Wrap outer captures in Rc<RefCell<>> and use _ref suffix
|
|
3614
|
+
// Wrap outer captures in Rc<RefCell<>> and use _ref suffix.
|
|
3615
|
+
// Clone existing Rc only when VarDecl actually emitted `Rc<RefCell<...>>` (see rc_cell_storage_*).
|
|
3440
3616
|
for outer_param in &outer_params {
|
|
3441
3617
|
let param_escaped = Self::escape_ident(outer_param);
|
|
3442
3618
|
let ref_name = format!("{}_ref", param_escaped);
|
|
3443
|
-
if
|
|
3619
|
+
if self.rc_cell_storage_contains(outer_param) {
|
|
3444
3620
|
code.push_str(&format!(" let {} = {}.clone();\n", ref_name, param_escaped));
|
|
3445
3621
|
} else {
|
|
3446
3622
|
code.push_str(&format!(" let {} = std::rc::Rc::new(RefCell::new({}.clone()));\n", ref_name, param_escaped));
|
|
@@ -3449,7 +3625,7 @@ impl Codegen {
|
|
|
3449
3625
|
for outer_var in &outer_vars {
|
|
3450
3626
|
let var_escaped = Self::escape_ident(outer_var);
|
|
3451
3627
|
let ref_name = format!("{}_ref", var_escaped);
|
|
3452
|
-
if
|
|
3628
|
+
if self.rc_cell_storage_contains(outer_var) {
|
|
3453
3629
|
code.push_str(&format!(" let {} = {}.clone();\n", ref_name, var_escaped));
|
|
3454
3630
|
} else {
|
|
3455
3631
|
code.push_str(&format!(" let {} = std::rc::Rc::new(RefCell::new({}.clone()));\n", ref_name, var_escaped));
|
|
@@ -3625,7 +3801,11 @@ impl Codegen {
|
|
|
3625
3801
|
if let Expr::Ident { name, .. } = expr {
|
|
3626
3802
|
let var_type = self.type_context.get_type(name.as_ref());
|
|
3627
3803
|
if &var_type == target_type {
|
|
3628
|
-
|
|
3804
|
+
let esc = Self::escape_ident(name.as_ref()).into_owned();
|
|
3805
|
+
if self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
3806
|
+
return Ok(format!("(*{}.borrow()).clone()", esc));
|
|
3807
|
+
}
|
|
3808
|
+
return Ok(esc);
|
|
3629
3809
|
}
|
|
3630
3810
|
}
|
|
3631
3811
|
|
|
@@ -3658,8 +3838,12 @@ impl Codegen {
|
|
|
3658
3838
|
Expr::Ident { name, .. } => {
|
|
3659
3839
|
let escaped = Self::escape_ident(name.as_ref());
|
|
3660
3840
|
if self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
3661
|
-
|
|
3662
|
-
|
|
3841
|
+
let var_type = self.type_context.get_type(name.as_ref());
|
|
3842
|
+
if var_type.is_native() {
|
|
3843
|
+
Ok((format!("(*{}.borrow()).clone()", escaped), var_type))
|
|
3844
|
+
} else {
|
|
3845
|
+
Ok((format!("(*{}.borrow()).clone()", escaped), RustType::Value))
|
|
3846
|
+
}
|
|
3663
3847
|
} else {
|
|
3664
3848
|
let var_type = self.type_context.get_type(name.as_ref());
|
|
3665
3849
|
if var_type.is_native() {
|