serpent-serializer 0.1.0

Serialize Lua values to round-trippable Lua source with cycle and shared-reference handling
Documentation
//! Shared helpers for the test suite.

#![allow(dead_code)]

use serpent_serializer::value::{Key, Table, Value};
use std::sync::atomic::{AtomicUsize, Ordering};

/// Strip volatile `--[[table: 0x...]]` and `--[[function: 0x...]]` addresses so
/// address-bearing output can be compared byte-exact. Replaces each address
/// with a fixed placeholder.
#[must_use]
pub fn norm_addr(s: &str) -> String {
    let mut out = String::new();
    let bytes = s.as_bytes();
    let mut i = 0;
    while i < bytes.len() {
        if s[i..].starts_with("0x") {
            out.push_str("0xADDR");
            i += 2;
            while i < bytes.len() && bytes[i].is_ascii_hexdigit() {
                i += 1;
            }
        } else {
            out.push(bytes[i] as char);
            i += 1;
        }
    }
    out
}

/// A fresh function id counter for building test functions.
static FID: AtomicUsize = AtomicUsize::new(1);

/// Build a function value with the given optional bytecode.
#[must_use]
pub fn func(bytecode: Option<Vec<u8>>) -> Value {
    Value::Function(serpent_serializer::value::Func {
        id: FID.fetch_add(1, Ordering::Relaxed),
        bytecode,
    })
}

/// A string value from a byte slice.
#[must_use]
pub fn s(b: &[u8]) -> Value {
    Value::Str(b.to_vec())
}

/// A string key from a byte slice.
#[must_use]
pub fn sk(b: &[u8]) -> Key {
    Key::Str(b.to_vec())
}

/// A numeric key.
#[must_use]
pub fn nk(n: f64) -> Key {
    Key::Number(n)
}

/// Build a table from array values and named entries for concise fixtures.
#[must_use]
pub fn table() -> Table {
    Table::new()
}

/// Deep structural equality with shared-reference awareness. Compares scalars by
/// value and tables by contents, treating already-paired identities as equal to
/// break cycles.
#[must_use]
pub fn deep_eq(a: &Value, b: &Value) -> bool {
    let mut seen: Vec<(usize, usize)> = Vec::new();
    deep_eq_inner(a, b, &mut seen)
}

fn deep_eq_inner(a: &Value, b: &Value, seen: &mut Vec<(usize, usize)>) -> bool {
    match (a, b) {
        (Value::Nil, Value::Nil) => true,
        (Value::Bool(x), Value::Bool(y)) => x == y,
        (Value::Number(x), Value::Number(y)) => x == y || (x.is_nan() && y.is_nan()),
        (Value::Str(x), Value::Str(y)) => x == y,
        (Value::Table(x), Value::Table(y)) => {
            let pair = (x.id(), y.id());
            if seen.contains(&pair) {
                return true;
            }
            seen.push(pair);
            let xe = x.entries();
            let ye = y.entries();
            let present = |list: &[(Key, Value)], k: &Key| {
                list.iter()
                    .find(|(kk, _)| kk.same(k))
                    .map(|(_, v)| v.clone())
            };
            let count = |list: &[(Key, Value)]| {
                list.iter()
                    .filter(|(_, v)| !matches!(v, Value::Nil))
                    .count()
            };
            if count(&xe) != count(&ye) {
                return false;
            }
            for (k, v) in &xe {
                if matches!(v, Value::Nil) {
                    continue;
                }
                match present(&ye, k) {
                    Some(v2) => {
                        if !deep_eq_inner(v, &v2, seen) {
                            return false;
                        }
                    }
                    None => return false,
                }
            }
            true
        }
        _ => false,
    }
}