Skip to main content

rl_lang/lexer/types/
string_literal.rs

1//! Double-quoted string literal scanner.
2//!
3//! Consumes everything between `"…"`, handling escape sequences, and emits
4//! [`TokenType::StringLiteral`].
5use crate::lexer::{tokenizer::Tokenizer, tokentypes::TokenType};
6use crate::utils::errors::Error;
7
8impl Tokenizer {
9    /// Scans a double-quoted string literal and emits [`TokenType::StringLiteral`].
10    ///
11    /// Supports multi-line strings and the following escape sequences:
12    ///
13    /// | Sequence | Meaning        |
14    /// |----------|----------------|
15    /// | `\n`     | newline        |
16    /// | `\t`     | tab            |
17    /// | `\r`     | carriage return|
18    /// | `\0`     | null           |
19    /// | `\\`     | backslash      |
20    /// | `\"`     | double quote   |
21    /// | `\'`     | single quote   |
22    ///
23    /// # Errors
24    ///
25    /// - `unterminated string` -> if EOF is reached before the closing `"`
26    /// - `unknown escape sequence` -> if `\` is followed by an unrecognized character
27    pub fn string_literal(&mut self) -> Result<(), Error> {
28        // construct new string
29        let mut value = String::new();
30
31        // while not the end of string which is determinated by "
32        while !self.is_at_end() && self.peek() != '"' {
33            // cache next character in ch
34            let ch = self.peek();
35            // if it is new line (e.g. pressed enter)
36            // increase line count and push escaped sequence into string
37            // then advance
38            if ch == '\n' {
39                self.line += 1;
40                value.push(ch);
41                self.advance();
42                continue;
43            }
44
45            // is it escape sequence?
46            if ch == '\\' {
47                // consume the first \
48                self.advance();
49                // safety check for end of file
50                if self.is_at_end() {
51                    return Err(self.err("unterminated string", self.current_span()));
52                }
53
54                // cache the next escaped character
55                let escaped_ch = self.peek();
56                // is it recognized escaped sequence?
57                let resolved_escape = match escaped_ch {
58                    'n' => '\n',
59                    't' => '\t',
60                    'r' => '\r',
61                    '0' => '\0',
62                    '\\' => '\\',
63                    '"' => '"',
64                    '\'' => '\'',
65                    other => {
66                        return Err(self.err(
67                            format!("unknown escape sequence '\\{}'", other),
68                            self.current_span(),
69                        ));
70                    }
71                };
72
73                // add the escaped sequence into value then advance
74                value.push(resolved_escape);
75                self.advance();
76                continue;
77            }
78
79            // if not escape sequence nor " then add to value and advance
80            value.push(ch);
81            self.advance();
82        }
83
84        // are we at end of file or there is "?
85        if self.is_at_end() {
86            return Err(self.err("unterminated string", self.current_span()));
87        }
88        // consume the "
89        self.advance();
90
91        // add the constructed string value
92        self.add_token(TokenType::StringLiteral(value));
93        Ok(())
94    }
95}