Skip to main content

sim_lib_lang_prolog/
matrix_row.rs

1//! Prolog conformance matrix row.
2
3use sim_kernel::Symbol;
4use sim_lib_standard_core::{
5    LanguageRow, LanguageRowBuilder, SourceConformanceCase, SourceExpectation,
6};
7
8use crate::{
9    prolog_conformance_case_symbol, prolog_logic_organ_symbol, prolog_profile,
10    prolog_surface_fidelity_symbol,
11};
12
13/// Builds the Prolog surface matrix row.
14pub fn prolog_matrix_row() -> LanguageRow {
15    LanguageRowBuilder::new(Symbol::new("prolog"), prolog_profile())
16        .with_cases(prolog_conformance_cases())
17        .build()
18}
19
20/// Minimal source cases for the Prolog surface matrix row.
21pub fn prolog_conformance_cases() -> Vec<SourceConformanceCase> {
22    let mut cases = vec![
23        prolog_case(
24            "fact",
25            "fact-color.prolog",
26            "color(red).",
27            "prolog:fact answers=1",
28        ),
29        prolog_case(
30            "rule",
31            "rule-painted.prolog",
32            "painted(X) :- color(X).",
33            "prolog:rule answers=1",
34        ),
35        prolog_case(
36            "query",
37            "query-color.prolog",
38            "?- color(X).",
39            "prolog:query answers=2",
40        ),
41        prolog_case(
42            "cut",
43            "cut-first-color.prolog",
44            "first_color(X) :- color(X), !.",
45            "prolog:cut answers=1 first=red",
46        ),
47    ];
48    cases.extend(prolog_arithmetic_cases());
49    cases.extend(prolog_list_cases());
50    cases.extend(prolog_all_solution_cases());
51    cases.extend(prolog_constraint_cases());
52    cases.extend(prolog_tabling_cases());
53    cases
54}
55
56fn prolog_case(
57    name: &str,
58    source_name: &str,
59    source: &str,
60    expected: &str,
61) -> SourceConformanceCase {
62    SourceConformanceCase {
63        symbol: prolog_conformance_case_symbol(name),
64        organ: prolog_logic_organ_symbol(),
65        source_name: source_name.to_owned(),
66        source: source.to_owned(),
67        expectation: SourceExpectation::LowersTo(expected.to_owned()),
68        affects_badge: Some(prolog_surface_fidelity_symbol()),
69    }
70}
71
72fn prolog_numbers_case(
73    name: &str,
74    source_name: &str,
75    source: &str,
76    expected: &str,
77) -> SourceConformanceCase {
78    SourceConformanceCase {
79        symbol: prolog_conformance_case_symbol(name),
80        organ: Symbol::qualified("numbers", "arith"),
81        source_name: source_name.to_owned(),
82        source: source.to_owned(),
83        expectation: SourceExpectation::LowersTo(expected.to_owned()),
84        affects_badge: Some(prolog_surface_fidelity_symbol()),
85    }
86}
87
88fn prolog_numbers_gap(
89    name: &str,
90    source_name: &str,
91    source: &str,
92    code: Symbol,
93    reason: &str,
94) -> SourceConformanceCase {
95    SourceConformanceCase {
96        symbol: prolog_conformance_case_symbol(name),
97        organ: Symbol::qualified("numbers", "arith"),
98        source_name: source_name.to_owned(),
99        source: source.to_owned(),
100        expectation: SourceExpectation::ExpectedGap {
101            code,
102            reason: reason.to_owned(),
103        },
104        affects_badge: Some(prolog_surface_fidelity_symbol()),
105    }
106}
107
108fn prolog_sequence_case(
109    name: &str,
110    source_name: &str,
111    source: &str,
112    expected: &str,
113) -> SourceConformanceCase {
114    SourceConformanceCase {
115        symbol: prolog_conformance_case_symbol(name),
116        organ: Symbol::new("sequence"),
117        source_name: source_name.to_owned(),
118        source: source.to_owned(),
119        expectation: SourceExpectation::LowersTo(expected.to_owned()),
120        affects_badge: Some(prolog_surface_fidelity_symbol()),
121    }
122}
123
124fn prolog_sequence_gap(
125    name: &str,
126    source_name: &str,
127    source: &str,
128    code: Symbol,
129    reason: &str,
130) -> SourceConformanceCase {
131    SourceConformanceCase {
132        symbol: prolog_conformance_case_symbol(name),
133        organ: Symbol::new("sequence"),
134        source_name: source_name.to_owned(),
135        source: source.to_owned(),
136        expectation: SourceExpectation::ExpectedGap {
137            code,
138            reason: reason.to_owned(),
139        },
140        affects_badge: Some(prolog_surface_fidelity_symbol()),
141    }
142}
143
144fn prolog_control_case(
145    name: &str,
146    source_name: &str,
147    source: &str,
148    expected: &str,
149) -> SourceConformanceCase {
150    SourceConformanceCase {
151        symbol: prolog_conformance_case_symbol(name),
152        organ: Symbol::new("control"),
153        source_name: source_name.to_owned(),
154        source: source.to_owned(),
155        expectation: SourceExpectation::LowersTo(expected.to_owned()),
156        affects_badge: Some(prolog_surface_fidelity_symbol()),
157    }
158}
159
160fn prolog_control_gap(
161    name: &str,
162    source_name: &str,
163    source: &str,
164    code: Symbol,
165    reason: &str,
166) -> SourceConformanceCase {
167    SourceConformanceCase {
168        symbol: prolog_conformance_case_symbol(name),
169        organ: Symbol::new("control"),
170        source_name: source_name.to_owned(),
171        source: source.to_owned(),
172        expectation: SourceExpectation::ExpectedGap {
173            code,
174            reason: reason.to_owned(),
175        },
176        affects_badge: Some(prolog_surface_fidelity_symbol()),
177    }
178}
179
180fn prolog_arithmetic_cases() -> Vec<SourceConformanceCase> {
181    vec![
182        prolog_numbers_case(
183            "is-promote",
184            "is-promote.prolog",
185            "X is 1 + 0.5.",
186            "prolog:is organ=numbers/arith answers=1 x=1.5",
187        ),
188        prolog_numbers_case(
189            "cmp-cross-domain",
190            "cmp-cross-domain.prolog",
191            "?- 2 =:= 2.0.",
192            "prolog:compare organ=numbers/arith answers=1",
193        ),
194        prolog_numbers_case(
195            "cmp-false",
196            "cmp-false.prolog",
197            "?- 3 < 2.",
198            "prolog:compare answers=0",
199        ),
200        prolog_numbers_gap(
201            "unbound-is",
202            "unbound-is.prolog",
203            "X is Y + 1.",
204            Symbol::qualified("prolog", "unbound-arithmetic"),
205            "is/2 requires the right side to be ground and evaluable",
206        ),
207    ]
208}
209
210fn prolog_list_cases() -> Vec<SourceConformanceCase> {
211    vec![
212        prolog_sequence_case(
213            "list-member",
214            "list-member.prolog",
215            "?- member(X, [a,b,c]).",
216            "prolog:member organ=sequence answers=3 xs=a,b,c",
217        ),
218        prolog_sequence_case(
219            "list-append",
220            "list-append.prolog",
221            "?- append([a], [b,c], Xs).",
222            "prolog:append organ=sequence answers=1 xs=(a b c)",
223        ),
224        prolog_sequence_gap(
225            "open-list",
226            "open-list.prolog",
227            "?- member(X, [a|Tail]).",
228            Symbol::qualified("prolog", "open-list"),
229            "open and improper lists are outside the closed Expr::List bridge",
230        ),
231    ]
232}
233
234fn prolog_all_solution_cases() -> Vec<SourceConformanceCase> {
235    vec![
236        prolog_sequence_case(
237            "findall-duplicates",
238            "findall-duplicates.prolog",
239            "findall(X, member(X, [a,b,a]), Xs).",
240            "prolog:findall organ=sequence answers=1 xs=(a b a)",
241        ),
242        prolog_sequence_case(
243            "bagof-groups",
244            "bagof-groups.prolog",
245            "bagof(Child, parent(Parent, Child), Children).",
246            "prolog:bagof organ=sequence answers=2 groups=alice:(bob bea);cara:(drew)",
247        ),
248        prolog_sequence_case(
249            "setof-sorted",
250            "setof-sorted.prolog",
251            "setof(X, member(X, [c,a,b,a]), Xs).",
252            "prolog:setof organ=sequence answers=1 xs=(a b c)",
253        ),
254        prolog_sequence_case(
255            "bagof-empty",
256            "bagof-empty.prolog",
257            "bagof(X, member(X, []), Xs).",
258            "prolog:bagof-empty organ=sequence answers=0",
259        ),
260    ]
261}
262
263fn prolog_constraint_cases() -> Vec<SourceConformanceCase> {
264    vec![
265        prolog_control_case(
266            "constraint-entailed",
267            "constraint-entailed.prolog",
268            "?- #=(2, 2).",
269            "prolog:constraint organ=control relation=#= verdict=entailed answers=1",
270        ),
271        prolog_control_case(
272            "constraint-disentailed",
273            "constraint-disentailed.prolog",
274            "?- #<(3, 2).",
275            "prolog:constraint organ=control relation=#< verdict=disentailed answers=0",
276        ),
277        prolog_control_gap(
278            "constraint-residual",
279            "constraint-residual.prolog",
280            "?- dif(X, 1).",
281            Symbol::qualified("prolog", "residual-constraint"),
282            "residual constraint demand is suspended on the control ledger",
283        ),
284    ]
285}
286
287fn prolog_tabling_cases() -> Vec<SourceConformanceCase> {
288    vec![prolog_sequence_case(
289        "tabling-left-recursive-path",
290        "tabling-left-recursive-path.prolog",
291        "table(path/2), path(a, Y).",
292        "prolog:tabling organ=sequence answers=2 ys=b,c",
293    )]
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    #[test]
301    fn prolog_matrix_row_language_symbol_is_prolog() {
302        let row = prolog_matrix_row();
303
304        assert_eq!(row.language, Symbol::new("prolog"));
305        assert!(!row.is_empty());
306        assert_eq!(row.cases.len(), 19);
307        assert!(
308            row.cases
309                .iter()
310                .filter(|case| matches!(case.expectation, SourceExpectation::LowersTo(_)))
311                .count()
312                == 16
313        );
314        assert!(
315            row.cases
316                .iter()
317                .any(|case| matches!(case.expectation, SourceExpectation::ExpectedGap { .. }))
318        );
319    }
320}