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