sim-shape 0.1.3

Shape algebra, comparison, and match-hook helpers.
Documentation
use std::sync::Arc;

use sim_kernel::testing::bare_cx as cx;
use sim_kernel::{Expr, Symbol};

use crate::{
    ExprKind, ExprKindShape, FieldShape, FieldSpec, Shape, TableExtraPolicy, TableFieldSpec,
    TableShape,
};

fn point_shape() -> FieldShape {
    FieldShape::new(
        Symbol::new("Point"),
        vec![FieldSpec::required(
            Symbol::new("x"),
            Arc::new(ExprKindShape::new(ExprKind::Bool)),
        )],
    )
}

fn object_expr(entries: Vec<(Expr, Expr)>) -> Expr {
    Expr::Extension {
        tag: Symbol::qualified("expr", "object"),
        payload: Box::new(Expr::Map(entries)),
    }
}

fn bool_field_map() -> Expr {
    Expr::Map(vec![(Expr::Symbol(Symbol::new("x")), Expr::Bool(true))])
}

#[test]
fn field_shape_rejects_duplicate_class_keys() {
    let mut cx = cx();
    let expr = object_expr(vec![
        (
            Expr::Symbol(Symbol::new("class")),
            Expr::Symbol(Symbol::new("Point")),
        ),
        (
            Expr::Symbol(Symbol::new("class")),
            Expr::Symbol(Symbol::new("Point")),
        ),
        (Expr::Symbol(Symbol::new("fields")), bool_field_map()),
    ]);

    let matched = point_shape().check_expr(&mut cx, &expr).unwrap();

    assert!(!matched.accepted);
    assert_eq!(
        matched.diagnostics[0].message,
        "shape-object: duplicate key class"
    );
}

#[test]
fn field_shape_rejects_duplicate_fields_keys() {
    let mut cx = cx();
    let expr = object_expr(vec![
        (
            Expr::Symbol(Symbol::new("class")),
            Expr::Symbol(Symbol::new("Point")),
        ),
        (Expr::Symbol(Symbol::new("fields")), bool_field_map()),
        (Expr::Symbol(Symbol::new("fields")), bool_field_map()),
    ]);

    let matched = point_shape().check_expr(&mut cx, &expr).unwrap();

    assert!(!matched.accepted);
    assert_eq!(
        matched.diagnostics[0].message,
        "shape-object: duplicate key fields"
    );
}

#[test]
fn field_shape_rejects_duplicate_object_field_keys() {
    let mut cx = cx();
    let expr = object_expr(vec![
        (
            Expr::Symbol(Symbol::new("class")),
            Expr::Symbol(Symbol::new("Point")),
        ),
        (
            Expr::Symbol(Symbol::new("fields")),
            Expr::Map(vec![
                (Expr::Symbol(Symbol::new("x")), Expr::Bool(true)),
                (
                    Expr::Symbol(Symbol::new("x")),
                    Expr::String("not-a-bool".to_owned()),
                ),
            ]),
        ),
    ]);

    let matched = point_shape().check_expr(&mut cx, &expr).unwrap();

    assert!(!matched.accepted);
    assert_eq!(
        matched.diagnostics[0].message,
        "shape-object fields: duplicate key x"
    );
}

#[test]
fn anonymous_field_shape_rejects_duplicate_map_keys() {
    let mut cx = cx();
    let expr = Expr::Map(vec![
        (Expr::Symbol(Symbol::new("x")), Expr::Bool(true)),
        (
            Expr::Symbol(Symbol::new("x")),
            Expr::String("not-a-bool".to_owned()),
        ),
    ]);
    let shape = FieldShape::anonymous(vec![FieldSpec::required(
        Symbol::new("x"),
        Arc::new(ExprKindShape::new(ExprKind::Bool)),
    )]);

    let matched = shape.check_expr(&mut cx, &expr).unwrap();

    assert!(!matched.accepted);
    assert_eq!(
        matched.diagnostics[0].message,
        "shape-fields: duplicate key x"
    );
}

#[test]
fn table_shape_rejects_duplicate_required_map_keys() {
    let mut cx = cx();
    let shape = TableShape::single(
        Symbol::new("n"),
        Arc::new(ExprKindShape::new(ExprKind::Bool)),
    );
    let expr = Expr::Map(vec![
        (Expr::Symbol(Symbol::new("n")), Expr::Bool(true)),
        (
            Expr::Symbol(Symbol::new("n")),
            Expr::String("not-a-bool".to_owned()),
        ),
    ]);

    let matched = shape.check_expr(&mut cx, &expr).unwrap();

    assert!(!matched.accepted);
    assert_eq!(
        matched.diagnostics[0].message,
        "shape-table: duplicate key n"
    );
}

#[test]
fn table_shape_rejects_duplicate_optional_table_keys() {
    let mut cx = cx();
    let shape = TableShape::new(
        vec![TableFieldSpec {
            key: Symbol::new("opt"),
            shape: Arc::new(ExprKindShape::new(ExprKind::Bool)),
            required: false,
        }],
        TableExtraPolicy::Allow,
    );
    let table = cx
        .factory()
        .table(vec![
            (Symbol::new("opt"), cx.factory().bool(true).unwrap()),
            (
                Symbol::new("opt"),
                cx.factory().string("not-a-bool".to_owned()).unwrap(),
            ),
        ])
        .unwrap();

    let matched = shape.check_value(&mut cx, table).unwrap();

    assert!(!matched.accepted);
    assert_eq!(
        matched.diagnostics[0].message,
        "shape-table: duplicate key opt"
    );
}