@tishlang/tish 1.6.0 → 1.8.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 +2 -0
- package/README.md +2 -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 +128 -137
- package/crates/js_to_tish/src/transform/stmt.rs +62 -32
- package/crates/tish/Cargo.toml +15 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +92 -39
- package/crates/tish/src/main.rs +172 -86
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +4 -2
- package/crates/tish/tests/integration_test.rs +216 -54
- package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
- package/crates/tish/tests/shortcircuit.rs +20 -5
- package/crates/tish_ast/src/ast.rs +92 -23
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +136 -8
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +65 -33
- package/crates/tish_builtins/src/construct.rs +34 -39
- package/crates/tish_builtins/src/globals.rs +42 -26
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/lib.rs +5 -5
- package/crates/tish_builtins/src/math.rs +5 -3
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +144 -22
- package/crates/tish_bytecode/src/chunk.rs +0 -1
- package/crates/tish_bytecode/src/compiler.rs +173 -71
- package/crates/tish_bytecode/src/opcode.rs +24 -6
- package/crates/tish_bytecode/src/peephole.rs +2 -2
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +1621 -453
- package/crates/tish_compile/src/infer.rs +75 -19
- package/crates/tish_compile/src/lib.rs +19 -8
- package/crates/tish_compile/src/resolve.rs +278 -137
- package/crates/tish_compile/src/types.rs +184 -24
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +181 -37
- package/crates/tish_compile_js/src/lib.rs +3 -1
- 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 +69 -59
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +107 -56
- package/crates/tish_core/src/lib.rs +4 -2
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/uri.rs +9 -6
- package/crates/tish_core/src/value.rs +145 -43
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_cranelift/src/link.rs +6 -9
- package/crates/tish_cranelift/src/lower.rs +14 -8
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +474 -165
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +12 -8
- package/crates/tish_eval/src/natives.rs +136 -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 +17 -6
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +149 -43
- package/crates/tish_lexer/src/lib.rs +232 -63
- package/crates/tish_lexer/src/token.rs +10 -6
- package/crates/tish_llvm/src/lib.rs +17 -8
- package/crates/tish_lsp/Cargo.toml +4 -1
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_lsp/src/builtin_goto.rs +261 -0
- package/crates/tish_lsp/src/import_goto.rs +549 -0
- package/crates/tish_lsp/src/main.rs +504 -106
- package/crates/tish_native/src/build.rs +4 -8
- package/crates/tish_native/src/lib.rs +54 -21
- package/crates/tish_opt/src/lib.rs +84 -52
- package/crates/tish_parser/src/lib.rs +45 -13
- package/crates/tish_parser/src/parser.rs +505 -130
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3436 -0
- package/crates/tish_resolve/src/pos.rs +133 -0
- package/crates/tish_runtime/Cargo.toml +68 -3
- package/crates/tish_runtime/src/http.rs +1136 -145
- package/crates/tish_runtime/src/http_fetch.rs +38 -27
- package/crates/tish_runtime/src/http_hyper.rs +418 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +375 -189
- package/crates/tish_runtime/src/promise.rs +199 -40
- package/crates/tish_runtime/src/promise_io.rs +2 -1
- package/crates/tish_runtime/src/timers.rs +37 -1
- package/crates/tish_runtime/src/ws.rs +65 -42
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
- package/crates/tish_ui/src/jsx.rs +317 -27
- package/crates/tish_ui/src/lib.rs +5 -2
- package/crates/tish_ui/src/runtime/hooks.rs +406 -45
- package/crates/tish_ui/src/runtime/mod.rs +36 -9
- package/crates/tish_vm/Cargo.toml +15 -5
- package/crates/tish_vm/src/vm.rs +725 -281
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
- package/crates/tish_wasm/src/lib.rs +55 -42
- package/crates/tish_wasm_runtime/Cargo.toml +2 -1
- package/crates/tish_wasm_runtime/src/lib.rs +1 -1
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
- package/justfile +8 -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.
|
|
@@ -61,17 +66,29 @@ fn file_content_hash(path: &Path) -> u64 {
|
|
|
61
66
|
///
|
|
62
67
|
/// Cache is keyed by backend (native, cranelift, js, wasi) so e.g. cranelift and wasi
|
|
63
68
|
/// compiles of the same file do not overwrite each other: .../cranelift/<stem>_<hash> vs .../wasi/<stem>_<hash>.wasm.
|
|
69
|
+
///
|
|
70
|
+
/// The artifact **basename** must be unique per `(stem, hash, backend)`: nested `tish build`
|
|
71
|
+
/// uses it as the Cargo binary name under the workspace `target/release/`. If native and
|
|
72
|
+
/// cranelift both used `strict_equality_<hash>`, parallel `cargo nextest` could run those
|
|
73
|
+
/// tests concurrently and corrupt the same `target/release/...` output (Linux: ETXTBSY when
|
|
74
|
+
/// executing a binary still being written).
|
|
64
75
|
fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
65
76
|
let stem = path.file_stem().unwrap().to_string_lossy();
|
|
66
77
|
let hash = file_content_hash(path);
|
|
67
78
|
let hash8 = &format!("{:016x}", hash)[..8];
|
|
68
79
|
let cache_base = integration_compile_cache_dir().join(backend);
|
|
69
80
|
let _ = std::fs::create_dir_all(&cache_base);
|
|
81
|
+
// Include `backend` in the leaf name so nested cargo bin names never collide across backends.
|
|
82
|
+
let leaf = format!("{}__{}__{}", stem, backend, hash8);
|
|
70
83
|
|
|
71
84
|
let (artifact_path, compile_args): (PathBuf, Vec<OsString>) = match backend {
|
|
72
85
|
"native" => {
|
|
73
|
-
let ext = if cfg!(target_os = "windows") {
|
|
74
|
-
|
|
86
|
+
let ext = if cfg!(target_os = "windows") {
|
|
87
|
+
".exe"
|
|
88
|
+
} else {
|
|
89
|
+
""
|
|
90
|
+
};
|
|
91
|
+
let cached = cache_base.join(format!("{}{}", leaf, ext));
|
|
75
92
|
let args = vec![
|
|
76
93
|
OsString::from("build"),
|
|
77
94
|
OsString::from(path),
|
|
@@ -81,8 +98,12 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
81
98
|
(cached, args)
|
|
82
99
|
}
|
|
83
100
|
"cranelift" => {
|
|
84
|
-
let ext = if cfg!(target_os = "windows") {
|
|
85
|
-
|
|
101
|
+
let ext = if cfg!(target_os = "windows") {
|
|
102
|
+
".exe"
|
|
103
|
+
} else {
|
|
104
|
+
""
|
|
105
|
+
};
|
|
106
|
+
let cached = cache_base.join(format!("{}{}", leaf, ext));
|
|
86
107
|
let args = vec![
|
|
87
108
|
OsString::from("build"),
|
|
88
109
|
OsString::from(path),
|
|
@@ -94,7 +115,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
94
115
|
(cached, args)
|
|
95
116
|
}
|
|
96
117
|
"js" => {
|
|
97
|
-
let cached = cache_base.join(format!("{}
|
|
118
|
+
let cached = cache_base.join(format!("{}.js", leaf));
|
|
98
119
|
let args = vec![
|
|
99
120
|
OsString::from("build"),
|
|
100
121
|
OsString::from(path),
|
|
@@ -106,7 +127,7 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
106
127
|
(cached, args)
|
|
107
128
|
}
|
|
108
129
|
"wasi" => {
|
|
109
|
-
let out_base = cache_base.join(
|
|
130
|
+
let out_base = cache_base.join(&leaf);
|
|
110
131
|
let artifact = out_base.with_extension("wasm");
|
|
111
132
|
let args = vec![
|
|
112
133
|
OsString::from("build"),
|
|
@@ -137,8 +158,12 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
137
158
|
}
|
|
138
159
|
|
|
139
160
|
// Copy to temp so caller can run and delete without touching cache.
|
|
140
|
-
let ext = artifact_path
|
|
141
|
-
|
|
161
|
+
let ext = artifact_path
|
|
162
|
+
.extension()
|
|
163
|
+
.map(|e| e.to_string_lossy().to_string())
|
|
164
|
+
.unwrap_or_default();
|
|
165
|
+
let temp_dest =
|
|
166
|
+
std::env::temp_dir().join(format!("tish_cached_{}_{}_{}", backend, stem, hash8));
|
|
142
167
|
let temp_dest = if ext.is_empty() {
|
|
143
168
|
temp_dest
|
|
144
169
|
} else {
|
|
@@ -151,12 +176,20 @@ fn compile_cached(bin: &Path, path: &Path, backend: &str) -> PathBuf {
|
|
|
151
176
|
/// Path to the tish CLI binary. When running under cargo-llvm-cov, the build goes to
|
|
152
177
|
/// target/llvm-cov-target and CARGO_TARGET_DIR may not be set for the test process.
|
|
153
178
|
fn tish_bin() -> PathBuf {
|
|
154
|
-
let bin_name = if cfg!(target_os = "windows") {
|
|
179
|
+
let bin_name = if cfg!(target_os = "windows") {
|
|
180
|
+
"tish.exe"
|
|
181
|
+
} else {
|
|
182
|
+
"tish"
|
|
183
|
+
};
|
|
155
184
|
let default = target_dir().join("debug").join(bin_name);
|
|
156
185
|
if default.exists() {
|
|
157
186
|
return default;
|
|
158
187
|
}
|
|
159
|
-
let llvm_cov = workspace_root()
|
|
188
|
+
let llvm_cov = workspace_root()
|
|
189
|
+
.join("target")
|
|
190
|
+
.join("llvm-cov-target")
|
|
191
|
+
.join("debug")
|
|
192
|
+
.join(bin_name);
|
|
160
193
|
if llvm_cov.exists() {
|
|
161
194
|
return llvm_cov;
|
|
162
195
|
}
|
|
@@ -167,9 +200,16 @@ fn tish_bin() -> PathBuf {
|
|
|
167
200
|
#[test]
|
|
168
201
|
fn test_tish_version_flag() {
|
|
169
202
|
let bin = tish_bin();
|
|
170
|
-
assert!(
|
|
203
|
+
assert!(
|
|
204
|
+
bin.exists(),
|
|
205
|
+
"tish binary not found. Run `cargo build -p tishlang` first."
|
|
206
|
+
);
|
|
171
207
|
let out = Command::new(&bin).arg("-V").output().expect("run tish -V");
|
|
172
|
-
assert!(
|
|
208
|
+
assert!(
|
|
209
|
+
out.status.success(),
|
|
210
|
+
"tish -V failed: {}",
|
|
211
|
+
String::from_utf8_lossy(&out.stderr)
|
|
212
|
+
);
|
|
173
213
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
174
214
|
assert!(
|
|
175
215
|
stdout.contains(env!("CARGO_PKG_VERSION")),
|
|
@@ -177,20 +217,35 @@ fn test_tish_version_flag() {
|
|
|
177
217
|
env!("CARGO_PKG_VERSION"),
|
|
178
218
|
stdout
|
|
179
219
|
);
|
|
180
|
-
let out2 = Command::new(&bin)
|
|
220
|
+
let out2 = Command::new(&bin)
|
|
221
|
+
.arg("--version")
|
|
222
|
+
.output()
|
|
223
|
+
.expect("run tish --version");
|
|
181
224
|
assert!(out2.status.success());
|
|
182
225
|
let stdout2 = String::from_utf8_lossy(&out2.stdout);
|
|
183
|
-
assert!(
|
|
226
|
+
assert!(
|
|
227
|
+
stdout2.contains(env!("CARGO_PKG_VERSION")),
|
|
228
|
+
"tish --version should print version"
|
|
229
|
+
);
|
|
184
230
|
}
|
|
185
231
|
|
|
186
232
|
/// Parse async-await example (validates async fn parsing).
|
|
187
233
|
#[test]
|
|
188
234
|
fn test_async_await_parse() {
|
|
189
|
-
let path = workspace_root()
|
|
235
|
+
let path = workspace_root()
|
|
236
|
+
.join("examples")
|
|
237
|
+
.join("async-await")
|
|
238
|
+
.join("src")
|
|
239
|
+
.join("main.tish");
|
|
190
240
|
if path.exists() {
|
|
191
241
|
let source = std::fs::read_to_string(&path).unwrap();
|
|
192
242
|
let result = tishlang_parser::parse(&source);
|
|
193
|
-
assert!(
|
|
243
|
+
assert!(
|
|
244
|
+
result.is_ok(),
|
|
245
|
+
"Parse failed for {}: {:?}",
|
|
246
|
+
path.display(),
|
|
247
|
+
result.err()
|
|
248
|
+
);
|
|
194
249
|
}
|
|
195
250
|
}
|
|
196
251
|
|
|
@@ -199,11 +254,20 @@ fn test_async_await_parse() {
|
|
|
199
254
|
#[cfg(feature = "http")]
|
|
200
255
|
fn test_async_await_compile_via_binary() {
|
|
201
256
|
let bin = tish_bin();
|
|
202
|
-
let path = workspace_root()
|
|
257
|
+
let path = workspace_root()
|
|
258
|
+
.join("examples")
|
|
259
|
+
.join("async-await")
|
|
260
|
+
.join("src")
|
|
261
|
+
.join("main.tish");
|
|
203
262
|
if path.exists() && bin.exists() {
|
|
204
263
|
let out = std::env::temp_dir().join("tish_async_test_out");
|
|
205
264
|
let compile_result = Command::new(&bin)
|
|
206
|
-
.args([
|
|
265
|
+
.args([
|
|
266
|
+
"build",
|
|
267
|
+
path.to_string_lossy().as_ref(),
|
|
268
|
+
"-o",
|
|
269
|
+
out.to_string_lossy().as_ref(),
|
|
270
|
+
])
|
|
207
271
|
.current_dir(workspace_root())
|
|
208
272
|
.output();
|
|
209
273
|
let compile_out = compile_result.expect("run tish build");
|
|
@@ -213,9 +277,7 @@ fn test_async_await_compile_via_binary() {
|
|
|
213
277
|
String::from_utf8_lossy(&compile_out.stderr)
|
|
214
278
|
);
|
|
215
279
|
// Run compiled binary to validate non-blocking fetchAll executes correctly
|
|
216
|
-
let run_result = Command::new(&out)
|
|
217
|
-
.current_dir(workspace_root())
|
|
218
|
-
.output();
|
|
280
|
+
let run_result = Command::new(&out).current_dir(workspace_root()).output();
|
|
219
281
|
let run_out = run_result.expect("run compiled async binary");
|
|
220
282
|
assert!(
|
|
221
283
|
run_out.status.success(),
|
|
@@ -223,7 +285,10 @@ fn test_async_await_compile_via_binary() {
|
|
|
223
285
|
String::from_utf8_lossy(&run_out.stderr)
|
|
224
286
|
);
|
|
225
287
|
let stdout = String::from_utf8_lossy(&run_out.stdout);
|
|
226
|
-
assert!(
|
|
288
|
+
assert!(
|
|
289
|
+
stdout.contains("Fetching"),
|
|
290
|
+
"expected output to mention fetching"
|
|
291
|
+
);
|
|
227
292
|
assert!(stdout.contains("Done"), "expected output to contain Done");
|
|
228
293
|
}
|
|
229
294
|
}
|
|
@@ -235,8 +300,16 @@ fn test_async_await_compile_via_binary() {
|
|
|
235
300
|
#[ignore = "timing and network sensitive; run manually: cargo test test_async_parallel_vs_sequential_timing -p tishlang--features http -- --ignored"]
|
|
236
301
|
fn test_async_parallel_vs_sequential_timing() {
|
|
237
302
|
let bin = tish_bin();
|
|
238
|
-
let parallel_src = workspace_root()
|
|
239
|
-
|
|
303
|
+
let parallel_src = workspace_root()
|
|
304
|
+
.join("examples")
|
|
305
|
+
.join("async-await")
|
|
306
|
+
.join("src")
|
|
307
|
+
.join("parallel.tish");
|
|
308
|
+
let sequential_src = workspace_root()
|
|
309
|
+
.join("examples")
|
|
310
|
+
.join("async-await")
|
|
311
|
+
.join("src")
|
|
312
|
+
.join("sequential.tish");
|
|
240
313
|
if !parallel_src.exists() || !sequential_src.exists() || !bin.exists() {
|
|
241
314
|
return;
|
|
242
315
|
}
|
|
@@ -245,28 +318,58 @@ fn test_async_parallel_vs_sequential_timing() {
|
|
|
245
318
|
|
|
246
319
|
// Compile both
|
|
247
320
|
let compile_par = Command::new(&bin)
|
|
248
|
-
.args([
|
|
321
|
+
.args([
|
|
322
|
+
"build",
|
|
323
|
+
parallel_src.to_string_lossy().as_ref(),
|
|
324
|
+
"-o",
|
|
325
|
+
out_parallel.to_string_lossy().as_ref(),
|
|
326
|
+
])
|
|
249
327
|
.current_dir(workspace_root())
|
|
250
328
|
.output();
|
|
251
|
-
assert!(
|
|
329
|
+
assert!(
|
|
330
|
+
compile_par.as_ref().unwrap().status.success(),
|
|
331
|
+
"compile parallel: {}",
|
|
332
|
+
String::from_utf8_lossy(&compile_par.as_ref().unwrap().stderr)
|
|
333
|
+
);
|
|
252
334
|
|
|
253
335
|
let compile_seq = Command::new(&bin)
|
|
254
|
-
.args([
|
|
336
|
+
.args([
|
|
337
|
+
"build",
|
|
338
|
+
sequential_src.to_string_lossy().as_ref(),
|
|
339
|
+
"-o",
|
|
340
|
+
out_sequential.to_string_lossy().as_ref(),
|
|
341
|
+
])
|
|
255
342
|
.current_dir(workspace_root())
|
|
256
343
|
.output();
|
|
257
|
-
assert!(
|
|
344
|
+
assert!(
|
|
345
|
+
compile_seq.as_ref().unwrap().status.success(),
|
|
346
|
+
"compile sequential: {}",
|
|
347
|
+
String::from_utf8_lossy(&compile_seq.as_ref().unwrap().stderr)
|
|
348
|
+
);
|
|
258
349
|
|
|
259
350
|
// Run parallel and time
|
|
260
351
|
let t_parallel = std::time::Instant::now();
|
|
261
|
-
let run_par = Command::new(&out_parallel)
|
|
352
|
+
let run_par = Command::new(&out_parallel)
|
|
353
|
+
.current_dir(workspace_root())
|
|
354
|
+
.output();
|
|
262
355
|
let elapsed_parallel = t_parallel.elapsed();
|
|
263
|
-
assert!(
|
|
356
|
+
assert!(
|
|
357
|
+
run_par.as_ref().unwrap().status.success(),
|
|
358
|
+
"run parallel: {}",
|
|
359
|
+
String::from_utf8_lossy(&run_par.as_ref().unwrap().stderr)
|
|
360
|
+
);
|
|
264
361
|
|
|
265
362
|
// Run sequential and time
|
|
266
363
|
let t_sequential = std::time::Instant::now();
|
|
267
|
-
let run_seq = Command::new(&out_sequential)
|
|
364
|
+
let run_seq = Command::new(&out_sequential)
|
|
365
|
+
.current_dir(workspace_root())
|
|
366
|
+
.output();
|
|
268
367
|
let elapsed_sequential = t_sequential.elapsed();
|
|
269
|
-
assert!(
|
|
368
|
+
assert!(
|
|
369
|
+
run_seq.as_ref().unwrap().status.success(),
|
|
370
|
+
"run sequential: {}",
|
|
371
|
+
String::from_utf8_lossy(&run_seq.as_ref().unwrap().stderr)
|
|
372
|
+
);
|
|
270
373
|
|
|
271
374
|
// PARALLEL MUST BE FASTER: parallel < sequential * 0.6 (parallel ~1s, sequential ~3s)
|
|
272
375
|
let parallel_secs = elapsed_parallel.as_secs_f64();
|
|
@@ -285,22 +388,34 @@ fn test_async_parallel_vs_sequential_timing() {
|
|
|
285
388
|
#[cfg(feature = "http")]
|
|
286
389
|
#[ignore = "requires async runtime; use test_async_await_compile_via_binary for CI"]
|
|
287
390
|
fn test_async_await_run() {
|
|
288
|
-
let path = workspace_root()
|
|
391
|
+
let path = workspace_root()
|
|
392
|
+
.join("examples")
|
|
393
|
+
.join("async-await")
|
|
394
|
+
.join("src")
|
|
395
|
+
.join("main.tish");
|
|
289
396
|
if path.exists() {
|
|
290
397
|
let source = std::fs::read_to_string(&path).unwrap();
|
|
291
398
|
let result = tishlang_eval::run(&source);
|
|
292
|
-
assert!(
|
|
399
|
+
assert!(
|
|
400
|
+
result.is_ok(),
|
|
401
|
+
"Run failed for {}: {:?}",
|
|
402
|
+
path.display(),
|
|
403
|
+
result.err()
|
|
404
|
+
);
|
|
293
405
|
}
|
|
294
406
|
}
|
|
295
407
|
|
|
296
|
-
/// Run Promise and setTimeout module tests (
|
|
408
|
+
/// Run Promise and setTimeout module tests (`promise` needs `http`; `settimeout` needs `timers`, which `http` enables).
|
|
297
409
|
/// Ignored: tishlang_eval::run() does not run the event loop.
|
|
298
410
|
#[test]
|
|
299
411
|
#[cfg(feature = "http")]
|
|
300
412
|
#[ignore = "requires async runtime"]
|
|
301
413
|
fn test_promise_and_settimeout() {
|
|
302
414
|
for name in ["promise", "settimeout"] {
|
|
303
|
-
let path = workspace_root()
|
|
415
|
+
let path = workspace_root()
|
|
416
|
+
.join("tests")
|
|
417
|
+
.join("modules")
|
|
418
|
+
.join(format!("{}.tish", name));
|
|
304
419
|
if path.exists() {
|
|
305
420
|
let source = std::fs::read_to_string(&path).unwrap();
|
|
306
421
|
let result = tishlang_eval::run(&source);
|
|
@@ -338,17 +453,26 @@ fn test_async_promise_settimeout_combined() {
|
|
|
338
453
|
/// VM run with Date global (resolve+merge+bytecode+run pipeline).
|
|
339
454
|
#[test]
|
|
340
455
|
fn test_vm_date_now() {
|
|
341
|
-
let path = workspace_root()
|
|
456
|
+
let path = workspace_root()
|
|
457
|
+
.join("tests")
|
|
458
|
+
.join("core")
|
|
459
|
+
.join("date.tish");
|
|
342
460
|
if !path.exists() {
|
|
343
461
|
return;
|
|
344
462
|
}
|
|
345
463
|
// Library path
|
|
346
464
|
let modules = tishlang_compile::resolve_project(&path, path.parent()).expect("resolve");
|
|
347
465
|
tishlang_compile::detect_cycles(&modules).expect("cycles");
|
|
348
|
-
let program = tishlang_compile::merge_modules(modules)
|
|
466
|
+
let program = tishlang_compile::merge_modules(modules)
|
|
467
|
+
.expect("merge")
|
|
468
|
+
.program;
|
|
349
469
|
let chunk = tishlang_bytecode::compile(&program).expect("compile");
|
|
350
470
|
let result = tishlang_vm::run(&chunk);
|
|
351
|
-
assert!(
|
|
471
|
+
assert!(
|
|
472
|
+
result.is_ok(),
|
|
473
|
+
"VM run (library) failed: {:?}",
|
|
474
|
+
result.err()
|
|
475
|
+
);
|
|
352
476
|
// Binary path - same flow as `tish run <file>`
|
|
353
477
|
let bin = tish_bin();
|
|
354
478
|
if bin.exists() {
|
|
@@ -379,20 +503,32 @@ fn test_vm_index_assign_direct() {
|
|
|
379
503
|
/// VM run via resolve+merge (same as tish run) - must also pass.
|
|
380
504
|
#[test]
|
|
381
505
|
fn test_vm_index_assign_via_resolve() {
|
|
382
|
-
let path = workspace_root()
|
|
506
|
+
let path = workspace_root()
|
|
507
|
+
.join("tests")
|
|
508
|
+
.join("core")
|
|
509
|
+
.join("array_sort_minimal.tish");
|
|
383
510
|
let modules = tishlang_compile::resolve_project(&path, path.parent()).expect("resolve");
|
|
384
511
|
tishlang_compile::detect_cycles(&modules).expect("cycles");
|
|
385
|
-
let program = tishlang_compile::merge_modules(modules)
|
|
512
|
+
let program = tishlang_compile::merge_modules(modules)
|
|
513
|
+
.expect("merge")
|
|
514
|
+
.program;
|
|
386
515
|
let chunk = tishlang_bytecode::compile(&program).expect("compile");
|
|
387
516
|
let result = tishlang_vm::run(&chunk);
|
|
388
|
-
assert!(
|
|
517
|
+
assert!(
|
|
518
|
+
result.is_ok(),
|
|
519
|
+
"VM IndexAssign via resolve failed: {:?}",
|
|
520
|
+
result.err()
|
|
521
|
+
);
|
|
389
522
|
}
|
|
390
523
|
|
|
391
524
|
/// tish run binary must pass array_sort_minimal (ensures CLI works).
|
|
392
525
|
#[test]
|
|
393
526
|
fn test_tish_run_index_assign() {
|
|
394
527
|
let bin = tish_bin();
|
|
395
|
-
let path = workspace_root()
|
|
528
|
+
let path = workspace_root()
|
|
529
|
+
.join("tests")
|
|
530
|
+
.join("core")
|
|
531
|
+
.join("array_sort_minimal.tish");
|
|
396
532
|
if !bin.exists() {
|
|
397
533
|
eprintln!("Skipping: tish binary not built");
|
|
398
534
|
return;
|
|
@@ -516,7 +652,12 @@ fn test_mvp_programs_interpreter() {
|
|
|
516
652
|
path.display()
|
|
517
653
|
)
|
|
518
654
|
});
|
|
519
|
-
assert_eq!(
|
|
655
|
+
assert_eq!(
|
|
656
|
+
stdout,
|
|
657
|
+
expected,
|
|
658
|
+
"Interpreter output mismatch for {}",
|
|
659
|
+
path.display()
|
|
660
|
+
);
|
|
520
661
|
}
|
|
521
662
|
}
|
|
522
663
|
}
|
|
@@ -562,7 +703,8 @@ fn test_mvp_programs_interp_vm_stdout_parity() {
|
|
|
562
703
|
let s_interp = String::from_utf8_lossy(&out_interp.stdout);
|
|
563
704
|
let s_vm = String::from_utf8_lossy(&out_vm.stdout);
|
|
564
705
|
assert_eq!(
|
|
565
|
-
s_interp,
|
|
706
|
+
s_interp,
|
|
707
|
+
s_vm,
|
|
566
708
|
"interp vs VM stdout mismatch for {}",
|
|
567
709
|
path.display()
|
|
568
710
|
);
|
|
@@ -603,7 +745,11 @@ fn test_mvp_programs_native() {
|
|
|
603
745
|
};
|
|
604
746
|
let _ = std::fs::remove_file(&out_bin);
|
|
605
747
|
if !out.status.success() {
|
|
606
|
-
return Some(format!(
|
|
748
|
+
return Some(format!(
|
|
749
|
+
"{}: {}",
|
|
750
|
+
path.display(),
|
|
751
|
+
String::from_utf8_lossy(&out.stderr)
|
|
752
|
+
));
|
|
607
753
|
}
|
|
608
754
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
609
755
|
if stdout != expected {
|
|
@@ -670,7 +816,11 @@ fn test_mvp_programs_cranelift() {
|
|
|
670
816
|
};
|
|
671
817
|
let _ = std::fs::remove_file(&out_bin);
|
|
672
818
|
if !out.status.success() {
|
|
673
|
-
return Some(format!(
|
|
819
|
+
return Some(format!(
|
|
820
|
+
"{}: {}",
|
|
821
|
+
path.display(),
|
|
822
|
+
String::from_utf8_lossy(&out.stderr)
|
|
823
|
+
));
|
|
674
824
|
}
|
|
675
825
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
676
826
|
if stdout != expected {
|
|
@@ -679,7 +829,11 @@ fn test_mvp_programs_cranelift() {
|
|
|
679
829
|
None
|
|
680
830
|
})
|
|
681
831
|
.collect();
|
|
682
|
-
assert!(
|
|
832
|
+
assert!(
|
|
833
|
+
errors.is_empty(),
|
|
834
|
+
"cranelift failures:\n{}",
|
|
835
|
+
errors.join("\n")
|
|
836
|
+
);
|
|
683
837
|
}
|
|
684
838
|
|
|
685
839
|
/// Compile each .tish file to WASI, run with wasmtime, and compare stdout to static expected (parallelized).
|
|
@@ -727,7 +881,11 @@ fn test_mvp_programs_wasi() {
|
|
|
727
881
|
};
|
|
728
882
|
let _ = std::fs::remove_file(&out_wasm);
|
|
729
883
|
if !out.status.success() {
|
|
730
|
-
return Some(format!(
|
|
884
|
+
return Some(format!(
|
|
885
|
+
"{}: {}",
|
|
886
|
+
path.display(),
|
|
887
|
+
String::from_utf8_lossy(&out.stderr)
|
|
888
|
+
));
|
|
731
889
|
}
|
|
732
890
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
733
891
|
if stdout != expected {
|
|
@@ -789,7 +947,11 @@ fn test_mvp_programs_js() {
|
|
|
789
947
|
String::from_utf8_lossy(&out.stderr)
|
|
790
948
|
);
|
|
791
949
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
792
|
-
assert_eq!(
|
|
950
|
+
assert_eq!(
|
|
951
|
+
stdout,
|
|
952
|
+
expected,
|
|
953
|
+
"JS output mismatch for {}",
|
|
954
|
+
path.display()
|
|
955
|
+
);
|
|
793
956
|
}
|
|
794
957
|
}
|
|
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]
|
|
@@ -42,9 +53,13 @@ fn test_and_shortcircuit_via_resolve_project() {
|
|
|
42
53
|
let path = path.canonicalize().expect("path");
|
|
43
54
|
let project_root = path.parent().unwrap();
|
|
44
55
|
let modules = resolve_project(&path, Some(project_root)).expect("resolve");
|
|
45
|
-
let program = merge_modules(modules).expect("merge");
|
|
56
|
+
let program = merge_modules(modules).expect("merge").program;
|
|
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
|
}
|