@tishlang/tish-lsp 3.6.0 → 3.7.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 +1 -0
- package/bin/tish-lsp +0 -0
- package/crates/tish/Cargo.toml +4 -1
- package/crates/tish/src/cli_help.rs +84 -0
- package/crates/tish/src/main.rs +93 -1
- package/crates/tish/tests/test_runner.rs +673 -0
- package/crates/tish_bytecode/src/compiler.rs +25 -4
- package/crates/tish_compile/src/resolve.rs +45 -12
- package/crates/tish_core/src/lib.rs +103 -11
- package/crates/tish_eval/src/eval.rs +16 -0
- package/crates/tish_eval/src/value_convert.rs +10 -0
- package/crates/tish_lsp/Cargo.toml +1 -1
- package/crates/tish_test/Cargo.toml +47 -0
- package/crates/tish_test/src/assert.rs +435 -0
- package/crates/tish_test/src/config.rs +85 -0
- package/crates/tish_test/src/coverage.rs +237 -0
- package/crates/tish_test/src/deep_equal.rs +261 -0
- package/crates/tish_test/src/discovery.rs +70 -0
- package/crates/tish_test/src/expect.rs +977 -0
- package/crates/tish_test/src/instrument.rs +457 -0
- package/crates/tish_test/src/isolation.rs +14 -0
- package/crates/tish_test/src/lib.rs +22 -0
- package/crates/tish_test/src/load.rs +152 -0
- package/crates/tish_test/src/mocks.rs +270 -0
- package/crates/tish_test/src/module.rs +318 -0
- package/crates/tish_test/src/registry.rs +235 -0
- package/crates/tish_test/src/report.rs +312 -0
- package/crates/tish_test/src/runner.rs +797 -0
- package/crates/tish_test/src/snapshots.rs +197 -0
- package/crates/tish_vm/src/vm.rs +17 -14
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-lsp +0 -0
- package/platform/darwin-x64/tish-lsp +0 -0
- package/platform/linux-arm64/tish-lsp +0 -0
- package/platform/linux-x64/tish-lsp +0 -0
- package/platform/win32-x64/tish-lsp.exe +0 -0
|
@@ -1521,6 +1521,27 @@ impl<'a> Compiler<'a> {
|
|
|
1521
1521
|
Ok(())
|
|
1522
1522
|
}
|
|
1523
1523
|
|
|
1524
|
+
/// Compile a function/arrow body **without** the outermost block's `EnterBlock`/`ExitBlock`.
|
|
1525
|
+
///
|
|
1526
|
+
/// A function body's top-level block is the frame's own scope: its bindings live in the
|
|
1527
|
+
/// frame-local map that is created fresh per call and dropped when the call returns, so there
|
|
1528
|
+
/// is nothing for `ExitBlock` to restore or hide. Emitting it is not merely wasted work — it is
|
|
1529
|
+
/// actively wrong. `ExitBlock` REMOVES the block's names from that map, and closures capture
|
|
1530
|
+
/// the map **by reference**, so any closure that outlived the call lost its captures
|
|
1531
|
+
/// ("Undefined variable"). An explicit `return` masked the bug by jumping over the teardown.
|
|
1532
|
+
/// See tests/core/parity_block_scope_capture.tish (#247).
|
|
1533
|
+
fn compile_fn_body(&mut self, body: &Statement) -> Result<(), CompileError> {
|
|
1534
|
+
if let Statement::Block { statements, .. } = body {
|
|
1535
|
+
self.enter_block_scope();
|
|
1536
|
+
for stmt in statements {
|
|
1537
|
+
self.compile_statement(stmt)?;
|
|
1538
|
+
}
|
|
1539
|
+
self.exit_block_scope();
|
|
1540
|
+
return Ok(());
|
|
1541
|
+
}
|
|
1542
|
+
self.compile_statement(body)
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1524
1545
|
fn compile_statement(&mut self, stmt: &Statement) -> Result<(), CompileError> {
|
|
1525
1546
|
self.mark_line(stmt.span());
|
|
1526
1547
|
match stmt {
|
|
@@ -1999,7 +2020,7 @@ impl<'a> Compiler<'a> {
|
|
|
1999
2020
|
if let Some(map) = simple_slots {
|
|
2000
2021
|
inner_comp.slot_ctx = Some(map);
|
|
2001
2022
|
inner_comp.emit_param_defaults_prologue(params)?;
|
|
2002
|
-
inner_comp.
|
|
2023
|
+
inner_comp.compile_fn_body(body)?;
|
|
2003
2024
|
} else if let Some(cap) = captured {
|
|
2004
2025
|
// Params (all uncaptured — gated) → slots 0..n (matching the VM's param binding);
|
|
2005
2026
|
// uncaptured body `let`s get fresh slots via the scope-aware allocator; captured
|
|
@@ -2011,7 +2032,7 @@ impl<'a> Compiler<'a> {
|
|
|
2011
2032
|
inner_comp.declare_slot(p);
|
|
2012
2033
|
}
|
|
2013
2034
|
inner_comp.emit_param_defaults_prologue(params)?;
|
|
2014
|
-
inner_comp.
|
|
2035
|
+
inner_comp.compile_fn_body(body)?;
|
|
2015
2036
|
general_frame_slots = Some(inner_comp.next_slot);
|
|
2016
2037
|
} else {
|
|
2017
2038
|
inner_comp.scope = vec![param_names
|
|
@@ -2021,7 +2042,7 @@ impl<'a> Compiler<'a> {
|
|
|
2021
2042
|
inner_comp
|
|
2022
2043
|
.emit_param_destructure_prologue(¶m_names[..formal_len], &slots)?;
|
|
2023
2044
|
inner_comp.emit_param_defaults_prologue(params)?;
|
|
2024
|
-
inner_comp.
|
|
2045
|
+
inner_comp.compile_fn_body(body)?;
|
|
2025
2046
|
}
|
|
2026
2047
|
inner_comp.emit(Opcode::LoadConst);
|
|
2027
2048
|
let idx = inner_comp.constant_idx(Constant::Null);
|
|
@@ -2769,7 +2790,7 @@ impl<'a> Compiler<'a> {
|
|
|
2769
2790
|
inner_comp.emit(Opcode::Return);
|
|
2770
2791
|
}
|
|
2771
2792
|
ArrowBody::Block(s) => {
|
|
2772
|
-
inner_comp.
|
|
2793
|
+
inner_comp.compile_fn_body(s)?;
|
|
2773
2794
|
let idx = inner_comp.constant_idx(Constant::Null);
|
|
2774
2795
|
inner_comp.emit(Opcode::LoadConst);
|
|
2775
2796
|
inner_comp.chunk.write_u16(idx);
|
|
@@ -81,13 +81,22 @@ const BUILTIN_ALIASES: &[(&str, &str)] = &[
|
|
|
81
81
|
("encoding", "tish:encoding"),
|
|
82
82
|
("crypto", "tish:crypto"),
|
|
83
83
|
("zip", "tish:zip"),
|
|
84
|
+
("test", "tish:test"),
|
|
85
|
+
("assert", "tish:assert"),
|
|
86
|
+
("assert/strict", "tish:assert"),
|
|
84
87
|
];
|
|
85
88
|
|
|
86
89
|
/// Normalize built-in spec to canonical form. Handles the Node `node:` prefix
|
|
87
|
-
/// (`node:fs` -> `tish:fs`, `node:fs/promises` -> `tish:fs/promises
|
|
90
|
+
/// (`node:fs` -> `tish:fs`, `node:fs/promises` -> `tish:fs/promises`,
|
|
91
|
+
/// `node:test` -> `tish:test`, `node:assert` / `node:assert/strict` -> `tish:assert`)
|
|
92
|
+
/// and bare aliases.
|
|
88
93
|
pub fn normalize_builtin_spec(spec: &str) -> Option<String> {
|
|
89
94
|
// Strip a leading `node:` so `node:fs` resolves the same as `fs`.
|
|
90
95
|
let spec = spec.strip_prefix("node:").unwrap_or(spec);
|
|
96
|
+
// `node:assert/strict` (and bare `assert/strict`) → `tish:assert`.
|
|
97
|
+
if spec == "assert/strict" {
|
|
98
|
+
return Some("tish:assert".to_string());
|
|
99
|
+
}
|
|
91
100
|
if spec.starts_with("tish:") {
|
|
92
101
|
return Some(spec.to_string());
|
|
93
102
|
}
|
|
@@ -97,7 +106,7 @@ pub fn normalize_builtin_spec(spec: &str) -> Option<String> {
|
|
|
97
106
|
.map(|(_, canonical)| (*canonical).to_string())
|
|
98
107
|
}
|
|
99
108
|
|
|
100
|
-
/// Built-in modules that come from tishlang_runtime, not from package.json.
|
|
109
|
+
/// Built-in modules that come from tishlang_runtime / tishlang_test, not from package.json.
|
|
101
110
|
pub fn is_builtin_native_spec(spec: &str) -> bool {
|
|
102
111
|
let spec = spec.strip_prefix("node:").unwrap_or(spec);
|
|
103
112
|
matches!(
|
|
@@ -114,10 +123,12 @@ pub fn is_builtin_native_spec(spec: &str) -> bool {
|
|
|
114
123
|
| "tish:encoding"
|
|
115
124
|
| "tish:crypto"
|
|
116
125
|
| "tish:zip"
|
|
126
|
+
| "tish:test"
|
|
127
|
+
| "tish:assert"
|
|
117
128
|
) || matches!(
|
|
118
129
|
spec,
|
|
119
130
|
"fs" | "fs/promises" | "http" | "timers" | "process" | "ws" | "tty" | "pty" | "net"
|
|
120
|
-
| "encoding" | "crypto" | "zip"
|
|
131
|
+
| "encoding" | "crypto" | "zip" | "test" | "assert" | "assert/strict"
|
|
121
132
|
)
|
|
122
133
|
}
|
|
123
134
|
|
|
@@ -1031,7 +1042,7 @@ fn load_module_recursive(
|
|
|
1031
1042
|
/// resolution for `import … from "@scope/pkg"` is interop, and the native-vs-merged axis is the one
|
|
1032
1043
|
/// piece tish adds on top, keyed off the native markers rather than mere presence of `tish.module`.
|
|
1033
1044
|
pub fn is_native_import(spec: &str) -> bool {
|
|
1034
|
-
// A leading `node:` (node:fs, node:fs/promises) resolves
|
|
1045
|
+
// A leading `node:` (node:fs, node:fs/promises, node:assert/strict) resolves like the bare form.
|
|
1035
1046
|
let spec = spec.strip_prefix("node:").unwrap_or(spec);
|
|
1036
1047
|
spec.starts_with("tish:")
|
|
1037
1048
|
|| spec.starts_with("cargo:")
|
|
@@ -1039,7 +1050,7 @@ pub fn is_native_import(spec: &str) -> bool {
|
|
|
1039
1050
|
|| matches!(
|
|
1040
1051
|
spec,
|
|
1041
1052
|
"fs" | "fs/promises" | "http" | "timers" | "process" | "ws" | "tty" | "net" | "encoding"
|
|
1042
|
-
| "crypto" | "zip"
|
|
1053
|
+
| "crypto" | "zip" | "test" | "assert" | "assert/strict"
|
|
1043
1054
|
)
|
|
1044
1055
|
}
|
|
1045
1056
|
|
|
@@ -2110,13 +2121,35 @@ pub fn merge_modules(mut modules: Vec<ResolvedModule>) -> Result<MergedProgram,
|
|
|
2110
2121
|
from.as_ref()
|
|
2111
2122
|
));
|
|
2112
2123
|
}
|
|
2113
|
-
ImportSpecifier::Default { name,
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2124
|
+
ImportSpecifier::Default { name, name_span } => {
|
|
2125
|
+
// Only `tish:assert` (and aliases) expose a callable default.
|
|
2126
|
+
// Other natives still require named imports.
|
|
2127
|
+
if canonical_spec != "tish:assert" {
|
|
2128
|
+
return Err(format!(
|
|
2129
|
+
"Default import '{}' not supported for native module '{}'. Use named import, e.g. import {{ egui }} from '{}'",
|
|
2130
|
+
name.as_ref(),
|
|
2131
|
+
from.as_ref(),
|
|
2132
|
+
from.as_ref()
|
|
2133
|
+
));
|
|
2134
|
+
}
|
|
2135
|
+
let init = Expr::NativeModuleLoad {
|
|
2136
|
+
spec: Arc::from(canonical_spec.clone()),
|
|
2137
|
+
export_name: Arc::from("default"),
|
|
2138
|
+
span: *span,
|
|
2139
|
+
};
|
|
2140
|
+
merge_push(
|
|
2141
|
+
&mut statements,
|
|
2142
|
+
&mut statement_sources,
|
|
2143
|
+
Statement::VarDecl {
|
|
2144
|
+
name: name.clone(),
|
|
2145
|
+
name_span: *name_span,
|
|
2146
|
+
mutable: false,
|
|
2147
|
+
type_ann: None,
|
|
2148
|
+
init: Some(init),
|
|
2149
|
+
span: *span,
|
|
2150
|
+
},
|
|
2151
|
+
src_path.clone(),
|
|
2152
|
+
);
|
|
2120
2153
|
}
|
|
2121
2154
|
}
|
|
2122
2155
|
}
|
|
@@ -109,17 +109,17 @@ pub fn process_argv() -> Vec<String> {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
112
|
+
// #303 — a thrown JS value parked while it unwinds across a boundary that can't carry a `Result`.
|
|
113
|
+
//
|
|
114
|
+
// The native value-fn ABI is `Fn(&[Value]) -> Value`, and `Callable::call` likewise returns a bare
|
|
115
|
+
// `Value`, so a `throw` crossing such a boundary can't ride a `Result`. It is parked here and picked
|
|
116
|
+
// up at the next checkpoint: native codegen checks it after each call; the VM checks it after each
|
|
117
|
+
// `Callable::call`; and the shared array builtins (`forEach`/`map`/`sort`/…) check it between
|
|
118
|
+
// elements so they stop iterating promptly instead of running the callback for the rest of the
|
|
119
|
+
// array. The slot lives in `tishlang_core` (rather than `tishlang_runtime`) so `tishlang_builtins`
|
|
120
|
+
// can poll it without a `builtins -> runtime` dependency cycle; `tishlang_runtime` and `tishlang_vm`
|
|
121
|
+
// share this one slot. First-throw-wins; drained exactly once by [`take_pending_throw`] at the frame
|
|
122
|
+
// that converts it back into a `Result`.
|
|
123
123
|
#[cfg(not(feature = "portable"))]
|
|
124
124
|
thread_local! {
|
|
125
125
|
static PENDING_THROW: core::cell::RefCell<Option<Value>> = const { core::cell::RefCell::new(None) };
|
|
@@ -151,6 +151,98 @@ pub fn take_pending_throw() -> Option<Value> {
|
|
|
151
151
|
PENDING_THROW.with(|c| c.borrow_mut().take())
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Cooperative execution deadline (`tish test --timeout`).
|
|
156
|
+
//
|
|
157
|
+
// Without this a runaway loop is uninterruptible: the runner can only notice a blown
|
|
158
|
+
// deadline *after* the body returns, which never happens. Backends poll
|
|
159
|
+
// [`execution_deadline_exceeded`] on loop back-edges and abort with a catchable error.
|
|
160
|
+
//
|
|
161
|
+
// Disarmed is the default and costs one relaxed load — `tish run` never arms it. Armed,
|
|
162
|
+
// the clock is read once every `DEADLINE_POLL_MASK + 1` back-edges so a hot loop is not
|
|
163
|
+
// paying a `now()` per iteration.
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
#[cfg(not(feature = "portable"))]
|
|
167
|
+
mod deadline {
|
|
168
|
+
use core::sync::atomic::{AtomicU64, Ordering};
|
|
169
|
+
use std::sync::LazyLock;
|
|
170
|
+
use std::time::Instant;
|
|
171
|
+
|
|
172
|
+
/// Nanoseconds since [`START`] at which execution must stop. 0 = disarmed.
|
|
173
|
+
static DEADLINE_NS: AtomicU64 = AtomicU64::new(0);
|
|
174
|
+
static START: LazyLock<Instant> = LazyLock::new(Instant::now);
|
|
175
|
+
|
|
176
|
+
const DEADLINE_POLL_MASK: u32 = 0x3FF;
|
|
177
|
+
|
|
178
|
+
thread_local! {
|
|
179
|
+
static POLL_TICK: core::cell::Cell<u32> = const { core::cell::Cell::new(0) };
|
|
180
|
+
static TRIPPED: core::cell::Cell<bool> = const { core::cell::Cell::new(false) };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// Arm the deadline `millis` from now, or disarm with `None`. Clears any previous trip.
|
|
184
|
+
pub fn set_execution_deadline(millis: Option<u64>) {
|
|
185
|
+
TRIPPED.with(|t| t.set(false));
|
|
186
|
+
POLL_TICK.with(|t| t.set(0));
|
|
187
|
+
match millis {
|
|
188
|
+
Some(ms) => {
|
|
189
|
+
let at = START.elapsed().as_nanos() as u64 + ms.saturating_mul(1_000_000);
|
|
190
|
+
// 0 means "disarmed"; nudge an exact-zero deadline to 1ns.
|
|
191
|
+
DEADLINE_NS.store(at.max(1), Ordering::Relaxed);
|
|
192
|
+
}
|
|
193
|
+
None => DEADLINE_NS.store(0, Ordering::Relaxed),
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/// Has the armed deadline passed? Cheap and false when disarmed.
|
|
198
|
+
#[inline]
|
|
199
|
+
pub fn execution_deadline_exceeded() -> bool {
|
|
200
|
+
let at = DEADLINE_NS.load(Ordering::Relaxed);
|
|
201
|
+
if at == 0 {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
if TRIPPED.with(|t| t.get()) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
let tick = POLL_TICK.with(|t| {
|
|
208
|
+
let n = t.get().wrapping_add(1);
|
|
209
|
+
t.set(n);
|
|
210
|
+
n
|
|
211
|
+
});
|
|
212
|
+
if tick & DEADLINE_POLL_MASK != 0 {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
if START.elapsed().as_nanos() as u64 >= at {
|
|
216
|
+
TRIPPED.with(|t| t.set(true));
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
false
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// Did this thread abort because the deadline tripped?
|
|
223
|
+
pub fn execution_deadline_tripped() -> bool {
|
|
224
|
+
TRIPPED.with(|t| t.get())
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Portable / single-core builds (GBA and friends) have no wall clock to poll.
|
|
229
|
+
#[cfg(feature = "portable")]
|
|
230
|
+
mod deadline {
|
|
231
|
+
pub fn set_execution_deadline(_millis: Option<u64>) {}
|
|
232
|
+
#[inline]
|
|
233
|
+
pub fn execution_deadline_exceeded() -> bool {
|
|
234
|
+
false
|
|
235
|
+
}
|
|
236
|
+
pub fn execution_deadline_tripped() -> bool {
|
|
237
|
+
false
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
pub use deadline::{execution_deadline_exceeded, execution_deadline_tripped, set_execution_deadline};
|
|
242
|
+
|
|
243
|
+
/// Message backends raise when [`execution_deadline_exceeded`] trips.
|
|
244
|
+
pub const DEADLINE_ERROR: &str = "Test exceeded its timeout and was aborted";
|
|
245
|
+
|
|
154
246
|
// Current user-function call depth for the bytecode VM. The VM's recursive path builds a fresh
|
|
155
247
|
// `Vm` per call (so no shared struct field can accumulate) and its `Callable::call` signature is
|
|
156
248
|
// fixed — a thread-local is the VM's equivalent of the interpreter's shared `Rc<Cell>` counter.
|
|
@@ -639,6 +639,7 @@ impl Evaluator {
|
|
|
639
639
|
}
|
|
640
640
|
Statement::While { cond, body, .. } => {
|
|
641
641
|
loop {
|
|
642
|
+
deadline_check()?;
|
|
642
643
|
if !self.eval_expr(cond)?.is_truthy() {
|
|
643
644
|
break;
|
|
644
645
|
}
|
|
@@ -778,6 +779,10 @@ impl Evaluator {
|
|
|
778
779
|
};
|
|
779
780
|
let mut ret = Ok(Value::Null);
|
|
780
781
|
loop {
|
|
782
|
+
if let Err(e) = deadline_check() {
|
|
783
|
+
ret = Err(e);
|
|
784
|
+
break;
|
|
785
|
+
}
|
|
781
786
|
self.scope = Rc::clone(&loop_env);
|
|
782
787
|
let cond_ok = match cond.as_ref() {
|
|
783
788
|
Some(c) => match self.eval_expr(c) {
|
|
@@ -909,6 +914,7 @@ impl Evaluator {
|
|
|
909
914
|
}
|
|
910
915
|
Statement::DoWhile { body, cond, .. } => {
|
|
911
916
|
loop {
|
|
917
|
+
deadline_check()?;
|
|
912
918
|
match self.eval_statement(body) {
|
|
913
919
|
Ok(_) => {}
|
|
914
920
|
Err(EvalError::Break) => break,
|
|
@@ -5389,6 +5395,16 @@ impl Evaluator {
|
|
|
5389
5395
|
}
|
|
5390
5396
|
}
|
|
5391
5397
|
|
|
5398
|
+
/// Cooperative timeout poll for loop back-edges (`tish test --timeout`). Disarmed on every
|
|
5399
|
+
/// other path, where it costs one relaxed atomic load.
|
|
5400
|
+
#[inline]
|
|
5401
|
+
fn deadline_check() -> Result<(), EvalError> {
|
|
5402
|
+
if tishlang_core::execution_deadline_exceeded() {
|
|
5403
|
+
return Err(EvalError::Error(tishlang_core::DEADLINE_ERROR.to_string()));
|
|
5404
|
+
}
|
|
5405
|
+
Ok(())
|
|
5406
|
+
}
|
|
5407
|
+
|
|
5392
5408
|
#[derive(Debug)]
|
|
5393
5409
|
enum EvalError {
|
|
5394
5410
|
Return(Value),
|
|
@@ -187,6 +187,16 @@ pub fn eval_to_core(v: &Value) -> Result<CoreValue, String> {
|
|
|
187
187
|
}
|
|
188
188
|
Value::Symbol(s) => Ok(CoreValue::Symbol(Arc::clone(s))),
|
|
189
189
|
Value::Opaque(o) => Ok(CoreValue::Opaque(Arc::clone(o))),
|
|
190
|
+
// A core function that came IN through `core_to_eval` round-trips back out unchanged.
|
|
191
|
+
Value::CoreFn(f) => Ok(CoreValue::Function(f.clone())),
|
|
192
|
+
// Interpreter closures carry an AST body plus a scope that only the `Evaluator` can
|
|
193
|
+
// resolve, and they do not outlive it. Say so instead of printing a discriminant.
|
|
194
|
+
Value::Function { .. } | Value::Native(_) => Err(
|
|
195
|
+
"Cannot pass an interpreter function to a native module: interpreter closures are \
|
|
196
|
+
bound to the evaluator and cannot be invoked from native code. Use the default \
|
|
197
|
+
(VM) backend."
|
|
198
|
+
.to_string(),
|
|
199
|
+
),
|
|
190
200
|
_ => Err(format!(
|
|
191
201
|
"Cannot pass {:?} to native function (unsupported type)",
|
|
192
202
|
std::mem::discriminant(v)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "tishlang_test"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "Native tish test runner, expect, and Node-compatible assert"
|
|
6
|
+
license-file = { workspace = true }
|
|
7
|
+
repository = { workspace = true }
|
|
8
|
+
|
|
9
|
+
[features]
|
|
10
|
+
default = ["fs", "regex", "promise"]
|
|
11
|
+
# Fan features into runtime + optional eval/vm so send-values / regex stay consistent.
|
|
12
|
+
fs = ["tishlang_runtime/fs", "tishlang_eval/fs", "tishlang_vm/fs"]
|
|
13
|
+
regex = [
|
|
14
|
+
"tishlang_core/regex",
|
|
15
|
+
"tishlang_runtime/regex",
|
|
16
|
+
"tishlang_eval/regex",
|
|
17
|
+
"tishlang_vm/regex",
|
|
18
|
+
]
|
|
19
|
+
promise = ["tishlang_runtime/promise", "tishlang_vm/promise"]
|
|
20
|
+
# Full runner needs compile + VM to load files
|
|
21
|
+
runner = [
|
|
22
|
+
"dep:tishlang_compile",
|
|
23
|
+
"dep:tishlang_opt",
|
|
24
|
+
"dep:tishlang_bytecode",
|
|
25
|
+
"dep:tishlang_vm",
|
|
26
|
+
"dep:tishlang_ast",
|
|
27
|
+
"dep:tishlang_eval",
|
|
28
|
+
]
|
|
29
|
+
watch = ["dep:notify"]
|
|
30
|
+
|
|
31
|
+
[dependencies]
|
|
32
|
+
tishlang_core = { path = "../tish_core", version = ">=0.1" }
|
|
33
|
+
tishlang_builtins = { path = "../tish_builtins", version = ">=0.1" }
|
|
34
|
+
tishlang_runtime = { path = "../tish_runtime", version = ">=0.1" }
|
|
35
|
+
tishlang_ast = { path = "../tish_ast", version = ">=0.1", optional = true }
|
|
36
|
+
tishlang_compile = { path = "../tish_compile", version = ">=0.1", optional = true }
|
|
37
|
+
tishlang_opt = { path = "../tish_opt", version = ">=0.1", optional = true }
|
|
38
|
+
tishlang_bytecode = { path = "../tish_bytecode", version = ">=0.1", optional = true }
|
|
39
|
+
tishlang_vm = { path = "../tish_vm", version = ">=0.1", optional = true }
|
|
40
|
+
tishlang_eval = { path = "../tish_eval", version = ">=0.1", optional = true }
|
|
41
|
+
serde_json = "1"
|
|
42
|
+
walkdir = "2"
|
|
43
|
+
regex = "1"
|
|
44
|
+
notify = { version = "7", optional = true }
|
|
45
|
+
|
|
46
|
+
[dev-dependencies]
|
|
47
|
+
tempfile = "3"
|