@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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
use std::collections::HashMap;
|
|
6
6
|
use std::sync::Arc;
|
|
7
|
-
use tishlang_ast::{BinOp, TypeAnnotation};
|
|
7
|
+
use tishlang_ast::{BinOp, FunParam, TypeAnnotation, TypedParam};
|
|
8
8
|
|
|
9
9
|
/// Concrete Rust type representation for code generation.
|
|
10
10
|
#[derive(Debug, Clone, PartialEq)]
|
|
@@ -23,8 +23,19 @@ pub enum RustType {
|
|
|
23
23
|
Vec(Box<RustType>),
|
|
24
24
|
/// Option<T> for nullable types (T | null)
|
|
25
25
|
Option(Box<RustType>),
|
|
26
|
-
///
|
|
26
|
+
/// Inline object shape — used during inference / annotation lowering
|
|
27
|
+
/// before a `Named` alias has been registered. Once a corresponding
|
|
28
|
+
/// `type Foo = { ... }` declaration is found in the program, occurrences
|
|
29
|
+
/// of this shape can be canonicalised into `RustType::Named("Foo")`.
|
|
27
30
|
Object(Vec<(Arc<str>, RustType)>),
|
|
31
|
+
/// User-defined named type (a struct emitted by the compiler).
|
|
32
|
+
/// The field list is duplicated here so the codegen can emit struct
|
|
33
|
+
/// literals, member access, and Value-conversion glue without going
|
|
34
|
+
/// back to a global registry on every call site.
|
|
35
|
+
Named {
|
|
36
|
+
name: Arc<str>,
|
|
37
|
+
fields: Vec<(Arc<str>, RustType)>,
|
|
38
|
+
},
|
|
28
39
|
/// Fn trait for typed functions
|
|
29
40
|
Function {
|
|
30
41
|
params: Vec<RustType>,
|
|
@@ -33,8 +44,21 @@ pub enum RustType {
|
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
impl RustType {
|
|
36
|
-
/// Convert a TypeAnnotation to a RustType.
|
|
47
|
+
/// Convert a TypeAnnotation to a RustType (no alias resolution).
|
|
48
|
+
/// Use [`Self::from_annotation_with_aliases`] when a registry is
|
|
49
|
+
/// available so user-defined `type X = { ... }` aliases land as
|
|
50
|
+
/// `RustType::Named` and can drive struct emission.
|
|
37
51
|
pub fn from_annotation(ann: &TypeAnnotation) -> Self {
|
|
52
|
+
Self::from_annotation_with_aliases(ann, &HashMap::new())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Like [`from_annotation`], but consults `aliases` so a `Simple(name)`
|
|
56
|
+
/// reference to a user-declared `type X = { ... }` resolves to a
|
|
57
|
+
/// `RustType::Named { name, fields }` carrying the struct shape.
|
|
58
|
+
pub fn from_annotation_with_aliases(
|
|
59
|
+
ann: &TypeAnnotation,
|
|
60
|
+
aliases: &HashMap<String, RustType>,
|
|
61
|
+
) -> Self {
|
|
38
62
|
match ann {
|
|
39
63
|
TypeAnnotation::Simple(name) => match name.as_ref() {
|
|
40
64
|
"number" => RustType::F64,
|
|
@@ -43,21 +67,38 @@ impl RustType {
|
|
|
43
67
|
"void" | "undefined" => RustType::Unit,
|
|
44
68
|
"null" => RustType::Unit,
|
|
45
69
|
"any" => RustType::Value,
|
|
46
|
-
|
|
70
|
+
other => {
|
|
71
|
+
// User-declared `type X = { ... }`: lift the inline
|
|
72
|
+
// object shape into a `Named` so the codegen can emit
|
|
73
|
+
// a Rust struct and direct field access for it.
|
|
74
|
+
if let Some(t) = aliases.get(other) {
|
|
75
|
+
if let RustType::Object(fields) = t {
|
|
76
|
+
return RustType::Named {
|
|
77
|
+
name: Arc::from(other),
|
|
78
|
+
fields: fields.clone(),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return t.clone();
|
|
82
|
+
}
|
|
83
|
+
RustType::Value
|
|
84
|
+
}
|
|
47
85
|
},
|
|
48
|
-
TypeAnnotation::Array(elem) =>
|
|
49
|
-
|
|
50
|
-
|
|
86
|
+
TypeAnnotation::Array(elem) => RustType::Vec(Box::new(
|
|
87
|
+
Self::from_annotation_with_aliases(elem, aliases),
|
|
88
|
+
)),
|
|
51
89
|
TypeAnnotation::Object(fields) => {
|
|
52
90
|
let typed_fields: Vec<_> = fields
|
|
53
91
|
.iter()
|
|
54
|
-
.map(|(k, v)| (k.clone(), Self::
|
|
92
|
+
.map(|(k, v)| (k.clone(), Self::from_annotation_with_aliases(v, aliases)))
|
|
55
93
|
.collect();
|
|
56
94
|
RustType::Object(typed_fields)
|
|
57
95
|
}
|
|
58
96
|
TypeAnnotation::Function { params, returns } => {
|
|
59
|
-
let typed_params: Vec<_> = params
|
|
60
|
-
|
|
97
|
+
let typed_params: Vec<_> = params
|
|
98
|
+
.iter()
|
|
99
|
+
.map(|p| Self::from_annotation_with_aliases(p, aliases))
|
|
100
|
+
.collect();
|
|
101
|
+
let typed_returns = Box::new(Self::from_annotation_with_aliases(returns, aliases));
|
|
61
102
|
RustType::Function {
|
|
62
103
|
params: typed_params,
|
|
63
104
|
returns: typed_returns,
|
|
@@ -66,15 +107,17 @@ impl RustType {
|
|
|
66
107
|
TypeAnnotation::Union(types) => {
|
|
67
108
|
// Check for T | null pattern -> Option<T>
|
|
68
109
|
if types.len() == 2 {
|
|
69
|
-
let has_null = types
|
|
70
|
-
|
|
71
|
-
|
|
110
|
+
let has_null = types
|
|
111
|
+
.iter()
|
|
112
|
+
.any(|t| matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null"));
|
|
72
113
|
if has_null {
|
|
73
|
-
let non_null = types.iter().find(
|
|
74
|
-
!matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null")
|
|
75
|
-
|
|
114
|
+
let non_null = types.iter().find(
|
|
115
|
+
|t| !matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null"),
|
|
116
|
+
);
|
|
76
117
|
if let Some(inner) = non_null {
|
|
77
|
-
return RustType::Option(Box::new(
|
|
118
|
+
return RustType::Option(Box::new(
|
|
119
|
+
Self::from_annotation_with_aliases(inner, aliases),
|
|
120
|
+
));
|
|
78
121
|
}
|
|
79
122
|
}
|
|
80
123
|
}
|
|
@@ -132,9 +175,11 @@ impl RustType {
|
|
|
132
175
|
RustType::Vec(inner) => format!("Vec<{}>", inner.to_rust_type_str()),
|
|
133
176
|
RustType::Option(inner) => format!("Option<{}>", inner.to_rust_type_str()),
|
|
134
177
|
RustType::Object(_) => {
|
|
135
|
-
//
|
|
178
|
+
// Anonymous inline shapes don't have a Rust struct; fall
|
|
179
|
+
// back to the dynamic Value path.
|
|
136
180
|
"Value".to_string()
|
|
137
181
|
}
|
|
182
|
+
RustType::Named { name, .. } => named_struct_ident(name),
|
|
138
183
|
RustType::Function { params, returns } => {
|
|
139
184
|
let params_str: Vec<_> = params.iter().map(|p| p.to_rust_type_str()).collect();
|
|
140
185
|
format!(
|
|
@@ -157,6 +202,23 @@ impl RustType {
|
|
|
157
202
|
RustType::Vec(_) => "Vec::new()".to_string(),
|
|
158
203
|
RustType::Option(_) => "None".to_string(),
|
|
159
204
|
RustType::Object(_) => "Value::Null".to_string(),
|
|
205
|
+
RustType::Named { fields, .. } => {
|
|
206
|
+
// Build a literal struct with each field at its own default,
|
|
207
|
+
// so unannotated decls of a typed struct still compile.
|
|
208
|
+
let init = fields
|
|
209
|
+
.iter()
|
|
210
|
+
.map(|(k, t)| format!("{}: {}", field_ident(k), t.default_value()))
|
|
211
|
+
.collect::<Vec<_>>()
|
|
212
|
+
.join(", ");
|
|
213
|
+
format!(
|
|
214
|
+
"{} {{ {} }}",
|
|
215
|
+
named_struct_ident(match self {
|
|
216
|
+
RustType::Named { name, .. } => name,
|
|
217
|
+
_ => unreachable!(),
|
|
218
|
+
}),
|
|
219
|
+
init
|
|
220
|
+
)
|
|
221
|
+
}
|
|
160
222
|
RustType::Function { .. } => "Value::Null".to_string(),
|
|
161
223
|
}
|
|
162
224
|
}
|
|
@@ -192,6 +254,22 @@ impl RustType {
|
|
|
192
254
|
value_expr, inner_conversion
|
|
193
255
|
)
|
|
194
256
|
}
|
|
257
|
+
RustType::Named { name, fields } => {
|
|
258
|
+
// Each field is fetched out of the Value::Object via
|
|
259
|
+
// `get_prop` and converted to its native type. Falls back
|
|
260
|
+
// to the field's `default_value()` if the field is absent
|
|
261
|
+
// (rare — usually these come from JSON or PG).
|
|
262
|
+
let field_assigns = fields
|
|
263
|
+
.iter()
|
|
264
|
+
.map(|(k, ty)| {
|
|
265
|
+
let fetch =
|
|
266
|
+
format!("tishlang_runtime::get_prop(&{}, {:?})", value_expr, k.as_ref());
|
|
267
|
+
format!("{}: {}", field_ident(k), ty.from_value_expr(&fetch))
|
|
268
|
+
})
|
|
269
|
+
.collect::<Vec<_>>()
|
|
270
|
+
.join(", ");
|
|
271
|
+
format!("{} {{ {} }}", named_struct_ident(name), field_assigns)
|
|
272
|
+
}
|
|
195
273
|
_ => value_expr.to_string(), // Fallback
|
|
196
274
|
}
|
|
197
275
|
}
|
|
@@ -212,7 +290,7 @@ impl RustType {
|
|
|
212
290
|
_ => (".iter().cloned()", inner.to_value_expr("v")),
|
|
213
291
|
};
|
|
214
292
|
format!(
|
|
215
|
-
"Value::Array(
|
|
293
|
+
"Value::Array(VmRef::new({}{}.map(|v| {}).collect()))",
|
|
216
294
|
native_expr, iter_suffix, val_expr
|
|
217
295
|
)
|
|
218
296
|
}
|
|
@@ -223,11 +301,57 @@ impl RustType {
|
|
|
223
301
|
native_expr, inner_to_value
|
|
224
302
|
)
|
|
225
303
|
}
|
|
304
|
+
RustType::Named { fields, .. } => {
|
|
305
|
+
// Walk fields, build an ObjectMap, wrap in Value::Object.
|
|
306
|
+
// The boundary is paid only when crossing into untyped
|
|
307
|
+
// Tish (JSON.stringify, calling a Value::Function, etc.);
|
|
308
|
+
// direct Rust-to-Rust paths between two Named values stay
|
|
309
|
+
// as plain struct moves.
|
|
310
|
+
let inserts = fields
|
|
311
|
+
.iter()
|
|
312
|
+
.map(|(k, ty)| {
|
|
313
|
+
let access = format!("{}.{}", native_expr, field_ident(k));
|
|
314
|
+
let v_expr = ty.to_value_expr(&access);
|
|
315
|
+
format!(
|
|
316
|
+
"_om.insert(::std::sync::Arc::from({:?}), {});",
|
|
317
|
+
k.as_ref(),
|
|
318
|
+
v_expr
|
|
319
|
+
)
|
|
320
|
+
})
|
|
321
|
+
.collect::<Vec<_>>()
|
|
322
|
+
.join(" ");
|
|
323
|
+
format!(
|
|
324
|
+
"{{ let mut _om = ObjectMap::default(); {} Value::Object(VmRef::new(_om)) }}",
|
|
325
|
+
inserts
|
|
326
|
+
)
|
|
327
|
+
}
|
|
226
328
|
_ => native_expr.to_string(), // Fallback
|
|
227
329
|
}
|
|
228
330
|
}
|
|
229
331
|
}
|
|
230
332
|
|
|
333
|
+
/// Map a Tish type-alias name to the Rust struct identifier we emit.
|
|
334
|
+
/// Prefixed so user names can never collide with runtime types like `Value`.
|
|
335
|
+
pub fn named_struct_ident(tish_name: &str) -> String {
|
|
336
|
+
format!("TishStruct_{}", tish_name)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/// Map a Tish field name (`randomNumber`) to a valid Rust identifier
|
|
340
|
+
/// (kept identical here — non-snake-case is allowed via
|
|
341
|
+
/// `#[allow(non_snake_case)]` on the struct, so JS-style camelCase keys
|
|
342
|
+
/// stay readable in the generated source).
|
|
343
|
+
pub fn field_ident(tish_name: &str) -> String {
|
|
344
|
+
// Reserve Rust keywords that would otherwise conflict.
|
|
345
|
+
match tish_name {
|
|
346
|
+
"type" | "ref" | "fn" | "match" | "move" | "mod" | "self" | "Self" | "super" | "use"
|
|
347
|
+
| "where" | "loop" | "yield" | "async" | "await" | "dyn" | "impl" | "trait" | "in"
|
|
348
|
+
| "as" | "box" | "crate" | "const" | "extern" | "let" | "mut" | "pub" | "static"
|
|
349
|
+
| "unsafe" | "abstract" | "become" | "do" | "final" | "macro" | "override" | "priv"
|
|
350
|
+
| "typeof" | "unsized" | "virtual" => format!("r#{}", tish_name),
|
|
351
|
+
_ => tish_name.to_string(),
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
231
355
|
/// Type context for tracking variable types during code generation.
|
|
232
356
|
#[derive(Debug, Clone, Default)]
|
|
233
357
|
pub struct TypeContext {
|
|
@@ -252,6 +376,23 @@ impl TypeContext {
|
|
|
252
376
|
self.scopes.pop();
|
|
253
377
|
}
|
|
254
378
|
|
|
379
|
+
/// Push a scope for a function or arrow body and record formals as [`RustType::Value`].
|
|
380
|
+
///
|
|
381
|
+
/// Native codegen always binds parameters from `args.get(i)` as `Value`; this prevents
|
|
382
|
+
/// outer locals (e.g. a loop counter inferred as [`RustType::F64`]) from shadowing the
|
|
383
|
+
/// wrong type for the same identifier.
|
|
384
|
+
pub fn push_fun_param_scope(&mut self, params: &[FunParam], rest_param: Option<&TypedParam>) {
|
|
385
|
+
self.push_scope();
|
|
386
|
+
for p in params {
|
|
387
|
+
for name in p.bound_names() {
|
|
388
|
+
self.define(name.as_ref(), RustType::Value);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if let Some(rp) = rest_param {
|
|
392
|
+
self.define(rp.name.as_ref(), RustType::Value);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
255
396
|
/// Define a variable in the current scope.
|
|
256
397
|
pub fn define(&mut self, name: &str, ty: RustType) {
|
|
257
398
|
if let Some(scope) = self.scopes.last_mut() {
|
|
@@ -271,9 +412,7 @@ impl TypeContext {
|
|
|
271
412
|
|
|
272
413
|
/// Check if a variable is typed (has a non-Value type).
|
|
273
414
|
pub fn is_typed(&self, name: &str) -> bool {
|
|
274
|
-
self.lookup(name)
|
|
275
|
-
.map(|ty| ty.is_native())
|
|
276
|
-
.unwrap_or(false)
|
|
415
|
+
self.lookup(name).map(|ty| ty.is_native()).unwrap_or(false)
|
|
277
416
|
}
|
|
278
417
|
|
|
279
418
|
/// Get the type of a variable, defaulting to Value if not found.
|
|
@@ -329,13 +468,34 @@ mod tests {
|
|
|
329
468
|
ctx.define("x", RustType::F64);
|
|
330
469
|
assert_eq!(ctx.get_type("x"), RustType::F64);
|
|
331
470
|
assert!(ctx.is_typed("x"));
|
|
332
|
-
|
|
471
|
+
|
|
333
472
|
ctx.push_scope();
|
|
334
473
|
ctx.define("y", RustType::String);
|
|
335
474
|
assert_eq!(ctx.get_type("y"), RustType::String);
|
|
336
475
|
assert_eq!(ctx.get_type("x"), RustType::F64); // Can still see outer scope
|
|
337
|
-
|
|
476
|
+
|
|
338
477
|
ctx.pop_scope();
|
|
339
478
|
assert_eq!(ctx.get_type("y"), RustType::Value); // y no longer visible
|
|
340
479
|
}
|
|
480
|
+
|
|
481
|
+
#[test]
|
|
482
|
+
fn push_fun_param_scope_shadows_outer() {
|
|
483
|
+
use tishlang_ast::{FunParam, Span, TypedParam};
|
|
484
|
+
|
|
485
|
+
let mut ctx = TypeContext::new();
|
|
486
|
+
ctx.define("n", RustType::F64);
|
|
487
|
+
let params = vec![FunParam::Simple(TypedParam {
|
|
488
|
+
name: "n".into(),
|
|
489
|
+
name_span: Span {
|
|
490
|
+
start: (0, 0),
|
|
491
|
+
end: (0, 0),
|
|
492
|
+
},
|
|
493
|
+
type_ann: None,
|
|
494
|
+
default: None,
|
|
495
|
+
})];
|
|
496
|
+
ctx.push_fun_param_scope(¶ms, None);
|
|
497
|
+
assert_eq!(ctx.get_type("n"), RustType::Value);
|
|
498
|
+
ctx.pop_scope();
|
|
499
|
+
assert_eq!(ctx.get_type("n"), RustType::F64);
|
|
500
|
+
}
|
|
341
501
|
}
|
|
@@ -10,6 +10,7 @@ repository = { workspace = true }
|
|
|
10
10
|
default = []
|
|
11
11
|
|
|
12
12
|
[dependencies]
|
|
13
|
+
sourcemap = "9.3"
|
|
13
14
|
tishlang_ast = { path = "../tish_ast", version = ">=0.1" }
|
|
14
15
|
tishlang_compile = { path = "../tish_compile", version = ">=0.1" }
|
|
15
16
|
tishlang_opt = { path = "../tish_opt", version = ">=0.1" }
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
//! Code generation: AST -> JavaScript source.
|
|
2
2
|
|
|
3
|
+
use std::path::{Path, PathBuf};
|
|
4
|
+
|
|
5
|
+
use sourcemap::SourceMapBuilder;
|
|
3
6
|
use tishlang_ast::{
|
|
4
7
|
ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern, Expr,
|
|
5
8
|
FunParam, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Statement, UnaryOp,
|
|
@@ -16,7 +19,9 @@ struct Codegen {
|
|
|
16
19
|
fn stmt_terminates_switch(stmt: Option<&Statement>) -> bool {
|
|
17
20
|
matches!(
|
|
18
21
|
stmt,
|
|
19
|
-
Some(Statement::Break { .. })
|
|
22
|
+
Some(Statement::Break { .. })
|
|
23
|
+
| Some(Statement::Return { .. })
|
|
24
|
+
| Some(Statement::Throw { .. })
|
|
20
25
|
)
|
|
21
26
|
}
|
|
22
27
|
|
|
@@ -43,6 +48,10 @@ impl Codegen {
|
|
|
43
48
|
self.output.push('\n');
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
fn output_line(&self) -> u32 {
|
|
52
|
+
self.output.as_bytes().iter().filter(|&&b| b == b'\n').count() as u32
|
|
53
|
+
}
|
|
54
|
+
|
|
46
55
|
fn escape_ident(s: &str) -> String {
|
|
47
56
|
let s = s.to_string();
|
|
48
57
|
if s == "await" || s == "default" {
|
|
@@ -52,10 +61,46 @@ impl Codegen {
|
|
|
52
61
|
}
|
|
53
62
|
}
|
|
54
63
|
|
|
55
|
-
fn emit_program(
|
|
64
|
+
fn emit_program(
|
|
65
|
+
&mut self,
|
|
66
|
+
program: &Program,
|
|
67
|
+
map_sources: Option<(&[PathBuf], &Path)>,
|
|
68
|
+
map_builder: Option<&mut SourceMapBuilder>,
|
|
69
|
+
) -> Result<(), CompileError> {
|
|
56
70
|
self.write("// Generated by tishlang_compile_js\n");
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
match (map_sources, map_builder) {
|
|
72
|
+
(Some((srcs, root)), Some(sm)) => {
|
|
73
|
+
for (i, stmt) in program.statements.iter().enumerate() {
|
|
74
|
+
if i < srcs.len() {
|
|
75
|
+
let dst_line = self.output_line();
|
|
76
|
+
let sp = stmt.span();
|
|
77
|
+
let src_line = sp.start.0.saturating_sub(1) as u32;
|
|
78
|
+
let src_col = sp.start.1.saturating_sub(1) as u32;
|
|
79
|
+
let abs = srcs[i].as_path();
|
|
80
|
+
let root_canon = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
|
|
81
|
+
let abs_canon = abs.canonicalize().unwrap_or_else(|_| abs.to_path_buf());
|
|
82
|
+
let rel = abs_canon
|
|
83
|
+
.strip_prefix(&root_canon)
|
|
84
|
+
.unwrap_or(abs_canon.as_path());
|
|
85
|
+
let rel_str = rel.to_string_lossy();
|
|
86
|
+
sm.add(
|
|
87
|
+
dst_line,
|
|
88
|
+
0,
|
|
89
|
+
src_line,
|
|
90
|
+
src_col,
|
|
91
|
+
Some(rel_str.as_ref()),
|
|
92
|
+
None,
|
|
93
|
+
false,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
self.emit_statement(stmt)?;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
_ => {
|
|
100
|
+
for stmt in &program.statements {
|
|
101
|
+
self.emit_statement(stmt)?;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
59
104
|
}
|
|
60
105
|
Ok(())
|
|
61
106
|
}
|
|
@@ -174,7 +219,12 @@ impl Codegen {
|
|
|
174
219
|
self.emit_statement(body)?;
|
|
175
220
|
self.indent -= 1;
|
|
176
221
|
}
|
|
177
|
-
Statement::ForOf {
|
|
222
|
+
Statement::ForOf {
|
|
223
|
+
name,
|
|
224
|
+
iterable,
|
|
225
|
+
body,
|
|
226
|
+
..
|
|
227
|
+
} => {
|
|
178
228
|
let escaped = Self::escape_ident(name.as_ref());
|
|
179
229
|
let it = self.emit_expr(iterable)?;
|
|
180
230
|
self.writeln(&format!("for (const {} of {})", escaped, it));
|
|
@@ -204,7 +254,10 @@ impl Codegen {
|
|
|
204
254
|
let async_prefix = if *async_ { "async " } else { "" };
|
|
205
255
|
let escaped = Self::escape_ident(name.as_ref());
|
|
206
256
|
let params_str = self.emit_params(params, rest_param.as_ref())?;
|
|
207
|
-
self.writeln(&format!(
|
|
257
|
+
self.writeln(&format!(
|
|
258
|
+
"{}function {} ({}) {{",
|
|
259
|
+
async_prefix, escaped, params_str
|
|
260
|
+
));
|
|
208
261
|
self.indent += 1;
|
|
209
262
|
if *async_ {
|
|
210
263
|
self.in_async = true;
|
|
@@ -291,6 +344,9 @@ impl Codegen {
|
|
|
291
344
|
Statement::Import { .. } | Statement::Export { .. } => {
|
|
292
345
|
// Resolved away by merge_modules
|
|
293
346
|
}
|
|
347
|
+
Statement::TypeAlias { .. }
|
|
348
|
+
| Statement::DeclareVar { .. }
|
|
349
|
+
| Statement::DeclareFun { .. } => {}
|
|
294
350
|
}
|
|
295
351
|
Ok(())
|
|
296
352
|
}
|
|
@@ -337,11 +393,9 @@ impl Codegen {
|
|
|
337
393
|
let parts: Vec<String> = elements
|
|
338
394
|
.iter()
|
|
339
395
|
.map(|el| match el {
|
|
340
|
-
Some(DestructElement::Ident(n)) =>
|
|
341
|
-
Ok(Self::escape_ident(n.as_ref()))
|
|
342
|
-
}
|
|
396
|
+
Some(DestructElement::Ident(n, _)) => Ok(Self::escape_ident(n.as_ref())),
|
|
343
397
|
Some(DestructElement::Pattern(p)) => self.emit_destruct_pattern(p),
|
|
344
|
-
Some(DestructElement::Rest(n)) => {
|
|
398
|
+
Some(DestructElement::Rest(n, _)) => {
|
|
345
399
|
Ok(format!("...{}", Self::escape_ident(n.as_ref())))
|
|
346
400
|
}
|
|
347
401
|
None => Ok("".to_string()),
|
|
@@ -355,7 +409,7 @@ impl Codegen {
|
|
|
355
409
|
.map(|p| {
|
|
356
410
|
let k = p.key.as_ref();
|
|
357
411
|
match &p.value {
|
|
358
|
-
DestructElement::Ident(n) => {
|
|
412
|
+
DestructElement::Ident(n, _) => {
|
|
359
413
|
if k == n.as_ref() {
|
|
360
414
|
Ok(k.to_string())
|
|
361
415
|
} else {
|
|
@@ -365,7 +419,7 @@ impl Codegen {
|
|
|
365
419
|
DestructElement::Pattern(pat) => {
|
|
366
420
|
Ok(format!("{}: {}", k, self.emit_destruct_pattern(pat)?))
|
|
367
421
|
}
|
|
368
|
-
DestructElement::Rest(n) => {
|
|
422
|
+
DestructElement::Rest(n, _) => {
|
|
369
423
|
Ok(format!("...{}", Self::escape_ident(n.as_ref())))
|
|
370
424
|
}
|
|
371
425
|
}
|
|
@@ -385,7 +439,9 @@ impl Codegen {
|
|
|
385
439
|
Literal::Null => "null".to_string(),
|
|
386
440
|
},
|
|
387
441
|
Expr::Ident { name, .. } => Self::escape_ident(name.as_ref()),
|
|
388
|
-
Expr::Binary {
|
|
442
|
+
Expr::Binary {
|
|
443
|
+
left, op, right, ..
|
|
444
|
+
} => {
|
|
389
445
|
let l = self.emit_expr(left)?;
|
|
390
446
|
let r = self.emit_expr(right)?;
|
|
391
447
|
let op_str = match op {
|
|
@@ -450,12 +506,14 @@ impl Codegen {
|
|
|
450
506
|
} => {
|
|
451
507
|
let obj = self.emit_expr(object)?;
|
|
452
508
|
let expr = match prop {
|
|
453
|
-
MemberProp::Name
|
|
454
|
-
if
|
|
455
|
-
|
|
509
|
+
MemberProp::Name { name, .. } => {
|
|
510
|
+
if name.parse::<u32>().is_ok()
|
|
511
|
+
|| !name.chars().all(|c| c.is_alphanumeric() || c == '_')
|
|
512
|
+
{
|
|
513
|
+
format!("{}[{:?}]", obj, name.as_ref())
|
|
456
514
|
} else {
|
|
457
515
|
let sep = if *optional { "?." } else { "." };
|
|
458
|
-
format!("{}{}{}", obj, sep,
|
|
516
|
+
format!("{}{}{}", obj, sep, name.as_ref())
|
|
459
517
|
}
|
|
460
518
|
}
|
|
461
519
|
MemberProp::Expr(e) => {
|
|
@@ -548,7 +606,9 @@ impl Codegen {
|
|
|
548
606
|
Expr::PrefixDec { name, .. } => {
|
|
549
607
|
format!("--{}", Self::escape_ident(name.as_ref()))
|
|
550
608
|
}
|
|
551
|
-
Expr::CompoundAssign {
|
|
609
|
+
Expr::CompoundAssign {
|
|
610
|
+
name, op, value, ..
|
|
611
|
+
} => {
|
|
552
612
|
let n = Self::escape_ident(name.as_ref());
|
|
553
613
|
let v = self.emit_expr(value)?;
|
|
554
614
|
let op_str = match op {
|
|
@@ -560,7 +620,9 @@ impl Codegen {
|
|
|
560
620
|
};
|
|
561
621
|
format!("({} {} {})", n, op_str, v)
|
|
562
622
|
}
|
|
563
|
-
Expr::LogicalAssign {
|
|
623
|
+
Expr::LogicalAssign {
|
|
624
|
+
name, op, value, ..
|
|
625
|
+
} => {
|
|
564
626
|
let n = Self::escape_ident(name.as_ref());
|
|
565
627
|
let v = self.emit_expr(value)?;
|
|
566
628
|
let op_str = match op {
|
|
@@ -570,7 +632,12 @@ impl Codegen {
|
|
|
570
632
|
};
|
|
571
633
|
format!("({} {} {})", n, op_str, v)
|
|
572
634
|
}
|
|
573
|
-
Expr::MemberAssign {
|
|
635
|
+
Expr::MemberAssign {
|
|
636
|
+
object,
|
|
637
|
+
prop,
|
|
638
|
+
value,
|
|
639
|
+
..
|
|
640
|
+
} => {
|
|
574
641
|
let obj = self.emit_expr(object)?;
|
|
575
642
|
let val = self.emit_expr(value)?;
|
|
576
643
|
format!("({}.{} = {})", obj, prop.as_ref(), val)
|
|
@@ -652,7 +719,6 @@ impl Codegen {
|
|
|
652
719
|
CallArg::Spread(e) => Ok(format!("...{}", self.emit_expr(e)?)),
|
|
653
720
|
}
|
|
654
721
|
}
|
|
655
|
-
|
|
656
722
|
}
|
|
657
723
|
|
|
658
724
|
/// Compile a single program (no imports) to JavaScript. JSX lowers to `h` / `Fragment` (Lattish).
|
|
@@ -663,29 +729,45 @@ pub fn compile_with_jsx(program: &Program, optimize: bool) -> Result<String, Com
|
|
|
663
729
|
program.clone()
|
|
664
730
|
};
|
|
665
731
|
let mut g = Codegen::new();
|
|
666
|
-
g.emit_program(&program)?;
|
|
732
|
+
g.emit_program(&program, None, None)?;
|
|
667
733
|
Ok(g.output)
|
|
668
734
|
}
|
|
669
735
|
|
|
670
|
-
///
|
|
671
|
-
|
|
672
|
-
pub
|
|
673
|
-
|
|
674
|
-
|
|
736
|
+
/// JavaScript plus optional v3 source map JSON (for publishing Tish libraries consumed from JS/TS).
|
|
737
|
+
#[derive(Debug, Clone)]
|
|
738
|
+
pub struct JsBundle {
|
|
739
|
+
pub js: String,
|
|
740
|
+
pub source_map_json: Option<String>,
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/// Same as [`compile_project_with_jsx`] plus a v3 source map pointing at merged statements’ original `.tish` files.
|
|
744
|
+
/// **Does not run AST optimization** (required so statement ↔ file alignment stays valid).
|
|
745
|
+
pub fn compile_project_with_jsx_and_source_map(
|
|
746
|
+
entry_path: &Path,
|
|
747
|
+
project_root: Option<&Path>,
|
|
748
|
+
output_js_file_name: &str,
|
|
749
|
+
) -> Result<JsBundle, CompileError> {
|
|
750
|
+
compile_project_js_inner(entry_path, project_root, false, true, output_js_file_name)
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
fn compile_project_js_inner(
|
|
754
|
+
entry_path: &Path,
|
|
755
|
+
project_root: Option<&Path>,
|
|
675
756
|
optimize: bool,
|
|
676
|
-
|
|
757
|
+
emit_source_map: bool,
|
|
758
|
+
output_js_file_name: &str,
|
|
759
|
+
) -> Result<JsBundle, CompileError> {
|
|
677
760
|
use tishlang_ast::Statement;
|
|
678
761
|
let modules = tishlang_compile::resolve_project(entry_path, project_root)
|
|
679
762
|
.map_err(|e| CompileError { message: e })?;
|
|
680
763
|
tishlang_compile::detect_cycles(&modules).map_err(|e| CompileError { message: e })?;
|
|
681
|
-
let
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
prog
|
|
687
|
-
}
|
|
764
|
+
let merged = tishlang_compile::merge_modules(modules).map_err(|e| CompileError { message: e })?;
|
|
765
|
+
let program = if optimize {
|
|
766
|
+
tishlang_opt::optimize(&merged.program)
|
|
767
|
+
} else {
|
|
768
|
+
merged.program.clone()
|
|
688
769
|
};
|
|
770
|
+
let stmt_sources = merged.statement_sources;
|
|
689
771
|
let default_export = program.statements.iter().find_map(|s| {
|
|
690
772
|
if let Statement::VarDecl { name, .. } = s {
|
|
691
773
|
let n = name.as_ref();
|
|
@@ -698,9 +780,71 @@ pub fn compile_project_with_jsx(
|
|
|
698
780
|
None
|
|
699
781
|
}
|
|
700
782
|
});
|
|
701
|
-
|
|
783
|
+
if emit_source_map && optimize {
|
|
784
|
+
return Err(CompileError {
|
|
785
|
+
message: "internal: source map requested with optimize".into(),
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
let root = project_root
|
|
789
|
+
.map(Path::to_path_buf)
|
|
790
|
+
.or_else(|| entry_path.parent().map(Path::to_path_buf))
|
|
791
|
+
.unwrap_or_else(|| PathBuf::from("."));
|
|
792
|
+
let mut gen = Codegen::new();
|
|
793
|
+
let mut map_builder = if emit_source_map {
|
|
794
|
+
let mut b = SourceMapBuilder::new(Some(output_js_file_name));
|
|
795
|
+
b.set_source_root(Some(""));
|
|
796
|
+
Some(b)
|
|
797
|
+
} else {
|
|
798
|
+
None
|
|
799
|
+
};
|
|
800
|
+
if let Some(ref mut b) = map_builder {
|
|
801
|
+
gen.emit_program(
|
|
802
|
+
&program,
|
|
803
|
+
Some((stmt_sources.as_slice(), root.as_path())),
|
|
804
|
+
Some(b),
|
|
805
|
+
)?;
|
|
806
|
+
} else {
|
|
807
|
+
gen.emit_program(&program, None, None)?;
|
|
808
|
+
}
|
|
809
|
+
let mut js = gen.output;
|
|
702
810
|
if let Some(name) = default_export {
|
|
703
811
|
js.push_str(&format!("\nexport default {};\n", name));
|
|
704
812
|
}
|
|
705
|
-
|
|
813
|
+
let map_json = if let Some(b) = map_builder {
|
|
814
|
+
let sm = b.into_sourcemap();
|
|
815
|
+
let mut v = Vec::new();
|
|
816
|
+
sm.to_writer(&mut v).map_err(|e| CompileError {
|
|
817
|
+
message: e.to_string(),
|
|
818
|
+
})?;
|
|
819
|
+
Some(String::from_utf8(v).map_err(|e| CompileError {
|
|
820
|
+
message: e.to_string(),
|
|
821
|
+
})?)
|
|
822
|
+
} else {
|
|
823
|
+
None
|
|
824
|
+
};
|
|
825
|
+
Ok(JsBundle {
|
|
826
|
+
js,
|
|
827
|
+
source_map_json: map_json,
|
|
828
|
+
})
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/// Compile a project from entry path, resolving and merging modules.
|
|
832
|
+
/// Uses shared resolve from tishlang_compile (same pipeline as native/WASM).
|
|
833
|
+
pub fn compile_project_with_jsx(
|
|
834
|
+
entry_path: &std::path::Path,
|
|
835
|
+
project_root: Option<&std::path::Path>,
|
|
836
|
+
optimize: bool,
|
|
837
|
+
) -> Result<String, CompileError> {
|
|
838
|
+
let stem = entry_path
|
|
839
|
+
.file_name()
|
|
840
|
+
.and_then(|s| s.to_str())
|
|
841
|
+
.unwrap_or("out.js");
|
|
842
|
+
let out_name = if stem.ends_with(".tish") {
|
|
843
|
+
format!("{}.js", stem.trim_end_matches(".tish"))
|
|
844
|
+
} else {
|
|
845
|
+
format!("{stem}.js")
|
|
846
|
+
};
|
|
847
|
+
Ok(
|
|
848
|
+
compile_project_js_inner(entry_path, project_root, optimize, false, &out_name)?.js,
|
|
849
|
+
)
|
|
706
850
|
}
|