use std::fmt;
use indexmap::IndexMap;
use ltk_hash::{BinHash, Hash as _, WadHash};
use crate::{
path::{KeyLiteral, PropertyPath, Segment, Subscript},
property::{values, Kind},
Bin, BinObject, PropertyValueEnum, ValueSlot,
};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ValueShape {
pub kind: Kind,
pub item_kind: Option<Kind>,
pub key_kind: Option<Kind>,
pub class: Option<BinHash>,
}
impl ValueShape {
#[must_use]
pub fn of<M>(value: &PropertyValueEnum<M>) -> Self {
use PropertyValueEnum as V;
let (item_kind, key_kind, class) = match value {
V::Container(list) => (Some(list.item_kind()), None, None),
V::UnorderedContainer(values::UnorderedContainer(list)) => {
(Some(list.item_kind()), None, None)
}
V::Optional(option) => (Some(option.item_kind()), None, None),
V::Map(map) => (Some(map.value_kind()), Some(map.key_kind()), None),
V::Embedded(values::Embedded(embed)) => (None, None, Some(embed.class_hash)),
_ => (None, None, None),
};
Self {
kind: value.kind(),
item_kind,
key_kind,
class,
}
}
#[must_use]
#[inline]
pub fn matches(&self, other: &Self) -> bool {
self == other
}
}
impl fmt::Display for ValueShape {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.kind)?;
match (self.key_kind, self.item_kind) {
(Some(key), Some(item)) => write!(f, "[{key:?}, {item:?}]")?,
(None, Some(item)) => write!(f, "[{item:?}]")?,
_ => {}
}
match self.class {
Some(class) => write!(f, " {class:08x}"),
None => Ok(()),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("{kind} (segment {segment})")]
pub struct ResolveError {
segment: usize,
kind: ResolveErrorKind,
}
impl ResolveError {
pub(crate) fn new(segment: usize, kind: ResolveErrorKind) -> Self {
Self { segment, kind }
}
#[must_use]
#[inline]
pub fn segment(&self) -> usize {
self.segment
}
#[must_use]
#[inline]
pub fn kind(&self) -> ResolveErrorKind {
self.kind
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum ResolveErrorKind {
#[error("no object {0:08x}")]
MissingObject(BinHash),
#[error("no property {0:08x}")]
MissingProperty(BinHash),
#[error("the pointer is null")]
NullPointer,
#[error("cannot descend into a {0:?}")]
CannotDescend(Kind),
#[error("a {0:?} cannot be subscripted that way")]
NotIndexable(Kind),
#[error("index {index} is out of range, the length is {len}")]
IndexOutOfRange {
index: u32,
len: usize,
},
#[error("the key does not convert to {0:?}")]
InvalidKey(Kind),
#[error("no entry has that key")]
KeyNotFound,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum PatchError {
#[error(transparent)]
Resolve(#[from] ResolveError),
#[error("type mismatch: the property is {expected}, the patch carries {found}")]
TypeMismatch {
expected: ValueShape,
found: ValueShape,
},
}
#[derive(Debug, Clone, Copy)]
enum Slot {
Item(usize),
OptionValue,
Entry(usize),
}
const MISSING: &str = "a Slot only ever names a slot that exists";
type Properties<M> = IndexMap<BinHash, PropertyValueEnum<M>>;
type Landing<'a, 'p, M> = (&'a Properties<M>, Segment<'p>, usize);
type LandingMut<'a, 'p, M> = (&'a mut Properties<M>, Segment<'p>, usize);
fn descend_check<M>(value: &PropertyValueEnum<M>) -> Result<(), ResolveErrorKind> {
use PropertyValueEnum as V;
match value {
V::Struct(pointer) if *pointer.class_hash == 0 => Err(ResolveErrorKind::NullPointer),
V::Struct(_) | V::Embedded(_) => Ok(()),
other => Err(ResolveErrorKind::CannotDescend(other.kind())),
}
}
fn properties_of<M>(value: &PropertyValueEnum<M>) -> Option<&Properties<M>> {
use PropertyValueEnum as V;
match value {
V::Struct(pointer) if *pointer.class_hash == 0 => None,
V::Struct(inner) | V::Embedded(values::Embedded(inner)) => Some(&inner.properties),
_ => None,
}
}
fn properties_of_mut<M>(value: &mut PropertyValueEnum<M>) -> Option<&mut Properties<M>> {
use PropertyValueEnum as V;
match value {
V::Struct(pointer) if *pointer.class_hash == 0 => None,
V::Struct(inner) | V::Embedded(values::Embedded(inner)) => Some(&mut inner.properties),
_ => None,
}
}
fn slot_for<M>(
value: &PropertyValueEnum<M>,
subscript: &Subscript<'_>,
) -> Result<Slot, ResolveErrorKind> {
use PropertyValueEnum as V;
match (value, subscript) {
(V::Container(list), Subscript::Index(index))
| (V::UnorderedContainer(values::UnorderedContainer(list)), Subscript::Index(index)) => {
match (*index as usize) < list.len() {
true => Ok(Slot::Item(*index as usize)),
false => Err(ResolveErrorKind::IndexOutOfRange {
index: *index,
len: list.len(),
}),
}
}
(V::Optional(option), Subscript::Index(index)) => match (*index, option.is_some()) {
(0, true) => Ok(Slot::OptionValue),
_ => Err(ResolveErrorKind::IndexOutOfRange {
index: *index,
len: usize::from(option.is_some()),
}),
},
(V::Map(map), Subscript::Key(literal)) => {
let wanted = key_as(map.key_kind(), literal)
.ok_or(ResolveErrorKind::InvalidKey(map.key_kind()))?;
map.entries()
.iter()
.position(|(key, _)| key_eq(key, &wanted))
.map(Slot::Entry)
.ok_or(ResolveErrorKind::KeyNotFound)
}
(value, _) => Err(ResolveErrorKind::NotIndexable(value.kind())),
}
}
fn take<M>(value: &PropertyValueEnum<M>, slot: Slot) -> &PropertyValueEnum<M> {
use PropertyValueEnum as V;
match (value, slot) {
(V::Container(list), Slot::Item(index))
| (V::UnorderedContainer(values::UnorderedContainer(list)), Slot::Item(index)) => {
list.get(index).expect(MISSING)
}
(V::Optional(option), Slot::OptionValue) => option.value().expect(MISSING),
(V::Map(map), Slot::Entry(index)) => &map.entries().get(index).expect(MISSING).1,
_ => unreachable!("{MISSING}"),
}
}
fn take_mut<M>(value: &mut PropertyValueEnum<M>, slot: Slot) -> ValueSlot<'_, M> {
use PropertyValueEnum as V;
match (value, slot) {
(V::Container(list), Slot::Item(index))
| (V::UnorderedContainer(values::UnorderedContainer(list)), Slot::Item(index)) => {
list.slot(index).expect(MISSING)
}
(V::Optional(option), Slot::OptionValue) => option.slot().expect(MISSING),
(V::Map(map), Slot::Entry(index)) => map.slot(index).expect(MISSING),
_ => unreachable!("{MISSING}"),
}
}
fn key_as(kind: Kind, literal: &KeyLiteral<'_>) -> Option<PropertyValueEnum> {
use KeyLiteral as L;
Some(match (kind, literal) {
(Kind::Bool, L::Bool(value)) => values::Bool::new(*value).into(),
(Kind::BitBool, L::Bool(value)) => values::BitBool::new(*value).into(),
(Kind::I8, L::Number(text)) => values::I8::new(text.parse().ok()?).into(),
(Kind::U8, L::Number(text)) => values::U8::new(text.parse().ok()?).into(),
(Kind::I16, L::Number(text)) => values::I16::new(text.parse().ok()?).into(),
(Kind::U16, L::Number(text)) => values::U16::new(text.parse().ok()?).into(),
(Kind::I32, L::Number(text)) => values::I32::new(text.parse().ok()?).into(),
(Kind::U32, L::Number(text)) => values::U32::new(text.parse().ok()?).into(),
(Kind::I64, L::Number(text)) => values::I64::new(text.parse().ok()?).into(),
(Kind::U64, L::Number(text)) => values::U64::new(text.parse().ok()?).into(),
(Kind::F32, L::Number(text)) => values::F32::new(text.parse().ok()?).into(),
(Kind::String, L::String(text)) => values::String::new(text.to_string()).into(),
(Kind::Hash, L::String(text)) => values::Hash::new(BinHash::hash_str(text)).into(),
(Kind::Hash, L::Number(text)) => values::Hash::new(text.parse::<u32>().ok()?).into(),
(Kind::WadChunkLink, L::String(text)) => {
values::WadChunkLink::new(WadHash::hash_str(text)).into()
}
(Kind::WadChunkLink, L::Number(text)) => {
values::WadChunkLink::new(text.parse::<u64>().ok()?).into()
}
_ => return None,
})
}
macro_rules! match_key {
($key:expr, $wanted:expr, [$($variant:ident,)*]) => {
match ($key, $wanted) {
$((PropertyValueEnum::$variant(key), PropertyValueEnum::$variant(wanted)) => {
key.value == wanted.value
})*
_ => false,
}
};
}
fn key_eq<M>(key: &PropertyValueEnum<M>, wanted: &PropertyValueEnum) -> bool {
match_key!(
key,
wanted,
[
Bool,
BitBool,
I8,
U8,
I16,
U16,
I32,
U32,
I64,
U64,
F32,
String,
Hash,
WadChunkLink,
]
)
}
fn step<'a, M>(
properties: &'a Properties<M>,
segment: &Segment<'_>,
index: usize,
) -> Result<&'a PropertyValueEnum<M>, ResolveError> {
let name_hash = segment.name_hash();
let value = properties
.get(&name_hash)
.ok_or_else(|| ResolveError::new(index, ResolveErrorKind::MissingProperty(name_hash)))?;
match &segment.subscript {
None => Ok(value),
Some(subscript) => {
let slot = slot_for(value, subscript).map_err(|kind| ResolveError::new(index, kind))?;
Ok(take(value, slot))
}
}
}
fn step_mut<'a, M>(
properties: &'a mut Properties<M>,
segment: &Segment<'_>,
index: usize,
) -> Result<ValueSlot<'a, M>, ResolveError> {
let name_hash = segment.name_hash();
let value = properties
.get_mut(&name_hash)
.ok_or_else(|| ResolveError::new(index, ResolveErrorKind::MissingProperty(name_hash)))?;
match &segment.subscript {
None => Ok(ValueSlot::free(value)),
Some(subscript) => {
let slot = slot_for(value, subscript).map_err(|kind| ResolveError::new(index, kind))?;
Ok(take_mut(value, slot))
}
}
}
fn locate<'a, 'p, M>(
properties: &'a Properties<M>,
path: &'p PropertyPath,
) -> Result<Landing<'a, 'p, M>, ResolveError> {
let mut properties = properties;
let mut segments = path.segments().enumerate().peekable();
loop {
let (index, segment) = segments
.next()
.expect("a PropertyPath has at least one segment");
if segments.peek().is_none() {
return Ok((properties, segment, index));
}
let value = step(properties, &segment, index)?;
descend_check(value).map_err(|kind| ResolveError::new(index + 1, kind))?;
properties = properties_of(value).expect("descend_check accepted this value");
}
}
fn locate_mut<'a, 'p, M>(
properties: &'a mut Properties<M>,
path: &'p PropertyPath,
) -> Result<LandingMut<'a, 'p, M>, ResolveError> {
let mut properties = properties;
let mut segments = path.segments().enumerate().peekable();
loop {
let (index, segment) = segments
.next()
.expect("a PropertyPath has at least one segment");
if segments.peek().is_none() {
return Ok((properties, segment, index));
}
let value = step_mut(properties, &segment, index)?.into_inner();
descend_check(value).map_err(|kind| ResolveError::new(index + 1, kind))?;
properties = properties_of_mut(value).expect("descend_check accepted this value");
}
}
pub(crate) fn walk<'a, M>(
properties: &'a Properties<M>,
path: &PropertyPath,
) -> Result<&'a PropertyValueEnum<M>, ResolveError> {
let (properties, segment, index) = locate(properties, path)?;
step(properties, &segment, index)
}
pub(crate) fn walk_mut<'a, M>(
properties: &'a mut Properties<M>,
path: &PropertyPath,
) -> Result<ValueSlot<'a, M>, ResolveError> {
let (properties, segment, index) = locate_mut(properties, path)?;
step_mut(properties, &segment, index)
}
pub(crate) fn patch_in<M>(
properties: &mut Properties<M>,
path: &PropertyPath,
value: PropertyValueEnum<M>,
) -> Result<Option<PropertyValueEnum<M>>, PatchError> {
let (properties, segment, index) = locate_mut(properties, path)?;
let name_hash = segment.name_hash();
if !properties.contains_key(&name_hash) {
return match segment.subscript {
None => {
properties.insert(name_hash, value);
Ok(None)
}
Some(_) => {
Err(ResolveError::new(index, ResolveErrorKind::MissingProperty(name_hash)).into())
}
};
}
let mut slot = step_mut(properties, &segment, index)?;
let expected = ValueShape::of(slot.get());
let found = ValueShape::of(&value);
if !expected.matches(&found) {
return Err(PatchError::TypeMismatch { expected, found });
}
Ok(Some(slot.set(value).expect(
"the shapes match, so the kinds do and no slot can reject the value",
)))
}
pub(crate) fn probe<M>(
properties: &Properties<M>,
path: &PropertyPath,
found: ValueShape,
) -> Result<bool, PatchError> {
let (properties, segment, index) = locate(properties, path)?;
let name_hash = segment.name_hash();
let Some(value) = properties.get(&name_hash) else {
return match segment.subscript {
None => Ok(true),
Some(_) => {
Err(ResolveError::new(index, ResolveErrorKind::MissingProperty(name_hash)).into())
}
};
};
let leaf = match &segment.subscript {
None => value,
Some(subscript) => {
let slot = slot_for(value, subscript).map_err(|kind| ResolveError::new(index, kind))?;
take(value, slot)
}
};
let expected = ValueShape::of(leaf);
match expected.matches(&found) {
true => Ok(false),
false => Err(PatchError::TypeMismatch { expected, found }),
}
}
impl<M> BinObject<M> {
pub fn resolve(&self, path: &PropertyPath) -> Result<&PropertyValueEnum<M>, ResolveError> {
walk(&self.properties, path)
}
pub fn resolve_mut(&mut self, path: &PropertyPath) -> Result<ValueSlot<'_, M>, ResolveError> {
walk_mut(&mut self.properties, path)
}
pub fn patch(
&mut self,
path: &PropertyPath,
value: PropertyValueEnum<M>,
) -> Result<Option<PropertyValueEnum<M>>, PatchError> {
patch_in(&mut self.properties, path, value)
}
pub(crate) fn probe(
&self,
path: &PropertyPath,
value: &PropertyValueEnum<M>,
) -> Result<bool, PatchError> {
probe(&self.properties, path, ValueShape::of(value))
}
}
impl<M> values::Struct<M> {
pub fn resolve(&self, path: &PropertyPath) -> Result<&PropertyValueEnum<M>, ResolveError> {
self.null_check()?;
walk(&self.properties, path)
}
pub fn resolve_mut(&mut self, path: &PropertyPath) -> Result<ValueSlot<'_, M>, ResolveError> {
self.null_check()?;
walk_mut(&mut self.properties, path)
}
pub fn patch(
&mut self,
path: &PropertyPath,
value: PropertyValueEnum<M>,
) -> Result<Option<PropertyValueEnum<M>>, PatchError> {
self.null_check()?;
patch_in(&mut self.properties, path, value)
}
fn null_check(&self) -> Result<(), ResolveError> {
match *self.class_hash {
0 => Err(ResolveError::new(0, ResolveErrorKind::NullPointer)),
_ => Ok(()),
}
}
}
impl<M> values::Embedded<M> {
pub fn resolve(&self, path: &PropertyPath) -> Result<&PropertyValueEnum<M>, ResolveError> {
walk(&self.0.properties, path)
}
pub fn resolve_mut(&mut self, path: &PropertyPath) -> Result<ValueSlot<'_, M>, ResolveError> {
walk_mut(&mut self.0.properties, path)
}
pub fn patch(
&mut self,
path: &PropertyPath,
value: PropertyValueEnum<M>,
) -> Result<Option<PropertyValueEnum<M>>, PatchError> {
patch_in(&mut self.0.properties, path, value)
}
}
impl<M> PropertyValueEnum<M> {
pub fn resolve(&self, path: &PropertyPath) -> Result<&PropertyValueEnum<M>, ResolveError> {
descend_check(self).map_err(|kind| ResolveError::new(0, kind))?;
walk(
properties_of(self).expect("descend_check accepted this value"),
path,
)
}
pub fn resolve_mut(&mut self, path: &PropertyPath) -> Result<ValueSlot<'_, M>, ResolveError> {
descend_check(self).map_err(|kind| ResolveError::new(0, kind))?;
walk_mut(
properties_of_mut(self).expect("descend_check accepted this value"),
path,
)
}
}
impl<M> Bin<M> {
pub fn resolve(
&self,
object_hash: impl Into<BinHash>,
path: &PropertyPath,
) -> Result<&PropertyValueEnum<M>, ResolveError> {
self.object(object_hash)?.resolve(path)
}
pub fn resolve_mut(
&mut self,
object_hash: impl Into<BinHash>,
path: &PropertyPath,
) -> Result<ValueSlot<'_, M>, ResolveError> {
let object_hash = object_hash.into();
self.objects
.get_mut(&object_hash)
.ok_or_else(|| ResolveError::new(0, ResolveErrorKind::MissingObject(object_hash)))?
.resolve_mut(path)
}
pub fn patch(
&mut self,
object_hash: impl Into<BinHash>,
path: &PropertyPath,
value: PropertyValueEnum<M>,
) -> Result<Option<PropertyValueEnum<M>>, PatchError> {
let object_hash = object_hash.into();
let object = self
.objects
.get_mut(&object_hash)
.ok_or_else(|| ResolveError::new(0, ResolveErrorKind::MissingObject(object_hash)))?;
object.patch(path, value)
}
fn object(&self, object_hash: impl Into<BinHash>) -> Result<&BinObject<M>, ResolveError> {
let object_hash = object_hash.into();
self.objects
.get(&object_hash)
.ok_or_else(|| ResolveError::new(0, ResolveErrorKind::MissingObject(object_hash)))
}
}