1use crate::form::{Form, List};
22use crate::messages::{raise, Refusal};
23
24#[derive(Clone, Debug, PartialEq)]
27pub struct Token {
28 pub text: String,
29 pub line: u32,
30}
31
32pub fn tokenize(s: &str) -> Vec<Token> {
34 let chars: Vec<char> = s.chars().collect();
35 let n = chars.len();
36 let mut out = Vec::new();
37 let mut i = 0;
38 let mut line: u32 = 1;
39 while i < n {
40 let c = chars[i];
41 match c {
42 '\n' => {
43 line += 1;
44 i += 1;
45 }
46 ' ' | '\t' | '\r' => {
47 i += 1;
48 }
49 '(' | ')' => {
50 out.push(Token {
51 text: c.to_string(),
52 line,
53 });
54 i += 1;
55 }
56 ';' => {
57 while i < n && chars[i] != '\r' && chars[i] != '\n' {
60 i += 1;
61 }
62 }
63 '"' => {
64 let start_line = line;
65 i += 1;
66 let mut buf = String::from("\"");
67 while i < n {
68 let c = chars[i];
69 if c == '\\' {
70 match chars.get(i + 1) {
71 Some('"') => {
72 buf.push('"');
73 i += 2;
74 }
75 Some('\\') => {
76 buf.push('\\');
77 i += 2;
78 }
79 _ => {
80 buf.push(c);
81 i += 1;
82 }
83 }
84 } else if c == '"' {
85 i += 1;
86 break;
87 } else {
88 if c == '\n' {
89 line += 1;
90 }
91 buf.push(c);
92 i += 1;
93 }
94 }
95 out.push(Token {
96 text: buf,
97 line: start_line,
98 });
99 }
100 _ => {
101 let start = i;
102 while i < n {
103 let c = chars[i];
104 if matches!(c, '(' | ')' | ' ' | '\t' | '\r' | '\n' | ';' | '"') {
105 break;
106 }
107 i += 1;
108 }
109 out.push(Token {
110 text: chars[start..i].iter().collect(),
111 line,
112 });
113 }
114 }
115 }
116 out
117}
118
119pub fn count_lf(s: &str) -> usize {
121 s.matches('\n').count()
122}
123
124fn user_line(raw_line: u32, line_offset: u32) -> Option<u32> {
127 if raw_line > line_offset {
128 Some(raw_line - line_offset)
129 } else {
130 None
131 }
132}
133
134pub fn src_line_tag(raw_line: u32, line_offset: u32) -> String {
136 match user_line(raw_line, line_offset) {
137 Some(n) => format!(" (vla line {n})"),
138 None => String::new(),
139 }
140}
141
142pub fn opened_at_tag(raw_line: u32, line_offset: u32) -> String {
144 match user_line(raw_line, line_offset) {
145 Some(n) => format!(" for the list opened at vla line {n}"),
146 None => String::new(),
147 }
148}
149
150pub fn parse_all(toks: &[Token], line_offset: u32) -> Result<Vec<Form>, Refusal> {
154 let mut pos = 0;
155 let mut out = Vec::new();
156 while pos < toks.len() {
157 out.push(parse_form(toks, &mut pos, line_offset, 1)?);
158 }
159 Ok(out)
160}
161
162pub const FORM_DEPTH_LIMIT: u32 = 64;
169
170fn parse_form(
172 toks: &[Token],
173 pos: &mut usize,
174 line_offset: u32,
175 depth: u32,
176) -> Result<Form, Refusal> {
177 let tok = &toks[*pos];
178 let raw_line = tok.line;
179 *pos += 1;
180 if tok.text == "(" {
181 if depth > FORM_DEPTH_LIMIT {
182 return Err(raise(
183 "vla-form-too-deep",
184 &[
185 ("limit", &FORM_DEPTH_LIMIT.to_string()),
186 ("loc", &src_line_tag(raw_line, line_offset)),
187 ],
188 ));
189 }
190 let line = user_line(raw_line, line_offset).unwrap_or(0);
193 let mut items = Vec::new();
194 while *pos < toks.len() {
195 if toks[*pos].text == ")" {
196 *pos += 1;
197 return Ok(Form::List(List::new(items, line)));
198 }
199 items.push(parse_form(toks, pos, line_offset, depth + 1)?);
200 }
201 Err(raise(
202 "vla-unbalanced-parens",
203 &[("loc", &opened_at_tag(raw_line, line_offset))],
204 ))
205 } else if tok.text == ")" {
206 Err(raise(
207 "vla-unexpected-close-paren",
208 &[("loc", &src_line_tag(raw_line, line_offset))],
209 ))
210 } else if let Some(content) = tok.text.strip_prefix('"') {
211 Ok(Form::Str(content.to_string()))
212 } else {
213 Ok(Form::Sym(tok.text.clone()))
214 }
215}
216
217pub fn read_forms(text: &str) -> Result<Vec<Form>, Refusal> {
220 parse_all(&tokenize(text), 0)
221}
222
223pub fn read_forms_with_lines(text: &str) -> Result<Vec<(Form, u32)>, Refusal> {
226 Ok(read_forms(text)?
227 .into_iter()
228 .map(|f| {
229 let line = f.as_list().map(|l| l.line).unwrap_or(0);
230 (f, line)
231 })
232 .collect())
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use crate::printer::{write_datum, write_pretty};
239
240 fn texts(toks: &[Token]) -> Vec<&str> {
241 toks.iter().map(|t| t.text.as_str()).collect()
242 }
243
244 #[test]
245 fn tokenize_follows_the_vba_character_classes() {
246 let toks = tokenize("(set! x \"a \\\"q\\\" b\\n\") ; a comment\r\n(y)");
247 assert_eq!(
248 texts(&toks),
249 vec!["(", "set!", "x", "\"a \"q\" b\\n", ")", "(", "y", ")"]
250 );
251 assert_eq!(toks[0].line, 1);
252 assert_eq!(toks[5].line, 2);
253 assert_eq!(
255 texts(&tokenize("abc\"d\"\te\rf")),
256 vec!["abc", "\"d", "e", "f"]
257 );
258 let toks = tokenize("\"two\nlines\" after");
260 assert_eq!(toks[0].text, "\"two\nlines");
261 assert_eq!(toks[0].line, 1);
262 assert_eq!(toks[1].line, 2);
263 assert_eq!(texts(&tokenize("\"open")), vec!["\"open"]);
265 assert!(tokenize("").is_empty());
267 assert!(tokenize(" ; only a comment\n").is_empty());
268 }
269
270 #[test]
271 fn parse_tags_lists_with_their_lines_less_the_offset() {
272 let forms = read_forms("(a\n (b) c)\n\n(d)").unwrap();
273 assert_eq!(forms.len(), 2);
274 let a = forms[0].as_list().unwrap();
275 assert_eq!(a.line, 1);
276 assert_eq!(a.items[1].as_list().unwrap().line, 2);
277 assert_eq!(forms[1].as_list().unwrap().line, 4);
278 let forms = parse_all(&tokenize("(p)\n(q)\n(r)"), 2).unwrap();
280 assert_eq!(forms[0].as_list().unwrap().line, 0);
281 assert_eq!(forms[1].as_list().unwrap().line, 0);
282 assert_eq!(forms[2].as_list().unwrap().line, 1);
283 let with_lines = read_forms_with_lines("x\n(y)").unwrap();
284 assert_eq!(with_lines[0], (Form::sym("x"), 0));
285 assert_eq!(with_lines[1].1, 2);
286 }
287
288 #[test]
289 fn the_two_reader_refusals_name_their_lines() {
290 let r = read_forms("(a\n(b").unwrap_err();
291 assert_eq!(r.id, "vla-unbalanced-parens");
292 assert_eq!(
293 r.text,
294 "unbalanced parentheses: missing ')' for the list opened at vla line 2"
295 );
296 let r = read_forms("(a)\n)").unwrap_err();
297 assert_eq!(r.id, "vla-unexpected-close-paren");
298 assert_eq!(r.text, "unexpected ')' (vla line 2)");
299 let r = parse_all(&tokenize("(a"), 5).unwrap_err();
301 assert_eq!(r.text, "unbalanced parentheses: missing ')'");
302 }
303
304 #[test]
305 fn a_list_past_the_depth_limit_is_refused_by_name() {
306 let nest = |n: usize| format!("{}{}", "(".repeat(n), ")".repeat(n));
310 assert_eq!(read_forms(&nest(64)).unwrap().len(), 1);
311 let r = read_forms(&nest(65)).unwrap_err();
312 assert_eq!(r.id, "vla-form-too-deep");
313 assert_eq!(
314 r.text,
315 "lists nested more than 64 deep (vla line 1) - no program needs that many"
316 );
317 assert_eq!(
318 read_forms(&nest(100_000)).unwrap_err().id,
319 "vla-form-too-deep"
320 );
321 }
322
323 const GOLDEN: &str = include_str!("../testdata/instructions_golden.vla");
324 const PRELUDE: &str = include_str!("../testdata/prelude.vla");
325
326 #[test]
327 fn the_golden_and_the_prelude_read_print_and_read_again() {
328 let golden = read_forms(GOLDEN).unwrap();
331 assert_eq!(golden.len(), 248);
332 let prelude = read_forms(PRELUDE).unwrap();
333 assert_eq!(prelude.len(), 46);
334 for forms in [&golden, &prelude] {
335 let flat: String = forms.iter().map(write_datum).collect::<Vec<_>>().join("\n");
336 assert_eq!(&read_forms(&flat).unwrap(), forms);
337 let pretty: String = forms
338 .iter()
339 .map(|f| write_pretty(f, 0))
340 .collect::<Vec<_>>()
341 .join("\n");
342 assert_eq!(&read_forms(&pretty).unwrap(), forms);
343 }
344 assert!(golden[0].head_is("const"));
345 assert!(prelude.iter().all(|f| f.head_is("defmacro")));
346 }
347
348 #[test]
349 fn count_lf_counts_line_feeds_only() {
350 assert_eq!(count_lf("a\r\nb\nc"), 2);
351 assert_eq!(count_lf(""), 0);
352 }
353}