@tishlang/tish 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +2 -0
- package/README.md +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/error.rs +2 -8
- package/crates/js_to_tish/src/transform/expr.rs +128 -137
- package/crates/js_to_tish/src/transform/stmt.rs +62 -32
- package/crates/tish/Cargo.toml +15 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +92 -39
- package/crates/tish/src/main.rs +172 -86
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +4 -2
- package/crates/tish/tests/integration_test.rs +216 -54
- package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
- package/crates/tish/tests/shortcircuit.rs +20 -5
- package/crates/tish_ast/src/ast.rs +92 -23
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +136 -8
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +65 -33
- package/crates/tish_builtins/src/construct.rs +34 -39
- package/crates/tish_builtins/src/globals.rs +42 -26
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/lib.rs +5 -5
- package/crates/tish_builtins/src/math.rs +5 -3
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +144 -22
- package/crates/tish_bytecode/src/chunk.rs +0 -1
- package/crates/tish_bytecode/src/compiler.rs +173 -71
- package/crates/tish_bytecode/src/opcode.rs +24 -6
- package/crates/tish_bytecode/src/peephole.rs +2 -2
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +1621 -453
- package/crates/tish_compile/src/infer.rs +75 -19
- package/crates/tish_compile/src/lib.rs +19 -8
- package/crates/tish_compile/src/resolve.rs +278 -137
- package/crates/tish_compile/src/types.rs +184 -24
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +181 -37
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
- package/crates/tish_compiler_wasm/src/lib.rs +16 -13
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +107 -56
- package/crates/tish_core/src/lib.rs +4 -2
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/uri.rs +9 -6
- package/crates/tish_core/src/value.rs +145 -43
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_cranelift/src/link.rs +6 -9
- package/crates/tish_cranelift/src/lower.rs +14 -8
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +474 -165
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +12 -8
- package/crates/tish_eval/src/natives.rs +136 -38
- package/crates/tish_eval/src/promise.rs +14 -8
- package/crates/tish_eval/src/timers.rs +28 -19
- package/crates/tish_eval/src/value.rs +17 -6
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +149 -43
- package/crates/tish_lexer/src/lib.rs +232 -63
- package/crates/tish_lexer/src/token.rs +10 -6
- package/crates/tish_llvm/src/lib.rs +17 -8
- package/crates/tish_lsp/Cargo.toml +4 -1
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_lsp/src/builtin_goto.rs +261 -0
- package/crates/tish_lsp/src/import_goto.rs +549 -0
- package/crates/tish_lsp/src/main.rs +504 -106
- package/crates/tish_native/src/build.rs +4 -8
- package/crates/tish_native/src/lib.rs +54 -21
- package/crates/tish_opt/src/lib.rs +84 -52
- package/crates/tish_parser/src/lib.rs +45 -13
- package/crates/tish_parser/src/parser.rs +505 -130
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3436 -0
- package/crates/tish_resolve/src/pos.rs +133 -0
- package/crates/tish_runtime/Cargo.toml +68 -3
- package/crates/tish_runtime/src/http.rs +1136 -145
- package/crates/tish_runtime/src/http_fetch.rs +38 -27
- package/crates/tish_runtime/src/http_hyper.rs +418 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +375 -189
- package/crates/tish_runtime/src/promise.rs +199 -40
- package/crates/tish_runtime/src/promise_io.rs +2 -1
- package/crates/tish_runtime/src/timers.rs +37 -1
- package/crates/tish_runtime/src/ws.rs +65 -42
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
- package/crates/tish_ui/src/jsx.rs +317 -27
- package/crates/tish_ui/src/lib.rs +5 -2
- package/crates/tish_ui/src/runtime/hooks.rs +406 -45
- package/crates/tish_ui/src/runtime/mod.rs +36 -9
- package/crates/tish_vm/Cargo.toml +15 -5
- package/crates/tish_vm/src/vm.rs +725 -281
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
- package/crates/tish_wasm/src/lib.rs +55 -42
- package/crates/tish_wasm_runtime/Cargo.toml +2 -1
- package/crates/tish_wasm_runtime/src/lib.rs +1 -1
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
- package/justfile +8 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
//! Code generation: AST -> Rust source.
|
|
2
2
|
|
|
3
|
+
use crate::resolve::is_builtin_native_spec;
|
|
4
|
+
use crate::types::{RustType, TypeContext};
|
|
3
5
|
use std::borrow::Cow;
|
|
4
6
|
use std::collections::{HashMap, HashSet};
|
|
5
7
|
use std::path::Path;
|
|
6
|
-
use tishlang_ast::{
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
use tishlang_ast::{
|
|
9
|
+
ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern, Expr,
|
|
10
|
+
FunParam, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement,
|
|
11
|
+
TypeAnnotation, UnaryOp,
|
|
12
|
+
};
|
|
9
13
|
|
|
10
14
|
/// Tracks variable usage for move/clone optimization.
|
|
11
15
|
/// A variable can be moved instead of cloned if it's at its last use.
|
|
@@ -41,7 +45,12 @@ impl UsageAnalyzer {
|
|
|
41
45
|
self.analyze_expr(e);
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
|
-
Statement::If {
|
|
48
|
+
Statement::If {
|
|
49
|
+
cond,
|
|
50
|
+
then_branch,
|
|
51
|
+
else_branch,
|
|
52
|
+
..
|
|
53
|
+
} => {
|
|
45
54
|
self.analyze_expr(cond);
|
|
46
55
|
self.analyze_statement(then_branch);
|
|
47
56
|
if let Some(e) = else_branch {
|
|
@@ -49,7 +58,13 @@ impl UsageAnalyzer {
|
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
60
|
Statement::Block { statements, .. } => self.analyze_statements(statements),
|
|
52
|
-
Statement::For {
|
|
61
|
+
Statement::For {
|
|
62
|
+
init,
|
|
63
|
+
cond,
|
|
64
|
+
update,
|
|
65
|
+
body,
|
|
66
|
+
..
|
|
67
|
+
} => {
|
|
53
68
|
if let Some(i) = init {
|
|
54
69
|
self.analyze_statement(i);
|
|
55
70
|
}
|
|
@@ -69,7 +84,12 @@ impl UsageAnalyzer {
|
|
|
69
84
|
self.analyze_expr(cond);
|
|
70
85
|
self.analyze_statement(body);
|
|
71
86
|
}
|
|
72
|
-
Statement::Switch {
|
|
87
|
+
Statement::Switch {
|
|
88
|
+
expr,
|
|
89
|
+
cases,
|
|
90
|
+
default_body,
|
|
91
|
+
..
|
|
92
|
+
} => {
|
|
73
93
|
self.analyze_expr(expr);
|
|
74
94
|
for (case_expr, stmts) in cases {
|
|
75
95
|
if let Some(e) = case_expr {
|
|
@@ -82,7 +102,12 @@ impl UsageAnalyzer {
|
|
|
82
102
|
}
|
|
83
103
|
}
|
|
84
104
|
Statement::Throw { value, .. } => self.analyze_expr(value),
|
|
85
|
-
Statement::Try {
|
|
105
|
+
Statement::Try {
|
|
106
|
+
body,
|
|
107
|
+
catch_body,
|
|
108
|
+
finally_body,
|
|
109
|
+
..
|
|
110
|
+
} => {
|
|
86
111
|
self.analyze_statement(body);
|
|
87
112
|
if let Some(c) = catch_body {
|
|
88
113
|
self.analyze_statement(c);
|
|
@@ -95,7 +120,11 @@ impl UsageAnalyzer {
|
|
|
95
120
|
self.analyze_statement(body);
|
|
96
121
|
}
|
|
97
122
|
Statement::Break { .. } | Statement::Continue { .. } => {}
|
|
98
|
-
Statement::Import { .. }
|
|
123
|
+
Statement::Import { .. }
|
|
124
|
+
| Statement::Export { .. }
|
|
125
|
+
| Statement::TypeAlias { .. }
|
|
126
|
+
| Statement::DeclareVar { .. }
|
|
127
|
+
| Statement::DeclareFun { .. } => {
|
|
99
128
|
// Import/Export should be resolved by merge_modules before compilation
|
|
100
129
|
}
|
|
101
130
|
}
|
|
@@ -153,14 +182,17 @@ impl UsageAnalyzer {
|
|
|
153
182
|
}
|
|
154
183
|
}
|
|
155
184
|
}
|
|
156
|
-
Expr::ArrowFunction { body, .. } => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
}
|
|
185
|
+
Expr::ArrowFunction { body, .. } => match body {
|
|
186
|
+
ArrowBody::Expr(e) => self.analyze_expr(e),
|
|
187
|
+
ArrowBody::Block(s) => self.analyze_statement(s),
|
|
188
|
+
},
|
|
162
189
|
Expr::Assign { value, .. } => self.analyze_expr(value),
|
|
163
|
-
Expr::Conditional {
|
|
190
|
+
Expr::Conditional {
|
|
191
|
+
cond,
|
|
192
|
+
then_branch,
|
|
193
|
+
else_branch,
|
|
194
|
+
..
|
|
195
|
+
} => {
|
|
164
196
|
self.analyze_expr(cond);
|
|
165
197
|
self.analyze_expr(then_branch);
|
|
166
198
|
self.analyze_expr(else_branch);
|
|
@@ -183,23 +215,37 @@ impl UsageAnalyzer {
|
|
|
183
215
|
*self.use_counts.entry(name.to_string()).or_insert(0) += 1;
|
|
184
216
|
self.analyze_expr(value);
|
|
185
217
|
}
|
|
186
|
-
Expr::PostfixInc { name, .. }
|
|
218
|
+
Expr::PostfixInc { name, .. }
|
|
219
|
+
| Expr::PostfixDec { name, .. }
|
|
220
|
+
| Expr::PrefixInc { name, .. }
|
|
221
|
+
| Expr::PrefixDec { name, .. } => {
|
|
187
222
|
*self.use_counts.entry(name.to_string()).or_insert(0) += 1;
|
|
188
223
|
}
|
|
189
224
|
Expr::MemberAssign { object, value, .. } => {
|
|
190
225
|
self.analyze_expr(object);
|
|
191
226
|
self.analyze_expr(value);
|
|
192
227
|
}
|
|
193
|
-
Expr::IndexAssign {
|
|
228
|
+
Expr::IndexAssign {
|
|
229
|
+
object,
|
|
230
|
+
index,
|
|
231
|
+
value,
|
|
232
|
+
..
|
|
233
|
+
} => {
|
|
194
234
|
self.analyze_expr(object);
|
|
195
235
|
self.analyze_expr(index);
|
|
196
236
|
self.analyze_expr(value);
|
|
197
237
|
}
|
|
198
238
|
Expr::Await { operand, .. } => self.analyze_expr(operand),
|
|
199
|
-
Expr::JsxElement {
|
|
239
|
+
Expr::JsxElement {
|
|
240
|
+
props, children, ..
|
|
241
|
+
} => {
|
|
200
242
|
for p in props {
|
|
201
243
|
match p {
|
|
202
|
-
tishlang_ast::JsxProp::Attr {
|
|
244
|
+
tishlang_ast::JsxProp::Attr {
|
|
245
|
+
value: tishlang_ast::JsxAttrValue::Expr(e),
|
|
246
|
+
..
|
|
247
|
+
}
|
|
248
|
+
| tishlang_ast::JsxProp::Spread(e) => self.analyze_expr(e),
|
|
203
249
|
_ => {}
|
|
204
250
|
}
|
|
205
251
|
}
|
|
@@ -240,7 +286,10 @@ pub struct CompileError {
|
|
|
240
286
|
|
|
241
287
|
impl CompileError {
|
|
242
288
|
fn new(msg: impl Into<String>, span: Option<Span>) -> Self {
|
|
243
|
-
Self {
|
|
289
|
+
Self {
|
|
290
|
+
message: msg.into(),
|
|
291
|
+
span,
|
|
292
|
+
}
|
|
244
293
|
}
|
|
245
294
|
}
|
|
246
295
|
|
|
@@ -262,21 +311,45 @@ fn program_uses_async(program: &Program) -> bool {
|
|
|
262
311
|
match s {
|
|
263
312
|
Statement::FunDecl { async_, .. } if *async_ => true,
|
|
264
313
|
Statement::Block { statements, .. } => statements.iter().any(stmt_has_async),
|
|
265
|
-
Statement::If {
|
|
266
|
-
|
|
314
|
+
Statement::If {
|
|
315
|
+
then_branch,
|
|
316
|
+
else_branch,
|
|
317
|
+
..
|
|
318
|
+
} => {
|
|
319
|
+
stmt_has_async(then_branch)
|
|
320
|
+
|| else_branch
|
|
321
|
+
.as_ref()
|
|
322
|
+
.is_some_and(|s| stmt_has_async(s.as_ref()))
|
|
267
323
|
}
|
|
268
|
-
Statement::While { body, .. }
|
|
324
|
+
Statement::While { body, .. }
|
|
325
|
+
| Statement::For { body, .. }
|
|
326
|
+
| Statement::ForOf { body, .. }
|
|
269
327
|
| Statement::DoWhile { body, .. } => stmt_has_async(body),
|
|
270
|
-
Statement::Switch {
|
|
271
|
-
cases
|
|
328
|
+
Statement::Switch {
|
|
329
|
+
cases,
|
|
330
|
+
default_body,
|
|
331
|
+
..
|
|
332
|
+
} => {
|
|
333
|
+
cases
|
|
334
|
+
.iter()
|
|
335
|
+
.any(|(_, stmts)| stmts.iter().any(stmt_has_async))
|
|
272
336
|
|| default_body
|
|
273
337
|
.as_ref()
|
|
274
338
|
.is_some_and(|stmts| stmts.iter().any(stmt_has_async))
|
|
275
339
|
}
|
|
276
|
-
Statement::Try {
|
|
340
|
+
Statement::Try {
|
|
341
|
+
body,
|
|
342
|
+
catch_body,
|
|
343
|
+
finally_body,
|
|
344
|
+
..
|
|
345
|
+
} => {
|
|
277
346
|
stmt_has_async(body)
|
|
278
|
-
|| catch_body
|
|
279
|
-
|
|
347
|
+
|| catch_body
|
|
348
|
+
.as_ref()
|
|
349
|
+
.is_some_and(|s| stmt_has_async(s.as_ref()))
|
|
350
|
+
|| finally_body
|
|
351
|
+
.as_ref()
|
|
352
|
+
.is_some_and(|s| stmt_has_async(s.as_ref()))
|
|
280
353
|
}
|
|
281
354
|
_ => false,
|
|
282
355
|
}
|
|
@@ -287,14 +360,16 @@ fn program_uses_async(program: &Program) -> bool {
|
|
|
287
360
|
Expr::Binary { left, right, .. } => expr_has_await(left) || expr_has_await(right),
|
|
288
361
|
Expr::Unary { operand, .. } | Expr::TypeOf { operand, .. } => expr_has_await(operand),
|
|
289
362
|
Expr::Call { callee, args, .. } => {
|
|
290
|
-
expr_has_await(callee)
|
|
291
|
-
|
|
292
|
-
|
|
363
|
+
expr_has_await(callee)
|
|
364
|
+
|| args.iter().any(|a| match a {
|
|
365
|
+
CallArg::Expr(e) | CallArg::Spread(e) => expr_has_await(e),
|
|
366
|
+
})
|
|
293
367
|
}
|
|
294
368
|
Expr::New { callee, args, .. } => {
|
|
295
|
-
expr_has_await(callee)
|
|
296
|
-
|
|
297
|
-
|
|
369
|
+
expr_has_await(callee)
|
|
370
|
+
|| args.iter().any(|a| match a {
|
|
371
|
+
CallArg::Expr(e) | CallArg::Spread(e) => expr_has_await(e),
|
|
372
|
+
})
|
|
298
373
|
}
|
|
299
374
|
Expr::Member { object, prop, .. } => {
|
|
300
375
|
expr_has_await(object)
|
|
@@ -305,32 +380,48 @@ fn program_uses_async(program: &Program) -> bool {
|
|
|
305
380
|
}
|
|
306
381
|
}
|
|
307
382
|
Expr::Index { object, index, .. } => expr_has_await(object) || expr_has_await(index),
|
|
308
|
-
Expr::Conditional {
|
|
309
|
-
|
|
383
|
+
Expr::Conditional {
|
|
384
|
+
cond,
|
|
385
|
+
then_branch,
|
|
386
|
+
else_branch,
|
|
387
|
+
..
|
|
388
|
+
} => expr_has_await(cond) || expr_has_await(then_branch) || expr_has_await(else_branch),
|
|
389
|
+
Expr::NullishCoalesce { left, right, .. } => {
|
|
390
|
+
expr_has_await(left) || expr_has_await(right)
|
|
310
391
|
}
|
|
311
|
-
Expr::NullishCoalesce { left, right, .. } => expr_has_await(left) || expr_has_await(right),
|
|
312
392
|
Expr::Array { elements, .. } => elements.iter().any(|el| match el {
|
|
313
393
|
ArrayElement::Expr(e) | ArrayElement::Spread(e) => expr_has_await(e),
|
|
314
394
|
}),
|
|
315
395
|
Expr::Object { props, .. } => props.iter().any(|p| match p {
|
|
316
396
|
ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => expr_has_await(e),
|
|
317
397
|
}),
|
|
318
|
-
Expr::Assign { value, .. }
|
|
319
|
-
| Expr::
|
|
398
|
+
Expr::Assign { value, .. }
|
|
399
|
+
| Expr::CompoundAssign { value, .. }
|
|
400
|
+
| Expr::LogicalAssign { value, .. }
|
|
401
|
+
| Expr::MemberAssign { value, .. }
|
|
402
|
+
| Expr::IndexAssign { value, .. } => expr_has_await(value),
|
|
320
403
|
Expr::ArrowFunction { body, .. } => match body {
|
|
321
404
|
ArrowBody::Expr(e) => expr_has_await(e),
|
|
322
405
|
ArrowBody::Block(s) => stmt_has_async(s),
|
|
323
406
|
},
|
|
324
407
|
Expr::TemplateLiteral { exprs, .. } => exprs.iter().any(expr_has_await),
|
|
325
|
-
Expr::JsxElement {
|
|
408
|
+
Expr::JsxElement {
|
|
409
|
+
props, children, ..
|
|
410
|
+
} => {
|
|
326
411
|
props.iter().any(|p| match p {
|
|
327
|
-
tishlang_ast::JsxProp::Attr {
|
|
412
|
+
tishlang_ast::JsxProp::Attr {
|
|
413
|
+
value: tishlang_ast::JsxAttrValue::Expr(e),
|
|
414
|
+
..
|
|
415
|
+
}
|
|
416
|
+
| tishlang_ast::JsxProp::Spread(e) => expr_has_await(e),
|
|
328
417
|
_ => false,
|
|
329
|
-
}) || children
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
children.iter().any(|c| matches!(c, tishlang_ast::JsxChild::Expr(e) if expr_has_await(e)))
|
|
418
|
+
}) || children
|
|
419
|
+
.iter()
|
|
420
|
+
.any(|c| matches!(c, tishlang_ast::JsxChild::Expr(e) if expr_has_await(e)))
|
|
333
421
|
}
|
|
422
|
+
Expr::JsxFragment { children, .. } => children
|
|
423
|
+
.iter()
|
|
424
|
+
.any(|c| matches!(c, tishlang_ast::JsxChild::Expr(e) if expr_has_await(e))),
|
|
334
425
|
_ => false,
|
|
335
426
|
}
|
|
336
427
|
}
|
|
@@ -340,21 +431,42 @@ fn program_uses_async(program: &Program) -> bool {
|
|
|
340
431
|
Statement::VarDecl { init, .. } => init.as_ref().is_some_and(expr_has_await),
|
|
341
432
|
Statement::VarDeclDestructure { init, .. } => expr_has_await(init),
|
|
342
433
|
Statement::ExprStmt { expr, .. } => expr_has_await(expr),
|
|
343
|
-
Statement::If {
|
|
344
|
-
|
|
345
|
-
|
|
434
|
+
Statement::If {
|
|
435
|
+
cond,
|
|
436
|
+
then_branch,
|
|
437
|
+
else_branch,
|
|
438
|
+
..
|
|
439
|
+
} => {
|
|
440
|
+
expr_has_await(cond)
|
|
441
|
+
|| stmt_has_await(then_branch)
|
|
442
|
+
|| else_branch
|
|
443
|
+
.as_ref()
|
|
444
|
+
.is_some_and(|s| stmt_has_await(s.as_ref()))
|
|
346
445
|
}
|
|
347
446
|
Statement::While { cond, body, .. } => expr_has_await(cond) || stmt_has_await(body),
|
|
348
|
-
Statement::For {
|
|
447
|
+
Statement::For {
|
|
448
|
+
init,
|
|
449
|
+
cond,
|
|
450
|
+
update,
|
|
451
|
+
body,
|
|
452
|
+
..
|
|
453
|
+
} => {
|
|
349
454
|
init.as_ref().is_some_and(|s| stmt_has_await(s.as_ref()))
|
|
350
455
|
|| cond.as_ref().is_some_and(expr_has_await)
|
|
351
456
|
|| update.as_ref().is_some_and(expr_has_await)
|
|
352
457
|
|| stmt_has_await(body)
|
|
353
458
|
}
|
|
354
|
-
Statement::ForOf { iterable, body, .. } =>
|
|
459
|
+
Statement::ForOf { iterable, body, .. } => {
|
|
460
|
+
expr_has_await(iterable) || stmt_has_await(body)
|
|
461
|
+
}
|
|
355
462
|
Statement::Return { value, .. } => value.as_ref().is_some_and(expr_has_await),
|
|
356
463
|
Statement::FunDecl { body, .. } => stmt_has_await(body),
|
|
357
|
-
Statement::Switch {
|
|
464
|
+
Statement::Switch {
|
|
465
|
+
expr,
|
|
466
|
+
cases,
|
|
467
|
+
default_body,
|
|
468
|
+
..
|
|
469
|
+
} => {
|
|
358
470
|
expr_has_await(expr)
|
|
359
471
|
|| cases.iter().any(|(c, stmts)| {
|
|
360
472
|
c.as_ref().is_some_and(expr_has_await) || stmts.iter().any(stmt_has_await)
|
|
@@ -365,23 +477,38 @@ fn program_uses_async(program: &Program) -> bool {
|
|
|
365
477
|
}
|
|
366
478
|
Statement::DoWhile { body, cond, .. } => stmt_has_await(body) || expr_has_await(cond),
|
|
367
479
|
Statement::Throw { value, .. } => expr_has_await(value),
|
|
368
|
-
Statement::Try {
|
|
480
|
+
Statement::Try {
|
|
481
|
+
body,
|
|
482
|
+
catch_body,
|
|
483
|
+
finally_body,
|
|
484
|
+
..
|
|
485
|
+
} => {
|
|
369
486
|
stmt_has_await(body)
|
|
370
|
-
|| catch_body
|
|
371
|
-
|
|
487
|
+
|| catch_body
|
|
488
|
+
.as_ref()
|
|
489
|
+
.is_some_and(|s| stmt_has_await(s.as_ref()))
|
|
490
|
+
|| finally_body
|
|
491
|
+
.as_ref()
|
|
492
|
+
.is_some_and(|s| stmt_has_await(s.as_ref()))
|
|
372
493
|
}
|
|
373
494
|
Statement::Import { .. } | Statement::Export { .. } => false,
|
|
374
495
|
_ => false,
|
|
375
496
|
}
|
|
376
497
|
}
|
|
377
|
-
program
|
|
498
|
+
program
|
|
499
|
+
.statements
|
|
500
|
+
.iter()
|
|
501
|
+
.any(|s| stmt_has_async(s) || stmt_has_await(s))
|
|
378
502
|
}
|
|
379
503
|
|
|
380
504
|
pub fn compile(program: &Program) -> Result<String, CompileError> {
|
|
381
505
|
compile_with_project_root(program, None)
|
|
382
506
|
}
|
|
383
507
|
|
|
384
|
-
pub fn compile_with_project_root(
|
|
508
|
+
pub fn compile_with_project_root(
|
|
509
|
+
program: &Program,
|
|
510
|
+
project_root: Option<&Path>,
|
|
511
|
+
) -> Result<String, CompileError> {
|
|
385
512
|
compile_with_features(program, project_root, &[])
|
|
386
513
|
}
|
|
387
514
|
|
|
@@ -416,24 +543,40 @@ pub fn compile_project_full(
|
|
|
416
543
|
> {
|
|
417
544
|
use crate::resolve;
|
|
418
545
|
let root = project_root.unwrap_or_else(|| entry_path.parent().unwrap_or(Path::new(".")));
|
|
419
|
-
let modules = resolve::resolve_project(entry_path, project_root)
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
let
|
|
428
|
-
|
|
546
|
+
let modules = resolve::resolve_project(entry_path, project_root).map_err(|e| CompileError {
|
|
547
|
+
message: e,
|
|
548
|
+
span: None,
|
|
549
|
+
})?;
|
|
550
|
+
resolve::detect_cycles(&modules).map_err(|e| CompileError {
|
|
551
|
+
message: e,
|
|
552
|
+
span: None,
|
|
553
|
+
})?;
|
|
554
|
+
let merged = resolve::merge_modules(modules).map_err(|e| CompileError {
|
|
555
|
+
message: e,
|
|
556
|
+
span: None,
|
|
557
|
+
})?;
|
|
558
|
+
let native_modules =
|
|
559
|
+
resolve::resolve_native_modules(&merged.program, root).map_err(|e| CompileError {
|
|
560
|
+
message: e,
|
|
561
|
+
span: None,
|
|
562
|
+
})?;
|
|
563
|
+
let native_build = resolve::compute_native_build_artifacts(
|
|
564
|
+
&merged.program,
|
|
565
|
+
root,
|
|
566
|
+
&native_modules,
|
|
567
|
+
)
|
|
568
|
+
.map_err(|e| CompileError {
|
|
569
|
+
message: e,
|
|
570
|
+
span: None,
|
|
571
|
+
})?;
|
|
429
572
|
let mut all_features: Vec<String> = features.to_vec();
|
|
430
|
-
for f in resolve::extract_native_import_features(&merged) {
|
|
573
|
+
for f in resolve::extract_native_import_features(&merged.program) {
|
|
431
574
|
if !all_features.contains(&f) {
|
|
432
575
|
all_features.push(f);
|
|
433
576
|
}
|
|
434
577
|
}
|
|
435
578
|
let rust = compile_with_native_modules(
|
|
436
|
-
&merged,
|
|
579
|
+
&merged.program,
|
|
437
580
|
project_root,
|
|
438
581
|
&all_features,
|
|
439
582
|
&native_modules,
|
|
@@ -463,7 +606,11 @@ pub fn compile_with_native_modules(
|
|
|
463
606
|
native_init: &std::collections::HashMap<String, crate::resolve::NativeModuleInit>,
|
|
464
607
|
optimize: bool,
|
|
465
608
|
) -> Result<String, CompileError> {
|
|
466
|
-
let program = if optimize {
|
|
609
|
+
let program = if optimize {
|
|
610
|
+
tishlang_opt::optimize(program)
|
|
611
|
+
} else {
|
|
612
|
+
program.clone()
|
|
613
|
+
};
|
|
467
614
|
// Type-inference pass: fills in `type_ann` on unannotated VarDecl nodes where
|
|
468
615
|
// the type is unambiguous (literals, arithmetic of typed vars, etc.).
|
|
469
616
|
let program = crate::infer::infer_program(&program);
|
|
@@ -521,8 +668,20 @@ struct Codegen {
|
|
|
521
668
|
usage_analyzer: Option<UsageAnalyzer>,
|
|
522
669
|
/// Type context for tracking variable types (for static typing)
|
|
523
670
|
type_context: TypeContext,
|
|
671
|
+
/// Registry of `type Foo = { ... }` declarations seen in the program.
|
|
672
|
+
/// Populated in a pre-pass so that any later `let x: Foo = ...` or
|
|
673
|
+
/// `fn f(x: Foo)` resolves to a `RustType::Named { name: "Foo", ... }`
|
|
674
|
+
/// and the codegen can emit a Rust struct + direct field access for
|
|
675
|
+
/// values of that type.
|
|
676
|
+
type_aliases: std::collections::HashMap<String, crate::types::RustType>,
|
|
524
677
|
/// Program uses JSX; emit `tishlang_ui` imports and `h` / `Fragment` globals.
|
|
525
678
|
program_has_jsx: bool,
|
|
679
|
+
/// `fn` names for Rust JSX: PascalCase tags matching these use a value binding; others are string intrinsics.
|
|
680
|
+
program_fun_decl_names: std::collections::HashSet<String>,
|
|
681
|
+
/// Nesting depth inside `Value::native(move |args| {{ ... }})` user functions / arrows.
|
|
682
|
+
/// `try`/`throw` lowering uses `return Err` only at depth 0 (e.g. `run()`); inside native
|
|
683
|
+
/// closures it must not return a `Result` from a `Value`-returning closure.
|
|
684
|
+
value_fn_depth: u32,
|
|
526
685
|
}
|
|
527
686
|
|
|
528
687
|
impl Codegen {
|
|
@@ -549,7 +708,191 @@ impl Codegen {
|
|
|
549
708
|
rc_cell_storage_scopes: vec![std::collections::HashSet::new()],
|
|
550
709
|
usage_analyzer: None,
|
|
551
710
|
type_context: TypeContext::new(),
|
|
711
|
+
type_aliases: std::collections::HashMap::new(),
|
|
552
712
|
program_has_jsx: false,
|
|
713
|
+
program_fun_decl_names: std::collections::HashSet::new(),
|
|
714
|
+
value_fn_depth: 0,
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/// Walk every `Statement::TypeAlias` in the program (including nested
|
|
719
|
+
/// ones inside blocks, ifs, loops, function bodies, and exports) and
|
|
720
|
+
/// register the resolved `RustType` under its alias name. Forward
|
|
721
|
+
/// references are handled by running this pass *before* any other
|
|
722
|
+
/// codegen step.
|
|
723
|
+
fn collect_type_aliases(&mut self, statements: &[Statement]) {
|
|
724
|
+
// Two passes so an alias `type B = A` can resolve `A` even if
|
|
725
|
+
// `A` is declared after `B` in source order.
|
|
726
|
+
let mut raw: Vec<(String, &TypeAnnotation)> = Vec::new();
|
|
727
|
+
Self::walk_type_aliases(statements, &mut raw);
|
|
728
|
+
// First-fixpoint resolution: keep iterating until no more aliases
|
|
729
|
+
// change shape. In practice 1–2 passes; capped to prevent infinite
|
|
730
|
+
// loops on (already rejected) self-referential aliases.
|
|
731
|
+
for _ in 0..8 {
|
|
732
|
+
let mut changed = false;
|
|
733
|
+
for (name, ann) in &raw {
|
|
734
|
+
let resolved = crate::types::RustType::from_annotation_with_aliases(
|
|
735
|
+
ann,
|
|
736
|
+
&self.type_aliases,
|
|
737
|
+
);
|
|
738
|
+
let prev: Option<crate::types::RustType> =
|
|
739
|
+
self.type_aliases.get(name).cloned();
|
|
740
|
+
if prev.as_ref() != Some(&resolved) {
|
|
741
|
+
self.type_aliases.insert(name.clone(), resolved);
|
|
742
|
+
changed = true;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if !changed {
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
fn walk_type_aliases<'p>(
|
|
752
|
+
statements: &'p [Statement],
|
|
753
|
+
out: &mut Vec<(String, &'p TypeAnnotation)>,
|
|
754
|
+
) {
|
|
755
|
+
for s in statements {
|
|
756
|
+
match s {
|
|
757
|
+
Statement::TypeAlias { name, ty, .. } => {
|
|
758
|
+
out.push((name.to_string(), ty));
|
|
759
|
+
}
|
|
760
|
+
Statement::Block { statements, .. } => Self::walk_type_aliases(statements, out),
|
|
761
|
+
Statement::If {
|
|
762
|
+
then_branch,
|
|
763
|
+
else_branch,
|
|
764
|
+
..
|
|
765
|
+
} => {
|
|
766
|
+
Self::walk_type_aliases(std::slice::from_ref(then_branch.as_ref()), out);
|
|
767
|
+
if let Some(e) = else_branch {
|
|
768
|
+
Self::walk_type_aliases(std::slice::from_ref(e.as_ref()), out);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
Statement::For { body, .. }
|
|
772
|
+
| Statement::ForOf { body, .. }
|
|
773
|
+
| Statement::While { body, .. }
|
|
774
|
+
| Statement::DoWhile { body, .. } => {
|
|
775
|
+
Self::walk_type_aliases(std::slice::from_ref(body.as_ref()), out);
|
|
776
|
+
}
|
|
777
|
+
Statement::Export { declaration, .. } => {
|
|
778
|
+
if let tishlang_ast::ExportDeclaration::Named(s) = declaration.as_ref() {
|
|
779
|
+
Self::walk_type_aliases(std::slice::from_ref(s.as_ref()), out);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
_ => {}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/// Emit a Rust `struct` definition for every type alias whose RHS is
|
|
788
|
+
/// an object shape. Each generated struct derives `Clone` + `Debug`
|
|
789
|
+
/// (cheap; field types are all `Copy`-or-cheap-clone in practice) and
|
|
790
|
+
/// is named `TishStruct_<TishAlias>`.
|
|
791
|
+
fn emit_named_struct_decls(&mut self) {
|
|
792
|
+
// Snapshot keys + values so we can mutate `self` (writing the
|
|
793
|
+
// emitted source) inside the loop.
|
|
794
|
+
let mut entries: Vec<(String, crate::types::RustType)> =
|
|
795
|
+
self.type_aliases.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
|
|
796
|
+
entries.sort_by(|a, b| a.0.cmp(&b.0));
|
|
797
|
+
let mut emitted_any = false;
|
|
798
|
+
for (name, ty) in entries {
|
|
799
|
+
if let crate::types::RustType::Named { fields, .. }
|
|
800
|
+
| crate::types::RustType::Object(fields)
|
|
801
|
+
// ^^ also accept inline shapes registered as aliases — though
|
|
802
|
+
// `from_annotation_with_aliases` should always have lifted
|
|
803
|
+
// them to `Named` by now.
|
|
804
|
+
= &ty
|
|
805
|
+
{
|
|
806
|
+
let struct_name = crate::types::named_struct_ident(&name);
|
|
807
|
+
self.write(&format!("#[derive(Clone, Debug, Default)]\n"));
|
|
808
|
+
self.write("#[allow(non_snake_case, non_camel_case_types)]\n");
|
|
809
|
+
self.write(&format!("pub struct {} {{\n", struct_name));
|
|
810
|
+
for (k, t) in fields {
|
|
811
|
+
self.write(&format!(
|
|
812
|
+
" pub {}: {},\n",
|
|
813
|
+
crate::types::field_ident(k),
|
|
814
|
+
t.to_rust_type_str()
|
|
815
|
+
));
|
|
816
|
+
}
|
|
817
|
+
self.write("}\n\n");
|
|
818
|
+
|
|
819
|
+
// Emit a hand-rolled JSON serialiser per struct so
|
|
820
|
+
// `JSON.stringify(typed_value)` (and `Vec<TishStruct_X>`)
|
|
821
|
+
// can bypass the `Value::Object` allocation entirely —
|
|
822
|
+
// we walk the struct's fields by name and write directly
|
|
823
|
+
// into the response buffer. ASCII-fast string escape is
|
|
824
|
+
// shared with the `Value` path via the
|
|
825
|
+
// `escape_json_string_into` helper that the runtime
|
|
826
|
+
// re-exports from `tishlang_core::json`.
|
|
827
|
+
self.write(&format!("impl {} {{\n", struct_name));
|
|
828
|
+
self.write(" pub fn _tish_write_json(&self, buf: &mut String) {\n");
|
|
829
|
+
self.write(" use std::fmt::Write as _;\n");
|
|
830
|
+
self.write(" buf.push('{');\n");
|
|
831
|
+
for (i, (k, t)) in fields.iter().enumerate() {
|
|
832
|
+
let sep = if i == 0 { "{" } else { ",{" };
|
|
833
|
+
let prefix = if i == 0 {
|
|
834
|
+
format!("\"\\\"{}\\\":\"", k.as_ref())
|
|
835
|
+
} else {
|
|
836
|
+
format!("\",\\\"{}\\\":\"", k.as_ref())
|
|
837
|
+
};
|
|
838
|
+
let _ = sep; // (lint silence; we built `prefix` above directly)
|
|
839
|
+
self.write(&format!(" buf.push_str({});\n", prefix));
|
|
840
|
+
let access = format!("self.{}", crate::types::field_ident(k));
|
|
841
|
+
match t {
|
|
842
|
+
crate::types::RustType::F64 => {
|
|
843
|
+
self.write(&format!(
|
|
844
|
+
" if {a}.is_nan() || {a}.is_infinite() {{ buf.push_str(\"null\"); }} else {{ let _ = write!(buf, \"{{}}\", {a}); }}\n",
|
|
845
|
+
a = access
|
|
846
|
+
));
|
|
847
|
+
}
|
|
848
|
+
crate::types::RustType::Bool => {
|
|
849
|
+
self.write(&format!(
|
|
850
|
+
" buf.push_str(if {} {{ \"true\" }} else {{ \"false\" }});\n",
|
|
851
|
+
access
|
|
852
|
+
));
|
|
853
|
+
}
|
|
854
|
+
crate::types::RustType::String => {
|
|
855
|
+
self.write(&format!(
|
|
856
|
+
" buf.push('\"'); tishlang_runtime::json::escape_into(buf, {}.as_str()); buf.push('\"');\n",
|
|
857
|
+
access
|
|
858
|
+
));
|
|
859
|
+
}
|
|
860
|
+
crate::types::RustType::Named { .. } => {
|
|
861
|
+
self.write(&format!(
|
|
862
|
+
" {}._tish_write_json(buf);\n",
|
|
863
|
+
access
|
|
864
|
+
));
|
|
865
|
+
}
|
|
866
|
+
crate::types::RustType::Vec(inner) if matches!(
|
|
867
|
+
inner.as_ref(),
|
|
868
|
+
crate::types::RustType::Named { .. }
|
|
869
|
+
) => {
|
|
870
|
+
self.write(" buf.push('[');\n");
|
|
871
|
+
self.write(&format!(
|
|
872
|
+
" for (i, item) in {}.iter().enumerate() {{ if i > 0 {{ buf.push(','); }} item._tish_write_json(buf); }}\n",
|
|
873
|
+
access
|
|
874
|
+
));
|
|
875
|
+
self.write(" buf.push(']');\n");
|
|
876
|
+
}
|
|
877
|
+
_ => {
|
|
878
|
+
// Fallback: convert the field to a Value and
|
|
879
|
+
// delegate to the dynamic stringifier.
|
|
880
|
+
let v_expr = t.to_value_expr(&access);
|
|
881
|
+
self.write(&format!(
|
|
882
|
+
" let _v: Value = {}; tishlang_runtime::json::stringify_into(buf, &_v);\n",
|
|
883
|
+
v_expr
|
|
884
|
+
));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
self.write(" buf.push('}');\n");
|
|
889
|
+
self.write(" }\n");
|
|
890
|
+
self.write("}\n\n");
|
|
891
|
+
emitted_any = true;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if emitted_any {
|
|
895
|
+
self.write("\n");
|
|
553
896
|
}
|
|
554
897
|
}
|
|
555
898
|
|
|
@@ -595,39 +938,47 @@ impl Codegen {
|
|
|
595
938
|
fn builtin_native_module_rust_init(&self, spec: &str, export_name: &str) -> Option<String> {
|
|
596
939
|
let init = match spec {
|
|
597
940
|
"tish:fs" if self.has_feature("fs") => match export_name {
|
|
598
|
-
"readFile" => Some("Value::
|
|
599
|
-
"writeFile" => Some("Value::
|
|
600
|
-
"fileExists" => Some("Value::
|
|
601
|
-
"isDir" => Some("Value::
|
|
602
|
-
"readDir" => Some("Value::
|
|
603
|
-
"mkdir" => Some("Value::
|
|
941
|
+
"readFile" => Some("Value::native(|args: &[Value]| tish_read_file(args))"),
|
|
942
|
+
"writeFile" => Some("Value::native(|args: &[Value]| tish_write_file(args))"),
|
|
943
|
+
"fileExists" => Some("Value::native(|args: &[Value]| tish_file_exists(args))"),
|
|
944
|
+
"isDir" => Some("Value::native(|args: &[Value]| tish_is_dir(args))"),
|
|
945
|
+
"readDir" => Some("Value::native(|args: &[Value]| tish_read_dir(args))"),
|
|
946
|
+
"mkdir" => Some("Value::native(|args: &[Value]| tish_mkdir(args))"),
|
|
604
947
|
_ => None,
|
|
605
948
|
},
|
|
606
949
|
"tish:http" if self.has_feature("http") => match export_name {
|
|
607
|
-
"fetch" => Some("Value::
|
|
608
|
-
"fetchAll" => Some("Value::
|
|
609
|
-
|
|
950
|
+
"fetch" => Some("Value::native(|args: &[Value]| tish_fetch_promise(args.to_vec()))"),
|
|
951
|
+
"fetchAll" => Some("Value::native(|args: &[Value]| tish_fetch_all_promise(args.to_vec()))"),
|
|
952
|
+
// `serve(port, handler)` (single shared handler) or
|
|
953
|
+
// `serve(port, { onWorker })` (per-worker factory). The
|
|
954
|
+
// latter dispatches into `http_serve_per_worker`, which
|
|
955
|
+
// calls onWorker once per accept thread to build that
|
|
956
|
+
// thread's handler.
|
|
957
|
+
"serve" => Some("Value::native(|args: &[Value]| { let handler = args.get(1).cloned().unwrap_or(Value::Null); match handler { Value::Function(f) => tish_http_serve(args, move |req_args| f(req_args)), Value::Object(ref opts) => { let factory = opts.borrow().get(&Arc::from(\"onWorker\")).cloned().unwrap_or(Value::Null); tishlang_runtime::http_serve_per_worker(args, factory) }, _ => Value::Null } })"),
|
|
610
958
|
"Promise" => Some("tish_promise_object()"),
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
"
|
|
959
|
+
_ => None,
|
|
960
|
+
},
|
|
961
|
+
"tish:timers" if self.has_feature("timers") => match export_name {
|
|
962
|
+
"setTimeout" => Some("Value::native(|args: &[Value]| tish_timer_set_timeout(args))"),
|
|
963
|
+
"setInterval" => Some("Value::native(|args: &[Value]| tish_timer_set_interval(args))"),
|
|
964
|
+
"clearTimeout" => Some("Value::native(|args: &[Value]| tish_timer_clear_timeout(args))"),
|
|
965
|
+
"clearInterval" => Some("Value::native(|args: &[Value]| tish_timer_clear_interval(args))"),
|
|
615
966
|
_ => None,
|
|
616
967
|
},
|
|
617
968
|
"tish:process" if self.has_feature("process") => match export_name {
|
|
618
|
-
"exit" => Some("Value::
|
|
619
|
-
"cwd" => Some("Value::
|
|
620
|
-
"exec" => Some("Value::
|
|
621
|
-
"argv" => Some("Value::Array(
|
|
622
|
-
"env" => Some("Value::Object(
|
|
623
|
-
"process" => Some("{ let mut m = ObjectMap::default(); m.insert(Arc::from(\"exit\"), Value::
|
|
969
|
+
"exit" => Some("Value::native(|args: &[Value]| tish_process_exit(args))"),
|
|
970
|
+
"cwd" => Some("Value::native(|args: &[Value]| tish_process_cwd(args))"),
|
|
971
|
+
"exec" => Some("Value::native(|args: &[Value]| tish_process_exec(args))"),
|
|
972
|
+
"argv" => Some("Value::Array(VmRef::new(std::env::args().map(|s| Value::String(s.into())).collect()))"),
|
|
973
|
+
"env" => Some("Value::Object(VmRef::new(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect()))"),
|
|
974
|
+
"process" => Some("{ let mut m = ObjectMap::default(); m.insert(Arc::from(\"exit\"), Value::native(|args: &[Value]| tish_process_exit(args))); m.insert(Arc::from(\"cwd\"), Value::native(|args: &[Value]| tish_process_cwd(args))); m.insert(Arc::from(\"exec\"), Value::native(|args: &[Value]| tish_process_exec(args))); m.insert(Arc::from(\"argv\"), Value::Array(VmRef::new(std::env::args().map(|s| Value::String(s.into())).collect()))); m.insert(Arc::from(\"env\"), Value::Object(VmRef::new(std::env::vars().map(|(k,v)| (Arc::from(k.as_str()), Value::String(v.into()))).collect::<ObjectMap>()))); Value::Object(VmRef::new(m)) }"),
|
|
624
975
|
_ => None,
|
|
625
976
|
},
|
|
626
977
|
"tish:ws" if self.has_feature("ws") => match export_name {
|
|
627
|
-
"WebSocket" => Some("Value::
|
|
628
|
-
"Server" => Some("Value::
|
|
629
|
-
"wsSend" => Some("Value::
|
|
630
|
-
"wsBroadcast" => Some("Value::
|
|
978
|
+
"WebSocket" => Some("Value::native(|args: &[Value]| tish_ws_client(args))"),
|
|
979
|
+
"Server" => Some("Value::native(|args: &[Value]| tish_ws_server_construct(args))"),
|
|
980
|
+
"wsSend" => Some("Value::native(|args: &[Value]| Value::Bool(tishlang_runtime::ws_send_native(args.first().unwrap_or(&Value::Null), &args.get(1).map(|v| v.to_display_string()).unwrap_or_default())))"),
|
|
981
|
+
"wsBroadcast" => Some("Value::native(|args: &[Value]| tishlang_runtime::ws_broadcast_native(args))"),
|
|
631
982
|
_ => None,
|
|
632
983
|
},
|
|
633
984
|
_ => return None,
|
|
@@ -637,7 +988,7 @@ impl Codegen {
|
|
|
637
988
|
|
|
638
989
|
fn has_feature(&self, name: &str) -> bool {
|
|
639
990
|
if self.features.contains("full") {
|
|
640
|
-
matches!(name, "http" | "fs" | "process" | "regex" | "ws")
|
|
991
|
+
matches!(name, "http" | "timers" | "fs" | "process" | "regex" | "ws")
|
|
641
992
|
} else {
|
|
642
993
|
self.features.contains(name)
|
|
643
994
|
}
|
|
@@ -668,17 +1019,23 @@ impl Codegen {
|
|
|
668
1019
|
/// Escape Rust reserved keywords by prefixing with r#
|
|
669
1020
|
fn escape_ident(name: &str) -> Cow<'_, str> {
|
|
670
1021
|
// Rust standard library macros that conflict with variable names
|
|
671
|
-
const RUST_MACROS: &[&str] = &[
|
|
1022
|
+
const RUST_MACROS: &[&str] = &[
|
|
1023
|
+
"line",
|
|
1024
|
+
"column",
|
|
1025
|
+
"file",
|
|
1026
|
+
"module_path",
|
|
1027
|
+
"stringify",
|
|
1028
|
+
"concat",
|
|
1029
|
+
];
|
|
672
1030
|
if RUST_MACROS.contains(&name) {
|
|
673
1031
|
return Cow::Owned(format!("r#{}", name));
|
|
674
1032
|
}
|
|
675
1033
|
const RUST_KEYWORDS: &[&str] = &[
|
|
676
|
-
"as", "async", "await", "break", "const", "continue", "crate", "dyn",
|
|
677
|
-
"
|
|
678
|
-
"
|
|
679
|
-
"
|
|
680
|
-
"
|
|
681
|
-
"final", "macro", "override", "priv", "try", "typeof", "unsized",
|
|
1034
|
+
"as", "async", "await", "break", "const", "continue", "crate", "dyn", "else", "enum",
|
|
1035
|
+
"extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod",
|
|
1036
|
+
"move", "mut", "pub", "ref", "return", "self", "Self", "static", "struct", "super",
|
|
1037
|
+
"trait", "true", "type", "unsafe", "use", "where", "while", "abstract", "become",
|
|
1038
|
+
"box", "do", "final", "macro", "override", "priv", "try", "typeof", "unsized",
|
|
682
1039
|
"virtual", "yield",
|
|
683
1040
|
];
|
|
684
1041
|
if RUST_KEYWORDS.contains(&name) {
|
|
@@ -717,20 +1074,20 @@ impl Codegen {
|
|
|
717
1074
|
if !Self::needs_clone(expr) {
|
|
718
1075
|
return false;
|
|
719
1076
|
}
|
|
720
|
-
|
|
1077
|
+
|
|
721
1078
|
// Check for last-use optimization on simple identifiers
|
|
722
1079
|
if let Expr::Ident { name, .. } = expr {
|
|
723
1080
|
// Don't optimize RefCell-wrapped vars (they're borrowed, not owned)
|
|
724
1081
|
if self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
725
1082
|
return true;
|
|
726
1083
|
}
|
|
727
|
-
|
|
1084
|
+
|
|
728
1085
|
// Inside a loop, any variable used in an init (e.g. "let x = outerVar") must be cloned:
|
|
729
1086
|
// the loop body runs multiple times, so we cannot move on the first iteration.
|
|
730
1087
|
if !self.loop_stack.is_empty() {
|
|
731
1088
|
return true;
|
|
732
1089
|
}
|
|
733
|
-
|
|
1090
|
+
|
|
734
1091
|
// Check if this is the last use
|
|
735
1092
|
if let Some(ref mut analyzer) = self.usage_analyzer {
|
|
736
1093
|
if analyzer.is_last_use(name.as_ref()) {
|
|
@@ -738,7 +1095,7 @@ impl Codegen {
|
|
|
738
1095
|
}
|
|
739
1096
|
}
|
|
740
1097
|
}
|
|
741
|
-
|
|
1098
|
+
|
|
742
1099
|
true
|
|
743
1100
|
}
|
|
744
1101
|
|
|
@@ -806,7 +1163,7 @@ impl Codegen {
|
|
|
806
1163
|
/// Returns Some(true) for ascending, Some(false) for descending, None if not detected
|
|
807
1164
|
fn detect_numeric_sort_comparator(expr: &Expr) -> Option<bool> {
|
|
808
1165
|
use tishlang_ast::ArrowBody;
|
|
809
|
-
|
|
1166
|
+
|
|
810
1167
|
if let Expr::ArrowFunction { params, body, .. } = expr {
|
|
811
1168
|
if params.len() != 2 {
|
|
812
1169
|
return None;
|
|
@@ -819,7 +1176,7 @@ impl Codegen {
|
|
|
819
1176
|
}
|
|
820
1177
|
_ => return None,
|
|
821
1178
|
};
|
|
822
|
-
|
|
1179
|
+
|
|
823
1180
|
// Body must be a single expression that's a subtraction
|
|
824
1181
|
let body_expr = match body {
|
|
825
1182
|
ArrowBody::Expr(e) => e.as_ref(),
|
|
@@ -831,10 +1188,24 @@ impl Codegen {
|
|
|
831
1188
|
}
|
|
832
1189
|
}
|
|
833
1190
|
};
|
|
834
|
-
|
|
835
|
-
if let Expr::Binary {
|
|
1191
|
+
|
|
1192
|
+
if let Expr::Binary {
|
|
1193
|
+
left,
|
|
1194
|
+
op: BinOp::Sub,
|
|
1195
|
+
right,
|
|
1196
|
+
..
|
|
1197
|
+
} = body_expr
|
|
1198
|
+
{
|
|
836
1199
|
// Check for a - b (ascending) or b - a (descending)
|
|
837
|
-
if let (
|
|
1200
|
+
if let (
|
|
1201
|
+
Expr::Ident {
|
|
1202
|
+
name: left_name, ..
|
|
1203
|
+
},
|
|
1204
|
+
Expr::Ident {
|
|
1205
|
+
name: right_name, ..
|
|
1206
|
+
},
|
|
1207
|
+
) = (left.as_ref(), right.as_ref())
|
|
1208
|
+
{
|
|
838
1209
|
if left_name.as_ref() == param_a && right_name.as_ref() == param_b {
|
|
839
1210
|
return Some(true); // ascending
|
|
840
1211
|
}
|
|
@@ -850,22 +1221,26 @@ impl Codegen {
|
|
|
850
1221
|
fn emit_program(&mut self, program: &Program) -> Result<(), CompileError> {
|
|
851
1222
|
self.is_async = program_uses_async(program);
|
|
852
1223
|
self.program_has_jsx = tishlang_ui::jsx::program_contains_jsx(program);
|
|
1224
|
+
self.program_fun_decl_names = tishlang_ui::jsx::collect_fun_decl_names(program);
|
|
853
1225
|
self.write("#![allow(unused, non_snake_case)]\n\n");
|
|
854
1226
|
self.write("use std::cell::RefCell;\n");
|
|
855
1227
|
self.write("use std::rc::Rc;\n");
|
|
856
1228
|
self.write("use std::sync::Arc;\n");
|
|
857
|
-
self.write("use tishlang_runtime::{console_debug as tish_console_debug, console_info as tish_console_info, console_log as tish_console_log, console_warn as tish_console_warn, console_error as tish_console_error, boolean as tish_boolean, decode_uri as tish_decode_uri, encode_uri as tish_encode_uri, in_operator as tish_in_operator, is_finite as tish_is_finite, is_nan as tish_is_nan, json_parse as tish_json_parse, json_stringify as tish_json_stringify, math_abs as tish_math_abs, math_ceil as tish_math_ceil, math_floor as tish_math_floor, math_max as tish_math_max, math_min as tish_math_min, math_round as tish_math_round, math_sqrt as tish_math_sqrt, parse_float as tish_parse_float, parse_int as tish_parse_int, math_random as tish_math_random, math_pow as tish_math_pow, math_sin as tish_math_sin, math_cos as tish_math_cos, math_tan as tish_math_tan, math_log as tish_math_log, math_exp as tish_math_exp, math_sign as tish_math_sign, math_trunc as tish_math_trunc, date_now as tish_date_now, array_is_array as tish_array_is_array, string_from_char_code as tish_string_from_char_code, object_assign as tish_object_assign, object_keys as tish_object_keys, object_values as tish_object_values, object_entries as tish_object_entries, object_from_entries as tish_object_from_entries, tish_construct, tish_uint8_array_constructor, tish_audio_context_constructor, ObjectMap, TishError, Value};\n");
|
|
1229
|
+
self.write("use tishlang_runtime::{console_debug as tish_console_debug, console_info as tish_console_info, console_log as tish_console_log, console_warn as tish_console_warn, console_error as tish_console_error, boolean as tish_boolean, decode_uri as tish_decode_uri, encode_uri as tish_encode_uri, string_escape_html_impl as tish_escape_html, in_operator as tish_in_operator, is_finite as tish_is_finite, is_nan as tish_is_nan, json_parse as tish_json_parse, json_stringify as tish_json_stringify, math_abs as tish_math_abs, math_ceil as tish_math_ceil, math_floor as tish_math_floor, math_max as tish_math_max, math_min as tish_math_min, math_round as tish_math_round, math_sqrt as tish_math_sqrt, parse_float as tish_parse_float, parse_int as tish_parse_int, math_random as tish_math_random, math_pow as tish_math_pow, math_sin as tish_math_sin, math_cos as tish_math_cos, math_tan as tish_math_tan, math_log as tish_math_log, math_exp as tish_math_exp, math_sign as tish_math_sign, math_trunc as tish_math_trunc, date_now as tish_date_now, array_is_array as tish_array_is_array, string_from_char_code as tish_string_from_char_code, object_assign as tish_object_assign, object_keys as tish_object_keys, object_values as tish_object_values, object_entries as tish_object_entries, object_from_entries as tish_object_from_entries, tish_construct, tish_uint8_array_constructor, tish_audio_context_constructor, register_static_route as tish_register_static_route, ObjectMap, TishError, Value, VmRef};\n");
|
|
858
1230
|
if self.program_has_jsx {
|
|
859
1231
|
self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
|
|
860
1232
|
}
|
|
861
1233
|
if self.has_feature("process") {
|
|
862
1234
|
self.write("use tishlang_runtime::{process_exit as tish_process_exit, process_cwd as tish_process_cwd, process_exec as tish_process_exec};\n");
|
|
863
1235
|
}
|
|
1236
|
+
if self.has_feature("timers") {
|
|
1237
|
+
self.write("use tishlang_runtime::{timer_set_timeout as tish_timer_set_timeout, timer_clear_timeout as tish_timer_clear_timeout, timer_set_interval as tish_timer_set_interval, timer_clear_interval as tish_timer_clear_interval};\n");
|
|
1238
|
+
}
|
|
864
1239
|
if self.has_feature("http") {
|
|
865
1240
|
if self.is_async {
|
|
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,
|
|
1241
|
+
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve, promise_object as tish_promise_object, await_promise as tish_await_promise};\n");
|
|
867
1242
|
} else {
|
|
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
|
|
1243
|
+
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve};\n");
|
|
869
1244
|
}
|
|
870
1245
|
}
|
|
871
1246
|
if self.has_feature("fs") {
|
|
@@ -879,6 +1254,19 @@ impl Codegen {
|
|
|
879
1254
|
}
|
|
880
1255
|
self.write("\n");
|
|
881
1256
|
|
|
1257
|
+
// Collect every `type Foo = { ... }` declaration in the program
|
|
1258
|
+
// (recursive, so they can also live inside blocks / branches) and
|
|
1259
|
+
// canonicalise each into a `RustType::Named` with its field list.
|
|
1260
|
+
// Aliases that resolve to a non-Object shape (e.g. `type N = number`)
|
|
1261
|
+
// are stored too, so later annotations like `let x: N = 0` still
|
|
1262
|
+
// pick up the right native type.
|
|
1263
|
+
self.collect_type_aliases(&program.statements);
|
|
1264
|
+
// Emit a Rust `struct` for every alias whose RHS is an object
|
|
1265
|
+
// shape. Subsequent `let x: Foo = ...` literals lower to plain
|
|
1266
|
+
// struct moves (no `VmRef::new(ObjectMap::from(..))` allocation),
|
|
1267
|
+
// and `x.field` becomes a direct field access.
|
|
1268
|
+
self.emit_named_struct_decls();
|
|
1269
|
+
|
|
882
1270
|
if self.is_async {
|
|
883
1271
|
self.writeln("#[tokio::main]");
|
|
884
1272
|
self.writeln("async fn main() {");
|
|
@@ -907,150 +1295,220 @@ impl Codegen {
|
|
|
907
1295
|
self.indent += 1;
|
|
908
1296
|
|
|
909
1297
|
// Initialize builtins
|
|
910
|
-
self.writeln("let mut console = Value::Object(
|
|
1298
|
+
self.writeln("let mut console = Value::Object(VmRef::new(ObjectMap::from([");
|
|
911
1299
|
self.indent += 1;
|
|
912
|
-
self.writeln("(Arc::from(\"debug\"), Value::
|
|
913
|
-
self.writeln("(Arc::from(\"info\"), Value::
|
|
914
|
-
self.writeln("(Arc::from(\"log\"), Value::
|
|
915
|
-
self.writeln("(Arc::from(\"warn\"), Value::
|
|
916
|
-
self.writeln("(Arc::from(\"error\"), Value::
|
|
1300
|
+
self.writeln("(Arc::from(\"debug\"), Value::native(|args: &[Value]| { tish_console_debug(args); Value::Null })),");
|
|
1301
|
+
self.writeln("(Arc::from(\"info\"), Value::native(|args: &[Value]| { tish_console_info(args); Value::Null })),");
|
|
1302
|
+
self.writeln("(Arc::from(\"log\"), Value::native(|args: &[Value]| { tish_console_log(args); Value::Null })),");
|
|
1303
|
+
self.writeln("(Arc::from(\"warn\"), Value::native(|args: &[Value]| { tish_console_warn(args); Value::Null })),");
|
|
1304
|
+
self.writeln("(Arc::from(\"error\"), Value::native(|args: &[Value]| { tish_console_error(args); Value::Null })),");
|
|
917
1305
|
self.indent -= 1;
|
|
918
|
-
self.writeln("])))
|
|
919
|
-
self.writeln(
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
self.writeln(
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
self.writeln(
|
|
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
|
+
);
|
|
1322
|
+
self.writeln(
|
|
1323
|
+
r#"let registerStaticRoute = Value::native(|args: &[Value]| { let path = match args.get(0) { Some(Value::String(s)) => s.to_string(), _ => return Value::Null }; let body = match args.get(1) { Some(Value::String(s)) => s.as_bytes().to_vec(), _ => return Value::Null }; let ct = match args.get(2) { Some(Value::String(s)) => s.to_string(), _ => "application/octet-stream".to_string() }; tish_register_static_route(&path, &body, &ct); Value::Null });"#,
|
|
1324
|
+
);
|
|
1325
|
+
self.writeln(
|
|
1326
|
+
"let htmlEscape = Value::native(|args: &[Value]| tish_escape_html(args.first().unwrap_or(&Value::Null)));",
|
|
1327
|
+
);
|
|
1328
|
+
self.writeln(
|
|
1329
|
+
"let isFinite = Value::native(|args: &[Value]| tish_is_finite(args));",
|
|
1330
|
+
);
|
|
1331
|
+
self.writeln("let isNaN = Value::native(|args: &[Value]| tish_is_nan(args));");
|
|
926
1332
|
self.writeln("let Infinity = Value::Number(f64::INFINITY);");
|
|
927
1333
|
self.writeln("let NaN = Value::Number(f64::NAN);");
|
|
928
|
-
self.writeln("let Math = Value::Object(
|
|
1334
|
+
self.writeln("let Math = Value::Object(VmRef::new(ObjectMap::from([");
|
|
929
1335
|
self.indent += 1;
|
|
930
|
-
self.writeln(
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
self.writeln("(Arc::from(\"
|
|
934
|
-
self.writeln(
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
self.writeln(
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
self.writeln("(Arc::from(\"
|
|
941
|
-
self.writeln("(Arc::from(\"
|
|
942
|
-
self.writeln("(Arc::from(\"
|
|
943
|
-
self.writeln("(Arc::from(\"
|
|
944
|
-
self.writeln(
|
|
945
|
-
|
|
1336
|
+
self.writeln(
|
|
1337
|
+
"(Arc::from(\"abs\"), Value::native(|args: &[Value]| tish_math_abs(args))),",
|
|
1338
|
+
);
|
|
1339
|
+
self.writeln("(Arc::from(\"sqrt\"), Value::native(|args: &[Value]| tish_math_sqrt(args))),");
|
|
1340
|
+
self.writeln(
|
|
1341
|
+
"(Arc::from(\"min\"), Value::native(|args: &[Value]| tish_math_min(args))),",
|
|
1342
|
+
);
|
|
1343
|
+
self.writeln(
|
|
1344
|
+
"(Arc::from(\"max\"), Value::native(|args: &[Value]| tish_math_max(args))),",
|
|
1345
|
+
);
|
|
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
|
+
self.writeln(
|
|
1351
|
+
"(Arc::from(\"pow\"), Value::native(|args: &[Value]| tish_math_pow(args))),",
|
|
1352
|
+
);
|
|
1353
|
+
self.writeln(
|
|
1354
|
+
"(Arc::from(\"sin\"), Value::native(|args: &[Value]| tish_math_sin(args))),",
|
|
1355
|
+
);
|
|
1356
|
+
self.writeln(
|
|
1357
|
+
"(Arc::from(\"cos\"), Value::native(|args: &[Value]| tish_math_cos(args))),",
|
|
1358
|
+
);
|
|
1359
|
+
self.writeln(
|
|
1360
|
+
"(Arc::from(\"tan\"), Value::native(|args: &[Value]| tish_math_tan(args))),",
|
|
1361
|
+
);
|
|
1362
|
+
self.writeln(
|
|
1363
|
+
"(Arc::from(\"log\"), Value::native(|args: &[Value]| tish_math_log(args))),",
|
|
1364
|
+
);
|
|
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))),");
|
|
946
1370
|
self.writeln("(Arc::from(\"PI\"), Value::Number(std::f64::consts::PI)),");
|
|
947
1371
|
self.writeln("(Arc::from(\"E\"), Value::Number(std::f64::consts::E)),");
|
|
948
1372
|
self.indent -= 1;
|
|
949
|
-
self.writeln("])))
|
|
950
|
-
self.writeln("let JSON = Value::Object(
|
|
1373
|
+
self.writeln("])));");
|
|
1374
|
+
self.writeln("let JSON = Value::Object(VmRef::new(ObjectMap::from([");
|
|
951
1375
|
self.indent += 1;
|
|
952
|
-
self.writeln("(Arc::from(\"parse\"), Value::
|
|
953
|
-
self.writeln("(Arc::from(\"stringify\"), Value::
|
|
1376
|
+
self.writeln("(Arc::from(\"parse\"), Value::native(|args: &[Value]| tish_json_parse(args))),");
|
|
1377
|
+
self.writeln("(Arc::from(\"stringify\"), Value::native(|args: &[Value]| tish_json_stringify(args))),");
|
|
954
1378
|
self.indent -= 1;
|
|
955
|
-
self.writeln("])))
|
|
1379
|
+
self.writeln("])));");
|
|
956
1380
|
|
|
957
|
-
self.writeln("let Array = Value::Object(
|
|
1381
|
+
self.writeln("let Array = Value::Object(VmRef::new(ObjectMap::from([");
|
|
958
1382
|
self.indent += 1;
|
|
959
|
-
self.writeln("(Arc::from(\"isArray\"), Value::
|
|
1383
|
+
self.writeln("(Arc::from(\"isArray\"), Value::native(|args: &[Value]| tish_array_is_array(args))),");
|
|
960
1384
|
self.indent -= 1;
|
|
961
|
-
self.writeln("])))
|
|
1385
|
+
self.writeln("])));");
|
|
962
1386
|
|
|
963
|
-
self.writeln("let String = Value::Object(
|
|
1387
|
+
self.writeln("let String = Value::Object(VmRef::new(ObjectMap::from([");
|
|
964
1388
|
self.indent += 1;
|
|
965
|
-
self.writeln("(Arc::from(\"fromCharCode\"), Value::
|
|
1389
|
+
self.writeln("(Arc::from(\"fromCharCode\"), Value::native(|args: &[Value]| tish_string_from_char_code(args))),");
|
|
966
1390
|
self.indent -= 1;
|
|
967
|
-
self.writeln("])))
|
|
1391
|
+
self.writeln("])));");
|
|
968
1392
|
|
|
969
|
-
self.writeln("let Date = Value::Object(
|
|
1393
|
+
self.writeln("let Date = Value::Object(VmRef::new(ObjectMap::from([");
|
|
970
1394
|
self.indent += 1;
|
|
971
|
-
self.writeln(
|
|
1395
|
+
self.writeln(
|
|
1396
|
+
"(Arc::from(\"now\"), Value::native(|args: &[Value]| tish_date_now(args))),",
|
|
1397
|
+
);
|
|
972
1398
|
self.indent -= 1;
|
|
973
|
-
self.writeln("])))
|
|
1399
|
+
self.writeln("])));");
|
|
974
1400
|
|
|
975
|
-
self.writeln("let Object = Value::Object(
|
|
1401
|
+
self.writeln("let Object = Value::Object(VmRef::new(ObjectMap::from([");
|
|
976
1402
|
self.indent += 1;
|
|
977
|
-
self.writeln("(Arc::from(\"assign\"), Value::
|
|
978
|
-
self.writeln("(Arc::from(\"keys\"), Value::
|
|
979
|
-
self.writeln("(Arc::from(\"values\"), Value::
|
|
980
|
-
self.writeln("(Arc::from(\"entries\"), Value::
|
|
981
|
-
self.writeln("(Arc::from(\"fromEntries\"), Value::
|
|
1403
|
+
self.writeln("(Arc::from(\"assign\"), Value::native(|args: &[Value]| tish_object_assign(args))),");
|
|
1404
|
+
self.writeln("(Arc::from(\"keys\"), Value::native(|args: &[Value]| tish_object_keys(args))),");
|
|
1405
|
+
self.writeln("(Arc::from(\"values\"), Value::native(|args: &[Value]| tish_object_values(args))),");
|
|
1406
|
+
self.writeln("(Arc::from(\"entries\"), Value::native(|args: &[Value]| tish_object_entries(args))),");
|
|
1407
|
+
self.writeln("(Arc::from(\"fromEntries\"), Value::native(|args: &[Value]| tish_object_from_entries(args))),");
|
|
982
1408
|
self.indent -= 1;
|
|
983
|
-
self.writeln("])))
|
|
1409
|
+
self.writeln("])));");
|
|
984
1410
|
|
|
985
1411
|
self.writeln("let Uint8Array = tish_uint8_array_constructor();");
|
|
986
1412
|
self.writeln("let AudioContext = tish_audio_context_constructor();");
|
|
987
1413
|
|
|
988
1414
|
if self.has_feature("process") {
|
|
989
|
-
self.writeln("let process = Value::Object(
|
|
1415
|
+
self.writeln("let process = Value::Object(VmRef::new({");
|
|
990
1416
|
self.indent += 1;
|
|
991
1417
|
self.writeln("let mut p = ObjectMap::default();");
|
|
992
|
-
self.writeln("p.insert(Arc::from(\"exit\"), Value::
|
|
993
|
-
self.writeln("p.insert(Arc::from(\"cwd\"), Value::
|
|
994
|
-
self.writeln("p.insert(Arc::from(\"exec\"), Value::
|
|
1418
|
+
self.writeln("p.insert(Arc::from(\"exit\"), Value::native(|args: &[Value]| tish_process_exit(args)));");
|
|
1419
|
+
self.writeln("p.insert(Arc::from(\"cwd\"), Value::native(|args: &[Value]| tish_process_cwd(args)));");
|
|
1420
|
+
self.writeln("p.insert(Arc::from(\"exec\"), Value::native(|args: &[Value]| tish_process_exec(args)));");
|
|
995
1421
|
self.writeln("let argv: Vec<Value> = std::env::args().map(|s| Value::String(s.into())).collect();");
|
|
996
|
-
self.writeln(
|
|
1422
|
+
self.writeln(
|
|
1423
|
+
"p.insert(Arc::from(\"argv\"), Value::Array(VmRef::new(argv)));",
|
|
1424
|
+
);
|
|
997
1425
|
self.writeln("let mut env_obj = ObjectMap::default();");
|
|
998
1426
|
self.writeln("for (key, value) in std::env::vars() {");
|
|
999
1427
|
self.indent += 1;
|
|
1000
1428
|
self.writeln("env_obj.insert(Arc::from(key.as_str()), Value::String(value.into()));");
|
|
1001
1429
|
self.indent -= 1;
|
|
1002
1430
|
self.writeln("}");
|
|
1003
|
-
self.writeln(
|
|
1431
|
+
self.writeln(
|
|
1432
|
+
"p.insert(Arc::from(\"env\"), Value::Object(VmRef::new(env_obj)));",
|
|
1433
|
+
);
|
|
1004
1434
|
self.writeln("p");
|
|
1005
1435
|
self.indent -= 1;
|
|
1006
|
-
self.writeln("}))
|
|
1436
|
+
self.writeln("}));");
|
|
1007
1437
|
}
|
|
1008
1438
|
|
|
1439
|
+
if self.has_feature("timers") {
|
|
1440
|
+
self.writeln("let setTimeout = Value::native(|args: &[Value]| tish_timer_set_timeout(args));");
|
|
1441
|
+
self.writeln("let clearTimeout = Value::native(|args: &[Value]| tish_timer_clear_timeout(args));");
|
|
1442
|
+
self.writeln("let setInterval = Value::native(|args: &[Value]| tish_timer_set_interval(args));");
|
|
1443
|
+
self.writeln("let clearInterval = Value::native(|args: &[Value]| tish_timer_clear_interval(args));");
|
|
1444
|
+
}
|
|
1009
1445
|
if self.has_feature("http") {
|
|
1010
|
-
self.writeln("let fetch = Value::
|
|
1011
|
-
self.writeln("let fetchAll = Value::
|
|
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)));");
|
|
1446
|
+
self.writeln("let fetch = Value::native(|args: &[Value]| tish_fetch_promise(args.to_vec()));");
|
|
1447
|
+
self.writeln("let fetchAll = Value::native(|args: &[Value]| tish_fetch_all_promise(args.to_vec()));");
|
|
1014
1448
|
if self.is_async {
|
|
1015
1449
|
self.writeln("let Promise = tish_promise_object();");
|
|
1016
1450
|
}
|
|
1017
|
-
|
|
1451
|
+
// `serve` supports two shapes:
|
|
1452
|
+
// 1. serve(port, handler) // single shared handler
|
|
1453
|
+
// 2. serve(port, { onWorker: (workerId) => handler, ... })
|
|
1454
|
+
//
|
|
1455
|
+
// Shape (2) lets users build per-worker state (DB connection,
|
|
1456
|
+
// cache, counter, ...) without a global mutex. The runtime
|
|
1457
|
+
// dispatches each accept thread to its own handler, all in
|
|
1458
|
+
// parallel under `send-values`.
|
|
1459
|
+
self.writeln("let serve = Value::native(|args: &[Value]| {");
|
|
1018
1460
|
self.indent += 1;
|
|
1019
|
-
self.writeln("let port = args.first().cloned().unwrap_or(Value::Null);");
|
|
1020
1461
|
self.writeln("let handler = args.get(1).cloned().unwrap_or(Value::Null);");
|
|
1021
|
-
self.writeln("
|
|
1462
|
+
self.writeln("match handler {");
|
|
1022
1463
|
self.indent += 1;
|
|
1023
|
-
self.writeln("tish_http_serve(args, move |req_args| f(req_args))");
|
|
1024
|
-
self.
|
|
1025
|
-
self.writeln("} else {");
|
|
1464
|
+
self.writeln("Value::Function(f) => tish_http_serve(args, move |req_args| f(req_args)),");
|
|
1465
|
+
self.writeln("Value::Object(ref opts) => {");
|
|
1026
1466
|
self.indent += 1;
|
|
1027
|
-
self.writeln("Value::Null");
|
|
1467
|
+
self.writeln("let factory = opts.borrow().get(&Arc::from(\"onWorker\")).cloned().unwrap_or(Value::Null);");
|
|
1468
|
+
self.writeln("tishlang_runtime::http_serve_per_worker(args, factory)");
|
|
1469
|
+
self.indent -= 1;
|
|
1470
|
+
self.writeln("},");
|
|
1471
|
+
self.writeln("_ => Value::Null,");
|
|
1028
1472
|
self.indent -= 1;
|
|
1029
1473
|
self.writeln("}");
|
|
1030
1474
|
self.indent -= 1;
|
|
1031
|
-
self.writeln("})
|
|
1475
|
+
self.writeln("});");
|
|
1032
1476
|
}
|
|
1033
1477
|
|
|
1034
1478
|
if self.has_feature("fs") {
|
|
1035
|
-
self.writeln(
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
self.writeln(
|
|
1039
|
-
|
|
1040
|
-
|
|
1479
|
+
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));",
|
|
1494
|
+
);
|
|
1041
1495
|
}
|
|
1042
1496
|
|
|
1043
1497
|
if self.has_feature("regex") {
|
|
1044
|
-
self.writeln(
|
|
1498
|
+
self.writeln(
|
|
1499
|
+
"let RegExp = Value::native(|args: &[Value]| regexp_new(args));",
|
|
1500
|
+
);
|
|
1045
1501
|
}
|
|
1046
1502
|
|
|
1047
1503
|
if self.program_has_jsx {
|
|
1048
1504
|
self.writeln("install_thread_local_host(Box::new(HeadlessHost::default()));");
|
|
1049
1505
|
self.writeln("let Fragment = fragment_value();");
|
|
1050
|
-
self.writeln("let h = Value::
|
|
1051
|
-
self.writeln("let text = Value::
|
|
1052
|
-
self.writeln(
|
|
1053
|
-
|
|
1506
|
+
self.writeln("let h = Value::native(|args: &[Value]| ui_h(args));");
|
|
1507
|
+
self.writeln("let text = Value::native(|args: &[Value]| ui_text(args));");
|
|
1508
|
+
self.writeln(
|
|
1509
|
+
"let useState = Value::native(|args: &[Value]| native_use_state(args));",
|
|
1510
|
+
);
|
|
1511
|
+
self.writeln("let createRoot = Value::native(|args: &[Value]| native_create_root(args));");
|
|
1054
1512
|
}
|
|
1055
1513
|
|
|
1056
1514
|
// Polars, Egui etc. are emitted via VarDecl from import { X } from 'tish:...'
|
|
@@ -1060,7 +1518,10 @@ impl Codegen {
|
|
|
1060
1518
|
*self.function_scope_stack.last_mut().unwrap() = top_level_funcs.clone();
|
|
1061
1519
|
for func_name in &top_level_funcs {
|
|
1062
1520
|
let escaped = Self::escape_ident(func_name);
|
|
1063
|
-
self.writeln(&format!(
|
|
1521
|
+
self.writeln(&format!(
|
|
1522
|
+
"let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
|
|
1523
|
+
escaped
|
|
1524
|
+
));
|
|
1064
1525
|
}
|
|
1065
1526
|
|
|
1066
1527
|
// Initialize usage analyzer for move/clone optimization
|
|
@@ -1097,9 +1558,11 @@ impl Codegen {
|
|
|
1097
1558
|
self.indent += 1;
|
|
1098
1559
|
self.type_context.push_scope();
|
|
1099
1560
|
self.outer_vars_stack.push(Vec::new());
|
|
1100
|
-
self.rc_cell_storage_scopes
|
|
1561
|
+
self.rc_cell_storage_scopes
|
|
1562
|
+
.push(std::collections::HashSet::new());
|
|
1101
1563
|
// Prepass: vars that must be RefCell because nested closures capture and mutate them
|
|
1102
|
-
let vars_mutated_by_nested =
|
|
1564
|
+
let vars_mutated_by_nested =
|
|
1565
|
+
Self::collect_vars_mutated_by_nested_closures(statements);
|
|
1103
1566
|
for v in &vars_mutated_by_nested {
|
|
1104
1567
|
self.refcell_wrapped_vars.insert(v.clone());
|
|
1105
1568
|
}
|
|
@@ -1109,7 +1572,10 @@ impl Codegen {
|
|
|
1109
1572
|
// Create cells for all functions in this scope
|
|
1110
1573
|
for func_name in &func_names {
|
|
1111
1574
|
let escaped = Self::escape_ident(func_name);
|
|
1112
|
-
self.writeln(&format!(
|
|
1575
|
+
self.writeln(&format!(
|
|
1576
|
+
"let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
|
|
1577
|
+
escaped
|
|
1578
|
+
));
|
|
1113
1579
|
}
|
|
1114
1580
|
for s in statements {
|
|
1115
1581
|
self.emit_statement(s)?;
|
|
@@ -1124,19 +1590,30 @@ impl Codegen {
|
|
|
1124
1590
|
self.indent -= 1;
|
|
1125
1591
|
self.writeln("}");
|
|
1126
1592
|
}
|
|
1127
|
-
Statement::VarDecl {
|
|
1128
|
-
|
|
1593
|
+
Statement::VarDecl {
|
|
1594
|
+
name,
|
|
1595
|
+
mutable,
|
|
1596
|
+
type_ann,
|
|
1597
|
+
init,
|
|
1598
|
+
..
|
|
1599
|
+
} => {
|
|
1600
|
+
// Determine the Rust type from annotation, consulting the
|
|
1601
|
+
// user-declared `type` aliases so a `let x: World = ...`
|
|
1602
|
+
// resolves to `RustType::Named { name: "World", fields }`
|
|
1603
|
+
// and we can emit a struct move instead of a Value box.
|
|
1129
1604
|
let rust_type = type_ann
|
|
1130
1605
|
.as_ref()
|
|
1131
|
-
.map(
|
|
1606
|
+
.map(|t| {
|
|
1607
|
+
crate::types::RustType::from_annotation_with_aliases(t, &self.type_aliases)
|
|
1608
|
+
})
|
|
1132
1609
|
.unwrap_or(RustType::Value);
|
|
1133
1610
|
|
|
1134
1611
|
// Track the variable type
|
|
1135
1612
|
self.type_context.define(name.as_ref(), rust_type.clone());
|
|
1136
|
-
|
|
1613
|
+
|
|
1137
1614
|
let mutability = if *mutable { "let mut" } else { "let" };
|
|
1138
1615
|
let escaped_name = Self::escape_ident(name.as_ref());
|
|
1139
|
-
|
|
1616
|
+
|
|
1140
1617
|
if rust_type.is_native() {
|
|
1141
1618
|
// Generate native typed variable
|
|
1142
1619
|
let expr_str = match init.as_ref() {
|
|
@@ -1146,7 +1623,7 @@ impl Codegen {
|
|
|
1146
1623
|
if self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
1147
1624
|
// Closure-mutated: same Rc<RefCell<T>> pattern as Value (assignments use borrow_mut)
|
|
1148
1625
|
self.writeln(&format!(
|
|
1149
|
-
"let {} =
|
|
1626
|
+
"let {} = VmRef::new({});",
|
|
1150
1627
|
escaped_name, expr_str
|
|
1151
1628
|
));
|
|
1152
1629
|
self.rc_cell_storage_define(name.as_ref());
|
|
@@ -1164,8 +1641,7 @@ impl Codegen {
|
|
|
1164
1641
|
let s = self.emit_expr(e)?;
|
|
1165
1642
|
// Variable refs (Ident) in init must always clone: they may be used
|
|
1166
1643
|
// multiple times (e.g. in a loop body) and we cannot move.
|
|
1167
|
-
let needs = matches!(e, Expr::Ident { .. })
|
|
1168
|
-
|| self.should_clone(e);
|
|
1644
|
+
let needs = matches!(e, Expr::Ident { .. }) || self.should_clone(e);
|
|
1169
1645
|
(s, needs)
|
|
1170
1646
|
}
|
|
1171
1647
|
None => ("Value::Null".to_string(), false),
|
|
@@ -1177,25 +1653,42 @@ impl Codegen {
|
|
|
1177
1653
|
} else {
|
|
1178
1654
|
expr_str.to_string()
|
|
1179
1655
|
};
|
|
1180
|
-
self.writeln(&format!(
|
|
1656
|
+
self.writeln(&format!(
|
|
1657
|
+
"let {} = VmRef::new({});",
|
|
1658
|
+
escaped_name, init_val
|
|
1659
|
+
));
|
|
1181
1660
|
self.rc_cell_storage_define(name.as_ref());
|
|
1182
1661
|
} else if clone_needed {
|
|
1183
|
-
self.writeln(&format!(
|
|
1662
|
+
self.writeln(&format!(
|
|
1663
|
+
"{} {} = ({}).clone();",
|
|
1664
|
+
mutability, escaped_name, expr_str
|
|
1665
|
+
));
|
|
1184
1666
|
} else {
|
|
1185
1667
|
self.writeln(&format!("{} {} = {};", mutability, escaped_name, expr_str));
|
|
1186
1668
|
}
|
|
1187
1669
|
}
|
|
1188
|
-
|
|
1670
|
+
|
|
1189
1671
|
if let Some(scope) = self.outer_vars_stack.last_mut() {
|
|
1190
1672
|
scope.push(name.to_string());
|
|
1191
1673
|
}
|
|
1192
1674
|
}
|
|
1193
|
-
Statement::VarDeclDestructure {
|
|
1675
|
+
Statement::VarDeclDestructure {
|
|
1676
|
+
pattern,
|
|
1677
|
+
mutable,
|
|
1678
|
+
init,
|
|
1679
|
+
span,
|
|
1680
|
+
..
|
|
1681
|
+
} => {
|
|
1194
1682
|
let expr = self.emit_expr(init)?;
|
|
1195
1683
|
let mutability = if *mutable { "let mut" } else { "let" };
|
|
1196
|
-
let clone_suffix = if Self::needs_clone(init) {
|
|
1684
|
+
let clone_suffix = if Self::needs_clone(init) {
|
|
1685
|
+
".clone()"
|
|
1686
|
+
} else {
|
|
1687
|
+
""
|
|
1688
|
+
};
|
|
1197
1689
|
self.writeln(&format!("let _destruct_val = ({}){};", expr, clone_suffix));
|
|
1198
1690
|
self.emit_destruct_bindings(pattern, "_destruct_val", mutability, *span)?;
|
|
1691
|
+
self.register_destruct_pattern_outer_vars(pattern);
|
|
1199
1692
|
}
|
|
1200
1693
|
Statement::ExprStmt { expr, .. } => {
|
|
1201
1694
|
let e = self.emit_expr(expr)?;
|
|
@@ -1232,7 +1725,12 @@ impl Codegen {
|
|
|
1232
1725
|
self.indent -= 1;
|
|
1233
1726
|
self.writeln("}");
|
|
1234
1727
|
}
|
|
1235
|
-
Statement::ForOf {
|
|
1728
|
+
Statement::ForOf {
|
|
1729
|
+
name,
|
|
1730
|
+
iterable,
|
|
1731
|
+
body,
|
|
1732
|
+
..
|
|
1733
|
+
} => {
|
|
1236
1734
|
let iter_expr = self.emit_expr(iterable)?;
|
|
1237
1735
|
self.writeln(&format!("{{ let _fof = ({}).clone();", iter_expr));
|
|
1238
1736
|
self.indent += 1;
|
|
@@ -1242,7 +1740,10 @@ impl Codegen {
|
|
|
1242
1740
|
self.indent += 1;
|
|
1243
1741
|
self.writeln("for _v in _arr.borrow().iter() {");
|
|
1244
1742
|
self.indent += 1;
|
|
1245
|
-
self.writeln(&format!(
|
|
1743
|
+
self.writeln(&format!(
|
|
1744
|
+
"let {} = _v.clone();",
|
|
1745
|
+
Self::escape_ident(name.as_ref())
|
|
1746
|
+
));
|
|
1246
1747
|
self.emit_statement(body)?;
|
|
1247
1748
|
self.indent -= 1;
|
|
1248
1749
|
self.writeln("}");
|
|
@@ -1320,12 +1821,10 @@ impl Codegen {
|
|
|
1320
1821
|
}
|
|
1321
1822
|
}
|
|
1322
1823
|
Statement::Continue { .. } => {
|
|
1323
|
-
let snippet = self
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
)
|
|
1328
|
-
});
|
|
1824
|
+
let snippet = self
|
|
1825
|
+
.loop_stack
|
|
1826
|
+
.last()
|
|
1827
|
+
.map(|(label, update)| (label.clone(), update.clone()));
|
|
1329
1828
|
if let Some((label, Some(update))) = snippet {
|
|
1330
1829
|
self.writeln(&update);
|
|
1331
1830
|
self.writeln(&format!("continue {};", label));
|
|
@@ -1341,7 +1840,15 @@ impl Codegen {
|
|
|
1341
1840
|
span: None,
|
|
1342
1841
|
});
|
|
1343
1842
|
}
|
|
1344
|
-
Statement::
|
|
1843
|
+
Statement::TypeAlias { .. }
|
|
1844
|
+
| Statement::DeclareVar { .. }
|
|
1845
|
+
| Statement::DeclareFun { .. } => {}
|
|
1846
|
+
Statement::Switch {
|
|
1847
|
+
expr,
|
|
1848
|
+
cases,
|
|
1849
|
+
default_body,
|
|
1850
|
+
..
|
|
1851
|
+
} => {
|
|
1345
1852
|
let e = self.emit_expr(expr)?;
|
|
1346
1853
|
self.writeln(&format!("let _sv = {};", e));
|
|
1347
1854
|
self.writeln("match () {");
|
|
@@ -1389,10 +1896,17 @@ impl Codegen {
|
|
|
1389
1896
|
}
|
|
1390
1897
|
Statement::Throw { value, .. } => {
|
|
1391
1898
|
let v = self.emit_expr(value)?;
|
|
1392
|
-
self.
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1899
|
+
if self.value_fn_depth > 0 {
|
|
1900
|
+
self.writeln(&format!(
|
|
1901
|
+
"{{ let _th = {}; panic!(\"uncaught throw: {{}}\", _th.to_display_string()); }}",
|
|
1902
|
+
v
|
|
1903
|
+
));
|
|
1904
|
+
} else {
|
|
1905
|
+
self.writeln(&format!(
|
|
1906
|
+
"return Err(Box::new(tishlang_runtime::TishError::Throw({})) as Box<dyn std::error::Error>);",
|
|
1907
|
+
v
|
|
1908
|
+
));
|
|
1909
|
+
}
|
|
1396
1910
|
}
|
|
1397
1911
|
Statement::Try {
|
|
1398
1912
|
body,
|
|
@@ -1407,7 +1921,7 @@ impl Codegen {
|
|
|
1407
1921
|
self.writeln("Ok(Value::Null)");
|
|
1408
1922
|
self.indent -= 1;
|
|
1409
1923
|
self.writeln("})();");
|
|
1410
|
-
|
|
1924
|
+
|
|
1411
1925
|
if let Some(catch_stmt) = catch_body {
|
|
1412
1926
|
if let Some(param) = catch_param {
|
|
1413
1927
|
self.writeln("if let Err(e) = _try_result {");
|
|
@@ -1417,12 +1931,27 @@ impl Codegen {
|
|
|
1417
1931
|
self.writeln("Ok(tish_err) => {");
|
|
1418
1932
|
self.indent += 1;
|
|
1419
1933
|
self.writeln("if let tishlang_runtime::TishError::Throw(v) = *tish_err {");
|
|
1420
|
-
self.writeln(&format!(
|
|
1934
|
+
self.writeln(&format!(
|
|
1935
|
+
"let {} = v.clone();",
|
|
1936
|
+
Self::escape_ident(param.as_ref())
|
|
1937
|
+
));
|
|
1421
1938
|
self.emit_statement(catch_stmt)?;
|
|
1422
|
-
self.
|
|
1939
|
+
if self.value_fn_depth > 0 {
|
|
1940
|
+
self.writeln(
|
|
1941
|
+
"} else { panic!(\"unhandled error in native Tish: {:?}\", *tish_err); }",
|
|
1942
|
+
);
|
|
1943
|
+
} else {
|
|
1944
|
+
self.writeln("} else { return Err(Box::new(tish_err)); }");
|
|
1945
|
+
}
|
|
1423
1946
|
self.indent -= 1;
|
|
1424
1947
|
self.writeln("}");
|
|
1425
|
-
self.
|
|
1948
|
+
if self.value_fn_depth > 0 {
|
|
1949
|
+
self.writeln(
|
|
1950
|
+
"Err(orig) => panic!(\"non-Tish error in native Tish: {:?}\", orig),",
|
|
1951
|
+
);
|
|
1952
|
+
} else {
|
|
1953
|
+
self.writeln("Err(orig) => return Err(orig),");
|
|
1954
|
+
}
|
|
1426
1955
|
self.indent -= 1;
|
|
1427
1956
|
self.writeln("}");
|
|
1428
1957
|
self.indent -= 1;
|
|
@@ -1434,25 +1963,36 @@ impl Codegen {
|
|
|
1434
1963
|
}
|
|
1435
1964
|
self.writeln("}");
|
|
1436
1965
|
}
|
|
1437
|
-
|
|
1966
|
+
|
|
1438
1967
|
if let Some(finally_stmt) = finally_body {
|
|
1439
1968
|
self.emit_statement(finally_stmt)?;
|
|
1440
1969
|
}
|
|
1441
1970
|
}
|
|
1442
|
-
Statement::FunDecl {
|
|
1971
|
+
Statement::FunDecl {
|
|
1972
|
+
name,
|
|
1973
|
+
params,
|
|
1974
|
+
rest_param,
|
|
1975
|
+
body,
|
|
1976
|
+
span,
|
|
1977
|
+
..
|
|
1978
|
+
} => {
|
|
1443
1979
|
// Use Rc<RefCell<>> pattern to allow recursive function calls
|
|
1444
1980
|
// The function can reference itself through the cell
|
|
1445
1981
|
let name_raw = name.as_ref();
|
|
1446
1982
|
let name_str = Self::escape_ident(name_raw);
|
|
1447
1983
|
// Check if cell was already created by block prescan
|
|
1448
|
-
let cell_exists = self
|
|
1984
|
+
let cell_exists = self
|
|
1985
|
+
.function_scope_stack
|
|
1449
1986
|
.last()
|
|
1450
1987
|
.map(|scope| scope.contains(&name_raw.to_string()))
|
|
1451
1988
|
.unwrap_or(false);
|
|
1452
1989
|
if !cell_exists {
|
|
1453
|
-
self.writeln(&format!(
|
|
1990
|
+
self.writeln(&format!(
|
|
1991
|
+
"let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
|
|
1992
|
+
name_str
|
|
1993
|
+
));
|
|
1454
1994
|
}
|
|
1455
|
-
|
|
1995
|
+
|
|
1456
1996
|
// Analyze body to find which identifiers are actually referenced
|
|
1457
1997
|
let mut referenced = HashSet::new();
|
|
1458
1998
|
Self::collect_stmt_idents(body, &mut referenced);
|
|
@@ -1461,9 +2001,10 @@ impl Codegen {
|
|
|
1461
2001
|
.flat_map(|p| p.bound_names())
|
|
1462
2002
|
.map(|n| n.to_string())
|
|
1463
2003
|
.collect();
|
|
1464
|
-
|
|
2004
|
+
|
|
1465
2005
|
// Collect all outer parameters that need to be captured (only those referenced)
|
|
1466
|
-
let outer_params: Vec<String> = self
|
|
2006
|
+
let outer_params: Vec<String> = self
|
|
2007
|
+
.outer_params_stack
|
|
1467
2008
|
.iter()
|
|
1468
2009
|
.flat_map(|p| p.iter().cloned())
|
|
1469
2010
|
.filter(|name| referenced.contains(name) && !param_names.contains(name))
|
|
@@ -1472,11 +2013,34 @@ impl Codegen {
|
|
|
1472
2013
|
// Exclude params and variables declared in this function's body (locals)
|
|
1473
2014
|
let mut local_var_names = HashSet::new();
|
|
1474
2015
|
Self::collect_local_var_names(body, &mut local_var_names);
|
|
1475
|
-
let outer_vars: Vec<String> = self
|
|
2016
|
+
let outer_vars: Vec<String> = self
|
|
2017
|
+
.outer_vars_stack
|
|
1476
2018
|
.iter()
|
|
1477
2019
|
.flat_map(|v| v.iter().cloned())
|
|
1478
|
-
.filter(|name|
|
|
1479
|
-
|
|
2020
|
+
.filter(|name| {
|
|
2021
|
+
referenced.contains(name)
|
|
2022
|
+
&& !param_names.contains(name)
|
|
2023
|
+
&& !local_var_names.contains(name)
|
|
2024
|
+
})
|
|
2025
|
+
.filter(|name| {
|
|
2026
|
+
![
|
|
2027
|
+
"Boolean",
|
|
2028
|
+
"console",
|
|
2029
|
+
"Math",
|
|
2030
|
+
"JSON",
|
|
2031
|
+
"Date",
|
|
2032
|
+
"Object",
|
|
2033
|
+
"process",
|
|
2034
|
+
"setTimeout",
|
|
2035
|
+
"clearTimeout",
|
|
2036
|
+
"setInterval",
|
|
2037
|
+
"clearInterval",
|
|
2038
|
+
"Promise",
|
|
2039
|
+
"RegExp",
|
|
2040
|
+
"Polars",
|
|
2041
|
+
]
|
|
2042
|
+
.contains(&name.as_str())
|
|
2043
|
+
})
|
|
1480
2044
|
.collect();
|
|
1481
2045
|
|
|
1482
2046
|
// Outer vars that are assigned in the body need RefCell (capture cell, add to refcell_wrapped_vars).
|
|
@@ -1499,9 +2063,15 @@ impl Codegen {
|
|
|
1499
2063
|
for outer_var in &outer_vars {
|
|
1500
2064
|
let var_escaped = Self::escape_ident(outer_var);
|
|
1501
2065
|
if self.rc_cell_storage_contains(outer_var) {
|
|
1502
|
-
self.writeln(&format!(
|
|
2066
|
+
self.writeln(&format!(
|
|
2067
|
+
"let {}_cell = {}.clone();",
|
|
2068
|
+
var_escaped, var_escaped
|
|
2069
|
+
));
|
|
1503
2070
|
} else {
|
|
1504
|
-
self.writeln(&format!(
|
|
2071
|
+
self.writeln(&format!(
|
|
2072
|
+
"let {}_cell = VmRef::new({}.clone());",
|
|
2073
|
+
var_escaped, var_escaped
|
|
2074
|
+
));
|
|
1505
2075
|
}
|
|
1506
2076
|
}
|
|
1507
2077
|
|
|
@@ -1510,57 +2080,119 @@ impl Codegen {
|
|
|
1510
2080
|
// Clone RefCell for outer vars so closure can capture
|
|
1511
2081
|
for outer_var in &outer_vars {
|
|
1512
2082
|
let var_escaped = Self::escape_ident(outer_var);
|
|
1513
|
-
self.writeln(&format!(
|
|
2083
|
+
self.writeln(&format!(
|
|
2084
|
+
"let {}_cell = {}_cell.clone();",
|
|
2085
|
+
var_escaped, var_escaped
|
|
2086
|
+
));
|
|
1514
2087
|
}
|
|
1515
2088
|
// Clone the cell so the closure can reference the function recursively
|
|
1516
2089
|
let needs_self_ref = referenced.contains(name_raw);
|
|
1517
2090
|
if needs_self_ref {
|
|
1518
|
-
self.writeln(&format!(
|
|
2091
|
+
self.writeln(&format!(
|
|
2092
|
+
"let {}_ref = {}_cell.clone();",
|
|
2093
|
+
name_str, name_str
|
|
2094
|
+
));
|
|
1519
2095
|
}
|
|
1520
2096
|
// Clone sibling function cells for mutual recursion
|
|
1521
|
-
let sibling_fns: Vec<String> = self
|
|
2097
|
+
let sibling_fns: Vec<String> = self
|
|
2098
|
+
.function_scope_stack
|
|
1522
2099
|
.last()
|
|
1523
|
-
.map(|scope|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
2100
|
+
.map(|scope| {
|
|
2101
|
+
scope
|
|
2102
|
+
.iter()
|
|
2103
|
+
.filter(|s| s.as_str() != name_raw && referenced.contains(s.as_str()))
|
|
2104
|
+
.cloned()
|
|
2105
|
+
.collect()
|
|
2106
|
+
})
|
|
1527
2107
|
.unwrap_or_default();
|
|
1528
2108
|
for sibling in &sibling_fns {
|
|
1529
2109
|
let sibling_escaped = Self::escape_ident(sibling);
|
|
1530
|
-
self.writeln(&format!(
|
|
2110
|
+
self.writeln(&format!(
|
|
2111
|
+
"let {}_ref = {}_cell.clone();",
|
|
2112
|
+
sibling_escaped, sibling_escaped
|
|
2113
|
+
));
|
|
1531
2114
|
}
|
|
1532
2115
|
// Clone outer parameters so they can be captured by the move closure
|
|
1533
2116
|
for outer_param in &outer_params {
|
|
1534
2117
|
let param_escaped = Self::escape_ident(outer_param);
|
|
1535
|
-
self.writeln(&format!(
|
|
2118
|
+
self.writeln(&format!(
|
|
2119
|
+
"let {} = {}.clone();",
|
|
2120
|
+
param_escaped, param_escaped
|
|
2121
|
+
));
|
|
1536
2122
|
}
|
|
1537
2123
|
// Only clone builtins that are actually referenced (clone so outer scope can still use them, e.g. process for PORT before serve)
|
|
1538
|
-
for builtin in &[
|
|
2124
|
+
for builtin in &[
|
|
2125
|
+
"Boolean",
|
|
2126
|
+
"console",
|
|
2127
|
+
"Math",
|
|
2128
|
+
"JSON",
|
|
2129
|
+
"Date",
|
|
2130
|
+
"Object",
|
|
2131
|
+
"Uint8Array",
|
|
2132
|
+
"AudioContext",
|
|
2133
|
+
"process",
|
|
2134
|
+
"setTimeout",
|
|
2135
|
+
"clearTimeout",
|
|
2136
|
+
"setInterval",
|
|
2137
|
+
"clearInterval",
|
|
2138
|
+
"Promise",
|
|
2139
|
+
"RegExp",
|
|
2140
|
+
"Polars",
|
|
2141
|
+
// Free-standing global functions used inside user-defined
|
|
2142
|
+
// functions also need to be cloned into the closure
|
|
2143
|
+
// capture, or the emitted Rust hits E0382 (moved value)
|
|
2144
|
+
// at the closure's defining `let`.
|
|
2145
|
+
"parseInt",
|
|
2146
|
+
"parseFloat",
|
|
2147
|
+
"isNaN",
|
|
2148
|
+
"isFinite",
|
|
2149
|
+
"encodeURI",
|
|
2150
|
+
"decodeURI",
|
|
2151
|
+
"htmlEscape",
|
|
2152
|
+
"registerStaticRoute",
|
|
2153
|
+
"String",
|
|
2154
|
+
"Infinity",
|
|
2155
|
+
"NaN",
|
|
2156
|
+
"serve",
|
|
2157
|
+
] {
|
|
1539
2158
|
if referenced.contains(*builtin) {
|
|
1540
2159
|
self.writeln(&format!("let {} = {}.clone();", builtin, builtin));
|
|
1541
2160
|
}
|
|
1542
2161
|
}
|
|
1543
|
-
self.writeln("Value::
|
|
2162
|
+
self.writeln("Value::native(move |args: &[Value]| {");
|
|
2163
|
+
self.value_fn_depth += 1;
|
|
1544
2164
|
self.indent += 1;
|
|
1545
2165
|
// Mutable outer vars: capture the RefCell so assignments use borrow_mut
|
|
1546
2166
|
for outer_var in &mutable_outer_vars {
|
|
1547
2167
|
let var_escaped = Self::escape_ident(outer_var);
|
|
1548
|
-
self.writeln(&format!(
|
|
2168
|
+
self.writeln(&format!(
|
|
2169
|
+
"let {} = {}_cell.clone();",
|
|
2170
|
+
var_escaped, var_escaped
|
|
2171
|
+
));
|
|
1549
2172
|
}
|
|
1550
2173
|
// Read-only outer vars: Value binding from borrow (avoids param-shadow issues)
|
|
1551
2174
|
for outer_var in &read_only_outer_vars {
|
|
1552
2175
|
let var_escaped = Self::escape_ident(outer_var);
|
|
1553
|
-
self.writeln(&format!(
|
|
2176
|
+
self.writeln(&format!(
|
|
2177
|
+
"let {} = (*{}_cell.borrow()).clone();",
|
|
2178
|
+
var_escaped, var_escaped
|
|
2179
|
+
));
|
|
1554
2180
|
}
|
|
1555
2181
|
// Make the function available by its name inside the closure (only if recursive)
|
|
1556
2182
|
if needs_self_ref {
|
|
1557
|
-
self.writeln(&format!(
|
|
2183
|
+
self.writeln(&format!(
|
|
2184
|
+
"let {} = (*{}_ref.borrow()).clone();",
|
|
2185
|
+
name_str, name_str
|
|
2186
|
+
));
|
|
1558
2187
|
}
|
|
1559
2188
|
// Make sibling functions available for mutual recursion
|
|
1560
2189
|
for sibling in &sibling_fns {
|
|
1561
2190
|
let sibling_escaped = Self::escape_ident(sibling);
|
|
1562
|
-
self.writeln(&format!(
|
|
1563
|
-
|
|
2191
|
+
self.writeln(&format!(
|
|
2192
|
+
"let {} = (*{}_ref.borrow()).clone();",
|
|
2193
|
+
sibling_escaped, sibling_escaped
|
|
2194
|
+
));
|
|
2195
|
+
}
|
|
1564
2196
|
// Extract just the parameter names (type annotations are parsed but not used in codegen yet)
|
|
1565
2197
|
let current_param_names: Vec<String> = params
|
|
1566
2198
|
.iter()
|
|
@@ -1589,58 +2221,88 @@ impl Codegen {
|
|
|
1589
2221
|
}
|
|
1590
2222
|
if let Some(rest) = rest_param {
|
|
1591
2223
|
self.writeln(&format!(
|
|
1592
|
-
"let {} = Value::Array(
|
|
2224
|
+
"let {} = Value::Array(VmRef::new(args[{}..].to_vec()));",
|
|
1593
2225
|
Self::escape_ident(rest.name.as_ref()),
|
|
1594
2226
|
params.len()
|
|
1595
2227
|
));
|
|
1596
2228
|
}
|
|
1597
|
-
|
|
1598
|
-
// Push current params to stack for nested functions
|
|
1599
|
-
self.outer_params_stack.push(current_param_names);
|
|
1600
|
-
|
|
1601
|
-
// Function bodies are sync closures (even Tish async fn) - use block_on for await
|
|
1602
|
-
self.async_context_stack.push(false);
|
|
1603
2229
|
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
//
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
self.
|
|
1618
|
-
}
|
|
1619
|
-
for s in statements {
|
|
1620
|
-
self.emit_statement(s)?;
|
|
2230
|
+
self.type_context
|
|
2231
|
+
.push_fun_param_scope(params, rest_param.as_ref());
|
|
2232
|
+
|
|
2233
|
+
let fun_body_res: Result<(), CompileError> = (|| -> Result<(), CompileError> {
|
|
2234
|
+
// Push current params to stack for nested functions
|
|
2235
|
+
self.outer_params_stack.push(current_param_names);
|
|
2236
|
+
|
|
2237
|
+
// Function bodies are sync closures (even Tish async fn) - use block_on for await
|
|
2238
|
+
self.async_context_stack.push(false);
|
|
2239
|
+
|
|
2240
|
+
// Mutable outer vars must be in refcell_wrapped_vars so Assign/CompoundAssign emit borrow_mut
|
|
2241
|
+
let saved_refcell = self.refcell_wrapped_vars.clone();
|
|
2242
|
+
for v in &mutable_outer_vars {
|
|
2243
|
+
self.refcell_wrapped_vars.insert(v.clone());
|
|
1621
2244
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
2245
|
+
|
|
2246
|
+
// Pre-scan body for nested functions (handles function body as Block)
|
|
2247
|
+
if let Statement::Block { statements, .. } = body.as_ref() {
|
|
2248
|
+
let nested_func_names = self.prescan_function_decls(statements);
|
|
2249
|
+
self.function_scope_stack.push(nested_func_names.clone());
|
|
2250
|
+
self.outer_vars_stack.push(Vec::new());
|
|
2251
|
+
self.rc_cell_storage_scopes
|
|
2252
|
+
.push(std::collections::HashSet::new());
|
|
2253
|
+
// Create cells for nested functions
|
|
2254
|
+
for func_name in &nested_func_names {
|
|
2255
|
+
let escaped = Self::escape_ident(func_name);
|
|
2256
|
+
self.writeln(&format!(
|
|
2257
|
+
"let {}_cell: VmRef<Value> = VmRef::new(Value::Null);",
|
|
2258
|
+
escaped
|
|
2259
|
+
));
|
|
2260
|
+
}
|
|
2261
|
+
for s in statements {
|
|
2262
|
+
self.emit_statement(s)?;
|
|
2263
|
+
}
|
|
2264
|
+
self.function_scope_stack.pop();
|
|
2265
|
+
self.outer_vars_stack.pop();
|
|
2266
|
+
self.rc_cell_storage_scopes.pop();
|
|
2267
|
+
} else {
|
|
2268
|
+
self.function_scope_stack.push(Vec::new());
|
|
2269
|
+
self.outer_vars_stack.push(Vec::new());
|
|
2270
|
+
self.rc_cell_storage_scopes
|
|
2271
|
+
.push(std::collections::HashSet::new());
|
|
2272
|
+
self.emit_statement(body)?;
|
|
2273
|
+
self.function_scope_stack.pop();
|
|
2274
|
+
self.outer_vars_stack.pop();
|
|
2275
|
+
self.rc_cell_storage_scopes.pop();
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
self.async_context_stack.pop();
|
|
2279
|
+
|
|
2280
|
+
// Restore refcell_wrapped_vars (remove mutable outer vars we added)
|
|
2281
|
+
self.refcell_wrapped_vars = saved_refcell;
|
|
2282
|
+
|
|
2283
|
+
// Pop params stack
|
|
2284
|
+
self.outer_params_stack.pop();
|
|
2285
|
+
|
|
2286
|
+
Ok(())
|
|
2287
|
+
})();
|
|
2288
|
+
|
|
2289
|
+
self.type_context.pop_scope();
|
|
2290
|
+
if let Err(e) = fun_body_res {
|
|
2291
|
+
self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
|
|
2292
|
+
return Err(e);
|
|
1627
2293
|
}
|
|
1628
|
-
|
|
1629
|
-
self.async_context_stack.pop();
|
|
1630
2294
|
|
|
1631
|
-
// Restore refcell_wrapped_vars (remove mutable outer vars we added)
|
|
1632
|
-
self.refcell_wrapped_vars = saved_refcell;
|
|
1633
|
-
|
|
1634
|
-
// Pop params stack
|
|
1635
|
-
self.outer_params_stack.pop();
|
|
1636
|
-
|
|
1637
2295
|
self.writeln("Value::Null");
|
|
1638
2296
|
self.indent -= 1;
|
|
1639
|
-
self.writeln("})
|
|
2297
|
+
self.writeln("})");
|
|
2298
|
+
self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
|
|
1640
2299
|
self.indent -= 1;
|
|
1641
2300
|
self.writeln("};");
|
|
1642
2301
|
// Update the cell with the actual function value
|
|
1643
|
-
self.writeln(&format!(
|
|
2302
|
+
self.writeln(&format!(
|
|
2303
|
+
"*{}_cell.borrow_mut() = {}.clone();",
|
|
2304
|
+
name_str, name_str
|
|
2305
|
+
));
|
|
1644
2306
|
}
|
|
1645
2307
|
}
|
|
1646
2308
|
Ok(())
|
|
@@ -1673,7 +2335,10 @@ impl Codegen {
|
|
|
1673
2335
|
}
|
|
1674
2336
|
}
|
|
1675
2337
|
}
|
|
1676
|
-
Ok(format!(
|
|
2338
|
+
Ok(format!(
|
|
2339
|
+
"{{ let mut _args: Vec<Value> = Vec::new(); {} _args }}",
|
|
2340
|
+
parts.join(" ")
|
|
2341
|
+
))
|
|
1677
2342
|
} else {
|
|
1678
2343
|
let mut emitted = Vec::new();
|
|
1679
2344
|
for arg in args {
|
|
@@ -1695,14 +2360,20 @@ impl Codegen {
|
|
|
1695
2360
|
}
|
|
1696
2361
|
}
|
|
1697
2362
|
|
|
1698
|
-
fn emit_destruct_bindings(
|
|
2363
|
+
fn emit_destruct_bindings(
|
|
2364
|
+
&mut self,
|
|
2365
|
+
pattern: &DestructPattern,
|
|
2366
|
+
value_expr: &str,
|
|
2367
|
+
mutability: &str,
|
|
2368
|
+
span: Span,
|
|
2369
|
+
) -> Result<(), CompileError> {
|
|
1699
2370
|
// Flat `let` bindings so names stay in scope for the rest of the function (e.g. JSX).
|
|
1700
2371
|
match pattern {
|
|
1701
2372
|
DestructPattern::Array(elements) => {
|
|
1702
2373
|
for (i, elem) in elements.iter().enumerate() {
|
|
1703
2374
|
if let Some(el) = elem {
|
|
1704
2375
|
match el {
|
|
1705
|
-
DestructElement::Ident(name) => {
|
|
2376
|
+
DestructElement::Ident(name, _) => {
|
|
1706
2377
|
self.writeln(&format!(
|
|
1707
2378
|
"{} {} = match &({}) {{ Value::Array(ref _a) => _a.borrow().get({}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
|
|
1708
2379
|
mutability,
|
|
@@ -1719,9 +2390,9 @@ impl Codegen {
|
|
|
1719
2390
|
));
|
|
1720
2391
|
self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
|
|
1721
2392
|
}
|
|
1722
|
-
DestructElement::Rest(name) => {
|
|
2393
|
+
DestructElement::Rest(name, _) => {
|
|
1723
2394
|
self.writeln(&format!(
|
|
1724
|
-
"{} {} = match &({}) {{ Value::Array(ref _a) => {{ let _b = _a.borrow(); Value::Array(
|
|
2395
|
+
"{} {} = match &({}) {{ Value::Array(ref _a) => {{ let _b = _a.borrow(); Value::Array(VmRef::new(_b.iter().skip({}).cloned().collect())) }}, _ => Value::Array(VmRef::new(Vec::new())) }};",
|
|
1725
2396
|
mutability,
|
|
1726
2397
|
Self::escape_ident(name.as_ref()),
|
|
1727
2398
|
value_expr,
|
|
@@ -1736,7 +2407,7 @@ impl Codegen {
|
|
|
1736
2407
|
for prop in props {
|
|
1737
2408
|
let key = prop.key.as_ref();
|
|
1738
2409
|
match &prop.value {
|
|
1739
|
-
DestructElement::Ident(name) => {
|
|
2410
|
+
DestructElement::Ident(name, _) => {
|
|
1740
2411
|
self.writeln(&format!(
|
|
1741
2412
|
"{} {} = match &({}) {{ Value::Object(ref _o) => _o.borrow().get({:?}).cloned().unwrap_or(Value::Null), _ => Value::Null }};",
|
|
1742
2413
|
mutability,
|
|
@@ -1753,7 +2424,7 @@ impl Codegen {
|
|
|
1753
2424
|
));
|
|
1754
2425
|
self.emit_destruct_bindings(nested, &nested_var, mutability, span)?;
|
|
1755
2426
|
}
|
|
1756
|
-
DestructElement::Rest(_) => {
|
|
2427
|
+
DestructElement::Rest(_, _) => {
|
|
1757
2428
|
return Err(CompileError::new(
|
|
1758
2429
|
"Rest in object destructuring not supported",
|
|
1759
2430
|
Some(span),
|
|
@@ -1766,6 +2437,47 @@ impl Codegen {
|
|
|
1766
2437
|
Ok(())
|
|
1767
2438
|
}
|
|
1768
2439
|
|
|
2440
|
+
/// Like `VarDecl` pushing onto `outer_vars_stack`, so nested `move` closures rebind
|
|
2441
|
+
/// destructured names via `_cell` / `.clone()` instead of moving `Value` multiple times.
|
|
2442
|
+
fn register_destruct_pattern_outer_vars(&mut self, pattern: &DestructPattern) {
|
|
2443
|
+
match pattern {
|
|
2444
|
+
DestructPattern::Array(elements) => {
|
|
2445
|
+
for el in elements.iter().flatten() {
|
|
2446
|
+
match el {
|
|
2447
|
+
DestructElement::Ident(name, _) => {
|
|
2448
|
+
if let Some(scope) = self.outer_vars_stack.last_mut() {
|
|
2449
|
+
scope.push(name.to_string());
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
DestructElement::Pattern(nested) => {
|
|
2453
|
+
self.register_destruct_pattern_outer_vars(nested);
|
|
2454
|
+
}
|
|
2455
|
+
DestructElement::Rest(name, _) => {
|
|
2456
|
+
if let Some(scope) = self.outer_vars_stack.last_mut() {
|
|
2457
|
+
scope.push(name.to_string());
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
DestructPattern::Object(props) => {
|
|
2464
|
+
for prop in props {
|
|
2465
|
+
match &prop.value {
|
|
2466
|
+
DestructElement::Ident(name, _) => {
|
|
2467
|
+
if let Some(scope) = self.outer_vars_stack.last_mut() {
|
|
2468
|
+
scope.push(name.to_string());
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
DestructElement::Pattern(nested) => {
|
|
2472
|
+
self.register_destruct_pattern_outer_vars(nested);
|
|
2473
|
+
}
|
|
2474
|
+
DestructElement::Rest(_, _) => {}
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
|
|
1769
2481
|
fn emit_expr(&mut self, expr: &Expr) -> Result<String, CompileError> {
|
|
1770
2482
|
Ok(match expr {
|
|
1771
2483
|
Expr::Literal { value, .. } => match value {
|
|
@@ -1819,6 +2531,51 @@ impl Codegen {
|
|
|
1819
2531
|
}
|
|
1820
2532
|
}
|
|
1821
2533
|
Expr::Call { callee, args, .. } => {
|
|
2534
|
+
// Typed-struct shortcut for `JSON.stringify(typedValue)`.
|
|
2535
|
+
// When the single arg has a known native type that owns a
|
|
2536
|
+
// hand-rolled `_tish_write_json` (struct or `Vec<struct>`),
|
|
2537
|
+
// emit a direct write into a String buffer and skip the
|
|
2538
|
+
// entire `Value::Object` / `Value::Array` allocation
|
|
2539
|
+
// round-trip + the dynamic stringifier walk. Wraps the
|
|
2540
|
+
// result in `Value::String` for the caller, which is what
|
|
2541
|
+
// the existing `JSON.stringify` returned anyway.
|
|
2542
|
+
if let Expr::Member {
|
|
2543
|
+
object,
|
|
2544
|
+
prop: MemberProp::Name { name: method_name, .. },
|
|
2545
|
+
..
|
|
2546
|
+
} = callee.as_ref()
|
|
2547
|
+
{
|
|
2548
|
+
if method_name.as_ref() == "stringify"
|
|
2549
|
+
&& matches!(object.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "JSON")
|
|
2550
|
+
{
|
|
2551
|
+
if args.len() == 1 {
|
|
2552
|
+
if let CallArg::Expr(arg) = &args[0] {
|
|
2553
|
+
let (arg_code, arg_ty) = self.emit_typed_expr(arg)?;
|
|
2554
|
+
match &arg_ty {
|
|
2555
|
+
crate::types::RustType::Named { .. } => {
|
|
2556
|
+
return Ok(format!(
|
|
2557
|
+
"{{ let mut _buf = String::with_capacity(128); ({})._tish_write_json(&mut _buf); Value::String(_buf.into()) }}",
|
|
2558
|
+
arg_code
|
|
2559
|
+
));
|
|
2560
|
+
}
|
|
2561
|
+
crate::types::RustType::Vec(inner)
|
|
2562
|
+
if matches!(
|
|
2563
|
+
inner.as_ref(),
|
|
2564
|
+
crate::types::RustType::Named { .. }
|
|
2565
|
+
) =>
|
|
2566
|
+
{
|
|
2567
|
+
return Ok(format!(
|
|
2568
|
+
"{{ let mut _buf = String::with_capacity(256); _buf.push('['); for (i, item) in ({}).iter().enumerate() {{ if i > 0 {{ _buf.push(','); }} item._tish_write_json(&mut _buf); }} _buf.push(']'); Value::String(_buf.into()) }}",
|
|
2569
|
+
arg_code
|
|
2570
|
+
));
|
|
2571
|
+
}
|
|
2572
|
+
_ => {}
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
|
|
1822
2579
|
// Compile-time embed: Polars.read_csv("<literal path>") when file exists
|
|
1823
2580
|
if let Some(init) = self.native_module_init.get("tish:polars") {
|
|
1824
2581
|
let crate_name = match init {
|
|
@@ -1834,7 +2591,7 @@ impl Codegen {
|
|
|
1834
2591
|
{
|
|
1835
2592
|
if let Expr::Member {
|
|
1836
2593
|
object,
|
|
1837
|
-
prop: MemberProp::Name
|
|
2594
|
+
prop: MemberProp::Name { name: ref method_name, .. },
|
|
1838
2595
|
..
|
|
1839
2596
|
} = callee.as_ref()
|
|
1840
2597
|
{
|
|
@@ -1865,7 +2622,12 @@ impl Codegen {
|
|
|
1865
2622
|
}
|
|
1866
2623
|
|
|
1867
2624
|
// Check for built-in method calls on arrays/strings
|
|
1868
|
-
if let Expr::Member {
|
|
2625
|
+
if let Expr::Member {
|
|
2626
|
+
object,
|
|
2627
|
+
prop: MemberProp::Name { name: method_name, .. },
|
|
2628
|
+
..
|
|
2629
|
+
} = callee.as_ref()
|
|
2630
|
+
{
|
|
1869
2631
|
// ── native Vec<T> push fast path ──────────────────────────────
|
|
1870
2632
|
if method_name.as_ref() == "push" {
|
|
1871
2633
|
if let Expr::Ident { name, .. } = object.as_ref() {
|
|
@@ -2248,9 +3010,43 @@ impl Codegen {
|
|
|
2248
3010
|
optional,
|
|
2249
3011
|
..
|
|
2250
3012
|
} => {
|
|
3013
|
+
// Fast path: typed struct member access. If `object` is
|
|
3014
|
+
// a local with `RustType::Named { fields }` and `prop` is
|
|
3015
|
+
// a literal field name of that struct, lower to a direct
|
|
3016
|
+
// Rust field access (`obj.field`), then wrap in
|
|
3017
|
+
// `Value::*` so the caller gets a `Value` as expected.
|
|
3018
|
+
if !optional {
|
|
3019
|
+
if let (Expr::Ident { name: var_name, .. }, MemberProp::Name { name: prop_name, .. }) =
|
|
3020
|
+
(object.as_ref(), prop)
|
|
3021
|
+
{
|
|
3022
|
+
let var_type = self.type_context.get_type(var_name.as_ref());
|
|
3023
|
+
if let RustType::Named { fields, .. } = &var_type {
|
|
3024
|
+
if let Some((_, field_ty)) =
|
|
3025
|
+
fields.iter().find(|(k, _)| k.as_ref() == prop_name.as_ref())
|
|
3026
|
+
{
|
|
3027
|
+
let var_esc = Self::escape_ident(var_name.as_ref()).into_owned();
|
|
3028
|
+
let access = if self.refcell_wrapped_vars.contains(var_name.as_ref()) {
|
|
3029
|
+
format!(
|
|
3030
|
+
"(*{}.borrow()).{}.clone()",
|
|
3031
|
+
var_esc,
|
|
3032
|
+
crate::types::field_ident(prop_name.as_ref())
|
|
3033
|
+
)
|
|
3034
|
+
} else {
|
|
3035
|
+
format!(
|
|
3036
|
+
"{}.{}",
|
|
3037
|
+
var_esc,
|
|
3038
|
+
crate::types::field_ident(prop_name.as_ref())
|
|
3039
|
+
)
|
|
3040
|
+
};
|
|
3041
|
+
// Caller expects a `Value`; wrap.
|
|
3042
|
+
return Ok(field_ty.to_value_expr(&access));
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
2251
3047
|
let obj = self.emit_expr(object)?;
|
|
2252
3048
|
let key = match prop {
|
|
2253
|
-
MemberProp::Name
|
|
3049
|
+
MemberProp::Name { name, .. } => format!("{:?}", name.as_ref()),
|
|
2254
3050
|
MemberProp::Expr(e) => {
|
|
2255
3051
|
let k = self.emit_expr(e)?;
|
|
2256
3052
|
format!("{}.to_display_string()", k)
|
|
@@ -2324,7 +3120,7 @@ impl Codegen {
|
|
|
2324
3120
|
}
|
|
2325
3121
|
}
|
|
2326
3122
|
}
|
|
2327
|
-
format!("{{ let mut _arr: Vec<Value> = Vec::new(); {} Value::Array(
|
|
3123
|
+
format!("{{ let mut _arr: Vec<Value> = Vec::new(); {} Value::Array(VmRef::new(_arr)) }}", parts.join(" "))
|
|
2328
3124
|
} else {
|
|
2329
3125
|
let mut els = Vec::new();
|
|
2330
3126
|
for elem in elements {
|
|
@@ -2343,7 +3139,7 @@ impl Codegen {
|
|
|
2343
3139
|
}
|
|
2344
3140
|
}
|
|
2345
3141
|
format!(
|
|
2346
|
-
"Value::Array(
|
|
3142
|
+
"Value::Array(VmRef::new(vec![{}]))",
|
|
2347
3143
|
els.join(", ")
|
|
2348
3144
|
)
|
|
2349
3145
|
}
|
|
@@ -2368,7 +3164,7 @@ impl Codegen {
|
|
|
2368
3164
|
}
|
|
2369
3165
|
}
|
|
2370
3166
|
}
|
|
2371
|
-
format!("{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::Object(
|
|
3167
|
+
format!("{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::Object(VmRef::new(_obj)) }}", parts.join(" "))
|
|
2372
3168
|
} else {
|
|
2373
3169
|
let mut parts = Vec::new();
|
|
2374
3170
|
for prop in props {
|
|
@@ -2382,7 +3178,7 @@ impl Codegen {
|
|
|
2382
3178
|
}
|
|
2383
3179
|
}
|
|
2384
3180
|
format!(
|
|
2385
|
-
"Value::Object(
|
|
3181
|
+
"Value::Object(VmRef::new(ObjectMap::from([{}])))",
|
|
2386
3182
|
parts.join(", ")
|
|
2387
3183
|
)
|
|
2388
3184
|
}
|
|
@@ -2803,9 +3599,12 @@ impl Codegen {
|
|
|
2803
3599
|
format!("Value::String([{}].concat().into())", parts.join(", "))
|
|
2804
3600
|
}
|
|
2805
3601
|
Expr::JsxElement { .. } | Expr::JsxFragment { .. } => {
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
3602
|
+
let fun_decls = self.program_fun_decl_names.clone();
|
|
3603
|
+
tishlang_ui::jsx::emit_jsx_rust(
|
|
3604
|
+
expr,
|
|
3605
|
+
&mut |e| self.emit_expr(e).map_err(|ce| ce.message),
|
|
3606
|
+
&fun_decls,
|
|
3607
|
+
)
|
|
2809
3608
|
.map_err(|m| CompileError::new(m, None))?
|
|
2810
3609
|
}
|
|
2811
3610
|
Expr::New { callee, args, .. } => {
|
|
@@ -2852,7 +3651,7 @@ impl Codegen {
|
|
|
2852
3651
|
}
|
|
2853
3652
|
})
|
|
2854
3653
|
}
|
|
2855
|
-
|
|
3654
|
+
|
|
2856
3655
|
/// Collect all identifiers referenced in an arrow body
|
|
2857
3656
|
fn collect_referenced_idents(body: &ArrowBody) -> HashSet<String> {
|
|
2858
3657
|
let mut idents = HashSet::new();
|
|
@@ -2862,10 +3661,12 @@ impl Codegen {
|
|
|
2862
3661
|
}
|
|
2863
3662
|
idents
|
|
2864
3663
|
}
|
|
2865
|
-
|
|
3664
|
+
|
|
2866
3665
|
fn collect_expr_idents(expr: &Expr, idents: &mut HashSet<String>) {
|
|
2867
3666
|
match expr {
|
|
2868
|
-
Expr::Ident { name, .. } => {
|
|
3667
|
+
Expr::Ident { name, .. } => {
|
|
3668
|
+
idents.insert(name.to_string());
|
|
3669
|
+
}
|
|
2869
3670
|
Expr::Assign { name, value, .. } => {
|
|
2870
3671
|
idents.insert(name.to_string());
|
|
2871
3672
|
Self::collect_expr_idents(value, idents);
|
|
@@ -2879,7 +3680,9 @@ impl Codegen {
|
|
|
2879
3680
|
Self::collect_expr_idents(callee, idents);
|
|
2880
3681
|
for arg in args {
|
|
2881
3682
|
match arg {
|
|
2882
|
-
CallArg::Expr(e) | CallArg::Spread(e) =>
|
|
3683
|
+
CallArg::Expr(e) | CallArg::Spread(e) => {
|
|
3684
|
+
Self::collect_expr_idents(e, idents)
|
|
3685
|
+
}
|
|
2883
3686
|
}
|
|
2884
3687
|
}
|
|
2885
3688
|
}
|
|
@@ -2887,19 +3690,28 @@ impl Codegen {
|
|
|
2887
3690
|
Self::collect_expr_idents(callee, idents);
|
|
2888
3691
|
for arg in args {
|
|
2889
3692
|
match arg {
|
|
2890
|
-
CallArg::Expr(e) | CallArg::Spread(e) =>
|
|
3693
|
+
CallArg::Expr(e) | CallArg::Spread(e) => {
|
|
3694
|
+
Self::collect_expr_idents(e, idents)
|
|
3695
|
+
}
|
|
2891
3696
|
}
|
|
2892
3697
|
}
|
|
2893
3698
|
}
|
|
2894
3699
|
Expr::Member { object, prop, .. } => {
|
|
2895
3700
|
Self::collect_expr_idents(object, idents);
|
|
2896
|
-
if let MemberProp::Expr(e) = prop {
|
|
3701
|
+
if let MemberProp::Expr(e) = prop {
|
|
3702
|
+
Self::collect_expr_idents(e, idents);
|
|
3703
|
+
}
|
|
2897
3704
|
}
|
|
2898
3705
|
Expr::MemberAssign { object, value, .. } => {
|
|
2899
3706
|
Self::collect_expr_idents(object, idents);
|
|
2900
3707
|
Self::collect_expr_idents(value, idents);
|
|
2901
3708
|
}
|
|
2902
|
-
Expr::IndexAssign {
|
|
3709
|
+
Expr::IndexAssign {
|
|
3710
|
+
object,
|
|
3711
|
+
index,
|
|
3712
|
+
value,
|
|
3713
|
+
..
|
|
3714
|
+
} => {
|
|
2903
3715
|
Self::collect_expr_idents(object, idents);
|
|
2904
3716
|
Self::collect_expr_idents(index, idents);
|
|
2905
3717
|
Self::collect_expr_idents(value, idents);
|
|
@@ -2908,13 +3720,20 @@ impl Codegen {
|
|
|
2908
3720
|
Self::collect_expr_idents(object, idents);
|
|
2909
3721
|
Self::collect_expr_idents(index, idents);
|
|
2910
3722
|
}
|
|
2911
|
-
Expr::Conditional {
|
|
3723
|
+
Expr::Conditional {
|
|
3724
|
+
cond,
|
|
3725
|
+
then_branch,
|
|
3726
|
+
else_branch,
|
|
3727
|
+
..
|
|
3728
|
+
} => {
|
|
2912
3729
|
Self::collect_expr_idents(cond, idents);
|
|
2913
3730
|
Self::collect_expr_idents(then_branch, idents);
|
|
2914
3731
|
Self::collect_expr_idents(else_branch, idents);
|
|
2915
3732
|
}
|
|
2916
|
-
Expr::PostfixInc { name, .. }
|
|
2917
|
-
|
|
3733
|
+
Expr::PostfixInc { name, .. }
|
|
3734
|
+
| Expr::PostfixDec { name, .. }
|
|
3735
|
+
| Expr::PrefixInc { name, .. }
|
|
3736
|
+
| Expr::PrefixDec { name, .. } => {
|
|
2918
3737
|
idents.insert(name.to_string());
|
|
2919
3738
|
}
|
|
2920
3739
|
Expr::CompoundAssign { name, value, .. } => {
|
|
@@ -2928,23 +3747,25 @@ impl Codegen {
|
|
|
2928
3747
|
Expr::Array { elements, .. } => {
|
|
2929
3748
|
for el in elements {
|
|
2930
3749
|
match el {
|
|
2931
|
-
ArrayElement::Expr(e) | ArrayElement::Spread(e) =>
|
|
3750
|
+
ArrayElement::Expr(e) | ArrayElement::Spread(e) => {
|
|
3751
|
+
Self::collect_expr_idents(e, idents)
|
|
3752
|
+
}
|
|
2932
3753
|
}
|
|
2933
3754
|
}
|
|
2934
3755
|
}
|
|
2935
3756
|
Expr::Object { props, .. } => {
|
|
2936
3757
|
for prop in props {
|
|
2937
3758
|
match prop {
|
|
2938
|
-
ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) =>
|
|
3759
|
+
ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => {
|
|
3760
|
+
Self::collect_expr_idents(e, idents)
|
|
3761
|
+
}
|
|
2939
3762
|
}
|
|
2940
3763
|
}
|
|
2941
3764
|
}
|
|
2942
|
-
Expr::ArrowFunction { body, .. } => {
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
}
|
|
2947
|
-
}
|
|
3765
|
+
Expr::ArrowFunction { body, .. } => match body {
|
|
3766
|
+
ArrowBody::Expr(e) => Self::collect_expr_idents(e, idents),
|
|
3767
|
+
ArrowBody::Block(s) => Self::collect_stmt_idents(s, idents),
|
|
3768
|
+
},
|
|
2948
3769
|
Expr::NullishCoalesce { left, right, .. } => {
|
|
2949
3770
|
Self::collect_expr_idents(left, idents);
|
|
2950
3771
|
Self::collect_expr_idents(right, idents);
|
|
@@ -2952,12 +3773,20 @@ impl Codegen {
|
|
|
2952
3773
|
Expr::TypeOf { operand, .. } => Self::collect_expr_idents(operand, idents),
|
|
2953
3774
|
Expr::Await { operand, .. } => Self::collect_expr_idents(operand, idents),
|
|
2954
3775
|
Expr::TemplateLiteral { exprs, .. } => {
|
|
2955
|
-
for e in exprs {
|
|
3776
|
+
for e in exprs {
|
|
3777
|
+
Self::collect_expr_idents(e, idents);
|
|
3778
|
+
}
|
|
2956
3779
|
}
|
|
2957
|
-
Expr::JsxElement {
|
|
3780
|
+
Expr::JsxElement {
|
|
3781
|
+
props, children, ..
|
|
3782
|
+
} => {
|
|
2958
3783
|
for p in props {
|
|
2959
3784
|
match p {
|
|
2960
|
-
tishlang_ast::JsxProp::Attr {
|
|
3785
|
+
tishlang_ast::JsxProp::Attr {
|
|
3786
|
+
value: tishlang_ast::JsxAttrValue::Expr(e),
|
|
3787
|
+
..
|
|
3788
|
+
}
|
|
3789
|
+
| tishlang_ast::JsxProp::Spread(e) => Self::collect_expr_idents(e, idents),
|
|
2961
3790
|
_ => {}
|
|
2962
3791
|
}
|
|
2963
3792
|
}
|
|
@@ -2978,7 +3807,7 @@ impl Codegen {
|
|
|
2978
3807
|
Expr::Literal { .. } => {}
|
|
2979
3808
|
}
|
|
2980
3809
|
}
|
|
2981
|
-
|
|
3810
|
+
|
|
2982
3811
|
/// Collect variable names that are assigned to in a statement/body (target of =, +=, ++, etc).
|
|
2983
3812
|
fn collect_assigned_idents_in_stmt(stmt: &Statement, names: &mut HashSet<String>) {
|
|
2984
3813
|
match stmt {
|
|
@@ -2989,14 +3818,25 @@ impl Codegen {
|
|
|
2989
3818
|
Self::collect_assigned_idents_in_stmt(s, names);
|
|
2990
3819
|
}
|
|
2991
3820
|
}
|
|
2992
|
-
Statement::If {
|
|
3821
|
+
Statement::If {
|
|
3822
|
+
cond,
|
|
3823
|
+
then_branch,
|
|
3824
|
+
else_branch,
|
|
3825
|
+
..
|
|
3826
|
+
} => {
|
|
2993
3827
|
Self::collect_assigned_idents_in_expr(cond, names);
|
|
2994
3828
|
Self::collect_assigned_idents_in_stmt(then_branch, names);
|
|
2995
3829
|
if let Some(eb) = else_branch {
|
|
2996
3830
|
Self::collect_assigned_idents_in_stmt(eb, names);
|
|
2997
3831
|
}
|
|
2998
3832
|
}
|
|
2999
|
-
Statement::For {
|
|
3833
|
+
Statement::For {
|
|
3834
|
+
init,
|
|
3835
|
+
cond,
|
|
3836
|
+
update,
|
|
3837
|
+
body,
|
|
3838
|
+
..
|
|
3839
|
+
} => {
|
|
3000
3840
|
if let Some(i) = init {
|
|
3001
3841
|
Self::collect_assigned_idents_in_stmt(i, names);
|
|
3002
3842
|
}
|
|
@@ -3016,7 +3856,12 @@ impl Codegen {
|
|
|
3016
3856
|
Self::collect_assigned_idents_in_expr(cond, names);
|
|
3017
3857
|
Self::collect_assigned_idents_in_stmt(body, names);
|
|
3018
3858
|
}
|
|
3019
|
-
Statement::Switch {
|
|
3859
|
+
Statement::Switch {
|
|
3860
|
+
expr,
|
|
3861
|
+
cases,
|
|
3862
|
+
default_body,
|
|
3863
|
+
..
|
|
3864
|
+
} => {
|
|
3020
3865
|
Self::collect_assigned_idents_in_expr(expr, names);
|
|
3021
3866
|
for (case_expr, stmts) in cases {
|
|
3022
3867
|
if let Some(e) = case_expr {
|
|
@@ -3032,7 +3877,12 @@ impl Codegen {
|
|
|
3032
3877
|
}
|
|
3033
3878
|
}
|
|
3034
3879
|
}
|
|
3035
|
-
Statement::Try {
|
|
3880
|
+
Statement::Try {
|
|
3881
|
+
body,
|
|
3882
|
+
catch_body,
|
|
3883
|
+
finally_body,
|
|
3884
|
+
..
|
|
3885
|
+
} => {
|
|
3036
3886
|
Self::collect_assigned_idents_in_stmt(body, names);
|
|
3037
3887
|
if let Some(c) = catch_body {
|
|
3038
3888
|
Self::collect_assigned_idents_in_stmt(c, names);
|
|
@@ -3048,7 +3898,13 @@ impl Codegen {
|
|
|
3048
3898
|
}
|
|
3049
3899
|
}
|
|
3050
3900
|
Statement::Throw { value, .. } => Self::collect_assigned_idents_in_expr(value, names),
|
|
3051
|
-
Statement::Break { .. }
|
|
3901
|
+
Statement::Break { .. }
|
|
3902
|
+
| Statement::Continue { .. }
|
|
3903
|
+
| Statement::Import { .. }
|
|
3904
|
+
| Statement::Export { .. }
|
|
3905
|
+
| Statement::TypeAlias { .. }
|
|
3906
|
+
| Statement::DeclareVar { .. }
|
|
3907
|
+
| Statement::DeclareFun { .. } => {}
|
|
3052
3908
|
}
|
|
3053
3909
|
}
|
|
3054
3910
|
|
|
@@ -3066,15 +3922,22 @@ impl Codegen {
|
|
|
3066
3922
|
names.insert(name.to_string());
|
|
3067
3923
|
Self::collect_assigned_idents_in_expr(value, names);
|
|
3068
3924
|
}
|
|
3069
|
-
Expr::PostfixInc { name, .. }
|
|
3070
|
-
| Expr::
|
|
3925
|
+
Expr::PostfixInc { name, .. }
|
|
3926
|
+
| Expr::PostfixDec { name, .. }
|
|
3927
|
+
| Expr::PrefixInc { name, .. }
|
|
3928
|
+
| Expr::PrefixDec { name, .. } => {
|
|
3071
3929
|
names.insert(name.to_string());
|
|
3072
3930
|
}
|
|
3073
3931
|
Expr::MemberAssign { object, value, .. } => {
|
|
3074
3932
|
Self::collect_assigned_idents_in_expr(object, names);
|
|
3075
3933
|
Self::collect_assigned_idents_in_expr(value, names);
|
|
3076
3934
|
}
|
|
3077
|
-
Expr::IndexAssign {
|
|
3935
|
+
Expr::IndexAssign {
|
|
3936
|
+
object,
|
|
3937
|
+
index,
|
|
3938
|
+
value,
|
|
3939
|
+
..
|
|
3940
|
+
} => {
|
|
3078
3941
|
Self::collect_assigned_idents_in_expr(object, names);
|
|
3079
3942
|
Self::collect_assigned_idents_in_expr(index, names);
|
|
3080
3943
|
Self::collect_assigned_idents_in_expr(value, names);
|
|
@@ -3114,17 +3977,20 @@ impl Codegen {
|
|
|
3114
3977
|
Self::collect_assigned_idents_in_expr(object, names);
|
|
3115
3978
|
Self::collect_assigned_idents_in_expr(index, names);
|
|
3116
3979
|
}
|
|
3117
|
-
Expr::Conditional {
|
|
3980
|
+
Expr::Conditional {
|
|
3981
|
+
cond,
|
|
3982
|
+
then_branch,
|
|
3983
|
+
else_branch,
|
|
3984
|
+
..
|
|
3985
|
+
} => {
|
|
3118
3986
|
Self::collect_assigned_idents_in_expr(cond, names);
|
|
3119
3987
|
Self::collect_assigned_idents_in_expr(then_branch, names);
|
|
3120
3988
|
Self::collect_assigned_idents_in_expr(else_branch, names);
|
|
3121
3989
|
}
|
|
3122
|
-
Expr::ArrowFunction { body, .. } => {
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
}
|
|
3127
|
-
}
|
|
3990
|
+
Expr::ArrowFunction { body, .. } => match body {
|
|
3991
|
+
ArrowBody::Expr(e) => Self::collect_assigned_idents_in_expr(e, names),
|
|
3992
|
+
ArrowBody::Block(s) => Self::collect_assigned_idents_in_stmt(s, names),
|
|
3993
|
+
},
|
|
3128
3994
|
Expr::Array { elements, .. } => {
|
|
3129
3995
|
for el in elements {
|
|
3130
3996
|
match el {
|
|
@@ -3152,7 +4018,9 @@ impl Codegen {
|
|
|
3152
4018
|
Self::collect_assigned_idents_in_expr(e, names);
|
|
3153
4019
|
}
|
|
3154
4020
|
}
|
|
3155
|
-
Expr::JsxElement {
|
|
4021
|
+
Expr::JsxElement {
|
|
4022
|
+
props, children, ..
|
|
4023
|
+
} => {
|
|
3156
4024
|
for p in props {
|
|
3157
4025
|
match p {
|
|
3158
4026
|
tishlang_ast::JsxProp::Attr {
|
|
@@ -3196,10 +4064,7 @@ impl Codegen {
|
|
|
3196
4064
|
Statement::VarDeclDestructure { pattern, .. } => {
|
|
3197
4065
|
Self::collect_destruct_names(pattern, names);
|
|
3198
4066
|
}
|
|
3199
|
-
Statement::For {
|
|
3200
|
-
init: Some(i),
|
|
3201
|
-
..
|
|
3202
|
-
} => {
|
|
4067
|
+
Statement::For { init: Some(i), .. } => {
|
|
3203
4068
|
if let Statement::VarDecl { name, .. } = i.as_ref() {
|
|
3204
4069
|
names.insert(name.to_string());
|
|
3205
4070
|
}
|
|
@@ -3288,11 +4153,17 @@ impl Codegen {
|
|
|
3288
4153
|
}
|
|
3289
4154
|
match body {
|
|
3290
4155
|
ArrowBody::Expr(e) => Self::collect_mutated_captures_from_expr(e, block_vars, result),
|
|
3291
|
-
ArrowBody::Block(s) =>
|
|
4156
|
+
ArrowBody::Block(s) => {
|
|
4157
|
+
Self::collect_mutated_captures_from_statements(s, block_vars, result)
|
|
4158
|
+
}
|
|
3292
4159
|
}
|
|
3293
4160
|
}
|
|
3294
4161
|
|
|
3295
|
-
fn collect_mutated_captures_from_expr(
|
|
4162
|
+
fn collect_mutated_captures_from_expr(
|
|
4163
|
+
expr: &Expr,
|
|
4164
|
+
block_vars: &HashSet<String>,
|
|
4165
|
+
result: &mut HashSet<String>,
|
|
4166
|
+
) {
|
|
3296
4167
|
match expr {
|
|
3297
4168
|
Expr::ArrowFunction { params, body, .. } => {
|
|
3298
4169
|
Self::collect_mutated_captures_from_arrow(params, body, block_vars, result);
|
|
@@ -3323,13 +4194,17 @@ impl Codegen {
|
|
|
3323
4194
|
Self::collect_mutated_captures_from_expr(e, block_vars, result);
|
|
3324
4195
|
}
|
|
3325
4196
|
}
|
|
3326
|
-
Expr::Conditional {
|
|
4197
|
+
Expr::Conditional {
|
|
4198
|
+
cond,
|
|
4199
|
+
then_branch,
|
|
4200
|
+
else_branch,
|
|
4201
|
+
..
|
|
4202
|
+
} => {
|
|
3327
4203
|
Self::collect_mutated_captures_from_expr(cond, block_vars, result);
|
|
3328
4204
|
Self::collect_mutated_captures_from_expr(then_branch, block_vars, result);
|
|
3329
4205
|
Self::collect_mutated_captures_from_expr(else_branch, block_vars, result);
|
|
3330
4206
|
}
|
|
3331
|
-
Expr::Binary { left, right, .. }
|
|
3332
|
-
| Expr::NullishCoalesce { left, right, .. } => {
|
|
4207
|
+
Expr::Binary { left, right, .. } | Expr::NullishCoalesce { left, right, .. } => {
|
|
3333
4208
|
Self::collect_mutated_captures_from_expr(left, block_vars, result);
|
|
3334
4209
|
Self::collect_mutated_captures_from_expr(right, block_vars, result);
|
|
3335
4210
|
}
|
|
@@ -3372,14 +4247,25 @@ impl Codegen {
|
|
|
3372
4247
|
Self::collect_mutated_captures_from_statements(s, block_vars, result);
|
|
3373
4248
|
}
|
|
3374
4249
|
}
|
|
3375
|
-
Statement::If {
|
|
4250
|
+
Statement::If {
|
|
4251
|
+
cond,
|
|
4252
|
+
then_branch,
|
|
4253
|
+
else_branch,
|
|
4254
|
+
..
|
|
4255
|
+
} => {
|
|
3376
4256
|
Self::collect_mutated_captures_from_expr(cond, block_vars, result);
|
|
3377
4257
|
Self::collect_mutated_captures_from_statements(then_branch, block_vars, result);
|
|
3378
4258
|
if let Some(eb) = else_branch {
|
|
3379
4259
|
Self::collect_mutated_captures_from_statements(eb, block_vars, result);
|
|
3380
4260
|
}
|
|
3381
4261
|
}
|
|
3382
|
-
Statement::For {
|
|
4262
|
+
Statement::For {
|
|
4263
|
+
init,
|
|
4264
|
+
cond,
|
|
4265
|
+
update,
|
|
4266
|
+
body,
|
|
4267
|
+
..
|
|
4268
|
+
} => {
|
|
3383
4269
|
if let Some(i) = init {
|
|
3384
4270
|
Self::collect_mutated_captures_from_statements(i, block_vars, result);
|
|
3385
4271
|
}
|
|
@@ -3399,7 +4285,12 @@ impl Codegen {
|
|
|
3399
4285
|
Self::collect_mutated_captures_from_expr(cond, block_vars, result);
|
|
3400
4286
|
Self::collect_mutated_captures_from_statements(body, block_vars, result);
|
|
3401
4287
|
}
|
|
3402
|
-
Statement::Switch {
|
|
4288
|
+
Statement::Switch {
|
|
4289
|
+
expr,
|
|
4290
|
+
cases,
|
|
4291
|
+
default_body,
|
|
4292
|
+
..
|
|
4293
|
+
} => {
|
|
3403
4294
|
Self::collect_mutated_captures_from_expr(expr, block_vars, result);
|
|
3404
4295
|
for (ce, stmts) in cases {
|
|
3405
4296
|
if let Some(e) = ce {
|
|
@@ -3415,7 +4306,12 @@ impl Codegen {
|
|
|
3415
4306
|
}
|
|
3416
4307
|
}
|
|
3417
4308
|
}
|
|
3418
|
-
Statement::Try {
|
|
4309
|
+
Statement::Try {
|
|
4310
|
+
body,
|
|
4311
|
+
catch_body,
|
|
4312
|
+
finally_body,
|
|
4313
|
+
..
|
|
4314
|
+
} => {
|
|
3419
4315
|
Self::collect_mutated_captures_from_statements(body, block_vars, result);
|
|
3420
4316
|
if let Some(c) = catch_body {
|
|
3421
4317
|
Self::collect_mutated_captures_from_statements(c, block_vars, result);
|
|
@@ -3433,7 +4329,9 @@ impl Codegen {
|
|
|
3433
4329
|
Statement::Return { value: Some(e), .. } => {
|
|
3434
4330
|
Self::collect_mutated_captures_from_expr(e, block_vars, result);
|
|
3435
4331
|
}
|
|
3436
|
-
Statement::Throw { value, .. } =>
|
|
4332
|
+
Statement::Throw { value, .. } => {
|
|
4333
|
+
Self::collect_mutated_captures_from_expr(value, block_vars, result)
|
|
4334
|
+
}
|
|
3437
4335
|
_ => {}
|
|
3438
4336
|
}
|
|
3439
4337
|
}
|
|
@@ -3452,37 +4350,66 @@ impl Codegen {
|
|
|
3452
4350
|
/// Collect variable names declared in a statement (VarDecl, Destructure, For init).
|
|
3453
4351
|
fn collect_local_var_names(stmt: &Statement, names: &mut HashSet<String>) {
|
|
3454
4352
|
match stmt {
|
|
3455
|
-
Statement::VarDecl { name, .. } => {
|
|
4353
|
+
Statement::VarDecl { name, .. } => {
|
|
4354
|
+
names.insert(name.to_string());
|
|
4355
|
+
}
|
|
3456
4356
|
Statement::VarDeclDestructure { pattern, .. } => {
|
|
3457
4357
|
Self::collect_destruct_names(pattern, names);
|
|
3458
4358
|
}
|
|
3459
4359
|
Statement::Block { statements, .. } => {
|
|
3460
|
-
for s in statements {
|
|
4360
|
+
for s in statements {
|
|
4361
|
+
Self::collect_local_var_names(s, names);
|
|
4362
|
+
}
|
|
3461
4363
|
}
|
|
3462
|
-
Statement::If {
|
|
4364
|
+
Statement::If {
|
|
4365
|
+
then_branch,
|
|
4366
|
+
else_branch,
|
|
4367
|
+
..
|
|
4368
|
+
} => {
|
|
3463
4369
|
Self::collect_local_var_names(then_branch, names);
|
|
3464
|
-
if let Some(eb) = else_branch {
|
|
4370
|
+
if let Some(eb) = else_branch {
|
|
4371
|
+
Self::collect_local_var_names(eb, names);
|
|
4372
|
+
}
|
|
3465
4373
|
}
|
|
3466
4374
|
Statement::For { init, body, .. } => {
|
|
3467
|
-
if let Some(i) = init {
|
|
4375
|
+
if let Some(i) = init {
|
|
4376
|
+
Self::collect_local_var_names(i, names);
|
|
4377
|
+
}
|
|
3468
4378
|
Self::collect_local_var_names(body, names);
|
|
3469
4379
|
}
|
|
3470
4380
|
Statement::ForOf { body, .. } => Self::collect_local_var_names(body, names),
|
|
3471
4381
|
Statement::While { body, .. } | Statement::DoWhile { body, .. } => {
|
|
3472
4382
|
Self::collect_local_var_names(body, names);
|
|
3473
4383
|
}
|
|
3474
|
-
Statement::Switch {
|
|
4384
|
+
Statement::Switch {
|
|
4385
|
+
cases,
|
|
4386
|
+
default_body,
|
|
4387
|
+
..
|
|
4388
|
+
} => {
|
|
3475
4389
|
for (_, stmts) in cases {
|
|
3476
|
-
for s in stmts {
|
|
4390
|
+
for s in stmts {
|
|
4391
|
+
Self::collect_local_var_names(s, names);
|
|
4392
|
+
}
|
|
3477
4393
|
}
|
|
3478
4394
|
if let Some(stmts) = default_body {
|
|
3479
|
-
for s in stmts {
|
|
4395
|
+
for s in stmts {
|
|
4396
|
+
Self::collect_local_var_names(s, names);
|
|
4397
|
+
}
|
|
3480
4398
|
}
|
|
3481
4399
|
}
|
|
3482
|
-
Statement::Try {
|
|
4400
|
+
Statement::Try {
|
|
4401
|
+
body,
|
|
4402
|
+
catch_body,
|
|
4403
|
+
finally_body,
|
|
4404
|
+
..
|
|
4405
|
+
} => {
|
|
3483
4406
|
Self::collect_local_var_names(body, names);
|
|
3484
|
-
if let Some(c) = catch_body {
|
|
3485
|
-
|
|
4407
|
+
if let Some(c) = catch_body {
|
|
4408
|
+
Self::collect_local_var_names(c, names);
|
|
4409
|
+
}
|
|
4410
|
+
if let Some(f) = finally_body {
|
|
4411
|
+
Self::collect_local_var_names(f, names);
|
|
4412
|
+
}
|
|
3486
4413
|
}
|
|
3487
4414
|
Statement::FunDecl { body, .. } => Self::collect_local_var_names(body, names),
|
|
3488
4415
|
_ => {}
|
|
@@ -3493,16 +4420,24 @@ impl Codegen {
|
|
|
3493
4420
|
match pattern {
|
|
3494
4421
|
DestructPattern::Array(elements) => {
|
|
3495
4422
|
for el in elements {
|
|
3496
|
-
if let Some(DestructElement::Ident(n)) = el {
|
|
3497
|
-
|
|
4423
|
+
if let Some(DestructElement::Ident(n, _)) = el {
|
|
4424
|
+
names.insert(n.to_string());
|
|
4425
|
+
}
|
|
4426
|
+
if let Some(DestructElement::Pattern(p)) = el {
|
|
4427
|
+
Self::collect_destruct_names(p, names);
|
|
4428
|
+
}
|
|
3498
4429
|
}
|
|
3499
4430
|
}
|
|
3500
4431
|
DestructPattern::Object(props) => {
|
|
3501
4432
|
for prop in props {
|
|
3502
4433
|
match &prop.value {
|
|
3503
|
-
DestructElement::Ident(n) => {
|
|
4434
|
+
DestructElement::Ident(n, _) => {
|
|
4435
|
+
names.insert(n.to_string());
|
|
4436
|
+
}
|
|
3504
4437
|
DestructElement::Pattern(p) => Self::collect_destruct_names(p, names),
|
|
3505
|
-
DestructElement::Rest(n) => {
|
|
4438
|
+
DestructElement::Rest(n, _) => {
|
|
4439
|
+
names.insert(n.to_string());
|
|
4440
|
+
}
|
|
3506
4441
|
}
|
|
3507
4442
|
}
|
|
3508
4443
|
}
|
|
@@ -3513,25 +4448,48 @@ impl Codegen {
|
|
|
3513
4448
|
match stmt {
|
|
3514
4449
|
Statement::ExprStmt { expr, .. } => Self::collect_expr_idents(expr, idents),
|
|
3515
4450
|
Statement::VarDecl { init, .. } => {
|
|
3516
|
-
if let Some(e) = init {
|
|
4451
|
+
if let Some(e) = init {
|
|
4452
|
+
Self::collect_expr_idents(e, idents);
|
|
4453
|
+
}
|
|
3517
4454
|
}
|
|
3518
4455
|
Statement::VarDeclDestructure { init, .. } => Self::collect_expr_idents(init, idents),
|
|
3519
4456
|
Statement::Block { statements, .. } => {
|
|
3520
|
-
for s in statements {
|
|
4457
|
+
for s in statements {
|
|
4458
|
+
Self::collect_stmt_idents(s, idents);
|
|
4459
|
+
}
|
|
3521
4460
|
}
|
|
3522
|
-
Statement::If {
|
|
4461
|
+
Statement::If {
|
|
4462
|
+
cond,
|
|
4463
|
+
then_branch,
|
|
4464
|
+
else_branch,
|
|
4465
|
+
..
|
|
4466
|
+
} => {
|
|
3523
4467
|
Self::collect_expr_idents(cond, idents);
|
|
3524
4468
|
Self::collect_stmt_idents(then_branch, idents);
|
|
3525
|
-
if let Some(e) = else_branch {
|
|
4469
|
+
if let Some(e) = else_branch {
|
|
4470
|
+
Self::collect_stmt_idents(e, idents);
|
|
4471
|
+
}
|
|
3526
4472
|
}
|
|
3527
4473
|
Statement::While { cond, body, .. } | Statement::DoWhile { body, cond, .. } => {
|
|
3528
4474
|
Self::collect_expr_idents(cond, idents);
|
|
3529
4475
|
Self::collect_stmt_idents(body, idents);
|
|
3530
4476
|
}
|
|
3531
|
-
Statement::For {
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
4477
|
+
Statement::For {
|
|
4478
|
+
init,
|
|
4479
|
+
cond,
|
|
4480
|
+
update,
|
|
4481
|
+
body,
|
|
4482
|
+
..
|
|
4483
|
+
} => {
|
|
4484
|
+
if let Some(s) = init {
|
|
4485
|
+
Self::collect_stmt_idents(s, idents);
|
|
4486
|
+
}
|
|
4487
|
+
if let Some(e) = cond {
|
|
4488
|
+
Self::collect_expr_idents(e, idents);
|
|
4489
|
+
}
|
|
4490
|
+
if let Some(e) = update {
|
|
4491
|
+
Self::collect_expr_idents(e, idents);
|
|
4492
|
+
}
|
|
3535
4493
|
Self::collect_stmt_idents(body, idents);
|
|
3536
4494
|
}
|
|
3537
4495
|
Statement::ForOf { iterable, body, .. } => {
|
|
@@ -3539,26 +4497,54 @@ impl Codegen {
|
|
|
3539
4497
|
Self::collect_stmt_idents(body, idents);
|
|
3540
4498
|
}
|
|
3541
4499
|
Statement::Return { value, .. } => {
|
|
3542
|
-
if let Some(e) = value {
|
|
4500
|
+
if let Some(e) = value {
|
|
4501
|
+
Self::collect_expr_idents(e, idents);
|
|
4502
|
+
}
|
|
3543
4503
|
}
|
|
3544
4504
|
Statement::Throw { value, .. } => Self::collect_expr_idents(value, idents),
|
|
3545
|
-
Statement::Try {
|
|
4505
|
+
Statement::Try {
|
|
4506
|
+
body,
|
|
4507
|
+
catch_body,
|
|
4508
|
+
finally_body,
|
|
4509
|
+
..
|
|
4510
|
+
} => {
|
|
3546
4511
|
Self::collect_stmt_idents(body, idents);
|
|
3547
|
-
if let Some(c) = catch_body {
|
|
3548
|
-
|
|
4512
|
+
if let Some(c) = catch_body {
|
|
4513
|
+
Self::collect_stmt_idents(c, idents);
|
|
4514
|
+
}
|
|
4515
|
+
if let Some(f) = finally_body {
|
|
4516
|
+
Self::collect_stmt_idents(f, idents);
|
|
4517
|
+
}
|
|
3549
4518
|
}
|
|
3550
|
-
Statement::Switch {
|
|
4519
|
+
Statement::Switch {
|
|
4520
|
+
expr,
|
|
4521
|
+
cases,
|
|
4522
|
+
default_body,
|
|
4523
|
+
..
|
|
4524
|
+
} => {
|
|
3551
4525
|
Self::collect_expr_idents(expr, idents);
|
|
3552
4526
|
for (case_expr, stmts) in cases {
|
|
3553
|
-
if let Some(e) = case_expr {
|
|
3554
|
-
|
|
4527
|
+
if let Some(e) = case_expr {
|
|
4528
|
+
Self::collect_expr_idents(e, idents);
|
|
4529
|
+
}
|
|
4530
|
+
for s in stmts {
|
|
4531
|
+
Self::collect_stmt_idents(s, idents);
|
|
4532
|
+
}
|
|
3555
4533
|
}
|
|
3556
4534
|
if let Some(stmts) = default_body {
|
|
3557
|
-
for s in stmts {
|
|
4535
|
+
for s in stmts {
|
|
4536
|
+
Self::collect_stmt_idents(s, idents);
|
|
4537
|
+
}
|
|
3558
4538
|
}
|
|
3559
4539
|
}
|
|
3560
4540
|
Statement::FunDecl { body, .. } => Self::collect_stmt_idents(body, idents),
|
|
3561
|
-
Statement::Break { .. }
|
|
4541
|
+
Statement::Break { .. }
|
|
4542
|
+
| Statement::Continue { .. }
|
|
4543
|
+
| Statement::Import { .. }
|
|
4544
|
+
| Statement::Export { .. }
|
|
4545
|
+
| Statement::TypeAlias { .. }
|
|
4546
|
+
| Statement::DeclareVar { .. }
|
|
4547
|
+
| Statement::DeclareFun { .. } => {}
|
|
3562
4548
|
}
|
|
3563
4549
|
}
|
|
3564
4550
|
|
|
@@ -3571,7 +4557,7 @@ impl Codegen {
|
|
|
3571
4557
|
// Build the arrow function as a Value::Function closure
|
|
3572
4558
|
let mut code = String::new();
|
|
3573
4559
|
code.push_str("{\n");
|
|
3574
|
-
|
|
4560
|
+
|
|
3575
4561
|
// Find which identifiers are actually referenced in the body
|
|
3576
4562
|
let referenced = Self::collect_referenced_idents(body);
|
|
3577
4563
|
// Exclude the arrow's own parameters - they're not outer captures
|
|
@@ -3589,17 +4575,42 @@ impl Codegen {
|
|
|
3589
4575
|
}
|
|
3590
4576
|
|
|
3591
4577
|
// Collect outer parameters that need to be captured
|
|
3592
|
-
let outer_params: Vec<String> = self
|
|
4578
|
+
let outer_params: Vec<String> = self
|
|
4579
|
+
.outer_params_stack
|
|
3593
4580
|
.iter()
|
|
3594
4581
|
.flat_map(|p| p.iter().cloned())
|
|
3595
4582
|
.filter(|name| referenced.contains(name) && !param_names.contains(name))
|
|
3596
4583
|
.collect();
|
|
3597
|
-
|
|
4584
|
+
|
|
3598
4585
|
// Collect outer variables (from outer scopes) that need to be captured
|
|
3599
|
-
let outer_vars: Vec<String> = self
|
|
4586
|
+
let outer_vars: Vec<String> = self
|
|
4587
|
+
.outer_vars_stack
|
|
3600
4588
|
.iter()
|
|
3601
4589
|
.flat_map(|v| v.iter().cloned())
|
|
3602
|
-
.filter(|name|
|
|
4590
|
+
.filter(|name| {
|
|
4591
|
+
referenced.contains(name)
|
|
4592
|
+
&& !param_names.contains(name)
|
|
4593
|
+
&& !local_var_names.contains(name)
|
|
4594
|
+
})
|
|
4595
|
+
.filter(|name| {
|
|
4596
|
+
![
|
|
4597
|
+
"Boolean",
|
|
4598
|
+
"console",
|
|
4599
|
+
"Math",
|
|
4600
|
+
"JSON",
|
|
4601
|
+
"Date",
|
|
4602
|
+
"Object",
|
|
4603
|
+
"process",
|
|
4604
|
+
"setTimeout",
|
|
4605
|
+
"clearTimeout",
|
|
4606
|
+
"setInterval",
|
|
4607
|
+
"clearInterval",
|
|
4608
|
+
"Promise",
|
|
4609
|
+
"RegExp",
|
|
4610
|
+
"Polars",
|
|
4611
|
+
]
|
|
4612
|
+
.contains(&name.as_str())
|
|
4613
|
+
})
|
|
3603
4614
|
.collect();
|
|
3604
4615
|
|
|
3605
4616
|
// Outer vars that are assigned in the body need RefCell; read-only get Value binding
|
|
@@ -3608,8 +4619,18 @@ impl Codegen {
|
|
|
3608
4619
|
ArrowBody::Expr(e) => Self::collect_assigned_idents_in_expr(e, &mut assigned_in_body),
|
|
3609
4620
|
ArrowBody::Block(s) => Self::collect_assigned_idents_in_stmt(s, &mut assigned_in_body),
|
|
3610
4621
|
}
|
|
3611
|
-
|
|
3612
|
-
|
|
4622
|
+
// Live cell capture: assigned here, or already `Rc<RefCell<Value>>` in a parent scope
|
|
4623
|
+
// (cleanups may only read `timer2` but must see updates from nested callbacks).
|
|
4624
|
+
let cell_capture_outer_vars: Vec<String> = outer_vars
|
|
4625
|
+
.iter()
|
|
4626
|
+
.filter(|v| assigned_in_body.contains(*v) || self.rc_cell_storage_contains(*v))
|
|
4627
|
+
.cloned()
|
|
4628
|
+
.collect();
|
|
4629
|
+
let read_only_outer_vars: Vec<String> = outer_vars
|
|
4630
|
+
.iter()
|
|
4631
|
+
.filter(|v| !assigned_in_body.contains(*v) && !self.rc_cell_storage_contains(*v))
|
|
4632
|
+
.cloned()
|
|
4633
|
+
.collect();
|
|
3613
4634
|
|
|
3614
4635
|
// Wrap outer captures in Rc<RefCell<>> and use _ref suffix.
|
|
3615
4636
|
// Clone existing Rc only when VarDecl actually emitted `Rc<RefCell<...>>` (see rc_cell_storage_*).
|
|
@@ -3617,62 +4638,110 @@ impl Codegen {
|
|
|
3617
4638
|
let param_escaped = Self::escape_ident(outer_param);
|
|
3618
4639
|
let ref_name = format!("{}_ref", param_escaped);
|
|
3619
4640
|
if self.rc_cell_storage_contains(outer_param) {
|
|
3620
|
-
code.push_str(&format!(
|
|
4641
|
+
code.push_str(&format!(
|
|
4642
|
+
" let {} = {}.clone();\n",
|
|
4643
|
+
ref_name, param_escaped
|
|
4644
|
+
));
|
|
3621
4645
|
} else {
|
|
3622
|
-
code.push_str(&format!(
|
|
4646
|
+
code.push_str(&format!(
|
|
4647
|
+
" let {} = VmRef::new({}.clone());\n",
|
|
4648
|
+
ref_name, param_escaped
|
|
4649
|
+
));
|
|
3623
4650
|
}
|
|
3624
4651
|
}
|
|
3625
4652
|
for outer_var in &outer_vars {
|
|
3626
4653
|
let var_escaped = Self::escape_ident(outer_var);
|
|
3627
4654
|
let ref_name = format!("{}_ref", var_escaped);
|
|
3628
4655
|
if self.rc_cell_storage_contains(outer_var) {
|
|
3629
|
-
code.push_str(&format!(
|
|
4656
|
+
code.push_str(&format!(
|
|
4657
|
+
" let {} = {}.clone();\n",
|
|
4658
|
+
ref_name, var_escaped
|
|
4659
|
+
));
|
|
3630
4660
|
} else {
|
|
3631
|
-
code.push_str(&format!(
|
|
4661
|
+
code.push_str(&format!(
|
|
4662
|
+
" let {} = VmRef::new({}.clone());\n",
|
|
4663
|
+
ref_name, var_escaped
|
|
4664
|
+
));
|
|
3632
4665
|
}
|
|
3633
4666
|
}
|
|
3634
4667
|
// Only clone builtins that are actually referenced (clone so outer scope can still use, e.g. process for PORT)
|
|
3635
|
-
for builtin in &[
|
|
4668
|
+
for builtin in &[
|
|
4669
|
+
"console",
|
|
4670
|
+
"Math",
|
|
4671
|
+
"JSON",
|
|
4672
|
+
"Date",
|
|
4673
|
+
"Object",
|
|
4674
|
+
"Uint8Array",
|
|
4675
|
+
"AudioContext",
|
|
4676
|
+
"process",
|
|
4677
|
+
"setTimeout",
|
|
4678
|
+
"clearTimeout",
|
|
4679
|
+
"setInterval",
|
|
4680
|
+
"clearInterval",
|
|
4681
|
+
"Promise",
|
|
4682
|
+
"RegExp",
|
|
4683
|
+
"Polars",
|
|
4684
|
+
] {
|
|
3636
4685
|
if referenced.contains(*builtin) {
|
|
3637
4686
|
code.push_str(&format!(" let {} = {}.clone();\n", builtin, builtin));
|
|
3638
4687
|
}
|
|
3639
4688
|
}
|
|
3640
4689
|
|
|
3641
4690
|
// Clone only function cells that are actually referenced in this arrow
|
|
3642
|
-
let referenced_funcs: Vec<String> = self
|
|
4691
|
+
let referenced_funcs: Vec<String> = self
|
|
4692
|
+
.function_scope_stack
|
|
3643
4693
|
.last()
|
|
3644
|
-
.map(|scope|
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
4694
|
+
.map(|scope| {
|
|
4695
|
+
scope
|
|
4696
|
+
.iter()
|
|
4697
|
+
.filter(|f| referenced.contains(f.as_str()) && !param_names.contains(*f))
|
|
4698
|
+
.cloned()
|
|
4699
|
+
.collect()
|
|
4700
|
+
})
|
|
3648
4701
|
.unwrap_or_default();
|
|
3649
4702
|
for func_name in &referenced_funcs {
|
|
3650
4703
|
let escaped = Self::escape_ident(func_name);
|
|
3651
|
-
code.push_str(&format!(
|
|
4704
|
+
code.push_str(&format!(
|
|
4705
|
+
" let {}_ref = {}_cell.clone();\n",
|
|
4706
|
+
escaped, escaped
|
|
4707
|
+
));
|
|
3652
4708
|
}
|
|
3653
4709
|
|
|
3654
|
-
code.push_str(" Value::
|
|
4710
|
+
code.push_str(" Value::native(move |args: &[Value]| {\n");
|
|
4711
|
+
self.value_fn_depth += 1;
|
|
3655
4712
|
|
|
3656
4713
|
// Make captured outer params available as plain Values (from _ref RefCells)
|
|
3657
4714
|
for outer_param in &outer_params {
|
|
3658
4715
|
let param_escaped = Self::escape_ident(outer_param);
|
|
3659
|
-
code.push_str(&format!(
|
|
4716
|
+
code.push_str(&format!(
|
|
4717
|
+
" let {} = (*{}_ref.borrow()).clone();\n",
|
|
4718
|
+
param_escaped, param_escaped
|
|
4719
|
+
));
|
|
3660
4720
|
}
|
|
3661
|
-
//
|
|
3662
|
-
for outer_var in &
|
|
4721
|
+
// Outer vars that share a RefCell with the parent: capture the cell (read + write)
|
|
4722
|
+
for outer_var in &cell_capture_outer_vars {
|
|
3663
4723
|
let var_escaped = Self::escape_ident(outer_var);
|
|
3664
|
-
code.push_str(&format!(
|
|
4724
|
+
code.push_str(&format!(
|
|
4725
|
+
" let {} = {}_ref.clone();\n",
|
|
4726
|
+
var_escaped, var_escaped
|
|
4727
|
+
));
|
|
3665
4728
|
}
|
|
3666
|
-
// Read-only outer vars: Value
|
|
4729
|
+
// Read-only outer vars: snapshot Value at closure creation
|
|
3667
4730
|
for outer_var in &read_only_outer_vars {
|
|
3668
4731
|
let var_escaped = Self::escape_ident(outer_var);
|
|
3669
|
-
code.push_str(&format!(
|
|
4732
|
+
code.push_str(&format!(
|
|
4733
|
+
" let {} = (*{}_ref.borrow()).clone();\n",
|
|
4734
|
+
var_escaped, var_escaped
|
|
4735
|
+
));
|
|
3670
4736
|
}
|
|
3671
4737
|
|
|
3672
4738
|
// Make captured functions available
|
|
3673
4739
|
for func_name in &referenced_funcs {
|
|
3674
4740
|
let escaped = Self::escape_ident(func_name);
|
|
3675
|
-
code.push_str(&format!(
|
|
4741
|
+
code.push_str(&format!(
|
|
4742
|
+
" let {} = (*{}_ref.borrow()).clone();\n",
|
|
4743
|
+
escaped, escaped
|
|
4744
|
+
));
|
|
3676
4745
|
}
|
|
3677
4746
|
|
|
3678
4747
|
// Extract parameters from args
|
|
@@ -3712,44 +4781,55 @@ impl Codegen {
|
|
|
3712
4781
|
// Push empty scope for variables declared inside this arrow function
|
|
3713
4782
|
self.outer_vars_stack.push(Vec::new());
|
|
3714
4783
|
|
|
3715
|
-
//
|
|
4784
|
+
// Cell-backed outer vars need refcell_wrapped_vars for Assign and for reads in emit_expr
|
|
3716
4785
|
let saved_refcell_vars = self.refcell_wrapped_vars.clone();
|
|
3717
|
-
for v in &
|
|
4786
|
+
for v in &cell_capture_outer_vars {
|
|
3718
4787
|
self.refcell_wrapped_vars.insert(v.clone());
|
|
3719
4788
|
}
|
|
3720
4789
|
|
|
3721
|
-
|
|
3722
|
-
|
|
4790
|
+
self.type_context.push_fun_param_scope(params, None);
|
|
4791
|
+
|
|
4792
|
+
let arrow_body_res: Result<(), CompileError> = match body {
|
|
3723
4793
|
tishlang_ast::ArrowBody::Expr(expr) => {
|
|
3724
4794
|
let expr_code = self.emit_expr(expr)?;
|
|
3725
4795
|
code.push_str(&format!(" {}\n", expr_code));
|
|
4796
|
+
Ok(())
|
|
3726
4797
|
}
|
|
3727
4798
|
tishlang_ast::ArrowBody::Block(block_stmt) => {
|
|
3728
4799
|
// For block bodies, emit the block statement
|
|
3729
4800
|
self.function_scope_stack.push(Vec::new());
|
|
3730
|
-
|
|
4801
|
+
|
|
3731
4802
|
// Save current output, emit to temp, then restore
|
|
3732
4803
|
let saved_output = std::mem::take(&mut self.output);
|
|
3733
4804
|
let saved_indent = self.indent;
|
|
3734
4805
|
self.indent = 2; // Base indent inside the closure
|
|
3735
|
-
|
|
4806
|
+
|
|
3736
4807
|
self.emit_statement(block_stmt)?;
|
|
3737
|
-
|
|
4808
|
+
|
|
3738
4809
|
let body_code = std::mem::replace(&mut self.output, saved_output);
|
|
3739
4810
|
self.indent = saved_indent;
|
|
3740
4811
|
self.function_scope_stack.pop();
|
|
3741
|
-
|
|
4812
|
+
|
|
3742
4813
|
code.push_str(&body_code);
|
|
3743
4814
|
code.push_str(" Value::Null\n");
|
|
4815
|
+
Ok(())
|
|
3744
4816
|
}
|
|
4817
|
+
};
|
|
4818
|
+
|
|
4819
|
+
self.type_context.pop_scope();
|
|
4820
|
+
if let Err(e) = arrow_body_res {
|
|
4821
|
+
self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
|
|
4822
|
+
return Err(e);
|
|
3745
4823
|
}
|
|
3746
4824
|
|
|
4825
|
+
self.value_fn_depth = self.value_fn_depth.saturating_sub(1);
|
|
4826
|
+
|
|
3747
4827
|
// Restore state
|
|
3748
4828
|
self.refcell_wrapped_vars = saved_refcell_vars;
|
|
3749
4829
|
self.outer_params_stack.pop();
|
|
3750
4830
|
self.outer_vars_stack.pop();
|
|
3751
4831
|
|
|
3752
|
-
code.push_str(" })
|
|
4832
|
+
code.push_str(" })\n");
|
|
3753
4833
|
code.push('}');
|
|
3754
4834
|
|
|
3755
4835
|
Ok(code)
|
|
@@ -3758,7 +4838,11 @@ impl Codegen {
|
|
|
3758
4838
|
/// Emit an expression as a native Rust type (not wrapped in Value).
|
|
3759
4839
|
/// Falls back to emit_expr + conversion if the expression cannot be directly
|
|
3760
4840
|
/// emitted as the target type.
|
|
3761
|
-
fn emit_native_expr(
|
|
4841
|
+
fn emit_native_expr(
|
|
4842
|
+
&mut self,
|
|
4843
|
+
expr: &Expr,
|
|
4844
|
+
target_type: &RustType,
|
|
4845
|
+
) -> Result<String, CompileError> {
|
|
3762
4846
|
// Try to emit literals directly as native types
|
|
3763
4847
|
if let Expr::Literal { value, .. } = expr {
|
|
3764
4848
|
match (target_type, value) {
|
|
@@ -3777,7 +4861,7 @@ impl Codegen {
|
|
|
3777
4861
|
_ => {}
|
|
3778
4862
|
}
|
|
3779
4863
|
}
|
|
3780
|
-
|
|
4864
|
+
|
|
3781
4865
|
// Try to emit array literals directly as Vec<T>
|
|
3782
4866
|
if let (RustType::Vec(inner_type), Expr::Array { elements, .. }) = (target_type, expr) {
|
|
3783
4867
|
let mut items = Vec::new();
|
|
@@ -3796,7 +4880,58 @@ impl Codegen {
|
|
|
3796
4880
|
}
|
|
3797
4881
|
return Ok(format!("vec![{}]", items.join(", ")));
|
|
3798
4882
|
}
|
|
3799
|
-
|
|
4883
|
+
|
|
4884
|
+
// Try to emit object literals directly as a Rust struct literal
|
|
4885
|
+
// when the target is a `RustType::Named` (a user `type Foo = {...}`
|
|
4886
|
+
// alias). Each property in source order is matched to a struct
|
|
4887
|
+
// field; missing fields fall back to `default_value()` so the
|
|
4888
|
+
// emit succeeds even on partial literals (rare, but harmless).
|
|
4889
|
+
if let (RustType::Named { name, fields }, Expr::Object { props, .. }) =
|
|
4890
|
+
(target_type, expr)
|
|
4891
|
+
{
|
|
4892
|
+
use std::collections::HashMap;
|
|
4893
|
+
let field_types: HashMap<&str, &RustType> = fields
|
|
4894
|
+
.iter()
|
|
4895
|
+
.map(|(k, t)| (k.as_ref(), t))
|
|
4896
|
+
.collect();
|
|
4897
|
+
let mut field_inits: HashMap<String, String> = HashMap::new();
|
|
4898
|
+
let mut bail = false;
|
|
4899
|
+
for prop in props {
|
|
4900
|
+
match prop {
|
|
4901
|
+
ObjectProp::KeyValue(key, value) => {
|
|
4902
|
+
if let Some(field_ty) = field_types.get(key.as_ref()) {
|
|
4903
|
+
let v = self.emit_native_expr(value, field_ty)?;
|
|
4904
|
+
field_inits.insert(crate::types::field_ident(key.as_ref()), v);
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
// Spread can't be statically matched to struct fields:
|
|
4908
|
+
// fall back to the dynamic Value path.
|
|
4909
|
+
ObjectProp::Spread(_) => {
|
|
4910
|
+
bail = true;
|
|
4911
|
+
break;
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
if !bail {
|
|
4916
|
+
let assigns = fields
|
|
4917
|
+
.iter()
|
|
4918
|
+
.map(|(k, t)| {
|
|
4919
|
+
let fid = crate::types::field_ident(k);
|
|
4920
|
+
match field_inits.remove(&fid) {
|
|
4921
|
+
Some(v) => format!("{}: {}", fid, v),
|
|
4922
|
+
None => format!("{}: {}", fid, t.default_value()),
|
|
4923
|
+
}
|
|
4924
|
+
})
|
|
4925
|
+
.collect::<Vec<_>>()
|
|
4926
|
+
.join(", ");
|
|
4927
|
+
return Ok(format!(
|
|
4928
|
+
"{} {{ {} }}",
|
|
4929
|
+
crate::types::named_struct_ident(name),
|
|
4930
|
+
assigns
|
|
4931
|
+
));
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
|
|
3800
4935
|
// Check if the identifier is already of the target type
|
|
3801
4936
|
if let Expr::Ident { name, .. } = expr {
|
|
3802
4937
|
let var_type = self.type_context.get_type(name.as_ref());
|
|
@@ -3808,7 +4943,7 @@ impl Codegen {
|
|
|
3808
4943
|
return Ok(esc);
|
|
3809
4944
|
}
|
|
3810
4945
|
}
|
|
3811
|
-
|
|
4946
|
+
|
|
3812
4947
|
// Fall back to emit_expr + conversion
|
|
3813
4948
|
let value_expr = self.emit_expr(expr)?;
|
|
3814
4949
|
Ok(target_type.from_value_expr(&value_expr))
|
|
@@ -3829,7 +4964,9 @@ impl Codegen {
|
|
|
3829
4964
|
// ── literals ─────────────────────────────────────────────────────────
|
|
3830
4965
|
Expr::Literal { value, .. } => match value {
|
|
3831
4966
|
Literal::Number(n) => Ok((format!("{}_f64", n), RustType::F64)),
|
|
3832
|
-
Literal::String(s) =>
|
|
4967
|
+
Literal::String(s) => {
|
|
4968
|
+
Ok((format!("{:?}.to_string()", s.as_ref()), RustType::String))
|
|
4969
|
+
}
|
|
3833
4970
|
Literal::Bool(b) => Ok((format!("{}", b), RustType::Bool)),
|
|
3834
4971
|
Literal::Null => Ok(("Value::Null".to_string(), RustType::Value)),
|
|
3835
4972
|
},
|
|
@@ -3855,7 +4992,13 @@ impl Codegen {
|
|
|
3855
4992
|
}
|
|
3856
4993
|
|
|
3857
4994
|
// ── binary expressions ───────────────────────────────────────────────
|
|
3858
|
-
Expr::Binary {
|
|
4995
|
+
Expr::Binary {
|
|
4996
|
+
left,
|
|
4997
|
+
op,
|
|
4998
|
+
right,
|
|
4999
|
+
span,
|
|
5000
|
+
..
|
|
5001
|
+
} => {
|
|
3859
5002
|
let (l, lt) = self.emit_typed_expr(left)?;
|
|
3860
5003
|
let (r, rt) = self.emit_typed_expr(right)?;
|
|
3861
5004
|
|
|
@@ -3882,14 +5025,27 @@ impl Codegen {
|
|
|
3882
5025
|
}
|
|
3883
5026
|
|
|
3884
5027
|
// Fall back: convert both sides to Value and use the runtime.
|
|
3885
|
-
let lv = if lt.is_native() {
|
|
3886
|
-
|
|
5028
|
+
let lv = if lt.is_native() {
|
|
5029
|
+
lt.to_value_expr(&l)
|
|
5030
|
+
} else {
|
|
5031
|
+
l
|
|
5032
|
+
};
|
|
5033
|
+
let rv = if rt.is_native() {
|
|
5034
|
+
rt.to_value_expr(&r)
|
|
5035
|
+
} else {
|
|
5036
|
+
r
|
|
5037
|
+
};
|
|
3887
5038
|
let result = self.emit_binop(&lv, *op, &rv, *span)?;
|
|
3888
5039
|
Ok((result, RustType::Value))
|
|
3889
5040
|
}
|
|
3890
5041
|
|
|
3891
5042
|
// ── array indexing ───────────────────────────────────────────────────
|
|
3892
|
-
Expr::Index {
|
|
5043
|
+
Expr::Index {
|
|
5044
|
+
object,
|
|
5045
|
+
index,
|
|
5046
|
+
optional,
|
|
5047
|
+
..
|
|
5048
|
+
} => {
|
|
3893
5049
|
// Native fast path: `vec[i]` where vec is Vec<T> and i is numeric.
|
|
3894
5050
|
if !optional {
|
|
3895
5051
|
if let Expr::Ident { name, .. } = object.as_ref() {
|
|
@@ -3958,19 +5114,28 @@ impl Codegen {
|
|
|
3958
5114
|
}
|
|
3959
5115
|
}
|
|
3960
5116
|
|
|
3961
|
-
fn emit_binop(
|
|
3962
|
-
&self,
|
|
3963
|
-
l: &str,
|
|
3964
|
-
op: BinOp,
|
|
3965
|
-
r: &str,
|
|
3966
|
-
span: Span,
|
|
3967
|
-
) -> Result<String, CompileError> {
|
|
5117
|
+
fn emit_binop(&self, l: &str, op: BinOp, r: &str, span: Span) -> Result<String, CompileError> {
|
|
3968
5118
|
Ok(match op {
|
|
3969
|
-
BinOp::Add => format!(
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
BinOp::
|
|
5119
|
+
BinOp::Add => format!(
|
|
5120
|
+
"tishlang_runtime::ops::add(&{}, &{}).unwrap_or(Value::Null)",
|
|
5121
|
+
l, r
|
|
5122
|
+
),
|
|
5123
|
+
BinOp::Sub => format!(
|
|
5124
|
+
"tishlang_runtime::ops::sub(&{}, &{}).unwrap_or(Value::Null)",
|
|
5125
|
+
l, r
|
|
5126
|
+
),
|
|
5127
|
+
BinOp::Mul => format!(
|
|
5128
|
+
"tishlang_runtime::ops::mul(&{}, &{}).unwrap_or(Value::Null)",
|
|
5129
|
+
l, r
|
|
5130
|
+
),
|
|
5131
|
+
BinOp::Div => format!(
|
|
5132
|
+
"tishlang_runtime::ops::div(&{}, &{}).unwrap_or(Value::Null)",
|
|
5133
|
+
l, r
|
|
5134
|
+
),
|
|
5135
|
+
BinOp::Mod => format!(
|
|
5136
|
+
"tishlang_runtime::ops::modulo(&{}, &{}).unwrap_or(Value::Null)",
|
|
5137
|
+
l, r
|
|
5138
|
+
),
|
|
3974
5139
|
BinOp::Pow => format!(
|
|
3975
5140
|
"Value::Number({{ let Value::Number(a) = &({}) else {{ panic!() }}; \
|
|
3976
5141
|
let Value::Number(b) = &({}) else {{ panic!() }}; a.powf(*b) }})",
|
|
@@ -3991,7 +5156,10 @@ impl Codegen {
|
|
|
3991
5156
|
BinOp::Shr => Self::emit_bitwise_binop(l, r, ">>"),
|
|
3992
5157
|
BinOp::In => format!("tish_in_operator(&{}, &{})", l, r),
|
|
3993
5158
|
BinOp::Eq | BinOp::Ne => {
|
|
3994
|
-
return Err(CompileError::new(
|
|
5159
|
+
return Err(CompileError::new(
|
|
5160
|
+
"Loose equality not supported",
|
|
5161
|
+
Some(span),
|
|
5162
|
+
))
|
|
3995
5163
|
}
|
|
3996
5164
|
})
|
|
3997
5165
|
}
|