Skip to main content

logicaffeine_lsp/
stdlib_docs.rs

1//! The stdlib teaching registry: every prelude definition's literate
2//! documentation, read once from the RAW embedded module sources
3//! (`loader::prelude_module_sources` — Notes included) through
4//! `teach::extract_literate_docs`. Hover, completion, and signature help
5//! fall back here when a name has no local definition, so `md5` teaches in
6//! the editor exactly what its `## Note` says in the source.
7
8use std::collections::HashMap;
9use std::sync::OnceLock;
10
11use logicaffeine_language::teach::extract_literate_docs;
12
13/// One stdlib definition's teaching surface.
14pub struct StdlibDoc {
15    /// The full `##` header (or `## Definition` body type line), verbatim.
16    pub signature: String,
17    /// The `## Note` prose directly above the definition.
18    pub doc: Option<String>,
19    /// True for type definitions (`## A … has/is`), false for functions.
20    pub is_type: bool,
21}
22
23fn registry() -> &'static HashMap<String, StdlibDoc> {
24    static DOCS: OnceLock<HashMap<String, StdlibDoc>> = OnceLock::new();
25    DOCS.get_or_init(|| {
26        let mut map = HashMap::new();
27        for src in logicaffeine_compile::loader::prelude_module_sources() {
28            for lit in extract_literate_docs(src) {
29                let is_type = !lit.signature.contains("## To");
30                map.entry(lit.name).or_insert(StdlibDoc {
31                    signature: lit.signature,
32                    doc: lit.doc,
33                    is_type,
34                });
35            }
36        }
37        map
38    })
39}
40
41/// The teaching surface for a stdlib prelude name, if it is one.
42pub fn stdlib_doc(name: &str) -> Option<&'static StdlibDoc> {
43    registry().get(name)
44}
45
46/// Every stdlib name with its doc, name-sorted (completion feed).
47pub fn all() -> Vec<(&'static str, &'static StdlibDoc)> {
48    let mut entries: Vec<(&'static str, &'static StdlibDoc)> =
49        registry().iter().map(|(name, doc)| (name.as_str(), doc)).collect();
50    entries.sort_by_key(|(name, _)| *name);
51    entries
52}
53
54/// Hover markdown for a stdlib name: the signature as code, then the prose.
55pub fn hover_md(name: &str, entry: &StdlibDoc) -> String {
56    let mut md = format!(
57        "**{name}** — standard library\n\n```\n{}\n```",
58        entry.signature
59    );
60    if let Some(doc) = &entry.doc {
61        md.push_str("\n\n");
62        md.push_str(doc);
63    }
64    md
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn the_registry_covers_the_prelude_vocabulary() {
73        for name in logicaffeine_compile::loader::prelude_vocabulary() {
74            assert!(
75                stdlib_doc(&name).is_some(),
76                "{name}: in prelude_vocabulary but missing from the stdlib docs registry"
77            );
78        }
79    }
80
81    #[test]
82    fn md5_teaches_from_its_literate_note() {
83        let entry = stdlib_doc("md5").expect("md5 is stdlib");
84        assert!(entry.signature.contains("md5"), "{}", entry.signature);
85        assert!(
86            entry.doc.as_deref().is_some_and(|d| d.contains("MD5")),
87            "md5's Note must teach: {:?}",
88            entry.doc
89        );
90        assert!(!entry.is_type);
91    }
92
93    #[test]
94    fn definition_body_types_are_typed_entries() {
95        let entry = stdlib_doc("Message").expect("net.md defines Message");
96        assert!(entry.is_type);
97        assert!(entry.doc.is_some(), "the Definition block's Note documents Message");
98    }
99
100    #[test]
101    fn hover_md_carries_signature_and_prose() {
102        let entry = stdlib_doc("read").expect("file.lg defines read");
103        let md = hover_md("read", entry);
104        assert!(md.contains("standard library"), "{md}");
105        assert!(md.contains("## To native read"), "{md}");
106        assert!(md.contains("Reads a whole file"), "{md}");
107    }
108}