@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,140 @@
|
|
|
1
|
+
//! Regression: concurrent HTTP handlers that mutate shared module-level state must not deadlock.
|
|
2
|
+
//!
|
|
3
|
+
//! ## What this guards
|
|
4
|
+
//!
|
|
5
|
+
//! Under `send-values` (forced on by the `http` feature), `serve(port, handler)` runs the handler
|
|
6
|
+
//! closure — a `NativeFn` (`Arc<dyn Callable>`, `Send + Sync`) — **directly on each accept thread**
|
|
7
|
+
//! (`tish_runtime::http::worker_loop_direct`). So N concurrent requests execute the SAME handler in
|
|
8
|
+
//! parallel, all sharing the captured module scope through `Arc<Mutex>` (`VmRef`). A handler that
|
|
9
|
+
//! mutates a module-level `let` (a request counter / cache / rate-limiter) therefore has many threads
|
|
10
|
+
//! reading and writing the same scope cell at once.
|
|
11
|
+
//!
|
|
12
|
+
//! This test drives that exact path without a network: it pulls the handler `Value::Function` out of
|
|
13
|
+
//! a freshly-run program and invokes it from many OS threads while they all read-modify-write a shared
|
|
14
|
+
//! module-level `let`. It exists to catch a regression where the VM's variable-write path holds a
|
|
15
|
+
//! scope guard across a re-acquisition of the same lock (which would deadlock concurrent writers and
|
|
16
|
+
//! hang `serve`). The watchdog turns such a hang into a fast, explicit test failure instead of a
|
|
17
|
+
//! stuck CI job.
|
|
18
|
+
//!
|
|
19
|
+
//! Note on coverage: macOS `SO_REUSEPORT` funnels HTTP accepts to a single worker thread, so a
|
|
20
|
+
//! network-level test can't actually run two handlers concurrently on macOS. Calling the handler
|
|
21
|
+
//! closure directly does — and OS thread scheduling + mutex semantics are platform-independent, so
|
|
22
|
+
//! this reproduces the Linux multi-worker dispatch contention the bug was reported against.
|
|
23
|
+
#![cfg(feature = "send-values")]
|
|
24
|
+
|
|
25
|
+
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
26
|
+
use std::sync::Arc;
|
|
27
|
+
use std::time::{Duration, Instant};
|
|
28
|
+
use tishlang_bytecode::compile;
|
|
29
|
+
use tishlang_core::{NativeFn, Value};
|
|
30
|
+
use tishlang_vm::Vm;
|
|
31
|
+
|
|
32
|
+
// `Value::Function` holds a `NativeFn` (= `Arc<dyn Callable>`, `Callable: Send + Sync` under
|
|
33
|
+
// send-values); invoke a handler via the trait-object method `.call(args)`.
|
|
34
|
+
type Handler = NativeFn;
|
|
35
|
+
|
|
36
|
+
/// Compile + run `src`, then pull a function it stored in a global back out.
|
|
37
|
+
fn export(vm: &Vm, name: &str) -> Handler {
|
|
38
|
+
match vm.get_global(name).unwrap_or_else(|| panic!("global `{name}` not found")) {
|
|
39
|
+
Value::Function(f) => f,
|
|
40
|
+
other => panic!("global `{name}` is not a function: {other:?}"),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn read_num(obj: &Value, field: &str) -> f64 {
|
|
45
|
+
match obj {
|
|
46
|
+
Value::Object(o) => match o.borrow().strings.get(field) {
|
|
47
|
+
Some(Value::Number(n)) => *n,
|
|
48
|
+
other => panic!("stats.{field} is not a number: {other:?}"),
|
|
49
|
+
},
|
|
50
|
+
other => panic!("stats is not an object: {other:?}"),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[test]
|
|
55
|
+
fn concurrent_handlers_mutating_shared_module_state_do_not_deadlock() {
|
|
56
|
+
// `handler`/`stats` are bare top-level assignments (undeclared names) -> stored in globals, so
|
|
57
|
+
// the test can pull them out. Both functions close over the same module-level `let`s, exactly as
|
|
58
|
+
// a real `serve` handler closes over module state. `served` is monotonic (only incremented), so
|
|
59
|
+
// it gives a deterministic plausibility bound even though the read-modify-write is racy.
|
|
60
|
+
let src = r#"
|
|
61
|
+
let active = 0
|
|
62
|
+
let maxActive = 0
|
|
63
|
+
let served = 0
|
|
64
|
+
fn handleRequest(req) {
|
|
65
|
+
active = active + 1
|
|
66
|
+
served = served + 1
|
|
67
|
+
if (active > maxActive) { maxActive = active }
|
|
68
|
+
let i = 0
|
|
69
|
+
while (i < 2000) { i = i + 1 } // brief CPU hold so handlers overlap
|
|
70
|
+
active = active - 1
|
|
71
|
+
return { status: 200, body: "ok" }
|
|
72
|
+
}
|
|
73
|
+
fn getStats() {
|
|
74
|
+
return { active: active, maxActive: maxActive, served: served }
|
|
75
|
+
}
|
|
76
|
+
handler = handleRequest
|
|
77
|
+
stats = getStats
|
|
78
|
+
"#;
|
|
79
|
+
let program = tishlang_parser::parse(src).expect("parse");
|
|
80
|
+
let chunk = compile(&program).expect("compile");
|
|
81
|
+
let mut vm = Vm::new();
|
|
82
|
+
vm.run(&chunk).expect("run top-level");
|
|
83
|
+
let handler = export(&vm, "handler");
|
|
84
|
+
let stats = export(&vm, "stats");
|
|
85
|
+
|
|
86
|
+
const THREADS: usize = 12;
|
|
87
|
+
const ITERS: usize = 100;
|
|
88
|
+
let total = THREADS * ITERS;
|
|
89
|
+
|
|
90
|
+
let done = Arc::new(AtomicUsize::new(0));
|
|
91
|
+
let start = Instant::now();
|
|
92
|
+
let mut handles = Vec::with_capacity(THREADS);
|
|
93
|
+
for t in 0..THREADS {
|
|
94
|
+
let h = handler.clone();
|
|
95
|
+
let done = Arc::clone(&done);
|
|
96
|
+
handles.push(std::thread::spawn(move || {
|
|
97
|
+
for i in 0..ITERS {
|
|
98
|
+
let resp = h.call(&[Value::Number((t * ITERS + i) as f64)]);
|
|
99
|
+
assert!(matches!(resp, Value::Object(_)), "handler must return a response object");
|
|
100
|
+
done.fetch_add(1, Ordering::Relaxed);
|
|
101
|
+
}
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Watchdog: if concurrent writers deadlocked on the scope mutex, `done` stops advancing.
|
|
106
|
+
// Fail fast (and loudly) rather than hang the test runner.
|
|
107
|
+
let mut last = 0usize;
|
|
108
|
+
let mut last_change = Instant::now();
|
|
109
|
+
while done.load(Ordering::Relaxed) < total {
|
|
110
|
+
let cur = done.load(Ordering::Relaxed);
|
|
111
|
+
if cur != last {
|
|
112
|
+
last = cur;
|
|
113
|
+
last_change = Instant::now();
|
|
114
|
+
}
|
|
115
|
+
assert!(
|
|
116
|
+
last_change.elapsed() < Duration::from_secs(15),
|
|
117
|
+
"DEADLOCK regression: concurrent handlers stalled at {cur}/{total} (no progress for 15s)"
|
|
118
|
+
);
|
|
119
|
+
std::thread::sleep(Duration::from_millis(20));
|
|
120
|
+
}
|
|
121
|
+
for h in handles {
|
|
122
|
+
h.join().expect("a handler thread panicked");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// All calls returned without hanging. Read the shared counters back.
|
|
126
|
+
let s = stats.call(&[]);
|
|
127
|
+
let served = read_num(&s, "served");
|
|
128
|
+
let max_active = read_num(&s, "maxActive");
|
|
129
|
+
let active = read_num(&s, "active");
|
|
130
|
+
eprintln!(
|
|
131
|
+
"completed {total} concurrent calls / {THREADS} threads in {:?}; served={served}, maxActive={max_active}, active(final)={active}",
|
|
132
|
+
start.elapsed()
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// `served` is monotonic, so it is deterministically in (0, total] regardless of lost updates.
|
|
136
|
+
assert!(served > 0.0 && served <= total as f64, "served={served} out of plausible range (0, {total}]");
|
|
137
|
+
// `maxActive` >= 2 proves at least two handlers were genuinely in-flight simultaneously, i.e. we
|
|
138
|
+
// actually exercised concurrent shared-state mutation (not an accidentally-serialized run).
|
|
139
|
+
assert!(max_active >= 2.0, "handlers never overlapped (maxActive={max_active}); test did not exercise concurrency");
|
|
140
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//! `DeclareVar` + block scopes: function `let` shadows script-level names (bytecode VM).
|
|
2
|
+
|
|
3
|
+
use tishlang_bytecode::compile;
|
|
4
|
+
use tishlang_vm::run;
|
|
5
|
+
|
|
6
|
+
#[test]
|
|
7
|
+
fn declare_var_shadows_script_let_inside_fn() {
|
|
8
|
+
let src = r#"
|
|
9
|
+
let x = 1
|
|
10
|
+
fn f() {
|
|
11
|
+
let x = 2
|
|
12
|
+
return x
|
|
13
|
+
}
|
|
14
|
+
let r = f()
|
|
15
|
+
console.log("script", x, "fn", r)
|
|
16
|
+
"#;
|
|
17
|
+
let program = tishlang_parser::parse(src).expect("parse");
|
|
18
|
+
let chunk = compile(&program).expect("compile");
|
|
19
|
+
run(&chunk).expect("run");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[test]
|
|
23
|
+
fn block_let_restores_outer_binding() {
|
|
24
|
+
let src = r#"
|
|
25
|
+
let x = 1
|
|
26
|
+
{
|
|
27
|
+
let x = 2
|
|
28
|
+
}
|
|
29
|
+
console.log(x)
|
|
30
|
+
"#;
|
|
31
|
+
let program = tishlang_parser::parse(src).expect("parse");
|
|
32
|
+
let chunk = compile(&program).expect("compile");
|
|
33
|
+
run(&chunk).expect("run");
|
|
34
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
//! Regression: bytecode peephole `chain_jumps` must not follow `JumpIfFalse` as if it were an
|
|
2
|
+
//! unconditional `Jump`. Doing so broke `===` + `||` when nested as the condition of an outer `if`
|
|
3
|
+
//! (default VM differed from `--backend interp` / `--no-optimize`).
|
|
4
|
+
//!
|
|
5
|
+
//! CLI parity for the same source is covered in `crates/tish/tests/run_optimize_stdout_parity.rs`.
|
|
6
|
+
|
|
7
|
+
use std::path::PathBuf;
|
|
8
|
+
|
|
9
|
+
use tishlang_bytecode::{
|
|
10
|
+
compile, compile_for_repl, compile_for_repl_unoptimized, compile_unoptimized,
|
|
11
|
+
};
|
|
12
|
+
use tishlang_core::Value;
|
|
13
|
+
|
|
14
|
+
fn run_chunk(chunk: &tishlang_bytecode::Chunk) -> Value {
|
|
15
|
+
tishlang_vm::run(chunk).expect("vm run")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// `tish run` ends with trailing `null` when the last statement is not a REPL-style expr; use
|
|
19
|
+
/// `compile_for_repl` so the VM return value reflects the `||` result (catches peephole/AST bugs).
|
|
20
|
+
#[test]
|
|
21
|
+
fn string_strict_eq_logical_or_repl_last_expr_is_true() {
|
|
22
|
+
let src = "let cmd = \"a\"\ncmd === \"a\" || cmd === \"b\"";
|
|
23
|
+
let opt = tishlang_opt::optimize(&tishlang_parser::parse(src).expect("parse"));
|
|
24
|
+
let v_peep = run_chunk(&compile_for_repl(&opt).expect("compile repl"));
|
|
25
|
+
let v_unopt = run_chunk(&compile_for_repl_unoptimized(&opt).expect("compile repl unopt"));
|
|
26
|
+
assert!(
|
|
27
|
+
v_peep.strict_eq(&v_unopt),
|
|
28
|
+
"peephole vs unopt repl: peep={v_peep:?} unopt={v_unopt:?}"
|
|
29
|
+
);
|
|
30
|
+
assert!(
|
|
31
|
+
matches!(&v_peep, Value::Bool(true)),
|
|
32
|
+
"expected true for cmd===a||cmd===b with cmd=a, got {v_peep:?}"
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// `?:` uses different codegen than `if`; both must agree with unoptimized bytecode.
|
|
37
|
+
#[test]
|
|
38
|
+
fn string_strict_eq_logical_or_inside_ternary_repl_last_expr() {
|
|
39
|
+
// Statement boundary: without `;` or `;`-like ASI, the parser can tie the `(` line to `let`.
|
|
40
|
+
let src = "let cmd = \"a\"\n;(cmd === \"a\" || cmd === \"b\") ? 1 : 0";
|
|
41
|
+
let opt = tishlang_opt::optimize(&tishlang_parser::parse(src).expect("parse"));
|
|
42
|
+
let v_peep = run_chunk(&compile_for_repl(&opt).expect("compile repl"));
|
|
43
|
+
let v_unopt = run_chunk(&compile_for_repl_unoptimized(&opt).expect("compile repl unopt"));
|
|
44
|
+
assert!(
|
|
45
|
+
v_peep.strict_eq(&v_unopt),
|
|
46
|
+
"peep={v_peep:?} unopt={v_unopt:?}"
|
|
47
|
+
);
|
|
48
|
+
assert!(
|
|
49
|
+
matches!(&v_peep, Value::Number(n) if *n == 1.0),
|
|
50
|
+
"expected 1, got {v_peep:?}"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[test]
|
|
55
|
+
fn logical_or_strict_eq_peephole_matches_unoptimized() {
|
|
56
|
+
let src = "let a = 1\nlet b = 2\na === 1 || b === 2";
|
|
57
|
+
let program = tishlang_parser::parse(src).expect("parse");
|
|
58
|
+
let program = tishlang_opt::optimize(&program);
|
|
59
|
+
|
|
60
|
+
let v_peep = run_chunk(&compile(&program).expect("compile"));
|
|
61
|
+
let v_raw = run_chunk(&compile_unoptimized(&program).expect("compile unopt"));
|
|
62
|
+
assert!(
|
|
63
|
+
v_peep.strict_eq(&v_raw),
|
|
64
|
+
"peephole changed semantics: peep={v_peep:?} raw={v_raw:?}"
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
let v_peep_repl = run_chunk(&compile_for_repl(&program).expect("compile repl"));
|
|
68
|
+
let v_raw_repl =
|
|
69
|
+
run_chunk(&compile_for_repl_unoptimized(&program).expect("compile repl unopt"));
|
|
70
|
+
assert!(
|
|
71
|
+
v_peep_repl.strict_eq(&v_raw_repl),
|
|
72
|
+
"repl: peep={v_peep_repl:?} raw={v_raw_repl:?}"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[test]
|
|
77
|
+
fn logical_or_inside_if_condition_peephole_matches_unoptimized() {
|
|
78
|
+
let src = "let a = 1\nlet b = 2\nif (a === 1 || b === 2) { 1 } else { 0 }";
|
|
79
|
+
let program = tishlang_parser::parse(src).expect("parse");
|
|
80
|
+
let program = tishlang_opt::optimize(&program);
|
|
81
|
+
|
|
82
|
+
let v_peep = run_chunk(&compile(&program).expect("compile"));
|
|
83
|
+
let v_raw = run_chunk(&compile_unoptimized(&program).expect("compile unopt"));
|
|
84
|
+
assert!(
|
|
85
|
+
v_peep.strict_eq(&v_raw),
|
|
86
|
+
"if + || : peep={v_peep:?} raw={v_raw:?}"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[test]
|
|
91
|
+
fn string_strict_eq_logical_or_ast_opt_matches_unoptimized_bytecode() {
|
|
92
|
+
let src = "let cmd = \"a\"\nif (cmd === \"a\" || cmd === \"b\") { 1 } else { 0 }";
|
|
93
|
+
let raw = tishlang_parser::parse(src).expect("parse");
|
|
94
|
+
let opt = tishlang_opt::optimize(&raw);
|
|
95
|
+
let v_raw = run_chunk(&compile_unoptimized(&raw).expect("raw"));
|
|
96
|
+
let v_opt = run_chunk(&compile_unoptimized(&opt).expect("opt"));
|
|
97
|
+
assert!(
|
|
98
|
+
v_raw.strict_eq(&v_opt),
|
|
99
|
+
"AST optimizer changed semantics: raw={v_raw:?} opt={v_opt:?}"
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#[test]
|
|
104
|
+
fn string_strict_eq_logical_or_peephole_matches_unoptimized() {
|
|
105
|
+
let src = "let cmd = \"a\"\nif (cmd === \"a\" || cmd === \"b\") { 1 } else { 0 }";
|
|
106
|
+
let program = tishlang_opt::optimize(&tishlang_parser::parse(src).expect("parse"));
|
|
107
|
+
let v_peep = run_chunk(&compile(&program).expect("compile"));
|
|
108
|
+
let v_raw = run_chunk(&compile_unoptimized(&program).expect("unopt"));
|
|
109
|
+
assert!(
|
|
110
|
+
v_peep.strict_eq(&v_raw),
|
|
111
|
+
"peephole + strings: peep={v_peep:?} raw={v_raw:?}"
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// `tish run path/to/file.tish` uses merge_modules; ensure that matches plain parse for the fixture.
|
|
116
|
+
#[test]
|
|
117
|
+
fn merged_module_program_bytecode_matches_parse_for_string_or_fixture() {
|
|
118
|
+
let fixture =
|
|
119
|
+
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/or_string_cmd.tish");
|
|
120
|
+
let src = std::fs::read_to_string(&fixture).expect("read fixture");
|
|
121
|
+
let modules = tishlang_compile::resolve_project(&fixture, Some(fixture.parent().unwrap()))
|
|
122
|
+
.expect("resolve");
|
|
123
|
+
let merged = tishlang_compile::merge_modules(modules)
|
|
124
|
+
.expect("merge")
|
|
125
|
+
.program;
|
|
126
|
+
let flat = tishlang_parser::parse(&src).expect("parse");
|
|
127
|
+
let m_opt = tishlang_opt::optimize(&merged);
|
|
128
|
+
let f_opt = tishlang_opt::optimize(&flat);
|
|
129
|
+
let c_m = compile(&m_opt).expect("compile merged");
|
|
130
|
+
let c_f = compile(&f_opt).expect("compile flat");
|
|
131
|
+
assert_eq!(
|
|
132
|
+
c_m.code, c_f.code,
|
|
133
|
+
"merge_modules vs parse produced different bytecode"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// `if (cmd === "a" || cmd === "b")` must match unoptimized VM semantics (Nop padding from other
|
|
138
|
+
/// peepholes must not confuse `chain_jumps`).
|
|
139
|
+
#[test]
|
|
140
|
+
fn string_eq_or_in_if_stmt_matches_unoptimized_repl() {
|
|
141
|
+
let src = "let cmd = \"a\"\nlet ok = false\nif (cmd === \"a\" || cmd === \"b\") { ok = true } else { ok = false }\nok";
|
|
142
|
+
let program = tishlang_opt::optimize(&tishlang_parser::parse(src).expect("parse"));
|
|
143
|
+
let v_peep = run_chunk(&compile_for_repl(&program).expect("compile repl"));
|
|
144
|
+
let v_raw = run_chunk(&compile_for_repl_unoptimized(&program).expect("compile repl unopt"));
|
|
145
|
+
assert!(v_peep.strict_eq(&v_raw), "peep={v_peep:?} raw={v_raw:?}");
|
|
146
|
+
assert!(
|
|
147
|
+
matches!(&v_peep, Value::Bool(true)),
|
|
148
|
+
"expected ok=true, got {v_peep:?}"
|
|
149
|
+
);
|
|
150
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "tishlang_wasm"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "WebAssembly backend for Tish - compiles to real .wasm"
|
|
6
|
+
|
|
7
|
+
license-file = { workspace = true }
|
|
8
|
+
repository = { workspace = true }
|
|
9
|
+
[dependencies]
|
|
10
|
+
tishlang_build_utils = { path = "../tish_build_utils", version = ">=0.1" }
|
|
11
|
+
tishlang_ast = { path = "../tish_ast", version = ">=0.1" }
|
|
12
|
+
tishlang_compile = { path = "../tish_compile", version = ">=0.1" }
|
|
13
|
+
tishlang_bytecode = { path = "../tish_bytecode", version = ">=0.1" }
|
|
14
|
+
tishlang_opt = { path = "../tish_opt", version = ">=0.1" }
|
|
15
|
+
base64 = "0.22"
|