@tishlang/tish 1.0.29 → 1.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +1 -0
- package/crates/js_to_tish/src/transform/expr.rs +15 -6
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/main.rs +1 -1
- package/crates/tish/tests/integration_test.rs +4 -3
- package/crates/tish_ast/src/ast.rs +65 -2
- package/crates/tish_build_utils/src/lib.rs +10 -2
- package/crates/tish_builtins/src/construct.rs +177 -0
- package/crates/tish_builtins/src/globals.rs +3 -5
- package/crates/tish_builtins/src/helpers.rs +2 -3
- package/crates/tish_builtins/src/lib.rs +1 -0
- package/crates/tish_builtins/src/object.rs +3 -4
- package/crates/tish_bytecode/src/compiler.rs +85 -11
- package/crates/tish_bytecode/src/opcode.rs +7 -3
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +604 -106
- package/crates/tish_compile/src/infer.rs +236 -0
- package/crates/tish_compile/src/lib.rs +52 -5
- package/crates/tish_compile/src/types.rs +42 -5
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +38 -94
- package/crates/tish_compile_js/src/lib.rs +0 -1
- package/crates/tish_compile_js/src/tests_jsx.rs +68 -0
- package/crates/tish_core/Cargo.toml +4 -0
- package/crates/tish_core/src/console_style.rs +7 -1
- package/crates/tish_core/src/json.rs +1 -2
- package/crates/tish_core/src/macros.rs +2 -3
- package/crates/tish_core/src/value.rs +13 -5
- package/crates/tish_cranelift/src/lib.rs +6 -4
- package/crates/tish_cranelift/src/lower.rs +5 -3
- package/crates/tish_cranelift_runtime/src/lib.rs +4 -2
- package/crates/tish_eval/Cargo.toml +2 -0
- package/crates/tish_eval/src/eval.rs +172 -79
- package/crates/tish_eval/src/http.rs +3 -4
- package/crates/tish_eval/src/lib.rs +7 -0
- package/crates/tish_eval/src/regex.rs +3 -2
- package/crates/tish_eval/src/value.rs +11 -13
- package/crates/tish_eval/src/value_convert.rs +4 -8
- package/crates/tish_fmt/src/lib.rs +49 -10
- package/crates/tish_lexer/src/token.rs +2 -0
- package/crates/tish_lint/src/lib.rs +9 -0
- package/crates/tish_llvm/src/lib.rs +4 -4
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_native/src/build.rs +16 -2
- package/crates/tish_native/src/lib.rs +10 -11
- package/crates/tish_opt/src/lib.rs +15 -0
- package/crates/tish_parser/src/lib.rs +101 -1
- package/crates/tish_parser/src/parser.rs +168 -51
- package/crates/tish_runtime/src/http.rs +4 -5
- package/crates/tish_runtime/src/http_fetch.rs +17 -10
- package/crates/tish_runtime/src/lib.rs +9 -2
- package/crates/tish_runtime/src/promise.rs +2 -3
- package/crates/tish_runtime/src/promise_io.rs +2 -3
- package/crates/tish_runtime/src/ws.rs +7 -7
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +390 -0
- package/crates/tish_ui/src/lib.rs +16 -0
- package/crates/tish_ui/src/runtime/hooks.rs +122 -0
- package/crates/tish_ui/src/runtime/mod.rs +173 -0
- package/crates/tish_vm/src/vm.rs +121 -27
- package/justfile +3 -3
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
- package/crates/tish_compile_js/src/js_intrinsics.rs +0 -82
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
//! Type inference pass: annotates `VarDecl` nodes with inferred `TypeAnnotation`s
|
|
2
|
+
//! where the user hasn't provided them, enabling codegen to emit native Rust types.
|
|
3
|
+
//!
|
|
4
|
+
//! Rules (conservative — only infer when unambiguous):
|
|
5
|
+
//! - Number literal init → `number`
|
|
6
|
+
//! - String literal init → `string`
|
|
7
|
+
//! - Bool literal init → `boolean`
|
|
8
|
+
//! - Arithmetic of two `number` expressions → `number`
|
|
9
|
+
//! - Comparison of two `number` expressions → `boolean`
|
|
10
|
+
//! - Already-annotated vars are left unchanged.
|
|
11
|
+
|
|
12
|
+
use std::collections::HashMap;
|
|
13
|
+
use tishlang_ast::{
|
|
14
|
+
ArrowBody, BinOp, CallArg, Expr, FunParam, Literal, Program, Statement, TypeAnnotation,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/// Scoped type environment used during inference.
|
|
18
|
+
#[derive(Default)]
|
|
19
|
+
pub struct InferCtx {
|
|
20
|
+
scopes: Vec<HashMap<String, TypeAnnotation>>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl InferCtx {
|
|
24
|
+
pub fn new() -> Self {
|
|
25
|
+
Self { scopes: vec![HashMap::new()] }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn push_scope(&mut self) {
|
|
29
|
+
self.scopes.push(HashMap::new());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn pop_scope(&mut self) {
|
|
33
|
+
self.scopes.pop();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fn define(&mut self, name: &str, ty: TypeAnnotation) {
|
|
37
|
+
if let Some(s) = self.scopes.last_mut() {
|
|
38
|
+
s.insert(name.to_string(), ty);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub fn lookup(&self, name: &str) -> Option<&TypeAnnotation> {
|
|
43
|
+
for s in self.scopes.iter().rev() {
|
|
44
|
+
if let Some(t) = s.get(name) {
|
|
45
|
+
return Some(t);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
None
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fn is_number(ann: &TypeAnnotation) -> bool {
|
|
53
|
+
matches!(ann, TypeAnnotation::Simple(s) if s.as_ref() == "number")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fn number_ann() -> TypeAnnotation {
|
|
57
|
+
TypeAnnotation::Simple("number".into())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn string_ann() -> TypeAnnotation {
|
|
61
|
+
TypeAnnotation::Simple("string".into())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn bool_ann() -> TypeAnnotation {
|
|
65
|
+
TypeAnnotation::Simple("boolean".into())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Infer the `TypeAnnotation` for an expression, if unambiguous.
|
|
69
|
+
pub fn infer_expr_type(expr: &Expr, ctx: &InferCtx) -> Option<TypeAnnotation> {
|
|
70
|
+
match expr {
|
|
71
|
+
Expr::Literal { value, .. } => match value {
|
|
72
|
+
Literal::Number(_) => Some(number_ann()),
|
|
73
|
+
Literal::String(_) => Some(string_ann()),
|
|
74
|
+
Literal::Bool(_) => Some(bool_ann()),
|
|
75
|
+
Literal::Null => None,
|
|
76
|
+
},
|
|
77
|
+
Expr::Ident { name, .. } => ctx.lookup(name.as_ref()).cloned(),
|
|
78
|
+
Expr::Binary { left, op, right, .. } => {
|
|
79
|
+
let lt = infer_expr_type(left, ctx)?;
|
|
80
|
+
let rt = infer_expr_type(right, ctx)?;
|
|
81
|
+
if is_number(<) && is_number(&rt) {
|
|
82
|
+
match op {
|
|
83
|
+
BinOp::Add
|
|
84
|
+
| BinOp::Sub
|
|
85
|
+
| BinOp::Mul
|
|
86
|
+
| BinOp::Div
|
|
87
|
+
| BinOp::Mod
|
|
88
|
+
| BinOp::Pow => Some(number_ann()),
|
|
89
|
+
BinOp::Lt
|
|
90
|
+
| BinOp::Le
|
|
91
|
+
| BinOp::Gt
|
|
92
|
+
| BinOp::Ge
|
|
93
|
+
| BinOp::StrictEq
|
|
94
|
+
| BinOp::StrictNe => Some(bool_ann()),
|
|
95
|
+
_ => None,
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
None
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
Expr::Unary { op, operand, .. } => {
|
|
102
|
+
use tishlang_ast::UnaryOp;
|
|
103
|
+
match op {
|
|
104
|
+
UnaryOp::Neg | UnaryOp::Pos => {
|
|
105
|
+
let t = infer_expr_type(operand, ctx)?;
|
|
106
|
+
if is_number(&t) { Some(number_ann()) } else { None }
|
|
107
|
+
}
|
|
108
|
+
UnaryOp::Not => Some(bool_ann()),
|
|
109
|
+
_ => None,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
_ => None,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Run inference over a program, returning a modified Program with additional
|
|
117
|
+
/// type annotations filled in on `VarDecl` nodes.
|
|
118
|
+
pub fn infer_program(program: &Program) -> Program {
|
|
119
|
+
let mut ctx = InferCtx::new();
|
|
120
|
+
Program { statements: infer_statements(&program.statements, &mut ctx) }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fn infer_statements(stmts: &[Statement], ctx: &mut InferCtx) -> Vec<Statement> {
|
|
124
|
+
stmts.iter().map(|s| infer_statement(s, ctx)).collect()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn infer_statement(stmt: &Statement, ctx: &mut InferCtx) -> Statement {
|
|
128
|
+
match stmt {
|
|
129
|
+
Statement::VarDecl { name, mutable, type_ann, init, span } => {
|
|
130
|
+
// Already annotated — propagate into ctx but don't change the node.
|
|
131
|
+
if let Some(ann) = type_ann {
|
|
132
|
+
ctx.define(name.as_ref(), ann.clone());
|
|
133
|
+
return stmt.clone();
|
|
134
|
+
}
|
|
135
|
+
// Try to infer from init expression.
|
|
136
|
+
let inferred = init.as_ref().and_then(|e| infer_expr_type(e, ctx));
|
|
137
|
+
if let Some(ref ann) = inferred {
|
|
138
|
+
ctx.define(name.as_ref(), ann.clone());
|
|
139
|
+
}
|
|
140
|
+
Statement::VarDecl {
|
|
141
|
+
name: name.clone(),
|
|
142
|
+
mutable: *mutable,
|
|
143
|
+
type_ann: inferred,
|
|
144
|
+
init: init.clone(),
|
|
145
|
+
span: *span,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
Statement::Block { statements, span } => {
|
|
149
|
+
ctx.push_scope();
|
|
150
|
+
let stmts = infer_statements(statements, ctx);
|
|
151
|
+
ctx.pop_scope();
|
|
152
|
+
Statement::Block { statements: stmts, span: *span }
|
|
153
|
+
}
|
|
154
|
+
Statement::For { init, cond, update, body, span } => {
|
|
155
|
+
// Scope for loop variable
|
|
156
|
+
ctx.push_scope();
|
|
157
|
+
let new_init = init.as_ref().map(|i| Box::new(infer_statement(i, ctx)));
|
|
158
|
+
let new_body = Box::new(infer_statement(body, ctx));
|
|
159
|
+
ctx.pop_scope();
|
|
160
|
+
Statement::For {
|
|
161
|
+
init: new_init,
|
|
162
|
+
cond: cond.clone(),
|
|
163
|
+
update: update.clone(),
|
|
164
|
+
body: new_body,
|
|
165
|
+
span: *span,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
Statement::ForOf { name, iterable, body, span } => {
|
|
169
|
+
ctx.push_scope();
|
|
170
|
+
let new_body = Box::new(infer_statement(body, ctx));
|
|
171
|
+
ctx.pop_scope();
|
|
172
|
+
Statement::ForOf {
|
|
173
|
+
name: name.clone(),
|
|
174
|
+
iterable: iterable.clone(),
|
|
175
|
+
body: new_body,
|
|
176
|
+
span: *span,
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
Statement::While { cond, body, span } => {
|
|
180
|
+
ctx.push_scope();
|
|
181
|
+
let new_body = Box::new(infer_statement(body, ctx));
|
|
182
|
+
ctx.pop_scope();
|
|
183
|
+
Statement::While { cond: cond.clone(), body: new_body, span: *span }
|
|
184
|
+
}
|
|
185
|
+
Statement::DoWhile { body, cond, span } => {
|
|
186
|
+
ctx.push_scope();
|
|
187
|
+
let new_body = Box::new(infer_statement(body, ctx));
|
|
188
|
+
ctx.pop_scope();
|
|
189
|
+
Statement::DoWhile { body: new_body, cond: cond.clone(), span: *span }
|
|
190
|
+
}
|
|
191
|
+
Statement::If { cond, then_branch, else_branch, span } => {
|
|
192
|
+
let new_then = Box::new(infer_statement(then_branch, ctx));
|
|
193
|
+
let new_else = else_branch.as_ref().map(|e| Box::new(infer_statement(e, ctx)));
|
|
194
|
+
Statement::If {
|
|
195
|
+
cond: cond.clone(),
|
|
196
|
+
then_branch: new_then,
|
|
197
|
+
else_branch: new_else,
|
|
198
|
+
span: *span,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
Statement::FunDecl { async_, name, params, rest_param, return_type, body, span } => {
|
|
202
|
+
ctx.push_scope();
|
|
203
|
+
for p in params {
|
|
204
|
+
if let FunParam::Simple(tp) = p {
|
|
205
|
+
if let Some(ann) = &tp.type_ann {
|
|
206
|
+
ctx.define(tp.name.as_ref(), ann.clone());
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if let Some(rp) = rest_param {
|
|
211
|
+
if let Some(ann) = &rp.type_ann {
|
|
212
|
+
ctx.define(rp.name.as_ref(), ann.clone());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
let new_body = Box::new(infer_statement(body, ctx));
|
|
216
|
+
ctx.pop_scope();
|
|
217
|
+
Statement::FunDecl {
|
|
218
|
+
async_: *async_,
|
|
219
|
+
name: name.clone(),
|
|
220
|
+
params: params.clone(),
|
|
221
|
+
rest_param: rest_param.clone(),
|
|
222
|
+
return_type: return_type.clone(),
|
|
223
|
+
body: new_body,
|
|
224
|
+
span: *span,
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// For statements with no interesting sub-structure, clone as-is.
|
|
228
|
+
_ => stmt.clone(),
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Suppress unused import warning — CallArg is used indirectly via tishlang_ast.
|
|
233
|
+
#[allow(dead_code)]
|
|
234
|
+
fn _uses_call_arg(_: &CallArg) {}
|
|
235
|
+
#[allow(dead_code)]
|
|
236
|
+
fn _uses_arrow_body(_: &ArrowBody) {}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
//! Emits Rust source that links to tishlang_runtime.
|
|
4
4
|
|
|
5
5
|
mod codegen;
|
|
6
|
+
mod infer;
|
|
6
7
|
mod resolve;
|
|
7
8
|
mod types;
|
|
8
9
|
|
|
@@ -25,6 +26,9 @@ mod tests {
|
|
|
25
26
|
|
|
26
27
|
#[test]
|
|
27
28
|
fn typed_assign_conversion() {
|
|
29
|
+
// With the inference pass and native emit, `total: number = 0` becomes f64.
|
|
30
|
+
// Assignment `total = total + n` (where n comes from ForOf over a Value::Array)
|
|
31
|
+
// emits a native f64 assignment that unboxes the Value result via from_value_expr.
|
|
28
32
|
let src = r#"
|
|
29
33
|
fn sum(...args: number[]): number {
|
|
30
34
|
let total: number = 0
|
|
@@ -34,11 +38,16 @@ fn sum(...args: number[]): number {
|
|
|
34
38
|
"#;
|
|
35
39
|
let program = parse(src).unwrap();
|
|
36
40
|
let rust = compile(&program).unwrap();
|
|
37
|
-
|
|
41
|
+
// total should be declared as f64
|
|
42
|
+
assert!(rust.contains("let mut total: f64"), "expected total: f64");
|
|
43
|
+
// The return value of run() should convert total back to Value
|
|
44
|
+
assert!(rust.contains("Value::Number(total)"), "expected Value::Number(total) wrapping");
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
#[test]
|
|
41
48
|
fn loop_var_decl_clone_outer_var() {
|
|
49
|
+
// With inference, outerVar = 42 gets inferred as f64. f64 is Copy, so no clone is
|
|
50
|
+
// needed — direct assignment is correct. The test verifies compilation succeeds.
|
|
42
51
|
let src = r#"
|
|
43
52
|
let outerVar = 42
|
|
44
53
|
for (let i = 0; i < 5; i = i + 1) {
|
|
@@ -47,14 +56,51 @@ for (let i = 0; i < 5; i = i + 1) {
|
|
|
47
56
|
"#;
|
|
48
57
|
let program = parse(src).unwrap();
|
|
49
58
|
let rust = compile(&program).unwrap();
|
|
59
|
+
// outerVar and x are f64 (inferred) — Copy assignment, no .clone() needed.
|
|
60
|
+
assert!(rust.contains("let mut outerVar: f64"), "expected outerVar: f64");
|
|
61
|
+
assert!(rust.contains("let mut x: f64"), "expected x: f64");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[test]
|
|
65
|
+
fn new_expression_lowers_to_construct_on_native() {
|
|
66
|
+
let src = "fn f() { return new Uint8Array(4) }";
|
|
67
|
+
let program = parse(src).unwrap();
|
|
68
|
+
let rust = compile(&program).unwrap();
|
|
69
|
+
assert!(
|
|
70
|
+
rust.contains("tish_construct"),
|
|
71
|
+
"expected new to lower to tish_construct, got snippet missing it"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// User-defined constructor name: `new ClassName(...)` must compile natively (host `construct`)
|
|
76
|
+
/// and is the same surface syntax as the JS target (`new` in emitted JavaScript).
|
|
77
|
+
#[test]
|
|
78
|
+
fn new_class_name_compiles_native_via_tish_construct() {
|
|
79
|
+
let src = r#"
|
|
80
|
+
fn ClassName(x) {
|
|
81
|
+
return x
|
|
82
|
+
}
|
|
83
|
+
fn factory() {
|
|
84
|
+
return new ClassName(42)
|
|
85
|
+
}
|
|
86
|
+
"#;
|
|
87
|
+
let program = parse(src).unwrap();
|
|
88
|
+
let rust = compile(&program).unwrap();
|
|
89
|
+
assert!(
|
|
90
|
+
rust.contains("tish_construct"),
|
|
91
|
+
"expected new ClassName to lower to tish_construct"
|
|
92
|
+
);
|
|
50
93
|
assert!(
|
|
51
|
-
rust.contains("
|
|
52
|
-
"expected
|
|
94
|
+
rust.contains("ClassName"),
|
|
95
|
+
"expected emitted Rust to reference ClassName callable"
|
|
53
96
|
);
|
|
54
97
|
}
|
|
55
98
|
|
|
56
99
|
#[test]
|
|
57
100
|
fn loop_var_decl_clone_via_project_full() {
|
|
101
|
+
// With the inference pass, `let outerVar = 42` is inferred as f64 (Copy) — no clone needed.
|
|
102
|
+
// This test verifies the full benchmark_granular project compiles and that outerVar
|
|
103
|
+
// is emitted as the inferred f64 type rather than requiring a Value clone.
|
|
58
104
|
let manifest = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
59
105
|
let bench = manifest.join("../../tests/core/benchmark_granular.tish").canonicalize().unwrap();
|
|
60
106
|
// Use same default features as tish CLI (http, fs, process, regex)
|
|
@@ -63,9 +109,10 @@ for (let i = 0; i < 5; i = i + 1) {
|
|
|
63
109
|
.map(String::from)
|
|
64
110
|
.collect::<Vec<_>>();
|
|
65
111
|
let (rust, _) = compile_project_full(&bench, bench.parent(), &features, true).unwrap();
|
|
112
|
+
// outerVar = 42 is inferred as f64; f64 is Copy so no .clone() is emitted.
|
|
66
113
|
assert!(
|
|
67
|
-
rust.contains("
|
|
68
|
-
"expected outerVar to be
|
|
114
|
+
rust.contains("let mut outerVar: f64"),
|
|
115
|
+
"expected outerVar to be inferred as f64 (Copy, no clone needed)"
|
|
69
116
|
);
|
|
70
117
|
}
|
|
71
118
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
use std::collections::HashMap;
|
|
6
6
|
use std::sync::Arc;
|
|
7
|
-
use tishlang_ast::TypeAnnotation;
|
|
7
|
+
use tishlang_ast::{BinOp, TypeAnnotation};
|
|
8
8
|
|
|
9
9
|
/// Concrete Rust type representation for code generation.
|
|
10
10
|
#[derive(Debug, Clone, PartialEq)]
|
|
@@ -89,6 +89,38 @@ impl RustType {
|
|
|
89
89
|
!matches!(self, RustType::Value)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/// Check if this type is numeric (f64).
|
|
93
|
+
pub fn is_numeric(&self) -> bool {
|
|
94
|
+
matches!(self, RustType::F64)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Infer the result type of a binary operation given the operand types.
|
|
98
|
+
/// Returns `None` if native code cannot be emitted (fall back to Value path).
|
|
99
|
+
pub fn result_type_of_binop(op: BinOp, lhs: &RustType, rhs: &RustType) -> Option<RustType> {
|
|
100
|
+
if lhs == &RustType::F64 && rhs == &RustType::F64 {
|
|
101
|
+
match op {
|
|
102
|
+
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod | BinOp::Pow => {
|
|
103
|
+
Some(RustType::F64)
|
|
104
|
+
}
|
|
105
|
+
BinOp::Lt
|
|
106
|
+
| BinOp::Le
|
|
107
|
+
| BinOp::Gt
|
|
108
|
+
| BinOp::Ge
|
|
109
|
+
| BinOp::StrictEq
|
|
110
|
+
| BinOp::StrictNe => Some(RustType::Bool),
|
|
111
|
+
_ => None,
|
|
112
|
+
}
|
|
113
|
+
} else if lhs == &RustType::Bool && rhs == &RustType::Bool {
|
|
114
|
+
match op {
|
|
115
|
+
BinOp::And | BinOp::Or => Some(RustType::Bool),
|
|
116
|
+
BinOp::StrictEq | BinOp::StrictNe => Some(RustType::Bool),
|
|
117
|
+
_ => None,
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
None
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
92
124
|
/// Get the Rust type string for code generation.
|
|
93
125
|
pub fn to_rust_type_str(&self) -> String {
|
|
94
126
|
match self {
|
|
@@ -169,14 +201,19 @@ impl RustType {
|
|
|
169
201
|
match self {
|
|
170
202
|
RustType::Value => native_expr.to_string(),
|
|
171
203
|
RustType::F64 => format!("Value::Number({})", native_expr),
|
|
172
|
-
RustType::String => format!("Value::String({}.into())", native_expr),
|
|
204
|
+
RustType::String => format!("Value::String({}.clone().into())", native_expr),
|
|
173
205
|
RustType::Bool => format!("Value::Bool({})", native_expr),
|
|
174
206
|
RustType::Unit => "Value::Null".to_string(),
|
|
175
207
|
RustType::Vec(inner) => {
|
|
176
|
-
|
|
208
|
+
// Use iter()/copied()/cloned() to avoid moving the vector.
|
|
209
|
+
let (iter_suffix, val_expr) = match inner.as_ref() {
|
|
210
|
+
RustType::F64 => (".iter().copied()", "Value::Number(v)".to_string()),
|
|
211
|
+
RustType::Bool => (".iter().copied()", "Value::Bool(v)".to_string()),
|
|
212
|
+
_ => (".iter().cloned()", inner.to_value_expr("v")),
|
|
213
|
+
};
|
|
177
214
|
format!(
|
|
178
|
-
"Value::Array(Rc::new(RefCell::new({}.
|
|
179
|
-
native_expr,
|
|
215
|
+
"Value::Array(Rc::new(RefCell::new({}{}.map(|v| {}).collect())))",
|
|
216
|
+
native_expr, iter_suffix, val_expr
|
|
180
217
|
)
|
|
181
218
|
}
|
|
182
219
|
RustType::Option(inner) => {
|
|
@@ -14,3 +14,4 @@ tishlang_ast = { path = "../tish_ast", version = ">=0.1" }
|
|
|
14
14
|
tishlang_compile = { path = "../tish_compile", version = ">=0.1" }
|
|
15
15
|
tishlang_opt = { path = "../tish_opt", version = ">=0.1" }
|
|
16
16
|
tishlang_parser = { path = "../tish_parser", version = ">=0.1" }
|
|
17
|
+
tishlang_ui = { path = "../tish_ui", default-features = false, features = ["compiler"] }
|
|
@@ -2,18 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
use tishlang_ast::{
|
|
4
4
|
ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern, Expr,
|
|
5
|
-
|
|
6
|
-
Statement, UnaryOp,
|
|
5
|
+
FunParam, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Statement, UnaryOp,
|
|
7
6
|
};
|
|
8
7
|
|
|
9
8
|
use crate::error::CompileError;
|
|
10
|
-
use crate::js_intrinsics::{JsIntrinsic, JsIntrinsics};
|
|
11
9
|
|
|
12
10
|
struct Codegen {
|
|
13
11
|
output: String,
|
|
14
12
|
indent: usize,
|
|
15
13
|
in_async: bool,
|
|
16
|
-
intrinsics: JsIntrinsics,
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
fn stmt_terminates_switch(stmt: Option<&Statement>) -> bool {
|
|
@@ -29,7 +26,6 @@ impl Codegen {
|
|
|
29
26
|
output: String::new(),
|
|
30
27
|
indent: 0,
|
|
31
28
|
in_async: false,
|
|
32
|
-
intrinsics: JsIntrinsics::new(),
|
|
33
29
|
}
|
|
34
30
|
}
|
|
35
31
|
|
|
@@ -61,9 +57,6 @@ impl Codegen {
|
|
|
61
57
|
for stmt in &program.statements {
|
|
62
58
|
self.emit_statement(stmt)?;
|
|
63
59
|
}
|
|
64
|
-
self.output = self
|
|
65
|
-
.intrinsics
|
|
66
|
-
.prepend_runtime_preamble(std::mem::take(&mut self.output));
|
|
67
60
|
Ok(())
|
|
68
61
|
}
|
|
69
62
|
|
|
@@ -304,20 +297,34 @@ impl Codegen {
|
|
|
304
297
|
|
|
305
298
|
fn emit_params(
|
|
306
299
|
&mut self,
|
|
307
|
-
params: &[
|
|
300
|
+
params: &[FunParam],
|
|
308
301
|
rest_param: Option<&tishlang_ast::TypedParam>,
|
|
309
302
|
) -> Result<String, CompileError> {
|
|
310
|
-
let mut parts: Vec<String> =
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
303
|
+
let mut parts: Vec<String> = Vec::new();
|
|
304
|
+
for p in params {
|
|
305
|
+
match p {
|
|
306
|
+
FunParam::Simple(tp) => {
|
|
307
|
+
let n = Self::escape_ident(tp.name.as_ref());
|
|
308
|
+
let s = if let Some(ref d) = tp.default {
|
|
309
|
+
format!("{} = {}", n, self.emit_expr(d)?)
|
|
310
|
+
} else {
|
|
311
|
+
n
|
|
312
|
+
};
|
|
313
|
+
parts.push(s);
|
|
314
|
+
}
|
|
315
|
+
FunParam::Destructure {
|
|
316
|
+
pattern,
|
|
317
|
+
type_ann: _,
|
|
318
|
+
default,
|
|
319
|
+
} => {
|
|
320
|
+
let mut s = self.emit_destruct_pattern(pattern)?;
|
|
321
|
+
if let Some(ref d) = default {
|
|
322
|
+
s = format!("{} = {}", s, self.emit_expr(d)?);
|
|
323
|
+
}
|
|
324
|
+
parts.push(s);
|
|
318
325
|
}
|
|
319
|
-
}
|
|
320
|
-
|
|
326
|
+
}
|
|
327
|
+
}
|
|
321
328
|
if let Some(rest) = rest_param {
|
|
322
329
|
parts.push(format!("...{}", Self::escape_ident(rest.name.as_ref())));
|
|
323
330
|
}
|
|
@@ -421,16 +428,6 @@ impl Codegen {
|
|
|
421
428
|
}
|
|
422
429
|
}
|
|
423
430
|
Expr::Call { callee, args, .. } => {
|
|
424
|
-
if let Some(kind) =
|
|
425
|
-
JsIntrinsics::classify_call(callee.as_ref(), args)?
|
|
426
|
-
{
|
|
427
|
-
self.intrinsics.mark(kind);
|
|
428
|
-
if kind == JsIntrinsic::Uint8Array {
|
|
429
|
-
let n = self.emit_call_arg(&args[0])?;
|
|
430
|
-
return Ok(JsIntrinsics::emit_expr(kind, &n));
|
|
431
|
-
}
|
|
432
|
-
return Ok(JsIntrinsics::emit_expr(kind, ""));
|
|
433
|
-
}
|
|
434
431
|
let c = self.emit_expr(callee)?;
|
|
435
432
|
let arg_strs: Result<Vec<_>, _> =
|
|
436
433
|
args.iter().map(|a| self.emit_call_arg(a)).collect();
|
|
@@ -438,6 +435,13 @@ impl Codegen {
|
|
|
438
435
|
// Tish uses null for undefined (e.g. empty array pop/shift)
|
|
439
436
|
format!("({}({}) ?? null)", c, arg_strs)
|
|
440
437
|
}
|
|
438
|
+
Expr::New { callee, args, .. } => {
|
|
439
|
+
let c = self.emit_expr(callee)?;
|
|
440
|
+
let arg_strs: Result<Vec<_>, _> =
|
|
441
|
+
args.iter().map(|a| self.emit_call_arg(a)).collect();
|
|
442
|
+
let arg_strs = arg_strs?.join(", ");
|
|
443
|
+
format!("(new {}({}) ?? null)", c, arg_strs)
|
|
444
|
+
}
|
|
441
445
|
Expr::Member {
|
|
442
446
|
object,
|
|
443
447
|
prop,
|
|
@@ -625,23 +629,11 @@ impl Codegen {
|
|
|
625
629
|
let o = self.emit_expr(operand)?;
|
|
626
630
|
format!("(await {})", o)
|
|
627
631
|
}
|
|
628
|
-
Expr::JsxElement {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
};
|
|
634
|
-
let props_str = self.emit_jsx_props(props)?;
|
|
635
|
-
let children_strs: Result<Vec<_>, _> =
|
|
636
|
-
children.iter().map(|c| self.emit_jsx_child(c)).collect();
|
|
637
|
-
let children_str = children_strs?.join(", ");
|
|
638
|
-
format!("h({}, {}, [{}])", tag_str, props_str, children_str)
|
|
639
|
-
}
|
|
640
|
-
Expr::JsxFragment { children, .. } => {
|
|
641
|
-
let children_strs: Result<Vec<_>, _> =
|
|
642
|
-
children.iter().map(|c| self.emit_jsx_child(c)).collect();
|
|
643
|
-
let children_str = children_strs?.join(", ");
|
|
644
|
-
format!("h(Fragment, null, [{}])", children_str)
|
|
632
|
+
Expr::JsxElement { .. } | Expr::JsxFragment { .. } => {
|
|
633
|
+
tishlang_ui::jsx::emit_jsx_js(expr, &mut |e| {
|
|
634
|
+
self.emit_expr(e).map_err(|ce| ce.message)
|
|
635
|
+
})
|
|
636
|
+
.map_err(|m| CompileError { message: m })?
|
|
645
637
|
}
|
|
646
638
|
Expr::NativeModuleLoad { spec, .. } => {
|
|
647
639
|
return Err(CompileError {
|
|
@@ -661,54 +653,6 @@ impl Codegen {
|
|
|
661
653
|
}
|
|
662
654
|
}
|
|
663
655
|
|
|
664
|
-
fn emit_jsx_props(&mut self, props: &[JsxProp]) -> Result<String, CompileError> {
|
|
665
|
-
if props.is_empty() {
|
|
666
|
-
return Ok("null".to_string());
|
|
667
|
-
}
|
|
668
|
-
let parts: Result<Vec<_>, _> = props
|
|
669
|
-
.iter()
|
|
670
|
-
.map(|p| match p {
|
|
671
|
-
JsxProp::Attr { name, value } => {
|
|
672
|
-
let val = match value {
|
|
673
|
-
JsxAttrValue::String(s) => format!("{:?}", s.as_ref()),
|
|
674
|
-
JsxAttrValue::Expr(e) => self.emit_expr(e)?,
|
|
675
|
-
JsxAttrValue::ImplicitTrue => "true".to_string(),
|
|
676
|
-
};
|
|
677
|
-
let key = name.as_ref();
|
|
678
|
-
Ok(if key.chars().all(|c| c.is_alphanumeric() || c == '_') {
|
|
679
|
-
format!("{}: {}", key, val)
|
|
680
|
-
} else {
|
|
681
|
-
format!("{:?}: {}", key, val)
|
|
682
|
-
})
|
|
683
|
-
}
|
|
684
|
-
JsxProp::Spread(e) => Ok(format!("...{}", self.emit_expr(e)?)),
|
|
685
|
-
})
|
|
686
|
-
.collect();
|
|
687
|
-
Ok(format!("{{ {} }}", parts?.join(", ")))
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
fn emit_jsx_child(&mut self, child: &JsxChild) -> Result<String, CompileError> {
|
|
691
|
-
match child {
|
|
692
|
-
JsxChild::Text(s) => Ok(format!("{:?}", s.as_ref())),
|
|
693
|
-
JsxChild::Expr(e) => {
|
|
694
|
-
let inner = self.emit_expr(e)?;
|
|
695
|
-
// Only wrap literals we know are primitives (number, bool, null). Never wrap:
|
|
696
|
-
// string/template (already strings), JSX (elements), Call (components), Array/Ident (may hold elements).
|
|
697
|
-
let needs_string = matches!(
|
|
698
|
-
e,
|
|
699
|
-
Expr::Literal {
|
|
700
|
-
value: Literal::Number(_) | Literal::Bool(_) | Literal::Null,
|
|
701
|
-
..
|
|
702
|
-
}
|
|
703
|
-
);
|
|
704
|
-
Ok(if needs_string {
|
|
705
|
-
format!("String({})", inner)
|
|
706
|
-
} else {
|
|
707
|
-
inner
|
|
708
|
-
})
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
656
|
}
|
|
713
657
|
|
|
714
658
|
/// Compile a single program (no imports) to JavaScript. JSX lowers to `h` / `Fragment` (Lattish).
|