use ltk_meta::{
property::values, Bin, BinFile, BinObject, BinOverride, Error as MetaError, PropertyKind,
PropertyPatch, PropertyValueEnum,
};
use crate::{
ast::{
diagnostics::{Diagnostic, DiagnosticWithSpan},
node::{root::FileKind, Map, MapEntry},
Ast, Object, RootEntry, RootPatch, Value,
},
ScalarType, Spanned,
};
#[derive(Debug)]
pub struct PartialBin {
pub bin: Bin,
pub diagnostics: Vec<DiagnosticWithSpan>,
}
impl PartialBin {
#[allow(clippy::result_large_err)]
#[inline(always)]
pub fn into_result(self) -> Result<Bin, Self> {
if self.diagnostics.is_empty() {
Ok(self.bin)
} else {
Err(self)
}
}
}
impl Ast {
fn bin_objects(&self) -> impl Iterator<Item = BinObject> + '_ {
self.root_entries().map(|RootEntry { path_hash, object }| {
let struct_val = object.to_bin_value();
BinObject {
path_hash: path_hash.value,
class_hash: struct_val.class_hash,
properties: struct_val.properties,
}
})
}
pub fn to_bin_file(&self) -> BinFile {
match self.roots.file_type().map(|root| root.value) {
Some(FileKind::Patch) => BinFile::Override(self.to_bin_override()),
_ => BinFile::Prop(self.prop_bin()),
}
}
pub fn to_bin_override(&self) -> BinOverride {
let deleted = self
.roots
.deleted()
.map(|root| root.value.as_slice())
.unwrap_or_default();
let patches = self
.roots
.patches()
.map(|root| root.value.as_slice())
.unwrap_or_default();
BinOverride::builder()
.deletions(deleted.iter().copied())
.objects(self.bin_objects())
.patches(patches.iter().filter_map(RootPatch::to_property_patch))
.build()
}
pub fn to_bin(&self, _text: &str) -> Bin {
self.prop_bin()
}
fn prop_bin(&self) -> Bin {
let objects = self.bin_objects().collect::<Vec<_>>();
let dependencies: Vec<String> = self
.roots
.linked
.clone()
.map(|linked| linked.into_inner())
.unwrap_or_default();
Bin::new(objects, dependencies)
}
pub fn into_partial_bin(mut self, text: &str) -> PartialBin {
if let Some(file_type) = self.roots.file_type() {
if file_type.value == FileKind::Patch {
let span = file_type.original(&self.roots).value_span();
self.diagnostics.push(
Diagnostic::UnexpectedFileKind {
span,
expected: FileKind::Prop,
found: FileKind::Patch,
}
.unwrap(),
);
}
}
let bin = self.to_bin(text);
PartialBin {
bin,
diagnostics: self.diagnostics,
}
}
}
impl RootPatch {
pub(crate) fn to_property_patch(&self) -> Option<PropertyPatch> {
Some(PropertyPatch::new(
self.object_hash.value,
self.path.value.clone(),
self.value.to_bin_value()?,
))
}
}
impl Object {
pub fn to_bin_value(&self) -> values::Struct {
values::Struct {
class_hash: self.class_hash.value,
properties: self
.properties
.iter()
.filter_map(|p| Some((p.name.value, p.value.as_ref()?.to_bin_value()?)))
.collect(),
}
}
}
fn assert<T>(result: Result<T, MetaError>, fallback: impl FnOnce() -> T) -> T {
match result {
Ok(v) => v,
Err(e) => {
debug_assert!(false, "ast::build should have prevented this: {e:?}");
fallback()
}
}
}
impl Value {
pub fn to_bin_value(&self) -> Option<PropertyValueEnum> {
use PropertyValueEnum as P;
Some(match self {
Value::Unknown(_) => return None,
Value::Unresolved { kind, .. } => kind.make_default(),
Value::None(_) => P::None(values::None),
Value::Bool(Spanned { value, .. }) => P::Bool(values::Bool::new(*value)),
Value::BitBool(Spanned { value, .. }) => P::BitBool(values::BitBool::new(*value)),
Value::I8(v) => P::I8(v.value.into()),
Value::U8(v) => P::U8(v.value.into()),
Value::I16(v) => P::I16(v.value.into()),
Value::U16(v) => P::U16(v.value.into()),
Value::I32(v) => P::I32(v.value.into()),
Value::U32(v) => P::U32(v.value.into()),
Value::I64(v) => P::I64(v.value.into()),
Value::U64(v) => P::U64(v.value.into()),
Value::F32(v) => P::F32(v.value.into()),
Value::Vector2(v) => P::Vector2(v.value.into()),
Value::Vector3(v) => P::Vector3(v.value.into()),
Value::Vector4(v) => P::Vector4(v.value.into()),
Value::Matrix44(v) => P::Matrix44(v.value.into()),
Value::Color(Spanned { value, .. }) => P::Color(values::Color::new(*value)),
Value::String(Spanned { value, .. }) => P::String(values::String::new(value.clone())),
Value::Hash(v) => P::Hash(values::Hash::new(v.value)),
Value::WadChunkLink(v) => P::WadChunkLink(values::WadChunkLink::new(v.value)),
Value::ObjectLink(v) => P::ObjectLink(values::ObjectLink::new(v.value)),
Value::Struct(s) => P::Struct(s.to_bin_value()),
Value::Embedded(s) => P::Embedded(values::Embedded(s.to_bin_value())),
Value::Container {
item_kind,
items,
span: _,
} => P::Container(container_from(*item_kind, items)),
Value::UnorderedContainer {
item_kind,
items,
span: _,
} => P::UnorderedContainer(values::UnorderedContainer(container_from(
*item_kind, items,
))),
Value::Map(Map {
key_kind,
value_kind,
entries,
span: _,
}) => {
let mut map = assert(
values::Map::empty(key_kind.kind(), value_kind.kind()),
|| {
values::Map::empty(PropertyKind::None, PropertyKind::None)
.expect("None is always a valid map key and value kind")
},
);
for MapEntry {
key: k, value: v, ..
} in entries
{
if let Some((k, v)) = k
.to_bin_value()
.zip(v.as_ref().and_then(|v| v.to_bin_value()))
{
assert(map.push(k, v), || ());
}
}
P::Map(map)
}
Value::Optional {
item_kind,
value,
span: _,
} => {
let inner = value.as_deref().and_then(Value::to_bin_value);
let item_kind = (*item_kind)?;
let optional = assert(values::Optional::new(item_kind.kind(), inner), || {
values::Optional::empty(item_kind.kind()).unwrap_or_default()
});
P::Optional(optional)
}
})
}
}
fn container_from(item_kind: ScalarType, items: &[Value]) -> values::Container {
let mut container = assert(values::Container::empty(item_kind.kind()), || {
values::Container::empty(PropertyKind::None).expect("None is always a valid item kind")
});
for item in items {
if let Some(value) = item.to_bin_value() {
assert(container.push(value), || ());
}
}
container
}