@tishlang/tish 1.6.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 +3 -1
- 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 +31 -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/src/codegen.rs +921 -299
- package/crates/tish_compile/src/infer.rs +69 -19
- package/crates/tish_compile/src/lib.rs +15 -5
- package/crates/tish_compile/src/resolve.rs +112 -69
- 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 +39 -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 +389 -142
- 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 +1 -6
- package/crates/tish_native/src/lib.rs +48 -19
- 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 +309 -112
- 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
|
@@ -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
|
}
|
|
@@ -109,10 +109,16 @@ pub fn resolve_virtual(
|
|
|
109
109
|
if files.contains_key(&with_ext) {
|
|
110
110
|
with_ext
|
|
111
111
|
} else {
|
|
112
|
-
return Err(format!(
|
|
112
|
+
return Err(format!(
|
|
113
|
+
"Entry file '{}' not in virtual file map",
|
|
114
|
+
entry_path
|
|
115
|
+
));
|
|
113
116
|
}
|
|
114
117
|
} else {
|
|
115
|
-
return Err(format!(
|
|
118
|
+
return Err(format!(
|
|
119
|
+
"Entry file '{}' not in virtual file map",
|
|
120
|
+
entry_path
|
|
121
|
+
));
|
|
116
122
|
};
|
|
117
123
|
|
|
118
124
|
let mut visited = HashSet::new();
|
|
@@ -148,9 +154,9 @@ fn load_module_recursive(
|
|
|
148
154
|
}
|
|
149
155
|
visited.insert(module_path.to_string());
|
|
150
156
|
|
|
151
|
-
let source = files
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
let source = files
|
|
158
|
+
.get(module_path)
|
|
159
|
+
.ok_or_else(|| format!("Module '{}' not in virtual file map", module_path))?;
|
|
154
160
|
let program = tishlang_parser::parse(source.trim())
|
|
155
161
|
.map_err(|e| format!("Parse error in {}: {}", module_path, e))?;
|
|
156
162
|
|
|
@@ -162,13 +168,7 @@ fn load_module_recursive(
|
|
|
162
168
|
}
|
|
163
169
|
let dep_key = resolve_import_to_key(from, from_dir, files)?;
|
|
164
170
|
if !path_to_module.contains_key(&dep_key) {
|
|
165
|
-
load_module_recursive(
|
|
166
|
-
&dep_key,
|
|
167
|
-
files,
|
|
168
|
-
visited,
|
|
169
|
-
path_to_module,
|
|
170
|
-
load_order,
|
|
171
|
-
)?;
|
|
171
|
+
load_module_recursive(&dep_key, files, visited, path_to_module, load_order)?;
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
}
|
|
@@ -197,11 +197,11 @@ pub fn detect_cycles_virtual(modules: &[VirtualModule]) -> Result<(), String> {
|
|
|
197
197
|
&mut stack,
|
|
198
198
|
&mut HashSet::new(),
|
|
199
199
|
)? {
|
|
200
|
-
let path_names: Vec<_> = stack
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
.
|
|
204
|
-
|
|
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
|
+
));
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
Ok(())
|
|
@@ -231,14 +231,8 @@ fn has_cycle_from(
|
|
|
231
231
|
stack.push(dep_idx);
|
|
232
232
|
let dep = &modules[dep_idx];
|
|
233
233
|
let dep_dir = parent_dir(&dep.path);
|
|
234
|
-
if has_cycle_from(
|
|
235
|
-
|
|
236
|
-
&dep.program,
|
|
237
|
-
path_to_idx,
|
|
238
|
-
modules,
|
|
239
|
-
stack,
|
|
240
|
-
visiting,
|
|
241
|
-
)? {
|
|
234
|
+
if has_cycle_from(dep_dir, &dep.program, path_to_idx, modules, stack, visiting)?
|
|
235
|
+
{
|
|
242
236
|
return Ok(true);
|
|
243
237
|
}
|
|
244
238
|
stack.pop();
|
|
@@ -307,7 +301,11 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
|
|
|
307
301
|
let dir = parent_dir(&module.path);
|
|
308
302
|
for stmt in &module.program.statements {
|
|
309
303
|
match stmt {
|
|
310
|
-
Statement::Import {
|
|
304
|
+
Statement::Import {
|
|
305
|
+
specifiers,
|
|
306
|
+
from,
|
|
307
|
+
span,
|
|
308
|
+
} => {
|
|
311
309
|
if is_native_import(from) {
|
|
312
310
|
let canonical_spec =
|
|
313
311
|
normalize_builtin_spec(from).unwrap_or_else(|| from.to_string());
|
|
@@ -387,18 +385,13 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
|
|
|
387
385
|
name: ns.clone(),
|
|
388
386
|
mutable: false,
|
|
389
387
|
type_ann: None,
|
|
390
|
-
init: Some(Expr::Object {
|
|
391
|
-
props,
|
|
392
|
-
span: *span,
|
|
393
|
-
}),
|
|
388
|
+
init: Some(Expr::Object { props, span: *span }),
|
|
394
389
|
span: *span,
|
|
395
390
|
});
|
|
396
391
|
}
|
|
397
392
|
ImportSpecifier::Default(bind) => {
|
|
398
|
-
let source =
|
|
399
|
-
.get("default")
|
|
400
|
-
.cloned()
|
|
401
|
-
.ok_or_else(|| {
|
|
393
|
+
let source =
|
|
394
|
+
dep_exports.get("default").cloned().ok_or_else(|| {
|
|
402
395
|
format!("Module '{}' has no default export", from)
|
|
403
396
|
})?;
|
|
404
397
|
statements.push(Statement::VarDecl {
|
|
@@ -415,21 +408,19 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
|
|
|
415
408
|
}
|
|
416
409
|
}
|
|
417
410
|
}
|
|
418
|
-
Statement::Export { declaration, .. } => {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
});
|
|
430
|
-
}
|
|
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
|
+
});
|
|
431
422
|
}
|
|
432
|
-
}
|
|
423
|
+
},
|
|
433
424
|
_ => statements.push(stmt.clone()),
|
|
434
425
|
}
|
|
435
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);
|
|
@@ -54,29 +54,70 @@ impl RegExpFlags {
|
|
|
54
54
|
let mut result = Self::default();
|
|
55
55
|
for c in flags.chars() {
|
|
56
56
|
match c {
|
|
57
|
-
'g' => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
'g' => {
|
|
58
|
+
if result.global {
|
|
59
|
+
return Err(format!("duplicate flag '{}'", c));
|
|
60
|
+
}
|
|
61
|
+
result.global = true;
|
|
62
|
+
}
|
|
63
|
+
'i' => {
|
|
64
|
+
if result.ignore_case {
|
|
65
|
+
return Err(format!("duplicate flag '{}'", c));
|
|
66
|
+
}
|
|
67
|
+
result.ignore_case = true;
|
|
68
|
+
}
|
|
69
|
+
'm' => {
|
|
70
|
+
if result.multiline {
|
|
71
|
+
return Err(format!("duplicate flag '{}'", c));
|
|
72
|
+
}
|
|
73
|
+
result.multiline = true;
|
|
74
|
+
}
|
|
75
|
+
's' => {
|
|
76
|
+
if result.dot_all {
|
|
77
|
+
return Err(format!("duplicate flag '{}'", c));
|
|
78
|
+
}
|
|
79
|
+
result.dot_all = true;
|
|
80
|
+
}
|
|
81
|
+
'u' => {
|
|
82
|
+
if result.unicode {
|
|
83
|
+
return Err(format!("duplicate flag '{}'", c));
|
|
84
|
+
}
|
|
85
|
+
result.unicode = true;
|
|
86
|
+
}
|
|
87
|
+
'y' => {
|
|
88
|
+
if result.sticky {
|
|
89
|
+
return Err(format!("duplicate flag '{}'", c));
|
|
90
|
+
}
|
|
91
|
+
result.sticky = true;
|
|
92
|
+
}
|
|
63
93
|
_ => return Err(format!("unknown flag '{}'", c)),
|
|
64
94
|
}
|
|
65
95
|
}
|
|
66
96
|
Ok(result)
|
|
67
97
|
}
|
|
68
|
-
|
|
69
98
|
}
|
|
70
99
|
|
|
71
100
|
#[cfg(feature = "regex")]
|
|
72
101
|
impl std::fmt::Display for RegExpFlags {
|
|
73
102
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
74
|
-
if self.global {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if self.
|
|
78
|
-
|
|
79
|
-
|
|
103
|
+
if self.global {
|
|
104
|
+
f.write_str("g")?;
|
|
105
|
+
}
|
|
106
|
+
if self.ignore_case {
|
|
107
|
+
f.write_str("i")?;
|
|
108
|
+
}
|
|
109
|
+
if self.multiline {
|
|
110
|
+
f.write_str("m")?;
|
|
111
|
+
}
|
|
112
|
+
if self.dot_all {
|
|
113
|
+
f.write_str("s")?;
|
|
114
|
+
}
|
|
115
|
+
if self.unicode {
|
|
116
|
+
f.write_str("u")?;
|
|
117
|
+
}
|
|
118
|
+
if self.sticky {
|
|
119
|
+
f.write_str("y")?;
|
|
120
|
+
}
|
|
80
121
|
Ok(())
|
|
81
122
|
}
|
|
82
123
|
}
|
|
@@ -96,23 +137,36 @@ impl TishRegExp {
|
|
|
96
137
|
pub fn new(pattern: &str, flags_str: &str) -> Result<Self, String> {
|
|
97
138
|
let flags = RegExpFlags::from_string(flags_str)?;
|
|
98
139
|
let mut regex_pattern = pattern.to_string();
|
|
99
|
-
|
|
140
|
+
|
|
100
141
|
if flags.ignore_case || flags.multiline || flags.dot_all {
|
|
101
142
|
let mut flag_prefix = String::from("(?");
|
|
102
|
-
if flags.ignore_case {
|
|
103
|
-
|
|
104
|
-
|
|
143
|
+
if flags.ignore_case {
|
|
144
|
+
flag_prefix.push('i');
|
|
145
|
+
}
|
|
146
|
+
if flags.multiline {
|
|
147
|
+
flag_prefix.push('m');
|
|
148
|
+
}
|
|
149
|
+
if flags.dot_all {
|
|
150
|
+
flag_prefix.push('s');
|
|
151
|
+
}
|
|
105
152
|
flag_prefix.push(')');
|
|
106
153
|
regex_pattern = format!("{}{}", flag_prefix, regex_pattern);
|
|
107
154
|
}
|
|
108
|
-
|
|
109
|
-
let regex =
|
|
110
|
-
.map_err(|e| format!("Invalid regular expression: {}", e))?;
|
|
111
|
-
|
|
112
|
-
Ok(Self {
|
|
155
|
+
|
|
156
|
+
let regex =
|
|
157
|
+
Regex::new(®ex_pattern).map_err(|e| format!("Invalid regular expression: {}", e))?;
|
|
158
|
+
|
|
159
|
+
Ok(Self {
|
|
160
|
+
source: pattern.to_string(),
|
|
161
|
+
flags,
|
|
162
|
+
regex: Arc::new(regex),
|
|
163
|
+
last_index: 0,
|
|
164
|
+
})
|
|
113
165
|
}
|
|
114
166
|
|
|
115
|
-
pub fn flags_string(&self) -> String {
|
|
167
|
+
pub fn flags_string(&self) -> String {
|
|
168
|
+
self.flags.to_string()
|
|
169
|
+
}
|
|
116
170
|
|
|
117
171
|
pub fn test(&mut self, input: &str) -> bool {
|
|
118
172
|
if self.flags.global || self.flags.sticky {
|
|
@@ -121,10 +175,10 @@ impl TishRegExp {
|
|
|
121
175
|
self.last_index = 0;
|
|
122
176
|
return false;
|
|
123
177
|
}
|
|
124
|
-
|
|
178
|
+
|
|
125
179
|
let byte_start: usize = input.chars().take(start).map(|c| c.len_utf8()).sum();
|
|
126
180
|
let search_str = &input[byte_start..];
|
|
127
|
-
|
|
181
|
+
|
|
128
182
|
match self.regex.find(search_str) {
|
|
129
183
|
Ok(Some(m)) => {
|
|
130
184
|
if self.flags.sticky && m.start() != 0 {
|
|
@@ -176,7 +230,12 @@ impl std::fmt::Debug for Value {
|
|
|
176
230
|
Value::Object(obj) => write!(f, "Object({:?})", obj.borrow()),
|
|
177
231
|
Value::Function(_) => write!(f, "Function"),
|
|
178
232
|
#[cfg(feature = "regex")]
|
|
179
|
-
Value::RegExp(re) => write!(
|
|
233
|
+
Value::RegExp(re) => write!(
|
|
234
|
+
f,
|
|
235
|
+
"RegExp(/{}/{})",
|
|
236
|
+
re.borrow().source,
|
|
237
|
+
re.borrow().flags_string()
|
|
238
|
+
),
|
|
180
239
|
Value::Promise(_) => write!(f, "Promise"),
|
|
181
240
|
Value::Opaque(o) => write!(f, "{}(opaque)", o.type_name()),
|
|
182
241
|
}
|
|
@@ -202,7 +261,8 @@ impl Value {
|
|
|
202
261
|
Value::Bool(b) => b.to_string(),
|
|
203
262
|
Value::Null => "null".to_string(),
|
|
204
263
|
Value::Array(arr) => {
|
|
205
|
-
let inner: Vec<String> =
|
|
264
|
+
let inner: Vec<String> =
|
|
265
|
+
arr.borrow().iter().map(|v| v.to_display_string()).collect();
|
|
206
266
|
format!("[{}]", inner.join(", "))
|
|
207
267
|
}
|
|
208
268
|
Value::Object(obj) => {
|
|
@@ -378,7 +438,11 @@ impl Value {
|
|
|
378
438
|
"trim".into(),
|
|
379
439
|
]
|
|
380
440
|
}
|
|
381
|
-
Value::Number(_) => vec![
|
|
441
|
+
Value::Number(_) => vec![
|
|
442
|
+
"toFixed".into(),
|
|
443
|
+
"toExponential".into(),
|
|
444
|
+
"toPrecision".into(),
|
|
445
|
+
],
|
|
382
446
|
_ => vec![],
|
|
383
447
|
}
|
|
384
448
|
}
|