Skip to main content

patch_prolog_frontend/
lib.rs

1//! plg-frontend: ISO Prolog tokenizer, parser, and source-level static
2//! analysis, ported from patch-prolog.
3//!
4//! Consumed by the compiler and the LSP. Compiled Prolog binaries carry a
5//! minimal goal-only parser inside the runtime instead.
6
7pub mod error;
8pub mod lint;
9pub mod parse_error;
10pub mod parser;
11pub mod source_map;
12pub mod tokenizer;
13
14pub use error::{PrologError, ThrownError, format_term};
15pub use parse_error::ParseError;
16pub use parser::{CallSite, CgClause, Parser, ProgramDirectives};
17pub use source_map::SourceMap;
18pub use tokenizer::{Token, TokenKind, Tokenizer};
19
20use std::sync::OnceLock;
21
22/// The `(name, arity)` of every predicate defined in the embedded stdlib
23/// (`plg_shared::STDLIB_PL`), parsed once and cached. Lets the REPL (and,
24/// eventually, the LSP) offer stdlib predicates — `member`/`append`/`length`/
25/// `reverse`/`nth0`/`nth1`/`last` — in completion from one source, alongside
26/// the builtin table. Returns an empty slice if the stdlib ever fails to parse
27/// (it can't in practice; the stdlib is fixed).
28pub fn stdlib_predicates() -> &'static [(String, usize)] {
29    static CACHE: OnceLock<Vec<(String, usize)>> = OnceLock::new();
30    CACHE.get_or_init(|| {
31        let mut interner = plg_shared::StringInterner::new();
32        let Ok((clauses, _)) =
33            Parser::parse_program_with_directives(plg_shared::STDLIB_PL, &mut interner)
34        else {
35            return Vec::new();
36        };
37        let seen: std::collections::BTreeSet<(String, usize)> = clauses
38            .iter()
39            .filter_map(|c| c.head.functor_arity())
40            .map(|(id, arity)| (interner.resolve(id).to_string(), arity))
41            .collect();
42        seen.into_iter().collect()
43    })
44}
45
46#[cfg(test)]
47mod tests {
48    use super::stdlib_predicates;
49
50    #[test]
51    fn stdlib_predicates_include_list_helpers() {
52        let preds = stdlib_predicates();
53        for (name, arity) in [("append", 3), ("member", 2), ("length", 2), ("reverse", 2)] {
54            assert!(
55                preds.iter().any(|(n, a)| n == name && *a == arity),
56                "stdlib missing {name}/{arity}"
57            );
58        }
59    }
60
61    #[test]
62    fn stdlib_predicates_is_cached_and_stable() {
63        // Same static slice on every call (OnceLock).
64        let a = stdlib_predicates() as *const _;
65        let b = stdlib_predicates() as *const _;
66        assert_eq!(a, b);
67    }
68}