rune/
reader.rs

1//! Lisp reader that reads an object from a string.
2use crate::core::{
3    env::{intern, sym},
4    gc::Context,
5    object::{Object, Symbol},
6};
7use crate::fns;
8use num_bigint::BigInt;
9use rune_core::macros::list;
10use std::str;
11use std::{fmt, iter::Peekable, str::CharIndices};
12use std::{fmt::Display, str::FromStr};
13
14type Result<T> = std::result::Result<T, Error>;
15
16/// Errors that can occur during reading a sexp from a string
17#[derive(PartialEq, Debug, Copy, Clone)]
18pub(crate) enum Error {
19    MissingCloseParen(usize),
20    MissingCloseBracket(usize),
21    MissingStringDel(usize),
22    MissingQuotedItem(usize),
23    ExtraItemInCdr(usize),
24    ExtraCloseParen(usize),
25    ExtraCloseBracket(usize),
26    UnexpectedChar(char, usize),
27    UnknownMacroCharacter(char, usize),
28    ParseInt(u8, usize),
29    MalformedUnicdoe(usize),
30    EmptyStream,
31}
32
33impl Display for Error {
34    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35        match self {
36            Error::MissingCloseParen(i) => write!(f, "Missing close paren: at {i}"),
37            Error::MissingCloseBracket(i) => write!(f, "Missing close bracket: at {i}"),
38            Error::MissingStringDel(i) => write!(f, "Missing closing string quote: at {i}"),
39            Error::ExtraCloseParen(i) => write!(f, "Extra Closing paren: at {i}"),
40            Error::ExtraCloseBracket(i) => write!(f, "Extra Closing brace: at {i}"),
41            Error::UnexpectedChar(chr, i) => write!(f, "Unexpected character {chr}: at {i}"),
42            Error::MalformedUnicdoe(i) => write!(f, "Malformed unicode: at {i}"),
43            Error::EmptyStream => write!(f, "Empty Stream"),
44            Error::ExtraItemInCdr(i) => write!(f, "Extra item in cdr: at {i}"),
45            Error::MissingQuotedItem(i) => write!(f, "Missing element after quote: at {i}"),
46            Error::ParseInt(radix, i) => {
47                write!(f, "invalid character for radix {radix}: at {i}")
48            }
49            Error::UnknownMacroCharacter(chr, i) => {
50                write!(f, "Unkown reader macro character {chr}: at {i}")
51            }
52        }
53    }
54}
55
56impl std::error::Error for Error {}
57
58impl Error {
59    fn mut_pos(&mut self) -> Option<&mut usize> {
60        match self {
61            Error::MissingCloseParen(i)
62            | Error::MissingCloseBracket(i)
63            | Error::MissingStringDel(i)
64            | Error::UnexpectedChar(_, i)
65            | Error::MalformedUnicdoe(i)
66            | Error::ExtraItemInCdr(i)
67            | Error::ExtraCloseParen(i)
68            | Error::ExtraCloseBracket(i)
69            | Error::MissingQuotedItem(i)
70            | Error::UnknownMacroCharacter(_, i)
71            | Error::ParseInt(_, i) => Some(i),
72            Error::EmptyStream => None,
73        }
74    }
75
76    pub(crate) fn update_pos(&mut self, offset: usize) {
77        if let Some(pos) = self.mut_pos() {
78            *pos += offset;
79        }
80    }
81}
82
83#[derive(PartialEq, Debug, Copy, Clone)]
84enum Token<'a> {
85    OpenParen(usize),
86    CloseParen(usize),
87    OpenBracket(usize),
88    CloseBracket(usize),
89    Quote(usize),
90    Backquote(usize),
91    Unquote(usize),
92    Splice(usize),
93    Sharp(usize),
94    QuestionMark(usize, char),
95    Ident(&'a str),
96    String(&'a str),
97}
98
99impl Display for Token<'_> {
100    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101        match self {
102            Token::OpenParen(_) => write!(f, "("),
103            Token::CloseParen(_) => write!(f, ")"),
104            Token::OpenBracket(_) => write!(f, "["),
105            Token::CloseBracket(_) => write!(f, "]"),
106            Token::Quote(_) => write!(f, "'"),
107            Token::Backquote(_) => write!(f, "`"),
108            Token::Unquote(_) => write!(f, ","),
109            Token::Splice(_) => write!(f, ",@"),
110            Token::Sharp(_) => write!(f, "#"),
111            Token::QuestionMark(_, chr) => write!(f, "?{chr}"),
112            Token::Ident(x) => write!(f, "{x}"),
113            Token::String(x) => write!(f, "\"{x}\""),
114        }
115    }
116}
117
118#[derive(Clone)]
119struct Tokenizer<'a> {
120    slice: &'a str,
121    iter: Peekable<CharIndices<'a>>,
122}
123
124impl<'a> Tokenizer<'a> {
125    fn new(slice: &'a str) -> Self {
126        Self { slice, iter: slice.char_indices().peekable() }
127    }
128
129    /// Given a [`Token`] calculate it's position relative to this `Tokenizer`.
130    fn relative_pos(&self, token: Token<'a>) -> usize {
131        match token {
132            Token::OpenParen(x)
133            | Token::CloseParen(x)
134            | Token::OpenBracket(x)
135            | Token::CloseBracket(x)
136            | Token::Quote(x)
137            | Token::Backquote(x)
138            | Token::Unquote(x)
139            | Token::Splice(x)
140            | Token::Sharp(x)
141            | Token::QuestionMark(x, _) => x,
142            Token::Ident(slice) | Token::String(slice) => {
143                let beg = self.slice.as_ptr() as usize;
144                let end = slice.as_ptr() as usize;
145                end - beg
146            }
147        }
148    }
149
150    /// Return the current position of the Tokenizer. This is the index of the
151    /// next character.
152    fn cur_pos(&mut self) -> usize {
153        match self.iter.peek() {
154            Some((idx, _)) => *idx,
155            None => self.slice.len(),
156        }
157    }
158
159    /// Skip characters until the closure returns true.
160    fn skip_till(&mut self, mut func: impl FnMut(char) -> bool) -> usize {
161        while self.iter.next_if(|x| !func(x.1)).is_some() {}
162        match self.iter.peek() {
163            Some((idx, _)) => *idx,
164            None => self.slice.len(),
165        }
166    }
167
168    /// Skip whitespace and comments until the next valid read character.
169    fn skip_till_char(&mut self) {
170        let mut in_comment = false;
171        let valid_char = |chr: char| {
172            if in_comment {
173                if chr == '\n' {
174                    in_comment = false;
175                }
176                false
177            } else if chr.is_ascii_whitespace() {
178                false
179            } else if chr == ';' {
180                in_comment = true;
181                false
182            } else {
183                true
184            }
185        };
186        self.skip_till(valid_char);
187    }
188
189    fn get_string(&mut self, open_delim_pos: usize) -> Result<Token<'a>> {
190        let mut skip = false;
191        let idx_chr = self.iter.find(|(_, chr)| !escaped(&mut skip, *chr) && *chr == '"');
192        match idx_chr {
193            Some((end, '"')) => Ok(Token::String(&self.slice[(open_delim_pos + 1)..end])),
194            _ => Err(Error::MissingStringDel(open_delim_pos)),
195        }
196    }
197
198    fn get_symbol(&mut self, beg: usize, chr: char) -> Token<'a> {
199        let mut skip = chr == '\\';
200        let end = self.skip_till(|c| !escaped(&mut skip, c) && !symbol_char(c));
201        Token::Ident(&self.slice[beg..end])
202    }
203
204    /// After having found a `,`, see if the next token is a `@` or not.
205    fn get_macro_char(&mut self, idx: usize) -> Token<'a> {
206        match self.iter.next_if(|(_, chr)| *chr == '@') {
207            Some(_) => Token::Splice(idx),
208            None => Token::Unquote(idx),
209        }
210    }
211
212    fn read_quoted_char(&mut self, idx: usize) -> Result<Token<'a>> {
213        match self.iter.next() {
214            Some((start, item)) => {
215                if item == '\\' {
216                    let Token::Ident(tok) = self.get_symbol(start, item) else { unreachable!() };
217                    let Some(chr) = tok.chars().nth(1) else {
218                        return Err(Error::MissingQuotedItem(start));
219                    };
220                    if chr == 'u' || chr == 'x' {
221                        match u32::from_str_radix(&tok[2..], 16) {
222                            Ok(digits) => match char::from_u32(digits) {
223                                Some(c) => Ok(Token::QuestionMark(start, c)),
224                                None => Err(Error::MalformedUnicdoe(start)),
225                            },
226                            Err(_) => Err(Error::MalformedUnicdoe(start)),
227                        }
228                    } else if tok.chars().count() == 2 {
229                        let new = match chr {
230                            'a' => '\u{07}',
231                            'b' => '\u{08}',
232                            'd' => '\u{7F}',
233                            'e' => '\u{1B}',
234                            'f' => '\u{0C}',
235                            'n' => '\n',
236                            'r' => '\r',
237                            's' => ' ',
238                            't' => '\t',
239                            'v' => '\u{0B}',
240                            c => c,
241                        };
242                        Ok(Token::QuestionMark(start, new))
243                    } else {
244                        // TODO implement keycode parsing
245                        Ok(Token::QuestionMark(start, '\0'))
246                    }
247                } else {
248                    match self.iter.peek() {
249                        Some((i, chr)) if symbol_char(*chr) && *chr != '?' => {
250                            Err(Error::UnexpectedChar(*chr, *i)) // ?aa
251                        }
252                        _ => Ok(Token::QuestionMark(idx, item)), // ?a
253                    }
254                }
255            }
256            None => Err(Error::MissingQuotedItem(idx)),
257        }
258    }
259
260    fn read_char(&mut self) -> Option<char> {
261        self.iter.next().map(|x| x.1)
262    }
263}
264
265impl<'a> Iterator for Tokenizer<'a> {
266    type Item = Result<Token<'a>>;
267
268    fn next(&mut self) -> Option<Self::Item> {
269        self.skip_till_char();
270        let (idx, chr) = self.iter.next()?;
271        let token = match chr {
272            '(' => Ok(Token::OpenParen(idx)),
273            ')' => Ok(Token::CloseParen(idx)),
274            '[' => Ok(Token::OpenBracket(idx)),
275            ']' => Ok(Token::CloseBracket(idx)),
276            '\'' => Ok(Token::Quote(idx)),
277            ',' => Ok(self.get_macro_char(idx)),
278            '`' => Ok(Token::Backquote(idx)),
279            '#' => Ok(Token::Sharp(idx)),
280            '?' => self.read_quoted_char(idx),
281            '"' => self.get_string(idx),
282            other if symbol_char(other) => Ok(self.get_symbol(idx, other)),
283            unknown => Err(Error::UnexpectedChar(unknown, idx)),
284        };
285        Some(token)
286    }
287}
288
289fn intern_symbol<'ob>(symbol: &str, cx: &'ob Context) -> Symbol<'ob> {
290    let mut escaped = false;
291    let is_not_escape = |c: &char| {
292        if escaped {
293            escaped = false;
294            true
295        } else if *c == '\\' {
296            escaped = true;
297            false
298        } else {
299            true
300        }
301    };
302    if symbol.contains('\\') {
303        let escaped_slice: String = symbol.chars().filter(is_not_escape).collect();
304        intern(escaped_slice.as_str(), cx)
305    } else {
306        intern(symbol, cx)
307    }
308}
309
310/// Parse a symbol from a string. This will either by a true symbol or a number
311/// literal.
312fn parse_symbol<'a>(slice: &str, cx: &'a Context) -> Object<'a> {
313    if let Ok(num) = slice.parse::<i64>() {
314        return cx.add(num);
315    }
316    if let Ok(num) = BigInt::from_str(slice) {
317        return cx.add(num);
318    }
319    match slice.parse::<f64>() {
320        Ok(num) => cx.add(num),
321        Err(_) => cx.add(intern_symbol(slice, cx)),
322    }
323}
324
325/// process escape characters in the string slice and return the resulting
326/// string.
327fn unescape_string<'a>(string: &str, cx: &'a Context) -> Object<'a> {
328    let mut escaped = false;
329    let unescape = |c: char| {
330        if escaped {
331            escaped = false;
332            // TODO: Handle unicode, hex, and octal escapes
333            match c {
334                'n' => Some('\n'),
335                't' => Some('\t'),
336                'r' => Some('\r'),
337                '\n' | ' ' => None,
338                _ => Some(c),
339            }
340        } else if c == '\\' {
341            escaped = true;
342            None
343        } else {
344            Some(c)
345        }
346    };
347    let mut new = cx.string_with_capacity(string.len());
348    for c in string.chars().filter_map(unescape) {
349        new.push(c);
350    }
351    cx.add(new)
352}
353
354/// Return true if `chr` is a valid symbol character.
355const fn symbol_char(chr: char) -> bool {
356    !matches!(chr, '\x00'..=' ' | '(' | ')' | '[' | ']' | '#' | ',' | '`' | ';' | '"' | '\'')
357}
358
359fn escaped(escaped: &mut bool, chr: char) -> bool {
360    if *escaped {
361        *escaped = false;
362        true
363    } else if chr == '\\' {
364        *escaped = true;
365        true
366    } else {
367        false
368    }
369}
370
371/// State of the reader.
372struct Reader<'a, 'ob> {
373    /// The iterator over the tokens in the current slice.
374    tokens: Tokenizer<'a>,
375    /// New objects are allocated in the context.
376    cx: &'ob Context<'ob>,
377}
378
379impl<'a, 'ob> Reader<'a, 'ob> {
380    /// Read the cdr of a literal list.
381    /// ```lisp
382    /// '(1 2 3 . 45)
383    ///           ^^^
384    /// ```
385    fn read_cdr(&mut self, delim: usize) -> Result<Option<Object<'ob>>> {
386        match self.tokens.next() {
387            Some(Ok(Token::CloseParen(_))) => Ok(None),
388            Some(Ok(sexp)) => {
389                let obj = self.read_sexp(sexp)?;
390                match self.tokens.next() {
391                    Some(Ok(Token::CloseParen(_))) => Ok(Some(obj)),
392                    Some(Ok(token)) => Err(Error::ExtraItemInCdr(self.tokens.relative_pos(token))),
393                    Some(Err(e)) => Err(e),
394                    None => Err(Error::MissingCloseParen(delim)),
395                }
396            }
397            Some(Err(e)) => Err(e),
398            None => Err(Error::MissingCloseParen(delim)),
399        }
400    }
401
402    fn read_list(&mut self, delim: usize) -> Result<Object<'ob>> {
403        let mut objects = Vec::new();
404        while let Some(token) = self.tokens.next() {
405            match token? {
406                Token::CloseParen(_) => return Ok(fns::slice_into_list(&objects, None, self.cx)),
407                Token::Ident(".") => {
408                    let cdr = self.read_cdr(delim)?;
409                    if cdr.is_none() {
410                        objects.push(parse_symbol(".", self.cx));
411                    }
412                    return Ok(fns::slice_into_list(&objects, cdr, self.cx));
413                }
414                tok => objects.push(self.read_sexp(tok)?),
415            }
416        }
417        Err(Error::MissingCloseParen(delim))
418    }
419
420    fn read_vec(&mut self, delim: usize) -> Result<Object<'ob>> {
421        let mut objects = self.cx.vec_new();
422        while let Some(token) = self.tokens.next() {
423            match token? {
424                Token::CloseBracket(_) => return Ok(self.cx.add(objects)),
425                tok => objects.push(self.read_sexp(tok)?),
426            }
427        }
428        Err(Error::MissingCloseBracket(delim))
429    }
430
431    /// Quote an item using `symbol`.
432    fn quote_item(&mut self, pos: usize, symbol: Symbol) -> Result<Object<'ob>> {
433        match self.tokens.next() {
434            Some(token) => {
435                let obj = self.read_sexp(token?)?;
436                Ok(list!(symbol, obj; self.cx))
437            }
438            None => Err(Error::MissingQuotedItem(pos)),
439        }
440    }
441
442    /// Read number with specificed radix
443    fn read_radix(&mut self, pos: usize, radix: u8) -> Result<Object<'ob>> {
444        if !(2..=36).contains(&radix) {
445            return Err(Error::ParseInt(radix, pos));
446        }
447
448        match self.tokens.next() {
449            Some(Ok(Token::Ident(ident))) => match usize::from_str_radix(ident, radix.into()) {
450                Ok(x) => Ok(self.cx.add(x as i64)),
451                Err(_) => Err(Error::ParseInt(radix, pos)),
452            },
453            _ => Err(Error::ParseInt(radix, pos)),
454        }
455    }
456
457    /// read a sharp quoted character. This could be used for reader macro's in
458    /// the future, but right now it just handles the special cases from elisp.
459    fn read_sharp(&mut self, pos: usize) -> Result<Object<'ob>> {
460        match self.tokens.read_char() {
461            Some('\'') => match self.tokens.next() {
462                Some(Ok(Token::OpenParen(i))) => {
463                    let list = self.read_list(i)?;
464                    Ok(list!(sym::FUNCTION, list; self.cx))
465                }
466                Some(token) => {
467                    let obj = self.read_sexp(token?)?;
468                    Ok(list!(sym::FUNCTION, obj; self.cx))
469                }
470                None => Err(Error::MissingQuotedItem(pos)),
471            },
472            Some('b') => self.read_radix(pos, 2),
473            Some('o') => self.read_radix(pos, 8),
474            Some('x') => self.read_radix(pos, 16),
475            Some(chr) if chr.is_ascii_digit() => {
476                let mut radix = (chr as u8) - b'0';
477                loop {
478                    match self.tokens.read_char() {
479                        Some('r') => break,
480                        Some(chr) if chr.is_ascii_digit() => {
481                            match radix
482                                .checked_mul(10)
483                                .and_then(|r| r.checked_add(chr as u8 - b'0'))
484                            {
485                                Some(r) => radix = r,
486                                // TODO: Better error for radix overflow
487                                None => return Err(Error::UnknownMacroCharacter(chr, pos)),
488                            }
489                        }
490                        Some(chr) => return Err(Error::UnknownMacroCharacter(chr, pos)),
491                        None => return Err(Error::MissingQuotedItem(pos)),
492                    }
493                }
494                self.read_radix(pos, radix)
495            }
496            Some(chr) => Err(Error::UnknownMacroCharacter(chr, pos)),
497            None => Err(Error::MissingQuotedItem(pos)),
498        }
499    }
500
501    fn read_sexp(&mut self, token: Token<'a>) -> Result<Object<'ob>> {
502        match token {
503            Token::OpenParen(i) => self.read_list(i),
504            Token::CloseParen(i) => Err(Error::ExtraCloseParen(i)),
505            Token::OpenBracket(i) => self.read_vec(i),
506            Token::CloseBracket(i) => Err(Error::ExtraCloseBracket(i)),
507            Token::Quote(i) => self.quote_item(i, sym::QUOTE),
508            Token::Unquote(i) => self.quote_item(i, sym::UNQUOTE),
509            Token::Splice(i) => self.quote_item(i, sym::SPLICE),
510            Token::Backquote(i) => self.quote_item(i, sym::BACKQUOTE),
511            Token::Sharp(i) => self.read_sharp(i),
512            Token::QuestionMark(_, c) => Ok((c as i64).into()),
513            Token::Ident(x) => Ok(parse_symbol(x, self.cx)),
514            Token::String(x) => Ok(unescape_string(x, self.cx)),
515        }
516    }
517}
518
519/// read a lisp object from `slice`. Return the object and index of next
520/// remaining character in the slice.
521pub(crate) fn read<'ob>(slice: &str, cx: &'ob Context) -> Result<(Object<'ob>, usize)> {
522    let mut reader = Reader { tokens: Tokenizer::new(slice), cx };
523    match reader.tokens.next() {
524        Some(Ok(t)) => reader.read_sexp(t).map(|x| (x, reader.tokens.cur_pos())),
525        Some(Err(e)) => Err(e),
526        None => Err(Error::EmptyStream),
527    }
528}
529
530#[cfg(test)]
531mod test {
532    use crate::core::{cons::Cons, gc::RootSet};
533
534    use super::*;
535
536    #[test]
537    fn tokens() {
538        let mut iter = Tokenizer::new("1 foo (\"bar\" . 1.3)");
539        assert_eq!(iter.next(), Some(Ok(Token::Ident("1"))));
540        assert_eq!(iter.next(), Some(Ok(Token::Ident("foo"))));
541        assert_eq!(iter.next(), Some(Ok(Token::OpenParen(6))));
542        assert_eq!(iter.next(), Some(Ok(Token::String("bar"))));
543        assert_eq!(iter.next(), Some(Ok(Token::Ident("."))));
544        assert_eq!(iter.next(), Some(Ok(Token::Ident("1.3"))));
545        assert_eq!(iter.next(), Some(Ok(Token::CloseParen(18))));
546        assert_eq!(iter.next(), None);
547    }
548
549    macro_rules! check_reader {
550        ($expect:expr, $compare:expr, $cx:expr) => {{
551            let obj = $cx.add($expect);
552            assert_eq!(obj, read($compare, $cx).unwrap().0)
553        }};
554    }
555
556    #[test]
557    fn test_read_number() {
558        let roots = &RootSet::default();
559        let cx = &Context::new(roots);
560        check_reader!(5, "5", cx);
561        check_reader!(49, "49", cx);
562        check_reader!(-105, "-105", cx);
563        check_reader!(1.5, "1.5", cx);
564        check_reader!(-3.0, "-3.0", cx);
565        check_reader!(1, "+1", cx);
566        check_reader!(1, "001", cx);
567        check_reader!(1, "#o001", cx);
568        check_reader!(8, "#o10", cx);
569        check_reader!(8, "#8r10", cx);
570        check_reader!(2385, "#o4521", cx);
571        check_reader!(2385, "#8r4521", cx);
572        check_reader!(0b1, "#b001", cx);
573        check_reader!(0b10, "#b10", cx);
574        check_reader!(0b10, "#2r10", cx);
575        check_reader!(0b10, "#00000002r10", cx);
576        check_reader!(0b101_1101, "#b1011101", cx);
577        check_reader!(0x1, "#x001", cx);
578        check_reader!(0x10, "#x10", cx);
579        check_reader!(0xdead_beef_i64, "#xDeAdBeEf", cx);
580        check_reader!(171, "#12r0123", cx);
581        check_reader!(49360, "#36r1234", cx);
582        assert_error("#37r1234", Error::ParseInt(37, 0), cx);
583        assert_error("#257r1234", Error::UnknownMacroCharacter('7', 0), cx);
584        assert_error("#123456r1234", Error::UnknownMacroCharacter('4', 0), cx);
585    }
586
587    #[test]
588    #[expect(clippy::non_ascii_literal)]
589    fn test_read_char() {
590        let roots = &RootSet::default();
591        let cx = &Context::new(roots);
592        check_reader!(97, "?a", cx);
593        check_reader!(21, "?", cx);
594        check_reader!(225, "?á", cx);
595        check_reader!(97, "?a?a", cx);
596        check_reader!(97, "?a#'foo ?a", cx);
597        assert_error("?aa", Error::UnexpectedChar('a', 2), cx);
598        assert_error("?", Error::MissingQuotedItem(0), cx);
599    }
600
601    #[test]
602    fn read_bool() {
603        let roots = &RootSet::default();
604        let cx = &Context::new(roots);
605        check_reader!(false, "nil", cx);
606        check_reader!(false, "()", cx);
607        check_reader!(true, "t", cx);
608    }
609
610    #[test]
611    fn test_read_symbol() {
612        let roots = &RootSet::default();
613        let cx = &Context::new(roots);
614        check_reader!(sym::IF, "if", cx);
615        check_reader!(intern("--1", cx), "--1", cx);
616        check_reader!(intern("1", cx), "\\1", cx);
617        check_reader!(intern("3.0.0", cx), "3.0.0", cx);
618        check_reader!(intern("1+", cx), "1+", cx);
619        check_reader!(intern("+1", cx), "\\+1", cx);
620        check_reader!(intern(" x", cx), "\\ x", cx);
621        check_reader!(intern("\\x", cx), "\\\\x", cx);
622        check_reader!(intern("x.y", cx), "x.y", cx);
623        check_reader!(intern("(* 1 2)", cx), "\\(*\\ 1\\ 2\\)", cx);
624        check_reader!(intern("+-*/_~!@$%^&=:<>{}", cx), "+-*/_~!@$%^&=:<>{}", cx);
625    }
626
627    #[test]
628    fn test_read_string() {
629        let roots = &RootSet::default();
630        let cx = &Context::new(roots);
631        check_reader!("foo", r#""foo""#, cx);
632        check_reader!("foo bar", r#""foo bar""#, cx);
633        check_reader!("foo\nbar\t\r", r#""foo\nbar\t\r""#, cx);
634        check_reader!(
635            "foobarbaz",
636            r#""foo\ bar\
637baz""#,
638            cx
639        );
640    }
641
642    #[test]
643    fn test_read_cons() {
644        let roots = &RootSet::default();
645        let cx = &Context::new(roots);
646        check_reader!(false, "()", cx);
647        check_reader!(Cons::new(1, 2, cx), "(1 . 2)", cx);
648        check_reader!(list!(1; cx), "(1)", cx);
649        check_reader!(list!("foo"; cx), "(\"foo\")", cx);
650        check_reader!(Cons::new(1, Cons::new(1.5, "foo", cx), cx), "(1 1.5 . \"foo\")", cx);
651        check_reader!(list!("foo", Cons::new(1, 1.5, cx); cx), "(\"foo\" (1 . 1.5))", cx);
652        check_reader!(list!(1, 1.5; cx), "(1 1.5)", cx);
653        check_reader!(list!(1, 1.5, -7; cx), "(1 1.5 -7)", cx);
654        check_reader!(list!(1, 1.5, intern(".", cx); cx), "(1 1.5 .)", cx);
655        check_reader!(list!(1, 1.5, intern("...", cx), 2; cx), "(1 1.5 ... 2)", cx);
656    }
657
658    #[test]
659    fn read_quote() {
660        let roots = &RootSet::default();
661        let cx = &Context::new(roots);
662        let quote = sym::QUOTE;
663        check_reader!(list!(quote, sym::IF; cx), "(quote if)", cx);
664        check_reader!(list!(quote, sym::IF; cx), "'if", cx);
665        check_reader!(list!(quote, list!(1, 2, 3; cx); cx), "'(1 2 3)", cx);
666        check_reader!(u32::from('a'), "?a", cx);
667        check_reader!(u32::from(' '), "?\\s", cx);
668        check_reader!(u32::from('\t'), "?\\t", cx);
669        check_reader!(u32::from('\u{AFD}'), "?\\uafd", cx);
670        check_reader!(0xabc_u32, "?\\xabc", cx);
671    }
672
673    #[test]
674    fn read_sharp() {
675        let roots = &RootSet::default();
676        let cx = &Context::new(roots);
677        let quote = sym::FUNCTION;
678        check_reader!(list!(quote, sym::IF; cx), "#'if", cx);
679        check_reader!(
680            list!(quote, list!(intern("lambda", cx), sym::IF, false, false; cx); cx),
681            "#'(lambda if () nil)",
682            cx
683        );
684        assert_error("#", Error::MissingQuotedItem(0), cx);
685        assert_error("#'", Error::MissingQuotedItem(0), cx);
686        assert_error("#a", Error::UnknownMacroCharacter('a', 0), cx);
687    }
688
689    #[test]
690    fn test_read_vec() {
691        let roots = &RootSet::default();
692        let cx = &Context::new(roots);
693        check_reader!(Vec::<Object>::new(), "[]", cx);
694        let vec: Vec<Object> = vec![1.into()];
695        check_reader!(vec, "[1]", cx);
696        let vec: Vec<Object> = vec![1.into(), 2.into()];
697        check_reader!(vec, "[1 2]", cx);
698        let vec: Vec<Object> = vec![1.into(), 2.into(), 3.into()];
699        check_reader!(vec, "[1 2 3]", cx);
700    }
701
702    fn assert_error(input: &str, error: Error, cx: &Context) {
703        let result = read(input, cx).err().unwrap();
704        assert_eq!(result, error);
705    }
706
707    #[test]
708    fn reader_error() {
709        let roots = &RootSet::default();
710        let cx = &Context::new(roots);
711        assert_error("", Error::EmptyStream, cx);
712        assert_error(" (1 2", Error::MissingCloseParen(1), cx);
713        assert_error("  (1 (2 3) 4", Error::MissingCloseParen(2), cx);
714        assert_error("  (1 (2 3 4", Error::MissingCloseParen(5), cx);
715        assert_error(" \"foo", Error::MissingStringDel(1), cx);
716        assert_error("(1 2 . 3 4)", Error::ExtraItemInCdr(9), cx);
717        assert_error("(1 3 \0)", Error::UnexpectedChar('\0', 5), cx);
718        assert_error(" '", Error::MissingQuotedItem(1), cx);
719        assert_error(" )", Error::ExtraCloseParen(1), cx);
720        assert_error("(1 . #o9 3)", Error::ParseInt(8, 5), cx);
721    }
722
723    #[test]
724    fn comments() {
725        let roots = &RootSet::default();
726        let cx = &Context::new(roots);
727        assert_error(" ; comment ", Error::EmptyStream, cx);
728        check_reader!(1, "; comment \n  1", cx);
729    }
730}