@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
|
@@ -7,16 +7,18 @@
|
|
|
7
7
|
//! - Compiled outputs are cached under `target/integration_compile_cache/` per backend.
|
|
8
8
|
|
|
9
9
|
use std::collections::hash_map::DefaultHasher;
|
|
10
|
+
use std::ffi::OsString;
|
|
10
11
|
use std::hash::{Hash, Hasher};
|
|
11
12
|
use std::io::Read;
|
|
12
|
-
use std::ffi::OsString;
|
|
13
13
|
use std::path::{Path, PathBuf};
|
|
14
14
|
use std::process::Command;
|
|
15
15
|
|
|
16
16
|
use rayon::prelude::*;
|
|
17
17
|
|
|
18
18
|
fn workspace_root() -> PathBuf {
|
|
19
|
-
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
19
|
+
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
20
|
+
.join("..")
|
|
21
|
+
.join("..")
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
fn core_dir() -> PathBuf {
|
|
@@ -25,7 +27,10 @@ fn core_dir() -> PathBuf {
|
|
|
25
27
|
|
|
26
28
|
/// Path to the static expected stdout for a .tish file (e.g. fn_any.tish -> fn_any.tish.expected).
|
|
27
29
|
fn expected_path(path: &Path) -> PathBuf {
|
|
28
|
-
path.with_file_name(format!(
|
|
30
|
+
path.with_file_name(format!(
|
|
31
|
+
"{}.expected",
|
|
32
|
+
path.file_name().unwrap().to_string_lossy()
|
|
33
|
+
))
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
/// Read static expected stdout for a test file. Returns None if the file does not exist.
|
|
@@ -70,7 +75,11 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
70
75
|
|
|
71
76
|
let (artifact_path, compile_args): (PathBuf, Vec<OsString>) = match backend {
|
|
72
77
|
"native" => {
|
|
73
|
-
let ext = if cfg!(target_os = "windows") {
|
|
78
|
+
let ext = if cfg!(target_os = "windows") {
|
|
79
|
+
".exe"
|
|
80
|
+
} else {
|
|
81
|
+
""
|
|
82
|
+
};
|
|
74
83
|
let cached = cache_base.join(format!("{}_{}{}", stem, hash8, ext));
|
|
75
84
|
let args = vec![
|
|
76
85
|
OsString::from("build"),
|
|
@@ -81,7 +90,11 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
81
90
|
(cached, args)
|
|
82
91
|
}
|
|
83
92
|
"cranelift" => {
|
|
84
|
-
let ext = if cfg!(target_os = "windows") {
|
|
93
|
+
let ext = if cfg!(target_os = "windows") {
|
|
94
|
+
".exe"
|
|
95
|
+
} else {
|
|
96
|
+
""
|
|
97
|
+
};
|
|
85
98
|
let cached = cache_base.join(format!("{}_{}{}", stem, hash8, ext));
|
|
86
99
|
let args = vec![
|
|
87
100
|
OsString::from("build"),
|
|
@@ -137,8 +150,12 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
137
150
|
}
|
|
138
151
|
|
|
139
152
|
// Copy to temp so caller can run and delete without touching cache.
|
|
140
|
-
let ext = artifact_path
|
|
141
|
-
|
|
153
|
+
let ext = artifact_path
|
|
154
|
+
.extension()
|
|
155
|
+
.map(|e| e.to_string_lossy().to_string())
|
|
156
|
+
.unwrap_or_default();
|
|
157
|
+
let temp_dest =
|
|
158
|
+
std::env::temp_dir().join(format!("tish_cached_{}_{}_{}", backend, stem, hash8));
|
|
142
159
|
let temp_dest = if ext.is_empty() {
|
|
143
160
|
temp_dest
|
|
144
161
|
} else {
|
|
@@ -151,12 +168,20 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
151
168
|
/// Path to the tish CLI binary. When running under cargo-llvm-cov, the build goes to
|
|
152
169
|
/// target/llvm-cov-target and CARGO_TARGET_DIR may not be set for the test process.
|
|
153
170
|
fn tish_bin() -> PathBuf {
|
|
154
|
-
let bin_name = if cfg!(target_os = "windows") {
|
|
171
|
+
let bin_name = if cfg!(target_os = "windows") {
|
|
172
|
+
"tish.exe"
|
|
173
|
+
} else {
|
|
174
|
+
"tish"
|
|
175
|
+
};
|
|
155
176
|
let default = target_dir().join("debug").join(bin_name);
|
|
156
177
|
if default.exists() {
|
|
157
178
|
return default;
|
|
158
179
|
}
|
|
159
|
-
let llvm_cov = workspace_root()
|
|
180
|
+
let llvm_cov = workspace_root()
|
|
181
|
+
.join("target")
|
|
182
|
+
.join("llvm-cov-target")
|
|
183
|
+
.join("debug")
|
|
184
|
+
.join(bin_name);
|
|
160
185
|
if llvm_cov.exists() {
|
|
161
186
|
return llvm_cov;
|
|
162
187
|
}
|
|
@@ -167,9 +192,16 @@ fn tish_bin() -> PathBuf {
|
|
|
167
192
|
#[test]
|
|
168
193
|
fn test_tish_version_flag() {
|
|
169
194
|
let bin = tish_bin();
|
|
170
|
-
assert!(
|
|
195
|
+
assert!(
|
|
196
|
+
bin.exists(),
|
|
197
|
+
"tish binary not found. Run `cargo build -p tishlang` first."
|
|
198
|
+
);
|
|
171
199
|
let out = Command::new(&bin).arg("-V").output().expect("run tish -V");
|
|
172
|
-
assert!(
|
|
200
|
+
assert!(
|
|
201
|
+
out.status.success(),
|
|
202
|
+
"tish -V failed: {}",
|
|
203
|
+
String::from_utf8_lossy(&out.stderr)
|
|
204
|
+
);
|
|
173
205
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
174
206
|
assert!(
|
|
175
207
|
stdout.contains(env!("CARGO_PKG_VERSION")),
|
|
@@ -177,20 +209,35 @@ fn test_tish_version_flag() {
|
|
|
177
209
|
env!("CARGO_PKG_VERSION"),
|
|
178
210
|
stdout
|
|
179
211
|
);
|
|
180
|
-
let out2 = Command::new(&bin)
|
|
212
|
+
let out2 = Command::new(&bin)
|
|
213
|
+
.arg("--version")
|
|
214
|
+
.output()
|
|
215
|
+
.expect("run tish --version");
|
|
181
216
|
assert!(out2.status.success());
|
|
182
217
|
let stdout2 = String::from_utf8_lossy(&out2.stdout);
|
|
183
|
-
assert!(
|
|
218
|
+
assert!(
|
|
219
|
+
stdout2.contains(env!("CARGO_PKG_VERSION")),
|
|
220
|
+
"tish --version should print version"
|
|
221
|
+
);
|
|
184
222
|
}
|
|
185
223
|
|
|
186
224
|
/// Parse async-await example (validates async fn parsing).
|
|
187
225
|
#[test]
|
|
188
226
|
fn test_async_await_parse() {
|
|
189
|
-
let path = workspace_root()
|
|
227
|
+
let path = workspace_root()
|
|
228
|
+
.join("examples")
|
|
229
|
+
.join("async-await")
|
|
230
|
+
.join("src")
|
|
231
|
+
.join("main.tish");
|
|
190
232
|
if path.exists() {
|
|
191
233
|
let source = std::fs::read_to_string(&path).unwrap();
|
|
192
234
|
let result = tishlang_parser::parse(&source);
|
|
193
|
-
assert!(
|
|
235
|
+
assert!(
|
|
236
|
+
result.is_ok(),
|
|
237
|
+
"Parse failed for {}: {:?}",
|
|
238
|
+
path.display(),
|
|
239
|
+
result.err()
|
|
240
|
+
);
|
|
194
241
|
}
|
|
195
242
|
}
|
|
196
243
|
|
|
@@ -199,11 +246,20 @@ fn test_async_await_parse() {
|
|
|
199
246
|
#[cfg(feature = "http")]
|
|
200
247
|
fn test_async_await_compile_via_binary() {
|
|
201
248
|
let bin = tish_bin();
|
|
202
|
-
let path = workspace_root()
|
|
249
|
+
let path = workspace_root()
|
|
250
|
+
.join("examples")
|
|
251
|
+
.join("async-await")
|
|
252
|
+
.join("src")
|
|
253
|
+
.join("main.tish");
|
|
203
254
|
if path.exists() && bin.exists() {
|
|
204
255
|
let out = std::env::temp_dir().join("tish_async_test_out");
|
|
205
256
|
let compile_result = Command::new(&bin)
|
|
206
|
-
.args([
|
|
257
|
+
.args([
|
|
258
|
+
"build",
|
|
259
|
+
path.to_string_lossy().as_ref(),
|
|
260
|
+
"-o",
|
|
261
|
+
out.to_string_lossy().as_ref(),
|
|
262
|
+
])
|
|
207
263
|
.current_dir(workspace_root())
|
|
208
264
|
.output();
|
|
209
265
|
let compile_out = compile_result.expect("run tish build");
|
|
@@ -213,9 +269,7 @@ fn test_async_await_compile_via_binary() {
|
|
|
213
269
|
String::from_utf8_lossy(&compile_out.stderr)
|
|
214
270
|
);
|
|
215
271
|
// Run compiled binary to validate non-blocking fetchAll executes correctly
|
|
216
|
-
let run_result = Command::new(&out)
|
|
217
|
-
.current_dir(workspace_root())
|
|
218
|
-
.output();
|
|
272
|
+
let run_result = Command::new(&out).current_dir(workspace_root()).output();
|
|
219
273
|
let run_out = run_result.expect("run compiled async binary");
|
|
220
274
|
assert!(
|
|
221
275
|
run_out.status.success(),
|
|
@@ -223,7 +277,10 @@ fn test_async_await_compile_via_binary() {
|
|
|
223
277
|
String::from_utf8_lossy(&run_out.stderr)
|
|
224
278
|
);
|
|
225
279
|
let stdout = String::from_utf8_lossy(&run_out.stdout);
|
|
226
|
-
assert!(
|
|
280
|
+
assert!(
|
|
281
|
+
stdout.contains("Fetching"),
|
|
282
|
+
"expected output to mention fetching"
|
|
283
|
+
);
|
|
227
284
|
assert!(stdout.contains("Done"), "expected output to contain Done");
|
|
228
285
|
}
|
|
229
286
|
}
|
|
@@ -235,8 +292,16 @@ fn test_async_await_compile_via_binary() {
|
|
|
235
292
|
#[ignore = "timing and network sensitive; run manually: cargo test test_async_parallel_vs_sequential_timing -p tishlang--features http -- --ignored"]
|
|
236
293
|
fn test_async_parallel_vs_sequential_timing() {
|
|
237
294
|
let bin = tish_bin();
|
|
238
|
-
let parallel_src = workspace_root()
|
|
239
|
-
|
|
295
|
+
let parallel_src = workspace_root()
|
|
296
|
+
.join("examples")
|
|
297
|
+
.join("async-await")
|
|
298
|
+
.join("src")
|
|
299
|
+
.join("parallel.tish");
|
|
300
|
+
let sequential_src = workspace_root()
|
|
301
|
+
.join("examples")
|
|
302
|
+
.join("async-await")
|
|
303
|
+
.join("src")
|
|
304
|
+
.join("sequential.tish");
|
|
240
305
|
if !parallel_src.exists() || !sequential_src.exists() || !bin.exists() {
|
|
241
306
|
return;
|
|
242
307
|
}
|
|
@@ -245,28 +310,58 @@ fn test_async_parallel_vs_sequential_timing() {
|
|
|
245
310
|
|
|
246
311
|
// Compile both
|
|
247
312
|
let compile_par = Command::new(&bin)
|
|
248
|
-
.args([
|
|
313
|
+
.args([
|
|
314
|
+
"build",
|
|
315
|
+
parallel_src.to_string_lossy().as_ref(),
|
|
316
|
+
"-o",
|
|
317
|
+
out_parallel.to_string_lossy().as_ref(),
|
|
318
|
+
])
|
|
249
319
|
.current_dir(workspace_root())
|
|
250
320
|
.output();
|
|
251
|
-
assert!(
|
|
321
|
+
assert!(
|
|
322
|
+
compile_par.as_ref().unwrap().status.success(),
|
|
323
|
+
"compile parallel: {}",
|
|
324
|
+
String::from_utf8_lossy(&compile_par.as_ref().unwrap().stderr)
|
|
325
|
+
);
|
|
252
326
|
|
|
253
327
|
let compile_seq = Command::new(&bin)
|
|
254
|
-
.args([
|
|
328
|
+
.args([
|
|
329
|
+
"build",
|
|
330
|
+
sequential_src.to_string_lossy().as_ref(),
|
|
331
|
+
"-o",
|
|
332
|
+
out_sequential.to_string_lossy().as_ref(),
|
|
333
|
+
])
|
|
255
334
|
.current_dir(workspace_root())
|
|
256
335
|
.output();
|
|
257
|
-
assert!(
|
|
336
|
+
assert!(
|
|
337
|
+
compile_seq.as_ref().unwrap().status.success(),
|
|
338
|
+
"compile sequential: {}",
|
|
339
|
+
String::from_utf8_lossy(&compile_seq.as_ref().unwrap().stderr)
|
|
340
|
+
);
|
|
258
341
|
|
|
259
342
|
// Run parallel and time
|
|
260
343
|
let t_parallel = std::time::Instant::now();
|
|
261
|
-
let run_par = Command::new(&out_parallel)
|
|
344
|
+
let run_par = Command::new(&out_parallel)
|
|
345
|
+
.current_dir(workspace_root())
|
|
346
|
+
.output();
|
|
262
347
|
let elapsed_parallel = t_parallel.elapsed();
|
|
263
|
-
assert!(
|
|
348
|
+
assert!(
|
|
349
|
+
run_par.as_ref().unwrap().status.success(),
|
|
350
|
+
"run parallel: {}",
|
|
351
|
+
String::from_utf8_lossy(&run_par.as_ref().unwrap().stderr)
|
|
352
|
+
);
|
|
264
353
|
|
|
265
354
|
// Run sequential and time
|
|
266
355
|
let t_sequential = std::time::Instant::now();
|
|
267
|
-
let run_seq = Command::new(&out_sequential)
|
|
356
|
+
let run_seq = Command::new(&out_sequential)
|
|
357
|
+
.current_dir(workspace_root())
|
|
358
|
+
.output();
|
|
268
359
|
let elapsed_sequential = t_sequential.elapsed();
|
|
269
|
-
assert!(
|
|
360
|
+
assert!(
|
|
361
|
+
run_seq.as_ref().unwrap().status.success(),
|
|
362
|
+
"run sequential: {}",
|
|
363
|
+
String::from_utf8_lossy(&run_seq.as_ref().unwrap().stderr)
|
|
364
|
+
);
|
|
270
365
|
|
|
271
366
|
// PARALLEL MUST BE FASTER: parallel < sequential * 0.6 (parallel ~1s, sequential ~3s)
|
|
272
367
|
let parallel_secs = elapsed_parallel.as_secs_f64();
|
|
@@ -285,11 +380,20 @@ fn test_async_parallel_vs_sequential_timing() {
|
|
|
285
380
|
#[cfg(feature = "http")]
|
|
286
381
|
#[ignore = "requires async runtime; use test_async_await_compile_via_binary for CI"]
|
|
287
382
|
fn test_async_await_run() {
|
|
288
|
-
let path = workspace_root()
|
|
383
|
+
let path = workspace_root()
|
|
384
|
+
.join("examples")
|
|
385
|
+
.join("async-await")
|
|
386
|
+
.join("src")
|
|
387
|
+
.join("main.tish");
|
|
289
388
|
if path.exists() {
|
|
290
389
|
let source = std::fs::read_to_string(&path).unwrap();
|
|
291
390
|
let result = tishlang_eval::run(&source);
|
|
292
|
-
assert!(
|
|
391
|
+
assert!(
|
|
392
|
+
result.is_ok(),
|
|
393
|
+
"Run failed for {}: {:?}",
|
|
394
|
+
path.display(),
|
|
395
|
+
result.err()
|
|
396
|
+
);
|
|
293
397
|
}
|
|
294
398
|
}
|
|
295
399
|
|
|
@@ -300,7 +404,10 @@ fn test_async_await_run() {
|
|
|
300
404
|
#[ignore = "requires async runtime"]
|
|
301
405
|
fn test_promise_and_settimeout() {
|
|
302
406
|
for name in ["promise", "settimeout"] {
|
|
303
|
-
let path = workspace_root()
|
|
407
|
+
let path = workspace_root()
|
|
408
|
+
.join("tests")
|
|
409
|
+
.join("modules")
|
|
410
|
+
.join(format!("{}.tish", name));
|
|
304
411
|
if path.exists() {
|
|
305
412
|
let source = std::fs::read_to_string(&path).unwrap();
|
|
306
413
|
let result = tishlang_eval::run(&source);
|
|
@@ -338,7 +445,10 @@ fn test_async_promise_settimeout_combined() {
|
|
|
338
445
|
/// VM run with Date global (resolve+merge+bytecode+run pipeline).
|
|
339
446
|
#[test]
|
|
340
447
|
fn test_vm_date_now() {
|
|
341
|
-
let path = workspace_root()
|
|
448
|
+
let path = workspace_root()
|
|
449
|
+
.join("tests")
|
|
450
|
+
.join("core")
|
|
451
|
+
.join("date.tish");
|
|
342
452
|
if !path.exists() {
|
|
343
453
|
return;
|
|
344
454
|
}
|
|
@@ -348,7 +458,11 @@ fn test_vm_date_now() {
|
|
|
348
458
|
let program = tishlang_compile::merge_modules(modules).expect("merge");
|
|
349
459
|
let chunk = tishlang_bytecode::compile(&program).expect("compile");
|
|
350
460
|
let result = tishlang_vm::run(&chunk);
|
|
351
|
-
assert!(
|
|
461
|
+
assert!(
|
|
462
|
+
result.is_ok(),
|
|
463
|
+
"VM run (library) failed: {:?}",
|
|
464
|
+
result.err()
|
|
465
|
+
);
|
|
352
466
|
// Binary path - same flow as `tish run <file>`
|
|
353
467
|
let bin = tish_bin();
|
|
354
468
|
if bin.exists() {
|
|
@@ -379,20 +493,30 @@ fn test_vm_index_assign_direct() {
|
|
|
379
493
|
/// VM run via resolve+merge (same as tish run) - must also pass.
|
|
380
494
|
#[test]
|
|
381
495
|
fn test_vm_index_assign_via_resolve() {
|
|
382
|
-
let path = workspace_root()
|
|
496
|
+
let path = workspace_root()
|
|
497
|
+
.join("tests")
|
|
498
|
+
.join("core")
|
|
499
|
+
.join("array_sort_minimal.tish");
|
|
383
500
|
let modules = tishlang_compile::resolve_project(&path, path.parent()).expect("resolve");
|
|
384
501
|
tishlang_compile::detect_cycles(&modules).expect("cycles");
|
|
385
502
|
let program = tishlang_compile::merge_modules(modules).expect("merge");
|
|
386
503
|
let chunk = tishlang_bytecode::compile(&program).expect("compile");
|
|
387
504
|
let result = tishlang_vm::run(&chunk);
|
|
388
|
-
assert!(
|
|
505
|
+
assert!(
|
|
506
|
+
result.is_ok(),
|
|
507
|
+
"VM IndexAssign via resolve failed: {:?}",
|
|
508
|
+
result.err()
|
|
509
|
+
);
|
|
389
510
|
}
|
|
390
511
|
|
|
391
512
|
/// tish run binary must pass array_sort_minimal (ensures CLI works).
|
|
392
513
|
#[test]
|
|
393
514
|
fn test_tish_run_index_assign() {
|
|
394
515
|
let bin = tish_bin();
|
|
395
|
-
let path = workspace_root()
|
|
516
|
+
let path = workspace_root()
|
|
517
|
+
.join("tests")
|
|
518
|
+
.join("core")
|
|
519
|
+
.join("array_sort_minimal.tish");
|
|
396
520
|
if !bin.exists() {
|
|
397
521
|
eprintln!("Skipping: tish binary not built");
|
|
398
522
|
return;
|
|
@@ -516,7 +640,12 @@ fn test_mvp_programs_interpreter() {
|
|
|
516
640
|
path.display()
|
|
517
641
|
)
|
|
518
642
|
});
|
|
519
|
-
assert_eq!(
|
|
643
|
+
assert_eq!(
|
|
644
|
+
stdout,
|
|
645
|
+
expected,
|
|
646
|
+
"Interpreter output mismatch for {}",
|
|
647
|
+
path.display()
|
|
648
|
+
);
|
|
520
649
|
}
|
|
521
650
|
}
|
|
522
651
|
}
|
|
@@ -562,7 +691,8 @@ fn test_mvp_programs_interp_vm_stdout_parity() {
|
|
|
562
691
|
let s_interp = String::from_utf8_lossy(&out_interp.stdout);
|
|
563
692
|
let s_vm = String::from_utf8_lossy(&out_vm.stdout);
|
|
564
693
|
assert_eq!(
|
|
565
|
-
s_interp,
|
|
694
|
+
s_interp,
|
|
695
|
+
s_vm,
|
|
566
696
|
"interp vs VM stdout mismatch for {}",
|
|
567
697
|
path.display()
|
|
568
698
|
);
|
|
@@ -603,7 +733,11 @@ fn test_mvp_programs_native() {
|
|
|
603
733
|
};
|
|
604
734
|
let _ = std::fs::remove_file(&out_bin);
|
|
605
735
|
if !out.status.success() {
|
|
606
|
-
return Some(format!(
|
|
736
|
+
return Some(format!(
|
|
737
|
+
"{}: {}",
|
|
738
|
+
path.display(),
|
|
739
|
+
String::from_utf8_lossy(&out.stderr)
|
|
740
|
+
));
|
|
607
741
|
}
|
|
608
742
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
609
743
|
if stdout != expected {
|
|
@@ -670,7 +804,11 @@ fn test_mvp_programs_cranelift() {
|
|
|
670
804
|
};
|
|
671
805
|
let _ = std::fs::remove_file(&out_bin);
|
|
672
806
|
if !out.status.success() {
|
|
673
|
-
return Some(format!(
|
|
807
|
+
return Some(format!(
|
|
808
|
+
"{}: {}",
|
|
809
|
+
path.display(),
|
|
810
|
+
String::from_utf8_lossy(&out.stderr)
|
|
811
|
+
));
|
|
674
812
|
}
|
|
675
813
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
676
814
|
if stdout != expected {
|
|
@@ -679,7 +817,11 @@ fn test_mvp_programs_cranelift() {
|
|
|
679
817
|
None
|
|
680
818
|
})
|
|
681
819
|
.collect();
|
|
682
|
-
assert!(
|
|
820
|
+
assert!(
|
|
821
|
+
errors.is_empty(),
|
|
822
|
+
"cranelift failures:\n{}",
|
|
823
|
+
errors.join("\n")
|
|
824
|
+
);
|
|
683
825
|
}
|
|
684
826
|
|
|
685
827
|
/// Compile each .tish file to WASI, run with wasmtime, and compare stdout to static expected (parallelized).
|
|
@@ -727,7 +869,11 @@ fn test_mvp_programs_wasi() {
|
|
|
727
869
|
};
|
|
728
870
|
let _ = std::fs::remove_file(&out_wasm);
|
|
729
871
|
if !out.status.success() {
|
|
730
|
-
return Some(format!(
|
|
872
|
+
return Some(format!(
|
|
873
|
+
"{}: {}",
|
|
874
|
+
path.display(),
|
|
875
|
+
String::from_utf8_lossy(&out.stderr)
|
|
876
|
+
));
|
|
731
877
|
}
|
|
732
878
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
733
879
|
if stdout != expected {
|
|
@@ -789,7 +935,11 @@ fn test_mvp_programs_js() {
|
|
|
789
935
|
String::from_utf8_lossy(&out.stderr)
|
|
790
936
|
);
|
|
791
937
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
792
|
-
assert_eq!(
|
|
938
|
+
assert_eq!(
|
|
939
|
+
stdout,
|
|
940
|
+
expected,
|
|
941
|
+
"JS output mismatch for {}",
|
|
942
|
+
path.display()
|
|
943
|
+
);
|
|
793
944
|
}
|
|
794
945
|
}
|
|
795
|
-
|
|
@@ -15,12 +15,7 @@ fn string_or_fixture_stdout_matches_with_and_without_optimize() {
|
|
|
15
15
|
assert!(fixture.is_file(), "missing fixture {}", fixture.display());
|
|
16
16
|
|
|
17
17
|
let out_default = Command::new(&tish)
|
|
18
|
-
.args([
|
|
19
|
-
"run",
|
|
20
|
-
"--feature",
|
|
21
|
-
"process",
|
|
22
|
-
fixture.to_str().unwrap(),
|
|
23
|
-
])
|
|
18
|
+
.args(["run", "--feature", "process", fixture.to_str().unwrap()])
|
|
24
19
|
.output()
|
|
25
20
|
.expect("spawn tish run");
|
|
26
21
|
assert!(
|
|
@@ -46,7 +41,8 @@ fn string_or_fixture_stdout_matches_with_and_without_optimize() {
|
|
|
46
41
|
);
|
|
47
42
|
|
|
48
43
|
assert_eq!(
|
|
49
|
-
out_default.stdout,
|
|
44
|
+
out_default.stdout,
|
|
45
|
+
out_noopt.stdout,
|
|
50
46
|
"stdout differs:\n default: {:?}\n noopt: {:?}",
|
|
51
47
|
String::from_utf8_lossy(&out_default.stdout),
|
|
52
48
|
String::from_utf8_lossy(&out_noopt.stdout)
|
|
@@ -14,7 +14,10 @@ fn test_and_shortcircuit_emits_jump() {
|
|
|
14
14
|
let chunk = compile_unoptimized(&program).expect("compile");
|
|
15
15
|
let code = &chunk.code;
|
|
16
16
|
let has_jump_if_false = code.windows(1).any(|w| w[0] == Opcode::JumpIfFalse as u8);
|
|
17
|
-
assert!(
|
|
17
|
+
assert!(
|
|
18
|
+
has_jump_if_false,
|
|
19
|
+
"And should emit JumpIfFalse for short-circuit"
|
|
20
|
+
);
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
#[test]
|
|
@@ -23,7 +26,11 @@ fn test_and_shortcircuit_runs_unoptimized() {
|
|
|
23
26
|
let program = parse(source).expect("parse");
|
|
24
27
|
let chunk = compile_unoptimized(&program).expect("compile");
|
|
25
28
|
let result = tishlang_vm::run(&chunk);
|
|
26
|
-
assert!(
|
|
29
|
+
assert!(
|
|
30
|
+
result.is_ok(),
|
|
31
|
+
"Should not throw (short-circuit avoids x.foo): {:?}",
|
|
32
|
+
result.err()
|
|
33
|
+
);
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
#[test]
|
|
@@ -33,7 +40,11 @@ fn test_and_shortcircuit_runs_optimized() {
|
|
|
33
40
|
let program = tishlang_opt::optimize(&program);
|
|
34
41
|
let chunk = tishlang_bytecode::compile(&program).expect("compile");
|
|
35
42
|
let result = tishlang_vm::run(&chunk);
|
|
36
|
-
assert!(
|
|
43
|
+
assert!(
|
|
44
|
+
result.is_ok(),
|
|
45
|
+
"Should not throw with peephole (short-circuit): {:?}",
|
|
46
|
+
result.err()
|
|
47
|
+
);
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
#[test]
|
|
@@ -46,5 +57,9 @@ fn test_and_shortcircuit_via_resolve_project() {
|
|
|
46
57
|
let program = tishlang_opt::optimize(&program); // Mirror CLI
|
|
47
58
|
let chunk = compile(&program).expect("compile");
|
|
48
59
|
let result = tishlang_vm::run(&chunk);
|
|
49
|
-
assert!(
|
|
60
|
+
assert!(
|
|
61
|
+
result.is_ok(),
|
|
62
|
+
"Should not throw via resolve+merge+opt (CLI path): {:?}",
|
|
63
|
+
result.err()
|
|
64
|
+
);
|
|
50
65
|
}
|
|
@@ -34,7 +34,6 @@ pub struct TypedParam {
|
|
|
34
34
|
pub default: Option<Expr>,
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
38
37
|
/// Single formal parameter: simple identifier or destructuring pattern.
|
|
39
38
|
#[derive(Debug, Clone, PartialEq)]
|
|
40
39
|
pub enum FunParam {
|
|
@@ -46,7 +45,6 @@ pub enum FunParam {
|
|
|
46
45
|
},
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
|
|
50
48
|
impl FunParam {
|
|
51
49
|
/// Variable names introduced by this formal parameter.
|
|
52
50
|
pub fn bound_names(&self) -> Vec<Arc<str>> {
|
|
@@ -123,7 +121,10 @@ pub struct DestructProp {
|
|
|
123
121
|
#[derive(Debug, Clone, PartialEq)]
|
|
124
122
|
pub enum ImportSpecifier {
|
|
125
123
|
/// Named: { foo } or { foo as bar }
|
|
126
|
-
Named {
|
|
124
|
+
Named {
|
|
125
|
+
name: Arc<str>,
|
|
126
|
+
alias: Option<Arc<str>>,
|
|
127
|
+
},
|
|
127
128
|
/// Namespace: * as M
|
|
128
129
|
Namespace(Arc<str>),
|
|
129
130
|
/// Default: import X from "..."
|
|
@@ -366,8 +367,8 @@ pub enum Expr {
|
|
|
366
367
|
},
|
|
367
368
|
/// Template literal: `text ${expr} text`
|
|
368
369
|
TemplateLiteral {
|
|
369
|
-
quasis: Vec<Arc<str>>,
|
|
370
|
-
exprs: Vec<Expr>,
|
|
370
|
+
quasis: Vec<Arc<str>>, // Static string parts (n+1 for n expressions)
|
|
371
|
+
exprs: Vec<Expr>, // Interpolated expressions (n)
|
|
371
372
|
span: Span,
|
|
372
373
|
},
|
|
373
374
|
/// Await expression: await operand
|
|
@@ -399,10 +400,7 @@ pub enum Expr {
|
|
|
399
400
|
#[derive(Debug, Clone, PartialEq)]
|
|
400
401
|
pub enum JsxProp {
|
|
401
402
|
/// name="value" or name={expr} or name (boolean shorthand)
|
|
402
|
-
Attr {
|
|
403
|
-
name: Arc<str>,
|
|
404
|
-
value: JsxAttrValue,
|
|
405
|
-
},
|
|
403
|
+
Attr { name: Arc<str>, value: JsxAttrValue },
|
|
406
404
|
/// {...expr}
|
|
407
405
|
Spread(Expr),
|
|
408
406
|
}
|
|
@@ -493,11 +491,11 @@ pub enum CallArg {
|
|
|
493
491
|
|
|
494
492
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
495
493
|
pub enum CompoundOp {
|
|
496
|
-
Add,
|
|
497
|
-
Sub,
|
|
498
|
-
Mul,
|
|
499
|
-
Div,
|
|
500
|
-
Mod,
|
|
494
|
+
Add, // +=
|
|
495
|
+
Sub, // -=
|
|
496
|
+
Mul, // *=
|
|
497
|
+
Div, // /=
|
|
498
|
+
Mod, // %=
|
|
501
499
|
}
|
|
502
500
|
|
|
503
501
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|