sim_lib_lang_prolog/
card.rs1use 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
15pub 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}