lemma-engine 0.8.14

A language that means business.
Documentation
use lemma::parsing::ast::DateTimeValue;
use lemma::Engine;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;

fn source() -> lemma::SourceType {
    lemma::SourceType::Path(Arc::new(PathBuf::from("test.lemma")))
}

fn eval_rule(code: &str, spec_name: &str, rule_name: &str) -> String {
    let mut engine = Engine::new();
    engine.load(code, source()).expect("Should parse and plan");
    let now = DateTimeValue::now();
    let response = engine
        .run(
            None,
            spec_name,
            Some(&now),
            HashMap::new(),
            false,
            lemma::EvaluationRequest::default(),
        )
        .expect("Should evaluate");
    response
        .results
        .get(rule_name)
        .unwrap_or_else(|| panic!("Rule '{}' not found", rule_name))
        .result
        .value()
        .unwrap_or_else(|| panic!("Rule '{}' returned non-value", rule_name))
        .to_string()
}

fn expect_plan_error(code: &str, expected_fragment: &str) {
    let mut engine = Engine::new();
    let result = engine.load(code, source());
    assert!(result.is_err(), "Expected planning error");
    let combined = result
        .unwrap_err()
        .iter()
        .map(ToString::to_string)
        .collect::<Vec<_>>()
        .join("; ");
    assert!(
        combined.contains(expected_fragment),
        "Expected error containing '{}', got: {}",
        expected_fragment,
        combined
    );
}

#[test]
fn calendar_add_calendar_and_convert_to_months() {
    let code = r#"spec s
data a: 1 year
data b: 6 months
rule total: (a + b) as months"#;
    let val = eval_rule(code, "s", "total");
    assert!(
        val.contains("18") && val.to_lowercase().contains("month"),
        "Expected 18 months, got: {val}"
    );
}

#[test]
fn date_add_calendar_uses_exact_month_arithmetic() {
    let code = r#"spec s
data start: 2024-01-31
rule end: start + 1 month"#;
    let val = eval_rule(code, "s", "end");
    assert!(
        val.contains("2024-02-29"),
        "Expected leap-year month addition, got: {val}"
    );
}

#[test]
fn duration_and_calendar_addition_rejected() {
    let code = r#"spec s
uses lemma si
data a: 1 weeks
data b: 1 month
rule total: a + b"#;
    expect_plan_error(code, "Cannot apply '+' to duration");
}

#[test]
fn calendar_to_duration_conversion_rejected() {
    let code = r#"spec s
uses lemma si
data c: 1 month
rule seconds: c as seconds"#;
    expect_plan_error(code, "Cannot convert c to quantity unit");
}

#[test]
fn duration_typed_slot_rejects_calendar_default() {
    let code = r#"spec s
uses lemma si
data d: si.duration -> default 1 month"#;
    expect_plan_error(code, "Unit 'month' is for calendar data");
    expect_plan_error(code, "Valid 'duration' units are");
}

#[test]
fn weight_quantity_calendar_default_lists_quantity_units() {
    let code = r#"spec s
data weight: quantity -> unit gram 1 -> unit kilogram 1000
data w: weight -> default 1 month"#;
    expect_plan_error(code, "Unit 'month' is for calendar data");
    expect_plan_error(code, "Valid 'weight' units are");
}

#[test]
fn text_calendar_default_suggests_quoted_text() {
    let code = r#"spec s
data notes: text -> default 1 month"#;
    expect_plan_error(code, "Unit 'month' is for calendar data");
    expect_plan_error(code, "double quotes");
}