@tishlang/tish 1.7.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 +1 -0
- package/README.md +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +28 -8
- package/crates/js_to_tish/src/transform/stmt.rs +49 -22
- package/crates/tish/Cargo.toml +15 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +16 -10
- package/crates/tish/src/main.rs +87 -32
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +1 -1
- package/crates/tish/tests/integration_test.rs +19 -7
- package/crates/tish/tests/shortcircuit.rs +1 -1
- package/crates/tish_ast/src/ast.rs +80 -9
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +105 -2
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +13 -12
- package/crates/tish_builtins/src/construct.rs +34 -33
- package/crates/tish_builtins/src/globals.rs +12 -11
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +73 -3
- package/crates/tish_bytecode/src/compiler.rs +12 -14
- package/crates/tish_bytecode/src/opcode.rs +12 -3
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +745 -199
- package/crates/tish_compile/src/infer.rs +6 -0
- package/crates/tish_compile/src/lib.rs +4 -3
- package/crates/tish_compile/src/resolve.rs +180 -82
- package/crates/tish_compile/src/types.rs +175 -11
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +152 -29
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +31 -12
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +102 -53
- package/crates/tish_core/src/lib.rs +3 -1
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/value.rs +53 -15
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +90 -28
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +3 -3
- package/crates/tish_eval/src/natives.rs +41 -0
- package/crates/tish_eval/src/value.rs +7 -3
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +120 -30
- package/crates/tish_lexer/src/lib.rs +20 -5
- package/crates/tish_lexer/src/token.rs +4 -0
- package/crates/tish_llvm/src/lib.rs +3 -1
- 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 +502 -102
- package/crates/tish_native/src/build.rs +3 -2
- package/crates/tish_native/src/lib.rs +6 -2
- package/crates/tish_opt/src/lib.rs +17 -2
- package/crates/tish_parser/src/lib.rs +10 -3
- package/crates/tish_parser/src/parser.rs +346 -56
- 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 +1123 -141
- package/crates/tish_runtime/src/http_fetch.rs +15 -14
- 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 +159 -29
- package/crates/tish_runtime/src/promise.rs +199 -36
- 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 +26 -28
- package/crates/tish_ui/src/jsx.rs +279 -8
- 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 +506 -259
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +3 -1
- package/crates/tish_wasm/src/lib.rs +17 -14
- 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 +1 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +68 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +5 -4
- 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
|
@@ -17,6 +17,7 @@ pub struct VirtualModule {
|
|
|
17
17
|
const BUILTIN_ALIASES: &[(&str, &str)] = &[
|
|
18
18
|
("fs", "tish:fs"),
|
|
19
19
|
("http", "tish:http"),
|
|
20
|
+
("timers", "tish:timers"),
|
|
20
21
|
("process", "tish:process"),
|
|
21
22
|
("ws", "tish:ws"),
|
|
22
23
|
];
|
|
@@ -35,7 +36,7 @@ fn is_native_import(spec: &str) -> bool {
|
|
|
35
36
|
spec.starts_with("tish:")
|
|
36
37
|
|| spec.starts_with("cargo:")
|
|
37
38
|
|| spec.starts_with('@')
|
|
38
|
-
|| matches!(spec, "fs" | "http" | "process" | "ws")
|
|
39
|
+
|| matches!(spec, "fs" | "http" | "timers" | "process" | "ws")
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
/// Normalize a virtual path: resolve . and .. components.
|
|
@@ -311,8 +312,14 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
|
|
|
311
312
|
normalize_builtin_spec(from).unwrap_or_else(|| from.to_string());
|
|
312
313
|
for spec in specifiers {
|
|
313
314
|
match spec {
|
|
314
|
-
ImportSpecifier::Named {
|
|
315
|
+
ImportSpecifier::Named {
|
|
316
|
+
name,
|
|
317
|
+
name_span,
|
|
318
|
+
alias,
|
|
319
|
+
alias_span,
|
|
320
|
+
} => {
|
|
315
321
|
let bind = alias.as_deref().unwrap_or(name.as_ref());
|
|
322
|
+
let decl_name_span = alias_span.as_ref().unwrap_or(name_span);
|
|
316
323
|
let init = Expr::NativeModuleLoad {
|
|
317
324
|
spec: Arc::from(canonical_spec.clone()),
|
|
318
325
|
export_name: name.clone(),
|
|
@@ -320,23 +327,24 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
|
|
|
320
327
|
};
|
|
321
328
|
statements.push(Statement::VarDecl {
|
|
322
329
|
name: Arc::from(bind),
|
|
330
|
+
name_span: *decl_name_span,
|
|
323
331
|
mutable: false,
|
|
324
332
|
type_ann: None,
|
|
325
333
|
init: Some(init),
|
|
326
334
|
span: *span,
|
|
327
335
|
});
|
|
328
336
|
}
|
|
329
|
-
ImportSpecifier::Namespace
|
|
337
|
+
ImportSpecifier::Namespace { name, .. } => {
|
|
330
338
|
return Err(format!(
|
|
331
339
|
"Namespace import (* as {}) not supported for native module '{}'",
|
|
332
|
-
|
|
340
|
+
name.as_ref(),
|
|
333
341
|
from.as_ref()
|
|
334
342
|
));
|
|
335
343
|
}
|
|
336
|
-
ImportSpecifier::Default
|
|
344
|
+
ImportSpecifier::Default { name, .. } => {
|
|
337
345
|
return Err(format!(
|
|
338
346
|
"Default import '{}' not supported for native module '{}'. Use named import.",
|
|
339
|
-
|
|
347
|
+
name.as_ref(),
|
|
340
348
|
from.as_ref()
|
|
341
349
|
));
|
|
342
350
|
}
|
|
@@ -351,15 +359,22 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
|
|
|
351
359
|
let dep_exports = &module_exports[dep_idx];
|
|
352
360
|
for spec in specifiers {
|
|
353
361
|
match spec {
|
|
354
|
-
ImportSpecifier::Named {
|
|
362
|
+
ImportSpecifier::Named {
|
|
363
|
+
name,
|
|
364
|
+
name_span,
|
|
365
|
+
alias,
|
|
366
|
+
alias_span,
|
|
367
|
+
} => {
|
|
355
368
|
let source = dep_exports
|
|
356
369
|
.get(name.as_ref())
|
|
357
370
|
.cloned()
|
|
358
371
|
.unwrap_or_else(|| name.to_string());
|
|
359
372
|
let bind = alias.as_deref().unwrap_or(name.as_ref());
|
|
360
373
|
if bind != source {
|
|
374
|
+
let decl_name_span = alias_span.as_ref().unwrap_or(name_span);
|
|
361
375
|
statements.push(Statement::VarDecl {
|
|
362
376
|
name: Arc::from(bind),
|
|
377
|
+
name_span: *decl_name_span,
|
|
363
378
|
mutable: false,
|
|
364
379
|
type_ann: None,
|
|
365
380
|
init: Some(Expr::Ident {
|
|
@@ -370,7 +385,7 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
|
|
|
370
385
|
});
|
|
371
386
|
}
|
|
372
387
|
}
|
|
373
|
-
ImportSpecifier::Namespace
|
|
388
|
+
ImportSpecifier::Namespace { name, name_span } => {
|
|
374
389
|
let mut props = Vec::new();
|
|
375
390
|
for (k, v) in dep_exports {
|
|
376
391
|
props.push(tishlang_ast::ObjectProp::KeyValue(
|
|
@@ -382,20 +397,22 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
|
|
|
382
397
|
));
|
|
383
398
|
}
|
|
384
399
|
statements.push(Statement::VarDecl {
|
|
385
|
-
name:
|
|
400
|
+
name: name.clone(),
|
|
401
|
+
name_span: *name_span,
|
|
386
402
|
mutable: false,
|
|
387
403
|
type_ann: None,
|
|
388
404
|
init: Some(Expr::Object { props, span: *span }),
|
|
389
405
|
span: *span,
|
|
390
406
|
});
|
|
391
407
|
}
|
|
392
|
-
ImportSpecifier::Default
|
|
408
|
+
ImportSpecifier::Default { name, name_span } => {
|
|
393
409
|
let source =
|
|
394
410
|
dep_exports.get("default").cloned().ok_or_else(|| {
|
|
395
411
|
format!("Module '{}' has no default export", from)
|
|
396
412
|
})?;
|
|
397
413
|
statements.push(Statement::VarDecl {
|
|
398
|
-
name:
|
|
414
|
+
name: name.clone(),
|
|
415
|
+
name_span: *name_span,
|
|
399
416
|
mutable: false,
|
|
400
417
|
type_ann: None,
|
|
401
418
|
init: Some(Expr::Ident {
|
|
@@ -412,12 +429,14 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
|
|
|
412
429
|
ExportDeclaration::Named(s) => statements.push(*s.clone()),
|
|
413
430
|
ExportDeclaration::Default(e) => {
|
|
414
431
|
let default_name = format!("__default_{}", idx);
|
|
432
|
+
let espan = e.span();
|
|
415
433
|
statements.push(Statement::VarDecl {
|
|
416
434
|
name: Arc::from(default_name),
|
|
435
|
+
name_span: espan,
|
|
417
436
|
mutable: false,
|
|
418
437
|
type_ann: None,
|
|
419
438
|
init: Some((*e).clone()),
|
|
420
|
-
span:
|
|
439
|
+
span: espan,
|
|
421
440
|
});
|
|
422
441
|
}
|
|
423
442
|
},
|
|
@@ -9,6 +9,14 @@ repository = { workspace = true }
|
|
|
9
9
|
[features]
|
|
10
10
|
default = []
|
|
11
11
|
regex = ["dep:fancy-regex"]
|
|
12
|
+
# Make `Value` (and its array / object / regex payloads) `Send + Sync` by
|
|
13
|
+
# switching the interior `Rc<RefCell<T>>` to `Arc<Mutex<T>>` and the native
|
|
14
|
+
# function type from `Rc<dyn Fn>` to `Arc<dyn Fn + Send + Sync>`. Enabled
|
|
15
|
+
# transitively by any crate that needs to pass `Value`s across threads —
|
|
16
|
+
# most notably `tishlang_runtime/http`, which dispatches HTTP handlers
|
|
17
|
+
# across a worker pool. Off by default so wasm / wasi / cranelift / llvm /
|
|
18
|
+
# interpreter builds pay no atomic-ref-count or mutex overhead.
|
|
19
|
+
send-values = []
|
|
12
20
|
|
|
13
21
|
[dependencies]
|
|
14
22
|
ahash = "0.8.11"
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
//! JSON parsing and stringification for Tish values.
|
|
2
2
|
|
|
3
|
-
use crate::Value;
|
|
4
|
-
use std::cell::RefCell;
|
|
5
|
-
use std::rc::Rc;
|
|
3
|
+
use crate::{Value, VmRef};
|
|
6
4
|
use std::sync::Arc;
|
|
7
5
|
|
|
8
6
|
/// Parse JSON string into a Value.
|
|
@@ -19,75 +17,126 @@ pub fn json_parse(json: &str) -> Result<Value, String> {
|
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
/// Stringify a Value to JSON.
|
|
20
|
+
///
|
|
21
|
+
/// Single-buffer write strategy: all nested values append into one
|
|
22
|
+
/// `String` via [`json_stringify_into`], so we never allocate a transient
|
|
23
|
+
/// per-node `String` only to copy + drop it on the way back up. For a
|
|
24
|
+
/// 20-row TFB `/queries` response (~40 numbers, 2 keys × 20 = ~80 string
|
|
25
|
+
/// ops) that saves dozens of small allocations per request.
|
|
22
26
|
pub fn json_stringify(value: &Value) -> String {
|
|
27
|
+
// 256 B is enough for typical TFB responses (`/db` is 31 B,
|
|
28
|
+
// `/queries=20` is ~700 B). Larger payloads reallocate normally.
|
|
29
|
+
let mut buf = String::with_capacity(256);
|
|
30
|
+
json_stringify_into(&mut buf, value);
|
|
31
|
+
buf
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Append a JSON-stringified `value` to `buf`. Used by JSON.stringify for
|
|
35
|
+
/// the recursive case so we don't pay for an intermediate `String` per
|
|
36
|
+
/// node.
|
|
37
|
+
pub fn json_stringify_into(buf: &mut String, value: &Value) {
|
|
23
38
|
match value {
|
|
24
|
-
Value::Null => "null"
|
|
25
|
-
Value::Bool(
|
|
39
|
+
Value::Null => buf.push_str("null"),
|
|
40
|
+
Value::Bool(true) => buf.push_str("true"),
|
|
41
|
+
Value::Bool(false) => buf.push_str("false"),
|
|
26
42
|
Value::Number(n) => {
|
|
27
43
|
if n.is_nan() || n.is_infinite() {
|
|
28
|
-
"null"
|
|
44
|
+
buf.push_str("null");
|
|
29
45
|
} else {
|
|
30
|
-
|
|
46
|
+
// `write!` avoids the heap allocation that `to_string`
|
|
47
|
+
// produces. The f64 → decimal formatter is the same
|
|
48
|
+
// either way (`std::fmt::Display`).
|
|
49
|
+
use std::fmt::Write;
|
|
50
|
+
let _ = write!(buf, "{}", n);
|
|
31
51
|
}
|
|
32
52
|
}
|
|
33
|
-
Value::String(s) =>
|
|
53
|
+
Value::String(s) => {
|
|
54
|
+
buf.push('"');
|
|
55
|
+
escape_json_string_into(buf, s);
|
|
56
|
+
buf.push('"');
|
|
57
|
+
}
|
|
34
58
|
Value::Array(arr) => {
|
|
35
59
|
let borrowed = arr.borrow();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if !first {
|
|
41
|
-
result.push(',');
|
|
60
|
+
buf.push('[');
|
|
61
|
+
for (i, item) in borrowed.iter().enumerate() {
|
|
62
|
+
if i > 0 {
|
|
63
|
+
buf.push(',');
|
|
42
64
|
}
|
|
43
|
-
|
|
44
|
-
result.push_str(&json_stringify(item));
|
|
65
|
+
json_stringify_into(buf, item);
|
|
45
66
|
}
|
|
46
|
-
|
|
47
|
-
result
|
|
67
|
+
buf.push(']');
|
|
48
68
|
}
|
|
49
69
|
Value::Object(obj) => {
|
|
50
70
|
let borrowed = obj.borrow();
|
|
51
|
-
|
|
52
|
-
keys.
|
|
53
|
-
let mut
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
// Sort keys for deterministic output. Pre-allocate to avoid
|
|
72
|
+
// a fresh `Vec` realloc inside `keys().collect()`.
|
|
73
|
+
let mut keys: Vec<&Arc<str>> = Vec::with_capacity(borrowed.len());
|
|
74
|
+
keys.extend(borrowed.keys());
|
|
75
|
+
keys.sort_unstable_by(|a, b| a.as_ref().cmp(b.as_ref()));
|
|
76
|
+
buf.push('{');
|
|
77
|
+
for (i, key) in keys.into_iter().enumerate() {
|
|
78
|
+
if i > 0 {
|
|
79
|
+
buf.push(',');
|
|
59
80
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
result.push_str(&json_stringify(borrowed.get(key).unwrap()));
|
|
81
|
+
buf.push('"');
|
|
82
|
+
escape_json_string_into(buf, key);
|
|
83
|
+
buf.push_str("\":");
|
|
84
|
+
json_stringify_into(buf, borrowed.get(key).unwrap());
|
|
65
85
|
}
|
|
66
|
-
|
|
67
|
-
result
|
|
86
|
+
buf.push('}');
|
|
68
87
|
}
|
|
69
|
-
Value::Function(_) => "null"
|
|
70
|
-
Value::Promise(_) => "null".to_string(),
|
|
71
|
-
Value::Opaque(_) => "null".to_string(),
|
|
88
|
+
Value::Function(_) | Value::Promise(_) | Value::Opaque(_) => buf.push_str("null"),
|
|
72
89
|
#[cfg(feature = "regex")]
|
|
73
|
-
Value::RegExp(_) => "null"
|
|
90
|
+
Value::RegExp(_) => buf.push_str("null"),
|
|
74
91
|
}
|
|
75
92
|
}
|
|
76
93
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
/// Append an escaped JSON string body (without the surrounding quotes)
|
|
95
|
+
/// to `buf`. Optimised for the common case where the input is ASCII and
|
|
96
|
+
/// contains no characters that need escaping — we fast-pass the bytes
|
|
97
|
+
/// straight through, only falling into the per-char path on a hit.
|
|
98
|
+
fn escape_json_string_into(buf: &mut String, s: &str) {
|
|
99
|
+
let bytes = s.as_bytes();
|
|
100
|
+
let mut start = 0usize;
|
|
101
|
+
for (i, &b) in bytes.iter().enumerate() {
|
|
102
|
+
// Anything < 0x20 is a JSON control char that must be escaped;
|
|
103
|
+
// 0x22 (`"`) and 0x5C (`\`) also need an explicit escape; bytes
|
|
104
|
+
// ≥ 0x80 are the start of a multi-byte UTF-8 sequence, which is
|
|
105
|
+
// valid JSON as-is.
|
|
106
|
+
if b < 0x20 || b == b'"' || b == b'\\' {
|
|
107
|
+
// Flush the run of clean bytes before this one in one push.
|
|
108
|
+
if start < i {
|
|
109
|
+
// SAFETY: `s` is `&str`, every byte in `start..i` was a
|
|
110
|
+
// single-byte ASCII char (we only stop on ASCII triggers
|
|
111
|
+
// below 0x80), so the slice is a valid `&str`.
|
|
112
|
+
buf.push_str(&s[start..i]);
|
|
113
|
+
}
|
|
114
|
+
match b {
|
|
115
|
+
b'"' => buf.push_str("\\\""),
|
|
116
|
+
b'\\' => buf.push_str("\\\\"),
|
|
117
|
+
b'\n' => buf.push_str("\\n"),
|
|
118
|
+
b'\r' => buf.push_str("\\r"),
|
|
119
|
+
b'\t' => buf.push_str("\\t"),
|
|
120
|
+
b'\x08' => buf.push_str("\\b"),
|
|
121
|
+
b'\x0c' => buf.push_str("\\f"),
|
|
122
|
+
_ => {
|
|
123
|
+
use std::fmt::Write;
|
|
124
|
+
let _ = write!(buf, "\\u{:04x}", b as u32);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
start = i + 1;
|
|
88
128
|
}
|
|
89
129
|
}
|
|
90
|
-
|
|
130
|
+
if start < bytes.len() {
|
|
131
|
+
buf.push_str(&s[start..]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[allow(dead_code)]
|
|
136
|
+
fn escape_json_string(s: &str) -> String {
|
|
137
|
+
let mut buf = String::with_capacity(s.len());
|
|
138
|
+
escape_json_string_into(&mut buf, s);
|
|
139
|
+
buf
|
|
91
140
|
}
|
|
92
141
|
|
|
93
142
|
fn parse_value(input: &str) -> Result<(Value, &str), String> {
|
|
@@ -241,7 +290,7 @@ fn parse_array(input: &str) -> Result<(Value, &str), String> {
|
|
|
241
290
|
|
|
242
291
|
input = input.trim_start();
|
|
243
292
|
if let Some(rest) = input.strip_prefix(']') {
|
|
244
|
-
return Ok((Value::Array(
|
|
293
|
+
return Ok((Value::Array(VmRef::new(items)), rest));
|
|
245
294
|
}
|
|
246
295
|
|
|
247
296
|
loop {
|
|
@@ -251,7 +300,7 @@ fn parse_array(input: &str) -> Result<(Value, &str), String> {
|
|
|
251
300
|
|
|
252
301
|
match input.chars().next() {
|
|
253
302
|
Some(',') => input = &input[1..],
|
|
254
|
-
Some(']') => return Ok((Value::Array(
|
|
303
|
+
Some(']') => return Ok((Value::Array(VmRef::new(items)), &input[1..])),
|
|
255
304
|
_ => return Err("Expected ',' or ']' in array".to_string()),
|
|
256
305
|
}
|
|
257
306
|
}
|
|
@@ -263,7 +312,7 @@ fn parse_object(input: &str) -> Result<(Value, &str), String> {
|
|
|
263
312
|
|
|
264
313
|
input = input.trim_start();
|
|
265
314
|
if let Some(rest) = input.strip_prefix('}') {
|
|
266
|
-
return Ok((Value::Object(
|
|
315
|
+
return Ok((Value::Object(VmRef::new(map)), rest));
|
|
267
316
|
}
|
|
268
317
|
|
|
269
318
|
loop {
|
|
@@ -290,7 +339,7 @@ fn parse_object(input: &str) -> Result<(Value, &str), String> {
|
|
|
290
339
|
|
|
291
340
|
match input.chars().next() {
|
|
292
341
|
Some(',') => input = &input[1..],
|
|
293
|
-
Some('}') => return Ok((Value::Object(
|
|
342
|
+
Some('}') => return Ok((Value::Object(VmRef::new(map)), &input[1..])),
|
|
294
343
|
_ => return Err("Expected ',' or '}' in object".to_string()),
|
|
295
344
|
}
|
|
296
345
|
}
|
|
@@ -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 json::{json_parse, json_stringify};
|
|
14
|
+
pub use json::{json_parse, json_stringify, json_stringify_into};
|
|
14
15
|
pub use uri::{percent_decode, percent_encode};
|
|
15
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
|
}
|
|
@@ -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.
|
|
@@ -202,17 +211,21 @@ impl TishRegExp {
|
|
|
202
211
|
|
|
203
212
|
/// Runtime value for Tish programs.
|
|
204
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>`.
|
|
205
218
|
#[derive(Clone)]
|
|
206
219
|
pub enum Value {
|
|
207
220
|
Number(f64),
|
|
208
221
|
String(Arc<str>),
|
|
209
222
|
Bool(bool),
|
|
210
223
|
Null,
|
|
211
|
-
Array(
|
|
212
|
-
Object(
|
|
224
|
+
Array(VmRef<Vec<Value>>),
|
|
225
|
+
Object(VmRef<ObjectMap>),
|
|
213
226
|
Function(NativeFn),
|
|
214
227
|
#[cfg(feature = "regex")]
|
|
215
|
-
RegExp(
|
|
228
|
+
RegExp(VmRef<TishRegExp>),
|
|
216
229
|
/// Promise (for native compile). Interpreter uses tishlang_eval::Value::Promise.
|
|
217
230
|
Promise(Arc<dyn TishPromise>),
|
|
218
231
|
/// Opaque handle to a native Rust type (e.g. Polars DataFrame).
|
|
@@ -308,35 +321,60 @@ impl Value {
|
|
|
308
321
|
(Value::String(a), Value::String(b)) => a == b,
|
|
309
322
|
(Value::Bool(a), Value::Bool(b)) => a == b,
|
|
310
323
|
(Value::Null, Value::Null) => true,
|
|
311
|
-
(Value::Array(a), Value::Array(b)) =>
|
|
312
|
-
(Value::Object(a), Value::Object(b)) =>
|
|
313
|
-
(
|
|
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),
|
|
314
330
|
#[cfg(feature = "regex")]
|
|
315
|
-
(Value::RegExp(a), Value::RegExp(b)) =>
|
|
331
|
+
(Value::RegExp(a), Value::RegExp(b)) => VmRef::ptr_eq(a, b),
|
|
316
332
|
(Value::Promise(a), Value::Promise(b)) => Arc::ptr_eq(a, b),
|
|
317
333
|
(Value::Opaque(a), Value::Opaque(b)) => Arc::ptr_eq(a, b),
|
|
318
334
|
_ => false,
|
|
319
335
|
}
|
|
320
336
|
}
|
|
321
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
|
+
|
|
322
360
|
/// Create a new array Value from a Vec.
|
|
323
361
|
pub fn array(items: Vec<Value>) -> Self {
|
|
324
|
-
Value::Array(
|
|
362
|
+
Value::Array(VmRef::new(items))
|
|
325
363
|
}
|
|
326
364
|
|
|
327
365
|
/// Create a new object Value from a property map.
|
|
328
366
|
pub fn object(map: ObjectMap) -> Self {
|
|
329
|
-
Value::Object(
|
|
367
|
+
Value::Object(VmRef::new(map))
|
|
330
368
|
}
|
|
331
369
|
|
|
332
370
|
/// Create an empty array Value.
|
|
333
371
|
pub fn empty_array() -> Self {
|
|
334
|
-
Value::Array(
|
|
372
|
+
Value::Array(VmRef::new(Vec::new()))
|
|
335
373
|
}
|
|
336
374
|
|
|
337
375
|
/// Create an empty object Value.
|
|
338
376
|
pub fn empty_object() -> Self {
|
|
339
|
-
Value::Object(
|
|
377
|
+
Value::Object(VmRef::new(ObjectMap::default()))
|
|
340
378
|
}
|
|
341
379
|
|
|
342
380
|
/// Extract the number value, if this is a Number.
|