@tishlang/tish 1.5.0 → 1.7.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 +1 -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 +101 -130
- package/crates/js_to_tish/src/transform/stmt.rs +25 -22
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/cli_help.rs +76 -29
- package/crates/tish/src/main.rs +85 -54
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
- package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
- package/crates/tish/tests/integration_test.rs +197 -47
- package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
- package/crates/tish/tests/shortcircuit.rs +19 -4
- package/crates/tish_ast/src/ast.rs +12 -14
- package/crates/tish_build_utils/src/lib.rs +64 -6
- package/crates/tish_builtins/src/array.rs +52 -21
- package/crates/tish_builtins/src/construct.rs +2 -8
- package/crates/tish_builtins/src/globals.rs +30 -15
- package/crates/tish_builtins/src/lib.rs +5 -5
- package/crates/tish_builtins/src/math.rs +5 -3
- package/crates/tish_builtins/src/string.rs +71 -19
- package/crates/tish_bytecode/src/chunk.rs +0 -1
- package/crates/tish_bytecode/src/compiler.rs +164 -60
- package/crates/tish_bytecode/src/opcode.rs +13 -4
- package/crates/tish_bytecode/src/peephole.rs +2 -2
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +989 -318
- package/crates/tish_compile/src/infer.rs +69 -19
- package/crates/tish_compile/src/lib.rs +21 -8
- package/crates/tish_compile/src/resolve.rs +515 -94
- package/crates/tish_compile/src/types.rs +10 -14
- package/crates/tish_compile_js/src/codegen.rs +34 -13
- 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 +40 -48
- package/crates/tish_core/src/json.rs +5 -3
- package/crates/tish_core/src/lib.rs +1 -1
- package/crates/tish_core/src/uri.rs +9 -6
- package/crates/tish_core/src/value.rs +92 -28
- package/crates/tish_cranelift/src/link.rs +6 -9
- package/crates/tish_cranelift/src/lower.rs +14 -8
- package/crates/tish_eval/src/eval.rs +398 -141
- package/crates/tish_eval/src/lib.rs +10 -6
- package/crates/tish_eval/src/natives.rs +95 -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 +10 -3
- package/crates/tish_fmt/src/lib.rs +29 -13
- package/crates/tish_lexer/src/lib.rs +217 -63
- package/crates/tish_lexer/src/token.rs +6 -6
- package/crates/tish_llvm/src/lib.rs +15 -8
- package/crates/tish_lsp/src/main.rs +41 -43
- package/crates/tish_native/src/build.rs +38 -15
- package/crates/tish_native/src/lib.rs +76 -32
- package/crates/tish_opt/src/lib.rs +67 -50
- package/crates/tish_parser/src/lib.rs +36 -11
- package/crates/tish_parser/src/parser.rs +172 -87
- package/crates/tish_runtime/src/http.rs +15 -6
- package/crates/tish_runtime/src/http_fetch.rs +24 -14
- package/crates/tish_runtime/src/lib.rs +224 -168
- package/crates/tish_runtime/src/promise.rs +1 -5
- package/crates/tish_runtime/src/ws.rs +45 -20
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
- package/crates/tish_ui/src/jsx.rs +41 -22
- package/crates/tish_ui/src/lib.rs +2 -2
- package/crates/tish_vm/src/vm.rs +320 -116
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +8 -3
- package/crates/tish_wasm/src/lib.rs +38 -28
- package/crates/tishlang_cargo_bindgen/Cargo.toml +25 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +52 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -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
|
@@ -45,9 +45,7 @@ impl RustType {
|
|
|
45
45
|
"any" => RustType::Value,
|
|
46
46
|
_ => RustType::Value, // Unknown types fall back to Value
|
|
47
47
|
},
|
|
48
|
-
TypeAnnotation::Array(elem) =>
|
|
49
|
-
RustType::Vec(Box::new(Self::from_annotation(elem)))
|
|
50
|
-
}
|
|
48
|
+
TypeAnnotation::Array(elem) => RustType::Vec(Box::new(Self::from_annotation(elem))),
|
|
51
49
|
TypeAnnotation::Object(fields) => {
|
|
52
50
|
let typed_fields: Vec<_> = fields
|
|
53
51
|
.iter()
|
|
@@ -66,13 +64,13 @@ impl RustType {
|
|
|
66
64
|
TypeAnnotation::Union(types) => {
|
|
67
65
|
// Check for T | null pattern -> Option<T>
|
|
68
66
|
if types.len() == 2 {
|
|
69
|
-
let has_null = types
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
let has_null = types
|
|
68
|
+
.iter()
|
|
69
|
+
.any(|t| matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null"));
|
|
72
70
|
if has_null {
|
|
73
|
-
let non_null = types.iter().find(
|
|
74
|
-
!matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null")
|
|
75
|
-
|
|
71
|
+
let non_null = types.iter().find(
|
|
72
|
+
|t| !matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null"),
|
|
73
|
+
);
|
|
76
74
|
if let Some(inner) = non_null {
|
|
77
75
|
return RustType::Option(Box::new(Self::from_annotation(inner)));
|
|
78
76
|
}
|
|
@@ -271,9 +269,7 @@ impl TypeContext {
|
|
|
271
269
|
|
|
272
270
|
/// Check if a variable is typed (has a non-Value type).
|
|
273
271
|
pub fn is_typed(&self, name: &str) -> bool {
|
|
274
|
-
self.lookup(name)
|
|
275
|
-
.map(|ty| ty.is_native())
|
|
276
|
-
.unwrap_or(false)
|
|
272
|
+
self.lookup(name).map(|ty| ty.is_native()).unwrap_or(false)
|
|
277
273
|
}
|
|
278
274
|
|
|
279
275
|
/// Get the type of a variable, defaulting to Value if not found.
|
|
@@ -329,12 +325,12 @@ mod tests {
|
|
|
329
325
|
ctx.define("x", RustType::F64);
|
|
330
326
|
assert_eq!(ctx.get_type("x"), RustType::F64);
|
|
331
327
|
assert!(ctx.is_typed("x"));
|
|
332
|
-
|
|
328
|
+
|
|
333
329
|
ctx.push_scope();
|
|
334
330
|
ctx.define("y", RustType::String);
|
|
335
331
|
assert_eq!(ctx.get_type("y"), RustType::String);
|
|
336
332
|
assert_eq!(ctx.get_type("x"), RustType::F64); // Can still see outer scope
|
|
337
|
-
|
|
333
|
+
|
|
338
334
|
ctx.pop_scope();
|
|
339
335
|
assert_eq!(ctx.get_type("y"), RustType::Value); // y no longer visible
|
|
340
336
|
}
|
|
@@ -16,7 +16,9 @@ struct Codegen {
|
|
|
16
16
|
fn stmt_terminates_switch(stmt: Option<&Statement>) -> bool {
|
|
17
17
|
matches!(
|
|
18
18
|
stmt,
|
|
19
|
-
Some(Statement::Break { .. })
|
|
19
|
+
Some(Statement::Break { .. })
|
|
20
|
+
| Some(Statement::Return { .. })
|
|
21
|
+
| Some(Statement::Throw { .. })
|
|
20
22
|
)
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -174,7 +176,12 @@ impl Codegen {
|
|
|
174
176
|
self.emit_statement(body)?;
|
|
175
177
|
self.indent -= 1;
|
|
176
178
|
}
|
|
177
|
-
Statement::ForOf {
|
|
179
|
+
Statement::ForOf {
|
|
180
|
+
name,
|
|
181
|
+
iterable,
|
|
182
|
+
body,
|
|
183
|
+
..
|
|
184
|
+
} => {
|
|
178
185
|
let escaped = Self::escape_ident(name.as_ref());
|
|
179
186
|
let it = self.emit_expr(iterable)?;
|
|
180
187
|
self.writeln(&format!("for (const {} of {})", escaped, it));
|
|
@@ -204,7 +211,10 @@ impl Codegen {
|
|
|
204
211
|
let async_prefix = if *async_ { "async " } else { "" };
|
|
205
212
|
let escaped = Self::escape_ident(name.as_ref());
|
|
206
213
|
let params_str = self.emit_params(params, rest_param.as_ref())?;
|
|
207
|
-
self.writeln(&format!(
|
|
214
|
+
self.writeln(&format!(
|
|
215
|
+
"{}function {} ({}) {{",
|
|
216
|
+
async_prefix, escaped, params_str
|
|
217
|
+
));
|
|
208
218
|
self.indent += 1;
|
|
209
219
|
if *async_ {
|
|
210
220
|
self.in_async = true;
|
|
@@ -337,9 +347,7 @@ impl Codegen {
|
|
|
337
347
|
let parts: Vec<String> = elements
|
|
338
348
|
.iter()
|
|
339
349
|
.map(|el| match el {
|
|
340
|
-
Some(DestructElement::Ident(n)) =>
|
|
341
|
-
Ok(Self::escape_ident(n.as_ref()))
|
|
342
|
-
}
|
|
350
|
+
Some(DestructElement::Ident(n)) => Ok(Self::escape_ident(n.as_ref())),
|
|
343
351
|
Some(DestructElement::Pattern(p)) => self.emit_destruct_pattern(p),
|
|
344
352
|
Some(DestructElement::Rest(n)) => {
|
|
345
353
|
Ok(format!("...{}", Self::escape_ident(n.as_ref())))
|
|
@@ -385,7 +393,9 @@ impl Codegen {
|
|
|
385
393
|
Literal::Null => "null".to_string(),
|
|
386
394
|
},
|
|
387
395
|
Expr::Ident { name, .. } => Self::escape_ident(name.as_ref()),
|
|
388
|
-
Expr::Binary {
|
|
396
|
+
Expr::Binary {
|
|
397
|
+
left, op, right, ..
|
|
398
|
+
} => {
|
|
389
399
|
let l = self.emit_expr(left)?;
|
|
390
400
|
let r = self.emit_expr(right)?;
|
|
391
401
|
let op_str = match op {
|
|
@@ -451,7 +461,9 @@ impl Codegen {
|
|
|
451
461
|
let obj = self.emit_expr(object)?;
|
|
452
462
|
let expr = match prop {
|
|
453
463
|
MemberProp::Name(p) => {
|
|
454
|
-
if p.parse::<u32>().is_ok()
|
|
464
|
+
if p.parse::<u32>().is_ok()
|
|
465
|
+
|| !p.chars().all(|c| c.is_alphanumeric() || c == '_')
|
|
466
|
+
{
|
|
455
467
|
format!("{}[{:?}]", obj, p.as_ref())
|
|
456
468
|
} else {
|
|
457
469
|
let sep = if *optional { "?." } else { "." };
|
|
@@ -548,7 +560,9 @@ impl Codegen {
|
|
|
548
560
|
Expr::PrefixDec { name, .. } => {
|
|
549
561
|
format!("--{}", Self::escape_ident(name.as_ref()))
|
|
550
562
|
}
|
|
551
|
-
Expr::CompoundAssign {
|
|
563
|
+
Expr::CompoundAssign {
|
|
564
|
+
name, op, value, ..
|
|
565
|
+
} => {
|
|
552
566
|
let n = Self::escape_ident(name.as_ref());
|
|
553
567
|
let v = self.emit_expr(value)?;
|
|
554
568
|
let op_str = match op {
|
|
@@ -560,7 +574,9 @@ impl Codegen {
|
|
|
560
574
|
};
|
|
561
575
|
format!("({} {} {})", n, op_str, v)
|
|
562
576
|
}
|
|
563
|
-
Expr::LogicalAssign {
|
|
577
|
+
Expr::LogicalAssign {
|
|
578
|
+
name, op, value, ..
|
|
579
|
+
} => {
|
|
564
580
|
let n = Self::escape_ident(name.as_ref());
|
|
565
581
|
let v = self.emit_expr(value)?;
|
|
566
582
|
let op_str = match op {
|
|
@@ -570,7 +586,12 @@ impl Codegen {
|
|
|
570
586
|
};
|
|
571
587
|
format!("({} {} {})", n, op_str, v)
|
|
572
588
|
}
|
|
573
|
-
Expr::MemberAssign {
|
|
589
|
+
Expr::MemberAssign {
|
|
590
|
+
object,
|
|
591
|
+
prop,
|
|
592
|
+
value,
|
|
593
|
+
..
|
|
594
|
+
} => {
|
|
574
595
|
let obj = self.emit_expr(object)?;
|
|
575
596
|
let val = self.emit_expr(value)?;
|
|
576
597
|
format!("({}.{} = {})", obj, prop.as_ref(), val)
|
|
@@ -652,7 +673,6 @@ impl Codegen {
|
|
|
652
673
|
CallArg::Spread(e) => Ok(format!("...{}", self.emit_expr(e)?)),
|
|
653
674
|
}
|
|
654
675
|
}
|
|
655
|
-
|
|
656
676
|
}
|
|
657
677
|
|
|
658
678
|
/// Compile a single program (no imports) to JavaScript. JSX lowers to `h` / `Fragment` (Lattish).
|
|
@@ -679,7 +699,8 @@ pub fn compile_project_with_jsx(
|
|
|
679
699
|
.map_err(|e| CompileError { message: e })?;
|
|
680
700
|
tishlang_compile::detect_cycles(&modules).map_err(|e| CompileError { message: e })?;
|
|
681
701
|
let program = {
|
|
682
|
-
let prog =
|
|
702
|
+
let prog =
|
|
703
|
+
tishlang_compile::merge_modules(modules).map_err(|e| CompileError { message: e })?;
|
|
683
704
|
if optimize {
|
|
684
705
|
tishlang_opt::optimize(&prog)
|
|
685
706
|
} else {
|
|
@@ -11,7 +11,11 @@ mod tests {
|
|
|
11
11
|
let src = r#"fn X() { return <div class="a">{"hi"}</div> }"#;
|
|
12
12
|
let program = parse(src).unwrap();
|
|
13
13
|
let js = compile_with_jsx(&program, false).unwrap();
|
|
14
|
-
assert!(
|
|
14
|
+
assert!(
|
|
15
|
+
js.contains("h(\"div\", { class: \"a\" }, [\"hi\"])"),
|
|
16
|
+
"{}",
|
|
17
|
+
js
|
|
18
|
+
);
|
|
15
19
|
assert!(!js.contains("function __h("));
|
|
16
20
|
}
|
|
17
21
|
|
|
@@ -57,7 +61,11 @@ mod tests {
|
|
|
57
61
|
let src = r#"fn X() { return <p>work!</p> }"#;
|
|
58
62
|
let program = parse(src).unwrap();
|
|
59
63
|
let js = compile_with_jsx(&program, false).unwrap();
|
|
60
|
-
assert!(
|
|
64
|
+
assert!(
|
|
65
|
+
js.contains(r#""work!""#),
|
|
66
|
+
"expected 'work!', got: {}",
|
|
67
|
+
&js[..400.min(js.len())]
|
|
68
|
+
);
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
#[test]
|
|
@@ -65,7 +73,11 @@ mod tests {
|
|
|
65
73
|
let src = r#"fn X() { return <p>hello 😔</p> }"#;
|
|
66
74
|
let program = parse(src).unwrap();
|
|
67
75
|
let js = compile_with_jsx(&program, false).unwrap();
|
|
68
|
-
assert!(
|
|
76
|
+
assert!(
|
|
77
|
+
js.contains("😔"),
|
|
78
|
+
"expected emoji, got: {}",
|
|
79
|
+
&js[..400.min(js.len())]
|
|
80
|
+
);
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
#[test]
|
|
@@ -93,10 +105,22 @@ mod tests {
|
|
|
93
105
|
let src = r#"fn X() { return <div class="x">{"a"}</div> }"#;
|
|
94
106
|
let program = parse(src).unwrap();
|
|
95
107
|
let js = compile_with_jsx(&program, false).unwrap();
|
|
96
|
-
assert!(
|
|
108
|
+
assert!(
|
|
109
|
+
js.contains("h(\"div\", { class: \"x\" }"),
|
|
110
|
+
"{}",
|
|
111
|
+
&js[..500.min(js.len())]
|
|
112
|
+
);
|
|
97
113
|
assert!(!js.contains("__vdom_h"), "{}", &js[..600.min(js.len())]);
|
|
98
|
-
assert!(
|
|
99
|
-
|
|
114
|
+
assert!(
|
|
115
|
+
!js.contains("window.__LATTISH_JSX_VDOM"),
|
|
116
|
+
"{}",
|
|
117
|
+
&js[..600.min(js.len())]
|
|
118
|
+
);
|
|
119
|
+
assert!(
|
|
120
|
+
!js.contains("__lattishVdomPatch"),
|
|
121
|
+
"{}",
|
|
122
|
+
&js[..600.min(js.len())]
|
|
123
|
+
);
|
|
100
124
|
}
|
|
101
125
|
|
|
102
126
|
/// Component calls like {Panel()} return DOM elements. Wrapping in String() produces [object HTMLDivElement].
|
|
@@ -14,29 +14,34 @@ use wasm_bindgen::prelude::*;
|
|
|
14
14
|
|
|
15
15
|
#[wasm_bindgen]
|
|
16
16
|
pub fn compile_to_bytecode(source: &str) -> Result<String, JsValue> {
|
|
17
|
-
let program =
|
|
17
|
+
let program =
|
|
18
|
+
tishlang_parser::parse(source.trim()).map_err(|e| JsValue::from_str(&e.to_string()))?;
|
|
18
19
|
let program = tishlang_opt::optimize(&program);
|
|
19
|
-
let chunk =
|
|
20
|
+
let chunk =
|
|
21
|
+
tishlang_bytecode::compile(&program).map_err(|e| JsValue::from_str(&e.to_string()))?;
|
|
20
22
|
Ok(base64::engine::general_purpose::STANDARD.encode(tishlang_bytecode::serialize(&chunk)))
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
#[wasm_bindgen]
|
|
24
26
|
pub fn compile_to_js(source: &str) -> Result<String, JsValue> {
|
|
25
|
-
let program =
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
let program =
|
|
28
|
+
tishlang_parser::parse(source.trim()).map_err(|e| JsValue::from_str(&e.to_string()))?;
|
|
29
|
+
tishlang_compile_js::compile_with_jsx(&program, true).map_err(|e| JsValue::from_str(&e.message))
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
#[wasm_bindgen]
|
|
31
|
-
pub fn compile_to_bytecode_with_imports(
|
|
33
|
+
pub fn compile_to_bytecode_with_imports(
|
|
34
|
+
entry_path: &str,
|
|
35
|
+
files_json: &str,
|
|
36
|
+
) -> Result<String, JsValue> {
|
|
32
37
|
let files: HashMap<String, String> = serde_json::from_str(files_json)
|
|
33
38
|
.map_err(|e| JsValue::from_str(&format!("Invalid files JSON: {}", e)))?;
|
|
34
|
-
let modules = resolve_virtual(entry_path, &files)
|
|
35
|
-
.map_err(|e| JsValue::from_str(&e))?;
|
|
39
|
+
let modules = resolve_virtual(entry_path, &files).map_err(|e| JsValue::from_str(&e))?;
|
|
36
40
|
detect_cycles_virtual(&modules).map_err(|e| JsValue::from_str(&e))?;
|
|
37
41
|
let program = merge_modules_virtual(modules).map_err(|e| JsValue::from_str(&e))?;
|
|
38
42
|
let program = tishlang_opt::optimize(&program);
|
|
39
|
-
let chunk =
|
|
43
|
+
let chunk =
|
|
44
|
+
tishlang_bytecode::compile(&program).map_err(|e| JsValue::from_str(&e.to_string()))?;
|
|
40
45
|
Ok(base64::engine::general_purpose::STANDARD.encode(tishlang_bytecode::serialize(&chunk)))
|
|
41
46
|
}
|
|
42
47
|
|
|
@@ -44,11 +49,9 @@ pub fn compile_to_bytecode_with_imports(entry_path: &str, files_json: &str) -> R
|
|
|
44
49
|
pub fn compile_to_js_with_imports(entry_path: &str, files_json: &str) -> Result<String, JsValue> {
|
|
45
50
|
let files: HashMap<String, String> = serde_json::from_str(files_json)
|
|
46
51
|
.map_err(|e| JsValue::from_str(&format!("Invalid files JSON: {}", e)))?;
|
|
47
|
-
let modules = resolve_virtual(entry_path, &files)
|
|
48
|
-
.map_err(|e| JsValue::from_str(&e))?;
|
|
52
|
+
let modules = resolve_virtual(entry_path, &files).map_err(|e| JsValue::from_str(&e))?;
|
|
49
53
|
detect_cycles_virtual(&modules).map_err(|e| JsValue::from_str(&e))?;
|
|
50
54
|
let program = merge_modules_virtual(modules).map_err(|e| JsValue::from_str(&e))?;
|
|
51
55
|
let program = tishlang_opt::optimize(&program);
|
|
52
|
-
tishlang_compile_js::compile_with_jsx(&program, true)
|
|
53
|
-
.map_err(|e| JsValue::from_str(&e.message))
|
|
56
|
+
tishlang_compile_js::compile_with_jsx(&program, true).map_err(|e| JsValue::from_str(&e.message))
|
|
54
57
|
}
|
|
@@ -33,6 +33,7 @@ fn normalize_builtin_spec(spec: &str) -> Option<String> {
|
|
|
33
33
|
|
|
34
34
|
fn is_native_import(spec: &str) -> bool {
|
|
35
35
|
spec.starts_with("tish:")
|
|
36
|
+
|| spec.starts_with("cargo:")
|
|
36
37
|
|| spec.starts_with('@')
|
|
37
38
|
|| matches!(spec, "fs" | "http" | "process" | "ws")
|
|
38
39
|
}
|
|
@@ -108,10 +109,16 @@ pub fn resolve_virtual(
|
|
|
108
109
|
if files.contains_key(&with_ext) {
|
|
109
110
|
with_ext
|
|
110
111
|
} else {
|
|
111
|
-
return Err(format!(
|
|
112
|
+
return Err(format!(
|
|
113
|
+
"Entry file '{}' not in virtual file map",
|
|
114
|
+
entry_path
|
|
115
|
+
));
|
|
112
116
|
}
|
|
113
117
|
} else {
|
|
114
|
-
return Err(format!(
|
|
118
|
+
return Err(format!(
|
|
119
|
+
"Entry file '{}' not in virtual file map",
|
|
120
|
+
entry_path
|
|
121
|
+
));
|
|
115
122
|
};
|
|
116
123
|
|
|
117
124
|
let mut visited = HashSet::new();
|
|
@@ -147,9 +154,9 @@ fn load_module_recursive(
|
|
|
147
154
|
}
|
|
148
155
|
visited.insert(module_path.to_string());
|
|
149
156
|
|
|
150
|
-
let source = files
|
|
151
|
-
|
|
152
|
-
|
|
157
|
+
let source = files
|
|
158
|
+
.get(module_path)
|
|
159
|
+
.ok_or_else(|| format!("Module '{}' not in virtual file map", module_path))?;
|
|
153
160
|
let program = tishlang_parser::parse(source.trim())
|
|
154
161
|
.map_err(|e| format!("Parse error in {}: {}", module_path, e))?;
|
|
155
162
|
|
|
@@ -161,13 +168,7 @@ fn load_module_recursive(
|
|
|
161
168
|
}
|
|
162
169
|
let dep_key = resolve_import_to_key(from, from_dir, files)?;
|
|
163
170
|
if !path_to_module.contains_key(&dep_key) {
|
|
164
|
-
load_module_recursive(
|
|
165
|
-
&dep_key,
|
|
166
|
-
files,
|
|
167
|
-
visited,
|
|
168
|
-
path_to_module,
|
|
169
|
-
load_order,
|
|
170
|
-
)?;
|
|
171
|
+
load_module_recursive(&dep_key, files, visited, path_to_module, load_order)?;
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
}
|
|
@@ -196,11 +197,11 @@ pub fn detect_cycles_virtual(modules: &[VirtualModule]) -> Result<(), String> {
|
|
|
196
197
|
&mut stack,
|
|
197
198
|
&mut HashSet::new(),
|
|
198
199
|
)? {
|
|
199
|
-
let path_names: Vec<_> = stack
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
.
|
|
203
|
-
|
|
200
|
+
let path_names: Vec<_> = stack.iter().map(|&i| modules[i].path.clone()).collect();
|
|
201
|
+
return Err(format!(
|
|
202
|
+
"Circular import detected: {}",
|
|
203
|
+
path_names.join(" -> ")
|
|
204
|
+
));
|
|
204
205
|
}
|
|
205
206
|
}
|
|
206
207
|
Ok(())
|
|
@@ -230,14 +231,8 @@ fn has_cycle_from(
|
|
|
230
231
|
stack.push(dep_idx);
|
|
231
232
|
let dep = &modules[dep_idx];
|
|
232
233
|
let dep_dir = parent_dir(&dep.path);
|
|
233
|
-
if has_cycle_from(
|
|
234
|
-
|
|
235
|
-
&dep.program,
|
|
236
|
-
path_to_idx,
|
|
237
|
-
modules,
|
|
238
|
-
stack,
|
|
239
|
-
visiting,
|
|
240
|
-
)? {
|
|
234
|
+
if has_cycle_from(dep_dir, &dep.program, path_to_idx, modules, stack, visiting)?
|
|
235
|
+
{
|
|
241
236
|
return Ok(true);
|
|
242
237
|
}
|
|
243
238
|
stack.pop();
|
|
@@ -306,7 +301,11 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
|
|
|
306
301
|
let dir = parent_dir(&module.path);
|
|
307
302
|
for stmt in &module.program.statements {
|
|
308
303
|
match stmt {
|
|
309
|
-
Statement::Import {
|
|
304
|
+
Statement::Import {
|
|
305
|
+
specifiers,
|
|
306
|
+
from,
|
|
307
|
+
span,
|
|
308
|
+
} => {
|
|
310
309
|
if is_native_import(from) {
|
|
311
310
|
let canonical_spec =
|
|
312
311
|
normalize_builtin_spec(from).unwrap_or_else(|| from.to_string());
|
|
@@ -386,18 +385,13 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
|
|
|
386
385
|
name: ns.clone(),
|
|
387
386
|
mutable: false,
|
|
388
387
|
type_ann: None,
|
|
389
|
-
init: Some(Expr::Object {
|
|
390
|
-
props,
|
|
391
|
-
span: *span,
|
|
392
|
-
}),
|
|
388
|
+
init: Some(Expr::Object { props, span: *span }),
|
|
393
389
|
span: *span,
|
|
394
390
|
});
|
|
395
391
|
}
|
|
396
392
|
ImportSpecifier::Default(bind) => {
|
|
397
|
-
let source =
|
|
398
|
-
.get("default")
|
|
399
|
-
.cloned()
|
|
400
|
-
.ok_or_else(|| {
|
|
393
|
+
let source =
|
|
394
|
+
dep_exports.get("default").cloned().ok_or_else(|| {
|
|
401
395
|
format!("Module '{}' has no default export", from)
|
|
402
396
|
})?;
|
|
403
397
|
statements.push(Statement::VarDecl {
|
|
@@ -414,21 +408,19 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
|
|
|
414
408
|
}
|
|
415
409
|
}
|
|
416
410
|
}
|
|
417
|
-
Statement::Export { declaration, .. } => {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
});
|
|
429
|
-
}
|
|
411
|
+
Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
412
|
+
ExportDeclaration::Named(s) => statements.push(*s.clone()),
|
|
413
|
+
ExportDeclaration::Default(e) => {
|
|
414
|
+
let default_name = format!("__default_{}", idx);
|
|
415
|
+
statements.push(Statement::VarDecl {
|
|
416
|
+
name: Arc::from(default_name),
|
|
417
|
+
mutable: false,
|
|
418
|
+
type_ann: None,
|
|
419
|
+
init: Some((*e).clone()),
|
|
420
|
+
span: e.span(),
|
|
421
|
+
});
|
|
430
422
|
}
|
|
431
|
-
}
|
|
423
|
+
},
|
|
432
424
|
_ => statements.push(stmt.clone()),
|
|
433
425
|
}
|
|
434
426
|
}
|
|
@@ -228,7 +228,7 @@ fn parse_number(input: &str) -> Result<(Value, &str), String> {
|
|
|
228
228
|
|
|
229
229
|
let num_str: String = chars[..end].iter().collect();
|
|
230
230
|
let byte_len: usize = chars[..end].iter().map(|c| c.len_utf8()).sum();
|
|
231
|
-
|
|
231
|
+
|
|
232
232
|
num_str
|
|
233
233
|
.parse::<f64>()
|
|
234
234
|
.map(|n| (Value::Number(n), &input[byte_len..]))
|
|
@@ -306,7 +306,9 @@ mod tests {
|
|
|
306
306
|
assert!(matches!(json_parse("true").unwrap(), Value::Bool(true)));
|
|
307
307
|
assert!(matches!(json_parse("false").unwrap(), Value::Bool(false)));
|
|
308
308
|
assert!(matches!(json_parse("42").unwrap(), Value::Number(n) if n == 42.0));
|
|
309
|
-
assert!(
|
|
309
|
+
assert!(
|
|
310
|
+
matches!(json_parse("\"hello\"").unwrap(), Value::String(s) if s.as_ref() == "hello")
|
|
311
|
+
);
|
|
310
312
|
}
|
|
311
313
|
|
|
312
314
|
#[test]
|
|
@@ -315,7 +317,7 @@ mod tests {
|
|
|
315
317
|
let value = json_parse(original).unwrap();
|
|
316
318
|
let stringified = json_stringify(&value);
|
|
317
319
|
let reparsed = json_parse(&stringified).unwrap();
|
|
318
|
-
|
|
320
|
+
|
|
319
321
|
match (&value, &reparsed) {
|
|
320
322
|
(Value::Object(a), Value::Object(b)) => {
|
|
321
323
|
assert_eq!(a.borrow().len(), b.borrow().len());
|
|
@@ -10,13 +10,13 @@ pub fn percent_decode(input: &str) -> Result<String, String> {
|
|
|
10
10
|
"%2F", "%2f", // /
|
|
11
11
|
"%3F", "%3f", // ?
|
|
12
12
|
"%3A", "%3a", // :
|
|
13
|
-
"%40",
|
|
14
|
-
"%26",
|
|
13
|
+
"%40", // @
|
|
14
|
+
"%26", // &
|
|
15
15
|
"%3D", "%3d", // =
|
|
16
16
|
"%2B", "%2b", // +
|
|
17
|
-
"%24",
|
|
17
|
+
"%24", // $
|
|
18
18
|
"%2C", "%2c", // ,
|
|
19
|
-
"%23",
|
|
19
|
+
"%23", // #
|
|
20
20
|
];
|
|
21
21
|
|
|
22
22
|
let mut result = String::with_capacity(input.len());
|
|
@@ -46,11 +46,14 @@ pub fn percent_decode(input: &str) -> Result<String, String> {
|
|
|
46
46
|
None => return Err("URIError: malformed URI sequence".to_string()),
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
if hex.len() == 2 {
|
|
51
51
|
let encoded = format!("%{}", hex);
|
|
52
52
|
// Check if this is a reserved character that should NOT be decoded
|
|
53
|
-
if RESERVED_ENCODED
|
|
53
|
+
if RESERVED_ENCODED
|
|
54
|
+
.iter()
|
|
55
|
+
.any(|r| r.eq_ignore_ascii_case(&encoded))
|
|
56
|
+
{
|
|
54
57
|
result.push_str(&encoded);
|
|
55
58
|
} else if let Ok(byte) = u8::from_str_radix(&hex, 16) {
|
|
56
59
|
result.push(byte as char);
|