1const PREAMBLE: &str = include_str!("../agent-preamble.md");
21
22const DIAGNOSTICS: &str = include_str!("../diagnostics.md");
31
32pub fn render() -> String {
34 let mut out = String::with_capacity(32 * 1024);
35
36 out.push_str("# Aura ");
37 out.push_str(env!("CARGO_PKG_VERSION"));
38 out.push_str(" — language reference for coding assistants\n\n");
39 out.push_str(
40 "Generated by `aura docs --agent` from the compiler's own definitions, so it \
41 describes the binary that produced it. Regenerate rather than edit.\n\n",
42 );
43
44 out.push_str(PREAMBLE.trim_end());
45 out.push_str("\n\n## Keywords\n\nAll ");
46 out.push_str(&crate::lexer::token::KEYWORDS.len().to_string());
47 out.push_str(" reserved words:\n\n");
48 out.push_str(&crate::lexer::token::KEYWORDS.join(", "));
49 out.push_str(
50 ".\n\nPlus `text`, which is *not* reserved: it opens a block string only \
51 where a value is expected, and is an ordinary identifier everywhere \
52 else.\n",
53 );
54
55 out.push_str("\n## Standard library\n\n");
56 out.push_str(
57 "Methods are called on a value (`\"a\".upper()`, `xs.map(f)`); builtins are \
58 global functions.\n\n",
59 );
60 out.push_str(&stdlib_section());
61
62 out.push_str("\n## Diagnostics\n\n");
63 out.push_str(
64 "Every code is stable. When one is reported, the fix is in this table rather \
65 than in guesswork.\n\n",
66 );
67 out.push_str(strip_front_matter(DIAGNOSTICS).trim_end());
68 out.push('\n');
69
70 out
71}
72
73fn stdlib_section() -> String {
80 let Ok(json) = eval_manifest(crate::STDLIB_MANIFEST) else {
81 return "_The stdlib manifest failed to evaluate; see crates/aura-lang/stdlib.aura._\n"
82 .to_string();
83 };
84 let Some(groups) = json.as_object() else {
85 return String::new();
86 };
87
88 let mut out = String::new();
89 for (receiver, methods) in groups {
90 let Some(methods) = methods.as_object() else {
91 continue;
92 };
93 out.push_str("### ");
94 out.push_str(if receiver == "builtins" {
95 "Builtins"
96 } else {
97 receiver
98 });
99 out.push_str("\n\n| Signature | |\n| --- | --- |\n");
100 for (name, spec) in methods {
101 let params = spec
102 .get("params")
103 .and_then(|p| p.as_array())
104 .map(|xs| {
105 xs.iter()
106 .filter_map(|v| v.as_str())
107 .collect::<Vec<_>>()
108 .join(", ")
109 })
110 .unwrap_or_default();
111 let returns = spec.get("returns").and_then(|v| v.as_str()).unwrap_or("?");
112 let doc = spec.get("doc").and_then(|v| v.as_str()).unwrap_or("");
113 out.push_str(&format!("| `{name}({params}) -> {returns}` | {doc} |\n"));
114 }
115 out.push('\n');
116 }
117 out
118}
119
120fn eval_manifest(src: &str) -> Result<serde_json::Value, ()> {
121 let tokens = crate::lexer::Lexer::new(src, 0)
122 .tokenize()
123 .map_err(|_| ())?;
124 let module = crate::parser::Parser::new(tokens)
125 .parse_module()
126 .map_err(|_| ())?;
127 let mut interp = crate::eval::Interpreter::new(crate::eval::Options {
128 strict: false,
129 dry_run: false,
130 });
131 let value = interp.eval_module(&module).map_err(|_| ())?;
132 crate::serialize::to_json(&value).map_err(|_| ())
133}
134
135fn strip_front_matter(md: &str) -> String {
138 let body = md
139 .split_once("\n## ")
140 .map_or(md, |(_, rest)| rest)
141 .to_string();
142 let mut out = String::from("### ");
144 out.push_str(&body.replace("\n## ", "\n### "));
145 out
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
156 fn the_reference_carries_every_section_it_claims() {
157 let doc = render();
158 for needle in [
159 "## Keywords",
160 "## Standard library",
161 "## Diagnostics",
162 "### Builtins",
163 "`:` exports, `=` does not",
164 ] {
165 assert!(doc.contains(needle), "missing from the reference: {needle}");
166 }
167 }
168
169 #[test]
173 fn the_stdlib_section_is_populated_from_the_manifest() {
174 let doc = render();
175 assert!(
176 !doc.contains("failed to evaluate"),
177 "the stdlib manifest did not evaluate"
178 );
179 for needle in ["`upper() -> String`", "`map(fn) -> List`", "### String"] {
180 assert!(
181 doc.contains(needle),
182 "expected {needle} in the stdlib tables"
183 );
184 }
185 }
186
187 #[test]
190 fn every_reserved_word_is_listed() {
191 let doc = render();
192 for kw in crate::lexer::token::KEYWORDS {
193 assert!(doc.contains(kw), "keyword absent from the reference: {kw}");
194 }
195 }
196}