sim-lib-numbers-rational 0.1.0

SIM workspace package for sim lib numbers rational.
Documentation
use std::sync::Arc;

use sim_codec::{Input, decode_with_codec, encode_value_with_codec};
use sim_codec_lisp::{LispCodecLib, encode_object_lisp};
use sim_kernel::{
    CapabilitySet, DefaultFactory, EagerPolicy, EncodeOptions, EncodePosition, Expr, ReadPolicy,
    Symbol, TrustLevel, read_construct_capability,
};

use crate::{
    RationalNumbersLib,
    implementation::{add_symbol, ops::rational_add_value_rule, value::make_rational},
};

fn cx() -> sim_kernel::Cx {
    let mut cx = sim_kernel::Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
    cx.load_lib(&sim_lib_numbers_f64::F64NumbersLib::new())
        .unwrap();
    cx.load_lib(&sim_lib_numbers_i64::I64NumbersLib::new())
        .unwrap();
    cx.load_lib(&sim_lib_numbers_bigint::BigIntNumbersLib::new())
        .unwrap();
    cx.load_lib(&RationalNumbersLib::new()).unwrap();
    cx
}

#[test]
fn rational_addition_stays_exact() {
    let mut cx = cx();
    let value = cx
        .apply_value_number_binary_op(
            &add_symbol(),
            cx.factory()
                .number_literal(Symbol::qualified("numbers", "rational"), "1/2".to_owned())
                .unwrap(),
            cx.factory()
                .number_literal(Symbol::qualified("numbers", "rational"), "1/3".to_owned())
                .unwrap(),
        )
        .unwrap();
    assert_eq!(
        value.object().as_expr(&mut cx).unwrap(),
        Expr::Number(sim_kernel::NumberLiteral {
            domain: Symbol::qualified("numbers", "rational"),
            canonical: "5/6".to_owned(),
        })
    );
}

#[test]
fn mixed_bigint_and_i64_rational_values_add() {
    let mut cx = cx();
    let numerator = cx
        .factory()
        .number_literal(
            Symbol::qualified("numbers", "bigint"),
            "1267650600228229401496703205376".to_owned(),
        )
        .unwrap();
    let denominator = cx
        .factory()
        .number_literal(Symbol::qualified("numbers", "i64"), "3".to_owned())
        .unwrap();
    let rational = make_rational(&mut cx, numerator, denominator).unwrap();
    let one_third = cx
        .factory()
        .number_literal(Symbol::qualified("numbers", "rational"), "1/3".to_owned())
        .unwrap();
    let value = rational_add_value_rule(&mut cx, rational, one_third).unwrap();
    assert_eq!(
        value.object().as_expr(&mut cx).unwrap(),
        Expr::Number(sim_kernel::NumberLiteral {
            domain: Symbol::qualified("numbers", "rational"),
            canonical: "1267650600228229401496703205377/3".to_owned(),
        })
    );
}

#[test]
fn noncompact_rationals_round_trip_as_read_constructs() {
    let mut cx = cx();
    let lisp_id = cx.registry_mut().fresh_codec_id();
    let lisp = LispCodecLib::new(lisp_id).unwrap();
    cx.load_lib(&lisp).unwrap();
    let numerator = cx
        .factory()
        .number_literal(
            Symbol::qualified("numbers", "bigint"),
            "1267650600228229401496703205376".to_owned(),
        )
        .unwrap();
    let denominator = cx
        .factory()
        .number_literal(Symbol::qualified("numbers", "i64"), "3".to_owned())
        .unwrap();
    let value = make_rational(&mut cx, numerator, denominator).unwrap();

    let compact_value = cx
        .factory()
        .number_literal(Symbol::qualified("numbers", "rational"), "1/2".to_owned())
        .unwrap();
    let compact = encode_value_with_codec(
        &mut cx,
        &Symbol::qualified("codec", "lisp"),
        &compact_value,
        EncodeOptions::default(),
    )
    .unwrap()
    .into_text()
    .unwrap();
    assert_eq!(compact, "1/2");

    cx.grant(read_construct_capability());
    let encoded = encode_object_lisp(
        &mut sim_kernel::WriteCx {
            cx: &mut cx,
            codec: lisp_id,
            options: EncodeOptions {
                position: EncodePosition::Quote,
                ..Default::default()
            },
        },
        value,
    )
    .unwrap();
    assert_eq!(
        encoded,
        "#(numbers/Rational 1267650600228229401496703205376 3)"
    );

    let decoded = decode_with_codec(
        &mut cx,
        &Symbol::qualified("codec", "lisp"),
        Input::Text(encoded),
        ReadPolicy {
            trust: TrustLevel::TrustedSource,
            capabilities: CapabilitySet::new().grant(read_construct_capability()),
        },
    )
    .unwrap();
    let Expr::Extension { tag, .. } = decoded else {
        panic!("expected decoded noncompact rational to stay structured");
    };
    assert_eq!(tag, Symbol::qualified("numbers", "Rational"));
}