1use beck_diag::depth::Nesting;
13use beck_diag::{Diagnostic, Diagnostics, FileId, Span};
14
15use crate::node::{Head, Lit, Node, Symbol};
16
17struct Reader<'a> {
18 file: FileId,
19 src: &'a [u8],
20 text: &'a str,
21 pos: usize,
22 nesting: Nesting,
26}
27
28pub fn read_all(file: FileId, src: &str, diags: &mut Diagnostics) -> Vec<Node> {
30 let mut r = Reader {
31 file,
32 src: src.as_bytes(),
33 text: src,
34 pos: 0,
35 nesting: Nesting::new(),
36 };
37 let mut out = Vec::new();
38 loop {
39 r.skip_trivia();
40 if r.pos >= r.src.len() {
41 break;
42 }
43 match r.form(diags) {
44 Some(n) => out.push(n),
45 None => break,
46 }
47 }
48 out
49}
50
51pub fn read_one(file: FileId, src: &str, diags: &mut Diagnostics) -> Option<Node> {
53 let forms = read_all(file, src, diags);
54 if forms.len() > 1 {
55 let span = forms[1].span();
56 diags.push(
57 Diagnostic::error("B0110", "expected a single form", span)
58 .with_primary_label("unexpected second form"),
59 );
60 }
61 forms.into_iter().next()
62}
63
64impl<'a> Reader<'a> {
65 fn span(&self, start: usize) -> Span {
66 Span::new(self.file, start..self.pos)
67 }
68
69 fn peek(&self) -> Option<u8> {
70 self.src.get(self.pos).copied()
71 }
72
73 fn skip_trivia(&mut self) {
74 while let Some(c) = self.peek() {
75 match c {
76 b' ' | b'\t' | b'\n' | b'\r' | b',' => self.pos += 1,
77 b';' => {
78 while let Some(c) = self.peek() {
79 self.pos += 1;
80 if c == b'\n' {
81 break;
82 }
83 }
84 }
85 _ => break,
86 }
87 }
88 }
89
90 fn form(&mut self, diags: &mut Diagnostics) -> Option<Node> {
91 self.skip_trivia();
92 let start = self.pos;
93 let c = self.peek()?;
94 match c {
95 b'(' | b'[' | b'{' => self.list(c, diags),
96 b')' | b']' | b'}' => {
97 self.pos += 1;
98 diags.push(
99 Diagnostic::error("B0111", "unbalanced closing delimiter", self.span(start))
100 .with_primary_label("no matching opening delimiter"),
101 );
102 None
103 }
104 b'"' => self.string(diags),
105 b'\'' => {
106 self.pos += 1;
109 let inner = self.form(diags)?;
110 Some(Node::form(
111 crate::node::sym::QUOTE,
112 vec![inner],
113 self.span(start),
114 ))
115 }
116 _ => self.atom(diags),
117 }
118 }
119
120 fn list(&mut self, open: u8, diags: &mut Diagnostics) -> Option<Node> {
121 if !self.nesting.enter() {
122 if self.nesting.should_report() {
123 let start = self.pos;
124 let note = self.nesting.note();
125 diags.push(
126 Diagnostic::error("B0121", "nesting is too deep to read", self.span(start))
127 .with_primary_label("the reader gave up here")
128 .with_note(note),
129 );
130 }
131 return None;
132 }
133 let out = self.list_inner(open, diags);
134 self.nesting.leave();
135 out
136 }
137
138 fn list_inner(&mut self, open: u8, diags: &mut Diagnostics) -> Option<Node> {
139 let start = self.pos;
140 let close = match open {
141 b'(' => b')',
142 b'[' => b']',
143 _ => b'}',
144 };
145 self.pos += 1;
146 let mut items: Vec<Node> = Vec::new();
147 loop {
148 self.skip_trivia();
149 match self.peek() {
150 None => {
151 diags.push(
152 Diagnostic::error("B0112", "unclosed list", self.span(start))
153 .with_primary_label("opened here, never closed"),
154 );
155 return None;
156 }
157 Some(c) if c == close => {
158 self.pos += 1;
159 break;
160 }
161 Some(c) if c == b')' || c == b']' || c == b'}' => {
162 diags.push(
163 Diagnostic::error(
164 "B0113",
165 "mismatched closing delimiter",
166 Span::new(self.file, self.pos..self.pos + 1),
167 )
168 .with_label(self.span(start), "opened here"),
169 );
170 self.pos += 1;
171 return None;
172 }
173 _ => items.push(self.form(diags)?),
174 }
175 }
176 let span = self.span(start);
177
178 match open {
180 b'[' => return Some(Node::form(crate::node::sym::LIST, items, span)),
181 b'{' => return Some(Node::form(crate::node::sym::RECORD, items, span)),
182 _ => {}
183 }
184
185 if items.is_empty() {
186 diags.push(
187 Diagnostic::error("B0114", "empty application", span)
188 .with_primary_label("`()` has no meaning")
189 .with_fix("write `unit` for the unit value"),
190 );
191 return None;
192 }
193
194 let head = items.remove(0);
198 match head.head {
199 Head::Sym(s) if head.args.is_empty() => {
200 normalise_typarams(s.as_str(), &mut items, span);
201 Some(Node::form_sym(s, items, span))
202 }
203 _ => {
204 let mut args = vec![head];
205 args.extend(items);
206 Some(Node::form(crate::node::sym::CALL, args, span))
207 }
208 }
209 }
210
211 fn string(&mut self, diags: &mut Diagnostics) -> Option<Node> {
212 let start = self.pos;
213 self.pos += 1;
214 let mut out = String::new();
215 loop {
216 match self.peek() {
217 None => {
218 diags.push(
219 Diagnostic::error("B0115", "unclosed string", self.span(start))
220 .with_primary_label("opened here"),
221 );
222 return None;
223 }
224 Some(b'"') => {
225 self.pos += 1;
226 break;
227 }
228 Some(b'\\') => {
229 self.pos += 1;
230 let c = self.peek()?;
231 self.pos += 1;
232 let decoded = match c {
233 b'n' => '\n',
234 b't' => '\t',
235 b'r' => '\r',
236 b'0' => '\0',
237 b'\\' => '\\',
238 b'"' => '"',
239 other => {
240 out.push('\\');
242 other as char
243 }
244 };
245 out.push(decoded);
246 }
247 Some(_) => {
248 let ch = self.text[self.pos..].chars().next()?;
249 self.pos += ch.len_utf8();
250 out.push(ch);
251 }
252 }
253 }
254 Some(Node::lit(Lit::Str(out.into()), self.span(start)))
255 }
256
257 fn atom(&mut self, diags: &mut Diagnostics) -> Option<Node> {
258 let start = self.pos;
259 while let Some(c) = self.peek() {
260 if c.is_ascii_whitespace()
261 || matches!(
262 c,
263 b'(' | b')' | b'[' | b']' | b'{' | b'}' | b'"' | b';' | b','
264 )
265 {
266 break;
267 }
268 self.pos += 1;
269 }
270 if self.pos == start {
271 self.pos += 1;
272 diags.push(Diagnostic::error(
273 "B0116",
274 "unreadable character",
275 self.span(start),
276 ));
277 return None;
278 }
279 let text = &self.text[start..self.pos];
280 let span = self.span(start);
281 Some(atom_node(text, span))
282 }
283}
284
285pub fn atom_node(text: &str, span: Span) -> Node {
287 if let Some(kw) = text.strip_prefix(':') {
288 if !kw.is_empty() {
289 return Node::lit(Lit::Keyword(kw.into()), span);
290 }
291 }
292 match text {
293 "true" | "True" => return Node::lit(Lit::Bool(true), span),
294 "false" | "False" => return Node::lit(Lit::Bool(false), span),
295 _ => {}
296 }
297 if let Ok(n) = text.parse::<i64>() {
298 return Node::lit(Lit::Int(n), span);
299 }
300 if text.contains('.') && !text.starts_with('.') {
301 if let Ok(n) = text.parse::<f64>() {
302 return Node::lit(Lit::Float(n), span);
303 }
304 }
305 Node::symbol(Symbol::new(text), span)
306}
307
308fn normalise_typarams(head: &str, items: &mut Vec<Node>, span: beck_diag::Span) {
319 use crate::node::sym;
320 let min = match head {
323 sym::DEF => 4,
324 sym::MODEL | sym::UNION => 1,
325 sym::NEWTYPE | sym::TYPE => 2,
326 sym::IMPL => 2,
329 _ => return,
330 };
331 let already = items
332 .get(1)
333 .map(|n| n.is_form(sym::TYPARAMS))
334 .unwrap_or(false);
335 if items.len() >= min && !already {
336 items.insert(1, Node::form(sym::TYPARAMS, Vec::new(), span));
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343 use crate::node::sym;
344
345 fn read(src: &str) -> Node {
346 let mut map = beck_diag::SourceMap::new();
347 let f = map.add("t.sx", src);
348 let mut d = Diagnostics::new();
349 let n = read_one(f, src, &mut d).expect("reads");
350 assert!(!d.has_errors(), "{}", d.render(&map));
351 n
352 }
353
354 #[test]
355 fn the_sketchs_notation_reads_as_written() {
356 let n = read("(def apply-event (fn [todos e] (assoc todos id 1)))");
357 assert_eq!(n.head_name(), Some("def"));
358 assert_eq!(n.args.len(), 2);
359 assert_eq!(n.args[0].as_var().unwrap().as_str(), "apply-event");
360 let f = &n.args[1];
361 assert_eq!(f.head_name(), Some("fn"));
362 assert_eq!(f.args[0].head_name(), Some(sym::LIST));
363 assert_eq!(f.args[1].head_name(), Some("assoc"));
364 }
365
366 #[test]
367 fn keywords_and_record_literals() {
368 let n = read("{:id id :text text}");
369 assert_eq!(n.head_name(), Some(sym::RECORD));
370 assert_eq!(n.args[0].as_keyword(), Some("id"));
371 assert_eq!(n.args[1].as_var().unwrap().as_str(), "id");
372 }
373
374 #[test]
375 fn a_computed_callee_becomes_the_call_head() {
376 let n = read("((. f g) x)");
377 assert_eq!(n.head_name(), Some(sym::CALL));
378 assert_eq!(n.args.len(), 2);
379 assert_eq!(n.args[0].head_name(), Some("."));
380 }
381
382 #[test]
383 fn literals_are_classified_not_stringly_typed() {
384 assert_eq!(read("42").as_lit(), Some(&Lit::Int(42)));
385 assert_eq!(read("4.5").as_lit(), Some(&Lit::Float(4.5)));
386 assert_eq!(read("true").as_lit(), Some(&Lit::Bool(true)));
387 assert_eq!(read(r#""hi""#).as_str_lit(), Some("hi"));
388 assert_eq!(read(":done").as_keyword(), Some("done"));
389 }
390
391 #[test]
392 fn quote_sugar_and_comments() {
393 let n = read("; a comment\n'(a b)");
394 assert_eq!(n.head_name(), Some(sym::QUOTE));
395 assert_eq!(n.args[0].head_name(), Some("a"));
396 }
397
398 #[test]
399 fn unbalanced_input_reports_rather_than_panics() {
400 let mut map = beck_diag::SourceMap::new();
401 let src = "(def a";
402 let f = map.add("t.sx", src);
403 let mut d = Diagnostics::new();
404 assert!(read_one(f, src, &mut d).is_none());
405 assert!(d.has_errors());
406 }
407}