Skip to main content

sim_lib_lang_prolog/
card.rs

1//! Prolog profile Card helpers.
2
3use sim_kernel::{
4    Cx, Ref, Result, Symbol, Value, card::card_for_ref_with_fallback,
5    standard::standard_profile_kind,
6};
7use sim_lib_logic::builtins::{BuiltinTable, tabling_memo_binding};
8use sim_lib_standard_core::{MatrixRunReport, standard_test_capability};
9
10use crate::{
11    generated_coverage::prolog_generated_coverage_card_fields, prolog_profile_symbol,
12    run_prolog_matrix_row,
13};
14
15/// Builds the Prolog profile Card with browseable conformance fields.
16///
17/// When the context lacks `standard.test`, the card reports zero counts and
18/// `conformance.fidelity = "unscored"` instead of running the harness.
19pub fn prolog_language_card(cx: &mut Cx) -> Result<Value> {
20    let language = prolog_language_symbol();
21    let mut entries = vec![
22        (
23            Symbol::new("profile"),
24            cx.factory().symbol(prolog_profile_symbol())?,
25        ),
26        (
27            Symbol::new("language"),
28            cx.factory().symbol(language.clone())?,
29        ),
30    ];
31    entries.extend(prolog_conformance_fields(cx, &language)?);
32    entries.extend(prolog_generated_coverage_fields(cx)?);
33    entries.extend(prolog_builtin_organ_fields(cx)?);
34    let fallback = cx.factory().table(entries)?;
35    card_for_ref_with_fallback(
36        cx,
37        Ref::Symbol(prolog_profile_symbol()),
38        Some(fallback),
39        Some(standard_profile_kind()),
40    )
41}
42
43fn prolog_builtin_organ_fields(cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
44    let mut table = BuiltinTable::standard();
45    table.register(tabling_memo_binding(Symbol::new("path")));
46    let keys = [
47        "is", "findall", "bagof", "setof", "member", "append", "length", "select", "#=", "#<",
48        "dif", "path",
49    ];
50    keys.into_iter()
51        .filter_map(|key| {
52            table
53                .organ_of(&Symbol::new(key))
54                .cloned()
55                .map(|organ| (builtin_organ_field(key), organ))
56        })
57        .map(|(field, organ)| Ok((field, cx.factory().symbol(organ)?)))
58        .collect()
59}
60
61fn builtin_organ_field(key: &str) -> Symbol {
62    let key = match key {
63        "#=" => "constraint.eq",
64        "#<" => "constraint.lt",
65        "path" => "tabling.path",
66        other => other,
67    };
68    Symbol::new(format!("builtin.{key}.organ"))
69}
70
71fn prolog_conformance_fields(cx: &mut Cx, language: &Symbol) -> Result<Vec<(Symbol, Value)>> {
72    if cx.require(&standard_test_capability()).is_ok() {
73        return run_prolog_matrix_row(cx)?.conformance_card_fields(cx, language);
74    }
75    MatrixRunReport::unscored_conformance_card_fields(cx)
76}
77
78fn prolog_generated_coverage_fields(cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
79    if cx.require(&standard_test_capability()).is_ok() {
80        return prolog_generated_coverage_card_fields(cx);
81    }
82    Ok(Vec::new())
83}
84
85fn prolog_language_symbol() -> Symbol {
86    Symbol::new("prolog")
87}
88
89#[cfg(test)]
90mod tests {
91    use sim_kernel::{Expr, NumberLiteral, testing::bare_cx as cx};
92    use sim_lib_standard_core::standard_test_capability;
93
94    use super::*;
95
96    #[test]
97    fn prolog_card_without_capability_emits_unscored() {
98        let mut cx = cx();
99
100        let card = prolog_language_card(&mut cx).unwrap();
101        let expr = card.object().as_expr(&mut cx).unwrap();
102
103        assert_eq!(number_table_value(&expr, "conformance.pass"), Some("0"));
104        assert_eq!(number_table_value(&expr, "conformance.gap"), Some("0"));
105        assert_eq!(number_table_value(&expr, "conformance.fail"), Some("0"));
106        assert_eq!(
107            table_value(&expr, "conformance.fidelity"),
108            Some(&Expr::String("unscored".to_owned()))
109        );
110    }
111
112    #[test]
113    fn prolog_card_with_capability_emits_fidelity() {
114        let mut cx = cx();
115        cx.grant(standard_test_capability());
116
117        let card = prolog_language_card(&mut cx).unwrap();
118        let expr = card.object().as_expr(&mut cx).unwrap();
119
120        assert_eq!(number_table_value(&expr, "conformance.pass"), Some("16"));
121        assert_eq!(number_table_value(&expr, "conformance.gap"), Some("3"));
122        assert_eq!(number_table_value(&expr, "conformance.fail"), Some("0"));
123        assert_eq!(
124            table_value(&expr, "conformance.fidelity"),
125            Some(&Expr::String("100%".to_owned()))
126        );
127        assert_eq!(
128            table_value(&expr, "builtin.is.organ"),
129            Some(&Expr::Symbol(Symbol::qualified("numbers", "arith")))
130        );
131        assert_eq!(
132            table_value(&expr, "builtin.tabling.path.organ"),
133            Some(&Expr::Symbol(Symbol::new("sequence")))
134        );
135        assert_ne!(
136            table_value(&expr, "coverage.generated.percent"),
137            Some(&Expr::String("unanchored".to_owned()))
138        );
139        assert_eq!(
140            table_value(&expr, "coverage.generated.citation"),
141            Some(&Expr::String("expr-space/core".to_owned()))
142        );
143    }
144
145    fn table_value<'a>(expr: &'a Expr, key: &str) -> Option<&'a Expr> {
146        let Expr::Map(entries) = expr else {
147            return None;
148        };
149        entries.iter().find_map(|(entry_key, entry_value)| {
150            let Expr::Symbol(entry_key) = entry_key else {
151                return None;
152            };
153            (entry_key == &Symbol::new(key)).then_some(entry_value)
154        })
155    }
156
157    fn number_table_value<'a>(expr: &'a Expr, key: &str) -> Option<&'a str> {
158        let value = table_value(expr, key)?;
159        let Expr::Number(NumberLiteral { domain, canonical }) = value else {
160            return None;
161        };
162        assert_eq!(domain, &Symbol::qualified("numbers", "u64"));
163        Some(canonical.as_str())
164    }
165}