sim-codec 0.1.6

SIM workspace package for sim codec.
Documentation
use std::sync::Arc;

use sim_kernel::{Cx, DefaultFactory, Expr, NoopEvalPolicy, Symbol};
use sim_shape::{ExprKind, ExprKindShape, Shape, TableExtraPolicy, TableFieldSpec, TableShape};

use super::domain_form::{DomainFormError, DomainValue, format_domain_form, parse_domain_form};

#[test]
fn projects_domain_values_to_expr_tree() {
    let form = parse_domain_form(
        "#(Chord 60 \"lead\" pitches=[60,\"64\",#(Note dur=1/4)] inner=#(Rest dur=1/8))",
    )
    .expect("parse");

    assert_eq!(
        form.to_expr_map(),
        Expr::Map(vec![
            (
                Expr::Symbol(Symbol::new("form")),
                Expr::String("Chord".to_owned()),
            ),
            (
                Expr::Symbol(Symbol::new("args")),
                Expr::List(vec![
                    Expr::String("60".to_owned()),
                    Expr::String("lead".to_owned()),
                ]),
            ),
            (
                Expr::Symbol(Symbol::new("pitches")),
                Expr::List(vec![
                    Expr::String("60".to_owned()),
                    Expr::String("64".to_owned()),
                    Expr::Map(vec![
                        (
                            Expr::Symbol(Symbol::new("form")),
                            Expr::String("Note".to_owned()),
                        ),
                        (
                            Expr::Symbol(Symbol::new("dur")),
                            Expr::String("1/4".to_owned()),
                        ),
                    ]),
                ]),
            ),
            (
                Expr::Symbol(Symbol::new("inner")),
                Expr::Map(vec![
                    (
                        Expr::Symbol(Symbol::new("form")),
                        Expr::String("Rest".to_owned()),
                    ),
                    (
                        Expr::Symbol(Symbol::new("dur")),
                        Expr::String("1/8".to_owned()),
                    ),
                ]),
            ),
        ])
    );
}

#[test]
fn projected_domain_form_is_accepted_by_table_shape() {
    let form = parse_domain_form("#(Note dur=1/4)").expect("parse");
    let expr = form.to_expr_map();
    let string_shape = || Arc::new(ExprKindShape::new(ExprKind::String));
    let shape = TableShape::new(
        vec![
            TableFieldSpec {
                key: Symbol::new("form"),
                shape: string_shape(),
                required: true,
            },
            TableFieldSpec {
                key: Symbol::new("dur"),
                shape: string_shape(),
                required: true,
            },
        ],
        TableExtraPolicy::Reject,
    );
    let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));

    assert!(shape.check_expr(&mut cx, &expr).unwrap().accepted);
}

#[test]
fn parses_keyed_form_with_list_and_nested_form() {
    let form = parse_domain_form("#(Note dur=4/4 pitch=C4 tags=[a,b] inner=#(Rest dur=1/4))")
        .expect("parse");
    assert_eq!(form.name, "Note");
    assert_eq!(form.atom("dur").unwrap(), "4/4");
    assert_eq!(form.atom("pitch").unwrap(), "C4");
    assert_eq!(
        form.list("tags").unwrap(),
        &[
            DomainValue::Atom("a".to_owned()),
            DomainValue::Atom("b".to_owned())
        ]
    );
    assert_eq!(form.form("inner").unwrap().name, "Rest");
}

#[test]
fn parses_positional_values() {
    let form = parse_domain_form("#(Chord 60 64 67)").expect("parse");
    assert_eq!(form.positional.len(), 3);
    assert!(form.fields.is_empty());
}

#[test]
fn round_trips_through_format() {
    let source = "#(Note dur=4/4 sym=\"a\\\"b\" pitches=[60,64])";
    let form = parse_domain_form(source).expect("parse");
    let rendered = format_domain_form(&form);
    assert_eq!(parse_domain_form(&rendered).unwrap(), form);
}

#[test]
fn missing_close_paren_is_unexpected_eof() {
    assert_eq!(
        parse_domain_form("#(Note dur=4/4"),
        Err(DomainFormError::UnexpectedEof)
    );
}

#[test]
fn duplicate_field_is_rejected() {
    assert_eq!(
        parse_domain_form("#(Note dur=4/4 dur=1/2)"),
        Err(DomainFormError::DuplicateField("dur".to_owned()))
    );
}

#[test]
fn trailing_input_is_rejected() {
    assert_eq!(
        parse_domain_form("#(Note dur=4/4) extra"),
        Err(DomainFormError::TrailingInput)
    );
}

#[test]
fn non_form_input_is_rejected() {
    assert_eq!(
        parse_domain_form("Note"),
        Err(DomainFormError::ExpectedForm)
    );
}

#[test]
fn escaped_quotes_round_trip() {
    let form = parse_domain_form("#(S v=\"he said \\\"hi\\\"\")").expect("parse");
    assert_eq!(form.string("v").unwrap(), "he said \"hi\"");
}

#[test]
fn field_atom_or_string_accepts_both_kinds() {
    let form = parse_domain_form("#(N a=bare s=\"quoted\")").expect("parse");
    assert_eq!(form.field_atom_or_string("a").unwrap(), "bare");
    assert_eq!(form.field_atom_or_string("s").unwrap(), "quoted");
}

#[test]
fn field_atom_or_string_rejects_list_and_missing() {
    let form = parse_domain_form("#(N tags=[a,b])").expect("parse");
    assert_eq!(
        form.field_atom_or_string("tags"),
        Err(DomainFormError::WrongFieldKind("tags".to_owned()))
    );
    assert_eq!(
        form.field_atom_or_string("nope"),
        Err(DomainFormError::MissingField("nope".to_owned()))
    );
}

#[test]
fn field_text_renders_every_value_kind() {
    let form = parse_domain_form("#(N a=bare s=\"q\\\"x\" tags=[a,b] inner=#(Rest dur=1/4))")
        .expect("parse");
    assert_eq!(form.field_text("a").unwrap(), "bare");
    assert_eq!(form.field_text("s").unwrap(), "\"q\\\"x\"");
    assert_eq!(form.field_text("tags").unwrap(), "[a,b]");
    assert_eq!(form.field_text("inner").unwrap(), "#(Rest dur=1/4)");
    assert_eq!(
        form.field_text("nope"),
        Err(DomainFormError::MissingField("nope".to_owned()))
    );
}

#[test]
fn value_as_form_and_atom_or_string() {
    let form = parse_domain_form("#(N inner=#(Rest dur=1/4) a=bare s=\"q\")").expect("parse");
    assert_eq!(form.field("inner").unwrap().as_form().unwrap().name, "Rest");
    assert_eq!(
        form.field("a").unwrap().as_form(),
        Err(DomainFormError::WrongValueKind)
    );
    assert_eq!(form.field("a").unwrap().atom_or_string().unwrap(), "bare");
    assert_eq!(form.field("s").unwrap().atom_or_string().unwrap(), "q");
    assert_eq!(
        form.field("inner").unwrap().atom_or_string(),
        Err(DomainFormError::WrongValueKind)
    );
}

#[test]
fn render_text_round_trips_a_form_value() {
    let source = "#(Note dur=4/4 sym=\"a\\\"b\" pitches=[60,64])";
    let form = parse_domain_form(source).expect("parse");
    let value = DomainValue::Form(form.clone());
    let rendered = value.render_text();
    assert_eq!(parse_domain_form(&rendered).unwrap(), form);
}

#[test]
fn render_text_formats_list_and_string_and_atom() {
    assert_eq!(DomainValue::Atom("60".to_owned()).render_text(), "60");
    assert_eq!(
        DomainValue::String("a\"b\\c".to_owned()).render_text(),
        "\"a\\\"b\\\\c\""
    );
    let list = DomainValue::List(vec![
        DomainValue::Atom("a".to_owned()),
        DomainValue::String("b".to_owned()),
    ]);
    assert_eq!(list.render_text(), "[a,\"b\"]");
}