wolfram-expr 0.6.0-alpha.2

Efficient and ergonomic representation of Wolfram expressions in Rust
Documentation
//! Cross-validation against canonical WXF bytes captured from the Wolfram kernel.
//!
//! For each fixture:
//!   1. The bytes were produced once by `BinarySerialize[expr]` in a real
//!      Wolfram kernel via `tests/fixtures/generate.wls`.
//!   2. We deserialize them with our deserializer and check the resulting [`Expr`]
//!      structurally matches a hand-constructed expected value.
//!
//! This proves that our parser handles the kernel's exact wire output —
//! varint encoding, header bytes, association rule tokens, packed-array
//! element type byte, zlib-compressed `8C:` payloads, etc. — without a
//! runtime `wolframscript` invocation. Re-run the generator script if
//! you add a test case or want to refresh against a newer kernel.

use wolfram_expr::{expr, from_wxf, to_wxf};
use wolfram_expr::{Association, ByteArray, Expr, ExprKind, NumericArray, RuleEntry};

#[path = "fixtures/wxf_kernel_fixtures.rs"]
mod fix;

/// Deserialize kernel WXF bytes and assert they match `expected`.
#[track_caller]
fn assert_parses_to(bytes: &[u8], expected: Expr) {
    let parsed: Expr = from_wxf(bytes).expect("deserialize kernel WXF");
    assert_eq!(parsed, expected);
}

/// Serialize `expr` and assert the bytes are identical to the kernel fixture.
#[track_caller]
fn assert_serializes_to(expr: Expr, fixture: &[u8]) {
    let bytes = to_wxf(&expr, None).expect("serialize WXF");
    assert_eq!(
        bytes.as_slice(),
        fixture,
        "serialized bytes don't match kernel fixture"
    );
}

#[test]
fn integer() {
    assert_parses_to(fix::INTEGER_42, Expr::from(42i64));
    assert_parses_to(fix::INTEGER_NEG_LARGE, Expr::from(-1_234_567_890i64));
}

#[test]
fn real() {
    assert_parses_to(fix::REAL_3_5, Expr::real(3.5));
}

#[test]
fn string() {
    assert_parses_to(fix::STRING_HELLO, Expr::from("hello"));
}

#[test]
fn symbol() {
    // The kernel strips the System` context on the wire — `Plus` arrives bare
    // and the cursor stores it as a context-less Symbol (it does NOT silently
    // re-add System`). User-package symbols like `MyPkg`x` keep their context.
    // `::Plus` is the context-less symbol `Plus` (leading `::` = no context).
    assert_parses_to(fix::SYMBOL_PLUS, expr!(::Plus));
    assert_parses_to(fix::SYMBOL_MYPKG_X, expr!(MyPkg::x));
}

#[test]
fn list() {
    // `List` arrives bare from the kernel (System` stripped), so use the
    // context-less `::List` form — `expr!(System::List[…])` / `Expr::list(…)`
    // would prefix it with System` and not match.
    assert_parses_to(fix::LIST_INTS, expr!(::List[1, 2, 3]));
    assert_parses_to(fix::LIST_EMPTY, expr!(::List[]));

    assert_parses_to(fix::LIST_MIXED, expr!(::List["a", 1, 2.5]));
}

#[test]
fn function_user_context() {
    assert_parses_to(fix::FUNCTION_MYPKG, expr!(MyPkg::myFunc[1, 2, 3]));
}

#[test]
fn association_plain() {
    let a: Association = vec![
        RuleEntry::rule(Expr::from("a"), Expr::from(1)),
        RuleEntry::rule(Expr::from("b"), Expr::from(2)),
    ];
    assert_parses_to(fix::ASSOCIATION_PLAIN, Expr::from(a));
}

#[test]
fn association_with_delayed_rule() {
    let a: Association = vec![
        RuleEntry::rule(Expr::from("a"), Expr::from(1)),
        RuleEntry::rule_delayed(Expr::from("b"), Expr::from(2)),
    ];
    assert_parses_to(fix::ASSOCIATION_DELAYED, Expr::from(a));
}

#[test]
fn byte_array() {
    let ba = Expr::from(ByteArray::from(vec![0u8, 1, 2, 0xff, 0x80]));
    assert_parses_to(fix::BYTE_ARRAY, ba);
}

#[test]
fn numeric_array_int32() {
    let arr = Expr::from(NumericArray::from_slice::<i32>(vec![3], &[10, 20, 30]));
    assert_parses_to(fix::NUMERIC_ARRAY_INT32, arr);
}

//==============================================================================
// expr! macro — build the same expressions with the macro and verify our
// serializer produces bytes identical to the kernel fixtures.
//==============================================================================

#[test]
fn expr_macro_integers() {
    assert_serializes_to(expr!(42), fix::INTEGER_42);
    assert_serializes_to(expr!(-1_234_567_890i64), fix::INTEGER_NEG_LARGE);
}

#[test]
fn expr_macro_real() {
    assert_serializes_to(expr!(3.5f64), fix::REAL_3_5);
}

#[test]
fn expr_macro_string() {
    assert_serializes_to(expr!("hello"), fix::STRING_HELLO);
}

#[test]
fn expr_macro_association() {
    assert_serializes_to(expr!({ "a" -> 1, "b" -> 2 }), fix::ASSOCIATION_PLAIN);
}

#[test]
fn expr_macro_byte_array() {
    let ba = ByteArray::from(vec![0u8, 1, 2, 0xff, 0x80]);
    assert_serializes_to(expr!(ba), fix::BYTE_ARRAY);
}

#[test]
fn expr_macro_numeric_array() {
    let arr = NumericArray::from_slice::<i32>(vec![3], &[10, 20, 30]);
    assert_serializes_to(expr!(arr), fix::NUMERIC_ARRAY_INT32);
}

#[test]
fn compressed_range_100() {
    // Kernel size-optimizes Range[100] into PackedArray[..., "Integer8"]
    // wrapped in an `8C:` zlib-compressed payload — exercises both the
    // compression handling and packed-array decoding.
    let parsed: Expr =
        from_wxf(fix::COMPRESSED_RANGE_100).expect("deserialize compressed kernel WXF");
    let ExprKind::PackedArray(arr) = parsed.kind() else {
        panic!("Range[100] should land as a PackedArray, got {:?}", parsed);
    };
    assert_eq!(arr.dimensions(), &[100]);
    assert_eq!(
        arr.try_as_slice::<i8>(),
        Some((1..=100i8).collect::<Vec<_>>().as_slice())
    );
}