1use std::collections::HashMap;
16use std::sync::OnceLock;
17
18use crate::form::Form;
19use crate::intrinsics::fold;
20use crate::reader::read_forms;
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum FormulaSupport {
25 Yes,
26 Refused,
27 Undeclared,
28}
29
30#[derive(Clone, Debug, PartialEq)]
32pub struct HeadRow {
33 pub symbol: String,
34 pub aliases: Vec<String>,
35 pub arity: String,
36 pub interp_routine: String,
37 pub vba_routine: String,
38 pub formula: FormulaSupport,
39 pub export_only: bool,
40}
41
42const TABLE_TEXT: &str = include_str!("../data/headtable.vla");
43
44fn parse_rows() -> Vec<HeadRow> {
45 let text = TABLE_TEXT.replace("\r\n", "\n");
46 let forms = match read_forms(&text) {
47 Ok(forms) => forms,
48 Err(_) => return Vec::new(),
49 };
50 let mut rows = Vec::with_capacity(forms.len());
51 for form in &forms {
52 let Form::List(l) = form else { continue };
53 if !l.head_is("head") || l.items.len() != 8 {
54 continue;
55 }
56 let (
57 Form::Sym(symbol),
58 Form::List(aliases),
59 Form::Str(arity),
60 Form::Str(interp),
61 Form::Str(vba),
62 Form::Sym(formula),
63 Form::Sym(export_only),
64 ) = (
65 &l.items[1],
66 &l.items[2],
67 &l.items[3],
68 &l.items[4],
69 &l.items[5],
70 &l.items[6],
71 &l.items[7],
72 )
73 else {
74 continue;
75 };
76 let formula = match formula.as_str() {
77 "yes" => FormulaSupport::Yes,
78 "refused" => FormulaSupport::Refused,
79 "undeclared" => FormulaSupport::Undeclared,
80 _ => continue,
81 };
82 let aliases = aliases
83 .items
84 .iter()
85 .filter_map(|a| match a {
86 Form::Sym(s) => Some(s.clone()),
87 _ => None,
88 })
89 .collect();
90 rows.push(HeadRow {
91 symbol: symbol.clone(),
92 aliases,
93 arity: arity.clone(),
94 interp_routine: interp.clone(),
95 vba_routine: vba.clone(),
96 formula,
97 export_only: export_only == "true",
98 });
99 }
100 rows
101}
102
103pub fn rows() -> &'static [HeadRow] {
105 static ROWS: OnceLock<Vec<HeadRow>> = OnceLock::new();
106 ROWS.get_or_init(parse_rows)
107}
108
109pub fn count() -> usize {
111 rows().len()
112}
113
114pub fn row(symbol: &str) -> Option<&'static HeadRow> {
117 let key = fold(symbol);
118 rows().iter().find(|r| fold(&r.symbol) == key).or_else(|| {
119 rows()
120 .iter()
121 .find(|r| r.aliases.iter().any(|a| fold(a) == key))
122 })
123}
124
125pub fn alias_map() -> &'static HashMap<String, String> {
129 static MAP: OnceLock<HashMap<String, String>> = OnceLock::new();
130 MAP.get_or_init(|| {
131 let mut m = HashMap::new();
132 for r in rows() {
133 for a in &r.aliases {
134 m.insert(fold(a), r.symbol.clone());
135 }
136 }
137 m
138 })
139}
140
141pub fn resolve_head_alias(folded_head: &str) -> String {
144 match alias_map().get(folded_head) {
145 Some(canon) => canon.clone(),
146 None => folded_head.to_string(),
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn the_table_is_read_whole() {
156 assert_eq!(count(), 65);
159 assert_eq!(rows()[0].symbol, "sub");
160 assert_eq!(rows()[0].vba_routine, "EmitProc");
161 assert_eq!(rows()[64].symbol, "not");
162 let yes = rows()
163 .iter()
164 .filter(|r| r.formula == FormulaSupport::Yes)
165 .count();
166 let refused = rows()
167 .iter()
168 .filter(|r| r.formula == FormulaSupport::Refused)
169 .count();
170 let undeclared = rows()
171 .iter()
172 .filter(|r| r.formula == FormulaSupport::Undeclared)
173 .count();
174 assert_eq!((yes, refused, undeclared), (18, 9, 38));
175 }
176
177 #[test]
178 fn export_only_is_exactly_raw_deflambda_lambda() {
179 let set: Vec<&str> = rows()
181 .iter()
182 .filter(|r| r.export_only)
183 .map(|r| r.symbol.as_str())
184 .collect();
185 assert_eq!(set, vec!["raw", "deflambda", "lambda"]);
186 }
187
188 #[test]
189 fn the_three_aliases_resolve_and_nothing_else_does() {
190 assert_eq!(resolve_head_alias("fijar!"), "set!");
191 assert_eq!(resolve_head_alias("si"), "if");
192 assert_eq!(resolve_head_alias("depurar"), "debug-print");
193 assert_eq!(resolve_head_alias("set!"), "set!");
194 assert_eq!(resolve_head_alias("unknown"), "unknown");
195 assert_eq!(alias_map().len(), 3);
196 assert_eq!(row("FIJAR!").unwrap().symbol, "set!");
197 assert_eq!(row("Mod").unwrap().arity, "2 (a, b)");
198 assert!(row("nope").is_none());
199 }
200}