@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.
Files changed (113) hide show
  1. package/Cargo.toml +2 -0
  2. package/README.md +2 -0
  3. package/bin/tish +0 -0
  4. package/crates/js_to_tish/src/error.rs +2 -8
  5. package/crates/js_to_tish/src/transform/expr.rs +128 -137
  6. package/crates/js_to_tish/src/transform/stmt.rs +62 -32
  7. package/crates/tish/Cargo.toml +15 -5
  8. package/crates/tish/src/cargo_native_registry.rs +29 -0
  9. package/crates/tish/src/cli_help.rs +92 -39
  10. package/crates/tish/src/main.rs +172 -86
  11. package/crates/tish/src/repl_completion.rs +3 -3
  12. package/crates/tish/tests/cargo_example_compile.rs +4 -2
  13. package/crates/tish/tests/integration_test.rs +216 -54
  14. package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
  15. package/crates/tish/tests/shortcircuit.rs +20 -5
  16. package/crates/tish_ast/src/ast.rs +92 -23
  17. package/crates/tish_build_utils/Cargo.toml +4 -0
  18. package/crates/tish_build_utils/src/lib.rs +136 -8
  19. package/crates/tish_builtins/Cargo.toml +5 -1
  20. package/crates/tish_builtins/src/array.rs +65 -33
  21. package/crates/tish_builtins/src/construct.rs +34 -39
  22. package/crates/tish_builtins/src/globals.rs +42 -26
  23. package/crates/tish_builtins/src/helpers.rs +2 -1
  24. package/crates/tish_builtins/src/lib.rs +5 -5
  25. package/crates/tish_builtins/src/math.rs +5 -3
  26. package/crates/tish_builtins/src/object.rs +3 -2
  27. package/crates/tish_builtins/src/string.rs +144 -22
  28. package/crates/tish_bytecode/src/chunk.rs +0 -1
  29. package/crates/tish_bytecode/src/compiler.rs +173 -71
  30. package/crates/tish_bytecode/src/opcode.rs +24 -6
  31. package/crates/tish_bytecode/src/peephole.rs +2 -2
  32. package/crates/tish_compile/Cargo.toml +1 -0
  33. package/crates/tish_compile/src/codegen.rs +1621 -453
  34. package/crates/tish_compile/src/infer.rs +75 -19
  35. package/crates/tish_compile/src/lib.rs +19 -8
  36. package/crates/tish_compile/src/resolve.rs +278 -137
  37. package/crates/tish_compile/src/types.rs +184 -24
  38. package/crates/tish_compile_js/Cargo.toml +1 -0
  39. package/crates/tish_compile_js/src/codegen.rs +181 -37
  40. package/crates/tish_compile_js/src/lib.rs +3 -1
  41. package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
  42. package/crates/tish_compiler_wasm/src/lib.rs +16 -13
  43. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
  44. package/crates/tish_core/Cargo.toml +8 -0
  45. package/crates/tish_core/src/json.rs +107 -56
  46. package/crates/tish_core/src/lib.rs +4 -2
  47. package/crates/tish_core/src/macros.rs +5 -5
  48. package/crates/tish_core/src/uri.rs +9 -6
  49. package/crates/tish_core/src/value.rs +145 -43
  50. package/crates/tish_core/src/vmref.rs +178 -0
  51. package/crates/tish_cranelift/src/link.rs +6 -9
  52. package/crates/tish_cranelift/src/lower.rs +14 -8
  53. package/crates/tish_eval/Cargo.toml +17 -2
  54. package/crates/tish_eval/src/eval.rs +474 -165
  55. package/crates/tish_eval/src/http.rs +61 -0
  56. package/crates/tish_eval/src/lib.rs +12 -8
  57. package/crates/tish_eval/src/natives.rs +136 -38
  58. package/crates/tish_eval/src/promise.rs +14 -8
  59. package/crates/tish_eval/src/timers.rs +28 -19
  60. package/crates/tish_eval/src/value.rs +17 -6
  61. package/crates/tish_eval/src/value_convert.rs +13 -5
  62. package/crates/tish_fmt/src/lib.rs +149 -43
  63. package/crates/tish_lexer/src/lib.rs +232 -63
  64. package/crates/tish_lexer/src/token.rs +10 -6
  65. package/crates/tish_llvm/src/lib.rs +17 -8
  66. package/crates/tish_lsp/Cargo.toml +4 -1
  67. package/crates/tish_lsp/README.md +1 -1
  68. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  69. package/crates/tish_lsp/src/import_goto.rs +549 -0
  70. package/crates/tish_lsp/src/main.rs +504 -106
  71. package/crates/tish_native/src/build.rs +4 -8
  72. package/crates/tish_native/src/lib.rs +54 -21
  73. package/crates/tish_opt/src/lib.rs +84 -52
  74. package/crates/tish_parser/src/lib.rs +45 -13
  75. package/crates/tish_parser/src/parser.rs +505 -130
  76. package/crates/tish_resolve/Cargo.toml +13 -0
  77. package/crates/tish_resolve/src/lib.rs +3436 -0
  78. package/crates/tish_resolve/src/pos.rs +133 -0
  79. package/crates/tish_runtime/Cargo.toml +68 -3
  80. package/crates/tish_runtime/src/http.rs +1136 -145
  81. package/crates/tish_runtime/src/http_fetch.rs +38 -27
  82. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  83. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  84. package/crates/tish_runtime/src/lib.rs +375 -189
  85. package/crates/tish_runtime/src/promise.rs +199 -40
  86. package/crates/tish_runtime/src/promise_io.rs +2 -1
  87. package/crates/tish_runtime/src/timers.rs +37 -1
  88. package/crates/tish_runtime/src/ws.rs +65 -42
  89. package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
  90. package/crates/tish_ui/src/jsx.rs +317 -27
  91. package/crates/tish_ui/src/lib.rs +5 -2
  92. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  93. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  94. package/crates/tish_vm/Cargo.toml +15 -5
  95. package/crates/tish_vm/src/vm.rs +725 -281
  96. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
  97. package/crates/tish_wasm/src/lib.rs +55 -42
  98. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  99. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  100. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  101. package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
  102. package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
  103. package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
  104. package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
  105. package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
  106. package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
  107. package/justfile +8 -0
  108. package/package.json +1 -1
  109. package/platform/darwin-arm64/tish +0 -0
  110. package/platform/darwin-x64/tish +0 -0
  111. package/platform/linux-arm64/tish +0 -0
  112. package/platform/linux-x64/tish +0 -0
  113. package/platform/win32-x64/tish.exe +0 -0
@@ -0,0 +1,189 @@
1
+ //! Process-level prefork for the `tish:http` server.
2
+ //!
3
+ //! ## Why
4
+ //!
5
+ //! Tish's `Value` type is reference-counted with `Rc`/`RefCell` and therefore
6
+ //! `!Send`. Serving HTTP in parallel across CPU cores with the existing VM
7
+ //! would require either
8
+ //!
9
+ //! 1. a wholesale `Rc → Arc` conversion across every Tish crate, or
10
+ //! 2. spinning up independent VM instances that never share a `Value`.
11
+ //!
12
+ //! Option 1 taxes every single-threaded Tish program with atomic ref-count
13
+ //! overhead forever. Option 2 is what this file implements, via the classic
14
+ //! UNIX **prefork** pattern:
15
+ //!
16
+ //! * The parent process (worker 0) `fork`s — actually `spawn`s a new
17
+ //! `std::process::Command` pointing at the current executable — once per
18
+ //! extra core. Each child re-executes the entire Tish program in its own
19
+ //! address space.
20
+ //! * All processes (parent + children) bind the same `port` with
21
+ //! `SO_REUSEPORT`; the kernel hashes incoming connections across them.
22
+ //! * Each process runs a *single-threaded* accept + dispatch loop, so the
23
+ //! Tish VM stays single-threaded and `Value` stays `Rc`-backed.
24
+ //!
25
+ //! ## Why this is the right default
26
+ //!
27
+ //! * **nginx, gunicorn, unicorn, puma (cluster), and phpfpm all ship this
28
+ //! model.** It's the battle-tested way to extract N-core throughput from a
29
+ //! single-threaded scripting runtime.
30
+ //! * Zero Tish-language semantic changes: users write `serve(port, handler)`
31
+ //! exactly as before and get N-core scaling for free.
32
+ //! * Every process has a fresh DB connection pool, a fresh cache, a fresh
33
+ //! whatever. No cache invalidation, no shared mutable state, no data races.
34
+ //! * Crash isolation: if one worker panics the others keep serving.
35
+ //!
36
+ //! ## Cost
37
+ //!
38
+ //! * Each worker re-runs top-level initialization (module imports, constant
39
+ //! folding, static route registration, cache warmup, ...). For typical
40
+ //! apps this is milliseconds and happens once at startup, in parallel. For
41
+ //! apps that preload hundreds of MB of in-process state (e.g. the TFB
42
+ //! `warmupWorldCache` that keeps 10 000 rows in RAM), the memory multiplier
43
+ //! is N×. Users who can't afford the memory set `TISH_HTTP_WORKERS=1`.
44
+ //!
45
+ //! ## Control surface
46
+ //!
47
+ //! | env var | default | effect |
48
+ //! |---------------------|------------------------|--------------------------------------|
49
+ //! | `TISH_HTTP_WORKERS` | `available_parallelism`| number of worker processes |
50
+ //! | `TISH_HTTP_PREFORK` | `1` (on) | set to `0` to force single-process |
51
+ //! | `TISH_WORKER_ID` | unset on parent | set on children by the parent |
52
+ //!
53
+ //! Children are launched with `TISH_WORKER_ID={1..N-1}` and
54
+ //! `TISH_HTTP_PREFORK=child`. The serve() runtime checks these before
55
+ //! deciding whether to fork again, preventing runaway forking.
56
+
57
+ use std::io;
58
+ use std::process::{Child, Command};
59
+ use std::sync::atomic::{AtomicBool, Ordering};
60
+ use std::sync::Arc;
61
+
62
+ /// Role of the current process in a prefork group.
63
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
64
+ pub enum PreforkRole {
65
+ /// The parent — owns the child PIDs, handles signals, re-execs nothing.
66
+ Parent,
67
+ /// A child spawned by the parent. Never forks again.
68
+ Child(usize),
69
+ /// Prefork disabled (single-process mode).
70
+ Single,
71
+ }
72
+
73
+ /// Inspect the environment to decide which role this process plays.
74
+ pub fn role_from_env() -> PreforkRole {
75
+ let is_child = std::env::var("TISH_HTTP_PREFORK")
76
+ .map(|v| v.eq_ignore_ascii_case("child"))
77
+ .unwrap_or(false);
78
+ if is_child {
79
+ let id = std::env::var("TISH_WORKER_ID")
80
+ .ok()
81
+ .and_then(|s| s.parse::<usize>().ok())
82
+ .unwrap_or(1);
83
+ return PreforkRole::Child(id);
84
+ }
85
+ let disabled = std::env::var("TISH_HTTP_PREFORK")
86
+ .map(|v| v == "0" || v.eq_ignore_ascii_case("false") || v.eq_ignore_ascii_case("off"))
87
+ .unwrap_or(false);
88
+ if disabled {
89
+ PreforkRole::Single
90
+ } else {
91
+ PreforkRole::Parent
92
+ }
93
+ }
94
+
95
+ /// Spawn `n - 1` child processes (current worker is worker 0). Each child
96
+ /// inherits stdio and gets `TISH_WORKER_ID={1..n-1}` +
97
+ /// `TISH_HTTP_PREFORK=child` so it doesn't recurse.
98
+ ///
99
+ /// Returns the child handles so the parent can reap / signal them.
100
+ pub fn spawn_children(n: usize) -> io::Result<Vec<Child>> {
101
+ if n <= 1 {
102
+ return Ok(Vec::new());
103
+ }
104
+ let exe = std::env::current_exe()?; // codacy-disable-line
105
+ let args: Vec<std::ffi::OsString> = std::env::args_os().skip(1).collect(); // codacy-disable-line
106
+ let mut out = Vec::with_capacity(n - 1);
107
+ for i in 1..n {
108
+ let mut cmd = Command::new(&exe);
109
+ cmd.args(&args);
110
+ cmd.env("TISH_WORKER_ID", i.to_string());
111
+ cmd.env("TISH_HTTP_PREFORK", "child");
112
+ // Children inherit the shared cache of 1 thread so they don't recurse
113
+ // into SO_REUSEPORT multi-listener logic. The parent keeps the same.
114
+ cmd.env("TISH_HTTP_WORKERS", "1");
115
+ // Inherit stdout/stderr: child logs stream into the same terminal.
116
+ let child = cmd.spawn()?;
117
+ out.push(child);
118
+ }
119
+ Ok(out)
120
+ }
121
+
122
+ /// Install a Ctrl-C / SIGTERM handler on the parent that propagates to all
123
+ /// children. Safe to call multiple times; the handler is stored in a
124
+ /// process-wide slot.
125
+ ///
126
+ /// Returns a shared stop flag that callers can poll from their accept loop.
127
+ pub fn install_parent_signal_handler(children: Vec<Child>) -> Arc<AtomicBool> {
128
+ let stop = Arc::new(AtomicBool::new(false));
129
+ let pids: Vec<u32> = children.iter().map(|c| c.id()).collect();
130
+ install_shutdown_handler(Arc::clone(&stop), pids);
131
+
132
+ // Reap children in the background so they don't zombify when the user
133
+ // ^C's or when a child dies on its own.
134
+ std::thread::Builder::new()
135
+ .name("tish-prefork-reaper".into())
136
+ .spawn(move || {
137
+ for mut child in children {
138
+ let _ = child.wait();
139
+ }
140
+ })
141
+ .ok();
142
+ stop
143
+ }
144
+
145
+ #[cfg(unix)]
146
+ fn install_shutdown_handler(stop: Arc<AtomicBool>, pids: Vec<u32>) {
147
+ // Store state in process-global statics so an `extern "C"` fn can reach
148
+ // them from inside a signal handler. This is the usual pattern for
149
+ // libc::signal callbacks — setting a flag + waking up listeners is the
150
+ // only async-signal-safe work we do here.
151
+ use std::sync::OnceLock;
152
+ static STOP_FLAG: OnceLock<Arc<AtomicBool>> = OnceLock::new();
153
+ static CHILD_PIDS: OnceLock<Vec<u32>> = OnceLock::new();
154
+
155
+ let _ = STOP_FLAG.set(stop);
156
+ let _ = CHILD_PIDS.set(pids);
157
+
158
+ extern "C" fn on_signal(sig: libc::c_int) {
159
+ if let Some(flag) = STOP_FLAG.get() {
160
+ flag.store(true, Ordering::Relaxed);
161
+ }
162
+ if let Some(pids) = CHILD_PIDS.get() {
163
+ for pid in pids {
164
+ // codacy-disable-next-line
165
+ unsafe {
166
+ libc::kill(*pid as libc::pid_t, libc::SIGTERM);
167
+ }
168
+ }
169
+ }
170
+ // Re-raise with default disposition so the parent actually exits.
171
+ // codacy-disable-next-line
172
+ unsafe {
173
+ libc::signal(sig, libc::SIG_DFL);
174
+ libc::raise(sig);
175
+ }
176
+ }
177
+
178
+ let h = on_signal as *const () as libc::sighandler_t;
179
+ // codacy-disable-next-line
180
+ unsafe {
181
+ libc::signal(libc::SIGINT, h);
182
+ libc::signal(libc::SIGTERM, h);
183
+ }
184
+ }
185
+
186
+ #[cfg(not(unix))]
187
+ fn install_shutdown_handler(_stop: Arc<AtomicBool>, _pids: Vec<u32>) {
188
+ // TODO: SetConsoleCtrlHandler on Windows.
189
+ }