1use crate::intrinsics::fold;
18use crate::messages::{raise, Refusal};
19
20#[derive(Clone, Debug)]
23pub enum Form {
24 Sym(String),
25 Str(String),
26 List(List),
27}
28
29#[derive(Clone, Debug, Default)]
32pub struct List {
33 pub items: Vec<Form>,
34 pub line: u32,
35}
36
37impl PartialEq for Form {
38 fn eq(&self, other: &Form) -> bool {
39 match (self, other) {
40 (Form::Sym(a), Form::Sym(b)) => a == b,
41 (Form::Str(a), Form::Str(b)) => a == b,
42 (Form::List(a), Form::List(b)) => a == b,
43 _ => false,
44 }
45 }
46}
47
48impl PartialEq for List {
49 fn eq(&self, other: &List) -> bool {
50 self.items == other.items
51 }
52}
53
54impl Form {
55 pub fn sym(text: &str) -> Form {
56 Form::Sym(text.to_string())
57 }
58
59 pub fn string(content: &str) -> Form {
60 Form::Str(content.to_string())
61 }
62
63 pub fn list(items: Vec<Form>) -> Form {
65 Form::List(List { items, line: 0 })
66 }
67
68 pub fn is_list(&self) -> bool {
69 matches!(self, Form::List(_))
70 }
71
72 pub fn is_sym(&self) -> bool {
74 matches!(self, Form::Sym(_))
75 }
76
77 pub fn is_keyword_arg(&self) -> bool {
80 match self {
81 Form::Sym(s) => s.starts_with(':') && s.len() > 1,
82 _ => false,
83 }
84 }
85
86 pub fn atom_text(&self) -> Option<String> {
89 match self {
90 Form::Sym(s) => Some(s.clone()),
91 Form::Str(s) => Some(format!("\"{s}")),
92 Form::List(_) => None,
93 }
94 }
95
96 pub fn as_list(&self) -> Option<&List> {
97 match self {
98 Form::List(l) => Some(l),
99 _ => None,
100 }
101 }
102
103 pub fn head_is(&self, name: &str) -> bool {
106 match self {
107 Form::List(l) => l.head_is(name),
108 _ => false,
109 }
110 }
111}
112
113impl List {
114 pub fn new(items: Vec<Form>, line: u32) -> List {
115 List { items, line }
116 }
117
118 pub fn count(&self) -> usize {
119 self.items.len()
120 }
121
122 pub fn nth(&self, i: usize) -> Result<&Form, Refusal> {
124 if i == 0 || i > self.items.len() {
125 return Err(raise("vla-form-missing-element", &[("n", &i.to_string())]));
126 }
127 Ok(&self.items[i - 1])
128 }
129
130 pub fn nth_list(&self, i: usize) -> Result<&List, Refusal> {
132 match self.nth(i)? {
133 Form::List(l) => Ok(l),
134 other => Err(raise(
135 "vla-expected-list-at-position",
136 &[
137 ("n", &i.to_string()),
138 ("value", &other.atom_text().unwrap_or_default()),
139 ],
140 )),
141 }
142 }
143
144 pub fn head_sym(&self) -> Result<String, Refusal> {
146 if self.items.is_empty() {
147 return Err(raise("vla-empty-form", &[]));
148 }
149 sym_text(&self.items[0])
150 }
151
152 pub fn head_is(&self, name: &str) -> bool {
154 match self.items.first() {
155 Some(Form::Sym(s)) => fold(s) == fold(name),
156 _ => false,
157 }
158 }
159
160 pub fn tail_from(&self, i: usize) -> List {
163 let start = i.saturating_sub(1).min(self.items.len());
164 List {
165 items: self.items[start..].to_vec(),
166 line: 0,
167 }
168 }
169}
170
171pub fn sym_text(f: &Form) -> Result<String, Refusal> {
173 match f {
174 Form::Sym(s) => Ok(s.clone()),
175 Form::Str(_) => Err(raise("vla-expected-symbol-got-string", &[])),
176 Form::List(_) => Err(raise("vla-expected-symbol-got-list", &[])),
177 }
178}
179
180pub fn str_lit_content(f: &Form) -> Result<String, Refusal> {
183 match f {
184 Form::Str(s) => Ok(s.clone()),
185 Form::Sym(s) => Err(raise("vla-expected-string-got-other", &[("value", s)])),
186 Form::List(_) => Err(raise("vla-expected-string-got-list", &[])),
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn equality_is_structural_and_ignores_lines() {
196 let a = Form::List(List::new(vec![Form::sym("a"), Form::string("b")], 7));
197 let b = Form::List(List::new(vec![Form::sym("a"), Form::string("b")], 0));
198 assert_eq!(a, b);
199 assert_ne!(Form::sym("x"), Form::string("x"));
200 assert_ne!(Form::sym("x"), Form::list(vec![Form::sym("x")]));
201 }
202
203 #[test]
204 fn atom_text_is_the_vba_view() {
205 assert_eq!(Form::sym("x").atom_text().unwrap(), "x");
206 assert_eq!(Form::string("x").atom_text().unwrap(), "\"x");
207 assert!(Form::list(vec![]).atom_text().is_none());
208 assert!(Form::sym(":key").is_keyword_arg());
209 assert!(!Form::sym(":").is_keyword_arg());
210 assert!(!Form::string(":key").is_keyword_arg());
211 }
212
213 #[test]
214 fn helpers_refuse_with_the_catalogue_ids() {
215 let l = List::new(vec![Form::sym("f"), Form::string("s")], 1);
216 assert_eq!(l.head_sym().unwrap(), "f");
217 assert!(l.head_is("F"));
218 assert_eq!(l.nth(3).unwrap_err().id, "vla-form-missing-element");
219 assert_eq!(
220 l.nth(3).unwrap_err().text,
221 "form is missing required element 3"
222 );
223 assert_eq!(
224 l.nth_list(2).unwrap_err().id,
225 "vla-expected-list-at-position"
226 );
227 assert_eq!(
228 sym_text(&Form::string("s")).unwrap_err().id,
229 "vla-expected-symbol-got-string"
230 );
231 assert_eq!(
232 sym_text(&Form::list(vec![])).unwrap_err().id,
233 "vla-expected-symbol-got-list"
234 );
235 assert_eq!(
236 str_lit_content(&Form::sym("s")).unwrap_err().id,
237 "vla-expected-string-got-other"
238 );
239 assert_eq!(List::default().head_sym().unwrap_err().id, "vla-empty-form");
240 assert_eq!(l.tail_from(2).items, vec![Form::string("s")]);
241 assert_eq!(l.tail_from(5).items, vec![]);
242 }
243}