use rustc_hash::FxHashSet as HashSet;
use std::sync::{
Arc,
atomic::{AtomicU64, Ordering},
};
use indexmap::IndexMap;
use serde_json::json;
use crate::parser::{Expr, TypeExpr};
pub type ObjectMap = IndexMap<Arc<str>, Value, rustc_hash::FxBuildHasher>;
pub(crate) const MAPPING_KEY_PREFIX: &str = "\0pklr:mapping-key:";
static NEXT_NAN_MAPPING_KEY: AtomicU64 = AtomicU64::new(0);
pub(crate) fn mapping_storage_key(value: &Value) -> Option<Arc<str>> {
let (kind, identity, display) = match value {
Value::String(_) => return None,
Value::Null => ("null", "0000000000000000".to_string(), "null".to_string()),
Value::Bool(value) => (
"bool",
format!("{:016x}", u64::from(*value)),
value.to_string(),
),
Value::Int(value) => ("int", format!("{:016x}", *value as u64), value.to_string()),
Value::Float(value) => {
let identity = if *value == 0.0 { 0.0 } else { *value };
(
"float",
format!("{:016x}", identity.to_bits()),
value.to_string(),
)
}
Value::Duration(value) => {
let identity = value.value_in(DurationUnit::Nanos);
let identity = if identity == 0.0 { 0.0 } else { identity };
let identity = if identity.is_nan() {
format!(
"nan-{:02x}-{:016x}-{:016x}",
value.unit as u8,
value.value.to_bits(),
value.nan_identity,
)
} else {
format!("finite-{:016x}", identity.to_bits())
};
(
"duration",
identity,
format!("{}.{}", value.value, value.unit.symbol()),
)
}
Value::DataSize(value) => {
let identity = value.value_in(DataSizeUnit::Bytes);
let identity = if identity == 0.0 { 0.0 } else { identity };
let identity = if identity.is_nan() {
format!(
"nan-{:02x}-{:016x}-{:016x}",
value.unit as u8,
value.value.to_bits(),
value.nan_identity,
)
} else {
format!("finite-{:016x}", identity.to_bits())
};
(
"dataSize",
identity,
format!("{}.{}", value.value, value.unit.symbol()),
)
}
Value::List(value) => (
"list",
mapping_compound_identity(&Value::List(value.clone())),
Value::List(value.clone()).to_json().to_string(),
),
Value::Object(map, source) => (
"object",
mapping_object_identity(map, source.as_deref()),
Value::Object(map.clone(), source.clone())
.to_json()
.to_string(),
),
other => (
"display",
"0000000000000000".to_string(),
format!("{other:?}"),
),
};
Some(format!("{MAPPING_KEY_PREFIX}{kind}:{identity}:{display}").into())
}
fn mapping_compound_identity(value: &Value) -> String {
match value {
Value::Null => "null".into(),
Value::Bool(value) => format!("bool:{value}"),
Value::Int(value) => format!("int:{value}"),
Value::Float(value) => format!("float:{:016x}", value.to_bits()),
Value::String(value) => format!("string:{}:{value}", value.len()),
Value::Duration(_) | Value::DataSize(_) => mapping_storage_key(value)
.expect("native units have mapping keys")
.to_string(),
Value::Regex(value) => format!("regex:{}:{}", value.pattern().len(), value.pattern()),
Value::List(items) => {
let mut identity = format!("list:{:?}:[", items.kind());
for item in items.iter() {
let item = mapping_compound_identity(item);
identity.push_str(&format!("{}:{item}", item.len()));
}
identity.push(']');
identity
}
Value::Object(map, source) => mapping_object_identity(map, source.as_deref()),
other => format!("debug:{other:?}"),
}
}
fn mapping_object_identity(map: &ObjectMap, source: Option<&ObjectSource>) -> String {
let mut members = map
.iter()
.map(|(key, value)| {
let value = mapping_compound_identity(value);
format!("{}:{key}:{}:{value}", key.len(), value.len())
})
.collect::<Vec<_>>();
members.sort_unstable();
let kind = source.map_or_else(
|| "Dynamic".to_string(),
|source| match source.kind {
ObjectKind::Mapping => "Mapping".to_string(),
ObjectKind::Object => source.type_identity.as_deref().map_or_else(
|| "Dynamic".to_string(),
|identity| format!("instance:{identity}"),
),
ObjectKind::Class => source.type_identity.as_deref().map_or_else(
|| "Class".to_string(),
|identity| format!("class:{identity}"),
),
},
);
format!("object:{kind}:{}", members.join("|"))
}
pub(crate) fn mapping_storage_keys_equal(a: &str, b: &str) -> bool {
fn parts(key: &str) -> Option<(&str, &str)> {
let key = key.strip_prefix(MAPPING_KEY_PREFIX)?;
let (kind, rest) = key.split_once(':')?;
let (identity, _) = rest.split_once(':')?;
matches!(kind, "duration" | "dataSize").then_some((kind, identity))
}
parts(a).zip(parts(b)).is_some_and(|(a, b)| a == b)
}
pub(crate) fn display_storage_key(key: &str) -> &str {
key.strip_prefix(MAPPING_KEY_PREFIX)
.and_then(|key| key.rsplit_once(':').map(|(_, display)| display))
.unwrap_or(key)
}
pub(crate) fn mapping_storage_value(key: &str) -> Value {
let Some(key) = key.strip_prefix(MAPPING_KEY_PREFIX) else {
return Value::String(key.into());
};
let Some((kind, rest)) = key.split_once(':') else {
return Value::String(key.into());
};
let Some((identity, display)) = rest.split_once(':') else {
return Value::String(key.into());
};
match kind {
"null" => Value::Null,
"bool" => display
.parse()
.map(Value::Bool)
.unwrap_or_else(|_| Value::String(display.into())),
"int" => display
.parse()
.map(Value::Int)
.unwrap_or_else(|_| Value::String(display.into())),
"float" => display
.parse()
.map(Value::Float)
.unwrap_or_else(|_| Value::String(display.into())),
"duration" => parse_unit_value(display, DurationUnit::parse)
.map(|(value, unit)| {
Value::Duration(Duration::with_mapping_identity(value, unit, identity))
})
.unwrap_or_else(|| Value::String(display.into())),
"dataSize" => parse_unit_value(display, DataSizeUnit::parse)
.map(|(value, unit)| {
Value::DataSize(DataSize::with_mapping_identity(value, unit, identity))
})
.unwrap_or_else(|| Value::String(display.into())),
_ => Value::String(display.into()),
}
}
fn parse_unit_value<U>(display: &str, unit: impl FnOnce(&str) -> Option<U>) -> Option<(f64, U)> {
let (value, unit_name) = display.rsplit_once('.')?;
Some((value.parse().ok()?, unit(unit_name)?))
}
fn mapping_nan_identity(identity: &str) -> Option<(u64, u64)> {
let mut parts = identity.strip_prefix("nan-")?.split('-');
let _unit = parts.next()?;
let bits = u64::from_str_radix(parts.next()?, 16).ok()?;
let nonce = u64::from_str_radix(parts.next()?, 16).ok()?;
parts.next().is_none().then_some((bits, nonce))
}
pub type ScopeMap = ObjectMap;
pub(crate) type TypeAliasMap = IndexMap<Arc<str>, Arc<TypeExpr>, rustc_hash::FxBuildHasher>;
pub(crate) type NameSet = rustc_hash::FxHashSet<Arc<str>>;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct CapturedScope {
pub values: ScopeMap,
pub declared: NameSet,
pub body_members: HashSet<Arc<str>>,
pub module_identities: IndexMap<String, String>,
pub type_aliases: TypeAliasMap,
pub type_namespace: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ObjectSource {
pub(crate) entries: crate::parser::Body,
pub(crate) captured: crate::eval::SourceScope,
pub(crate) body_members: HashSet<Arc<str>>,
pub(crate) is_open: bool,
pub(crate) is_abstract: bool,
pub(crate) type_name: Option<String>,
pub(crate) type_identity: Option<String>,
pub(crate) parent_type_names: Vec<String>,
pub(crate) parent_type_identities: Vec<String>,
pub(crate) entry_scopes: Vec<Option<Arc<CapturedScope>>>,
pub(crate) evaluated_properties: Vec<Arc<str>>,
pub(crate) elements: Vec<Value>,
pub(crate) mapping_value_types: Vec<String>,
pub(crate) deprecated: IndexMap<String, Option<String>>,
pub(crate) poisoned_members: Option<Arc<IndexMap<Arc<str>, PoisonedMember>>>,
pub(crate) kind: ObjectKind,
pub(crate) is_parsed_json: bool,
pub(crate) prototype: Option<Value>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum ObjectKind {
#[default]
Object,
Mapping,
Class,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct PoisonedMember {
pub(crate) message: String,
pub(crate) rendered: bool,
}
impl ObjectSource {
pub fn entries(&self) -> &[crate::parser::Entry] {
&self.entries
}
pub fn is_open(&self) -> bool {
self.is_open
}
pub fn type_name(&self) -> Option<&str> {
self.type_name.as_deref()
}
pub fn parent_type_names(&self) -> &[String] {
&self.parent_type_names
}
pub fn scope(&self) -> &ScopeMap {
&self.captured.parts().values
}
pub(crate) fn scope_declared(&self) -> &NameSet {
&self.captured.parts().declared
}
pub(crate) fn scope_module_identities(&self) -> &IndexMap<String, String> {
&self.captured.parts().module_identities
}
pub(crate) fn scope_type_aliases(&self) -> &TypeAliasMap {
&self.captured.parts().type_aliases
}
pub(crate) fn is_metadata_only(&self) -> bool {
(self.poisoned_members.is_some()
|| self.is_abstract
|| (matches!(self.kind, ObjectKind::Mapping) && self.mapping_value_types.is_empty()))
&& self.entries.is_empty()
&& self.evaluated_properties.is_empty()
&& self.type_name.is_none()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum ListKind {
#[default]
List,
Listing,
Set,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ListValue {
items: Arc<Vec<Value>>,
kind: ListKind,
}
impl ListValue {
pub fn new(kind: ListKind, items: impl Into<Arc<Vec<Value>>>) -> Self {
ListValue {
items: items.into(),
kind,
}
}
pub fn kind(&self) -> ListKind {
self.kind
}
pub fn with_kind(mut self, kind: ListKind) -> Self {
self.kind = kind;
self
}
pub(crate) fn items_ptr(&self) -> *const Vec<Value> {
Arc::as_ptr(&self.items)
}
pub fn make_mut(&mut self) -> &mut Vec<Value> {
Arc::make_mut(&mut self.items)
}
}
impl std::ops::Deref for ListValue {
type Target = Vec<Value>;
fn deref(&self) -> &Vec<Value> {
&self.items
}
}
impl From<Vec<Value>> for ListValue {
fn from(items: Vec<Value>) -> Self {
ListValue::new(ListKind::List, items)
}
}
impl From<Arc<Vec<Value>>> for ListValue {
fn from(items: Arc<Vec<Value>>) -> Self {
ListValue::new(ListKind::List, items)
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[non_exhaustive]
pub enum Value {
#[default]
Null,
Bool(bool),
Int(i64),
Float(f64),
String(Arc<str>),
Object(Arc<ObjectMap>, Option<Arc<ObjectSource>>),
List(ListValue),
Lambda(Arc<[String]>, Arc<Expr>, Arc<ScopeMap>),
Regex(Arc<Regex>),
Duration(Duration),
DataSize(DataSize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DurationUnit {
Nanos,
Micros,
Millis,
Seconds,
Minutes,
Hours,
Days,
}
impl DurationUnit {
pub const ALL: [DurationUnit; 7] = [
DurationUnit::Nanos,
DurationUnit::Micros,
DurationUnit::Millis,
DurationUnit::Seconds,
DurationUnit::Minutes,
DurationUnit::Hours,
DurationUnit::Days,
];
pub fn symbol(self) -> &'static str {
match self {
DurationUnit::Nanos => "ns",
DurationUnit::Micros => "us",
DurationUnit::Millis => "ms",
DurationUnit::Seconds => "s",
DurationUnit::Minutes => "min",
DurationUnit::Hours => "h",
DurationUnit::Days => "d",
}
}
pub fn parse(symbol: &str) -> Option<Self> {
Self::ALL.into_iter().find(|unit| unit.symbol() == symbol)
}
pub fn nanos(self) -> f64 {
match self {
DurationUnit::Nanos => 1.0,
DurationUnit::Micros => 1e3,
DurationUnit::Millis => 1e6,
DurationUnit::Seconds => 1e9,
DurationUnit::Minutes => 60e9,
DurationUnit::Hours => 3600e9,
DurationUnit::Days => 86400e9,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Duration {
pub value: f64,
pub unit: DurationUnit,
nan_identity: u64,
}
impl Duration {
pub(crate) fn new(value: f64, unit: DurationUnit) -> Self {
Self {
value,
unit,
nan_identity: NEXT_NAN_MAPPING_KEY.fetch_add(1, Ordering::Relaxed),
}
}
fn with_mapping_identity(value: f64, unit: DurationUnit, identity: &str) -> Self {
let (value, nan_identity) = mapping_nan_identity(identity)
.map(|(bits, nonce)| (f64::from_bits(bits), nonce))
.unwrap_or_else(|| (value, NEXT_NAN_MAPPING_KEY.fetch_add(1, Ordering::Relaxed)));
Self {
value,
unit,
nan_identity,
}
}
pub fn value_in(&self, unit: DurationUnit) -> f64 {
self.value * self.unit.nanos() / unit.nanos()
}
}
impl PartialEq for Duration {
fn eq(&self, other: &Self) -> bool {
self.value == other.value && self.unit == other.unit
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DataSizeUnit {
Bytes,
Kilobytes,
Kibibytes,
Megabytes,
Mebibytes,
Gigabytes,
Gibibytes,
Terabytes,
Tebibytes,
Petabytes,
Pebibytes,
}
impl DataSizeUnit {
pub const ALL: [DataSizeUnit; 11] = [
DataSizeUnit::Bytes,
DataSizeUnit::Kilobytes,
DataSizeUnit::Kibibytes,
DataSizeUnit::Megabytes,
DataSizeUnit::Mebibytes,
DataSizeUnit::Gigabytes,
DataSizeUnit::Gibibytes,
DataSizeUnit::Terabytes,
DataSizeUnit::Tebibytes,
DataSizeUnit::Petabytes,
DataSizeUnit::Pebibytes,
];
pub fn symbol(self) -> &'static str {
match self {
DataSizeUnit::Bytes => "b",
DataSizeUnit::Kilobytes => "kb",
DataSizeUnit::Kibibytes => "kib",
DataSizeUnit::Megabytes => "mb",
DataSizeUnit::Mebibytes => "mib",
DataSizeUnit::Gigabytes => "gb",
DataSizeUnit::Gibibytes => "gib",
DataSizeUnit::Terabytes => "tb",
DataSizeUnit::Tebibytes => "tib",
DataSizeUnit::Petabytes => "pb",
DataSizeUnit::Pebibytes => "pib",
}
}
pub fn parse(symbol: &str) -> Option<Self> {
Self::ALL.into_iter().find(|unit| unit.symbol() == symbol)
}
pub fn bytes(self) -> i64 {
match self {
DataSizeUnit::Bytes => 1,
DataSizeUnit::Kilobytes => 1000,
DataSizeUnit::Kibibytes => 1024,
DataSizeUnit::Megabytes => 1000_i64.pow(2),
DataSizeUnit::Mebibytes => 1024_i64.pow(2),
DataSizeUnit::Gigabytes => 1000_i64.pow(3),
DataSizeUnit::Gibibytes => 1024_i64.pow(3),
DataSizeUnit::Terabytes => 1000_i64.pow(4),
DataSizeUnit::Tebibytes => 1024_i64.pow(4),
DataSizeUnit::Petabytes => 1000_i64.pow(5),
DataSizeUnit::Pebibytes => 1024_i64.pow(5),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct DataSize {
pub value: f64,
pub unit: DataSizeUnit,
nan_identity: u64,
}
impl DataSize {
pub(crate) fn new(value: f64, unit: DataSizeUnit) -> Self {
Self {
value,
unit,
nan_identity: NEXT_NAN_MAPPING_KEY.fetch_add(1, Ordering::Relaxed),
}
}
fn with_mapping_identity(value: f64, unit: DataSizeUnit, identity: &str) -> Self {
let (value, nan_identity) = mapping_nan_identity(identity)
.map(|(bits, nonce)| (f64::from_bits(bits), nonce))
.unwrap_or_else(|| (value, NEXT_NAN_MAPPING_KEY.fetch_add(1, Ordering::Relaxed)));
Self {
value,
unit,
nan_identity,
}
}
pub fn value_in(&self, unit: DataSizeUnit) -> f64 {
self.value * self.unit.bytes() as f64 / unit.bytes() as f64
}
}
impl PartialEq for DataSize {
fn eq(&self, other: &Self) -> bool {
self.value == other.value && self.unit == other.unit
}
}
pub struct Regex {
pattern: String,
compiled: fancy_regex::Regex,
}
impl Regex {
pub fn new(pattern: &str) -> Result<Self, String> {
let compiled = fancy_regex::RegexBuilder::new(&expand_quotes(pattern))
.build()
.map_err(|e| e.to_string())?;
Ok(Self {
pattern: pattern.to_string(),
compiled,
})
}
pub fn pattern(&self) -> &str {
&self.pattern
}
pub(crate) fn compiled(&self) -> &fancy_regex::Regex {
&self.compiled
}
}
fn expand_quotes(pattern: &str) -> std::borrow::Cow<'_, str> {
if !pattern.contains("\\Q") {
return pattern.into();
}
let mut out = String::with_capacity(pattern.len());
let mut rest = pattern;
while let Some(i) = rest.find('\\') {
out.push_str(&rest[..i]);
let escaped = &rest[i + 1..];
match escaped.chars().next() {
Some('Q') => {
let quoted = &escaped[1..];
let (literal, after) = match quoted.find("\\E") {
Some(end) => ("ed[..end], "ed[end + 2..]),
None => (quoted, ""),
};
out.push_str(&fancy_regex::escape(literal));
rest = after;
}
Some(c) => {
out.push('\\');
out.push(c);
rest = &escaped[c.len_utf8()..];
}
None => {
out.push('\\');
rest = "";
}
}
}
out.push_str(rest);
out.into()
}
impl std::fmt::Debug for Regex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Regex").field(&self.pattern).finish()
}
}
impl PartialEq for Regex {
fn eq(&self, other: &Self) -> bool {
self.pattern == other.pattern
}
}
impl Value {
pub fn type_name(&self) -> &'static str {
match self {
Value::Null => "Null",
Value::Bool(_) => "Boolean",
Value::Int(_) => "Int",
Value::Float(_) => "Float",
Value::String(_) => "String",
Value::Object(..) => "Dynamic",
Value::List(_) => "List",
Value::Lambda(params, ..) => match params.len() {
0 => "Function0",
1 => "Function1",
2 => "Function2",
3 => "Function3",
4 => "Function4",
_ => "Function5",
},
Value::Regex(_) => "Regex",
Value::Duration(_) => "Duration",
Value::DataSize(_) => "DataSize",
}
}
pub fn try_to_json(&self) -> Result<serde_json::Value, crate::Error> {
let mut unrenderable = None;
let json = self.json_checked(&mut unrenderable);
match unrenderable {
None => Ok(json),
Some(value) => Err(value.json_render_error()),
}
}
fn json_checked<'a>(&'a self, unrenderable: &mut Option<&'a Value>) -> serde_json::Value {
match self {
Value::Float(f) if !f.is_finite() => {
unrenderable.get_or_insert(self);
serde_json::Value::Null
}
Value::Regex(_) | Value::Duration(_) | Value::DataSize(_) => {
unrenderable.get_or_insert(self);
serde_json::Value::Null
}
Value::Object(map, _) => {
let mut obj = serde_json::Map::new();
for (k, v) in map.iter() {
if unrenderable.is_some() {
break;
}
obj.insert(
display_storage_key(k).to_string(),
v.json_checked(unrenderable),
);
}
serde_json::Value::Object(obj)
}
Value::List(items) => {
let mut array = Vec::with_capacity(items.len());
for item in items.iter() {
if unrenderable.is_some() {
break;
}
array.push(item.json_checked(unrenderable));
}
serde_json::Value::Array(array)
}
_ => self.to_json(),
}
}
fn json_render_error(&self) -> crate::Error {
if let Value::Float(f) = self {
return crate::Error::Eval(format!(
"Cannot render value `{}` as JSON.",
if f.is_nan() {
"NaN"
} else if *f > 0.0 {
"∞"
} else {
"-∞"
}
));
}
crate::Error::Eval(format!(
"Cannot render value of type `{}` as JSON.\nValue: {}",
self.type_name(),
crate::eval::stdlib::render_value(self)
))
}
pub fn to_json(&self) -> serde_json::Value {
match self {
Value::Null => serde_json::Value::Null,
Value::Bool(b) => json!(b),
Value::Int(n) => json!(n),
Value::Float(f) => json!(f),
Value::String(s) => serde_json::Value::String(s.to_string()),
Value::Object(map, source) => {
let mut obj = serde_json::Map::new();
let is_method = |name: &str| {
source.as_ref().is_some_and(|source| {
source.entries.iter().any(|entry| {
matches!(entry, crate::parser::Entry::Property(prop)
if prop.is_method && &*prop.name == name)
})
})
};
for (k, v) in map.iter() {
if matches!(v, Value::Lambda(..)) && is_method(k) {
continue;
}
obj.insert(display_storage_key(k).to_string(), v.to_json());
}
serde_json::Value::Object(obj)
}
Value::List(items) => {
serde_json::Value::Array(items.iter().map(|v| v.to_json()).collect())
}
Value::Lambda(..) => json!("<lambda>"),
Value::Regex(regex) => json!({ "_type": "regex", "pattern": regex.pattern() }),
Value::Duration(_) | Value::DataSize(_) => {
json!(crate::eval::stdlib::render_value(self))
}
}
}
pub fn as_str(&self) -> Option<&str> {
if let Value::String(s) = self {
Some(s)
} else {
None
}
}
pub fn as_object_mut(&mut self) -> Option<&mut ObjectMap> {
if let Value::Object(m, _) = self {
Some(Arc::make_mut(m))
} else {
None
}
}
pub fn merge(&mut self, other: Value) {
match (self, other) {
(Value::Object(base, _), Value::Object(overlay, _)) => {
let base_map = Arc::make_mut(base);
for (k, v) in overlay.iter() {
let storage_key = base_map
.get_key_value(k)
.map(|(key, _)| key.clone())
.or_else(|| {
base_map
.keys()
.find(|stored| mapping_storage_keys_equal(stored, k))
.cloned()
})
.unwrap_or_else(|| k.clone());
base_map.insert(storage_key, v.clone());
}
}
(s, other) => *s = other,
}
}
}
impl From<serde_json::Value> for Value {
fn from(v: serde_json::Value) -> Self {
match v {
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(b) => Value::Bool(b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Value::Int(i)
} else {
Value::Float(n.as_f64().unwrap_or(f64::NAN))
}
}
serde_json::Value::String(s) => Value::String(s.into()),
serde_json::Value::Array(a) => {
let items: Vec<Value> = a.into_iter().map(Value::from).collect();
Value::List(ListValue::new(ListKind::Listing, items))
}
serde_json::Value::Object(o) => {
let mut map = ObjectMap::default();
for (k, v) in o {
map.insert(k.into(), Value::from(v));
}
Value::Object(Arc::new(map), None)
}
}
}
}