use std::{
fmt::{LowerHex, Write},
iter::once,
};
use ltk_hash::{BinHash, HashValue, WadHash};
use ltk_meta::{
path::PropertyPath, property::values, Bin, BinObject, BinOverride, PropertyKind, PropertyPatch,
PropertyValueEnum,
};
use crate::span::Span;
use crate::{
ast::node::root::FileKind,
cst::{Child, ChildRange, Cst, ErrorRange, Kind, Node, NodeId, TokenId},
hash_algorithm_name, hash_width_name,
parse::{Error, Token, TokenKind as Tok},
HashProvider, HashType, PropertyValueExt as _, RitoType, RitobinName as _,
};
#[derive(Default)]
pub struct Builder<H: HashProvider> {
buf: String,
hashes: H,
nodes: Vec<Node>,
children: Vec<Child>,
errors: Vec<Error>,
tokens: Vec<Token>,
}
impl Builder<()> {
pub fn new() -> Builder<()> {
Builder::default()
}
}
impl<H: HashProvider> Builder<H> {
pub fn with_hashes<H2: HashProvider>(self, hashes: H2) -> Builder<H2> {
Builder {
buf: self.buf,
hashes,
nodes: self.nodes,
children: self.children,
tokens: self.tokens,
errors: self.errors,
}
}
pub fn build(mut self, bin: &Bin) -> (Cst, String) {
self.bin_to_cst(bin);
self.finish()
}
pub fn build_override(mut self, patch: &BinOverride) -> (Cst, String) {
self.override_to_cst(patch);
self.finish()
}
fn finish(self) -> (Cst, String) {
(
Cst {
nodes: self.nodes,
children: self.children,
tokens: self.tokens,
errors: self.errors,
},
self.buf,
)
}
pub fn text_buffer(&self) -> &str {
&self.buf
}
pub fn into_text_buffer(self) -> String {
self.buf
}
}
impl<H: HashProvider> Builder<H> {
pub fn token(&mut self, kind: Tok) -> Child {
let id = TokenId(self.tokens.len().try_into().unwrap());
self.tokens.push(Token {
kind,
span: Span::default(),
});
Child::Token(id)
}
pub fn children(&mut self, children: impl IntoIterator<Item = Child>) -> ChildRange {
let start: u32 = self.children.len().try_into().unwrap();
self.children.extend(children);
let end: u32 = self.children.len().try_into().unwrap();
ChildRange {
start,
len: end - start,
}
}
pub fn tree(&mut self, kind: Kind, children: impl IntoIterator<Item = Child>) -> Child {
let children = self.children(children);
let id = NodeId(self.nodes.len().try_into().unwrap());
self.nodes.push(Node {
span: Span::default(),
kind,
children,
errors: ErrorRange::empty(),
});
Child::Tree(id)
}
fn number(&mut self, v: impl std::fmt::Display) -> Child {
let lit = self.spanned_display(Tok::Number, v);
self.tree(Kind::Literal, [lit])
}
fn spanned_token(&mut self, kind: Tok, str: impl AsRef<str>) -> Child {
let start = self.buf.len() as u32;
self.buf.write_str(str.as_ref()).unwrap();
let end = self.buf.len() as u32;
let id = TokenId(self.tokens.len().try_into().unwrap());
self.tokens.push(Token {
kind,
span: Span::new(start, end),
});
Child::Token(id)
}
fn spanned_display(&mut self, kind: Tok, v: impl std::fmt::Display) -> Child {
let start = self.buf.len() as u32;
write!(self.buf, "{v}").unwrap();
let end = self.buf.len() as u32;
let id = TokenId(self.tokens.len().try_into().unwrap());
self.tokens.push(Token {
kind,
span: Span::new(start, end),
});
Child::Token(id)
}
fn spanned_hexlit<T: LowerHex>(&mut self, v: T) -> Child {
let start = self.buf.len() as u32;
write!(self.buf, "0x{v:x}").unwrap();
let end = self.buf.len() as u32;
let id = TokenId(self.tokens.len().try_into().unwrap());
self.tokens.push(Token {
kind: Tok::HexLit,
span: Span::new(start, end),
});
Child::Token(id)
}
fn string(&mut self, v: impl AsRef<str>) -> Child {
self.spanned_token(Tok::String, v)
}
fn hash_hash_lit(&mut self, h: BinHash) -> Child {
match self.hashes.lookup_hash(h).map(|h| format!("\"{h}\"")) {
Some(h) => self.spanned_token(Tok::String, h),
None => self.spanned_hexlit(h),
}
}
fn hash_value_lit(&mut self, h: HashValue) -> Child {
match h.try_as_bin_hash() {
Some(h) => self.hash_hash_lit(h),
None => self.spanned_hexlit(h),
}
}
fn hash_type_lit(&mut self, h: BinHash) -> Child {
match self.hashes.lookup_type(h).map(|h| h.to_string()) {
Some(h) => self.spanned_token(Tok::Name, h),
None => self.spanned_hexlit(h),
}
}
fn hash_field_lit(&mut self, h: BinHash) -> Child {
match self.hashes.lookup_field(h).map(|h| h.to_string()) {
Some(h) => self.spanned_token(Tok::Name, h),
None => self.spanned_hexlit(h),
}
}
fn hash_entry_lit(&mut self, h: BinHash) -> Child {
match self.hashes.lookup_entry(h).map(|h| format!("\"{h}\"")) {
Some(h) => self.spanned_token(Tok::String, h),
None => self.spanned_hexlit(h),
}
}
fn hash_link_lit(&mut self, h: BinHash) -> Child {
let name = self
.hashes
.lookup_entry(h)
.or_else(|| self.hashes.lookup_hash(h))
.map(|h| format!("\"{h}\""));
match name {
Some(h) => self.spanned_token(Tok::String, h),
None => self.spanned_hexlit(h),
}
}
fn hash_wad_lit(&mut self, h: WadHash) -> Child {
match self.hashes.lookup_wad(h).map(|h| format!("\"{h}\"")) {
Some(h) => self.spanned_token(Tok::String, h),
None => self.spanned_hexlit(h),
}
}
fn block(&mut self, children: Vec<Child>) -> Child {
let lcurly = self.token(Tok::LCurly);
let rcurly = self.token(Tok::RCurly);
self.tree(
Kind::Block,
once(lcurly).chain(children).chain(once(rcurly)),
)
}
fn bool(&mut self, v: bool) -> Child {
let tok = self.token(match v {
true => Tok::True,
false => Tok::False,
});
self.tree(Kind::Literal, [tok])
}
fn value_to_cst(&mut self, value: &PropertyValueEnum) -> Child {
match value {
PropertyValueEnum::Bool(b) => self.bool(**b),
PropertyValueEnum::BitBool(b) => self.bool(**b),
PropertyValueEnum::U8(n) => self.number(**n),
PropertyValueEnum::U16(n) => self.number(**n),
PropertyValueEnum::U32(n) => self.number(**n),
PropertyValueEnum::U64(n) => self.number(**n),
PropertyValueEnum::I8(n) => self.number(**n),
PropertyValueEnum::I16(n) => self.number(**n),
PropertyValueEnum::I32(n) => self.number(**n),
PropertyValueEnum::I64(n) => self.number(**n),
PropertyValueEnum::F32(n) => self.number(**n),
PropertyValueEnum::Vector2(v) => {
let items = v
.to_array()
.iter()
.map(|v| {
let v = self.number(*v);
self.tree(Kind::ListItem, [v])
})
.collect();
self.block(items)
}
PropertyValueEnum::Vector3(v) => {
let items = v
.to_array()
.iter()
.map(|v| {
let v = self.number(*v);
self.tree(Kind::ListItem, [v])
})
.collect();
self.block(items)
}
PropertyValueEnum::Vector4(v) => {
let items = v
.to_array()
.iter()
.map(|v| {
let v = self.number(*v);
self.tree(Kind::ListItem, [v])
})
.collect();
self.block(items)
}
PropertyValueEnum::Matrix44(v) => {
let items = v
.transpose() .to_cols_array_2d()
.iter()
.flat_map(|v| {
let values = [
self.number(v[0]),
self.number(v[1]),
self.number(v[2]),
self.number(v[3]),
];
[
self.tree(Kind::ListItem, [values[0]]),
self.tree(Kind::ListItem, [values[1]]),
self.tree(Kind::ListItem, [values[2]]),
self.tree(Kind::ListItem, [values[3]]),
]
})
.collect();
self.block(items)
}
PropertyValueEnum::Color(v) => {
let items = v
.to_array()
.iter()
.map(|v| {
let v = self.number(*v);
self.tree(Kind::ListItem, [v])
})
.collect();
self.block(items)
}
PropertyValueEnum::String(s) => {
let s = self.string(crate::escaping::escape(s));
let quote = self.token(Tok::Quote);
self.tree(Kind::Literal, [quote, s, quote])
}
PropertyValueEnum::Hash(h) => self.hash_value_lit(**h),
PropertyValueEnum::WadChunkLink(h) => self.hash_wad_lit(**h),
PropertyValueEnum::ObjectLink(h) => self.hash_link_lit(**h),
PropertyValueEnum::Container(container)
| PropertyValueEnum::UnorderedContainer(values::UnorderedContainer(container)) => {
let mut children = vec![self.token(Tok::LCurly)];
for item in container.clone().into_items() {
let item = self.value_to_cst(&item);
children.push(self.tree(Kind::ListItem, [item]));
}
children.push(self.token(Tok::RCurly));
self.tree(Kind::TypeArgList, children)
}
PropertyValueEnum::Embedded(values::Embedded(s)) | PropertyValueEnum::Struct(s) => {
let k = self.hash_type_lit(s.class_hash);
let children = s
.properties
.iter()
.map(|(k, v)| self.property_to_cst(*k, v))
.collect();
let children = self.block(children);
self.tree(Kind::Class, [k, children])
}
PropertyValueEnum::Optional(optional) => {
let children = match optional.clone().into_inner() {
Some(v) => vec![self.value_to_cst(&v)],
None => vec![],
};
self.block(children)
}
PropertyValueEnum::None(_) => {
let tok = self.token(Tok::Null);
self.tree(Kind::Literal, [tok])
}
PropertyValueEnum::Map(map) => {
let children = map
.entries()
.iter()
.map(|(k, v)| {
let k = self.value_to_cst(k);
let eq = self.token(Tok::Eq);
let v = self.value_to_cst(v);
self.tree(Kind::Entry, [k, eq, v])
})
.collect();
self.block(children)
}
}
}
fn entry_tree(&mut self, key: Child, kind: Option<Child>, value: Child) -> Child {
let colon = self.token(Tok::Colon);
let eq = self.token(Tok::Eq);
match kind {
Some(kind) => self.tree(Kind::Entry, [key, colon, kind, eq, value]),
None => self.tree(Kind::Entry, [key, eq, value]),
}
}
fn rito_type(&mut self, rito_type: RitoType) -> Child {
let mut children = self.kind_name(rito_type.base, rito_type.hash_types[0]);
if let Some(sub) = rito_type.subtypes[0] {
let name = self.kind_name(sub, rito_type.hash_types[1]);
let mut args = vec![self.token(Tok::LBrack), self.tree(Kind::TypeArg, name)];
if let Some(sub) = rito_type.subtypes[1] {
let name = self.kind_name(sub, rito_type.hash_types[2]);
args.push(self.tree(Kind::TypeArg, name));
}
args.push(self.token(Tok::RBrack));
children.push(self.tree(Kind::TypeArgList, args));
}
self.tree(Kind::TypeExpr, children)
}
fn kind_name(&mut self, kind: PropertyKind, hash_type: HashType) -> Vec<Child> {
let mut children = vec![self.spanned_token(Tok::Name, kind.to_rito_name())];
if kind == PropertyKind::Hash && hash_type != HashType::DEFAULT {
let width = self.spanned_token(Tok::Name, hash_width_name(hash_type.width));
let mut args = vec![self.token(Tok::LBrack), self.tree(Kind::TypeArg, [width])];
if let Some(algorithm) = hash_type.algorithm {
let algorithm = self.spanned_token(Tok::Name, hash_algorithm_name(algorithm));
args.push(self.tree(Kind::TypeArg, [algorithm]));
}
args.push(self.token(Tok::RBrack));
children.push(self.tree(Kind::TypeArgList, args));
}
children
}
fn property_to_cst(&mut self, name_hash: BinHash, value: &PropertyValueEnum) -> Child {
let k = self.hash_field_lit(name_hash);
let t = self.rito_type(value.rito_type());
let v = self.value_to_cst(value);
self.entry_tree(k, Some(t), v)
}
fn class(&mut self, class_name: Child, items: Vec<Child>) -> Child {
let items = self.block(items);
self.tree(Kind::Class, [class_name, items])
}
fn bin_object_to_cst(&mut self, obj: &BinObject) -> Child {
let k = self.hash_entry_lit(obj.path_hash);
let class_hash = self.hash_type_lit(obj.class_hash);
let class_values = obj
.properties
.iter()
.map(|(k, v)| {
let k = self.hash_field_lit(*k);
let k = self.tree(Kind::EntryKey, [k]);
let t = self.rito_type(v.rito_type());
let v = self.value_to_cst(v);
self.entry_tree(k, Some(t), v)
})
.collect();
let value = self.class(class_hash, class_values);
self.entry_tree(k, None, value)
}
fn entry(&mut self, key: impl AsRef<str>, kind: RitoType, value: Child) -> Child {
let key = self.spanned_token(Tok::Comment, key);
let kind = self.rito_type(kind);
let key = self.tree(Kind::EntryKey, [key]);
let value = self.tree(Kind::EntryValue, [value]);
self.entry_tree(key, Some(kind), value)
}
fn file_node(&mut self) {
self.nodes.push(Node {
kind: Kind::File,
span: Span::default(),
children: ChildRange::empty(),
errors: ErrorRange::empty(),
});
}
fn set_file_roots(&mut self, roots: impl IntoIterator<Item = Child>) {
let roots = self.children(roots);
if let Some(file) = self.nodes.first_mut() {
file.children = roots;
}
}
fn header_to_cst(&mut self, file_kind: FileKind) -> [Child; 3] {
let comment = self.spanned_token(Tok::Comment, format!("#{file_kind}_text"));
let comment = self.tree(Kind::Comment, vec![comment]);
let type_entry = self.string(format!("\"{file_kind}\""));
let type_entry = self.entry("type", RitoType::simple(PropertyKind::String), type_entry);
let version = self.number("3");
let version = self.entry("version", RitoType::simple(PropertyKind::U32), version);
[comment, type_entry, version]
}
fn list_root(&mut self, key: &str, item_kind: PropertyKind, items: Vec<Child>) -> Child {
let items = items
.into_iter()
.map(|item| self.tree(Kind::ListItem, [item]))
.collect();
let items = self.block(items);
self.entry(key, RitoType::container(item_kind), items)
}
fn linked_to_cst(&mut self, dependencies: &[String]) -> Child {
let linked = dependencies
.iter()
.map(|dep| {
let lit = self.string(format!("\"{dep}\""));
self.tree(Kind::Literal, [lit])
})
.collect();
self.list_root("linked", PropertyKind::String, linked)
}
fn entries_to_cst<'o>(&mut self, objects: impl IntoIterator<Item = &'o BinObject>) -> Child {
let entries = objects
.into_iter()
.map(|obj| self.bin_object_to_cst(obj))
.collect();
let entries = self.block(entries);
self.entry(
"entries",
RitoType::map(PropertyKind::Hash, PropertyKind::Embedded),
entries,
)
}
fn bin_to_cst(&mut self, bin: &Bin) {
self.file_node();
let [comment, type_entry, version] = self.header_to_cst(FileKind::Prop);
let linked = self.linked_to_cst(&bin.dependencies);
let entries = self.entries_to_cst(bin.objects.values());
self.set_file_roots([comment, type_entry, version, linked, entries]);
}
fn override_to_cst(&mut self, patch: &BinOverride) {
self.file_node();
let [comment, type_entry, version] = self.header_to_cst(FileKind::Patch);
let linked = self.linked_to_cst(&[]);
let entries = self.entries_to_cst(patch.objects.values());
let records = patch
.patches
.iter()
.map(|record| self.patch_to_cst(record))
.collect();
let records = self.block(records);
let records = self.entry(
"patches",
RitoType::map(PropertyKind::Hash, PropertyKind::Embedded),
records,
);
let mut roots = vec![comment, type_entry, version, linked, entries, records];
if !patch.deleted.is_empty() {
let deleted = patch
.deleted
.iter()
.map(|hash| self.hash_entry_lit(*hash))
.collect();
roots.push(self.list_root("deleted", PropertyKind::Hash, deleted));
}
self.set_file_roots(roots);
}
fn patch_to_cst(&mut self, record: &PropertyPatch) -> Child {
let object = self.hash_entry_lit(record.object_hash);
let path = self.path_literal(&record.path);
let path = self.named_field("path", RitoType::simple(PropertyKind::String), path);
let value = self.value_to_cst(&record.value);
let value = self.named_field("value", record.value.rito_type(), value);
let class_name = self.spanned_token(Tok::Name, "patch");
let body = self.class(class_name, vec![path, value]);
self.entry_tree(object, None, body)
}
fn named_field(&mut self, name: &str, rito_type: RitoType, value: Child) -> Child {
let key = self.spanned_token(Tok::Name, name);
let key = self.tree(Kind::EntryKey, [key]);
let rito_type = self.rito_type(rito_type);
self.entry_tree(key, Some(rito_type), value)
}
fn path_literal(&mut self, path: &PropertyPath) -> Child {
let s = self.string(crate::escaping::escape(path.as_str()));
let quote = self.token(Tok::Quote);
self.tree(Kind::Literal, [quote, s, quote])
}
}
#[cfg(test)]
mod test {
use glam::Vec2;
use ltk_meta::{property::values, Bin, BinObject};
use super::*;
use crate::{print::CstPrinter, HashMapProvider};
fn roundtrip(bin: Bin) {
println!("bin: {bin:#?}");
let builder = Builder::new();
let (cst, buf) = builder.build(&bin);
let mut str = String::new();
cst.print(&mut str, &buf);
println!("cst:\n{str}");
let mut str = String::new();
CstPrinter::new(&buf, &mut str, Default::default())
.print(&cst)
.unwrap();
println!("RITOBIN:\n{str}");
let cst2 = Cst::parse(&str);
assert!(
cst2.errors.is_empty(),
"errors parsing ritobin - {:#?}",
cst2.errors
);
let partial2 = cst2.build_bin(&str);
assert!(
partial2.diagnostics.is_empty(),
"errors building tree from reparsed ritobin - {:#?}",
partial2.diagnostics
);
let bin2 = partial2.bin;
pretty_assertions::assert_eq!(bin2, bin);
}
#[test]
fn null() {
roundtrip(
Bin::builder()
.object(
BinObject::builder(0xDEADBEEF, 0x12344321)
.property(0x1, values::None)
.build(),
)
.build(),
);
}
#[test]
fn numerics() {
roundtrip(
Bin::builder()
.object(
BinObject::builder(0xDEADBEEF, 0x12344321)
.property(0x1, values::U64::new(12))
.property(0x2, values::U32::new(23))
.property(0x3, values::U16::new(34))
.property(0x4, values::U8::new(45))
.property(0x11, values::I64::new(-12))
.property(0x22, values::I32::new(-23))
.property(0x33, values::I16::new(-34))
.property(0x44, values::I8::new(45))
.property(0x66, values::Hash::new(123123))
.property(0x99, values::F32::new(-45.45345))
.property(0x98, values::F32::new(199999.))
.build(),
)
.build(),
);
}
#[test]
fn vectors_colors_and_matrices() {
roundtrip(
Bin::builder()
.object(
BinObject::builder(0xDEADBEEF, 0x12344321)
.property(0x1, values::Vector2::new(glam::Vec2::new(0.1, -65.0)))
.property(0x2, values::Vector3::new(glam::Vec3::new(1000., -0.0, 2.)))
.property(
0x3,
values::Vector4::new(glam::Vec4::new(0.1, -65.0, 100.0, 481.)),
)
.property(
0x4,
values::Color::new(ltk_primitives::Color {
r: 123,
g: 255,
b: 2,
a: 5,
}),
)
.property(
0x5,
values::Matrix44::new(glam::Mat4::from_cols_array(&[
0.1, 0.5, 0.7, 0.9, 10.0, 11.2, 13.8, 15.3, 19.52, -0.123, -55.11,
-13.005, 23.0, 99.02, 101.1, 500.0,
])),
)
.build(),
)
.build(),
);
}
#[test]
fn bool_bitbool() {
roundtrip(
Bin::builder()
.object(
BinObject::builder(0xDEADBEEF, 0x12344321)
.property(0x1, values::Bool::new(true))
.property(0x2, values::Bool::new(false))
.property(0x3, values::BitBool::new(true))
.property(0x4, values::BitBool::new(false))
.build(),
)
.build(),
);
}
#[test]
fn string() {
roundtrip(
Bin::builder()
.object(
BinObject::builder(0xDEADBEEF, 0x12344321)
.property(0x44444444, values::String::from("hello"))
.build(),
)
.build(),
);
}
#[test]
fn hashes() {
roundtrip(
Bin::builder()
.object(
BinObject::builder(0xDEADBEEF, 0x12344321)
.property(0x1, values::Hash::new(123123))
.property(0x2, values::Hash::new(u32::MAX))
.property(0x3, values::ObjectLink::new(123123))
.property(0x4, values::ObjectLink::new(u32::MAX))
.property(0x5, values::WadChunkLink::new(123123))
.property(0x6, values::WadChunkLink::new(u64::MAX))
.build(),
)
.build(),
);
}
fn print_value(hashes: &HashMapProvider, value: impl Into<PropertyValueEnum>) -> String {
let bin = Bin::builder()
.object(
BinObject::builder(0xDEADBEEF, 0x12344321)
.property(0x1, value)
.build(),
)
.build();
let (cst, buf) = Builder::new().with_hashes(hashes).build(&bin);
let mut str = String::new();
CstPrinter::new(&buf, &mut str, Default::default())
.print(&cst)
.unwrap();
str
}
#[test]
fn link_prints_name_from_entry_table() {
let mut hashes = HashMapProvider::new();
hashes.insert_entry(0x58a7d43d, "Characters/Rengar/CAC/Rengar_Base");
let text = print_value(&hashes, values::ObjectLink::new(0x58a7d43d));
assert!(
text.contains(r#"link = "Characters/Rengar/CAC/Rengar_Base""#),
"{text}"
);
let text = print_value(&hashes, values::Hash::new(0x58a7d43d));
assert!(text.contains("hash = 0x58a7d43d"), "{text}");
}
#[test]
fn wide_hash_prints_u64_type() {
let hashes = HashMapProvider::new();
let text = print_value(&hashes, values::Hash::new(HashValue::wide(0x1a)));
assert!(text.contains("hash[u64] = 0x000000000000001a"), "{text}");
}
#[test]
fn wide_hash_roundtrip() {
let wide = |hash| PropertyValueEnum::from(values::Hash::new(HashValue::wide(hash)));
roundtrip(
Bin::builder()
.object(
BinObject::builder(0xDEADBEEF, 0x12344321)
.property(0x1, wide(0x5d07_ca0d_22ff_9588))
.property(0x2, wide(0))
.property(
0x3,
values::Container::new(PropertyKind::Hash, vec![wide(1), wide(2)])
.unwrap(),
)
.property(
0x4,
values::Map::new(
PropertyKind::Hash,
PropertyKind::Hash,
vec![(wide(3), values::Hash::new(4).into())],
)
.unwrap(),
)
.property(
0x5,
values::Optional::new(PropertyKind::Hash, Some(wide(5))).unwrap(),
)
.build(),
)
.build(),
);
}
#[test]
fn link_falls_back_to_hash_table() {
let mut hashes = HashMapProvider::new();
hashes.insert_hash(0x1, "hash table");
hashes.insert_hash(0x2, "hash table");
hashes.insert_entry(0x2, "entry table");
let text = print_value(&hashes, values::ObjectLink::new(0x1));
assert!(text.contains(r#"link = "hash table""#), "{text}");
let text = print_value(&hashes, values::ObjectLink::new(0x2));
assert!(text.contains(r#"link = "entry table""#), "{text}");
}
#[test]
fn struct_and_embedded() {
roundtrip(
Bin::builder()
.object(
BinObject::builder(0xDEADBEEF, 0x12344321)
.property(
0x91,
values::Struct {
class_hash: 0x123.into(),
properties: [
(0x1.into(), values::U64::new(5).into()),
(0x2.into(), values::U64::new(10).into()),
]
.into_iter()
.collect(),
},
)
.property(
0x92,
values::Embedded(values::Struct {
class_hash: 0x234.into(),
properties: [
(0x2.into(), values::U64::new(5).into()),
(0x3.into(), values::U64::new(10).into()),
]
.into_iter()
.collect(),
}),
)
.build(),
)
.build(),
);
}
#[test]
fn lists() {
roundtrip(
Bin::builder()
.object(
BinObject::builder(0xDEADBEEF, 0x12344321)
.property(
0x9191919,
values::Container::from(vec![
values::U64::new(5),
values::U64::new(6),
values::U64::new(7),
]),
)
.property(
0x9191918,
values::UnorderedContainer(values::Container::from(vec![
values::U32::new(5),
values::U32::new(6),
values::U32::new(7),
])),
)
.build(),
)
.build(),
);
}
#[test]
fn map() {
roundtrip(
Bin::builder()
.object(
BinObject::builder(0xfeeb1e, 0x111)
.property(
0x1,
values::Map::new(
PropertyKind::String,
PropertyKind::U64,
vec![(
values::String::from("asdasd").into(),
values::U64::new(1).into(),
)],
)
.unwrap(),
)
.build(),
)
.build(),
);
}
#[test]
fn optional() {
roundtrip(
Bin::builder()
.object(
BinObject::builder(0xDEADBEEF, 0x12344321)
.property(
0x1,
values::Optional::new(PropertyKind::Vector2, None).unwrap(),
)
.property(
0x2,
values::Optional::new(
PropertyKind::Vector2,
Some(values::Vector2::new(Vec2::new(0.1, -5.0)).into()),
)
.unwrap(),
)
.build(),
)
.build(),
);
}
}