@tishlang/tish 1.7.0 → 1.9.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/transform/expr.rs +28 -8
- package/crates/js_to_tish/src/transform/stmt.rs +49 -22
- package/crates/tish/Cargo.toml +14 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +16 -10
- package/crates/tish/src/main.rs +87 -32
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +1 -1
- package/crates/tish/tests/integration_test.rs +19 -7
- package/crates/tish/tests/shortcircuit.rs +1 -1
- package/crates/tish_ast/src/ast.rs +80 -9
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +105 -2
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +13 -12
- package/crates/tish_builtins/src/construct.rs +34 -33
- package/crates/tish_builtins/src/globals.rs +12 -11
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +73 -3
- package/crates/tish_bytecode/src/compiler.rs +12 -14
- package/crates/tish_bytecode/src/opcode.rs +12 -3
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +745 -199
- package/crates/tish_compile/src/infer.rs +6 -0
- package/crates/tish_compile/src/lib.rs +4 -3
- package/crates/tish_compile/src/resolve.rs +180 -82
- package/crates/tish_compile/src/types.rs +175 -11
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +152 -29
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +31 -12
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +102 -53
- package/crates/tish_core/src/lib.rs +3 -1
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/value.rs +53 -15
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +90 -28
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +3 -3
- package/crates/tish_eval/src/natives.rs +41 -0
- package/crates/tish_eval/src/value.rs +7 -3
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +120 -30
- package/crates/tish_lexer/src/lib.rs +20 -5
- package/crates/tish_lexer/src/token.rs +4 -0
- package/crates/tish_llvm/src/lib.rs +3 -1
- 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 +502 -102
- package/crates/tish_native/src/build.rs +3 -2
- package/crates/tish_native/src/lib.rs +6 -2
- package/crates/tish_opt/src/lib.rs +17 -2
- package/crates/tish_parser/src/lib.rs +10 -3
- package/crates/tish_parser/src/parser.rs +346 -56
- package/crates/tish_pg/Cargo.toml +34 -0
- package/crates/tish_pg/README.md +38 -0
- package/crates/tish_pg/src/error.rs +52 -0
- package/crates/tish_pg/src/lib.rs +967 -0
- 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 +1123 -141
- package/crates/tish_runtime/src/http_fetch.rs +15 -14
- 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 +159 -29
- package/crates/tish_runtime/src/promise.rs +199 -36
- 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 +26 -28
- package/crates/tish_ui/src/jsx.rs +279 -8
- 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 +506 -259
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +3 -1
- package/crates/tish_wasm/src/lib.rs +17 -14
- 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 +1 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +68 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +5 -4
- 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,19 +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) => RustType::Vec(Box::new(
|
|
86
|
+
TypeAnnotation::Array(elem) => RustType::Vec(Box::new(
|
|
87
|
+
Self::from_annotation_with_aliases(elem, aliases),
|
|
88
|
+
)),
|
|
49
89
|
TypeAnnotation::Object(fields) => {
|
|
50
90
|
let typed_fields: Vec<_> = fields
|
|
51
91
|
.iter()
|
|
52
|
-
.map(|(k, v)| (k.clone(), Self::
|
|
92
|
+
.map(|(k, v)| (k.clone(), Self::from_annotation_with_aliases(v, aliases)))
|
|
53
93
|
.collect();
|
|
54
94
|
RustType::Object(typed_fields)
|
|
55
95
|
}
|
|
56
96
|
TypeAnnotation::Function { params, returns } => {
|
|
57
|
-
let typed_params: Vec<_> = params
|
|
58
|
-
|
|
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));
|
|
59
102
|
RustType::Function {
|
|
60
103
|
params: typed_params,
|
|
61
104
|
returns: typed_returns,
|
|
@@ -72,7 +115,9 @@ impl RustType {
|
|
|
72
115
|
|t| !matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null"),
|
|
73
116
|
);
|
|
74
117
|
if let Some(inner) = non_null {
|
|
75
|
-
return RustType::Option(Box::new(
|
|
118
|
+
return RustType::Option(Box::new(
|
|
119
|
+
Self::from_annotation_with_aliases(inner, aliases),
|
|
120
|
+
));
|
|
76
121
|
}
|
|
77
122
|
}
|
|
78
123
|
}
|
|
@@ -130,9 +175,11 @@ impl RustType {
|
|
|
130
175
|
RustType::Vec(inner) => format!("Vec<{}>", inner.to_rust_type_str()),
|
|
131
176
|
RustType::Option(inner) => format!("Option<{}>", inner.to_rust_type_str()),
|
|
132
177
|
RustType::Object(_) => {
|
|
133
|
-
//
|
|
178
|
+
// Anonymous inline shapes don't have a Rust struct; fall
|
|
179
|
+
// back to the dynamic Value path.
|
|
134
180
|
"Value".to_string()
|
|
135
181
|
}
|
|
182
|
+
RustType::Named { name, .. } => named_struct_ident(name),
|
|
136
183
|
RustType::Function { params, returns } => {
|
|
137
184
|
let params_str: Vec<_> = params.iter().map(|p| p.to_rust_type_str()).collect();
|
|
138
185
|
format!(
|
|
@@ -155,6 +202,23 @@ impl RustType {
|
|
|
155
202
|
RustType::Vec(_) => "Vec::new()".to_string(),
|
|
156
203
|
RustType::Option(_) => "None".to_string(),
|
|
157
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
|
+
}
|
|
158
222
|
RustType::Function { .. } => "Value::Null".to_string(),
|
|
159
223
|
}
|
|
160
224
|
}
|
|
@@ -190,6 +254,22 @@ impl RustType {
|
|
|
190
254
|
value_expr, inner_conversion
|
|
191
255
|
)
|
|
192
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
|
+
}
|
|
193
273
|
_ => value_expr.to_string(), // Fallback
|
|
194
274
|
}
|
|
195
275
|
}
|
|
@@ -210,7 +290,7 @@ impl RustType {
|
|
|
210
290
|
_ => (".iter().cloned()", inner.to_value_expr("v")),
|
|
211
291
|
};
|
|
212
292
|
format!(
|
|
213
|
-
"Value::Array(
|
|
293
|
+
"Value::Array(VmRef::new({}{}.map(|v| {}).collect()))",
|
|
214
294
|
native_expr, iter_suffix, val_expr
|
|
215
295
|
)
|
|
216
296
|
}
|
|
@@ -221,11 +301,57 @@ impl RustType {
|
|
|
221
301
|
native_expr, inner_to_value
|
|
222
302
|
)
|
|
223
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
|
+
}
|
|
224
328
|
_ => native_expr.to_string(), // Fallback
|
|
225
329
|
}
|
|
226
330
|
}
|
|
227
331
|
}
|
|
228
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
|
+
|
|
229
355
|
/// Type context for tracking variable types during code generation.
|
|
230
356
|
#[derive(Debug, Clone, Default)]
|
|
231
357
|
pub struct TypeContext {
|
|
@@ -250,6 +376,23 @@ impl TypeContext {
|
|
|
250
376
|
self.scopes.pop();
|
|
251
377
|
}
|
|
252
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
|
+
|
|
253
396
|
/// Define a variable in the current scope.
|
|
254
397
|
pub fn define(&mut self, name: &str, ty: RustType) {
|
|
255
398
|
if let Some(scope) = self.scopes.last_mut() {
|
|
@@ -334,4 +477,25 @@ mod tests {
|
|
|
334
477
|
ctx.pop_scope();
|
|
335
478
|
assert_eq!(ctx.get_type("y"), RustType::Value); // y no longer visible
|
|
336
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
|
+
}
|
|
337
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,
|
|
@@ -45,6 +48,10 @@ impl Codegen {
|
|
|
45
48
|
self.output.push('\n');
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
fn output_line(&self) -> u32 {
|
|
52
|
+
self.output.as_bytes().iter().filter(|&&b| b == b'\n').count() as u32
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
fn escape_ident(s: &str) -> String {
|
|
49
56
|
let s = s.to_string();
|
|
50
57
|
if s == "await" || s == "default" {
|
|
@@ -54,10 +61,46 @@ impl Codegen {
|
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
|
|
57
|
-
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> {
|
|
58
70
|
self.write("// Generated by tishlang_compile_js\n");
|
|
59
|
-
|
|
60
|
-
|
|
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
|
+
}
|
|
61
104
|
}
|
|
62
105
|
Ok(())
|
|
63
106
|
}
|
|
@@ -301,6 +344,9 @@ impl Codegen {
|
|
|
301
344
|
Statement::Import { .. } | Statement::Export { .. } => {
|
|
302
345
|
// Resolved away by merge_modules
|
|
303
346
|
}
|
|
347
|
+
Statement::TypeAlias { .. }
|
|
348
|
+
| Statement::DeclareVar { .. }
|
|
349
|
+
| Statement::DeclareFun { .. } => {}
|
|
304
350
|
}
|
|
305
351
|
Ok(())
|
|
306
352
|
}
|
|
@@ -347,9 +393,9 @@ impl Codegen {
|
|
|
347
393
|
let parts: Vec<String> = elements
|
|
348
394
|
.iter()
|
|
349
395
|
.map(|el| match el {
|
|
350
|
-
Some(DestructElement::Ident(n)) => Ok(Self::escape_ident(n.as_ref())),
|
|
396
|
+
Some(DestructElement::Ident(n, _)) => Ok(Self::escape_ident(n.as_ref())),
|
|
351
397
|
Some(DestructElement::Pattern(p)) => self.emit_destruct_pattern(p),
|
|
352
|
-
Some(DestructElement::Rest(n)) => {
|
|
398
|
+
Some(DestructElement::Rest(n, _)) => {
|
|
353
399
|
Ok(format!("...{}", Self::escape_ident(n.as_ref())))
|
|
354
400
|
}
|
|
355
401
|
None => Ok("".to_string()),
|
|
@@ -363,7 +409,7 @@ impl Codegen {
|
|
|
363
409
|
.map(|p| {
|
|
364
410
|
let k = p.key.as_ref();
|
|
365
411
|
match &p.value {
|
|
366
|
-
DestructElement::Ident(n) => {
|
|
412
|
+
DestructElement::Ident(n, _) => {
|
|
367
413
|
if k == n.as_ref() {
|
|
368
414
|
Ok(k.to_string())
|
|
369
415
|
} else {
|
|
@@ -373,7 +419,7 @@ impl Codegen {
|
|
|
373
419
|
DestructElement::Pattern(pat) => {
|
|
374
420
|
Ok(format!("{}: {}", k, self.emit_destruct_pattern(pat)?))
|
|
375
421
|
}
|
|
376
|
-
DestructElement::Rest(n) => {
|
|
422
|
+
DestructElement::Rest(n, _) => {
|
|
377
423
|
Ok(format!("...{}", Self::escape_ident(n.as_ref())))
|
|
378
424
|
}
|
|
379
425
|
}
|
|
@@ -460,14 +506,14 @@ impl Codegen {
|
|
|
460
506
|
} => {
|
|
461
507
|
let obj = self.emit_expr(object)?;
|
|
462
508
|
let expr = match prop {
|
|
463
|
-
MemberProp::Name
|
|
464
|
-
if
|
|
465
|
-
|| !
|
|
509
|
+
MemberProp::Name { name, .. } => {
|
|
510
|
+
if name.parse::<u32>().is_ok()
|
|
511
|
+
|| !name.chars().all(|c| c.is_alphanumeric() || c == '_')
|
|
466
512
|
{
|
|
467
|
-
format!("{}[{:?}]", obj,
|
|
513
|
+
format!("{}[{:?}]", obj, name.as_ref())
|
|
468
514
|
} else {
|
|
469
515
|
let sep = if *optional { "?." } else { "." };
|
|
470
|
-
format!("{}{}{}", obj, sep,
|
|
516
|
+
format!("{}{}{}", obj, sep, name.as_ref())
|
|
471
517
|
}
|
|
472
518
|
}
|
|
473
519
|
MemberProp::Expr(e) => {
|
|
@@ -683,30 +729,45 @@ pub fn compile_with_jsx(program: &Program, optimize: bool) -> Result<String, Com
|
|
|
683
729
|
program.clone()
|
|
684
730
|
};
|
|
685
731
|
let mut g = Codegen::new();
|
|
686
|
-
g.emit_program(&program)?;
|
|
732
|
+
g.emit_program(&program, None, None)?;
|
|
687
733
|
Ok(g.output)
|
|
688
734
|
}
|
|
689
735
|
|
|
690
|
-
///
|
|
691
|
-
|
|
692
|
-
pub
|
|
693
|
-
|
|
694
|
-
|
|
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>,
|
|
695
756
|
optimize: bool,
|
|
696
|
-
|
|
757
|
+
emit_source_map: bool,
|
|
758
|
+
output_js_file_name: &str,
|
|
759
|
+
) -> Result<JsBundle, CompileError> {
|
|
697
760
|
use tishlang_ast::Statement;
|
|
698
761
|
let modules = tishlang_compile::resolve_project(entry_path, project_root)
|
|
699
762
|
.map_err(|e| CompileError { message: e })?;
|
|
700
763
|
tishlang_compile::detect_cycles(&modules).map_err(|e| CompileError { message: e })?;
|
|
701
|
-
let
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
} else {
|
|
707
|
-
prog
|
|
708
|
-
}
|
|
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()
|
|
709
769
|
};
|
|
770
|
+
let stmt_sources = merged.statement_sources;
|
|
710
771
|
let default_export = program.statements.iter().find_map(|s| {
|
|
711
772
|
if let Statement::VarDecl { name, .. } = s {
|
|
712
773
|
let n = name.as_ref();
|
|
@@ -719,9 +780,71 @@ pub fn compile_project_with_jsx(
|
|
|
719
780
|
None
|
|
720
781
|
}
|
|
721
782
|
});
|
|
722
|
-
|
|
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;
|
|
723
810
|
if let Some(name) = default_export {
|
|
724
811
|
js.push_str(&format!("\nexport default {};\n", name));
|
|
725
812
|
}
|
|
726
|
-
|
|
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
|
+
)
|
|
727
850
|
}
|
|
@@ -7,7 +7,9 @@ mod error;
|
|
|
7
7
|
#[cfg(test)]
|
|
8
8
|
mod tests_jsx;
|
|
9
9
|
|
|
10
|
-
pub use codegen::{
|
|
10
|
+
pub use codegen::{
|
|
11
|
+
compile_project_with_jsx, compile_project_with_jsx_and_source_map, compile_with_jsx, JsBundle,
|
|
12
|
+
};
|
|
11
13
|
pub use error::CompileError;
|
|
12
14
|
|
|
13
15
|
/// JSX lowers to `h` / `Fragment`; merge the `lattish` runtime for hooks and DOM.
|