molframe-query 0.3.0

Typed and textual structural selection plans for molframe
Documentation
use super::*;
use crate::ast::GeometricExpr;

fn expression(source: &str) -> Expr {
    match parse(source) {
        Ok(parsed) => parsed.expr,
        Err(findings) => panic!("parse failed: {findings:?}"),
    }
}

#[test]
fn and_binds_more_tightly_than_or() {
    let parsed = expression("all or none and all");
    assert!(matches!(
        parsed,
        Expr::Or(_, right) if matches!(*right, Expr::And(_, _))
    ));
}

#[test]
fn a_mixed_unparenthesised_boolean_expression_warns() {
    let parsed = match parse("all or none and all") {
        Ok(parsed) => parsed,
        Err(findings) => panic!("parse failed: {findings:?}"),
    };
    assert!(
        parsed
            .warnings
            .iter()
            .any(|warning| warning.code() == Code::W4001)
    );
    let parenthesised = match parse("all or (none and all)") {
        Ok(parsed) => parsed,
        Err(findings) => panic!("parse failed: {findings:?}"),
    };
    assert!(
        !parenthesised
            .warnings
            .iter()
            .any(|warning| warning.code() == Code::W4001)
    );
}

#[test]
fn geometric_modifiers_build_typed_nodes() {
    assert!(matches!(
        expression("within 5 of (resname ATP)"),
        Expr::Geometric(GeometricExpr::Within { radius, .. }) if (radius - 5.0).abs() < f32::EPSILON
    ));
    assert!(matches!(
        expression("point 1 2 3 4"),
        Expr::Geometric(GeometricExpr::Point { point: [x, y, z], radius })
            if (x - 1.0).abs() < f32::EPSILON
                && (y - 2.0).abs() < f32::EPSILON
                && (z - 3.0).abs() < f32::EPSILON
                && (radius - 4.0).abs() < f32::EPSILON
    ));
}

#[test]
fn membership_comparison_range_and_escaped_keyword_parse() {
    assert!(matches!(
        expression("name CA CB"),
        Expr::Membership { column: Column::AtomName, values } if values.len() == 2
    ));
    assert!(matches!(
        expression("occupancy >= 0.5"),
        Expr::Comparison {
            column: Column::Occupancy,
            operator: Operator::GreaterEqual,
            ..
        }
    ));
    assert!(matches!(
        expression("resid 10:20"),
        Expr::Membership { column: Column::ResidueId, values } if values[0].as_ref() == "10:20"
    ));
    assert!(matches!(
        expression("resname \\water"),
        Expr::Membership { values, .. } if values[0].as_ref() == "water"
    ));
}

#[test]
fn flipped_comparisons_are_normalised() {
    assert!(matches!(
        expression("prop 5 > x"),
        Expr::Comparison { column: Column::X, operator: Operator::Less, value, .. }
            if (value - 5.0).abs() < f64::EPSILON
    ));
}

#[test]
fn malformed_and_unknown_selections_return_registered_diagnostics() {
    assert_eq!(
        parse("within nope of all")
            .err()
            .and_then(|findings| findings.first().map(Diagnostic::code)),
        Some(Code::E4002)
    );
    assert_eq!(
        parse("wibble A")
            .err()
            .and_then(|findings| findings.first().map(Diagnostic::code)),
        Some(Code::E4004)
    );
}

fn span_of(source: &str) -> (u64, u64) {
    let Err(findings) = parse(source) else {
        panic!("{source:?} should not parse");
    };
    let Some(span) = findings[0].span() else {
        panic!("{source:?} produced an unlocated finding: {findings:?}");
    };
    (span.start.byte_offset, span.end)
}

#[test]
fn an_unknown_keyword_is_located_at_the_keyword() {
    assert_eq!(span_of("protein and resnam HEM"), (12, 18));
}

#[test]
fn a_missing_closing_parenthesis_points_past_the_end() {
    assert_eq!(span_of("(protein and water"), (18, 18));
}

#[test]
fn a_bad_number_is_located_at_the_number() {
    assert_eq!(span_of("within five of protein"), (7, 11));
}

#[test]
fn an_empty_selection_is_located_at_the_end_of_the_source() {
    assert_eq!(span_of("   "), (3, 3));
}

#[test]
fn a_trailing_token_is_located() {
    assert_eq!(span_of("protein )"), (8, 9));
}

#[test]
fn a_malformed_dollar_reference_is_a_located_syntax_error() {
    assert_eq!(span_of("protein or $9x"), (11, 14));
}

#[test]
fn a_dollar_name_is_a_group_reference() {
    assert!(matches!(expression("$pocket"), Expr::Group(name) if &*name == "pocket"));
}