use std::collections::HashMap;
use std::sync::{OnceLock, RwLock};
struct Table {
by_id: Vec<&'static [u8]>,
by_bytes: HashMap<&'static [u8], u64>,
}
fn table() -> &'static RwLock<Table> {
static T: OnceLock<RwLock<Table>> = OnceLock::new();
T.get_or_init(|| {
let empty: &'static [u8] = b"\0";
let mut by_bytes = HashMap::new();
by_bytes.insert(&empty[..0], 0);
RwLock::new(Table {
by_id: vec![empty],
by_bytes,
})
})
}
pub fn intern(bytes: &[u8]) -> u64 {
let owned;
let bytes = if bytes.contains(&0) {
owned = bytes
.iter()
.copied()
.filter(|b| *b != 0)
.collect::<Vec<u8>>();
&owned[..]
} else {
bytes
};
if let Some(&id) = table()
.read()
.unwrap_or_else(|e| e.into_inner())
.by_bytes
.get(bytes)
{
return id;
}
let mut t = table().write().unwrap_or_else(|e| e.into_inner());
if let Some(&id) = t.by_bytes.get(bytes) {
return id;
}
let mut stored = Vec::with_capacity(bytes.len() + 1);
stored.extend_from_slice(bytes);
stored.push(0);
let stored: &'static [u8] = Box::leak(stored.into_boxed_slice());
let id = t.by_id.len() as u64;
t.by_id.push(stored);
t.by_bytes.insert(&stored[..stored.len() - 1], id);
id
}
pub fn snapshot() -> Vec<Vec<u8>> {
let t = table().read().unwrap_or_else(|e| e.into_inner());
t.by_id[1..]
.iter()
.map(|s| s[..s.len() - 1].to_vec())
.collect()
}
pub fn preload(strings: &[Vec<u8>]) -> Result<(), String> {
for (i, s) in strings.iter().enumerate() {
let want = (i + 1) as u64;
let got = intern(s);
if got != want {
return Err(format!(
"string {want} of the design cannot take its id: this process already \
interned `{}` as {got}",
String::from_utf8_lossy(s)
));
}
}
Ok(())
}
pub fn bytes(id: u64) -> &'static [u8] {
let t = table().read().unwrap_or_else(|e| e.into_inner());
let s = t.by_id.get(id as usize).copied().unwrap_or(b"\0");
&s[..s.len() - 1]
}
pub fn c_str(id: u64) -> *const u8 {
let t = table().read().unwrap_or_else(|e| e.into_inner());
t.by_id.get(id as usize).copied().unwrap_or(b"\0").as_ptr()
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StrOp {
Concat = 0,
Replicate = 1,
Len = 2,
Getc = 3,
Putc = 4,
ToUpper = 5,
ToLower = 6,
Compare = 7,
ICompare = 8,
Substr = 9,
AToI = 10,
AToHex = 11,
AToOct = 12,
AToBin = 13,
IToA = 14,
HexToA = 15,
OctToA = 16,
BinToA = 17,
FromBits = 18,
ToBitsLo = 19,
ToBitsHi = 20,
ToBitsWord = 21,
}
impl StrOp {
pub fn from_u32(v: u32) -> Option<StrOp> {
use StrOp::*;
Some(match v {
0 => Concat,
1 => Replicate,
2 => Len,
3 => Getc,
4 => Putc,
5 => ToUpper,
6 => ToLower,
7 => Compare,
8 => ICompare,
9 => Substr,
10 => AToI,
11 => AToHex,
12 => AToOct,
13 => AToBin,
14 => IToA,
15 => HexToA,
16 => OctToA,
17 => BinToA,
18 => FromBits,
19 => ToBitsLo,
20 => ToBitsHi,
21 => ToBitsWord,
_ => return None,
})
}
}
fn strcmp(x: &[u8], y: &[u8]) -> u64 {
let n = x.len().max(y.len());
for i in 0..n {
let (p, q) = (
i64::from(x.get(i).copied().unwrap_or(0)),
i64::from(y.get(i).copied().unwrap_or(0)),
);
if p != q {
return (p - q) as u64;
}
}
0
}
fn parse_leading(s: &[u8], radix: u32) -> u64 {
let mut i = 0;
let mut v: u64 = 0;
while i < s.len() {
let c = s[i];
if c == b'_' {
i += 1;
continue;
}
match (c as char).to_digit(radix) {
Some(d) => v = v.wrapping_mul(u64::from(radix)).wrapping_add(u64::from(d)),
None => break,
}
i += 1;
}
(v as u32 as i32) as i64 as u64
}
pub fn apply(op: StrOp, a: u64, b: u64, c: u64) -> u64 {
let s = || bytes(a);
match op {
StrOp::Concat => {
let (x, y) = (bytes(a), bytes(b));
if y.is_empty() {
return a;
}
if x.is_empty() {
return b;
}
intern(&[x, y].concat())
}
StrOp::Replicate => intern(&s().repeat(b.min(1 << 24) as usize)),
StrOp::Len => s().len() as u64,
StrOp::Getc => s().get(b as usize).copied().map_or(0, u64::from),
StrOp::Putc => {
let mut v = s().to_vec();
match v.get_mut(b as usize) {
Some(slot) if c as u8 != 0 => {
*slot = c as u8;
intern(&v)
}
_ => a,
}
}
StrOp::ToUpper => intern(&s().to_ascii_uppercase()),
StrOp::ToLower => intern(&s().to_ascii_lowercase()),
StrOp::Compare => strcmp(bytes(a), bytes(b)),
StrOp::ICompare => strcmp(
&bytes(a).to_ascii_lowercase(),
&bytes(b).to_ascii_lowercase(),
),
StrOp::Substr => {
let v = s();
let (i, j) = (b as i64, c as i64);
if i < 0 || j < i || j as usize >= v.len() {
0
} else {
intern(&v[i as usize..=j as usize])
}
}
StrOp::AToI => parse_leading(s(), 10),
StrOp::AToHex => parse_leading(s(), 16),
StrOp::AToOct => parse_leading(s(), 8),
StrOp::AToBin => parse_leading(s(), 2),
StrOp::IToA | StrOp::HexToA | StrOp::OctToA | StrOp::BinToA => {
let width = (c & 0xff) as u32;
let signed = c & 0x100 != 0;
let mask = if width >= 64 {
u64::MAX
} else {
(1u64 << width) - 1
};
let v = a & mask;
let negative = signed && width > 0 && (v >> (width - 1)) & 1 == 1;
let (sign, mag) = if negative {
("-", (v | !mask).wrapping_neg())
} else {
("", v)
};
let text = match op {
StrOp::IToA => format!("{sign}{mag}"),
StrOp::HexToA => format!("{sign}{mag:x}"),
StrOp::OctToA => format!("{sign}{mag:o}"),
_ => format!("{sign}{mag:b}"),
};
intern(text.as_bytes())
}
StrOp::FromBits => {
let width = c.min(128) as u32;
let v = (u128::from(b) << 64) | u128::from(a);
let v = if width >= 128 {
v
} else {
v & ((1u128 << width) - 1)
};
let n = width.div_ceil(8);
let out: Vec<u8> = (0..n)
.rev()
.map(|k| (v >> (8 * k)) as u8)
.filter(|byte| *byte != 0)
.collect();
intern(&out)
}
StrOp::ToBitsWord => {
let rev: Vec<u8> = s().iter().rev().copied().collect();
let (width, k) = (b, c);
let mut w: u64 = 0;
for j in 0..8u64 {
let byte = u64::from(rev.get((8 * k + j) as usize).copied().unwrap_or(0));
w |= byte << (8 * j);
}
let lo = 64 * k;
if lo >= width {
0
} else if width - lo < 64 {
w & ((1u64 << (width - lo)) - 1)
} else {
w
}
}
StrOp::ToBitsLo | StrOp::ToBitsHi => {
let width = b.min(128) as u32;
let mut v: u128 = 0;
for byte in s() {
v = (v << 8) | u128::from(*byte);
}
let v = if width >= 128 {
v
} else {
v & ((1u128 << width) - 1)
};
if op == StrOp::ToBitsLo {
v as u64
} else {
(v >> 64) as u64
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn interning_gives_equal_contents_one_id() {
assert_eq!(intern(b""), 0);
let a = intern(b"hello");
assert_eq!(intern(b"hello"), a);
assert_ne!(intern(b"hellO"), a);
assert_eq!(bytes(a), b"hello");
assert_eq!(intern(b"a\0b"), intern(b"ab"), "NUL characters are dropped");
}
#[test]
fn operations_follow_lrm_6_16() {
let hi = intern(b"Hello");
assert_eq!(
bytes(apply(StrOp::Concat, hi, intern(b" W"), 0)),
b"Hello W"
);
assert_eq!(apply(StrOp::Len, hi, 0, 0), 5);
assert_eq!(apply(StrOp::Getc, hi, 1, 0), u64::from(b'e'));
assert_eq!(apply(StrOp::Getc, hi, 9, 0), 0);
assert_eq!(bytes(apply(StrOp::Putc, hi, 0, u64::from(b'J'))), b"Jello");
assert_eq!(
apply(StrOp::Putc, hi, 0, 0),
hi,
"putc of 0 changes nothing"
);
assert_eq!(bytes(apply(StrOp::ToUpper, hi, 0, 0)), b"HELLO");
assert_eq!(
apply(StrOp::Compare, intern(b"a"), intern(b"b"), 0) as i64,
-1
);
assert_eq!(apply(StrOp::ICompare, intern(b"ABC"), intern(b"abc"), 0), 0);
assert_eq!(bytes(apply(StrOp::Substr, hi, 1, 3)), b"ell");
assert_eq!(apply(StrOp::Substr, hi, 3, 9), 0);
assert_eq!(apply(StrOp::AToI, intern(b"1_234x"), 0, 0) as i64, 1234);
assert_eq!(apply(StrOp::AToI, intern(b" 12"), 0, 0), 0);
assert_eq!(apply(StrOp::AToI, intern(b"-5"), 0, 0), 0);
assert_eq!(apply(StrOp::AToHex, intern(b"ff_zz"), 0, 0), 255);
assert_eq!(
bytes(apply(StrOp::IToA, (-5i64) as u64, 0, 32 | 0x100)),
b"-5"
);
assert_eq!(bytes(apply(StrOp::HexToA, 255, 0, 32)), b"ff");
assert_eq!(bytes(apply(StrOp::FromBits, 0x4142, 0, 32)), b"AB");
assert_eq!(apply(StrOp::ToBitsLo, intern(b"AB"), 16, 0), 0x4142);
assert_eq!(apply(StrOp::ToBitsLo, intern(b"ABC"), 16, 0), 0x4243);
let long = intern(b"0123456789ABCDEFGH");
assert_eq!(
apply(StrOp::ToBitsWord, long, 144, 0),
u64::from_be_bytes(*b"ABCDEFGH")
);
assert_eq!(apply(StrOp::ToBitsWord, long, 144, 2), 0x3031);
assert_eq!(apply(StrOp::ToBitsWord, long, 140, 2), 0x031);
}
}