@tishlang/tish-format 3.15.2 → 3.15.3
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/bin/tish-format +0 -0
- package/crates/tish_core/src/json.rs +70 -10
- package/crates/tish_core/src/vmref.rs +56 -21
- package/crates/tish_fmt/Cargo.toml +1 -1
- package/package.json +1 -1
- 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/bin/tish-format
CHANGED
|
Binary file
|
|
@@ -363,6 +363,18 @@ fn parse_key<'a>(input: &'a str, cache: &mut KeyCache) -> Result<(Arc<str>, &'a
|
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
/// The four hex digits after `\\u`, consuming them (and counting their bytes); None if they
|
|
367
|
+
/// aren't four hex digits.
|
|
368
|
+
fn hex4(chars: &mut core::iter::Peekable<core::str::Chars<'_>>, byte_count: &mut usize) -> Option<u32> {
|
|
369
|
+
let mut hex = String::new();
|
|
370
|
+
for _ in 0..4 {
|
|
371
|
+
let c = chars.next()?;
|
|
372
|
+
hex.push(c);
|
|
373
|
+
*byte_count += c.len_utf8();
|
|
374
|
+
}
|
|
375
|
+
u32::from_str_radix(&hex, 16).ok()
|
|
376
|
+
}
|
|
377
|
+
|
|
366
378
|
/// Decode a JSON string that contains at least one escape (the slow path).
|
|
367
379
|
fn parse_string_escaped(input: &str) -> Result<(Value, &str), String> {
|
|
368
380
|
let input = &input[1..]; // skip opening quote
|
|
@@ -404,19 +416,39 @@ fn parse_string_escaped(input: &str) -> Result<(Value, &str), String> {
|
|
|
404
416
|
result.push('/');
|
|
405
417
|
byte_count += 1;
|
|
406
418
|
}
|
|
419
|
+
Some('b') => {
|
|
420
|
+
result.push('\u{8}');
|
|
421
|
+
byte_count += 1;
|
|
422
|
+
}
|
|
423
|
+
Some('f') => {
|
|
424
|
+
result.push('\u{c}');
|
|
425
|
+
byte_count += 1;
|
|
426
|
+
}
|
|
407
427
|
Some('u') => {
|
|
408
428
|
byte_count += 1;
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
429
|
+
match hex4(&mut chars, &mut byte_count) {
|
|
430
|
+
// A UTF-16 surrogate pair (`\ud83d\ude00`) is one character. A lone
|
|
431
|
+
// surrogate has no character of its own, so it reads as U+FFFD.
|
|
432
|
+
Some(high @ 0xD800..=0xDBFF) => {
|
|
433
|
+
let mut ahead = chars.clone();
|
|
434
|
+
let mut ahead_bytes = 0;
|
|
435
|
+
let low = if ahead.next() == Some('\\') && ahead.next() == Some('u') {
|
|
436
|
+
hex4(&mut ahead, &mut ahead_bytes)
|
|
437
|
+
} else {
|
|
438
|
+
None
|
|
439
|
+
};
|
|
440
|
+
match low {
|
|
441
|
+
Some(low @ 0xDC00..=0xDFFF) => {
|
|
442
|
+
chars = ahead;
|
|
443
|
+
byte_count += 2 + ahead_bytes;
|
|
444
|
+
let n = 0x10000 + ((high - 0xD800) << 10) + (low - 0xDC00);
|
|
445
|
+
result.push(char::from_u32(n).unwrap_or('\u{FFFD}'));
|
|
446
|
+
}
|
|
447
|
+
_ => result.push('\u{FFFD}'),
|
|
448
|
+
}
|
|
419
449
|
}
|
|
450
|
+
Some(n) => result.push(char::from_u32(n).unwrap_or('\u{FFFD}')),
|
|
451
|
+
None => {}
|
|
420
452
|
}
|
|
421
453
|
}
|
|
422
454
|
Some(c) => {
|
|
@@ -664,3 +696,31 @@ mod tests {
|
|
|
664
696
|
}
|
|
665
697
|
}
|
|
666
698
|
}
|
|
699
|
+
|
|
700
|
+
#[cfg(test)]
|
|
701
|
+
mod escape_tests {
|
|
702
|
+
use super::json_parse;
|
|
703
|
+
use crate::Value;
|
|
704
|
+
|
|
705
|
+
fn text(json: &str) -> String {
|
|
706
|
+
match json_parse(json).unwrap() {
|
|
707
|
+
Value::String(s) => s.to_string(),
|
|
708
|
+
other => panic!("not a string: {other:?}"),
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
#[test]
|
|
713
|
+
fn surrogate_pairs_are_one_character() {
|
|
714
|
+
assert_eq!(text(r#""😀 ok""#), "😀 ok");
|
|
715
|
+
assert_eq!(text(r#""a😀b""#), "a😀b");
|
|
716
|
+
assert_eq!(text(r#""é""#), "é");
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
#[test]
|
|
720
|
+
fn lone_surrogates_and_short_escapes() {
|
|
721
|
+
assert_eq!(text(r#""\ud83d x""#), "\u{FFFD} x");
|
|
722
|
+
assert_eq!(text(r#""\ude00""#), "\u{FFFD}");
|
|
723
|
+
assert_eq!(text(r#""\ud83dA""#), "\u{FFFD}A");
|
|
724
|
+
assert_eq!(text(r#""\b\f\n""#), "\u{8}\u{c}\n");
|
|
725
|
+
}
|
|
726
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
//! | feature `send-values` | `VmRef<T>` | `NativeFn` | targets |
|
|
16
16
|
//! |---------------------------|-------------------------|----------------------------------|--------------------------------------------------|
|
|
17
17
|
//! | **off** *(default)* | `Rc<RefCell<T>>` | `Rc<dyn Fn + 'static>` | wasm32, wasi, interpreter, cranelift/llvm VMs |
|
|
18
|
-
//! | **on** | `Arc<
|
|
18
|
+
//! | **on** | `Arc<RwLock<T>>` | `Arc<dyn Fn + Send + Sync>` | Rust native with `http` enabled (server workloads) |
|
|
19
19
|
//!
|
|
20
20
|
//! The *API* is identical in both configurations (`borrow` / `borrow_mut`
|
|
21
21
|
//! / `ptr_eq` / `Clone`), so every existing call site in the workspace
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
//! bit-identical to the pre-migration baseline.
|
|
29
29
|
//! * **Rust native, non-server**: same — `send-values` only activates
|
|
30
30
|
//! when something in the dependency graph (usually `http`) needs it.
|
|
31
|
-
//! * **Rust native with server**: `Arc<
|
|
31
|
+
//! * **Rust native with server**: `Arc<RwLock<T>>` pays ~3–5 ns per
|
|
32
32
|
//! `borrow` in the uncontended case (single atomic CAS). On Tish's
|
|
33
33
|
//! hot paths — roughly 6–12 borrows per request — that's ~30–60 ns of
|
|
34
34
|
//! overhead. In exchange we get `N×` handler scaling across cores,
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
//! ```
|
|
46
46
|
//!
|
|
47
47
|
//! Returned guard types (`VmReadGuard<'_, T>`, `VmWriteGuard<'_, T>`) are
|
|
48
|
-
//! type aliases that pick `Ref`/`RefMut` or `
|
|
48
|
+
//! type aliases that pick `Ref`/`RefMut` or `RwLock` read/write guards depending on the
|
|
49
49
|
//! feature. They both `Deref` (and, for write guards, `DerefMut`) to `T`
|
|
50
50
|
//! just like the underlying types.
|
|
51
51
|
|
|
@@ -113,45 +113,45 @@ mod imp {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
// --------------------------------------------------------------------------
|
|
116
|
-
// Thread-safe backing store (opt-in): Arc<
|
|
116
|
+
// Thread-safe backing store (opt-in): Arc<RwLock<T>>
|
|
117
117
|
// --------------------------------------------------------------------------
|
|
118
118
|
#[cfg(feature = "send-values")]
|
|
119
119
|
mod imp {
|
|
120
|
-
use parking_lot::
|
|
120
|
+
use parking_lot::RwLock;
|
|
121
121
|
use std::sync::Arc;
|
|
122
122
|
|
|
123
123
|
#[derive(Default)]
|
|
124
|
-
pub struct VmRef<T: ?Sized>(pub(super) Arc<
|
|
124
|
+
pub struct VmRef<T: ?Sized>(pub(super) Arc<RwLock<T>>);
|
|
125
125
|
|
|
126
|
-
/// Read guard alias.
|
|
127
|
-
///
|
|
128
|
-
|
|
129
|
-
///
|
|
130
|
-
pub type
|
|
126
|
+
/// Read guard alias. Readers share the lock, like `RefCell::borrow`: native codegen reads the
|
|
127
|
+
/// same cell more than once within one statement (`{ a: x, b: n - x }` → two
|
|
128
|
+
/// `(*x.borrow())` temporaries alive until the statement ends), which an exclusive lock turned
|
|
129
|
+
/// into a self-deadlock.
|
|
130
|
+
pub type ReadGuard<'a, T> = parking_lot::RwLockReadGuard<'a, T>;
|
|
131
|
+
/// Write guard alias (exclusive).
|
|
132
|
+
pub type WriteGuard<'a, T> = parking_lot::RwLockWriteGuard<'a, T>;
|
|
131
133
|
|
|
132
134
|
impl<T> VmRef<T> {
|
|
133
135
|
#[inline]
|
|
134
136
|
pub fn new(value: T) -> Self {
|
|
135
|
-
VmRef(Arc::new(
|
|
137
|
+
VmRef(Arc::new(RwLock::new(value)))
|
|
136
138
|
}
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
impl<T: ?Sized> VmRef<T> {
|
|
140
|
-
///
|
|
141
|
-
///
|
|
142
|
-
///
|
|
143
|
-
///
|
|
144
|
-
/// every property/element access locks. It also has no poisoning, so
|
|
145
|
-
/// there is no `Result` to swallow (a handler panic aborts the thread
|
|
146
|
-
/// regardless).
|
|
142
|
+
/// Shared access. `read_recursive` so a thread that already holds a read guard can take
|
|
143
|
+
/// another even while a writer on another thread waits (a plain `read` would queue behind
|
|
144
|
+
/// that writer and deadlock). `parking_lot` rather than `std`: its uncontended lock is a
|
|
145
|
+
/// single atomic, and every property/element access takes one.
|
|
147
146
|
#[inline]
|
|
148
147
|
pub fn borrow(&self) -> ReadGuard<'_, T> {
|
|
149
|
-
self.0.
|
|
148
|
+
self.0.read_recursive()
|
|
150
149
|
}
|
|
151
150
|
|
|
151
|
+
/// Exclusive access.
|
|
152
152
|
#[inline]
|
|
153
153
|
pub fn borrow_mut(&self) -> WriteGuard<'_, T> {
|
|
154
|
-
self.0.
|
|
154
|
+
self.0.write()
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
#[inline]
|
|
@@ -207,3 +207,38 @@ impl<T: fmt::Debug> fmt::Debug for VmRef<T> {
|
|
|
207
207
|
write!(f, "RefCell {{ value: {:?} }}", &*self.borrow())
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
+
|
|
211
|
+
#[cfg(all(test, feature = "send-values"))]
|
|
212
|
+
mod send_values_tests {
|
|
213
|
+
use super::VmRef;
|
|
214
|
+
|
|
215
|
+
/// Native codegen keeps `(*x.borrow())` temporaries alive to the end of a statement, so one
|
|
216
|
+
/// statement can read the same cell twice: `{ a: x, b: n - x }`. That must not deadlock.
|
|
217
|
+
#[test]
|
|
218
|
+
fn two_reads_of_one_cell_at_once() {
|
|
219
|
+
let x = VmRef::new(2.0_f64);
|
|
220
|
+
let sum = *x.borrow() + *x.borrow();
|
|
221
|
+
assert_eq!(sum, 4.0);
|
|
222
|
+
*x.borrow_mut() = sum;
|
|
223
|
+
assert_eq!(*x.borrow(), 4.0);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/// Readers on other threads still see writes, and a held read guard doesn't block another
|
|
227
|
+
/// reader while a writer waits elsewhere.
|
|
228
|
+
#[test]
|
|
229
|
+
fn readers_and_writers_across_threads() {
|
|
230
|
+
let x = VmRef::new(0_u32);
|
|
231
|
+
let held = x.borrow();
|
|
232
|
+
let writer = {
|
|
233
|
+
let x = x.clone();
|
|
234
|
+
std::thread::spawn(move || {
|
|
235
|
+
*x.borrow_mut() += 1;
|
|
236
|
+
})
|
|
237
|
+
};
|
|
238
|
+
std::thread::sleep(std::time::Duration::from_millis(20));
|
|
239
|
+
assert_eq!(*x.borrow(), 0, "a second read while a writer waits");
|
|
240
|
+
drop(held);
|
|
241
|
+
writer.join().unwrap();
|
|
242
|
+
assert_eq!(*x.borrow(), 1);
|
|
243
|
+
}
|
|
244
|
+
}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|