@tishlang/tish-format 1.0.13 → 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 +2 -0
- package/bin/tish-format +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +1 -0
- package/crates/tish/Cargo.toml +10 -2
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cli_help.rs +15 -4
- package/crates/tish/src/main.rs +93 -21
- package/crates/tish/src/repl_completion.rs +0 -1
- package/crates/tish/tests/error_source_location.rs +36 -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 +402 -91
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/src/ast.rs +37 -8
- package/crates/tish_builtins/Cargo.toml +2 -0
- package/crates/tish_builtins/src/array.rs +375 -13
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +59 -19
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +86 -6
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +5 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +2 -2
- package/crates/tish_builtins/src/string.rs +19 -20
- package/crates/tish_builtins/src/symbol.rs +1 -1
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/src/chunk.rs +69 -1
- package/crates/tish_bytecode/src/compiler.rs +933 -89
- package/crates/tish_bytecode/src/encoding.rs +2 -0
- package/crates/tish_bytecode/src/lib.rs +2 -1
- package/crates/tish_bytecode/src/opcode.rs +47 -4
- package/crates/tish_bytecode/src/serialize.rs +31 -1
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +2334 -349
- package/crates/tish_compile/src/infer.rs +1395 -6
- package/crates/tish_compile/src/lib.rs +50 -8
- package/crates/tish_compile/src/resolve.rs +584 -21
- package/crates/tish_compile/src/types.rs +106 -2
- package/crates/tish_compile_js/src/codegen.rs +67 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +64 -0
- package/crates/tish_core/Cargo.toml +7 -1
- package/crates/tish_core/src/console_style.rs +11 -1
- package/crates/tish_core/src/json.rs +81 -38
- package/crates/tish_core/src/lib.rs +3 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/value.rs +679 -25
- package/crates/tish_core/src/vmref.rs +13 -8
- package/crates/tish_cranelift/src/link.rs +17 -4
- package/crates/tish_cranelift_runtime/Cargo.toml +1 -0
- package/crates/tish_eval/Cargo.toml +6 -0
- package/crates/tish_eval/src/eval.rs +665 -117
- package/crates/tish_eval/src/http.rs +4 -1
- package/crates/tish_eval/src/natives.rs +165 -13
- package/crates/tish_eval/src/value.rs +31 -13
- package/crates/tish_eval/src/value_convert.rs +10 -4
- 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 +1 -1
- package/crates/tish_fmt/src/lib.rs +61 -5
- package/crates/tish_lexer/src/lib.rs +397 -9
- package/crates/tish_lexer/src/token.rs +7 -0
- package/crates/tish_lint/src/lib.rs +2 -10
- package/crates/tish_lsp/src/import_goto.rs +2 -0
- package/crates/tish_lsp/src/main.rs +439 -26
- package/crates/tish_native/src/build.rs +55 -1
- package/crates/tish_opt/src/lib.rs +126 -23
- package/crates/tish_parser/src/lib.rs +55 -1
- package/crates/tish_parser/src/parser.rs +456 -34
- package/crates/tish_pg/src/lib.rs +3 -3
- package/crates/tish_resolve/src/lib.rs +99 -59
- package/crates/tish_runtime/Cargo.toml +4 -0
- package/crates/tish_runtime/src/http.rs +66 -17
- package/crates/tish_runtime/src/http_fetch.rs +29 -8
- package/crates/tish_runtime/src/http_hyper.rs +25 -2
- package/crates/tish_runtime/src/lib.rs +299 -44
- package/crates/tish_runtime/src/promise.rs +328 -18
- package/crates/tish_runtime/src/timers.rs +13 -7
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +35 -18
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +2 -2
- package/crates/tish_ui/src/jsx.rs +10 -0
- package/crates/tish_ui/src/runtime/hooks.rs +19 -15
- package/crates/tish_ui/src/runtime/mod.rs +15 -12
- package/crates/tish_vm/Cargo.toml +14 -1
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +2 -0
- package/crates/tish_vm/src/vm.rs +1546 -202
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
- package/crates/tish_wasm/src/lib.rs +6 -2
- package/crates/tish_wasm_runtime/src/gpu.rs +17 -1
- package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
- package/crates/tishlang_cargo_bindgen/src/lib.rs +2 -2
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +1 -1
- package/justfile +8 -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
- package/README.md +0 -138
|
@@ -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
|
+
}
|
|
@@ -379,10 +379,14 @@ fn main() {
|
|
|
379
379
|
message: format!("Cannot write main.rs: {}", e),
|
|
380
380
|
})?;
|
|
381
381
|
|
|
382
|
-
// Build
|
|
382
|
+
// Build into a SHARED target dir (one per host), not per-program. The wasi runtime + embedded
|
|
383
|
+
// VM then compile ONCE and are reused by every wasi build; only each program's tiny main is
|
|
384
|
+
// rebuilt. Without this each program left its own multi-GB `target/` and a full-suite sweep
|
|
385
|
+
// would fill the disk (same issue fixed for cranelift; see full-backend-parity-plan.md A3).
|
|
386
|
+
// cargo's target lock serializes concurrent builds safely.
|
|
383
387
|
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
|
|
384
388
|
let bin_name = format!("tish_wasi_{}", stem);
|
|
385
|
-
let target_dir =
|
|
389
|
+
let target_dir = std::env::temp_dir().join("tishlang_wasi_target");
|
|
386
390
|
let build_status = Command::new(&cargo)
|
|
387
391
|
.current_dir(&build_dir)
|
|
388
392
|
.env("CARGO_TARGET_DIR", &target_dir)
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
//! command API is synchronous, so this covers it)
|
|
11
11
|
//! - `js_new(ctorNameOrHandle, argsArray)`
|
|
12
12
|
//! - `js_typeof(handle)` — debugging
|
|
13
|
-
//! - `f32a(arr)` / `u16a(arr)` / `u8a(arr)` — tish `number[]` → real typed array
|
|
13
|
+
//! - `f32a(arr)` / `u16a(arr)` / `u8a(arr)` / `u32a(arr)` — tish `number[]` → real typed array
|
|
14
14
|
//! - `request_animation_frame(cb)` — drive a render loop
|
|
15
15
|
//!
|
|
16
16
|
//! GPU/JS objects (device, queue, context, buffers, pipelines, textures,
|
|
@@ -284,6 +284,21 @@ fn ffi_u8a() -> Value {
|
|
|
284
284
|
})
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
fn ffi_u32a() -> Value {
|
|
288
|
+
Value::native(|args: &[Value]| {
|
|
289
|
+
let arr = match args.first() {
|
|
290
|
+
Some(Value::Array(a)) => a.clone(),
|
|
291
|
+
_ => return Value::Null,
|
|
292
|
+
};
|
|
293
|
+
let b = arr.borrow();
|
|
294
|
+
let ta = js_sys::Uint32Array::new_with_length(b.len() as u32);
|
|
295
|
+
for (i, v) in b.iter().enumerate() {
|
|
296
|
+
ta.set_index(i as u32, v.as_number().unwrap_or(0.0) as u32);
|
|
297
|
+
}
|
|
298
|
+
wrap(ta.into())
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
|
|
287
302
|
// ---------------------------------------------------------------------------
|
|
288
303
|
// requestAnimationFrame render loop
|
|
289
304
|
// ---------------------------------------------------------------------------
|
|
@@ -362,6 +377,7 @@ fn install_ffi(vm: &mut Vm) {
|
|
|
362
377
|
vm.set_global("f32a".into(), ffi_f32a());
|
|
363
378
|
vm.set_global("u16a".into(), ffi_u16a());
|
|
364
379
|
vm.set_global("u8a".into(), ffi_u8a());
|
|
380
|
+
vm.set_global("u32a".into(), ffi_u32a());
|
|
365
381
|
vm.set_global("request_animation_frame".into(), ffi_request_animation_frame());
|
|
366
382
|
}
|
|
367
383
|
|
|
@@ -39,9 +39,7 @@ fn classify_tish_abi(item: &ItemFn) -> Option<SignatureClass> {
|
|
|
39
39
|
if value_args != 1 || sig.inputs.len() != 1 {
|
|
40
40
|
return None;
|
|
41
41
|
}
|
|
42
|
-
let
|
|
43
|
-
return None;
|
|
44
|
-
};
|
|
42
|
+
let ret_ty = return_type_inner(&sig.output)?;
|
|
45
43
|
if !is_value_type(ret_ty) {
|
|
46
44
|
return None;
|
|
47
45
|
}
|
|
@@ -121,7 +121,7 @@ fn generate_from_resolved(
|
|
|
121
121
|
impl BindgenConfig {
|
|
122
122
|
/// Write using [`generate_from_registry_dependency`].
|
|
123
123
|
pub fn write_files(&self) -> io::Result<()> {
|
|
124
|
-
generate_from_registry_dependency(self).map_err(
|
|
124
|
+
generate_from_registry_dependency(self).map_err(io::Error::other)
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -199,7 +199,7 @@ fn render_generated_lib(
|
|
|
199
199
|
name = rust_fn
|
|
200
200
|
),
|
|
201
201
|
SignatureClass::SerializeRefToResultString => format!(
|
|
202
|
-
"pub fn {name}(args: &[Value]) -> Value {{\n let Some(v) = args.first() else {{ return Value::Null }};\n match _tish_upstream::{name}(&tish_to_json(v)) {{\n Ok(s) => Value::String(
|
|
202
|
+
"pub fn {name}(args: &[Value]) -> Value {{\n let Some(v) = args.first() else {{ return Value::Null }};\n match _tish_upstream::{name}(&tish_to_json(v)) {{\n Ok(s) => Value::String(arcstr::ArcStr::from(s)),\n Err(_) => Value::Null,\n }}\n}}\n\n",
|
|
203
203
|
name = rust_fn
|
|
204
204
|
),
|
|
205
205
|
SignatureClass::DeserializeStrToResult => format!(
|
package/justfile
CHANGED
|
@@ -258,6 +258,14 @@ perf-suite *ARGS:
|
|
|
258
258
|
perf-suite-gen:
|
|
259
259
|
./scripts/generate_perf_ci_main.sh
|
|
260
260
|
|
|
261
|
+
# HTTP throughput: tish vs Node, single vs multi-worker, plaintext + json (needs oha + jq)
|
|
262
|
+
perf-http *ARGS:
|
|
263
|
+
./scripts/run_http_perf.sh {{ARGS}}
|
|
264
|
+
|
|
265
|
+
# Perf gauntlet: compute benchmarks vs Node, incl. known-fail targets to evolve past (needs node)
|
|
266
|
+
perf-gauntlet *ARGS:
|
|
267
|
+
./scripts/run_perf_gauntlet.sh {{ARGS}}
|
|
268
|
+
|
|
261
269
|
# Show binary sizes for different builds
|
|
262
270
|
sizes:
|
|
263
271
|
@echo "Building secure binary..."
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/README.md
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
<picture>
|
|
2
|
-
<img alt="Tishlang" src="https://tishlang.com/banner.png">
|
|
3
|
-
</picture>
|
|
4
|
-
|
|
5
|
-
# Tish
|
|
6
|
-
|
|
7
|
-
<p>
|
|
8
|
-
<a href="https://npmjs.com/package/@tishlang/tish?activeTab=readme"><img src="https://img.shields.io/npm/v/@tishlang/tish?style=flat-square&colorA=1C1C1C&colorB=B688FF" alt="npm version" /></a>
|
|
9
|
-
<a href="https://npmcharts.com/compare/@tishlang/tish"><img src="https://img.shields.io/npm/dm/@tishlang/tish.svg?style=flat-square&colorA=1C1C1C&colorB=B688FF" alt="downloads" /></a>
|
|
10
|
-
<a href="https://nodejs.org/en/about/previous-releases"><img src="https://img.shields.io/node/v/@tishlang/tish.svg?style=flat-square&colorA=1C1C1C&colorB=B688FF" alt="node version"></a>
|
|
11
|
-
<a href="https://crates.io/crates/tishlang"><img src="https://img.shields.io/crates/v/tishlang?style=flat-square&colorA=1C1C1C&colorB=B688FF" alt="crate version" /></a>
|
|
12
|
-
<a href="https://github.com/tishlang/tish/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-PIF-blue.svg?style=flat-square&colorA=1C1C1C&colorB=B688FF" alt="license" /></a>
|
|
13
|
-
|
|
14
|
-
</p>
|
|
15
|
-
|
|
16
|
-
Tish is a TypeScript- and JavaScript-compatible language implemented in Rust. It is aimed at teams who want a familiar surface syntax, predictable semantics, and the option to ship either interpreted scripts or runtime free **native binaries**.
|
|
17
|
-
|
|
18
|
-
The same source can run on a tree-walking interpreter, a bytecode VM, or compiled targets (native, WASM/WASI, and others). Network, filesystem, and process APIs are **feature-gated** so defaults stay safe. For the full syntax and semantics, see the canonical spec in-repo; for tutorials and reference, see [tishlang.com/docs](https://tishlang.com/docs).
|
|
19
|
-
|
|
20
|
-
## 🔥 Features
|
|
21
|
-
|
|
22
|
-
- **JS-like surface** — `let` / `const`, `fn`, arrows, template literals, async/await, modules, and a large slice of familiar builtins (`console`, `Math`, `JSON`, arrays, strings, objects).
|
|
23
|
-
- **Two ways to run** — interpret for fast iteration (`tish run`, REPL); compile to native or WASM for distribution and performance (`tish build`).
|
|
24
|
-
- **Memory-safe implementation** — Rust-hosted runtime and compiler pipeline; no GC in the host language for the toolchain itself.
|
|
25
|
-
- **Secure by default** — I/O and platform APIs (`http`, `fs`, `process`, etc.) are opt-in via features.
|
|
26
|
-
- **No `undefined`** — `null` only where JS would use undefined; strict equality (`===` / `!==`) without loose coercion.
|
|
27
|
-
- **Optional types** — TypeScript-style annotations are parsed for tooling and future checking; see the language reference for status.
|
|
28
|
-
|
|
29
|
-
Full specification: [docs/LANGUAGE.md](docs/LANGUAGE.md). Implementation status, gaps, and JS compatibility: [docs/plan-gap-analysis.md](docs/plan-gap-analysis.md).
|
|
30
|
-
|
|
31
|
-
## 📚 Documentation
|
|
32
|
-
|
|
33
|
-
User-facing guides and reference:
|
|
34
|
-
|
|
35
|
-
| Section | Description |
|
|
36
|
-
|---------|-------------|
|
|
37
|
-
| [Getting started](https://tishlang.com/docs/getting-started/installation/) | Install Tish, build your first app |
|
|
38
|
-
| [First app](https://tishlang.com/docs/getting-started/first-app/) | Run and build workflows, targets |
|
|
39
|
-
| [Editor & IDE](https://tishlang.com/docs/getting-started/editor/) | VS Code, LSP, tasks, Neovim |
|
|
40
|
-
| [Language server](https://tishlang.com/docs/reference/language-server/) | `tish-lsp` capabilities |
|
|
41
|
-
| [Formatting](https://tishlang.com/docs/reference/formatting/) | `tish-fmt` |
|
|
42
|
-
| [Linting](https://tishlang.com/docs/reference/linting/) | `tish-lint` |
|
|
43
|
-
| [Interactive REPL](https://tishlang.com/docs/getting-started/repl/) | Multi-line input, completion, history |
|
|
44
|
-
| [Language overview](https://tishlang.com/docs/language/overview/) | Syntax, keywords, semantics |
|
|
45
|
-
| [Tish vs JavaScript](https://tishlang.com/docs/language/vs-javascript/) | Differences and additions from JS |
|
|
46
|
-
| [Builtins](https://tishlang.com/docs/builtins/overview/) | Console, Math, JSON, Array, String, Object |
|
|
47
|
-
| [Features (APIs)](https://tishlang.com/docs/features/http/) | `http`, `fs`, `process`, `regex` — feature-gated |
|
|
48
|
-
| [Native backend](https://tishlang.com/docs/reference/native-backend/) | Rust, Cranelift, LLVM compilation |
|
|
49
|
-
| [WASM targets](https://tishlang.com/docs/reference/wasm-targets/) | Web and WASI |
|
|
50
|
-
| [Deploy](https://tishlang.com/docs/deploy/overview/) | Platform and hosting |
|
|
51
|
-
|
|
52
|
-
### In-repo docs
|
|
53
|
-
|
|
54
|
-
Contributor- and spec-oriented material in [docs/](docs/):
|
|
55
|
-
|
|
56
|
-
| File | Purpose |
|
|
57
|
-
|------|---------|
|
|
58
|
-
| [LANGUAGE.md](docs/LANGUAGE.md) | Canonical language reference (syntax, semantics, builtins) |
|
|
59
|
-
| [ecma-alignment.md](docs/ecma-alignment.md) | ECMA-262 / test262 mapping |
|
|
60
|
-
| [plan-gap-analysis.md](docs/plan-gap-analysis.md) | Implementation audit, MVP checklist |
|
|
61
|
-
| [architecture-next-steps.md](docs/architecture-next-steps.md) | Crate layout, design decisions |
|
|
62
|
-
| [builtins-gap-analysis.md](docs/builtins-gap-analysis.md) | Builtins across Rust vs bytecode VM (Cranelift/WASI) |
|
|
63
|
-
|
|
64
|
-
## 🛠️ Toolchain
|
|
65
|
-
|
|
66
|
-
| Tool | Purpose |
|
|
67
|
-
|------|---------|
|
|
68
|
-
| **`tish`** | CLI — `run`, `repl`, `build`, `dump-ast` |
|
|
69
|
-
| **`tish-fmt`** | Formatter |
|
|
70
|
-
| **`tish-lint`** | Linter (`--format sarif` for code scanning) |
|
|
71
|
-
| **`tish-lsp`** | Language server; uses `tish_fmt` / `tish_lint` as libraries |
|
|
72
|
-
| **`tish-security/`** (sibling repo) | Rules & CI examples — clone next to `tish/` as `../tish-security` ([OpenGrep](https://github.com/opengrep/opengrep), ast-grep, Comby, Codacy scaffold, fixtures) |
|
|
73
|
-
| **`tree-sitter-tish/`** | Tree-sitter grammar (editors, ast-grep, future OpenGrep integration) |
|
|
74
|
-
| **VS Code extension** | [tish-vscode](https://github.com/tishlang/tish-vscode) — grammar, snippets, LSP client, tasks |
|
|
75
|
-
|
|
76
|
-
Related docs on [tishlang.com](https://tishlang.com/docs): [Editor & IDE](https://tishlang.com/docs/getting-started/editor/), [Language server](https://tishlang.com/docs/reference/language-server/), [Formatting](https://tishlang.com/docs/reference/formatting/), [Linting](https://tishlang.com/docs/reference/linting/).
|
|
77
|
-
|
|
78
|
-
## 📦 Installation
|
|
79
|
-
|
|
80
|
-
Install globally:
|
|
81
|
-
|
|
82
|
-
```sh
|
|
83
|
-
brew tap tishlang/tish https://github.com/tishlang/tish
|
|
84
|
-
brew install tish
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Or locally with npm:
|
|
88
|
-
|
|
89
|
-
```sh
|
|
90
|
-
npm install @tishlang/tish
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
More options: [Installation](https://tishlang.com/docs/getting-started/installation/).
|
|
94
|
-
|
|
95
|
-
## ⚡ Quick start
|
|
96
|
-
|
|
97
|
-
```sh
|
|
98
|
-
npx @tishlang/create-tish-app my-app
|
|
99
|
-
cd my-app
|
|
100
|
-
npx @tishlang/tish run src/main.tish
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## ▶️ Run and build
|
|
104
|
-
|
|
105
|
-
```tish
|
|
106
|
-
// hello.tish
|
|
107
|
-
fn greeting(name) = `Hello, ${name}!`
|
|
108
|
-
console.log(greeting("World"))
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
tish run hello.tish
|
|
113
|
-
# Hello, World!
|
|
114
|
-
|
|
115
|
-
tish build hello.tish -o hello
|
|
116
|
-
./hello
|
|
117
|
-
# Hello, World!
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Native binaries are standalone (no Tish or Rust runtime required on the machine that runs them). Backends, flags, and WASM are covered in [First app](https://tishlang.com/docs/getting-started/first-app/), [Native backend](https://tishlang.com/docs/reference/native-backend/), and [WASM targets](https://tishlang.com/docs/reference/wasm-targets/).
|
|
121
|
-
|
|
122
|
-
## 🤝 Contribution
|
|
123
|
-
|
|
124
|
-
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for building, testing, and code style. Tish is licensed under the [Pay It Forward License (PIF)](https://payitforwardlicense.com/).
|
|
125
|
-
|
|
126
|
-
## 💪 Performance
|
|
127
|
-
|
|
128
|
-
JavaScript equivalents live in `tests/core/*.js`. Compare Tish with Node.js or Bun:
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
./scripts/run_performance_manual.sh
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Details: [docs/perf.md](docs/perf.md).
|
|
135
|
-
|
|
136
|
-
## 📝 License
|
|
137
|
-
|
|
138
|
-
Tish is licensed under the [Pay It Forward License (PIF)](https://payitforwardlicense.com/). See [LICENSE](LICENSE).
|