@tishlang/tish-format 1.0.12 → 2.0.1
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 +51 -0
- package/LICENSE +13 -0
- package/bin/tish-format +0 -0
- package/crates/js_to_tish/Cargo.toml +11 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +55 -0
- package/crates/js_to_tish/src/lib.rs +11 -0
- package/crates/js_to_tish/src/span_util.rs +35 -0
- package/crates/js_to_tish/src/transform/expr.rs +611 -0
- package/crates/js_to_tish/src/transform/stmt.rs +503 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +62 -0
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +576 -0
- package/crates/tish/src/main.rs +853 -0
- package/crates/tish/src/repl_completion.rs +199 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/error_source_location.rs +36 -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/fixtures/runtime_error_location.tish +5 -0
- package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
- package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
- package/crates/tish/tests/integration_test.rs +1406 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +649 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +11 -0
- package/crates/tish_build_utils/src/lib.rs +577 -0
- package/crates/tish_builtins/Cargo.toml +22 -0
- package/crates/tish_builtins/src/array.rs +803 -0
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +199 -0
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +293 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +21 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +646 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +164 -0
- package/crates/tish_bytecode/src/compiler.rs +2604 -0
- package/crates/tish_bytecode/src/encoding.rs +102 -0
- package/crates/tish_bytecode/src/lib.rs +20 -0
- package/crates/tish_bytecode/src/opcode.rs +185 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +193 -0
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +27 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +7317 -0
- package/crates/tish_compile/src/infer.rs +1681 -0
- package/crates/tish_compile/src/lib.rs +206 -0
- package/crates/tish_compile/src/resolve.rs +1951 -0
- package/crates/tish_compile/src/types.rs +605 -0
- package/crates/tish_compile_js/Cargo.toml +18 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +938 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/lib.rs +26 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
- package/crates/tish_compiler_wasm/Cargo.toml +21 -0
- package/crates/tish_compiler_wasm/src/lib.rs +57 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
- package/crates/tish_core/Cargo.toml +32 -0
- package/crates/tish_core/src/console_style.rs +170 -0
- package/crates/tish_core/src/json.rs +430 -0
- package/crates/tish_core/src/lib.rs +20 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +1350 -0
- package/crates/tish_core/src/vmref.rs +183 -0
- package/crates/tish_cranelift/Cargo.toml +19 -0
- package/crates/tish_cranelift/src/lib.rs +43 -0
- package/crates/tish_cranelift/src/link.rs +130 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +51 -0
- package/crates/tish_eval/src/eval.rs +4265 -0
- package/crates/tish_eval/src/http.rs +191 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +551 -0
- package/crates/tish_eval/src/promise.rs +179 -0
- package/crates/tish_eval/src/regex.rs +299 -0
- package/crates/tish_eval/src/timers.rs +120 -0
- package/crates/tish_eval/src/value.rs +336 -0
- package/crates/tish_eval/src/value_convert.rs +117 -0
- package/crates/tish_ffi/Cargo.toml +26 -0
- package/crates/tish_ffi/src/lib.rs +518 -0
- package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
- package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
- package/crates/tish_ffi/tests/loader.rs +65 -0
- package/crates/tish_fmt/Cargo.toml +16 -0
- package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
- package/crates/tish_fmt/src/lib.rs +2157 -0
- package/crates/tish_jsx_web/Cargo.toml +9 -0
- package/crates/tish_jsx_web/README.md +5 -0
- package/crates/tish_jsx_web/src/lib.rs +2 -0
- package/crates/tish_lexer/Cargo.toml +9 -0
- package/crates/tish_lexer/src/lib.rs +1104 -0
- package/crates/tish_lexer/src/token.rs +170 -0
- package/crates/tish_lint/Cargo.toml +18 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
- package/crates/tish_lint/src/lib.rs +281 -0
- package/crates/tish_llvm/Cargo.toml +13 -0
- package/crates/tish_llvm/src/lib.rs +115 -0
- package/crates/tish_lsp/Cargo.toml +25 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/builtin_goto.rs +362 -0
- package/crates/tish_lsp/src/import_goto.rs +564 -0
- package/crates/tish_lsp/src/main.rs +1459 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +481 -0
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +416 -0
- package/crates/tish_opt/Cargo.toml +13 -0
- package/crates/tish_opt/src/lib.rs +1046 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +386 -0
- package/crates/tish_parser/src/parser.rs +2726 -0
- 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 +955 -0
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3601 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +100 -0
- package/crates/tish_runtime/src/http.rs +1347 -0
- package/crates/tish_runtime/src/http_fetch.rs +492 -0
- package/crates/tish_runtime/src/http_hyper.rs +441 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +1447 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +558 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +172 -0
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +778 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +692 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +573 -0
- package/crates/tish_ui/src/runtime/mod.rs +183 -0
- package/crates/tish_vm/Cargo.toml +60 -0
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +41 -0
- package/crates/tish_vm/src/vm.rs +3536 -0
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
- package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
- package/crates/tish_wasm/Cargo.toml +15 -0
- package/crates/tish_wasm/src/lib.rs +428 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
- package/crates/tish_wasm_runtime/src/lib.rs +42 -0
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
- package/justfile +276 -0
- package/package.json +2 -2
- package/platform/darwin-arm64/tish-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- package/platform/win32-x64/tish-fmt.exe +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#[derive(Debug)]
|
|
2
|
+
pub struct CompileError {
|
|
3
|
+
pub message: String,
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
impl CompileError {
|
|
7
|
+
pub fn new(msg: &str) -> Self {
|
|
8
|
+
Self {
|
|
9
|
+
message: msg.to_string(),
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
impl std::fmt::Display for CompileError {
|
|
15
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
16
|
+
write!(f, "{}", self.message)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl std::error::Error for CompileError {}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//! Tish to JavaScript transpiler backend.
|
|
2
|
+
//! Uses shared resolve from tishlang_compile for unified pipeline.
|
|
3
|
+
|
|
4
|
+
mod codegen;
|
|
5
|
+
mod error;
|
|
6
|
+
|
|
7
|
+
#[cfg(test)]
|
|
8
|
+
mod tests_jsx;
|
|
9
|
+
|
|
10
|
+
pub use codegen::{
|
|
11
|
+
compile_project_with_jsx, compile_project_with_jsx_and_source_map, compile_with_jsx, JsBundle,
|
|
12
|
+
};
|
|
13
|
+
pub use error::CompileError;
|
|
14
|
+
|
|
15
|
+
/// JSX lowers to `h` / `Fragment`; merge the `lattish` runtime for hooks and DOM.
|
|
16
|
+
pub fn compile(program: &tishlang_ast::Program, optimize: bool) -> Result<String, CompileError> {
|
|
17
|
+
compile_with_jsx(program, optimize)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub fn compile_project(
|
|
21
|
+
entry_path: &std::path::Path,
|
|
22
|
+
project_root: Option<&std::path::Path>,
|
|
23
|
+
optimize: bool,
|
|
24
|
+
) -> Result<String, CompileError> {
|
|
25
|
+
compile_project_with_jsx(entry_path, project_root, optimize)
|
|
26
|
+
}
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
#[cfg(test)]
|
|
2
|
+
mod tests {
|
|
3
|
+
use std::io::Write;
|
|
4
|
+
|
|
5
|
+
use tishlang_parser::parse;
|
|
6
|
+
|
|
7
|
+
use crate::{compile_project_with_jsx, compile_with_jsx};
|
|
8
|
+
|
|
9
|
+
#[test]
|
|
10
|
+
fn lattish_jsx_emits_h_with_children_array() {
|
|
11
|
+
let src = r#"fn X() { return <div class="a">{"hi"}</div> }"#;
|
|
12
|
+
let program = parse(src).unwrap();
|
|
13
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
14
|
+
assert!(
|
|
15
|
+
js.contains("h(\"div\", { class: \"a\" }, [\"hi\"])"),
|
|
16
|
+
"{}",
|
|
17
|
+
js
|
|
18
|
+
);
|
|
19
|
+
assert!(!js.contains("function __h("));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[test]
|
|
23
|
+
fn fragment_lattish_uses_fragment_symbol() {
|
|
24
|
+
let src = "fn X() { return <><b>{\"1\"}</b></> }";
|
|
25
|
+
let program = parse(src).unwrap();
|
|
26
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
27
|
+
assert!(js.contains("h(Fragment, null, ["));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#[test]
|
|
31
|
+
fn jsx_keyword_text_after_child_element() {
|
|
32
|
+
// #108: a text run *after* a nested child element must be lexed as JSX text, so a bare
|
|
33
|
+
// reserved keyword (`as`, `in`, `if`, `return`, `let`) in that run is plain text, not a
|
|
34
|
+
// keyword token. Text-only children already worked; the bug was failing to re-enter
|
|
35
|
+
// JSX-text mode once a child element had closed.
|
|
36
|
+
for kw in ["as", "in", "if", "return", "let"] {
|
|
37
|
+
let src = format!("fn V() {{ return <div><span>x</span> {kw} JSON</div> }}");
|
|
38
|
+
let program =
|
|
39
|
+
parse(&src).unwrap_or_else(|e| panic!("parse failed for trailing `{kw}`: {e}"));
|
|
40
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
41
|
+
let expected = format!("[h(\"span\", null, [\"x\"]), \" {kw} JSON\"]");
|
|
42
|
+
assert!(
|
|
43
|
+
js.contains(&expected),
|
|
44
|
+
"trailing `{kw}` after child element: expected {expected:?} in output, got: {js}"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#[test]
|
|
50
|
+
fn jsx_text_between_and_after_multiple_children() {
|
|
51
|
+
// Text both between and after child elements stays text (incl. a keyword run between).
|
|
52
|
+
let src = r#"fn V() { return <div><span>x</span> as <b>y</b> in z</div> }"#;
|
|
53
|
+
let program = parse(src).unwrap();
|
|
54
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
55
|
+
assert!(
|
|
56
|
+
js.contains(
|
|
57
|
+
"[h(\"span\", null, [\"x\"]), \" as \", h(\"b\", null, [\"y\"]), \" in z\"]"
|
|
58
|
+
),
|
|
59
|
+
"{js}"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[test]
|
|
64
|
+
fn jsx_self_closing_child_then_keyword_text() {
|
|
65
|
+
// A self-closing child (`<br/>`) followed by keyword text must also re-enter text mode.
|
|
66
|
+
let src = r#"fn V() { return <div><br/> as JSON</div> }"#;
|
|
67
|
+
let program = parse(src).unwrap();
|
|
68
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
69
|
+
assert!(
|
|
70
|
+
js.contains("[h(\"br\", null, []), \" as JSON\"]"),
|
|
71
|
+
"{js}"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[test]
|
|
76
|
+
fn jsx_child_element_inside_expr_container_not_text_mode() {
|
|
77
|
+
// Re-entering JSX text mode after a child closes must NOT happen when that child lived
|
|
78
|
+
// inside a `{…}` expression container — otherwise the `)`/`,` that follows is swallowed as
|
|
79
|
+
// JsxText ("Expected RParen, got JsxText"). Regression for the #108 fix itself, caught by
|
|
80
|
+
// the downstream suite (tish-audio / tish-midi use `{items.map(x => <li>{x}</li>)}`).
|
|
81
|
+
let src = r#"fn V(items) { return <ul>{items.map(x => <li>{x}</li>)}</ul> }"#;
|
|
82
|
+
let program = parse(src).expect("map-in-container must parse");
|
|
83
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
84
|
+
assert!(js.contains("h(\"ul\""), "{js}");
|
|
85
|
+
assert!(js.contains(".map("), "{js}");
|
|
86
|
+
|
|
87
|
+
// And the combined case: a `{…}` container followed by trailing keyword text.
|
|
88
|
+
let src2 = r#"fn V(items) { return <div>{items.map(x => <span>{x}</span>)} as JSON</div> }"#;
|
|
89
|
+
let program2 = parse(src2).expect("container-then-text must parse");
|
|
90
|
+
let js2 = compile_with_jsx(&program2, false).unwrap();
|
|
91
|
+
assert!(js2.contains("\" as JSON\""), "{js2}");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#[test]
|
|
95
|
+
fn jsx_text_whitespace_coalesced() {
|
|
96
|
+
let src = r#"fn X() { return <p>First paragraph</p> }"#;
|
|
97
|
+
let program = parse(src).unwrap();
|
|
98
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
99
|
+
assert!(
|
|
100
|
+
js.contains("\"First paragraph\""),
|
|
101
|
+
"expected \"First paragraph\" in output, got: {}",
|
|
102
|
+
&js[..400.min(js.len())]
|
|
103
|
+
);
|
|
104
|
+
assert!(
|
|
105
|
+
!js.contains("\"First\", \"paragraph\""),
|
|
106
|
+
"text should be coalesced, not split"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#[test]
|
|
111
|
+
fn jsx_text_whitespace_coalesced_multiline() {
|
|
112
|
+
let src = "fn App() {\n return <p>First paragraph</p>\n}";
|
|
113
|
+
let program = parse(src).unwrap();
|
|
114
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
115
|
+
assert!(
|
|
116
|
+
js.contains("\"First paragraph\""),
|
|
117
|
+
"multiline: expected \"First paragraph\", got: {}",
|
|
118
|
+
&js[..400.min(js.len())]
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#[test]
|
|
123
|
+
fn jsx_text_punctuation_no_space() {
|
|
124
|
+
// Punctuation (e.g. !) concatenates without space: "work!" not "work !"
|
|
125
|
+
let src = r#"fn X() { return <p>work!</p> }"#;
|
|
126
|
+
let program = parse(src).unwrap();
|
|
127
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
128
|
+
assert!(
|
|
129
|
+
js.contains(r#""work!""#),
|
|
130
|
+
"expected 'work!', got: {}",
|
|
131
|
+
&js[..400.min(js.len())]
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[test]
|
|
136
|
+
fn jsx_text_emojis() {
|
|
137
|
+
let src = r#"fn X() { return <p>hello 😔</p> }"#;
|
|
138
|
+
let program = parse(src).unwrap();
|
|
139
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
140
|
+
assert!(
|
|
141
|
+
js.contains("😔"),
|
|
142
|
+
"expected emoji, got: {}",
|
|
143
|
+
&js[..400.min(js.len())]
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#[test]
|
|
148
|
+
fn jsx_text_whitespace_via_compile_project() {
|
|
149
|
+
let dir = std::env::temp_dir().join("tishlang_compile_project_test");
|
|
150
|
+
let _ = std::fs::create_dir_all(&dir);
|
|
151
|
+
let path = dir.join("test.tish");
|
|
152
|
+
let src = "fn App() {\n return <p>First paragraph</p>\n}";
|
|
153
|
+
let mut f = std::fs::File::create(&path).unwrap();
|
|
154
|
+
f.write_all(src.as_bytes()).unwrap();
|
|
155
|
+
f.sync_all().unwrap();
|
|
156
|
+
drop(f);
|
|
157
|
+
let js = compile_project_with_jsx(&path, Some(&dir), false)
|
|
158
|
+
.expect("compile_project_with_jsx failed");
|
|
159
|
+
assert!(
|
|
160
|
+
js.contains("\"First paragraph\""),
|
|
161
|
+
"compile_project: expected \"First paragraph\", got: {}",
|
|
162
|
+
&js[..500.min(js.len())]
|
|
163
|
+
);
|
|
164
|
+
let _ = std::fs::remove_file(&path);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[test]
|
|
168
|
+
fn jsx_never_emits_vdom_helpers_or_prelude_flags() {
|
|
169
|
+
let src = r#"fn X() { return <div class="x">{"a"}</div> }"#;
|
|
170
|
+
let program = parse(src).unwrap();
|
|
171
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
172
|
+
assert!(
|
|
173
|
+
js.contains("h(\"div\", { class: \"x\" }"),
|
|
174
|
+
"{}",
|
|
175
|
+
&js[..500.min(js.len())]
|
|
176
|
+
);
|
|
177
|
+
assert!(!js.contains("__vdom_h"), "{}", &js[..600.min(js.len())]);
|
|
178
|
+
assert!(
|
|
179
|
+
!js.contains("window.__LATTISH_JSX_VDOM"),
|
|
180
|
+
"{}",
|
|
181
|
+
&js[..600.min(js.len())]
|
|
182
|
+
);
|
|
183
|
+
assert!(
|
|
184
|
+
!js.contains("__lattishVdomPatch"),
|
|
185
|
+
"{}",
|
|
186
|
+
&js[..600.min(js.len())]
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// Component calls like {Panel()} return DOM elements. Wrapping in String() produces [object HTMLDivElement].
|
|
191
|
+
#[test]
|
|
192
|
+
fn jsx_component_call_not_wrapped_in_string() {
|
|
193
|
+
let src = r#"
|
|
194
|
+
fn Panel() { return <div class="p">content</div> }
|
|
195
|
+
fn App() { return <div>{Panel()}</div> }
|
|
196
|
+
"#;
|
|
197
|
+
let program = parse(src).unwrap();
|
|
198
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
199
|
+
assert!(
|
|
200
|
+
js.contains("Panel()"),
|
|
201
|
+
"component call should appear as Panel(), got: {}",
|
|
202
|
+
&js[..500.min(js.len())]
|
|
203
|
+
);
|
|
204
|
+
assert!(
|
|
205
|
+
!js.contains("String(Panel()"),
|
|
206
|
+
"component calls must NOT be wrapped in String() - causes [object HTMLDivElement]. got: {}",
|
|
207
|
+
&js[..600.min(js.len())]
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// Nested JSX elements must not be String()'d or they render as [object HTMLDivElement].
|
|
212
|
+
#[test]
|
|
213
|
+
fn jsx_nested_element_not_wrapped_in_string() {
|
|
214
|
+
let src = r#"fn X() { return <div><span>inner</span></div> }"#;
|
|
215
|
+
let program = parse(src).unwrap();
|
|
216
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
217
|
+
assert!(
|
|
218
|
+
!js.contains("String(h("),
|
|
219
|
+
"nested JSX elements must NOT be wrapped in String(). got: {}",
|
|
220
|
+
&js[..500.min(js.len())]
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/// Literal number/bool/null get String() for display. Idents (e.g. {items}) are NOT wrapped—they may hold elements.
|
|
225
|
+
#[test]
|
|
226
|
+
fn jsx_literal_number_wrapped_in_string() {
|
|
227
|
+
let src = r#"fn X() { return <span>{42}</span> }"#;
|
|
228
|
+
let program = parse(src).unwrap();
|
|
229
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
230
|
+
assert!(
|
|
231
|
+
js.contains("String(42)"),
|
|
232
|
+
"literal number in JSX should be wrapped in String(). got: {}",
|
|
233
|
+
&js[..500.min(js.len())]
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// Array/ident like {items} (array of buttons) must NOT be String()'d or we get [object HTMLButtonElement].
|
|
238
|
+
#[test]
|
|
239
|
+
fn jsx_array_of_elements_not_wrapped_in_string() {
|
|
240
|
+
let src = r#"
|
|
241
|
+
fn FileList() {
|
|
242
|
+
let items = []
|
|
243
|
+
items.push(<button>a</button>)
|
|
244
|
+
items.push(<button>b</button>)
|
|
245
|
+
return <div>{items}</div>
|
|
246
|
+
}
|
|
247
|
+
"#;
|
|
248
|
+
let program = parse(src).unwrap();
|
|
249
|
+
let js = compile_with_jsx(&program, false).unwrap();
|
|
250
|
+
assert!(
|
|
251
|
+
!js.contains("String(items)"),
|
|
252
|
+
"array/ident in JSX must NOT be wrapped in String() - causes [object HTMLButtonElement]. got: {}",
|
|
253
|
+
&js[..600.min(js.len())]
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// `>` inside `{ ... }` attribute values must be a comparison operator, not end of opening tag.
|
|
258
|
+
#[test]
|
|
259
|
+
fn jsx_gt_comparison_inside_attribute_expression() {
|
|
260
|
+
let src = r#"fn X() {
|
|
261
|
+
return <button
|
|
262
|
+
type="button"
|
|
263
|
+
onclick={() => {
|
|
264
|
+
let nm = "a"
|
|
265
|
+
if (nm && nm.length > 0) { print(nm) }
|
|
266
|
+
}}
|
|
267
|
+
>{"ok"}</button>
|
|
268
|
+
}"#;
|
|
269
|
+
let program = parse(src).expect("parse multi-line JSX with > comparison in attr");
|
|
270
|
+
let js = compile_with_jsx(&program, false).expect("compile");
|
|
271
|
+
assert!(
|
|
272
|
+
js.contains("length > 0") || js.contains("length>0"),
|
|
273
|
+
"expected compiled JS to preserve greater-than comparison, got: {}",
|
|
274
|
+
&js[..800.min(js.len())]
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/// Nested JSX inside an attribute callback must still close inner `<tag>` correctly.
|
|
279
|
+
#[test]
|
|
280
|
+
fn jsx_nested_element_inside_attribute_expression() {
|
|
281
|
+
let src = r#"fn X() {
|
|
282
|
+
return <button
|
|
283
|
+
onclick={() => {
|
|
284
|
+
let x = <span>{"inner"}</span>
|
|
285
|
+
print(x)
|
|
286
|
+
}}
|
|
287
|
+
>{"outer"}</button>
|
|
288
|
+
}"#;
|
|
289
|
+
let program = parse(src).expect("parse nested JSX inside onclick");
|
|
290
|
+
let js = compile_with_jsx(&program, false).expect("compile");
|
|
291
|
+
assert!(
|
|
292
|
+
js.contains("\"inner\""),
|
|
293
|
+
"expected nested span text in output, got: {}",
|
|
294
|
+
&js[..900.min(js.len())]
|
|
295
|
+
);
|
|
296
|
+
assert!(
|
|
297
|
+
js.contains("\"outer\""),
|
|
298
|
+
"expected button child text in output, got: {}",
|
|
299
|
+
&js[..900.min(js.len())]
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
#[test]
|
|
304
|
+
fn new_date_global_emits_valid_js_with_and_without_optimize() {
|
|
305
|
+
let src = "let epoch = new Date(0)\nconsole.log(epoch.getTime())";
|
|
306
|
+
let program = parse(src).expect("parse");
|
|
307
|
+
for optimize in [false, true] {
|
|
308
|
+
let js = compile_with_jsx(&program, optimize).expect("compile");
|
|
309
|
+
assert!(
|
|
310
|
+
js.contains("new Date(0)"),
|
|
311
|
+
"optimize={optimize}: expected `new Date(0)` in JS output:\n{js}"
|
|
312
|
+
);
|
|
313
|
+
assert!(
|
|
314
|
+
!js.contains("let epoch = new;"),
|
|
315
|
+
"optimize={optimize}: broken `new` emission (missing constructor):\n{js}"
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
#[test]
|
|
321
|
+
fn new_uint8array_emits_direct_new_no_preamble() {
|
|
322
|
+
let src = "fn f(n) { return new Uint8Array(n) }";
|
|
323
|
+
let program = parse(src).expect("parse");
|
|
324
|
+
let js = compile_with_jsx(&program, false).expect("compile");
|
|
325
|
+
assert!(
|
|
326
|
+
js.contains("new Uint8Array("),
|
|
327
|
+
"expected direct new Uint8Array, got: {}",
|
|
328
|
+
&js[..500.min(js.len())]
|
|
329
|
+
);
|
|
330
|
+
assert!(
|
|
331
|
+
!js.contains("__tishUint8Array"),
|
|
332
|
+
"should not emit legacy intrinsic helper"
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
#[test]
|
|
337
|
+
fn new_audio_context_emits_direct_new_no_preamble() {
|
|
338
|
+
let src = "fn f() { return new AudioContext() }";
|
|
339
|
+
let program = parse(src).expect("parse");
|
|
340
|
+
let js = compile_with_jsx(&program, false).expect("compile");
|
|
341
|
+
assert!(
|
|
342
|
+
js.contains("new AudioContext("),
|
|
343
|
+
"expected new AudioContext, got: {}",
|
|
344
|
+
&js[..500.min(js.len())]
|
|
345
|
+
);
|
|
346
|
+
assert!(
|
|
347
|
+
!js.contains("__tishWebAudioCreateContext"),
|
|
348
|
+
"should not emit legacy intrinsic helper"
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
#[test]
|
|
353
|
+
fn new_class_name_emits_direct_new_js() {
|
|
354
|
+
let src = r#"
|
|
355
|
+
fn ClassName(x) {
|
|
356
|
+
return x
|
|
357
|
+
}
|
|
358
|
+
fn factory() {
|
|
359
|
+
return new ClassName(42)
|
|
360
|
+
}
|
|
361
|
+
"#;
|
|
362
|
+
let program = parse(src).expect("parse");
|
|
363
|
+
let js = compile_with_jsx(&program, false).expect("compile");
|
|
364
|
+
assert!(
|
|
365
|
+
js.contains("new ClassName("),
|
|
366
|
+
"expected new ClassName( in JS output, got: {}",
|
|
367
|
+
&js[..800.min(js.len())]
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
#[test]
|
|
372
|
+
fn fn_body_two_lets_not_split_by_closing_brace() {
|
|
373
|
+
let src = "fn h() {\n let a = 1\n let b = 2\n}\n";
|
|
374
|
+
let program = parse(src).expect("parse");
|
|
375
|
+
let js = compile_with_jsx(&program, false).expect("compile");
|
|
376
|
+
let i = js.find("let a = 1").expect("let a");
|
|
377
|
+
let j = js.find("let b = 2").expect("let b");
|
|
378
|
+
assert!(
|
|
379
|
+
!js[i..j].contains('}'),
|
|
380
|
+
"first let must not end in an inner block before second let (regression #43): {:?}",
|
|
381
|
+
&js[i..j]
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
#[test]
|
|
386
|
+
fn control_flow_wraps_lexical_decl_body_in_block_for_valid_js() {
|
|
387
|
+
let src = r#"fn f() {
|
|
388
|
+
if (true)
|
|
389
|
+
const x = 1
|
|
390
|
+
while (false)
|
|
391
|
+
let y = 2
|
|
392
|
+
for (;;)
|
|
393
|
+
const z = 3
|
|
394
|
+
for (const v of [])
|
|
395
|
+
let w = 4
|
|
396
|
+
}"#;
|
|
397
|
+
let program = parse(src).expect("parse");
|
|
398
|
+
let js = compile_with_jsx(&program, false).expect("compile");
|
|
399
|
+
for (label, key, decl) in [
|
|
400
|
+
("if", "if (true)", "const x = 1"),
|
|
401
|
+
("while", "while (false)", "let y = 2"),
|
|
402
|
+
("for", "for (; ; )", "const z = 3"),
|
|
403
|
+
("for-of", "for (const v of [])", "let w = 4"),
|
|
404
|
+
] {
|
|
405
|
+
let i = js.find(key).expect(label);
|
|
406
|
+
let j = js.find(decl).expect(label);
|
|
407
|
+
assert!(
|
|
408
|
+
i < j && js[i..j].contains('{'),
|
|
409
|
+
"{label}: expected '{{' between {key:?} and {decl:?}, got {:?}",
|
|
410
|
+
&js[i..j]
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "tishlang_compiler_wasm"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "Tish compiler as WASM for browser (parse + bytecode + JS)"
|
|
6
|
+
license-file = { workspace = true }
|
|
7
|
+
repository = { workspace = true }
|
|
8
|
+
|
|
9
|
+
[lib]
|
|
10
|
+
crate-type = ["cdylib"]
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
base64 = "0.22"
|
|
14
|
+
serde_json = "1.0"
|
|
15
|
+
wasm-bindgen = "0.2"
|
|
16
|
+
|
|
17
|
+
tishlang_ast = { path = "../tish_ast", version = ">=0.1" }
|
|
18
|
+
tishlang_bytecode = { path = "../tish_bytecode", version = ">=0.1" }
|
|
19
|
+
tishlang_compile_js = { path = "../tish_compile_js", version = ">=0.1" }
|
|
20
|
+
tishlang_opt = { path = "../tish_opt", version = ">=0.1" }
|
|
21
|
+
tishlang_parser = { path = "../tish_parser", version = ">=0.1" }
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//! Tish compiler exposed to JS via wasm-bindgen.
|
|
2
|
+
//! Compiles single source string → bytecode (base64) or JS. Used by playground, REPL, try-it pages.
|
|
3
|
+
//!
|
|
4
|
+
//! `compile_to_js` / `compile_to_js_with_imports` use Lattish-style JSX lowering. Prepend a compiled
|
|
5
|
+
//! **Lattish.tish** runtime (same as playground `lattish-runtime.js`) so the iframe/script scope has
|
|
6
|
+
//! hooks and the JSX helpers; source files do not need to import the JSX helper by name.
|
|
7
|
+
|
|
8
|
+
mod resolve_virtual;
|
|
9
|
+
|
|
10
|
+
use base64::Engine;
|
|
11
|
+
use resolve_virtual::{detect_cycles_virtual, merge_modules_virtual, resolve_virtual};
|
|
12
|
+
use std::collections::HashMap;
|
|
13
|
+
use wasm_bindgen::prelude::*;
|
|
14
|
+
|
|
15
|
+
#[wasm_bindgen]
|
|
16
|
+
pub fn compile_to_bytecode(source: &str) -> Result<String, JsValue> {
|
|
17
|
+
let program =
|
|
18
|
+
tishlang_parser::parse(source.trim()).map_err(|e| JsValue::from_str(&e.to_string()))?;
|
|
19
|
+
let program = tishlang_opt::optimize(&program);
|
|
20
|
+
let chunk =
|
|
21
|
+
tishlang_bytecode::compile(&program).map_err(|e| JsValue::from_str(&e.to_string()))?;
|
|
22
|
+
Ok(base64::engine::general_purpose::STANDARD.encode(tishlang_bytecode::serialize(&chunk)))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[wasm_bindgen]
|
|
26
|
+
pub fn compile_to_js(source: &str) -> Result<String, JsValue> {
|
|
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))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[wasm_bindgen]
|
|
33
|
+
pub fn compile_to_bytecode_with_imports(
|
|
34
|
+
entry_path: &str,
|
|
35
|
+
files_json: &str,
|
|
36
|
+
) -> Result<String, JsValue> {
|
|
37
|
+
let files: HashMap<String, String> = serde_json::from_str(files_json)
|
|
38
|
+
.map_err(|e| JsValue::from_str(&format!("Invalid files JSON: {}", e)))?;
|
|
39
|
+
let modules = resolve_virtual(entry_path, &files).map_err(|e| JsValue::from_str(&e))?;
|
|
40
|
+
detect_cycles_virtual(&modules).map_err(|e| JsValue::from_str(&e))?;
|
|
41
|
+
let program = merge_modules_virtual(modules).map_err(|e| JsValue::from_str(&e))?;
|
|
42
|
+
let program = tishlang_opt::optimize(&program);
|
|
43
|
+
let chunk =
|
|
44
|
+
tishlang_bytecode::compile(&program).map_err(|e| JsValue::from_str(&e.to_string()))?;
|
|
45
|
+
Ok(base64::engine::general_purpose::STANDARD.encode(tishlang_bytecode::serialize(&chunk)))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#[wasm_bindgen]
|
|
49
|
+
pub fn compile_to_js_with_imports(entry_path: &str, files_json: &str) -> Result<String, JsValue> {
|
|
50
|
+
let files: HashMap<String, String> = serde_json::from_str(files_json)
|
|
51
|
+
.map_err(|e| JsValue::from_str(&format!("Invalid files JSON: {}", e)))?;
|
|
52
|
+
let modules = resolve_virtual(entry_path, &files).map_err(|e| JsValue::from_str(&e))?;
|
|
53
|
+
detect_cycles_virtual(&modules).map_err(|e| JsValue::from_str(&e))?;
|
|
54
|
+
let program = merge_modules_virtual(modules).map_err(|e| JsValue::from_str(&e))?;
|
|
55
|
+
let program = tishlang_opt::optimize(&program);
|
|
56
|
+
tishlang_compile_js::compile_with_jsx(&program, true).map_err(|e| JsValue::from_str(&e.message))
|
|
57
|
+
}
|