@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
|
@@ -8,8 +8,10 @@ mod json;
|
|
|
8
8
|
mod macros;
|
|
9
9
|
mod uri;
|
|
10
10
|
mod value;
|
|
11
|
+
mod vmref;
|
|
11
12
|
|
|
12
13
|
pub use console_style::{format_value_styled, format_values_for_console, use_console_colors};
|
|
13
|
-
pub use
|
|
14
|
-
pub use json::{json_parse, json_stringify};
|
|
14
|
+
pub use json::{json_parse, json_stringify, json_stringify_into};
|
|
15
15
|
pub use uri::{percent_decode, percent_encode};
|
|
16
|
+
pub use value::*;
|
|
17
|
+
pub use vmref::{VmReadGuard, VmRef, VmWriteGuard};
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
#[macro_export]
|
|
24
24
|
macro_rules! tish_module {
|
|
25
25
|
($($name:expr => $fn:expr),* $(,)?) => {{
|
|
26
|
-
use std::cell::RefCell;
|
|
27
|
-
use std::rc::Rc;
|
|
28
26
|
use std::sync::Arc;
|
|
29
|
-
use $crate::{ObjectMap, Value};
|
|
27
|
+
use $crate::{ObjectMap, Value, VmRef};
|
|
30
28
|
let mut map = ObjectMap::default();
|
|
31
29
|
$(
|
|
32
|
-
|
|
30
|
+
// `Value::native` picks the right Rc / Arc wrapper depending on
|
|
31
|
+
// whether the `send-values` feature is enabled upstream.
|
|
32
|
+
map.insert(Arc::from($name), Value::native($fn));
|
|
33
33
|
)*
|
|
34
|
-
Value::Object(
|
|
34
|
+
Value::Object(VmRef::new(map))
|
|
35
35
|
}};
|
|
36
36
|
}
|
|
@@ -10,13 +10,13 @@ pub fn percent_decode(input: &str) -> Result<String, String> {
|
|
|
10
10
|
"%2F", "%2f", // /
|
|
11
11
|
"%3F", "%3f", // ?
|
|
12
12
|
"%3A", "%3a", // :
|
|
13
|
-
"%40",
|
|
14
|
-
"%26",
|
|
13
|
+
"%40", // @
|
|
14
|
+
"%26", // &
|
|
15
15
|
"%3D", "%3d", // =
|
|
16
16
|
"%2B", "%2b", // +
|
|
17
|
-
"%24",
|
|
17
|
+
"%24", // $
|
|
18
18
|
"%2C", "%2c", // ,
|
|
19
|
-
"%23",
|
|
19
|
+
"%23", // #
|
|
20
20
|
];
|
|
21
21
|
|
|
22
22
|
let mut result = String::with_capacity(input.len());
|
|
@@ -46,11 +46,14 @@ pub fn percent_decode(input: &str) -> Result<String, String> {
|
|
|
46
46
|
None => return Err("URIError: malformed URI sequence".to_string()),
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
if hex.len() == 2 {
|
|
51
51
|
let encoded = format!("%{}", hex);
|
|
52
52
|
// Check if this is a reserved character that should NOT be decoded
|
|
53
|
-
if RESERVED_ENCODED
|
|
53
|
+
if RESERVED_ENCODED
|
|
54
|
+
.iter()
|
|
55
|
+
.any(|r| r.eq_ignore_ascii_case(&encoded))
|
|
56
|
+
{
|
|
54
57
|
result.push_str(&encoded);
|
|
55
58
|
} else if let Ok(byte) = u8::from_str_radix(&hex, 16) {
|
|
56
59
|
result.push(byte as char);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
//! Unified Value type for Tish runtime values.
|
|
2
2
|
|
|
3
|
-
use std::cell::RefCell;
|
|
4
|
-
use std::rc::Rc;
|
|
5
3
|
use std::sync::Arc;
|
|
6
4
|
|
|
7
5
|
use ahash::AHashMap;
|
|
8
6
|
|
|
7
|
+
use crate::vmref::VmRef;
|
|
8
|
+
|
|
9
9
|
/// Property map for objects and other `Arc<str>` → `Value` tables (VM globals, scopes).
|
|
10
10
|
/// Uses a faster hasher than `std::collections::HashMap` for string-heavy workloads.
|
|
11
11
|
pub type ObjectMap = AHashMap<Arc<str>, Value>;
|
|
@@ -14,8 +14,17 @@ pub type ObjectMap = AHashMap<Arc<str>, Value>;
|
|
|
14
14
|
use fancy_regex::Regex;
|
|
15
15
|
|
|
16
16
|
/// Native function signature.
|
|
17
|
-
///
|
|
18
|
-
|
|
17
|
+
///
|
|
18
|
+
/// When the `send-values` feature is enabled this is
|
|
19
|
+
/// `Arc<dyn Fn + Send + Sync>`, so handler closures can be dispatched across
|
|
20
|
+
/// HTTP worker threads (`tishlang_runtime::http::serve`). Otherwise it stays
|
|
21
|
+
/// `Rc<dyn Fn>` for zero-overhead single-threaded execution (wasm / wasi /
|
|
22
|
+
/// interpreter / cranelift / llvm VMs and any Rust native build without
|
|
23
|
+
/// `http`).
|
|
24
|
+
#[cfg(feature = "send-values")]
|
|
25
|
+
pub type NativeFn = Arc<dyn Fn(&[Value]) -> Value + Send + Sync>;
|
|
26
|
+
#[cfg(not(feature = "send-values"))]
|
|
27
|
+
pub type NativeFn = std::rc::Rc<dyn Fn(&[Value]) -> Value>;
|
|
19
28
|
|
|
20
29
|
/// Trait for opaque Rust types exposed to Tish (e.g. Polars DataFrame).
|
|
21
30
|
/// Implementors provide method dispatch so Tish can call methods on the value.
|
|
@@ -54,29 +63,70 @@ impl RegExpFlags {
|
|
|
54
63
|
let mut result = Self::default();
|
|
55
64
|
for c in flags.chars() {
|
|
56
65
|
match c {
|
|
57
|
-
'g' => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
'g' => {
|
|
67
|
+
if result.global {
|
|
68
|
+
return Err(format!("duplicate flag '{}'", c));
|
|
69
|
+
}
|
|
70
|
+
result.global = true;
|
|
71
|
+
}
|
|
72
|
+
'i' => {
|
|
73
|
+
if result.ignore_case {
|
|
74
|
+
return Err(format!("duplicate flag '{}'", c));
|
|
75
|
+
}
|
|
76
|
+
result.ignore_case = true;
|
|
77
|
+
}
|
|
78
|
+
'm' => {
|
|
79
|
+
if result.multiline {
|
|
80
|
+
return Err(format!("duplicate flag '{}'", c));
|
|
81
|
+
}
|
|
82
|
+
result.multiline = true;
|
|
83
|
+
}
|
|
84
|
+
's' => {
|
|
85
|
+
if result.dot_all {
|
|
86
|
+
return Err(format!("duplicate flag '{}'", c));
|
|
87
|
+
}
|
|
88
|
+
result.dot_all = true;
|
|
89
|
+
}
|
|
90
|
+
'u' => {
|
|
91
|
+
if result.unicode {
|
|
92
|
+
return Err(format!("duplicate flag '{}'", c));
|
|
93
|
+
}
|
|
94
|
+
result.unicode = true;
|
|
95
|
+
}
|
|
96
|
+
'y' => {
|
|
97
|
+
if result.sticky {
|
|
98
|
+
return Err(format!("duplicate flag '{}'", c));
|
|
99
|
+
}
|
|
100
|
+
result.sticky = true;
|
|
101
|
+
}
|
|
63
102
|
_ => return Err(format!("unknown flag '{}'", c)),
|
|
64
103
|
}
|
|
65
104
|
}
|
|
66
105
|
Ok(result)
|
|
67
106
|
}
|
|
68
|
-
|
|
69
107
|
}
|
|
70
108
|
|
|
71
109
|
#[cfg(feature = "regex")]
|
|
72
110
|
impl std::fmt::Display for RegExpFlags {
|
|
73
111
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
74
|
-
if self.global {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if self.
|
|
78
|
-
|
|
79
|
-
|
|
112
|
+
if self.global {
|
|
113
|
+
f.write_str("g")?;
|
|
114
|
+
}
|
|
115
|
+
if self.ignore_case {
|
|
116
|
+
f.write_str("i")?;
|
|
117
|
+
}
|
|
118
|
+
if self.multiline {
|
|
119
|
+
f.write_str("m")?;
|
|
120
|
+
}
|
|
121
|
+
if self.dot_all {
|
|
122
|
+
f.write_str("s")?;
|
|
123
|
+
}
|
|
124
|
+
if self.unicode {
|
|
125
|
+
f.write_str("u")?;
|
|
126
|
+
}
|
|
127
|
+
if self.sticky {
|
|
128
|
+
f.write_str("y")?;
|
|
129
|
+
}
|
|
80
130
|
Ok(())
|
|
81
131
|
}
|
|
82
132
|
}
|
|
@@ -96,23 +146,36 @@ impl TishRegExp {
|
|
|
96
146
|
pub fn new(pattern: &str, flags_str: &str) -> Result<Self, String> {
|
|
97
147
|
let flags = RegExpFlags::from_string(flags_str)?;
|
|
98
148
|
let mut regex_pattern = pattern.to_string();
|
|
99
|
-
|
|
149
|
+
|
|
100
150
|
if flags.ignore_case || flags.multiline || flags.dot_all {
|
|
101
151
|
let mut flag_prefix = String::from("(?");
|
|
102
|
-
if flags.ignore_case {
|
|
103
|
-
|
|
104
|
-
|
|
152
|
+
if flags.ignore_case {
|
|
153
|
+
flag_prefix.push('i');
|
|
154
|
+
}
|
|
155
|
+
if flags.multiline {
|
|
156
|
+
flag_prefix.push('m');
|
|
157
|
+
}
|
|
158
|
+
if flags.dot_all {
|
|
159
|
+
flag_prefix.push('s');
|
|
160
|
+
}
|
|
105
161
|
flag_prefix.push(')');
|
|
106
162
|
regex_pattern = format!("{}{}", flag_prefix, regex_pattern);
|
|
107
163
|
}
|
|
108
|
-
|
|
109
|
-
let regex =
|
|
110
|
-
.map_err(|e| format!("Invalid regular expression: {}", e))?;
|
|
111
|
-
|
|
112
|
-
Ok(Self {
|
|
164
|
+
|
|
165
|
+
let regex =
|
|
166
|
+
Regex::new(®ex_pattern).map_err(|e| format!("Invalid regular expression: {}", e))?;
|
|
167
|
+
|
|
168
|
+
Ok(Self {
|
|
169
|
+
source: pattern.to_string(),
|
|
170
|
+
flags,
|
|
171
|
+
regex: Arc::new(regex),
|
|
172
|
+
last_index: 0,
|
|
173
|
+
})
|
|
113
174
|
}
|
|
114
175
|
|
|
115
|
-
pub fn flags_string(&self) -> String {
|
|
176
|
+
pub fn flags_string(&self) -> String {
|
|
177
|
+
self.flags.to_string()
|
|
178
|
+
}
|
|
116
179
|
|
|
117
180
|
pub fn test(&mut self, input: &str) -> bool {
|
|
118
181
|
if self.flags.global || self.flags.sticky {
|
|
@@ -121,10 +184,10 @@ impl TishRegExp {
|
|
|
121
184
|
self.last_index = 0;
|
|
122
185
|
return false;
|
|
123
186
|
}
|
|
124
|
-
|
|
187
|
+
|
|
125
188
|
let byte_start: usize = input.chars().take(start).map(|c| c.len_utf8()).sum();
|
|
126
189
|
let search_str = &input[byte_start..];
|
|
127
|
-
|
|
190
|
+
|
|
128
191
|
match self.regex.find(search_str) {
|
|
129
192
|
Ok(Some(m)) => {
|
|
130
193
|
if self.flags.sticky && m.start() != 0 {
|
|
@@ -148,17 +211,21 @@ impl TishRegExp {
|
|
|
148
211
|
|
|
149
212
|
/// Runtime value for Tish programs.
|
|
150
213
|
/// Used by both interpreter and compiled code.
|
|
214
|
+
///
|
|
215
|
+
/// **Thread safety**: `Value: Send + Sync`. Mutable payloads live inside
|
|
216
|
+
/// [`VmRef`], a `Send + Sync` `Arc<Mutex<T>>` wrapper that preserves the
|
|
217
|
+
/// `RefCell`-style borrow API. Functions are `Arc<dyn Fn + Send + Sync>`.
|
|
151
218
|
#[derive(Clone)]
|
|
152
219
|
pub enum Value {
|
|
153
220
|
Number(f64),
|
|
154
221
|
String(Arc<str>),
|
|
155
222
|
Bool(bool),
|
|
156
223
|
Null,
|
|
157
|
-
Array(
|
|
158
|
-
Object(
|
|
224
|
+
Array(VmRef<Vec<Value>>),
|
|
225
|
+
Object(VmRef<ObjectMap>),
|
|
159
226
|
Function(NativeFn),
|
|
160
227
|
#[cfg(feature = "regex")]
|
|
161
|
-
RegExp(
|
|
228
|
+
RegExp(VmRef<TishRegExp>),
|
|
162
229
|
/// Promise (for native compile). Interpreter uses tishlang_eval::Value::Promise.
|
|
163
230
|
Promise(Arc<dyn TishPromise>),
|
|
164
231
|
/// Opaque handle to a native Rust type (e.g. Polars DataFrame).
|
|
@@ -176,7 +243,12 @@ impl std::fmt::Debug for Value {
|
|
|
176
243
|
Value::Object(obj) => write!(f, "Object({:?})", obj.borrow()),
|
|
177
244
|
Value::Function(_) => write!(f, "Function"),
|
|
178
245
|
#[cfg(feature = "regex")]
|
|
179
|
-
Value::RegExp(re) => write!(
|
|
246
|
+
Value::RegExp(re) => write!(
|
|
247
|
+
f,
|
|
248
|
+
"RegExp(/{}/{})",
|
|
249
|
+
re.borrow().source,
|
|
250
|
+
re.borrow().flags_string()
|
|
251
|
+
),
|
|
180
252
|
Value::Promise(_) => write!(f, "Promise"),
|
|
181
253
|
Value::Opaque(o) => write!(f, "{}(opaque)", o.type_name()),
|
|
182
254
|
}
|
|
@@ -202,7 +274,8 @@ impl Value {
|
|
|
202
274
|
Value::Bool(b) => b.to_string(),
|
|
203
275
|
Value::Null => "null".to_string(),
|
|
204
276
|
Value::Array(arr) => {
|
|
205
|
-
let inner: Vec<String> =
|
|
277
|
+
let inner: Vec<String> =
|
|
278
|
+
arr.borrow().iter().map(|v| v.to_display_string()).collect();
|
|
206
279
|
format!("[{}]", inner.join(", "))
|
|
207
280
|
}
|
|
208
281
|
Value::Object(obj) => {
|
|
@@ -248,35 +321,60 @@ impl Value {
|
|
|
248
321
|
(Value::String(a), Value::String(b)) => a == b,
|
|
249
322
|
(Value::Bool(a), Value::Bool(b)) => a == b,
|
|
250
323
|
(Value::Null, Value::Null) => true,
|
|
251
|
-
(Value::Array(a), Value::Array(b)) =>
|
|
252
|
-
(Value::Object(a), Value::Object(b)) =>
|
|
253
|
-
(
|
|
324
|
+
(Value::Array(a), Value::Array(b)) => VmRef::ptr_eq(a, b),
|
|
325
|
+
(Value::Object(a), Value::Object(b)) => VmRef::ptr_eq(a, b),
|
|
326
|
+
#[cfg(feature = "send-values")]
|
|
327
|
+
(Value::Function(a), Value::Function(b)) => Arc::ptr_eq(a, b),
|
|
328
|
+
#[cfg(not(feature = "send-values"))]
|
|
329
|
+
(Value::Function(a), Value::Function(b)) => std::rc::Rc::ptr_eq(a, b),
|
|
254
330
|
#[cfg(feature = "regex")]
|
|
255
|
-
(Value::RegExp(a), Value::RegExp(b)) =>
|
|
331
|
+
(Value::RegExp(a), Value::RegExp(b)) => VmRef::ptr_eq(a, b),
|
|
256
332
|
(Value::Promise(a), Value::Promise(b)) => Arc::ptr_eq(a, b),
|
|
257
333
|
(Value::Opaque(a), Value::Opaque(b)) => Arc::ptr_eq(a, b),
|
|
258
334
|
_ => false,
|
|
259
335
|
}
|
|
260
336
|
}
|
|
261
337
|
|
|
338
|
+
/// Wrap a Rust closure in a `Value::Function`. Automatically picks
|
|
339
|
+
/// `Rc<dyn Fn>` or `Arc<dyn Fn + Send + Sync>` based on the
|
|
340
|
+
/// `send-values` feature, so callers don't have to `cfg`-gate their
|
|
341
|
+
/// code. The input bound tracks the feature too: when `send-values`
|
|
342
|
+
/// is enabled the closure must be `Send + Sync`, otherwise any `Fn`
|
|
343
|
+
/// is accepted.
|
|
344
|
+
#[cfg(feature = "send-values")]
|
|
345
|
+
pub fn native<F>(f: F) -> Self
|
|
346
|
+
where
|
|
347
|
+
F: Fn(&[Value]) -> Value + Send + Sync + 'static,
|
|
348
|
+
{
|
|
349
|
+
Value::Function(Arc::new(f))
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
#[cfg(not(feature = "send-values"))]
|
|
353
|
+
pub fn native<F>(f: F) -> Self
|
|
354
|
+
where
|
|
355
|
+
F: Fn(&[Value]) -> Value + 'static,
|
|
356
|
+
{
|
|
357
|
+
Value::Function(std::rc::Rc::new(f))
|
|
358
|
+
}
|
|
359
|
+
|
|
262
360
|
/// Create a new array Value from a Vec.
|
|
263
361
|
pub fn array(items: Vec<Value>) -> Self {
|
|
264
|
-
Value::Array(
|
|
362
|
+
Value::Array(VmRef::new(items))
|
|
265
363
|
}
|
|
266
364
|
|
|
267
365
|
/// Create a new object Value from a property map.
|
|
268
366
|
pub fn object(map: ObjectMap) -> Self {
|
|
269
|
-
Value::Object(
|
|
367
|
+
Value::Object(VmRef::new(map))
|
|
270
368
|
}
|
|
271
369
|
|
|
272
370
|
/// Create an empty array Value.
|
|
273
371
|
pub fn empty_array() -> Self {
|
|
274
|
-
Value::Array(
|
|
372
|
+
Value::Array(VmRef::new(Vec::new()))
|
|
275
373
|
}
|
|
276
374
|
|
|
277
375
|
/// Create an empty object Value.
|
|
278
376
|
pub fn empty_object() -> Self {
|
|
279
|
-
Value::Object(
|
|
377
|
+
Value::Object(VmRef::new(ObjectMap::default()))
|
|
280
378
|
}
|
|
281
379
|
|
|
282
380
|
/// Extract the number value, if this is a Number.
|
|
@@ -378,7 +476,11 @@ impl Value {
|
|
|
378
476
|
"trim".into(),
|
|
379
477
|
]
|
|
380
478
|
}
|
|
381
|
-
Value::Number(_) => vec![
|
|
479
|
+
Value::Number(_) => vec![
|
|
480
|
+
"toFixed".into(),
|
|
481
|
+
"toExponential".into(),
|
|
482
|
+
"toPrecision".into(),
|
|
483
|
+
],
|
|
382
484
|
_ => vec![],
|
|
383
485
|
}
|
|
384
486
|
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
//! Shared-mutable reference used by the Tish runtime for `Value::Array`,
|
|
2
|
+
//! `Value::Object`, and `Value::RegExp` payloads.
|
|
3
|
+
//!
|
|
4
|
+
//! ## Why this exists
|
|
5
|
+
//!
|
|
6
|
+
//! Tish's `Value` uses interior mutability for arrays, objects, and regex
|
|
7
|
+
//! state. Historically that was `Rc<RefCell<T>>`, which is fast but
|
|
8
|
+
//! `!Send` — so `Value` couldn't move across threads, which in turn meant
|
|
9
|
+
//! `serve(port, handler)` had to serialise every request through one
|
|
10
|
+
//! VM dispatcher thread.
|
|
11
|
+
//!
|
|
12
|
+
//! `VmRef<T>` lets the build system pick the right trade-off **per
|
|
13
|
+
//! compile target**:
|
|
14
|
+
//!
|
|
15
|
+
//! | feature `send-values` | `VmRef<T>` | `NativeFn` | targets |
|
|
16
|
+
//! |---------------------------|-------------------------|----------------------------------|--------------------------------------------------|
|
|
17
|
+
//! | **off** *(default)* | `Rc<RefCell<T>>` | `Rc<dyn Fn + 'static>` | wasm32, wasi, interpreter, cranelift/llvm VMs |
|
|
18
|
+
//! | **on** | `Arc<Mutex<T>>` | `Arc<dyn Fn + Send + Sync>` | Rust native with `http` enabled (server workloads) |
|
|
19
|
+
//!
|
|
20
|
+
//! The *API* is identical in both configurations (`borrow` / `borrow_mut`
|
|
21
|
+
//! / `ptr_eq` / `Clone`), so every existing call site in the workspace
|
|
22
|
+
//! compiles unchanged. What flips is only the underlying primitive.
|
|
23
|
+
//!
|
|
24
|
+
//! ## Why this matters for performance
|
|
25
|
+
//!
|
|
26
|
+
//! * **wasm / wasi / cranelift / llvm / interpreter**: still pure
|
|
27
|
+
//! `Rc<RefCell<T>>`. Zero atomic ops, no mutex churn, behaviour
|
|
28
|
+
//! bit-identical to the pre-migration baseline.
|
|
29
|
+
//! * **Rust native, non-server**: same — `send-values` only activates
|
|
30
|
+
//! when something in the dependency graph (usually `http`) needs it.
|
|
31
|
+
//! * **Rust native with server**: `Arc<Mutex<T>>` pays ~3–5 ns per
|
|
32
|
+
//! `borrow` in the uncontended case (single atomic CAS). On Tish's
|
|
33
|
+
//! hot paths — roughly 6–12 borrows per request — that's ~30–60 ns of
|
|
34
|
+
//! overhead. In exchange we get `N×` handler scaling across cores,
|
|
35
|
+
//! which recovers orders of magnitude more throughput than it costs.
|
|
36
|
+
//!
|
|
37
|
+
//! ## API surface
|
|
38
|
+
//!
|
|
39
|
+
//! ```ignore
|
|
40
|
+
//! let cell = VmRef::new(42);
|
|
41
|
+
//! *cell.borrow() + 1; // read
|
|
42
|
+
//! *cell.borrow_mut() = 99; // write
|
|
43
|
+
//! VmRef::ptr_eq(&a, &b); // identity
|
|
44
|
+
//! let clone = cell.clone(); // shared ownership
|
|
45
|
+
//! ```
|
|
46
|
+
//!
|
|
47
|
+
//! Returned guard types (`VmReadGuard<'_, T>`, `VmWriteGuard<'_, T>`) are
|
|
48
|
+
//! type aliases that pick `Ref`/`RefMut` or `MutexGuard` depending on the
|
|
49
|
+
//! feature. They both `Deref` (and, for write guards, `DerefMut`) to `T`
|
|
50
|
+
//! just like the underlying types.
|
|
51
|
+
|
|
52
|
+
use std::fmt;
|
|
53
|
+
|
|
54
|
+
// --------------------------------------------------------------------------
|
|
55
|
+
// Single-threaded backing store (default): Rc<RefCell<T>>
|
|
56
|
+
// --------------------------------------------------------------------------
|
|
57
|
+
#[cfg(not(feature = "send-values"))]
|
|
58
|
+
mod imp {
|
|
59
|
+
use std::cell::RefCell;
|
|
60
|
+
use std::rc::Rc;
|
|
61
|
+
|
|
62
|
+
#[derive(Default)]
|
|
63
|
+
pub struct VmRef<T: ?Sized>(pub(super) Rc<RefCell<T>>);
|
|
64
|
+
|
|
65
|
+
/// Read guard alias. On the single-threaded path this is a true
|
|
66
|
+
/// `Ref<'_, T>`, so multiple readers can coexist.
|
|
67
|
+
pub type ReadGuard<'a, T> = std::cell::Ref<'a, T>;
|
|
68
|
+
/// Write guard alias. Exclusive, `DerefMut`.
|
|
69
|
+
pub type WriteGuard<'a, T> = std::cell::RefMut<'a, T>;
|
|
70
|
+
|
|
71
|
+
impl<T> VmRef<T> {
|
|
72
|
+
#[inline]
|
|
73
|
+
pub fn new(value: T) -> Self {
|
|
74
|
+
VmRef(Rc::new(RefCell::new(value)))
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
impl<T: ?Sized> VmRef<T> {
|
|
79
|
+
#[inline]
|
|
80
|
+
pub fn borrow(&self) -> ReadGuard<'_, T> {
|
|
81
|
+
self.0.borrow()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#[inline]
|
|
85
|
+
pub fn borrow_mut(&self) -> WriteGuard<'_, T> {
|
|
86
|
+
self.0.borrow_mut()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[inline]
|
|
90
|
+
pub fn ptr_eq(a: &Self, b: &Self) -> bool {
|
|
91
|
+
Rc::ptr_eq(&a.0, &b.0)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#[inline]
|
|
95
|
+
pub fn strong_count(this: &Self) -> usize {
|
|
96
|
+
Rc::strong_count(&this.0)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
impl<T: ?Sized> Clone for VmRef<T> {
|
|
101
|
+
#[inline]
|
|
102
|
+
fn clone(&self) -> Self {
|
|
103
|
+
VmRef(Rc::clone(&self.0))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// --------------------------------------------------------------------------
|
|
109
|
+
// Thread-safe backing store (opt-in): Arc<Mutex<T>>
|
|
110
|
+
// --------------------------------------------------------------------------
|
|
111
|
+
#[cfg(feature = "send-values")]
|
|
112
|
+
mod imp {
|
|
113
|
+
use std::sync::{Arc, Mutex};
|
|
114
|
+
|
|
115
|
+
#[derive(Default)]
|
|
116
|
+
pub struct VmRef<T: ?Sized>(pub(super) Arc<Mutex<T>>);
|
|
117
|
+
|
|
118
|
+
/// Read guard alias. On the multi-threaded path both readers and
|
|
119
|
+
/// writers share a single `MutexGuard` (exclusive access).
|
|
120
|
+
pub type ReadGuard<'a, T> = std::sync::MutexGuard<'a, T>;
|
|
121
|
+
/// Write guard alias.
|
|
122
|
+
pub type WriteGuard<'a, T> = std::sync::MutexGuard<'a, T>;
|
|
123
|
+
|
|
124
|
+
impl<T> VmRef<T> {
|
|
125
|
+
#[inline]
|
|
126
|
+
pub fn new(value: T) -> Self {
|
|
127
|
+
VmRef(Arc::new(Mutex::new(value)))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
impl<T: ?Sized> VmRef<T> {
|
|
132
|
+
/// Acquire the inner mutex. Poisoning is swallowed — a Tish
|
|
133
|
+
/// handler panic already aborts the enclosing thread; there is
|
|
134
|
+
/// no invariant worth preserving past that point.
|
|
135
|
+
#[inline]
|
|
136
|
+
pub fn borrow(&self) -> ReadGuard<'_, T> {
|
|
137
|
+
self.0.lock().unwrap_or_else(|p| p.into_inner())
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[inline]
|
|
141
|
+
pub fn borrow_mut(&self) -> WriteGuard<'_, T> {
|
|
142
|
+
self.0.lock().unwrap_or_else(|p| p.into_inner())
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#[inline]
|
|
146
|
+
pub fn ptr_eq(a: &Self, b: &Self) -> bool {
|
|
147
|
+
Arc::ptr_eq(&a.0, &b.0)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#[inline]
|
|
151
|
+
pub fn strong_count(this: &Self) -> usize {
|
|
152
|
+
Arc::strong_count(&this.0)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
impl<T: ?Sized> Clone for VmRef<T> {
|
|
157
|
+
#[inline]
|
|
158
|
+
fn clone(&self) -> Self {
|
|
159
|
+
VmRef(Arc::clone(&self.0))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
pub use imp::{ReadGuard as VmReadGuard, VmRef, WriteGuard as VmWriteGuard};
|
|
165
|
+
|
|
166
|
+
impl<T: fmt::Debug> fmt::Debug for VmRef<T> {
|
|
167
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
168
|
+
// Match `RefCell`'s debug format so snapshot-test output stays
|
|
169
|
+
// stable across the migration.
|
|
170
|
+
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
171
|
+
let guard = self.borrow();
|
|
172
|
+
format!("{:?}", &*guard)
|
|
173
|
+
})) {
|
|
174
|
+
Ok(s) => write!(f, "RefCell {{ value: {} }}", s),
|
|
175
|
+
Err(_) => write!(f, "RefCell {{ value: <borrowed> }}"),
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -12,9 +12,8 @@ pub fn link_to_binary(
|
|
|
12
12
|
output_path: &Path,
|
|
13
13
|
features: &[String],
|
|
14
14
|
) -> Result<(), CraneliftError> {
|
|
15
|
-
let workspace_root =
|
|
16
|
-
message: e
|
|
17
|
-
})?;
|
|
15
|
+
let workspace_root =
|
|
16
|
+
tishlang_build_utils::find_workspace_root().map_err(|e| CraneliftError { message: e })?;
|
|
18
17
|
let out_name = output_path
|
|
19
18
|
.file_stem()
|
|
20
19
|
.and_then(|s| s.to_str())
|
|
@@ -104,17 +103,15 @@ fn main() {{
|
|
|
104
103
|
message: format!("Cannot write build.rs: {}", e),
|
|
105
104
|
})?;
|
|
106
105
|
|
|
107
|
-
tishlang_build_utils::run_cargo_build(&build_dir, None)
|
|
106
|
+
tishlang_build_utils::run_cargo_build(&build_dir, None)
|
|
107
|
+
.map_err(|e| CraneliftError { message: e })?;
|
|
108
108
|
|
|
109
109
|
let binary_dir = build_dir.join("target").join("release");
|
|
110
|
-
let binary =
|
|
111
|
-
|
|
112
|
-
.map_err(|e| CraneliftError { message: e })?;
|
|
110
|
+
let binary = tishlang_build_utils::find_release_binary(&binary_dir, out_name)
|
|
111
|
+
.map_err(|e| CraneliftError { message: e })?;
|
|
113
112
|
let target = tishlang_build_utils::resolve_output_path(output_path, out_name);
|
|
114
113
|
tishlang_build_utils::copy_binary_to_output(&binary, &target)
|
|
115
114
|
.map_err(|e| CraneliftError { message: e })?;
|
|
116
115
|
|
|
117
116
|
Ok(())
|
|
118
117
|
}
|
|
119
|
-
|
|
120
|
-
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
use std::path::Path;
|
|
9
9
|
|
|
10
|
-
use cranelift::codegen::settings::Configurable;
|
|
11
10
|
use cranelift::codegen::settings;
|
|
11
|
+
use cranelift::codegen::settings::Configurable;
|
|
12
12
|
use cranelift_module::{DataDescription, Linkage, Module};
|
|
13
13
|
use cranelift_object::{ObjectBuilder, ObjectModule};
|
|
14
14
|
|
|
@@ -18,9 +18,11 @@ use crate::CraneliftError;
|
|
|
18
18
|
|
|
19
19
|
pub fn lower_and_emit(chunk: &Chunk, object_path: &Path) -> Result<(), CraneliftError> {
|
|
20
20
|
let mut settings_builder = settings::builder();
|
|
21
|
-
settings_builder
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
settings_builder
|
|
22
|
+
.set("opt_level", "speed")
|
|
23
|
+
.map_err(|_| CraneliftError {
|
|
24
|
+
message: "Failed to set opt_level".to_string(),
|
|
25
|
+
})?;
|
|
24
26
|
let flags = settings::Flags::new(settings_builder);
|
|
25
27
|
|
|
26
28
|
let isa_builder = cranelift_native::builder().map_err(|e| CraneliftError {
|
|
@@ -30,10 +32,14 @@ pub fn lower_and_emit(chunk: &Chunk, object_path: &Path) -> Result<(), Cranelift
|
|
|
30
32
|
message: format!("Failed to finish ISA: {}", e),
|
|
31
33
|
})?;
|
|
32
34
|
|
|
33
|
-
let object_builder = ObjectBuilder::new(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
let object_builder = ObjectBuilder::new(
|
|
36
|
+
isa,
|
|
37
|
+
"tishlang_cranelift",
|
|
38
|
+
cranelift_module::default_libcall_names(),
|
|
39
|
+
)
|
|
40
|
+
.map_err(|e| CraneliftError {
|
|
41
|
+
message: format!("Failed to create ObjectBuilder: {}", e),
|
|
42
|
+
})?;
|
|
37
43
|
let mut module = ObjectModule::new(object_builder);
|
|
38
44
|
|
|
39
45
|
// Serialize chunk and emit as data - link step will build a Rust binary that reads it
|
|
@@ -8,7 +8,22 @@ license-file = { workspace = true }
|
|
|
8
8
|
repository = { workspace = true }
|
|
9
9
|
[features]
|
|
10
10
|
default = []
|
|
11
|
-
|
|
11
|
+
# setTimeout / setInterval / clear* (standalone or with `import { … } from "tish:timers"`).
|
|
12
|
+
timers = []
|
|
13
|
+
http = [
|
|
14
|
+
"timers",
|
|
15
|
+
"tokio",
|
|
16
|
+
"reqwest",
|
|
17
|
+
"futures",
|
|
18
|
+
"tiny_http",
|
|
19
|
+
"tishlang_core/regex",
|
|
20
|
+
"dep:tishlang_runtime",
|
|
21
|
+
"tishlang_runtime/http",
|
|
22
|
+
# Interpreter + http means the runtime's `NativeFn` is `Arc<... + Send>`,
|
|
23
|
+
# so the interpreter's `CoreFn` variant must use the same shape.
|
|
24
|
+
"tishlang_core/send-values",
|
|
25
|
+
"tishlang_builtins/send-values",
|
|
26
|
+
]
|
|
12
27
|
fs = []
|
|
13
28
|
process = []
|
|
14
29
|
regex = ["dep:fancy-regex", "tishlang_core/regex"]
|
|
@@ -17,7 +32,7 @@ ws = ["dep:tishlang_runtime", "tishlang_runtime/ws"]
|
|
|
17
32
|
|
|
18
33
|
[dependencies]
|
|
19
34
|
ahash = "0.8.12"
|
|
20
|
-
rand = "0.10.
|
|
35
|
+
rand = "0.10.1"
|
|
21
36
|
tishlang_ast = { path = "../tish_ast", version = ">=0.1" }
|
|
22
37
|
tishlang_builtins = { path = "../tish_builtins", version = ">=0.1" }
|
|
23
38
|
tishlang_parser = { path = "../tish_parser", version = ">=0.1" }
|