use std::{
borrow::Cow,
fmt::{self, Write as _},
};
use ltk_hash::{BinHash, Hash as _};
use super::{MapKey, ValuePath, ValueSegment};
use crate::{
path::{parse, FieldNames, KeyLiteral, PropertyPath, PropertyPathError, PropertyPathErrorKind},
property::Kind,
walk::Leaf,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NamedPath {
pub text: String,
pub named: usize,
pub unnamed: usize,
}
impl NamedPath {
#[must_use]
pub fn is_complete(&self) -> bool {
self.unnamed == 0
}
}
impl fmt::Display for NamedPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.text)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("{kind} (segment {segment})")]
pub struct Nameless {
pub segment: usize,
pub kind: NamelessKind,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum NamelessKind {
#[error("no name for field {field:08x}")]
Field {
field: BinHash,
class: Option<BinHash>,
},
#[error("no literal for a {0:?} key")]
Key(Kind),
#[error("not a property path: {0}")]
Path(PropertyPathError),
}
impl ValuePath {
pub fn to_property_path(&self, names: &dyn FieldNames) -> Result<PropertyPath, Nameless> {
let grammar = |segment, error| Nameless {
segment,
kind: NamelessKind::Path(error),
};
let no_property = |segment| {
grammar(
segment,
PropertyPathError::new(0, PropertyPathErrorKind::EmptySegment),
)
};
let mut path: Option<PropertyPath> = None;
let mut fields = self.fields();
for (segment, item) in self.segments.iter().enumerate() {
match item {
ValueSegment::Field(_) => {
let (field, class) = fields.next().expect("one class per field segment");
let name = spell_field(names, field, class).ok_or(Nameless {
segment,
kind: NamelessKind::Field { field, class },
})?;
match &mut path {
Some(path) => path.push_field(&name),
None => PropertyPath::new(name.into_owned()).map(|new| path = Some(new)),
}
.map_err(|error| grammar(segment, error))?;
}
ValueSegment::Index(index) => {
let path = path.as_mut().ok_or_else(|| no_property(segment))?;
let index = u32::try_from(*index).map_err(|_| {
grammar(
segment,
PropertyPathError::new(path.len(), PropertyPathErrorKind::InvalidIndex),
)
})?;
path.push_index(index)
.map_err(|error| grammar(segment, error))?;
}
ValueSegment::Key(key) => {
let path = path.as_mut().ok_or_else(|| no_property(segment))?;
let number;
let literal = match key {
MapKey::Bool(v) => KeyLiteral::Bool(*v),
MapKey::String(v) => KeyLiteral::String(Cow::Borrowed(v)),
MapKey::F32(v) if v.get().is_finite() => {
number = v.get().to_string();
KeyLiteral::Number(&number)
}
other => {
number = decimal(other).ok_or(Nameless {
segment,
kind: NamelessKind::Key(other.kind()),
})?;
KeyLiteral::Number(&number)
}
};
path.push_key(&literal)
.map_err(|error| grammar(segment, error))?;
}
}
}
path.ok_or_else(|| no_property(0))
}
#[must_use]
pub fn to_named(&self, names: &dyn FieldNames) -> NamedPath {
let mut named = NamedPath {
text: String::new(),
named: 0,
unnamed: 0,
};
let mut fields = self.fields();
for (i, segment) in self.segments.iter().enumerate() {
let spelled = match segment {
ValueSegment::Field(_) => {
let (field, class) = fields.next().expect("one class per field segment");
if i > 0 {
named.text.push('.');
}
let name = spell_field(names, field, class);
match &name {
Some(name) => named.text.push_str(name),
None => push_fmt(&mut named.text, format_args!("{field:08x}")),
}
Some(name.is_some())
}
ValueSegment::Index(_) => {
push_fmt(&mut named.text, format_args!("{segment}"));
None
}
ValueSegment::Key(MapKey::Hash(hash)) => {
let text = hash
.try_as_bin_hash()
.and_then(|hash| spell_hash(names, hash));
let key = match &text {
Some(text) => Leaf::String(text),
None => Leaf::Hash(*hash),
};
push_fmt(&mut named.text, format_args!("{{{}}}", KeyText(key)));
Some(text.is_some())
}
ValueSegment::Key(_) => {
push_fmt(&mut named.text, format_args!("{segment}"));
None
}
};
match spelled {
Some(true) => named.named += 1,
Some(false) => named.unnamed += 1,
None => {}
}
}
named
}
}
fn spell_field(
names: &dyn FieldNames,
field: BinHash,
class: Option<BinHash>,
) -> Option<Cow<'_, str>> {
names.field(field, class).filter(|name| {
!name.is_empty()
&& name.chars().all(parse::is_name_char)
&& BinHash::hash_str(name.as_ref()) == field
})
}
fn spell_hash(names: &dyn FieldNames, hash: BinHash) -> Option<Cow<'_, str>> {
names
.hash(hash)
.filter(|text| BinHash::hash_str(text.as_ref()) == hash)
}
fn decimal(key: &MapKey) -> Option<String> {
Some(match key {
MapKey::I8(v) => v.to_string(),
MapKey::U8(v) => v.to_string(),
MapKey::I16(v) => v.to_string(),
MapKey::U16(v) => v.to_string(),
MapKey::I32(v) => v.to_string(),
MapKey::U32(v) => v.to_string(),
MapKey::I64(v) => v.to_string(),
MapKey::U64(v) => v.to_string(),
MapKey::Hash(v) => v.as_u64().to_string(),
MapKey::File(v) => v.0.to_string(),
MapKey::None
| MapKey::Bool(_)
| MapKey::F32(_)
| MapKey::Vector2(_)
| MapKey::Vector3(_)
| MapKey::Vector4(_)
| MapKey::Matrix44(_)
| MapKey::Color(_)
| MapKey::String(_) => return None,
})
}
struct KeyText<'a>(Leaf<'a>);
impl fmt::Display for KeyText<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.write_key(f)
}
}
fn push_fmt(text: &mut String, args: fmt::Arguments<'_>) {
text.write_fmt(args)
.expect("writing to a String never fails");
}