jetcrab\lexer\scanners/
string.rs

1use crate::lexer::{LexerError, TokenKind};
2
3pub trait StringReader {
4    fn read_string(&mut self) -> Result<TokenKind, LexerError>;
5    fn read_template_string(&mut self) -> Result<TokenKind, LexerError>;
6}
7
8impl<T> StringReader for T
9where
10    T: LexerCore,
11{
12    fn read_string(&mut self) -> Result<TokenKind, LexerError> {
13        let quote = self.source()[self.pos()];
14        self.advance_pos();
15
16        let mut string = String::new();
17        let mut found_closing_quote = false;
18
19        while self.pos() < self.source().len() {
20            let c = self.source()[self.pos()];
21
22            if c == quote {
23                self.advance_pos();
24                found_closing_quote = true;
25                break;
26            } else if c == '\\' {
27                self.advance_pos();
28                if self.pos() < self.source().len() {
29                    let escaped = self.source()[self.pos()];
30                    match escaped {
31                        'n' => string.push('\n'),
32                        't' => string.push('\t'),
33                        'r' => string.push('\r'),
34                        '\\' => string.push('\\'),
35                        '"' => string.push('"'),
36                        '\'' => string.push('\''),
37                        _ => string.push(escaped),
38                    }
39                    self.advance_pos();
40                }
41            } else {
42                string.push(c);
43                self.advance_pos();
44            }
45        }
46
47        if !found_closing_quote {
48            return Err(LexerError::UnterminatedString);
49        }
50
51        Ok(TokenKind::String(string))
52    }
53
54    fn read_template_string(&mut self) -> Result<TokenKind, LexerError> {
55        self.advance_pos();
56
57        let mut template = String::new();
58
59        while self.pos() < self.source().len() {
60            let c = self.source()[self.pos()];
61
62            if c == '`' {
63                self.advance_pos();
64                break;
65            } else if c == '$' && self.peek_char(1) == Some('{') {
66                template.push_str("${");
67                self.advance_pos();
68                self.advance_pos();
69            } else if c == '\\' {
70                self.advance_pos();
71                if self.pos() < self.source().len() {
72                    let escaped = self.source()[self.pos()];
73                    match escaped {
74                        'n' => template.push('\n'),
75                        't' => template.push('\t'),
76                        'r' => template.push('\r'),
77                        '\\' => template.push('\\'),
78                        '`' => template.push('`'),
79                        '$' => template.push('$'),
80                        _ => template.push(escaped),
81                    }
82                    self.advance_pos();
83                }
84            } else {
85                template.push(c);
86                self.advance_pos();
87            }
88        }
89
90        Ok(TokenKind::TemplateString(template))
91    }
92}
93
94use crate::lexer::scanners::LexerCore;
95
96pub trait LexerCoreExt {
97    fn peek_char(&self, offset: usize) -> Option<char>;
98}
99
100impl<T> LexerCoreExt for T
101where
102    T: LexerCore,
103{
104    fn peek_char(&self, offset: usize) -> Option<char> {
105        let pos = self.pos() + offset;
106        if pos < self.source().len() {
107            Some(self.source()[pos])
108        } else {
109            None
110        }
111    }
112}