Skip to main content

oxilean_codegen/prolog_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use super::prologgoalbuilder_type::PrologGoalBuilder;
7
8/// Right-hand side element of a DCG rule.
9#[derive(Debug, Clone, PartialEq)]
10pub enum DcgRhs {
11    /// A nonterminal call.
12    NonTerminal(PrologTerm),
13    /// A terminal list `[a, b, c]`.
14    Terminals(Vec<PrologTerm>),
15    /// An epsilon (empty string) — written `[]`.
16    Epsilon,
17    /// A Prolog goal `{Goal1, Goal2}`.
18    Goal(Vec<PrologTerm>),
19    /// A disjunction `(A | B)`.
20    Disjunction(Vec<DcgRhs>, Vec<DcgRhs>),
21    /// A pushback notation `A, B` sequence.
22    Seq(Vec<DcgRhs>),
23}
24/// A top-level Prolog directive `:- Goal.`
25#[derive(Debug, Clone, PartialEq)]
26pub enum PrologDirective {
27    /// `:- module(Name, [Exports]).`
28    Module(String, Vec<String>),
29    /// `:- use_module(library(Name)).`
30    UseModuleLibrary(String),
31    /// `:- use_module(path).`
32    UseModulePath(String),
33    /// `:- use_module(path, [Imports]).`
34    UseModuleImports(String, Vec<String>),
35    /// `:- dynamic Name/Arity.`
36    Dynamic(String, usize),
37    /// `:- discontiguous Name/Arity.`
38    Discontiguous(String, usize),
39    /// `:- ensure_loaded(path).`
40    EnsureLoaded(String),
41    /// `:- module_info.`
42    ModuleInfo,
43    /// `:- set_prolog_flag(Flag, Value).`
44    SetPrologFlag(String, String),
45    /// `:- op(Priority, Type, Operator).`
46    Op(u16, String, String),
47    /// `:- meta_predicate Declaration.`
48    MetaPredicate(PrologTerm),
49    /// An arbitrary directive goal.
50    Arbitrary(PrologTerm),
51}
52impl PrologDirective {
53    /// Emit this directive as a Prolog string.
54    pub fn emit(&self) -> String {
55        match self {
56            PrologDirective::Module(name, exports) => {
57                let exp_str = exports.join(", ");
58                format!(":- module({}, [{}]).", name, exp_str)
59            }
60            PrologDirective::UseModuleLibrary(lib) => {
61                format!(":- use_module(library({})).", lib)
62            }
63            PrologDirective::UseModulePath(path) => format!(":- use_module({}).", path),
64            PrologDirective::UseModuleImports(path, imports) => {
65                let imp_str = imports.join(", ");
66                format!(":- use_module({}, [{}]).", path, imp_str)
67            }
68            PrologDirective::Dynamic(name, arity) => {
69                format!(":- dynamic {}/{}.", name, arity)
70            }
71            PrologDirective::Discontiguous(name, arity) => {
72                format!(":- discontiguous {}/{}.", name, arity)
73            }
74            PrologDirective::EnsureLoaded(path) => format!(":- ensure_loaded({}).", path),
75            PrologDirective::ModuleInfo => ":- module_info.".to_string(),
76            PrologDirective::SetPrologFlag(flag, val) => {
77                format!(":- set_prolog_flag({}, {}).", flag, val)
78            }
79            PrologDirective::Op(prio, op_type, op) => {
80                format!(":- op({}, {}, {}).", prio, op_type, op)
81            }
82            PrologDirective::MetaPredicate(term) => {
83                format!(":- meta_predicate {}.", term)
84            }
85            PrologDirective::Arbitrary(goal) => format!(":- {}.", goal),
86        }
87    }
88}
89/// Fluent builder for `PrologModule`.
90#[allow(dead_code)]
91pub struct PrologModuleBuilder {
92    pub(super) module: PrologModule,
93}
94impl PrologModuleBuilder {
95    /// Start a named module.
96    #[allow(dead_code)]
97    pub fn new(name: impl Into<String>) -> Self {
98        PrologModuleBuilder {
99            module: PrologModule::new(name),
100        }
101    }
102    /// Start a script (no module declaration).
103    #[allow(dead_code)]
104    pub fn script() -> Self {
105        PrologModuleBuilder {
106            module: PrologModule::script(),
107        }
108    }
109    /// Export a predicate.
110    #[allow(dead_code)]
111    pub fn export(mut self, indicator: impl Into<String>) -> Self {
112        self.module.exported_predicates.push(indicator.into());
113        self
114    }
115    /// Add a `use_module(library(Name))` directive.
116    #[allow(dead_code)]
117    pub fn use_library(mut self, lib: impl Into<String>) -> Self {
118        self.module
119            .items
120            .push(PrologItem::Directive(PrologDirective::UseModuleLibrary(
121                lib.into(),
122            )));
123        self
124    }
125    /// Add a `use_module(path)` directive.
126    #[allow(dead_code)]
127    pub fn use_module(mut self, path: impl Into<String>) -> Self {
128        self.module
129            .items
130            .push(PrologItem::Directive(PrologDirective::UseModulePath(
131                path.into(),
132            )));
133        self
134    }
135    /// Add a predicate.
136    #[allow(dead_code)]
137    pub fn add_predicate(mut self, pred: PrologPredicate) -> Self {
138        self.module.items.push(PrologItem::Predicate(pred));
139        self
140    }
141    /// Add a DCG rule.
142    #[allow(dead_code)]
143    pub fn add_dcg(mut self, rule: DcgRule) -> Self {
144        self.module.items.push(PrologItem::Dcg(rule));
145        self
146    }
147    /// Add a blank line.
148    #[allow(dead_code)]
149    pub fn blank(mut self) -> Self {
150        self.module.items.push(PrologItem::BlankLine);
151        self
152    }
153    /// Add a section comment.
154    #[allow(dead_code)]
155    pub fn section(mut self, title: impl Into<String>) -> Self {
156        self.module
157            .items
158            .push(PrologItem::SectionComment(title.into()));
159        self
160    }
161    /// Add a line comment.
162    #[allow(dead_code)]
163    pub fn comment(mut self, text: impl Into<String>) -> Self {
164        self.module.items.push(PrologItem::LineComment(text.into()));
165        self
166    }
167    /// Set the description.
168    #[allow(dead_code)]
169    pub fn description(mut self, desc: impl Into<String>) -> Self {
170        self.module.description = Some(desc.into());
171        self
172    }
173    /// Finalise.
174    #[allow(dead_code)]
175    pub fn build(self) -> PrologModule {
176        self.module
177    }
178    /// Emit directly.
179    #[allow(dead_code)]
180    pub fn emit(self) -> String {
181        PrologBackend::swi().emit_module(&self.module)
182    }
183}
184/// Fluent builder for `PrologPredicate`.
185#[allow(dead_code)]
186pub struct PrologPredicateBuilder {
187    pub(super) pred: PrologPredicate,
188}
189impl PrologPredicateBuilder {
190    /// Start building a predicate.
191    #[allow(dead_code)]
192    pub fn new(name: impl Into<String>, arity: usize) -> Self {
193        PrologPredicateBuilder {
194            pred: PrologPredicate::new(name, arity),
195        }
196    }
197    /// Mark as dynamic.
198    #[allow(dead_code)]
199    pub fn dynamic(mut self) -> Self {
200        self.pred.is_dynamic = true;
201        self
202    }
203    /// Mark as exported.
204    #[allow(dead_code)]
205    pub fn exported(mut self) -> Self {
206        self.pred.is_exported = true;
207        self
208    }
209    /// Mark as discontiguous.
210    #[allow(dead_code)]
211    pub fn discontiguous(mut self) -> Self {
212        self.pred.is_discontiguous = true;
213        self
214    }
215    /// Set module qualification.
216    #[allow(dead_code)]
217    pub fn module(mut self, m: impl Into<String>) -> Self {
218        self.pred.module = Some(m.into());
219        self
220    }
221    /// Add a doc comment.
222    #[allow(dead_code)]
223    pub fn doc(mut self, d: impl Into<String>) -> Self {
224        self.pred.doc = Some(d.into());
225        self
226    }
227    /// Add a clause.
228    #[allow(dead_code)]
229    pub fn clause(mut self, c: PrologClause) -> Self {
230        self.pred.clauses.push(c);
231        self
232    }
233    /// Add a fact clause.
234    #[allow(dead_code)]
235    pub fn fact(mut self, head: PrologTerm) -> Self {
236        self.pred.clauses.push(PrologClause::fact(head));
237        self
238    }
239    /// Add a rule clause.
240    #[allow(dead_code)]
241    pub fn rule(mut self, head: PrologTerm, body: Vec<PrologTerm>) -> Self {
242        self.pred.clauses.push(PrologClause::rule(head, body));
243        self
244    }
245    /// Finalise.
246    #[allow(dead_code)]
247    pub fn build(self) -> PrologPredicate {
248        self.pred
249    }
250    /// Emit.
251    #[allow(dead_code)]
252    pub fn emit(self) -> String {
253        self.pred.emit()
254    }
255}
256/// Fluent builder for `PrologClause`.
257#[allow(dead_code)]
258pub struct PrologClauseBuilder {
259    pub(super) head: PrologTerm,
260    pub(super) body: Vec<PrologTerm>,
261    pub(super) comment: Option<String>,
262}
263impl PrologClauseBuilder {
264    /// Start building a clause with the given head.
265    #[allow(dead_code)]
266    pub fn head(head: PrologTerm) -> Self {
267        PrologClauseBuilder {
268            head,
269            body: vec![],
270            comment: None,
271        }
272    }
273    /// Add a body goal.
274    #[allow(dead_code)]
275    pub fn goal(mut self, g: PrologTerm) -> Self {
276        self.body.push(g);
277        self
278    }
279    /// Add goals from a `PrologGoalBuilder`.
280    #[allow(dead_code)]
281    pub fn goals(mut self, builder: PrologGoalBuilder) -> Self {
282        self.body.extend(builder.build());
283        self
284    }
285    /// Set a comment.
286    #[allow(dead_code)]
287    pub fn comment(mut self, c: impl Into<String>) -> Self {
288        self.comment = Some(c.into());
289        self
290    }
291    /// Build into a `PrologClause`.
292    #[allow(dead_code)]
293    pub fn build(self) -> PrologClause {
294        let mut clause = if self.body.is_empty() {
295            PrologClause::fact(self.head)
296        } else {
297            PrologClause::rule(self.head, self.body)
298        };
299        clause.comment = self.comment;
300        clause
301    }
302}
303/// An item at the top level of a Prolog source file.
304#[derive(Debug, Clone, PartialEq)]
305pub enum PrologItem {
306    /// A top-level directive.
307    Directive(PrologDirective),
308    /// A predicate (grouped clauses).
309    Predicate(PrologPredicate),
310    /// A standalone clause (not grouped into a predicate).
311    Clause(PrologClause),
312    /// A DCG grammar rule.
313    Dcg(DcgRule),
314    /// A blank line separator (for readability).
315    BlankLine,
316    /// A section comment `% === ... ===`.
317    SectionComment(String),
318    /// A line comment.
319    LineComment(String),
320}
321impl PrologItem {
322    /// Emit this item as a Prolog string.
323    pub fn emit(&self) -> String {
324        match self {
325            PrologItem::Directive(d) => d.emit(),
326            PrologItem::Predicate(p) => p.emit(),
327            PrologItem::Clause(c) => c.emit(),
328            PrologItem::Dcg(r) => r.emit(),
329            PrologItem::BlankLine => String::new(),
330            PrologItem::SectionComment(s) => {
331                let bar = "=".repeat(s.len() + 8);
332                format!("% {}\n% === {} ===\n% {}", bar, s, bar)
333            }
334            PrologItem::LineComment(s) => format!("% {}", s),
335        }
336    }
337}
338/// Generate complete snippets of common Prolog predicates.
339#[allow(dead_code)]
340pub struct PrologSnippets;
341impl PrologSnippets {
342    /// Generate a complete `member/2` predicate.
343    #[allow(dead_code)]
344    pub fn member_predicate() -> PrologPredicate {
345        let mut pred = PrologPredicate::new("member", 2);
346        pred.add_clause(PrologClause::fact(compound(
347            "member",
348            vec![var("X"), PrologTerm::list_partial(vec![var("X")], var("_"))],
349        )));
350        pred.add_clause(PrologClause::rule(
351            compound(
352                "member",
353                vec![var("X"), PrologTerm::list_partial(vec![var("_")], var("T"))],
354            ),
355            vec![compound("member", vec![var("X"), var("T")])],
356        ));
357        pred
358    }
359    /// Generate a complete `append/3` predicate.
360    #[allow(dead_code)]
361    pub fn append_predicate() -> PrologPredicate {
362        let mut pred = PrologPredicate::new("append", 3);
363        pred.add_clause(PrologClause::fact(compound(
364            "append",
365            vec![PrologTerm::Nil, var("L"), var("L")],
366        )));
367        pred.add_clause(PrologClause::rule(
368            compound(
369                "append",
370                vec![
371                    PrologTerm::list_partial(vec![var("H")], var("T")),
372                    var("L"),
373                    PrologTerm::list_partial(vec![var("H")], var("R")),
374                ],
375            ),
376            vec![compound("append", vec![var("T"), var("L"), var("R")])],
377        ));
378        pred
379    }
380    /// Generate a `length/2` predicate.
381    #[allow(dead_code)]
382    pub fn length_predicate() -> PrologPredicate {
383        let mut pred = PrologPredicate::new("my_length", 2);
384        pred.add_clause(PrologClause::fact(compound(
385            "my_length",
386            vec![PrologTerm::Nil, PrologTerm::Integer(0)],
387        )));
388        pred.add_clause(PrologClause::rule(
389            compound(
390                "my_length",
391                vec![PrologTerm::list_partial(vec![var("_")], var("T")), var("N")],
392            ),
393            vec![
394                compound("my_length", vec![var("T"), var("N1")]),
395                is_eval(var("N"), arith_add(var("N1"), PrologTerm::Integer(1))),
396            ],
397        ));
398        pred
399    }
400    /// Generate a `max_list/2` predicate.
401    #[allow(dead_code)]
402    pub fn max_list_predicate() -> PrologPredicate {
403        let mut pred = PrologPredicate::new("max_list", 2);
404        pred.add_clause(PrologClause::fact(compound(
405            "max_list",
406            vec![PrologTerm::list(vec![var("X")]), var("X")],
407        )));
408        pred.add_clause(PrologClause::rule(
409            compound(
410                "max_list",
411                vec![
412                    PrologTerm::list_partial(vec![var("H")], var("T")),
413                    var("Max"),
414                ],
415            ),
416            vec![
417                compound("max_list", vec![var("T"), var("Max1")]),
418                is_eval(var("Max"), compound("max", vec![var("H"), var("Max1")])),
419            ],
420        ));
421        pred
422    }
423    /// Generate a `sum_list/2` predicate.
424    #[allow(dead_code)]
425    pub fn sum_list_predicate() -> PrologPredicate {
426        let mut pred = PrologPredicate::new("sum_list", 2);
427        pred.add_clause(PrologClause::fact(compound(
428            "sum_list",
429            vec![PrologTerm::Nil, PrologTerm::Integer(0)],
430        )));
431        pred.add_clause(PrologClause::rule(
432            compound(
433                "sum_list",
434                vec![
435                    PrologTerm::list_partial(vec![var("H")], var("T")),
436                    var("Sum"),
437                ],
438            ),
439            vec![
440                compound("sum_list", vec![var("T"), var("Rest")]),
441                is_eval(var("Sum"), arith_add(var("H"), var("Rest"))),
442            ],
443        ));
444        pred
445    }
446    /// Generate a `last/2` predicate.
447    #[allow(dead_code)]
448    pub fn last_predicate() -> PrologPredicate {
449        let mut pred = PrologPredicate::new("my_last", 2);
450        pred.add_clause(PrologClause::fact(compound(
451            "my_last",
452            vec![PrologTerm::list(vec![var("X")]), var("X")],
453        )));
454        pred.add_clause(PrologClause::rule(
455            compound(
456                "my_last",
457                vec![
458                    PrologTerm::list_partial(vec![var("_")], var("T")),
459                    var("Last"),
460                ],
461            ),
462            vec![compound("my_last", vec![var("T"), var("Last")])],
463        ));
464        pred
465    }
466    /// Generate a `reverse/2` predicate (accumulator style).
467    #[allow(dead_code)]
468    pub fn reverse_predicate() -> PrologPredicate {
469        let mut pred = PrologPredicate::new("my_reverse", 2);
470        pred.add_clause(PrologClause::rule(
471            compound("my_reverse", vec![var("L"), var("R")]),
472            vec![compound(
473                "reverse_acc",
474                vec![var("L"), PrologTerm::Nil, var("R")],
475            )],
476        ));
477        pred
478    }
479    /// Generate a `msort_dedup/2` predicate using msort + remove_dups.
480    #[allow(dead_code)]
481    pub fn msort_dedup_predicate() -> PrologPredicate {
482        let mut pred = PrologPredicate::new("msort_dedup", 2);
483        pred.add_clause(PrologClause::rule(
484            compound("msort_dedup", vec![var("List"), var("Sorted")]),
485            vec![
486                compound("msort", vec![var("List"), var("Tmp")]),
487                compound("list_to_set", vec![var("Tmp"), var("Sorted")]),
488            ],
489        ));
490        pred
491    }
492    /// Generate a `flatten/2` predicate (simple version).
493    #[allow(dead_code)]
494    pub fn flatten_predicate() -> PrologPredicate {
495        let mut pred = PrologPredicate::new("my_flatten", 2);
496        pred.add_clause(PrologClause::fact(compound(
497            "my_flatten",
498            vec![PrologTerm::Nil, PrologTerm::Nil],
499        )));
500        pred.add_clause(PrologClause::rule(
501            compound(
502                "my_flatten",
503                vec![
504                    PrologTerm::list_partial(vec![var("H")], var("T")),
505                    var("Flat"),
506                ],
507            ),
508            vec![
509                compound("is_list", vec![var("H")]),
510                PrologTerm::Cut,
511                compound("my_flatten", vec![var("H"), var("FH")]),
512                compound("my_flatten", vec![var("T"), var("FT")]),
513                compound("append", vec![var("FH"), var("FT"), var("Flat")]),
514            ],
515        ));
516        pred.add_clause(PrologClause::rule(
517            compound(
518                "my_flatten",
519                vec![
520                    PrologTerm::list_partial(vec![var("H")], var("T")),
521                    var("Flat"),
522                ],
523            ),
524            vec![
525                compound("my_flatten", vec![var("T"), var("FT")]),
526                compound(
527                    "append",
528                    vec![PrologTerm::list(vec![var("H")]), var("FT"), var("Flat")],
529                ),
530            ],
531        ));
532        pred
533    }
534    /// Generate a `nth0/3` predicate.
535    #[allow(dead_code)]
536    pub fn nth0_predicate() -> PrologPredicate {
537        let mut pred = PrologPredicate::new("my_nth0", 3);
538        pred.add_clause(PrologClause::fact(compound(
539            "my_nth0",
540            vec![
541                PrologTerm::Integer(0),
542                PrologTerm::list_partial(vec![var("H")], var("_")),
543                var("H"),
544            ],
545        )));
546        pred.add_clause(PrologClause::rule(
547            compound(
548                "my_nth0",
549                vec![
550                    var("N"),
551                    PrologTerm::list_partial(vec![var("_")], var("T")),
552                    var("Elem"),
553                ],
554            ),
555            vec![
556                arith_gt(var("N"), PrologTerm::Integer(0)),
557                is_eval(var("N1"), arith_sub(var("N"), PrologTerm::Integer(1))),
558                compound("my_nth0", vec![var("N1"), var("T"), var("Elem")]),
559            ],
560        ));
561        pred
562    }
563}
564/// Prolog argument mode.
565#[derive(Debug, Clone, PartialEq)]
566#[allow(dead_code)]
567pub enum PrologMode {
568    /// `+` — argument must be instantiated.
569    In,
570    /// `-` — argument will be instantiated by the predicate.
571    Out,
572    /// `?` — argument may or may not be instantiated.
573    InOut,
574    /// `:` — meta-argument (goal, callable).
575    Meta,
576    /// `@` — argument will not be further instantiated.
577    NotFurther,
578}
579/// A Prolog predicate: a collection of clauses sharing the same functor/arity.
580#[derive(Debug, Clone, PartialEq)]
581pub struct PrologPredicate {
582    /// Predicate name (functor).
583    pub name: String,
584    /// Arity.
585    pub arity: usize,
586    /// All clauses for this predicate (in order).
587    pub clauses: Vec<PrologClause>,
588    /// Whether this predicate is declared `:- dynamic`.
589    pub is_dynamic: bool,
590    /// Whether this predicate is exported from its module.
591    pub is_exported: bool,
592    /// Whether this predicate is declared `:- discontiguous`.
593    pub is_discontiguous: bool,
594    /// Optional module qualification.
595    pub module: Option<String>,
596    /// Optional documentation string.
597    pub doc: Option<String>,
598}
599impl PrologPredicate {
600    /// Create a new predicate with no clauses.
601    pub fn new(name: impl Into<String>, arity: usize) -> Self {
602        PrologPredicate {
603            name: name.into(),
604            arity,
605            clauses: vec![],
606            is_dynamic: false,
607            is_exported: false,
608            is_discontiguous: false,
609            module: None,
610            doc: None,
611        }
612    }
613    /// Add a clause.
614    pub fn add_clause(&mut self, clause: PrologClause) {
615        self.clauses.push(clause);
616    }
617    /// Mark as dynamic.
618    pub fn dynamic(mut self) -> Self {
619        self.is_dynamic = true;
620        self
621    }
622    /// Mark as exported.
623    pub fn exported(mut self) -> Self {
624        self.is_exported = true;
625        self
626    }
627    /// Mark as discontiguous.
628    pub fn discontiguous(mut self) -> Self {
629        self.is_discontiguous = true;
630        self
631    }
632    /// The indicator `Name/Arity` as a string.
633    pub fn indicator(&self) -> String {
634        format!("{}/{}", self.name, self.arity)
635    }
636    /// Emit all clauses for this predicate.
637    pub fn emit(&self) -> String {
638        let mut out = String::new();
639        if let Some(doc) = &self.doc {
640            for line in doc.lines() {
641                out.push_str(&format!("%% {}\n", line));
642            }
643        }
644        if self.is_dynamic {
645            out.push_str(&format!(":- dynamic {}/{}.\n", self.name, self.arity));
646        }
647        if self.is_discontiguous {
648            out.push_str(&format!(":- discontiguous {}/{}.\n", self.name, self.arity));
649        }
650        for clause in &self.clauses {
651            out.push_str(&clause.emit());
652            out.push('\n');
653        }
654        out
655    }
656}
657/// A complete Prolog source module (maps to a `.pl` file).
658#[derive(Debug, Clone)]
659pub struct PrologModule {
660    /// Module name (used in `:- module/2`). `None` means no module declaration.
661    pub name: Option<String>,
662    /// Exported predicates as `"Name/Arity"` strings.
663    pub exported_predicates: Vec<String>,
664    /// Top-level items in order.
665    pub items: Vec<PrologItem>,
666    /// File-level comment/description.
667    pub description: Option<String>,
668}
669impl PrologModule {
670    /// Create a new named module.
671    pub fn new(name: impl Into<String>) -> Self {
672        PrologModule {
673            name: Some(name.into()),
674            exported_predicates: vec![],
675            items: vec![],
676            description: None,
677        }
678    }
679    /// Create a script (no module declaration).
680    pub fn script() -> Self {
681        PrologModule {
682            name: None,
683            exported_predicates: vec![],
684            items: vec![],
685            description: None,
686        }
687    }
688    /// Set the file description.
689    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
690        self.description = Some(desc.into());
691        self
692    }
693    /// Export a predicate.
694    pub fn export(&mut self, indicator: impl Into<String>) {
695        self.exported_predicates.push(indicator.into());
696    }
697    /// Add an item.
698    pub fn add(&mut self, item: PrologItem) {
699        self.items.push(item);
700    }
701    /// Add a directive.
702    pub fn directive(&mut self, d: PrologDirective) {
703        self.items.push(PrologItem::Directive(d));
704    }
705    /// Add a predicate.
706    pub fn predicate(&mut self, p: PrologPredicate) {
707        self.items.push(PrologItem::Predicate(p));
708    }
709    /// Add a DCG rule.
710    pub fn dcg_rule(&mut self, r: DcgRule) {
711        self.items.push(PrologItem::Dcg(r));
712    }
713    /// Add a blank line.
714    pub fn blank(&mut self) {
715        self.items.push(PrologItem::BlankLine);
716    }
717    /// Add a section comment.
718    pub fn section(&mut self, title: impl Into<String>) {
719        self.items.push(PrologItem::SectionComment(title.into()));
720    }
721}
722/// Fluent builder for `DcgRule`.
723#[allow(dead_code)]
724pub struct PrologDCGBuilder {
725    pub(super) lhs: PrologTerm,
726    pub(super) rhs: Vec<DcgRhs>,
727    pub(super) guards: Vec<PrologTerm>,
728    pub(super) comment: Option<String>,
729}
730impl PrologDCGBuilder {
731    /// Start a DCG rule with the given LHS nonterminal.
732    #[allow(dead_code)]
733    pub fn lhs(lhs: PrologTerm) -> Self {
734        PrologDCGBuilder {
735            lhs,
736            rhs: vec![],
737            guards: vec![],
738            comment: None,
739        }
740    }
741    /// Add a nonterminal call.
742    #[allow(dead_code)]
743    pub fn nonterminal(mut self, t: PrologTerm) -> Self {
744        self.rhs.push(DcgRhs::NonTerminal(t));
745        self
746    }
747    /// Add terminal tokens.
748    #[allow(dead_code)]
749    pub fn terminals(mut self, ts: Vec<PrologTerm>) -> Self {
750        self.rhs.push(DcgRhs::Terminals(ts));
751        self
752    }
753    /// Add an epsilon.
754    #[allow(dead_code)]
755    pub fn epsilon(mut self) -> Self {
756        self.rhs.push(DcgRhs::Epsilon);
757        self
758    }
759    /// Add a Prolog guard `{Goals}`.
760    #[allow(dead_code)]
761    pub fn guard(mut self, g: PrologTerm) -> Self {
762        self.guards.push(g);
763        self
764    }
765    /// Add a comment.
766    #[allow(dead_code)]
767    pub fn comment(mut self, c: impl Into<String>) -> Self {
768        self.comment = Some(c.into());
769        self
770    }
771    /// Build the `DcgRule`.
772    #[allow(dead_code)]
773    pub fn build(self) -> DcgRule {
774        DcgRule {
775            lhs: self.lhs,
776            rhs: self.rhs,
777            guards: self.guards,
778            comment: self.comment,
779        }
780    }
781    /// Emit directly.
782    #[allow(dead_code)]
783    pub fn emit(self) -> String {
784        self.build().emit()
785    }
786}
787/// The Prolog code generation backend.
788///
789/// Converts a `PrologModule` (or individual items) into `.pl` source text.
790#[derive(Debug, Default)]
791pub struct PrologBackend {
792    /// Whether to emit SWI-Prolog-specific pragmas (`:- use_module(library(lists))`).
793    pub swi_mode: bool,
794    /// Whether to add `:- encoding(utf8).` at the top.
795    pub utf8_encoding: bool,
796    /// Extra options reserved for future use.
797    pub options: PrologBackendOptions,
798}
799impl PrologBackend {
800    /// Create a backend in default (ISO) mode.
801    pub fn new() -> Self {
802        PrologBackend {
803            swi_mode: false,
804            utf8_encoding: false,
805            options: PrologBackendOptions {
806                blank_between_predicates: true,
807                emit_docs: true,
808            },
809        }
810    }
811    /// Create a backend targeting SWI-Prolog.
812    pub fn swi() -> Self {
813        PrologBackend {
814            swi_mode: true,
815            utf8_encoding: true,
816            options: PrologBackendOptions {
817                blank_between_predicates: true,
818                emit_docs: true,
819            },
820        }
821    }
822    /// Emit a complete `PrologModule` as `.pl` source text.
823    pub fn emit_module(&self, module: &PrologModule) -> String {
824        let mut out = String::new();
825        if self.utf8_encoding {
826            out.push_str(":- encoding(utf8).\n");
827        }
828        if let Some(desc) = &module.description {
829            out.push_str("%% ");
830            out.push_str(desc);
831            out.push('\n');
832        }
833        if let Some(name) = &module.name {
834            let exports = module.exported_predicates.join(",\n               ");
835            if module.exported_predicates.is_empty() {
836                out.push_str(&format!(":- module({}, []).\n", name));
837            } else {
838                out.push_str(&format!(
839                    ":- module({}, [\n               {}\n              ]).\n",
840                    name, exports
841                ));
842            }
843            out.push('\n');
844        }
845        for item in &module.items {
846            let s = item.emit();
847            if !s.is_empty() {
848                out.push_str(&s);
849                out.push('\n');
850            } else {
851                out.push('\n');
852            }
853            if self.options.blank_between_predicates && matches!(item, PrologItem::Predicate(_)) {
854                out.push('\n');
855            }
856        }
857        out
858    }
859    /// Emit a single clause.
860    pub fn emit_clause(&self, clause: &PrologClause) -> String {
861        clause.emit()
862    }
863    /// Emit a single predicate.
864    pub fn emit_predicate(&self, pred: &PrologPredicate) -> String {
865        pred.emit()
866    }
867    /// Emit a single DCG rule.
868    pub fn emit_dcg(&self, rule: &DcgRule) -> String {
869        rule.emit()
870    }
871    /// Emit a single directive.
872    pub fn emit_directive(&self, directive: &PrologDirective) -> String {
873        directive.emit()
874    }
875    /// Emit a list of clauses separated by newlines.
876    pub fn emit_clauses(&self, clauses: &[PrologClause]) -> String {
877        clauses
878            .iter()
879            .map(|c| c.emit())
880            .collect::<Vec<_>>()
881            .join("\n")
882    }
883    /// Build a standard SWI-Prolog module preamble.
884    pub fn build_swi_preamble(
885        &self,
886        module_name: &str,
887        exports: &[(&str, usize)],
888        libraries: &[&str],
889    ) -> String {
890        let mut out = String::new();
891        out.push_str(":- encoding(utf8).\n\n");
892        let exp_strs: Vec<String> = exports
893            .iter()
894            .map(|(n, a)| format!("{}/{}", n, a))
895            .collect();
896        if exp_strs.is_empty() {
897            out.push_str(&format!(":- module({}, []).\n", module_name));
898        } else {
899            let exp_list = exp_strs.join(",\n               ");
900            out.push_str(&format!(
901                ":- module({}, [\n               {}\n              ]).\n",
902                module_name, exp_list
903            ));
904        }
905        for lib in libraries {
906            out.push_str(&format!(":- use_module(library({})).\n", lib));
907        }
908        out
909    }
910}
911/// Build dynamic database manipulation sequences.
912#[allow(dead_code)]
913pub struct PrologAssertionBuilder;
914impl PrologAssertionBuilder {
915    /// `assertz(Head)` — assert a fact at the end.
916    #[allow(dead_code)]
917    pub fn assertz_fact(head: PrologTerm) -> PrologTerm {
918        compound("assertz", vec![head])
919    }
920    /// `asserta(Head)` — assert a fact at the front.
921    #[allow(dead_code)]
922    pub fn asserta_fact(head: PrologTerm) -> PrologTerm {
923        compound("asserta", vec![head])
924    }
925    /// `assertz((Head :- Body))` — assert a rule at the end.
926    #[allow(dead_code)]
927    pub fn assertz_rule(head: PrologTerm, body: PrologTerm) -> PrologTerm {
928        let rule = PrologTerm::Op(":-".to_string(), Box::new(head), Box::new(body));
929        compound("assertz", vec![rule])
930    }
931    /// `retract(Head)` — retract a fact.
932    #[allow(dead_code)]
933    pub fn retract(head: PrologTerm) -> PrologTerm {
934        compound("retract", vec![head])
935    }
936    /// `retractall(Head)` — retract all matching facts.
937    #[allow(dead_code)]
938    pub fn retractall(head: PrologTerm) -> PrologTerm {
939        compound("retractall", vec![head])
940    }
941    /// `abolish(Name/Arity)` — remove all clauses.
942    #[allow(dead_code)]
943    pub fn abolish(name: &str, arity: usize) -> PrologTerm {
944        compound(
945            "abolish",
946            vec![PrologTerm::Op(
947                "/".to_string(),
948                Box::new(atom(name)),
949                Box::new(PrologTerm::Integer(arity as i64)),
950            )],
951        )
952    }
953}
954/// Configuration options for the Prolog backend.
955#[derive(Debug, Default, Clone)]
956pub struct PrologBackendOptions {
957    /// Emit blank lines between predicates.
958    pub blank_between_predicates: bool,
959    /// Emit `%% doc` comments for predicates with doc strings.
960    pub emit_docs: bool,
961}
962/// Build CLP(FD) constraint goals for SWI-Prolog.
963#[allow(dead_code)]
964pub struct PrologConstraints;
965impl PrologConstraints {
966    /// `X in Low..High` — domain constraint.
967    #[allow(dead_code)]
968    pub fn in_range(x: PrologTerm, low: PrologTerm, high: PrologTerm) -> PrologTerm {
969        PrologTerm::Op(
970            "in".to_string(),
971            Box::new(x),
972            Box::new(PrologTerm::Op(
973                "..".to_string(),
974                Box::new(low),
975                Box::new(high),
976            )),
977        )
978    }
979    /// `X #= Y` — arithmetic equality constraint.
980    #[allow(dead_code)]
981    pub fn clp_eq(x: PrologTerm, y: PrologTerm) -> PrologTerm {
982        PrologTerm::Op("#=".to_string(), Box::new(x), Box::new(y))
983    }
984    /// `X #\= Y` — arithmetic disequality constraint.
985    #[allow(dead_code)]
986    pub fn clp_neq(x: PrologTerm, y: PrologTerm) -> PrologTerm {
987        PrologTerm::Op("#\\=".to_string(), Box::new(x), Box::new(y))
988    }
989    /// `X #< Y` — less-than constraint.
990    #[allow(dead_code)]
991    pub fn clp_lt(x: PrologTerm, y: PrologTerm) -> PrologTerm {
992        PrologTerm::Op("#<".to_string(), Box::new(x), Box::new(y))
993    }
994    /// `X #> Y` — greater-than constraint.
995    #[allow(dead_code)]
996    pub fn clp_gt(x: PrologTerm, y: PrologTerm) -> PrologTerm {
997        PrologTerm::Op("#>".to_string(), Box::new(x), Box::new(y))
998    }
999    /// `X #=< Y` — less-than-or-equal constraint.
1000    #[allow(dead_code)]
1001    pub fn clp_le(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1002        PrologTerm::Op("#=<".to_string(), Box::new(x), Box::new(y))
1003    }
1004    /// `X #>= Y` — greater-than-or-equal constraint.
1005    #[allow(dead_code)]
1006    pub fn clp_ge(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1007        PrologTerm::Op("#>=".to_string(), Box::new(x), Box::new(y))
1008    }
1009    /// `all_different(Vars)` — all-different constraint.
1010    #[allow(dead_code)]
1011    pub fn all_different(vars: Vec<PrologTerm>) -> PrologTerm {
1012        compound("all_different", vec![PrologTerm::list(vars)])
1013    }
1014    /// `label(Vars)` — labelling goal.
1015    #[allow(dead_code)]
1016    pub fn label(vars: Vec<PrologTerm>) -> PrologTerm {
1017        compound("label", vec![PrologTerm::list(vars)])
1018    }
1019    /// `sum(Vars, #=, Sum)` — sum constraint.
1020    #[allow(dead_code)]
1021    pub fn sum_eq(vars: Vec<PrologTerm>, sum: PrologTerm) -> PrologTerm {
1022        compound("sum", vec![PrologTerm::list(vars), atom("#="), sum])
1023    }
1024}
1025/// A Prolog clause: either a fact `Head.` or a rule `Head :- Body.`
1026#[derive(Debug, Clone, PartialEq)]
1027pub struct PrologClause {
1028    /// The head of the clause.
1029    pub head: PrologTerm,
1030    /// Body goals (empty for facts).
1031    pub body: Vec<PrologTerm>,
1032    /// True if this is a fact (body is empty and displayed without `:-`).
1033    pub is_fact: bool,
1034    /// Optional comment for documentation.
1035    pub comment: Option<String>,
1036}
1037impl PrologClause {
1038    /// Create a fact clause.
1039    pub fn fact(head: PrologTerm) -> Self {
1040        PrologClause {
1041            head,
1042            body: vec![],
1043            is_fact: true,
1044            comment: None,
1045        }
1046    }
1047    /// Create a rule clause.
1048    pub fn rule(head: PrologTerm, body: Vec<PrologTerm>) -> Self {
1049        PrologClause {
1050            head,
1051            body,
1052            is_fact: false,
1053            comment: None,
1054        }
1055    }
1056    /// Add a comment.
1057    pub fn with_comment(mut self, comment: impl Into<String>) -> Self {
1058        self.comment = Some(comment.into());
1059        self
1060    }
1061    /// Emit this clause as a Prolog string.
1062    pub fn emit(&self) -> String {
1063        let mut out = String::new();
1064        if let Some(c) = &self.comment {
1065            out.push_str(&format!("% {}\n", c));
1066        }
1067        if self.is_fact || self.body.is_empty() {
1068            out.push_str(&format!("{}.", self.head));
1069        } else {
1070            out.push_str(&format!("{} :-", self.head));
1071            if self.body.len() == 1 {
1072                out.push_str(&format!("\n    {}.", self.body[0]));
1073            } else {
1074                for (i, goal) in self.body.iter().enumerate() {
1075                    if i < self.body.len() - 1 {
1076                        out.push_str(&format!("\n    {},", goal));
1077                    } else {
1078                        out.push_str(&format!("\n    {}.", goal));
1079                    }
1080                }
1081            }
1082        }
1083        out
1084    }
1085}
1086/// Prolog type descriptor for plDoc-style type checking.
1087#[derive(Debug, Clone, PartialEq)]
1088#[allow(dead_code)]
1089pub enum PrologType {
1090    /// `integer`
1091    Integer,
1092    /// `float`
1093    Float,
1094    /// `atom`
1095    Atom,
1096    /// `string`
1097    PrologString,
1098    /// `is_list(T)`
1099    List(Box<PrologType>),
1100    /// `compound`
1101    Compound,
1102    /// `callable`
1103    Callable,
1104    /// `term`
1105    Term,
1106    /// `boolean` (true/false atom)
1107    Boolean,
1108    /// `var`
1109    Var,
1110    /// `nonvar`
1111    Nonvar,
1112    /// `number`
1113    Number,
1114    /// `atomic`
1115    Atomic,
1116    /// `positive_integer`
1117    PositiveInteger,
1118    /// `nonneg`
1119    NonNeg,
1120    /// Custom type name.
1121    Custom(String),
1122}
1123/// A DCG (Definite Clause Grammar) rule: `lhs --> rhs.`
1124#[derive(Debug, Clone, PartialEq)]
1125pub struct DcgRule {
1126    /// Left-hand side nonterminal.
1127    pub lhs: PrologTerm,
1128    /// Right-hand side: sequence of nonterminals, terminals `[token]`, and pushback notation.
1129    pub rhs: Vec<DcgRhs>,
1130    /// Optional Prolog goals in `{...}`.
1131    pub guards: Vec<PrologTerm>,
1132    /// Optional comment.
1133    pub comment: Option<String>,
1134}
1135impl DcgRule {
1136    /// Emit this DCG rule as a Prolog string.
1137    pub fn emit(&self) -> String {
1138        let mut out = String::new();
1139        if let Some(c) = &self.comment {
1140            out.push_str(&format!("% {}\n", c));
1141        }
1142        out.push_str(&format!("{} -->", self.lhs));
1143        let mut parts: Vec<String> = self.rhs.iter().map(|r| format!("{}", r)).collect();
1144        if !self.guards.is_empty() {
1145            let guard_str = self
1146                .guards
1147                .iter()
1148                .map(|g| format!("{}", g))
1149                .collect::<Vec<_>>()
1150                .join(", ");
1151            parts.push(format!("{{{}}}", guard_str));
1152        }
1153        if parts.is_empty() {
1154            out.push_str("\n    [].");
1155        } else if parts.len() == 1 {
1156            out.push_str(&format!("\n    {}.", parts[0]));
1157        } else {
1158            for (i, p) in parts.iter().enumerate() {
1159                if i < parts.len() - 1 {
1160                    out.push_str(&format!("\n    {},", p));
1161                } else {
1162                    out.push_str(&format!("\n    {}.", p));
1163                }
1164            }
1165        }
1166        out
1167    }
1168}
1169/// A plDoc-style predicate type signature.
1170#[derive(Debug, Clone)]
1171#[allow(dead_code)]
1172pub struct PrologTypeSig {
1173    /// Predicate name.
1174    pub name: String,
1175    /// Parameter types and modes (mode, type).
1176    pub params: Vec<(PrologMode, PrologType)>,
1177    /// Optional description.
1178    pub description: Option<String>,
1179}
1180impl PrologTypeSig {
1181    /// Emit the type signature as a plDoc comment block.
1182    #[allow(dead_code)]
1183    pub fn emit_pldoc(&self) -> String {
1184        let mut out = String::new();
1185        out.push_str(&format!("%% {}/{}\n", self.name, self.params.len()));
1186        if let Some(desc) = &self.description {
1187            out.push_str(&format!("%  {}\n", desc));
1188        }
1189        for (mode, ty) in &self.params {
1190            out.push_str(&format!("%  @param {} {}\n", mode, ty));
1191        }
1192        out
1193    }
1194    /// Emit as a `:- pred` directive (SICStus style).
1195    #[allow(dead_code)]
1196    pub fn emit_pred_directive(&self) -> String {
1197        let param_str: Vec<String> = self
1198            .params
1199            .iter()
1200            .map(|(m, t)| format!("{}({})", m, t))
1201            .collect();
1202        format!(":- pred {}({}).\n", self.name, param_str.join(", "))
1203    }
1204}
1205/// Build an arithmetic expression for use in `X is Expr`.
1206#[allow(dead_code)]
1207pub struct PrologArith;
1208impl PrologArith {
1209    /// Add: `X + Y`.
1210    #[allow(dead_code)]
1211    pub fn add(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1212        arith_add(x, y)
1213    }
1214    /// Sub: `X - Y`.
1215    #[allow(dead_code)]
1216    pub fn sub(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1217        arith_sub(x, y)
1218    }
1219    /// Mul: `X * Y`.
1220    #[allow(dead_code)]
1221    pub fn mul(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1222        arith_mul(x, y)
1223    }
1224    /// Div: `X // Y` — integer division.
1225    #[allow(dead_code)]
1226    pub fn idiv(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1227        PrologTerm::Op("//".to_string(), Box::new(x), Box::new(y))
1228    }
1229    /// Mod: `X mod Y`.
1230    #[allow(dead_code)]
1231    pub fn mmod(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1232        arith_mod(x, y)
1233    }
1234    /// Absolute value: `abs(X)`.
1235    #[allow(dead_code)]
1236    pub fn abs(x: PrologTerm) -> PrologTerm {
1237        compound("abs", vec![x])
1238    }
1239    /// Max: `max(X, Y)`.
1240    #[allow(dead_code)]
1241    pub fn max(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1242        compound("max", vec![x, y])
1243    }
1244    /// Min: `min(X, Y)`.
1245    #[allow(dead_code)]
1246    pub fn min(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1247        compound("min", vec![x, y])
1248    }
1249    /// Exponentiation: `X ^ Y`.
1250    #[allow(dead_code)]
1251    pub fn pow(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1252        PrologTerm::Op("^".to_string(), Box::new(x), Box::new(y))
1253    }
1254    /// Square root: `sqrt(X)`.
1255    #[allow(dead_code)]
1256    pub fn sqrt(x: PrologTerm) -> PrologTerm {
1257        compound("sqrt", vec![x])
1258    }
1259    /// Truncate: `truncate(X)`.
1260    #[allow(dead_code)]
1261    pub fn truncate(x: PrologTerm) -> PrologTerm {
1262        compound("truncate", vec![x])
1263    }
1264    /// Round: `round(X)`.
1265    #[allow(dead_code)]
1266    pub fn round(x: PrologTerm) -> PrologTerm {
1267        compound("round", vec![x])
1268    }
1269    /// Sign: `sign(X)`.
1270    #[allow(dead_code)]
1271    pub fn sign(x: PrologTerm) -> PrologTerm {
1272        compound("sign", vec![x])
1273    }
1274    /// Bitwise AND: `X /\ Y`.
1275    #[allow(dead_code)]
1276    pub fn bitand(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1277        PrologTerm::Op("/\\".to_string(), Box::new(x), Box::new(y))
1278    }
1279    /// Bitwise OR: `X \/ Y`.
1280    #[allow(dead_code)]
1281    pub fn bitor(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1282        PrologTerm::Op("\\/".to_string(), Box::new(x), Box::new(y))
1283    }
1284    /// Bitwise XOR: `X xor Y`.
1285    #[allow(dead_code)]
1286    pub fn xor(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1287        PrologTerm::Op("xor".to_string(), Box::new(x), Box::new(y))
1288    }
1289    /// Left shift: `X << Y`.
1290    #[allow(dead_code)]
1291    pub fn shl(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1292        PrologTerm::Op("<<".to_string(), Box::new(x), Box::new(y))
1293    }
1294    /// Right shift: `X >> Y`.
1295    #[allow(dead_code)]
1296    pub fn shr(x: PrologTerm, y: PrologTerm) -> PrologTerm {
1297        PrologTerm::Op(">>".to_string(), Box::new(x), Box::new(y))
1298    }
1299}
1300/// A Prolog term (the universal data structure in Prolog).
1301#[derive(Debug, Clone, PartialEq)]
1302pub enum PrologTerm {
1303    /// An atom: lowercase identifier or quoted string, e.g. `foo`, `'hello world'`
1304    Atom(String),
1305    /// An integer literal, e.g. `42`, `-7`
1306    Integer(i64),
1307    /// A float literal, e.g. `3.14`
1308    Float(f64),
1309    /// A variable: uppercase-starting or `_`-prefixed name, e.g. `X`, `_Acc`
1310    Variable(String),
1311    /// A compound term: `functor(arg1, arg2, ...)`, e.g. `f(a, b)`, `+(1, 2)`
1312    Compound(String, Vec<PrologTerm>),
1313    /// A proper or partial list: `[H|T]`, `[1,2,3]`
1314    List(Vec<PrologTerm>, Option<Box<PrologTerm>>),
1315    /// The empty list `[]`
1316    Nil,
1317    /// A DCG rule term: `phrase(Rule, List)`
1318    DcgPhrase(Box<PrologTerm>, Box<PrologTerm>),
1319    /// An operator term written in infix notation, e.g. `X is Y + 1`
1320    Op(String, Box<PrologTerm>, Box<PrologTerm>),
1321    /// A prefix operator term, e.g. `\+(X)`
1322    PrefixOp(String, Box<PrologTerm>),
1323    /// A cut `!`
1324    Cut,
1325    /// Anonymous variable `_`
1326    Anon,
1327}
1328impl PrologTerm {
1329    /// Create an atom.
1330    pub fn atom(s: impl Into<String>) -> Self {
1331        PrologTerm::Atom(s.into())
1332    }
1333    /// Create a variable.
1334    pub fn var(s: impl Into<String>) -> Self {
1335        PrologTerm::Variable(s.into())
1336    }
1337    /// Create a compound term.
1338    pub fn compound(functor: impl Into<String>, args: Vec<PrologTerm>) -> Self {
1339        PrologTerm::Compound(functor.into(), args)
1340    }
1341    /// Create a proper list from elements.
1342    pub fn list(elems: Vec<PrologTerm>) -> Self {
1343        PrologTerm::List(elems, None)
1344    }
1345    /// Create a partial list `[H1,H2,...|T]`.
1346    pub fn list_partial(elems: Vec<PrologTerm>, tail: PrologTerm) -> Self {
1347        PrologTerm::List(elems, Some(Box::new(tail)))
1348    }
1349    /// Create an infix operator term.
1350    pub fn op(op: impl Into<String>, lhs: PrologTerm, rhs: PrologTerm) -> Self {
1351        PrologTerm::Op(op.into(), Box::new(lhs), Box::new(rhs))
1352    }
1353    /// Create a prefix operator term.
1354    pub fn prefix_op(op: impl Into<String>, arg: PrologTerm) -> Self {
1355        PrologTerm::PrefixOp(op.into(), Box::new(arg))
1356    }
1357    /// Arity of this term as a functor (0 for atoms, variables, etc.)
1358    pub fn functor_arity(&self) -> usize {
1359        match self {
1360            PrologTerm::Compound(_, args) => args.len(),
1361            _ => 0,
1362        }
1363    }
1364    /// True if this term needs parentheses when used as an argument.
1365    pub(super) fn needs_parens_as_arg(&self) -> bool {
1366        matches!(self, PrologTerm::Op(_, _, _) | PrologTerm::PrefixOp(_, _))
1367    }
1368    /// Whether an atom needs quoting.
1369    pub(super) fn needs_quoting(s: &str) -> bool {
1370        if s.is_empty() {
1371            return true;
1372        }
1373        let mut chars = s.chars();
1374        let first = chars
1375            .next()
1376            .expect("s is non-empty; guaranteed by early return on s.is_empty() above");
1377        if s.chars().all(|c| "#&*+-./:<=>?@\\^~".contains(c)) {
1378            return false;
1379        }
1380        if !first.is_lowercase() && first != '_' {
1381            return true;
1382        }
1383        s.chars().any(|c| !c.is_alphanumeric() && c != '_')
1384    }
1385    /// Format an atom, quoting if necessary.
1386    pub(super) fn fmt_atom(s: &str) -> String {
1387        if Self::needs_quoting(s) {
1388            format!("'{}'", s.replace('\'', "\\'"))
1389        } else {
1390            s.to_string()
1391        }
1392    }
1393}
1394/// Standard higher-order predicates in Prolog.
1395#[allow(dead_code)]
1396pub struct PrologMetaPredicates;
1397impl PrologMetaPredicates {
1398    /// A `maplist/2` call: `maplist(Goal, List)`.
1399    #[allow(dead_code)]
1400    pub fn maplist(goal: PrologTerm, list: PrologTerm) -> PrologTerm {
1401        compound("maplist", vec![goal, list])
1402    }
1403    /// A `maplist/3` call: `maplist(Goal, List, Result)`.
1404    #[allow(dead_code)]
1405    pub fn maplist2(goal: PrologTerm, list: PrologTerm, result: PrologTerm) -> PrologTerm {
1406        compound("maplist", vec![goal, list, result])
1407    }
1408    /// An `include/3` call.
1409    #[allow(dead_code)]
1410    pub fn include(goal: PrologTerm, list: PrologTerm, result: PrologTerm) -> PrologTerm {
1411        compound("include", vec![goal, list, result])
1412    }
1413    /// An `exclude/3` call.
1414    #[allow(dead_code)]
1415    pub fn exclude(goal: PrologTerm, list: PrologTerm, result: PrologTerm) -> PrologTerm {
1416        compound("exclude", vec![goal, list, result])
1417    }
1418    /// A `foldl/4` call.
1419    #[allow(dead_code)]
1420    pub fn foldl(goal: PrologTerm, list: PrologTerm, v0: PrologTerm, v: PrologTerm) -> PrologTerm {
1421        compound("foldl", vec![goal, list, v0, v])
1422    }
1423    /// An `aggregate_all/3` call.
1424    #[allow(dead_code)]
1425    pub fn aggregate_all(template: PrologTerm, goal: PrologTerm, result: PrologTerm) -> PrologTerm {
1426        compound("aggregate_all", vec![template, goal, result])
1427    }
1428    /// A `call/N` call.
1429    #[allow(dead_code)]
1430    pub fn call_n(f: PrologTerm, mut args: Vec<PrologTerm>) -> PrologTerm {
1431        let mut all_args = vec![f];
1432        all_args.append(&mut args);
1433        compound("call", all_args)
1434    }
1435    /// A `once/1` call.
1436    #[allow(dead_code)]
1437    pub fn once(goal: PrologTerm) -> PrologTerm {
1438        compound("once", vec![goal])
1439    }
1440    /// An `ignore/1` call (succeeds even if goal fails).
1441    #[allow(dead_code)]
1442    pub fn ignore(goal: PrologTerm) -> PrologTerm {
1443        compound("ignore", vec![goal])
1444    }
1445    /// A `forall/2` call: `forall(Cond, Action)`.
1446    #[allow(dead_code)]
1447    pub fn forall(cond: PrologTerm, action: PrologTerm) -> PrologTerm {
1448        compound("forall", vec![cond, action])
1449    }
1450    /// A `findall/3` call.
1451    #[allow(dead_code)]
1452    pub fn findall(template: PrologTerm, goal: PrologTerm, bag: PrologTerm) -> PrologTerm {
1453        compound("findall", vec![template, goal, bag])
1454    }
1455    /// A `bagof/3` call.
1456    #[allow(dead_code)]
1457    pub fn bagof(template: PrologTerm, goal: PrologTerm, bag: PrologTerm) -> PrologTerm {
1458        compound("bagof", vec![template, goal, bag])
1459    }
1460    /// A `setof/3` call.
1461    #[allow(dead_code)]
1462    pub fn setof(template: PrologTerm, goal: PrologTerm, bag: PrologTerm) -> PrologTerm {
1463        compound("setof", vec![template, goal, bag])
1464    }
1465}