ltk_meta 0.9.1

Metadata formats and utilities for League Toolkit
Documentation
//! The tree the walk sees: two sealed traits, a declaration, a child segment, and a decoded leaf.

use std::fmt;

use glam::{Mat4, Vec2, Vec3, Vec4};
use ltk_hash::{BinHash, HashValue, WadHash};
use ltk_primitives::Color;

use crate::{path::MapKey, property::values, property::Kind, Error, PropertyValueEnum};

pub(crate) mod sealed {
    pub trait Sealed {}
}

/// Whether a [`Kind`] is a node kind.
///
/// Sealed: implemented for [`Kind`] and by nothing else. A node kind is `Struct` or
/// `Embedded`. The walk asks nothing else of a kind.
///
/// # Examples
///
/// ```
/// use ltk_meta::{walk::TreeKind, PropertyKind};
///
/// assert!(PropertyKind::Struct.is_node());
/// assert!(PropertyKind::Embedded.is_node());
/// assert!(!PropertyKind::ObjectLink.is_node());
/// assert!(!PropertyKind::Container.is_node());
/// ```
pub trait TreeKind: Copy + sealed::Sealed {
    /// Whether a value of this kind is a node: `Struct` or `Embedded`.
    ///
    /// [`Kind::is_primitive`] answers a different question, which kinds a value model treats
    /// as leaves. The two are not complements: `ObjectLink` and `BitBool` are neither
    /// primitive nor a node.
    fn is_node(self) -> bool;
}

impl sealed::Sealed for Kind {}
impl TreeKind for Kind {
    #[inline]
    fn is_node(self) -> bool {
        matches!(self, Kind::Struct | Kind::Embedded)
    }
}

/// A value the walk can cross.
///
/// Sealed: implemented for `&'a PropertyValueEnum` and for [`RawValue<'a>`], and by
/// nothing else. A visitor written against this trait runs over either tree.
///
/// [`RawValue<'a>`]: super::RawValue
pub trait TreeValue<'a>: Copy + sealed::Sealed {
    /// The node type this tree's `Struct` and `Embedded` values are.
    type Node: TreeNode<'a, Value = Self>;
    /// The values inside a container, optional or map, each with the child segment reaching it.
    type Children: Iterator<Item = Result<(ChildSegment<Self>, Self), Error>>;

    /// The kind this value is.
    fn kind(&self) -> Kind;

    /// What this value's header declares: its kind, item and key kinds, class and count.
    ///
    /// Over a view, a leaf is not read and a complex value's contents are not decoded. A
    /// container's items, a map's entries and a node's properties stay unread.
    ///
    /// # Errors
    ///
    /// Over a view, a header that does not decode: a truncated header, a kind byte that is no
    /// kind, [`Error::InvalidNesting`] for a container item kind, or [`Error::InvalidKeyType`] for
    /// a key kind no map is keyed by. The owned tree never fails.
    fn declaration(&self) -> Result<Declaration, Error>;

    /// Whether this value is a node or can contain one.
    ///
    /// True for a `Struct` or `Embedded` whose class hash is not 0, and for a container,
    /// optional or map whose item kind [`TreeKind::is_node`]. An empty optional or container
    /// of a node kind answers true. Entering it costs nothing. The answer is read off
    /// [`TreeValue::declaration`].
    ///
    /// # Errors
    ///
    /// Those of [`TreeValue::declaration`]. The owned tree never fails.
    fn can_contain_node(&self) -> Result<bool, Error> {
        let declaration = self.declaration()?;
        Ok(match declaration.class {
            Some(class) => *class != 0,
            None => declaration.item_kind.is_some_and(TreeKind::is_node),
        })
    }

    /// This value as a node, if it is a `Struct` or `Embedded` with a class hash that is not 0.
    ///
    /// # Errors
    ///
    /// Over a view, a header that does not decode. The owned tree never fails.
    fn as_node(&self) -> Result<Option<Self::Node>, Error>;

    /// The values inside this one, with the segment reaching each. Empty for a leaf and for a
    /// node. A node's contents are its properties.
    ///
    /// # Errors
    ///
    /// Over a view, a header that does not decode. The owned tree never fails.
    fn children(&self) -> Result<Self::Children, Error>;

    /// This value decoded, if it is a leaf kind. `None` for every complex kind.
    ///
    /// # Errors
    ///
    /// Over a view, a leaf that does not decode. The owned tree never fails.
    fn as_leaf(&self) -> Result<Option<Leaf<'a>>, Error>;

    /// This value as a map key. Every kind [`Kind::is_valid_map_key`] admits converts.
    ///
    /// # Errors
    ///
    /// [`Error::InvalidKeyType`] for a kind no map is keyed by. Over a view, a leaf that does not
    /// decode.
    fn map_key(&self) -> Result<MapKey, Error> {
        self.as_leaf()?
            .and_then(MapKey::from_leaf)
            .ok_or(Error::InvalidKeyType(self.kind()))
    }

    /// The whole value, owned. Allocates. A visitor reaches for it for a subtree, not for a
    /// leaf.
    ///
    /// # Errors
    ///
    /// Over a view, whatever the eager reader raises for the same bytes. The owned tree never
    /// fails.
    fn to_value(&self) -> Result<PropertyValueEnum, Error>;
}

/// A node the walk can visit: a class and properties.
///
/// Sealed: implemented for the owned tree's node, [`NodeRef`](super::NodeRef), and for
/// [`StructView<'a>`]. An object's root is a `StructView` over the same bytes.
///
/// [`StructView<'a>`]: crate::stream::StructView
pub trait TreeNode<'a>: Copy + sealed::Sealed {
    /// The value type of this tree.
    type Value: TreeValue<'a, Node = Self>;
    /// The properties in file order. A view's kind byte can fail to decode. Items are
    /// `Result`. The owned tree never fails.
    type Properties: Iterator<Item = Result<(BinHash, Self::Value), Error>>;

    /// The class hash this node carries.
    fn class_hash(&self) -> BinHash;

    /// The properties, in file order.
    fn properties(&self) -> Self::Properties;

    /// One property by field hash. The owned tree looks it up by key. The view scans in
    /// place.
    ///
    /// # Errors
    ///
    /// Over a view, a kind byte that does not decode before the property is reached. The
    /// owned tree never fails.
    fn get(&self, field: BinHash) -> Result<Option<Self::Value>, Error>;

    /// The whole node, owned, as a `Struct` carrying this class and every property.
    /// Allocates. The object's path hash is not part of a `Struct`. A root's path hash is
    /// [`Node::object_hash`](super::Node::object_hash).
    ///
    /// # Errors
    ///
    /// Over a view, whatever the eager reader raises for the same bytes. The owned tree never
    /// fails.
    fn to_struct(&self) -> Result<values::Struct, Error>;
}

/// What a value's header declares: its kind, item and key kinds, class and count.
///
/// [`TreeValue::declaration`] returns one over either tree. Each field is `None` for a kind
/// that declares nothing of it. A pointer's class is recorded, 0 for the null pointer.
/// [`ValueShape`] converts from a declaration and leaves a pointer's class and the count out.
///
/// Non-exhaustive: built by the crate and read by field.
///
/// # Examples
///
/// ```
/// use ltk_meta::{property::{values, Kind}, walk::TreeValue, PropertyValueEnum};
///
/// let empty: PropertyValueEnum = values::Optional::empty(Kind::String)?.into();
/// let declaration = (&empty).declaration()?;
///
/// assert_eq!(declaration.kind, Kind::Optional);
/// assert_eq!(declaration.item_kind, Some(Kind::String));
/// assert_eq!(declaration.count, Some(0));
/// # Ok::<(), ltk_meta::Error>(())
/// ```
///
/// [`ValueShape`]: crate::path::ValueShape
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Declaration {
    /// The kind of the value.
    pub kind: Kind,
    /// For a container or an optional the item kind, for a map the value kind.
    pub item_kind: Option<Kind>,
    /// For a map the key kind.
    pub key_kind: Option<Kind>,
    /// For a `Struct` or an `Embedded` the class it carries. 0 is the null pointer.
    pub class: Option<BinHash>,
    /// For a container or a map the number of items it holds, and for an optional 0 or 1.
    pub count: Option<usize>,
}

impl Declaration {
    /// A value of `kind` declaring nothing else: the declaration of a leaf.
    pub(crate) const fn bare(kind: Kind) -> Self {
        Self {
            kind,
            item_kind: None,
            key_kind: None,
            class: None,
            count: None,
        }
    }
}

/// The segment from a container, optional or map to one value inside it.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ChildSegment<V> {
    /// A container element by position, or the value of a present optional, which is 0.
    Index(usize),
    /// A map entry, by its key value.
    Key(V),
}

/// A leaf, decoded and borrowed.
///
/// The client's names for the tags, not the wire enum's: `File` is [`Kind::WadChunkLink`],
/// `Link` is [`Kind::ObjectLink`], `Flag` is [`Kind::BitBool`].
///
/// Non-exhaustive: the set of leaf kinds is the game's. A match outside this crate carries a
/// wildcard arm.
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum Leaf<'a> {
    /// [`Kind::None`].
    None,
    /// [`Kind::Bool`].
    Bool(bool),
    /// [`Kind::I8`].
    I8(i8),
    /// [`Kind::U8`].
    U8(u8),
    /// [`Kind::I16`].
    I16(i16),
    /// [`Kind::U16`].
    U16(u16),
    /// [`Kind::I32`].
    I32(i32),
    /// [`Kind::U32`].
    U32(u32),
    /// [`Kind::I64`].
    I64(i64),
    /// [`Kind::U64`].
    U64(u64),
    /// [`Kind::F32`].
    F32(f32),
    /// [`Kind::Vector2`].
    Vector2(Vec2),
    /// [`Kind::Vector3`].
    Vector3(Vec3),
    /// [`Kind::Vector4`].
    Vector4(Vec4),
    /// [`Kind::Matrix44`].
    Matrix44(Mat4),
    /// [`Kind::Color`].
    Color(Color<u8>),
    /// [`Kind::String`].
    String(&'a str),
    /// [`Kind::Hash`]. The value includes its stored width.
    Hash(HashValue),
    /// [`Kind::WadChunkLink`].
    File(WadHash),
    /// [`Kind::ObjectLink`].
    Link(BinHash),
    /// [`Kind::BitBool`].
    Flag(bool),
}

impl Leaf<'_> {
    /// The kind this leaf is.
    #[must_use]
    pub fn kind(&self) -> Kind {
        match self {
            Self::None => Kind::None,
            Self::Bool(_) => Kind::Bool,
            Self::I8(_) => Kind::I8,
            Self::U8(_) => Kind::U8,
            Self::I16(_) => Kind::I16,
            Self::U16(_) => Kind::U16,
            Self::I32(_) => Kind::I32,
            Self::U32(_) => Kind::U32,
            Self::I64(_) => Kind::I64,
            Self::U64(_) => Kind::U64,
            Self::F32(_) => Kind::F32,
            Self::Vector2(_) => Kind::Vector2,
            Self::Vector3(_) => Kind::Vector3,
            Self::Vector4(_) => Kind::Vector4,
            Self::Matrix44(_) => Kind::Matrix44,
            Self::Color(_) => Kind::Color,
            Self::String(_) => Kind::String,
            Self::Hash(_) => Kind::Hash,
            Self::File(_) => Kind::WadChunkLink,
            Self::Link(_) => Kind::ObjectLink,
            Self::Flag(_) => Kind::BitBool,
        }
    }

    /// Writes this leaf as the text inside a `{key}` segment of the hash form.
    ///
    /// An integer in decimal, a bool as `true` or `false`, a float in its shortest
    /// round-trip form, a string as a JSON string, a hash as lowercase zero-padded hex, a
    /// vector, color or matrix as its components in parentheses, and `None` as nothing. Writes
    /// a `Hash` with 8 digits at width 4 and with 16 digits at width 8.
    pub(crate) fn write_key(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::None => Ok(()),
            Self::Bool(v) | Self::Flag(v) => write!(f, "{v}"),
            Self::I8(v) => write!(f, "{v}"),
            Self::U8(v) => write!(f, "{v}"),
            Self::I16(v) => write!(f, "{v}"),
            Self::U16(v) => write!(f, "{v}"),
            Self::I32(v) => write!(f, "{v}"),
            Self::U32(v) => write!(f, "{v}"),
            Self::I64(v) => write!(f, "{v}"),
            Self::U64(v) => write!(f, "{v}"),
            Self::F32(v) => write!(f, "{v}"),
            Self::Vector2(v) => write_tuple(f, &v.to_array()),
            Self::Vector3(v) => write_tuple(f, &v.to_array()),
            Self::Vector4(v) => write_tuple(f, &v.to_array()),
            Self::Matrix44(v) => write_tuple(f, &v.transpose().to_cols_array()),
            Self::Color(c) => write_tuple(f, &[c.r, c.g, c.b, c.a]),
            Self::String(s) => write_json_string(f, s),
            Self::Hash(h) => write!(f, "{h}"),
            Self::Link(h) => write!(f, "{h:08x}"),
            Self::File(h) => write!(f, "{h:016x}"),
        }
    }
}

/// `(a, b, c)`.
fn write_tuple<T: fmt::Display>(f: &mut fmt::Formatter<'_>, items: &[T]) -> fmt::Result {
    f.write_str("(")?;
    for (i, item) in items.iter().enumerate() {
        if i > 0 {
            f.write_str(", ")?;
        }
        write!(f, "{item}")?;
    }
    f.write_str(")")
}

/// A JSON string literal. `"`, `\` and the control characters are escaped, as `serde_json`
/// writes them.
fn write_json_string(f: &mut fmt::Formatter<'_>, s: &str) -> fmt::Result {
    f.write_str("\"")?;
    for c in s.chars() {
        match c {
            '"' => f.write_str("\\\"")?,
            '\\' => f.write_str("\\\\")?,
            '\n' => f.write_str("\\n")?,
            '\r' => f.write_str("\\r")?,
            '\t' => f.write_str("\\t")?,
            '\u{8}' => f.write_str("\\b")?,
            '\u{c}' => f.write_str("\\f")?,
            c if (c as u32) < 0x20 => write!(f, "\\u{:04x}", c as u32)?,
            c => write!(f, "{c}")?,
        }
    }
    f.write_str("\"")
}