m2-syn 0.4.0

Typed syntax trees, traversal, and parser adapters for Macaulay2
Documentation
//! Integration tests for native cell parser.

use std::sync::Arc;

use m2_syn::{Lex, LexErrorKind, NativeCellParser, NativeCellState, NativeParseError, Spanned};

#[test]
fn a_long_function_remains_typed_until_its_block_closes() {
    let mut parser = NativeCellParser::new();

    assert_eq!(parser.push_line("f = x -> ("), NativeCellState::Incomplete);
    assert!(parser.active_syntax().is_some());

    assert_eq!(parser.push_line("y := x + 1;"), NativeCellState::Incomplete);
    assert_eq!(parser.push_line("y^2"), NativeCellState::Incomplete);
    assert_eq!(
        parser.active_syntax().unwrap().to_m2(),
        "f = x -> (\ny := x + 1;\ny^2"
    );
    assert_eq!(parser.lexer_diagnostics().len(), 1);
    assert_eq!(
        parser.lexer_diagnostics()[0].kind(),
        LexErrorKind::UnterminatedGroup
    );
    assert_eq!(parser.lexer_diagnostics()[0].expected(), Some(")"));

    assert_eq!(parser.push_line(")"), NativeCellState::Complete);
    assert_eq!(parser.active_source(), "");
    assert!(parser.active_syntax().is_none());

    let completed = parser.take_completed();
    assert_eq!(completed.len(), 1);
    assert_eq!(completed[0].source(), "f = x -> (\ny := x + 1;\ny^2\n)\n");
    assert_eq!(
        completed[0].syntax().to_m2(),
        "f = x -> (\ny := x + 1;\ny^2\n)"
    );
    assert_eq!(parser.state(), NativeCellState::Empty);
}

#[test]
fn an_operator_tail_is_incomplete_but_an_explicit_bad_boundary_is_invalid() {
    let mut parser = NativeCellParser::new();
    assert_eq!(parser.push_line("x +"), NativeCellState::Incomplete);
    assert!(matches!(
        parser.parse_error(),
        Some(NativeParseError::MissingOperand { .. })
    ));

    assert_eq!(parser.push_line("1"), NativeCellState::Complete);
    let completed = parser.take_completed();
    assert_eq!(completed[0].source(), "x +\n1\n");
    assert_eq!(completed[0].syntax().to_m2(), "x +\n1");

    let mut parser = NativeCellParser::new();
    assert_eq!(parser.push_line("x +;"), NativeCellState::Invalid);
    assert!(matches!(
        parser.parse_error(),
        Some(NativeParseError::MissingOperand { .. })
    ));
}

#[test]
fn required_control_clauses_continue_across_submitted_lines() {
    let mut parser = NativeCellParser::new();
    assert_eq!(parser.push_line("if x"), NativeCellState::Incomplete);
    assert_eq!(parser.push_line("then y"), NativeCellState::Complete);

    let completed = parser.take_completed();
    assert_eq!(completed.len(), 1);
    assert_eq!(completed[0].source(), "if x\nthen y\n");
    assert_eq!(completed[0].syntax().to_m2(), "if x\nthen y");

    let mut parser = NativeCellParser::new();
    assert_eq!(parser.push_line("1 = x"), NativeCellState::Invalid);
    assert!(matches!(
        parser.parse_error(),
        Some(NativeParseError::InvalidAssignmentTarget { .. })
    ));
}

#[test]
fn a_mismatched_closer_is_invalid_and_can_be_abandoned() {
    let mut parser = NativeCellParser::new();
    assert_eq!(parser.push_line("f("), NativeCellState::Incomplete);
    assert_eq!(parser.push_line("]"), NativeCellState::Invalid);
    assert_eq!(parser.lexer_diagnostics().len(), 1);
    assert_eq!(
        parser.lexer_diagnostics()[0].kind(),
        LexErrorKind::UnexpectedClosingDelimiter
    );

    parser.reset_active();
    assert_eq!(parser.state(), NativeCellState::Empty);
    assert_eq!(parser.active_source(), "");
}

#[test]
fn nested_unclosed_groups_report_every_required_closer() {
    let mut parser = NativeCellParser::new();
    assert_eq!(parser.push_line("f([x"), NativeCellState::Incomplete);
    assert!(parser.active_syntax().is_some());
    assert_eq!(parser.lexer_diagnostics().len(), 2);
    assert_eq!(parser.lexer_diagnostics()[0].expected(), Some("]"));
    assert_eq!(parser.lexer_diagnostics()[1].expected(), Some(")"));

    assert_eq!(parser.push_line("])"), NativeCellState::Complete);
    let completed = parser.take_completed();
    assert_eq!(completed[0].syntax().to_m2(), "f([x\n])");
}

#[test]
fn an_unterminated_block_comment_is_incomplete_until_its_closer_arrives() {
    let mut parser = NativeCellParser::new();
    assert_eq!(
        parser.push_line("x -* comment"),
        NativeCellState::Incomplete
    );
    assert_eq!(parser.lexer_diagnostics().len(), 1);
    assert_eq!(
        parser.lexer_diagnostics()[0].kind(),
        LexErrorKind::UnterminatedBlockComment
    );

    assert_eq!(parser.push_line("*-"), NativeCellState::Complete);
    let completed = parser.take_completed();
    assert_eq!(completed[0].source(), "x -* comment\n*-\n");
    assert_eq!(completed[0].syntax().to_m2(), "x -* comment\n*-");
}

#[test]
fn a_completed_prefix_is_not_rebuilt_with_the_unfinished_tail() {
    let mut parser = NativeCellParser::new();
    assert_eq!(parser.push_line("x = 1; f("), NativeCellState::Incomplete);
    assert_eq!(parser.completed().len(), 1);
    assert_eq!(parser.completed()[0].source(), "x = 1;");
    assert_eq!(parser.completed()[0].syntax().to_m2(), "x = 1;");
    assert_eq!(parser.active_source(), " f(\n");

    let source = Arc::clone(parser.completed()[0].syntax().span().source());
    assert_eq!(parser.push_line("x"), NativeCellState::Incomplete);
    assert!(Arc::ptr_eq(
        &source,
        parser.completed()[0].syntax().span().source()
    ));

    assert_eq!(parser.push_line(")"), NativeCellState::Complete);
    let completed = parser.take_completed();
    assert_eq!(completed.len(), 2);
    assert_eq!(completed[0].syntax().to_m2(), "x = 1;");
    assert_eq!(completed[1].syntax().to_m2(), "f(\nx\n)");
}