use std::{slice::from_ref, str::FromStr};
use ahash::AHashMap;
use num_bigint::BigInt;
#[cfg(test)]
use strum::IntoEnumIterator;
use strum::{EnumCount, EnumIter, EnumString, FromRepr, IntoStaticStr};
use crate::{
function::Function,
hash::{ASCII_HASHES, HashValue, STATIC_HASHES, WithHash, hash_python_str},
value::Value,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
pub struct StringId(u32);
impl StringId {
#[inline]
pub fn from_index(index: u16) -> Self {
Self(u32::from(index))
}
#[inline]
pub fn index(self) -> usize {
self.0 as usize
}
#[must_use]
pub const fn from_ascii(byte: u8) -> Self {
Self(byte as u32)
}
#[must_use]
pub const fn from_static(value: StaticStrings) -> Self {
Self(value as u32)
}
}
const STATIC_STRING_ID_OFFSET: u16 = 1000;
const INTERN_STRING_ID_OFFSET: usize = 10_000;
pub(crate) static ASCII_STRS: [&str; 128] = const {
const ASCII_BYTES: [u8; 128] = const {
let mut bytes: [u8; 128] = [0; 128];
let mut i: u8 = 0;
while i < 128 {
bytes[i as usize] = i;
i += 1;
}
bytes
};
let mut strs: [&str; 128] = [""; 128];
let mut i = 0;
while i < 128 {
strs[i] = match str::from_utf8(from_ref(&ASCII_BYTES[i])) {
Ok(s) => s,
Err(_) => panic!("invalid ascii byte"),
};
i += 1;
}
strs
};
#[repr(u16)]
#[derive(
Debug,
Clone,
Copy,
FromRepr,
EnumCount,
EnumIter,
EnumString,
IntoStaticStr,
PartialEq,
Eq,
Hash,
serde::Serialize,
serde::Deserialize,
)]
#[strum(serialize_all = "snake_case")]
pub enum StaticStrings {
#[strum(serialize = "")]
EmptyString = STATIC_STRING_ID_OFFSET,
#[strum(serialize = "<module>")]
Module,
Append,
Insert,
Extend,
Reverse,
Sort,
Get,
Keys,
Values,
Items,
Setdefault,
Popitem,
Fromkeys,
Pop,
Clear,
Copy,
Add,
Remove,
Discard,
Update,
Union,
Intersection,
Difference,
SymmetricDifference,
Issubset,
Issuperset,
Isdisjoint,
Join,
Lower,
Upper,
Capitalize,
Title,
Swapcase,
Casefold,
Isalpha,
Isdigit,
Isalnum,
Isnumeric,
Isspace,
Islower,
Isupper,
Isascii,
Isdecimal,
Find,
Rfind,
Index,
Rindex,
Count,
Startswith,
Endswith,
Strip,
Lstrip,
Rstrip,
Removeprefix,
Removesuffix,
Split,
Rsplit,
Splitlines,
Partition,
Rpartition,
Replace,
Center,
Ljust,
Rjust,
Zfill,
Expandtabs,
Tabsize,
Keepends,
Obj,
Object,
Source,
Base,
Encode,
Isidentifier,
Istitle,
Decode,
Hex,
Fromhex,
Sys,
#[strum(serialize = "sys.version_info")]
SysVersionInfo,
Version,
VersionInfo,
Platform,
Stdout,
Stderr,
Major,
Minor,
Micro,
Releaselevel,
Serial,
Final,
#[strum(serialize = "3.14.0 (Monty)")]
MontyVersionString,
Monty,
#[strum(serialize = "StatResult")]
OsStatResult,
StMode,
StIno,
StDev,
StNlink,
StUid,
StGid,
StSize,
StAtime,
StMtime,
StCtime,
Typing,
#[strum(serialize = "TYPE_CHECKING")]
TypeChecking,
#[strum(serialize = "Any")]
Any,
#[strum(serialize = "Optional")]
Optional,
#[strum(serialize = "Union")]
UnionType,
#[strum(serialize = "List")]
ListType,
#[strum(serialize = "Dict")]
DictType,
#[strum(serialize = "Tuple")]
TupleType,
#[strum(serialize = "Set")]
SetType,
#[strum(serialize = "FrozenSet")]
FrozenSet,
#[strum(serialize = "Callable")]
Callable,
#[strum(serialize = "Type")]
Type,
#[strum(serialize = "Sequence")]
Sequence,
#[strum(serialize = "Mapping")]
Mapping,
#[strum(serialize = "Iterable")]
Iterable,
#[strum(serialize = "Iterator")]
IteratorType,
#[strum(serialize = "Generator")]
Generator,
#[strum(serialize = "ClassVar")]
ClassVar,
#[strum(serialize = "Final")]
FinalType,
#[strum(serialize = "Literal")]
Literal,
#[strum(serialize = "TypeVar")]
TypeVar,
#[strum(serialize = "Generic")]
Generic,
#[strum(serialize = "Protocol")]
Protocol,
#[strum(serialize = "Annotated")]
Annotated,
#[strum(serialize = "Self")]
SelfType,
#[strum(serialize = "Never")]
Never,
#[strum(serialize = "NoReturn")]
NoReturn,
Asyncio,
Gather,
Run,
Os,
Getenv,
Environ,
Default,
Args,
#[strum(serialize = "__name__")]
DunderName,
#[strum(serialize = "__enter__")]
Enter,
#[strum(serialize = "__exit__")]
Exit,
Pathlib,
#[strum(serialize = "Path")]
PathClass,
Name,
Parent,
Stem,
Suffix,
Suffixes,
Parts,
IsAbsolute,
Joinpath,
WithName,
WithStem,
WithSuffix,
AsPosix,
#[strum(serialize = "__fspath__")]
Fspath,
Exists,
IsFile,
IsDir,
IsSymlink,
#[strum(serialize = "stat")]
StatMethod,
ReadBytes,
ReadText,
Iterdir,
Resolve,
Absolute,
WriteText,
WriteBytes,
AppendText,
AppendBytes,
Mkdir,
Unlink,
Rmdir,
Rename,
Open,
Read,
Write,
Close,
Flush,
Readable,
Writable,
Seekable,
Readline,
Readlines,
Tell,
Seek,
Closed,
Mode,
Encoding,
File,
Buffering,
Errors,
Newline,
Closefd,
Opener,
Repl,
Old,
New,
Start,
Stop,
Step,
Math,
Floor,
Ceil,
Trunc,
Sqrt,
Isqrt,
Cbrt,
Pow,
Exp,
Exp2,
Expm1,
Log,
Log1p,
Log2,
Log10,
Fabs,
Isnan,
Isinf,
Isfinite,
Copysign,
Isclose,
Nextafter,
Ulp,
Sin,
Cos,
Tan,
Asin,
Acos,
Atan,
Atan2,
Sinh,
Cosh,
Tanh,
Asinh,
Acosh,
Atanh,
Degrees,
Radians,
Factorial,
Gcd,
Lcm,
Comb,
Perm,
Fmod,
Remainder,
Modf,
Frexp,
Ldexp,
Gamma,
Lgamma,
Erf,
Erfc,
Pi,
#[strum(serialize = "e")]
MathE,
Tau,
#[strum(serialize = "inf")]
MathInf,
#[strum(serialize = "nan")]
MathNan,
Json,
Loads,
Dumps,
#[strum(serialize = "JSONDecodeError")]
JsonDecodeError,
Indent,
#[strum(serialize = "sort_keys")]
SortKeys,
#[strum(serialize = "ensure_ascii")]
EnsureAscii,
#[strum(serialize = "allow_nan")]
AllowNan,
Separators,
Skipkeys,
Datetime,
Date,
Timedelta,
Timezone,
Today,
Now,
Utc,
TotalSeconds,
Tzinfo,
Year,
Month,
Day,
Hour,
Minute,
Second,
Microsecond,
Fold,
Days,
Seconds,
Microseconds,
Milliseconds,
Minutes,
Hours,
Weeks,
Offset,
Tz,
Number,
Ndigits,
Isoformat,
Strftime,
Weekday,
Isoweekday,
Timestamp,
Strptime,
Fromisoformat,
Re,
Compile,
Match,
Search,
Fullmatch,
Findall,
Sub,
Group,
Groups,
Span,
End,
#[strum(serialize = "Pattern")]
PatternClass,
#[strum(serialize = "Match")]
MatchClass,
#[strum(serialize = "pattern")]
PatternAttr,
#[strum(serialize = "string")]
StringAttr,
Flags,
#[strum(serialize = "IGNORECASE")]
Ignorecase,
#[strum(serialize = "I")]
I,
#[strum(serialize = "MULTILINE")]
MultilineFlag,
#[strum(serialize = "M")]
M,
#[strum(serialize = "DOTALL")]
DotallFlag,
#[strum(serialize = "S")]
S,
#[strum(serialize = "NOFLAG")]
NoFlag,
#[strum(serialize = "ASCII")]
AsciiFlag,
#[strum(serialize = "A")]
A,
#[strum(serialize = "PatternError")]
PatternError,
#[strum(serialize = "error")]
Error,
Escape,
Finditer,
Groupdict,
Gc,
Collect,
Disable,
Enable,
Key,
Sep,
Maxsplit,
Strict,
ReturnExceptions,
RelTol,
AbsTol,
Format,
Parents,
ExistOk,
Setrecursionlimit,
Unicodedata,
Normalize,
#[strum(serialize = "is_normalized")]
IsNormalized,
Category,
Lookup,
Combining,
#[strum(serialize = "unidata_version")]
UnidataVersion,
#[strum(serialize = "__main__")]
DunderMain,
#[strum(serialize = "__doc__")]
DunderDoc,
#[strum(serialize = "None")]
NoneRepr,
#[strum(serialize = "True")]
TrueRepr,
#[strum(serialize = "False")]
FalseRepr,
#[strum(serialize = "Ellipsis")]
EllipsisRepr,
Listdir,
Makedirs,
#[strum(serialize = "fspath")]
OsFspath,
Altsep,
Extsep,
Curdir,
Pardir,
Linesep,
Devnull,
Posix,
#[strum(serialize = "..")]
ParentDirString,
#[strum(serialize = "/dev/null")]
DevNullString,
Path,
DirFd,
FollowSymlinks,
Src,
Dst,
SrcDirFd,
DstDirFd,
Itertools,
Repeat,
Times,
Dataclasses,
Dataclass,
IsDataclass,
#[strum(serialize = "__dataclass_fields__")]
DataclassFields,
Collections,
Deque,
Appendleft,
Extendleft,
Popleft,
Rotate,
Maxlen,
#[strum(serialize = "iterable")]
IterableArg,
Namedtuple,
Defaultdict,
#[strum(serialize = "Counter")]
Counter,
#[strum(serialize = "most_common")]
MostCommon,
Elements,
Total,
Subtract,
Typename,
#[strum(serialize = "field_names")]
FieldNames,
#[strum(serialize = "_fields")]
UnderFields,
#[strum(serialize = "_field_defaults")]
UnderFieldDefaults,
#[strum(serialize = "_make")]
UnderMake,
#[strum(serialize = "_replace")]
UnderReplace,
#[strum(serialize = "_asdict")]
UnderAsdict,
Defaults,
#[strum(serialize = "module")]
ModuleKwarg,
#[strum(serialize = "default_factory")]
DefaultFactory,
#[strum(serialize = "__missing__")]
DunderMissing,
#[strum(serialize = "__module__")]
DunderModule,
#[strum(serialize = "__getnewargs__")]
DunderGetnewargs,
#[strum(serialize = "__qualname__")]
DunderQualname,
Pairwise,
Compress,
Data,
Selectors,
Islice,
Chain,
Cycle,
#[strum(serialize = "NotImplemented")]
NotImplementedRepr,
}
#[cfg(test)]
pub(crate) fn static_strings_fingerprint() -> u64 {
const OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
const PRIME: u64 = 0x0100_0000_01b3;
fn update(hash: &mut u64, bytes: &[u8]) {
for byte in u32::try_from(bytes.len())
.expect("fingerprint field length fits u32")
.to_le_bytes()
{
*hash ^= u64::from(byte);
*hash = hash.wrapping_mul(PRIME);
}
for byte in bytes {
*hash ^= u64::from(*byte);
*hash = hash.wrapping_mul(PRIME);
}
}
let mut hash = OFFSET_BASIS;
for value in StaticStrings::iter() {
update(&mut hash, &(value as u16).to_le_bytes());
update(&mut hash, format!("{value:?}").as_bytes());
let string: &'static str = value.into();
update(&mut hash, string.as_bytes());
update(
&mut hash,
&postcard::to_allocvec(&value).expect("StaticStrings serialization cannot fail"),
);
}
hash
}
impl StaticStrings {
pub fn from_string_id(id: StringId) -> Option<Self> {
u16::try_from(id.0).ok().and_then(Self::from_repr)
}
}
impl From<StaticStrings> for StringId {
fn from(value: StaticStrings) -> Self {
Self(value as u32)
}
}
impl From<StaticStrings> for Value {
fn from(value: StaticStrings) -> Self {
Self::InternString(value.into())
}
}
impl PartialEq<StaticStrings> for StringId {
fn eq(&self, other: &StaticStrings) -> bool {
*self == Self::from(*other)
}
}
impl PartialEq<StringId> for StaticStrings {
fn eq(&self, other: &StringId) -> bool {
StringId::from(*self) == *other
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct BytesId(u32);
impl BytesId {
#[inline]
pub fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct LongIntId(u32);
impl LongIntId {
#[inline]
pub fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
pub struct FunctionId(u32);
impl FunctionId {
#[inline]
pub fn from_index(index: u16) -> Self {
Self(u32::from(index))
}
#[inline]
pub fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Debug, Default, Clone)]
pub struct InternerBuilder {
string_map: AHashMap<String, StringId>,
strings: Vec<WithHash<String>>,
bytes: Vec<WithHash<Vec<u8>>>,
long_ints: Vec<WithHash<BigInt>>,
}
impl InternerBuilder {
pub fn new(code: &str) -> Self {
let capacity = code.bytes().filter(|&b| b == b'"' || b == b'\'').count() >> 1;
Self {
string_map: AHashMap::with_capacity(capacity),
strings: Vec::with_capacity(capacity),
bytes: Vec::new(),
long_ints: Vec::new(),
}
}
pub(crate) fn from_interns(interns: &Interns, code: &str) -> Self {
let mut builder = Self::new(code);
builder.strings.clone_from(&interns.strings);
builder.bytes.clone_from(&interns.bytes);
builder.long_ints.clone_from(&interns.long_ints);
builder.string_map = builder
.strings
.iter()
.enumerate()
.map(|(index, entry)| {
let id = StringId(
u32::try_from(INTERN_STRING_ID_OFFSET + index).expect("StringId overflow while seeding interner"),
);
(entry.value().clone(), id)
})
.collect();
builder
}
pub fn intern(&mut self, s: &str) -> StringId {
if s.len() == 1 {
StringId::from_ascii(s.as_bytes()[0])
} else if let Ok(ss) = StaticStrings::from_str(s) {
ss.into()
} else {
*self.string_map.entry(s.to_owned()).or_insert_with(|| {
let string_id = self.strings.len() + INTERN_STRING_ID_OFFSET;
let id = StringId(string_id.try_into().expect("StringId overflow"));
self.strings.push(WithHash::for_str(s.to_owned()));
id
})
}
}
pub fn intern_bytes(&mut self, b: &[u8]) -> BytesId {
let id = BytesId(self.bytes.len().try_into().expect("BytesId overflow"));
self.bytes.push(WithHash::for_bytes(b.to_vec()));
id
}
pub fn intern_long_int(&mut self, bi: BigInt) -> LongIntId {
let id = LongIntId(self.long_ints.len().try_into().expect("LongIntId overflow"));
self.long_ints.push(WithHash::for_long_int(bi));
id
}
#[inline]
pub fn get_str(&self, id: StringId) -> &str {
get_str(&self.strings, id)
}
}
fn get_str(strings: &[WithHash<String>], id: StringId) -> &str {
if let Some(ascii_str) = ASCII_STRS.get(id.index()) {
ascii_str
} else if let Some(intern_index) = id.index().checked_sub(INTERN_STRING_ID_OFFSET) {
strings[intern_index].value()
} else {
let static_str = StaticStrings::from_string_id(id).expect("Invalid static string ID");
static_str.into()
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(from = "InternsWire")]
pub(crate) struct Interns {
strings: Vec<WithHash<String>>,
bytes: Vec<WithHash<Vec<u8>>>,
long_ints: Vec<WithHash<BigInt>>,
functions: Vec<Function>,
#[serde(skip)]
string_id_by_name: AHashMap<String, StringId>,
}
#[derive(serde::Deserialize)]
struct InternsWire {
strings: Vec<WithHash<String>>,
bytes: Vec<WithHash<Vec<u8>>>,
long_ints: Vec<WithHash<BigInt>>,
functions: Vec<Function>,
}
impl From<Interns> for InternsWire {
fn from(interns: Interns) -> Self {
Self {
strings: interns.strings,
bytes: interns.bytes,
long_ints: interns.long_ints,
functions: interns.functions,
}
}
}
impl From<InternsWire> for Interns {
fn from(wire: InternsWire) -> Self {
let string_id_by_name = build_string_id_by_name(&wire.strings);
Self {
strings: wire.strings,
bytes: wire.bytes,
long_ints: wire.long_ints,
functions: wire.functions,
string_id_by_name,
}
}
}
fn build_string_id_by_name(strings: &[WithHash<String>]) -> AHashMap<String, StringId> {
strings
.iter()
.enumerate()
.map(|(index, entry)| {
let id = StringId(
u32::try_from(INTERN_STRING_ID_OFFSET + index)
.expect("StringId overflow while building reverse interns map"),
);
(entry.value().clone(), id)
})
.collect()
}
impl Interns {
pub fn new(interner: InternerBuilder, functions: Vec<Function>) -> Self {
Self {
strings: interner.strings,
bytes: interner.bytes,
long_ints: interner.long_ints,
functions,
string_id_by_name: interner.string_map,
}
}
#[inline]
pub fn get_str(&self, id: StringId) -> &str {
get_str(&self.strings, id)
}
#[inline]
pub fn get_bytes(&self, id: BytesId) -> &[u8] {
self.bytes[id.index()].value()
}
#[inline]
pub fn get_long_int(&self, id: LongIntId) -> &BigInt {
self.long_ints[id.index()].value()
}
#[inline]
pub fn get_function(&self, id: FunctionId) -> &Function {
self.functions.get(id.index()).expect("Function not found")
}
#[inline]
pub fn str_hash(&self, id: StringId) -> HashValue {
if id.index() < ASCII_STRS.len() {
ASCII_HASHES.get_or_compute(id.index(), || hash_python_str(ASCII_STRS[id.index()]))
} else if let Some(intern_index) = id.index().checked_sub(INTERN_STRING_ID_OFFSET) {
self.strings[intern_index].hash()
} else {
let static_str = StaticStrings::from_string_id(id).expect("Invalid static string ID");
STATIC_HASHES.get_or_compute((static_str as usize) - STATIC_STRING_ID_OFFSET as usize, || {
hash_python_str(static_str.into())
})
}
}
#[inline]
pub fn bytes_hash(&self, id: BytesId) -> HashValue {
self.bytes[id.index()].hash()
}
#[inline]
pub fn long_int_hash(&self, id: LongIntId) -> HashValue {
self.long_ints[id.index()].hash()
}
pub fn get_string_id_by_name(&self, s: &str) -> Option<StringId> {
if s.len() == 1 {
return Some(StringId::from_ascii(s.as_bytes()[0]));
}
if let Ok(ss) = StaticStrings::from_str(s) {
return Some(ss.into());
}
self.string_id_by_name.get(s).copied()
}
pub fn set_functions(&mut self, functions: Vec<Function>) {
self.functions = functions;
}
pub(crate) fn functions_clone(&self) -> Vec<Function> {
self.functions.clone()
}
}