xlog-logic 0.9.2

Parser, compiler, and optimizer for XLOG logic programs
Documentation
use std::path::Path;

use xlog_logic::{ParserSession, Term};

fn facts(count: usize) -> String {
    let mut source = String::new();
    for idx in 0..count {
        source.push_str(&format!("edge({}, {}).\n", idx, idx + 1));
    }
    source
}

#[test]
fn splits_statement_units_with_stable_spans() {
    let source =
        "\n// comment\n#pragma prob_engine = mc\n0.5::edge(1, 2).\nreach(X, Y) :- edge(X, Y).\n";
    let units = ParserSession::split_statements(source);
    assert_eq!(units.len(), 3);
    assert_eq!(units[0].text, "#pragma prob_engine = mc");
    assert_eq!(units[0].span.line, 3);
    assert_eq!(units[0].span.column, 1);
    assert_eq!(units[1].text, "0.5::edge(1, 2).");
    assert!(units[1].span.start < units[1].span.end);
}

#[test]
fn reuses_unchanged_statement_parses_after_single_edit() {
    let mut session = ParserSession::new();
    let path = Path::new("synthetic/main.xlog");
    let original = facts(500);
    let first = session
        .parse_path(path, &original)
        .expect("initial incremental parse");
    assert_eq!(first.stats.hits, 0);
    assert_eq!(first.stats.misses, 500);

    let edited = original.replace("edge(250, 251).", "edge(250, 999).");
    let second = session
        .parse_path(path, &edited)
        .expect("second incremental parse");
    assert_eq!(second.stats.statement_count, 500);
    assert_eq!(second.stats.hits, 499);
    assert_eq!(second.stats.misses, 1);
    assert_eq!(second.stats.invalidated, 1);
    assert!(second.stats.estimated_speedup() >= 500.0);

    let edited_fact = second
        .program
        .facts()
        .find(|rule| {
            matches!(
                rule.head.terms.as_slice(),
                [Term::Integer(250), Term::Integer(999)]
            )
        })
        .expect("edited fact present");
    assert_eq!(edited_fact.head.predicate, "edge");
}

#[test]
fn invalidates_module_and_sources_that_import_it() {
    let mut session = ParserSession::new();
    let module = Path::new("modules/graph.xlog");
    let main = Path::new("main.xlog");

    session
        .parse_path(module, "edge(1, 2).\n")
        .expect("parse module");
    session
        .parse_path(main, "use graph.\nreach(X, Y) :- edge(X, Y).\n")
        .expect("parse importer");
    assert_eq!(session.cached_source_count(), 2);

    let removed = session.invalidate_module(module);
    assert_eq!(removed, 2);
    assert_eq!(session.cached_source_count(), 0);

    let parsed = session
        .parse_path(main, "use graph.\nreach(X, Y) :- edge(X, Y).\n")
        .expect("reparse importer");
    assert_eq!(parsed.stats.module_invalidations, 2);
    assert_eq!(parsed.stats.hits, 0);
}

#[test]
fn parse_errors_report_original_statement_span() {
    let mut session = ParserSession::new();
    let err = session
        .parse_path("bad.xlog", "\nedge(1, 2).\nbad(.\n")
        .expect_err("bad statement should fail")
        .to_string();
    assert!(err.contains("incremental parse error"), "err={err}");
    assert!(err.contains("3:1"), "err={err}");
    assert!(err.contains("bytes"), "err={err}");
}