use indexmap::IndexMap;
use smallvec::SmallVec;
use std::fmt;
pub type UclArray = Vec<UclValue>;
#[derive(Debug)]
pub enum UclValue {
Object(UclObject),
Array(UclArray),
Integer(i64),
Float(f64),
Time(f64),
String(String),
Boolean(bool),
Null,
}
impl Clone for UclValue {
fn clone(&self) -> Self {
match self {
UclValue::Object(_) | UclValue::Array(_) => clone_tree(self),
UclValue::Integer(i) => UclValue::Integer(*i),
UclValue::Float(f) => UclValue::Float(*f),
UclValue::Time(t) => UclValue::Time(*t),
UclValue::String(s) => UclValue::String(s.clone()),
UclValue::Boolean(b) => UclValue::Boolean(*b),
UclValue::Null => UclValue::Null,
}
}
}
impl PartialEq for UclValue {
fn eq(&self, other: &Self) -> bool {
let mut pending = Vec::new();
push_values(self, other, &mut pending) && equal_pending(pending)
}
}
impl PartialEq for UclObject {
fn eq(&self, other: &Self) -> bool {
let mut pending = Vec::new();
push_objects(self, other, &mut pending) && equal_pending(pending)
}
}
impl PartialEq for Entry {
fn eq(&self, other: &Self) -> bool {
let mut pending = Vec::new();
push_entries(self, other, &mut pending) && equal_pending(pending)
}
}
impl PartialEq for Slot {
fn eq(&self, other: &Self) -> bool {
let mut pending = Vec::new();
push_slots(self, other, &mut pending) && equal_pending(pending)
}
}
type Pending<'a> = Vec<(&'a UclValue, &'a UclValue)>;
fn equal_pending(mut pending: Pending<'_>) -> bool {
while let Some((a, b)) = pending.pop() {
if !push_values(a, b, &mut pending) {
return false;
}
}
true
}
fn push_values<'a>(a: &'a UclValue, b: &'a UclValue, pending: &mut Pending<'a>) -> bool {
match (a, b) {
(UclValue::Object(x), UclValue::Object(y)) => push_objects(x, y, pending),
(UclValue::Array(x), UclValue::Array(y)) => {
pending.extend(x.iter().zip(y));
x.len() == y.len()
}
(UclValue::Integer(x), UclValue::Integer(y)) => x == y,
(UclValue::Float(x), UclValue::Float(y)) | (UclValue::Time(x), UclValue::Time(y)) => x == y,
(UclValue::String(x), UclValue::String(y)) => x == y,
(UclValue::Boolean(x), UclValue::Boolean(y)) => x == y,
(UclValue::Null, UclValue::Null) => true,
_ => false,
}
}
fn push_objects<'a>(x: &'a UclObject, y: &'a UclObject, pending: &mut Pending<'a>) -> bool {
x.entries.len() == y.entries.len()
&& x.entries.iter().all(|(key, entry)| {
y.entries
.get(key)
.is_some_and(|other| push_entries(entry, other, pending))
})
}
fn push_entries<'a>(x: &'a Entry, y: &'a Entry, pending: &mut Pending<'a>) -> bool {
x.slots.len() == y.slots.len()
&& x.slots
.iter()
.zip(&y.slots)
.all(|(s, t)| push_slots(s, t, pending))
}
fn push_slots<'a>(s: &'a Slot, t: &'a Slot, pending: &mut Pending<'a>) -> bool {
pending.push((&s.value, &t.value));
(s.priority, s.inherited, s.collected) == (t.priority, t.inherited, t.collected)
}
fn clone_tree(root: &UclValue) -> UclValue {
enum Step<'a> {
Copy(&'a UclValue),
Build(&'a UclValue),
}
let mut todo = vec![Step::Copy(root)];
let mut done: Vec<UclValue> = Vec::new();
while let Some(step) = todo.pop() {
match step {
Step::Copy(value @ UclValue::Object(object)) => {
todo.push(Step::Build(value));
todo.extend(
object
.entries()
.flat_map(Entry::values)
.rev()
.map(Step::Copy),
);
}
Step::Copy(value @ UclValue::Array(items)) => {
todo.push(Step::Build(value));
todo.extend(items.iter().rev().map(Step::Copy));
}
Step::Copy(scalar) => done.push(scalar.clone()),
Step::Build(UclValue::Object(object)) => {
let count = object.entries().map(Entry::len).sum::<usize>();
let mut values = done.split_off(done.len() - count).into_iter();
let entries = object
.entries
.iter()
.map(|(key, entry)| {
let slots = entry
.slots
.iter()
.map(|slot| slot.with_value(values.next().expect("copied")))
.collect();
(key.clone(), Entry { slots })
})
.collect();
done.push(UclValue::Object(UclObject { entries }));
}
Step::Build(UclValue::Array(items)) => {
let elements = done.split_off(done.len() - items.len());
done.push(UclValue::Array(elements));
}
Step::Build(_) => unreachable!("only containers are built"),
}
}
done.pop().expect("the root was copied")
}
pub(crate) fn discard(value: UclValue) {
let mut stack = vec![value];
while let Some(value) = stack.pop() {
match value {
UclValue::Object(object) => stack.extend(
object
.into_iter()
.flat_map(|(_, entry)| entry.into_values()),
),
UclValue::Array(items) => stack.extend(items),
_ => {}
}
}
}
pub(crate) fn keep_first_container_values(value: &mut UclValue) {
let mut stack = vec![value];
let mut removed = Vec::new();
while let Some(value) = stack.pop() {
match value {
UclValue::Object(object) => {
for entry in object.entries.values_mut() {
if entry.slots.len() > 1 && is_container(&entry.slots[0].value) {
removed.extend(entry.slots.drain(1..).map(Slot::into_value));
}
stack.extend(entry.slots.iter_mut().map(Slot::value_mut));
}
}
UclValue::Array(items) => stack.extend(items.iter_mut()),
_ => {}
}
}
for value in removed {
discard(value);
}
}
pub(crate) fn nesting(value: &UclValue) -> usize {
let mut deepest = 0;
let mut stack = vec![(value, 1)];
while let Some((value, depth)) = stack.pop() {
match value {
UclValue::Object(object) => {
deepest = deepest.max(depth);
stack.extend(
object
.entries()
.flat_map(Entry::values)
.map(|v| (v, depth + 1)),
);
}
UclValue::Array(items) => {
deepest = deepest.max(depth);
stack.extend(items.iter().map(|v| (v, depth + 1)));
}
_ => {}
}
}
deepest
}
impl UclValue {
pub fn is_object(&self) -> bool {
matches!(self, UclValue::Object(_))
}
pub fn is_array(&self) -> bool {
matches!(self, UclValue::Array(_))
}
pub fn is_string(&self) -> bool {
matches!(self, UclValue::String(_))
}
pub fn is_time(&self) -> bool {
matches!(self, UclValue::Time(_))
}
pub fn is_null(&self) -> bool {
matches!(self, UclValue::Null)
}
pub fn as_object(&self) -> Option<&UclObject> {
match self {
UclValue::Object(obj) => Some(obj),
_ => None,
}
}
pub fn as_object_mut(&mut self) -> Option<&mut UclObject> {
match self {
UclValue::Object(obj) => Some(obj),
_ => None,
}
}
pub fn as_array(&self) -> Option<&UclArray> {
match self {
UclValue::Array(arr) => Some(arr),
_ => None,
}
}
pub fn as_array_mut(&mut self) -> Option<&mut UclArray> {
match self {
UclValue::Array(arr) => Some(arr),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
UclValue::String(s) => Some(s),
_ => None,
}
}
pub fn as_integer(&self) -> Option<i64> {
match self {
UclValue::Integer(i) => Some(*i),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
UclValue::Float(f) => Some(*f),
_ => None,
}
}
pub fn as_time(&self) -> Option<f64> {
match self {
UclValue::Time(t) => Some(*t),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
UclValue::Boolean(b) => Some(*b),
_ => None,
}
}
pub fn type_name(&self) -> &'static str {
match self {
UclValue::Object(_) => "object",
UclValue::Array(_) => "array",
UclValue::Integer(_) => "int",
UclValue::Float(_) => "float",
UclValue::Time(_) => "time",
UclValue::String(_) => "string",
UclValue::Boolean(_) => "boolean",
UclValue::Null => "null",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub enum DuplicateStrategy {
#[default]
Append,
Merge,
Rewrite,
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct ParserFlags(u32);
impl ParserFlags {
pub const DEFAULT: Self = Self(0);
pub const KEY_LOWERCASE: Self = Self(1 << 0);
pub const ZEROCOPY: Self = Self(1 << 1);
pub const NO_TIME: Self = Self(1 << 2);
pub const NO_IMPLICIT_ARRAYS: Self = Self(1 << 3);
pub const SAVE_COMMENTS: Self = Self(1 << 4);
pub const DISABLE_MACRO: Self = Self(1 << 5);
pub const NO_FILEVARS: Self = Self(1 << 6);
const ALL: u32 = (1 << 7) - 1;
pub const fn empty() -> Self {
Self(0)
}
pub const fn bits(self) -> u32 {
self.0
}
pub const fn from_bits_truncate(bits: u32) -> Self {
Self(bits & Self::ALL)
}
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub fn insert(&mut self, other: Self) {
self.0 |= other.0;
}
pub fn remove(&mut self, other: Self) {
self.0 &= !other.0;
}
}
impl std::ops::BitOr for ParserFlags {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl std::ops::BitOrAssign for ParserFlags {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
impl std::ops::BitAnd for ParserFlags {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Self(self.0 & rhs.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DuplicateKeyError {
pub key: String,
}
impl fmt::Display for DuplicateKeyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "key '{}' cannot take another value", self.key)
}
}
impl std::error::Error for DuplicateKeyError {}
pub const MAX_PRIORITY: u8 = 0x0f;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Placement {
Slot(usize),
Collected(usize),
Merged,
Dropped,
}
#[derive(Debug, Clone)]
pub struct Slot {
value: UclValue,
priority: u8,
inherited: bool,
collected: bool,
}
impl Slot {
pub fn new(value: UclValue, priority: u8) -> Self {
Self {
value,
priority: priority & MAX_PRIORITY,
inherited: false,
collected: false,
}
}
#[cfg(test)]
pub(crate) fn inherited(value: UclValue, priority: u8) -> Self {
Self::new(value, priority).into_inherited()
}
pub(crate) fn collection(value: UclValue) -> Self {
Self {
collected: true,
..Self::new(value, 0)
}
}
pub(crate) fn with_value(&self, value: UclValue) -> Self {
Self { value, ..*self }
}
pub(crate) fn into_inherited(self) -> Self {
Self {
inherited: true,
..self
}
}
pub fn value(&self) -> &UclValue {
&self.value
}
pub fn value_mut(&mut self) -> &mut UclValue {
&mut self.value
}
pub fn into_value(self) -> UclValue {
self.value
}
pub fn priority(&self) -> u8 {
self.priority
}
pub fn is_inherited(&self) -> bool {
self.inherited
}
pub(crate) fn is_collected(&self) -> bool {
self.collected
}
}
#[derive(Debug, Clone)]
pub struct Entry {
slots: SmallVec<[Slot; 1]>,
}
impl Entry {
pub fn new(value: UclValue) -> Self {
Self::from_slot(Slot::new(value, 0))
}
pub fn from_slot(slot: Slot) -> Self {
let mut slots = SmallVec::new();
slots.push(slot);
Self { slots }
}
pub fn len(&self) -> usize {
self.slots.len()
}
pub fn is_empty(&self) -> bool {
false
}
pub fn is_multi(&self) -> bool {
self.slots.len() > 1
}
pub fn first(&self) -> &UclValue {
&self.slots[0].value
}
pub fn first_mut(&mut self) -> &mut UclValue {
&mut self.slots[0].value
}
pub fn last(&self) -> &UclValue {
&self.slots[self.slots.len() - 1].value
}
pub fn values(&self) -> Values<'_> {
Values(self.slots.iter())
}
pub fn slots(&self) -> &[Slot] {
&self.slots
}
pub fn value_at_mut(&mut self, index: usize) -> Option<&mut UclValue> {
self.slots.get_mut(index).map(|s| &mut s.value)
}
pub fn push(&mut self, value: UclValue) {
self.slots.push(Slot::new(value, 0));
}
pub fn push_slot(&mut self, slot: Slot) {
self.slots.push(slot);
}
pub fn into_value(self) -> UclValue {
if self.slots.len() == 1 {
self.slots.into_iter().next().unwrap().value
} else {
UclValue::Array(self.slots.into_iter().map(|s| s.value).collect())
}
}
pub fn into_values(self) -> impl Iterator<Item = UclValue> {
self.slots.into_iter().map(|s| s.value)
}
fn head(&self) -> &Slot {
&self.slots[0]
}
fn add_by_priority(
&mut self,
key: &str,
slot: Slot,
replace_inherited: bool,
flags: ParserFlags,
) -> Result<Placement, DuplicateKeyError> {
let head = self.head();
if (replace_inherited && head.inherited) || slot.priority > head.priority {
*self = Entry::from_slot(slot);
Ok(Placement::Slot(0))
} else if slot.priority == head.priority {
if flags.contains(ParserFlags::NO_IMPLICIT_ARRAYS) {
return self.collect(key, slot);
}
self.slots.push(slot);
Ok(Placement::Slot(self.slots.len() - 1))
} else {
Ok(Placement::Dropped)
}
}
fn collect(&mut self, key: &str, slot: Slot) -> Result<Placement, DuplicateKeyError> {
if self.head().collected {
return match &mut self.slots[0].value {
UclValue::Array(items) => {
items.push(slot.value);
Ok(Placement::Collected(items.len() - 1))
}
_ => Err(DuplicateKeyError {
key: key.to_owned(),
}),
};
}
let head = std::mem::take(&mut self.slots)
.into_iter()
.next()
.expect("an entry holds at least one value");
let items: UclArray = vec![head.value, slot.value];
*self = Entry::from_slot(Slot::collection(UclValue::Array(items)));
Ok(Placement::Collected(1))
}
fn merge(
&mut self,
key: &str,
slot: Slot,
flags: ParserFlags,
) -> Result<Placement, DuplicateKeyError> {
let head = &mut self.slots[0];
if !is_container(&head.value) {
return self.add_by_priority(key, slot, false, flags);
}
if !is_container(&slot.value) {
head.value = slot.value;
return Ok(Placement::Slot(0));
}
match (&mut head.value, slot.value) {
(UclValue::Object(target), UclValue::Object(source)) => {
for (name, entry) in source {
for inner in entry.slots {
target.insert_slot_with_strategy(
name.as_str(),
inner,
DuplicateStrategy::Merge,
flags,
)?;
}
}
Ok(Placement::Merged)
}
(UclValue::Array(target), UclValue::Array(source)) => {
target.extend(source);
Ok(Placement::Merged)
}
_ => Err(DuplicateKeyError {
key: key.to_owned(),
}),
}
}
}
fn is_container(value: &UclValue) -> bool {
matches!(value, UclValue::Object(_) | UclValue::Array(_))
}
#[derive(Debug, Clone)]
pub struct Values<'a>(std::slice::Iter<'a, Slot>);
impl<'a> Iterator for Values<'a> {
type Item = &'a UclValue;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|s| &s.value)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl DoubleEndedIterator for Values<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
self.0.next_back().map(|s| &s.value)
}
}
impl ExactSizeIterator for Values<'_> {}
#[derive(Debug, Clone, Default)]
pub struct UclObject {
entries: IndexMap<String, Entry>,
}
impl UclObject {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
entries: IndexMap::with_capacity(capacity),
}
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn contains_key(&self, key: &str) -> bool {
self.entries.contains_key(key)
}
pub fn get(&self, key: &str) -> Option<&UclValue> {
self.entries.get(key).map(Entry::first)
}
pub fn get_mut(&mut self, key: &str) -> Option<&mut UclValue> {
self.entries.get_mut(key).map(Entry::first_mut)
}
pub fn get_all(&self, key: &str) -> Values<'_> {
match self.entries.get(key) {
Some(entry) => entry.values(),
None => Values([].iter()),
}
}
pub fn entry(&self, key: &str) -> Option<&Entry> {
self.entries.get(key)
}
pub fn entry_mut(&mut self, key: &str) -> Option<&mut Entry> {
self.entries.get_mut(key)
}
pub fn get_index(&self, index: usize) -> Option<(&String, &Entry)> {
self.entries.get_index(index)
}
pub fn get_index_mut(&mut self, index: usize) -> Option<(&String, &mut Entry)> {
self.entries.get_index_mut(index)
}
pub fn index_of(&self, key: &str) -> Option<usize> {
self.entries.get_index_of(key)
}
pub fn rename_key(&mut self, old: &str, new: impl Into<String>) -> bool {
match self.entries.get_index_of(old) {
Some(index) => self.entries.replace_index(index, new.into()).is_ok(),
None => false,
}
}
pub fn insert(&mut self, key: impl Into<String>, value: UclValue) -> Option<Entry> {
self.entries.insert(key.into(), Entry::new(value))
}
pub fn insert_entry(&mut self, key: impl Into<String>, entry: Entry) -> Option<Entry> {
self.entries.insert(key.into(), entry)
}
pub fn append(&mut self, key: impl Into<String>, value: UclValue) {
let key = key.into();
match self.entries.get_mut(&key) {
Some(entry) => entry.push(value),
None => {
self.entries.insert(key, Entry::new(value));
}
}
}
pub fn remove(&mut self, key: &str) -> Option<Entry> {
self.entries.shift_remove(key)
}
pub fn remove_index(&mut self, index: usize) -> Option<(String, Entry)> {
self.entries.shift_remove_index(index)
}
pub fn iter(&self) -> indexmap::map::Iter<'_, String, Entry> {
self.entries.iter()
}
pub fn iter_mut(&mut self) -> indexmap::map::IterMut<'_, String, Entry> {
self.entries.iter_mut()
}
pub fn keys(&self) -> indexmap::map::Keys<'_, String, Entry> {
self.entries.keys()
}
pub fn entries(&self) -> indexmap::map::Values<'_, String, Entry> {
self.entries.values()
}
pub fn insert_with_strategy(
&mut self,
key: impl Into<String>,
value: UclValue,
priority: u8,
strategy: DuplicateStrategy,
flags: ParserFlags,
) -> Result<(), DuplicateKeyError> {
self.insert_slot_with_strategy(key, Slot::new(value, priority), strategy, flags)
}
pub fn insert_slot_with_strategy(
&mut self,
key: impl Into<String>,
slot: Slot,
strategy: DuplicateStrategy,
flags: ParserFlags,
) -> Result<(), DuplicateKeyError> {
self.insert_slot_placed(key, slot, strategy, flags)
.map(|_| ())
}
pub fn insert_slot_placed(
&mut self,
key: impl Into<String>,
slot: Slot,
strategy: DuplicateStrategy,
flags: ParserFlags,
) -> Result<Placement, DuplicateKeyError> {
let mut key = key.into();
if flags.contains(ParserFlags::KEY_LOWERCASE) {
key.make_ascii_lowercase();
}
let Some(entry) = self.entries.get_mut(&key) else {
self.entries.insert(key, Entry::from_slot(slot));
return Ok(Placement::Slot(0));
};
match strategy {
DuplicateStrategy::Append => entry.add_by_priority(&key, slot, true, flags),
DuplicateStrategy::Merge => entry.merge(&key, slot, flags),
DuplicateStrategy::Rewrite => {
*entry = Entry::from_slot(slot);
Ok(Placement::Slot(0))
}
DuplicateStrategy::Error => Err(DuplicateKeyError { key }),
}
}
}
impl std::ops::Index<&str> for UclObject {
type Output = UclValue;
fn index(&self, key: &str) -> &UclValue {
self.get(key)
.unwrap_or_else(|| panic!("key '{key}' not found in UCL object"))
}
}
impl IntoIterator for UclObject {
type Item = (String, Entry);
type IntoIter = indexmap::map::IntoIter<String, Entry>;
fn into_iter(self) -> Self::IntoIter {
self.entries.into_iter()
}
}
impl<'a> IntoIterator for &'a UclObject {
type Item = (&'a String, &'a Entry);
type IntoIter = indexmap::map::Iter<'a, String, Entry>;
fn into_iter(self) -> Self::IntoIter {
self.entries.iter()
}
}
impl<K: Into<String>> FromIterator<(K, UclValue)> for UclObject {
fn from_iter<I: IntoIterator<Item = (K, UclValue)>>(iter: I) -> Self {
let mut object = UclObject::new();
for (k, v) in iter {
object.append(k, v);
}
object
}
}
impl<K: Into<String>> Extend<(K, UclValue)> for UclObject {
fn extend<I: IntoIterator<Item = (K, UclValue)>>(&mut self, iter: I) {
for (k, v) in iter {
self.append(k, v);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn int(i: i64) -> UclValue {
UclValue::Integer(i)
}
fn values(obj: &UclObject, key: &str) -> Vec<UclValue> {
obj.get_all(key).cloned().collect()
}
#[test]
fn clone_keeps_order_priorities_and_marks() {
let mut inner = UclObject::new();
inner.insert_entry("z", Entry::from_slot(Slot::inherited(int(1), 3)));
inner.append("y", UclValue::Time(1.5));
inner.append("y", UclValue::Array(vec![int(2), UclValue::Null]));
let mut obj = UclObject::new();
obj.insert("b", UclValue::Object(inner));
obj.insert_entry(
"a",
Entry::from_slot(Slot::collection(UclValue::Array(vec![
UclValue::String("s".into()),
UclValue::Array(vec![]),
UclValue::Object(UclObject::new()),
]))),
);
obj.append("a", UclValue::Float(-0.0));
let value = UclValue::Object(obj);
let copy = value.clone();
assert_eq!(copy, value);
let (copy, value) = (copy.as_object().unwrap(), value.as_object().unwrap());
assert_eq!(copy.keys().collect::<Vec<_>>(), ["b", "a"]);
let inner = copy["b"].as_object().unwrap();
assert_eq!(inner.keys().collect::<Vec<_>>(), ["z", "y"]);
let z = &inner.entry("z").unwrap().slots()[0];
assert_eq!((z.priority(), z.is_inherited()), (3, true));
assert!(copy.entry("a").unwrap().slots()[0].is_collected());
assert!(value.entry("a").unwrap().slots()[0].is_collected());
}
#[test]
fn clone_does_not_recurse() {
std::thread::Builder::new()
.stack_size(2 << 20)
.spawn(|| {
let mut value = int(1);
for depth in 0..20_000 {
value = if depth % 2 == 0 {
UclValue::Array(vec![value])
} else {
UclValue::Object([("k", value)].into_iter().collect())
};
}
let copy = value.clone();
assert_eq!(nesting(©), 20_000);
assert!(copy == value);
std::mem::forget((value, copy));
})
.unwrap()
.join()
.unwrap();
}
#[test]
fn equality_is_that_of_the_derived_comparisons() {
let obj = |entries: Vec<(&str, Vec<Slot>)>| {
let mut o = UclObject::new();
for (key, slots) in entries {
let mut slots = slots.into_iter();
let mut entry = Entry::from_slot(slots.next().unwrap());
slots.for_each(|slot| entry.push_slot(slot));
o.insert_entry(key.to_string(), entry);
}
UclValue::Object(o)
};
let s = |v: UclValue| Slot::new(v, 0);
let a = obj(vec![
("x", vec![s(int(1))]),
("y", vec![s(int(2)), s(int(3))]),
]);
let b = obj(vec![
("y", vec![s(int(2)), s(int(3))]),
("x", vec![s(int(1))]),
]);
assert!(a == b);
let c = obj(vec![
("x", vec![s(int(1))]),
("y", vec![s(int(3)), s(int(2))]),
]);
assert!(a != c);
let d = obj(vec![
("x", vec![Slot::new(int(1), 2)]),
("y", vec![s(int(2)), s(int(3))]),
]);
assert!(a != d);
let e = obj(vec![
("x", vec![Slot::inherited(int(1), 0)]),
("y", vec![s(int(2)), s(int(3))]),
]);
assert!(a != e);
let f = obj(vec![
("x", vec![Slot::collection(int(1))]),
("y", vec![s(int(2)), s(int(3))]),
]);
assert!(a != f);
assert!(a != obj(vec![("x", vec![s(int(1))])]));
assert!(obj(vec![("x", vec![s(int(1))])]) != a);
assert!(int(1) != UclValue::Float(1.0));
assert!(UclValue::Float(1.0) != UclValue::Time(1.0));
assert!(UclValue::Time(1.5) == UclValue::Time(1.5));
assert!(UclValue::Float(f64::NAN) != UclValue::Float(f64::NAN));
assert!(
UclValue::Array(vec![UclValue::Float(f64::NAN)])
!= UclValue::Array(vec![UclValue::Float(f64::NAN)])
);
let arr = |items: Vec<UclValue>| UclValue::Array(items);
assert!(arr(vec![int(1), arr(vec![])]) == arr(vec![int(1), arr(vec![])]));
assert!(arr(vec![int(1)]) != arr(vec![int(1), int(1)]));
assert!(arr(vec![arr(vec![int(1)])]) != arr(vec![arr(vec![int(2)])]));
let (UclValue::Object(x), UclValue::Object(y)) = (&a, &b) else {
unreachable!()
};
assert!(x == y);
assert!(x.entry("y") == y.entry("y"));
assert!(x.entry("x") != y.entry("y"));
assert!(x.entry("y").unwrap().slots()[0] == y.entry("y").unwrap().slots()[0]);
assert!(x.entry("y").unwrap().slots()[0] != y.entry("y").unwrap().slots()[1]);
}
fn insert(obj: &mut UclObject, key: &str, v: UclValue, pri: u8, s: DuplicateStrategy) {
obj.insert_with_strategy(key, v, pri, s, ParserFlags::DEFAULT)
.unwrap();
}
#[test]
fn test_append_equal_priority_makes_implicit_array() {
let mut obj = UclObject::new();
insert(&mut obj, "k", int(1), 0, DuplicateStrategy::Append);
insert(&mut obj, "k", int(2), 0, DuplicateStrategy::Append);
assert_eq!(values(&obj, "k"), vec![int(1), int(2)]);
assert!(obj.entry("k").unwrap().is_multi());
assert_eq!(obj.get("k"), Some(&int(1)));
}
#[test]
fn test_explicit_array_is_not_flattened() {
let mut obj = UclObject::new();
insert(
&mut obj,
"k",
UclValue::Array(vec![int(1), int(2)]),
0,
DuplicateStrategy::Append,
);
insert(&mut obj, "k", int(3), 0, DuplicateStrategy::Append);
assert_eq!(
values(&obj, "k"),
vec![UclValue::Array(vec![int(1), int(2)]), int(3)]
);
}
#[test]
fn test_append_keeps_key_position() {
let mut obj = UclObject::new();
insert(&mut obj, "a", int(1), 0, DuplicateStrategy::Append);
insert(&mut obj, "b", int(2), 0, DuplicateStrategy::Append);
insert(&mut obj, "a", int(3), 0, DuplicateStrategy::Append);
assert_eq!(obj.keys().collect::<Vec<_>>(), vec!["a", "b"]);
}
#[test]
fn test_append_priorities() {
let mut obj = UclObject::new();
insert(&mut obj, "k", int(1), 2, DuplicateStrategy::Append);
insert(&mut obj, "k", int(2), 1, DuplicateStrategy::Append);
assert_eq!(values(&obj, "k"), vec![int(1)]);
insert(&mut obj, "k", int(3), 0, DuplicateStrategy::Append);
insert(&mut obj, "k", int(4), 5, DuplicateStrategy::Append);
assert_eq!(values(&obj, "k"), vec![int(4)]);
assert_eq!(obj.entry("k").unwrap().slots()[0].priority(), 5);
}
#[test]
fn test_priority_is_masked_to_four_bits() {
assert_eq!(Slot::new(UclValue::Null, 0x1f).priority(), 0x0f);
}
#[test]
fn test_inherited_value_is_always_replaced() {
let mut obj = UclObject::new();
obj.insert_slot_with_strategy(
"k",
Slot::inherited(int(1), 3),
DuplicateStrategy::Append,
ParserFlags::DEFAULT,
)
.unwrap();
insert(&mut obj, "k", int(2), 0, DuplicateStrategy::Append);
assert_eq!(values(&obj, "k"), vec![int(2)]);
}
#[test]
fn test_rewrite_and_error() {
let mut obj = UclObject::new();
insert(&mut obj, "k", int(1), 5, DuplicateStrategy::Rewrite);
insert(&mut obj, "k", int(2), 0, DuplicateStrategy::Rewrite);
assert_eq!(values(&obj, "k"), vec![int(2)]);
let err = obj
.insert_with_strategy(
"k",
int(3),
0,
DuplicateStrategy::Error,
ParserFlags::DEFAULT,
)
.unwrap_err();
assert_eq!(
err,
DuplicateKeyError {
key: "k".to_string()
}
);
assert_eq!(values(&obj, "k"), vec![int(2)]);
}
#[test]
fn test_merge_objects_and_arrays() {
let mut first = UclObject::new();
first.insert("a", int(1));
let mut second = UclObject::new();
second.insert("b", int(2));
second.insert("a", int(3));
let mut obj = UclObject::new();
insert(
&mut obj,
"o",
UclValue::Object(first),
0,
DuplicateStrategy::Merge,
);
insert(
&mut obj,
"o",
UclValue::Object(second),
0,
DuplicateStrategy::Merge,
);
let merged = obj.get("o").unwrap().as_object().unwrap();
assert_eq!(values(merged, "a"), vec![int(1), int(3)]);
assert_eq!(values(merged, "b"), vec![int(2)]);
assert_eq!(obj.entry("o").unwrap().len(), 1);
insert(
&mut obj,
"arr",
UclValue::Array(vec![int(1)]),
0,
DuplicateStrategy::Merge,
);
insert(
&mut obj,
"arr",
UclValue::Array(vec![int(2)]),
0,
DuplicateStrategy::Merge,
);
assert_eq!(obj.get("arr"), Some(&UclValue::Array(vec![int(1), int(2)])));
}
#[test]
fn test_no_implicit_arrays_collects_into_one_explicit_array() {
let flags = ParserFlags::NO_IMPLICIT_ARRAYS;
let mut obj = UclObject::new();
for i in 1..=3 {
obj.insert_with_strategy("k", int(i), 0, DuplicateStrategy::Append, flags)
.unwrap();
}
assert_eq!(
values(&obj, "k"),
vec![UclValue::Array(vec![int(1), int(2), int(3)])]
);
let mut obj = UclObject::new();
let written = UclValue::Array(vec![int(1), int(2)]);
obj.insert_with_strategy("k", written.clone(), 0, DuplicateStrategy::Append, flags)
.unwrap();
obj.insert_with_strategy("k", int(3), 0, DuplicateStrategy::Append, flags)
.unwrap();
assert_eq!(
values(&obj, "k"),
vec![UclValue::Array(vec![written, int(3)])]
);
}
#[test]
fn test_key_lowercase_flag() {
let mut obj = UclObject::new();
obj.insert_with_strategy(
"ALIAS",
int(1),
0,
DuplicateStrategy::Append,
ParserFlags::KEY_LOWERCASE,
)
.unwrap();
assert!(obj.contains_key("alias"));
assert!(!obj.contains_key("ALIAS"));
}
fn object(pairs: &[(&str, UclValue)]) -> UclValue {
UclValue::Object(pairs.iter().cloned().collect())
}
fn priorities(obj: &UclObject, key: &str) -> Vec<u8> {
obj.entry(key)
.unwrap()
.slots()
.iter()
.map(Slot::priority)
.collect()
}
fn insert_slot(obj: &mut UclObject, key: &str, slot: Slot, s: DuplicateStrategy) {
obj.insert_slot_with_strategy(key, slot, s, ParserFlags::DEFAULT)
.unwrap();
}
#[test]
fn test_rewrite_takes_the_new_priority() {
let mut obj = UclObject::new();
insert(&mut obj, "k", int(1), 3, DuplicateStrategy::Rewrite);
insert(&mut obj, "k", int(2), 1, DuplicateStrategy::Rewrite);
assert_eq!(values(&obj, "k"), vec![int(2)]);
assert_eq!(priorities(&obj, "k"), vec![1]);
}
#[test]
fn test_error_strategy_rejects_repeat_of_inherited_value() {
let mut obj = UclObject::new();
insert_slot(
&mut obj,
"k",
Slot::inherited(int(1), 0),
DuplicateStrategy::Error,
);
let err = obj
.insert_with_strategy(
"k",
int(2),
0,
DuplicateStrategy::Error,
ParserFlags::DEFAULT,
)
.unwrap_err();
assert_eq!(err.key, "k");
assert_eq!(values(&obj, "k"), vec![int(1)]);
}
#[test]
fn test_merge_container_then_scalar_keeps_container_priority() {
let mut obj = UclObject::new();
insert(
&mut obj,
"a",
object(&[("x", int(1))]),
3,
DuplicateStrategy::Merge,
);
insert(&mut obj, "a", int(2), 1, DuplicateStrategy::Merge);
assert_eq!(values(&obj, "a"), vec![int(2)]);
assert_eq!(priorities(&obj, "a"), vec![3]);
insert(
&mut obj,
"b",
UclValue::Array(vec![int(1)]),
1,
DuplicateStrategy::Merge,
);
insert(&mut obj, "b", int(2), 3, DuplicateStrategy::Merge);
assert_eq!(values(&obj, "b"), vec![int(2)]);
assert_eq!(priorities(&obj, "b"), vec![1]);
}
#[test]
fn test_merge_container_type_mismatch_is_an_error() {
let arr = UclValue::Array(vec![int(1)]);
let obj_value = object(&[("x", int(1))]);
for (first, second) in [(arr.clone(), obj_value.clone()), (obj_value, arr)] {
let mut obj = UclObject::new();
insert(&mut obj, "a", first, 0, DuplicateStrategy::Merge);
let err = obj
.insert_with_strategy(
"a",
second,
0,
DuplicateStrategy::Merge,
ParserFlags::DEFAULT,
)
.unwrap_err();
assert_eq!(err.key, "a");
}
}
#[test]
fn test_merge_arrays_keep_the_existing_priority() {
let mut obj = UclObject::new();
insert(
&mut obj,
"a",
UclValue::Array(vec![int(1)]),
3,
DuplicateStrategy::Merge,
);
insert(
&mut obj,
"a",
UclValue::Array(vec![int(2)]),
1,
DuplicateStrategy::Merge,
);
assert_eq!(
values(&obj, "a"),
vec![UclValue::Array(vec![int(1), int(2)])]
);
assert_eq!(priorities(&obj, "a"), vec![3]);
}
#[test]
fn test_merge_nested_values_resolve_by_their_own_priority() {
let mut inner = UclObject::new();
insert(&mut inner, "a", int(1), 3, DuplicateStrategy::Append);
let mut obj = UclObject::new();
insert(
&mut obj,
"x",
UclValue::Object(inner),
3,
DuplicateStrategy::Append,
);
let mut lower = UclObject::new();
insert(&mut lower, "b", int(2), 1, DuplicateStrategy::Append);
insert(
&mut obj,
"x",
UclValue::Object(lower),
1,
DuplicateStrategy::Merge,
);
let mut higher = UclObject::new();
insert(&mut higher, "a", int(9), 5, DuplicateStrategy::Append);
insert(
&mut obj,
"x",
UclValue::Object(higher),
5,
DuplicateStrategy::Merge,
);
assert_eq!(priorities(&obj, "x"), vec![3]);
let merged = obj.get("x").unwrap().as_object().unwrap();
assert_eq!(merged.keys().collect::<Vec<_>>(), vec!["a", "b"]);
assert_eq!(values(merged, "a"), vec![int(9)]);
assert_eq!(priorities(merged, "a"), vec![5]);
assert_eq!(priorities(merged, "b"), vec![1]);
}
#[test]
fn test_merge_uses_only_the_first_value() {
let mut obj = UclObject::new();
insert(
&mut obj,
"a",
object(&[("x", int(1))]),
0,
DuplicateStrategy::Append,
);
insert(
&mut obj,
"a",
object(&[("y", int(2))]),
0,
DuplicateStrategy::Append,
);
insert(
&mut obj,
"a",
object(&[("z", int(3))]),
0,
DuplicateStrategy::Merge,
);
assert_eq!(
values(&obj, "a"),
vec![
object(&[("x", int(1)), ("z", int(3))]),
object(&[("y", int(2))])
]
);
insert(&mut obj, "a", int(5), 0, DuplicateStrategy::Merge);
assert_eq!(values(&obj, "a"), vec![int(5), object(&[("y", int(2))])]);
}
#[test]
fn test_merge_scalar_first_follows_append_priorities() {
let mut obj = UclObject::new();
insert(&mut obj, "a", int(1), 2, DuplicateStrategy::Merge);
insert(
&mut obj,
"a",
object(&[("y", int(2))]),
2,
DuplicateStrategy::Merge,
);
insert(&mut obj, "a", int(3), 1, DuplicateStrategy::Merge);
assert_eq!(values(&obj, "a"), vec![int(1), object(&[("y", int(2))])]);
insert(&mut obj, "a", int(4), 5, DuplicateStrategy::Merge);
assert_eq!(values(&obj, "a"), vec![int(4)]);
}
#[test]
fn test_merge_does_not_replace_inherited_values() {
let mut obj = UclObject::new();
insert_slot(
&mut obj,
"s",
Slot::inherited(int(1), 0),
DuplicateStrategy::Append,
);
insert(&mut obj, "s", int(2), 0, DuplicateStrategy::Merge);
assert_eq!(values(&obj, "s"), vec![int(1), int(2)]);
let inherited = Slot::inherited(object(&[("x", int(1))]), 0);
insert_slot(&mut obj, "o", inherited, DuplicateStrategy::Append);
insert(
&mut obj,
"o",
object(&[("y", int(2))]),
0,
DuplicateStrategy::Merge,
);
assert_eq!(
values(&obj, "o"),
vec![object(&[("x", int(1)), ("y", int(2))])]
);
insert(
&mut obj,
"o",
object(&[("z", int(3))]),
0,
DuplicateStrategy::Append,
);
assert_eq!(values(&obj, "o"), vec![object(&[("z", int(3))])]);
}
#[test]
fn test_no_implicit_arrays_priorities() {
let flags = ParserFlags::NO_IMPLICIT_ARRAYS;
let mut obj = UclObject::new();
for (v, pri) in [(1, 0), (2, 3), (3, 1), (4, 3)] {
obj.insert_with_strategy("a", int(v), pri, DuplicateStrategy::Append, flags)
.unwrap();
}
assert_eq!(
values(&obj, "a"),
vec![UclValue::Array(vec![int(2), int(4)])]
);
assert_eq!(priorities(&obj, "a"), vec![0]);
obj.insert_with_strategy("a", int(5), 3, DuplicateStrategy::Append, flags)
.unwrap();
assert_eq!(values(&obj, "a"), vec![int(5)]);
assert_eq!(priorities(&obj, "a"), vec![3]);
let mut obj = UclObject::new();
obj.insert_slot_with_strategy(
"a",
Slot::inherited(int(1), 0),
DuplicateStrategy::Append,
flags,
)
.unwrap();
obj.insert_with_strategy("a", int(2), 0, DuplicateStrategy::Append, flags)
.unwrap();
assert_eq!(values(&obj, "a"), vec![int(2)]);
}
#[test]
fn test_no_implicit_arrays_under_merge() {
let flags = ParserFlags::NO_IMPLICIT_ARRAYS;
let merge = DuplicateStrategy::Merge;
let mut obj = UclObject::new();
for v in [int(1), int(2), UclValue::Array(vec![int(3)])] {
obj.insert_with_strategy("b", v, 0, merge, flags).unwrap();
}
assert_eq!(
values(&obj, "b"),
vec![UclValue::Array(vec![int(1), int(2), int(3)])]
);
obj.insert_with_strategy("b", int(5), 0, merge, flags)
.unwrap();
assert_eq!(values(&obj, "b"), vec![int(5)]);
assert!(
obj.insert_with_strategy("b", int(6), 0, DuplicateStrategy::Append, flags)
.is_err()
);
}
#[test]
fn test_key_lowercase_merges_keys_that_differ_in_case() {
let flags = ParserFlags::KEY_LOWERCASE;
let mut obj = UclObject::new();
for key in ["A", "a", "É"] {
obj.insert_with_strategy(key, int(1), 0, DuplicateStrategy::Append, flags)
.unwrap();
}
assert_eq!(obj.keys().collect::<Vec<_>>(), vec!["a", "É"]);
assert_eq!(values(&obj, "a"), vec![int(1), int(1)]);
}
#[test]
fn test_entry_into_value_views_implicit_array_as_sequence() {
let mut entry = Entry::new(int(1));
assert_eq!(entry.clone().into_value(), int(1));
entry.push(int(2));
assert_eq!(entry.into_value(), UclValue::Array(vec![int(1), int(2)]));
}
#[test]
fn test_insert_slot_placed_reports_where_the_value_went() {
let append = DuplicateStrategy::Append;
let flags = ParserFlags::DEFAULT;
let mut obj = UclObject::new();
let place = |obj: &mut UclObject, v, pri, s, f| {
obj.insert_slot_placed("k", Slot::new(v, pri), s, f)
.unwrap()
};
assert_eq!(
place(&mut obj, int(1), 1, append, flags),
Placement::Slot(0)
);
assert_eq!(
place(&mut obj, int(2), 1, append, flags),
Placement::Slot(1)
);
assert_eq!(
place(&mut obj, int(3), 0, append, flags),
Placement::Dropped
);
assert_eq!(
place(&mut obj, int(4), 2, append, flags),
Placement::Slot(0)
);
let merge = DuplicateStrategy::Merge;
let empty = || UclValue::Object(UclObject::new());
let mut obj = UclObject::new();
assert_eq!(
place(&mut obj, empty(), 0, merge, flags),
Placement::Slot(0)
);
assert_eq!(place(&mut obj, empty(), 0, merge, flags), Placement::Merged);
let nia = ParserFlags::NO_IMPLICIT_ARRAYS;
let mut obj = UclObject::new();
assert_eq!(place(&mut obj, int(1), 0, append, nia), Placement::Slot(0));
assert_eq!(
place(&mut obj, empty(), 0, append, nia),
Placement::Collected(1)
);
assert_eq!(
place(&mut obj, empty(), 0, append, nia),
Placement::Collected(2)
);
assert!(
obj.entry_mut("k")
.unwrap()
.value_at_mut(0)
.unwrap()
.is_array()
);
}
#[test]
fn test_parser_flags_match_libucl_bits() {
assert_eq!(ParserFlags::KEY_LOWERCASE.bits(), 1);
assert_eq!(ParserFlags::NO_FILEVARS.bits(), 64);
let flags = ParserFlags::NO_TIME | ParserFlags::DISABLE_MACRO;
assert!(flags.contains(ParserFlags::NO_TIME));
assert!(!flags.contains(ParserFlags::ZEROCOPY));
assert_eq!(ParserFlags::from_bits_truncate(0xffff).bits(), 127);
}
}