Skip to main content

nounsql_core/
lexer.rs

1use crate::diag::Diagnostic;
2use crate::span::{Span, Spanned};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum Tok {
6    Ident(String),
7    /// 文字列リテラル。引用符を除いた中身。
8    Str(String),
9    Num(String),
10    /// `eval(...)` の中身を生のまま保持する。
11    Eval(String),
12    /// 行コメント。`parse` では捨てられる。ハイライトのために保持する。
13    Comment(String),
14    LBrace,
15    RBrace,
16    LBracket,
17    RBracket,
18    LParen,
19    RParen,
20    Comma,
21    Eq,
22    Newline,
23    Eof,
24}
25
26impl Tok {
27    pub fn describe(&self) -> String {
28        match self {
29            Tok::Ident(s) => format!("`{s}`"),
30            Tok::Str(_) => "文字列".into(),
31            Tok::Num(s) => format!("`{s}`"),
32            Tok::Eval(_) => "`eval(...)`".into(),
33            Tok::Comment(_) => "コメント".into(),
34            Tok::LBrace => "`{`".into(),
35            Tok::RBrace => "`}`".into(),
36            Tok::LBracket => "`[`".into(),
37            Tok::RBracket => "`]`".into(),
38            Tok::LParen => "`(`".into(),
39            Tok::RParen => "`)`".into(),
40            Tok::Comma => "`,`".into(),
41            Tok::Eq => "`=`".into(),
42            Tok::Newline => "改行".into(),
43            Tok::Eof => "入力の終わり".into(),
44        }
45    }
46}
47
48pub type Token = Spanned<Tok>;
49
50/// 構文解析用。コメントを落とす。
51pub fn lex(src: &str) -> (Vec<Token>, Vec<Diagnostic>) {
52    let (tokens, diags) = lex_all(src);
53    (
54        tokens
55            .into_iter()
56            .filter(|t| !matches!(t.value, Tok::Comment(_)))
57            .collect(),
58        diags,
59    )
60}
61
62/// コメントを含む全トークン。シンタックスハイライト用。
63pub fn lex_all(src: &str) -> (Vec<Token>, Vec<Diagnostic>) {
64    Lexer::new(src).run()
65}
66
67struct Lexer<'a> {
68    src: &'a str,
69    bytes: &'a [u8],
70    pos: usize,
71    tokens: Vec<Token>,
72    diags: Vec<Diagnostic>,
73}
74
75impl<'a> Lexer<'a> {
76    fn new(src: &'a str) -> Self {
77        Self {
78            src,
79            bytes: src.as_bytes(),
80            pos: 0,
81            tokens: Vec::new(),
82            diags: Vec::new(),
83        }
84    }
85
86    fn run(mut self) -> (Vec<Token>, Vec<Diagnostic>) {
87        while self.pos < self.bytes.len() {
88            let c = self.bytes[self.pos];
89            match c {
90                b' ' | b'\t' | b'\r' => self.pos += 1,
91                b'#' => self.lex_comment(),
92                b'\n' => self.push_single(Tok::Newline),
93                b'{' => self.push_single(Tok::LBrace),
94                b'}' => self.push_single(Tok::RBrace),
95                b'[' => self.push_single(Tok::LBracket),
96                b']' => self.push_single(Tok::RBracket),
97                b'(' => self.push_single(Tok::LParen),
98                b')' => self.push_single(Tok::RParen),
99                b',' => self.push_single(Tok::Comma),
100                b'=' => self.push_single(Tok::Eq),
101                b'"' => self.lex_string(),
102                b'0'..=b'9' | b'-' => self.lex_number(),
103                _ if is_ident_start(c) => self.lex_ident(),
104                _ => {
105                    let span = Span::new(self.pos, self.pos + 1);
106                    let ch = self.src[self.pos..].chars().next().unwrap_or('?');
107                    self.diags
108                        .push(Diagnostic::error(span, format!("解釈できない文字 `{ch}`")));
109                    self.pos += ch.len_utf8();
110                }
111            }
112        }
113        let end = self.bytes.len();
114        self.tokens.push(Token::new(Tok::Eof, Span::new(end, end)));
115        (self.tokens, self.diags)
116    }
117
118    fn push_single(&mut self, tok: Tok) {
119        let span = Span::new(self.pos, self.pos + 1);
120        self.tokens.push(Token::new(tok, span));
121        self.pos += 1;
122    }
123
124    fn lex_comment(&mut self) {
125        let start = self.pos;
126        while self.pos < self.bytes.len() && self.bytes[self.pos] != b'\n' {
127            self.pos += 1;
128        }
129        let span = Span::new(start, self.pos);
130        self.tokens.push(Token::new(
131            Tok::Comment(self.src[start..self.pos].into()),
132            span,
133        ));
134    }
135
136    fn lex_string(&mut self) {
137        let start = self.pos;
138        self.pos += 1;
139        let mut value = String::new();
140        loop {
141            if self.pos >= self.bytes.len() || self.bytes[self.pos] == b'\n' {
142                let span = Span::new(start, self.pos);
143                self.diags
144                    .push(Diagnostic::error(span, "文字列が閉じられていない"));
145                self.tokens.push(Token::new(Tok::Str(value), span));
146                return;
147            }
148            match self.bytes[self.pos] {
149                b'"' => {
150                    self.pos += 1;
151                    let span = Span::new(start, self.pos);
152                    self.tokens.push(Token::new(Tok::Str(value), span));
153                    return;
154                }
155                b'\\' if self.pos + 1 < self.bytes.len() => {
156                    let esc = self.bytes[self.pos + 1];
157                    value.push(match esc {
158                        b'n' => '\n',
159                        b't' => '\t',
160                        _ => esc as char,
161                    });
162                    self.pos += 2;
163                }
164                _ => {
165                    let ch = self.src[self.pos..].chars().next().unwrap_or('?');
166                    value.push(ch);
167                    self.pos += ch.len_utf8();
168                }
169            }
170        }
171    }
172
173    fn lex_number(&mut self) {
174        let start = self.pos;
175        if self.bytes[self.pos] == b'-' {
176            self.pos += 1;
177        }
178        while self.pos < self.bytes.len()
179            && (self.bytes[self.pos].is_ascii_digit() || self.bytes[self.pos] == b'.')
180        {
181            self.pos += 1;
182        }
183        let span = Span::new(start, self.pos);
184        self.tokens
185            .push(Token::new(Tok::Num(self.src[start..self.pos].into()), span));
186    }
187
188    fn lex_ident(&mut self) {
189        let start = self.pos;
190        while self.pos < self.bytes.len() && is_ident_continue(self.bytes[self.pos]) {
191            self.pos += 1;
192        }
193        let name = &self.src[start..self.pos];
194
195        // `eval(` は中身を生のまま1トークンにする。SQL式をDSLの字句解析にかけない。
196        if name == "eval" && self.peek_non_space() == Some(b'(') {
197            if let Some(body) = self.lex_eval_body() {
198                let span = Span::new(start, self.pos);
199                self.tokens.push(Token::new(Tok::Eval(body), span));
200                return;
201            }
202            let span = Span::new(start, self.pos);
203            self.diags
204                .push(Diagnostic::error(span, "`eval(` が閉じられていない"));
205            self.tokens.push(Token::new(Tok::Eval(String::new()), span));
206            return;
207        }
208
209        let span = Span::new(start, self.pos);
210        self.tokens.push(Token::new(Tok::Ident(name.into()), span));
211    }
212
213    fn peek_non_space(&self) -> Option<u8> {
214        self.bytes[self.pos..]
215            .iter()
216            .find(|&&c| c != b' ' && c != b'\t')
217            .copied()
218    }
219
220    /// `(` から対応する `)` までを読み、中身を返す。読めなければ位置を戻さず None。
221    fn lex_eval_body(&mut self) -> Option<String> {
222        while self.pos < self.bytes.len() && matches!(self.bytes[self.pos], b' ' | b'\t') {
223            self.pos += 1;
224        }
225        if self.bytes.get(self.pos) != Some(&b'(') {
226            return None;
227        }
228        self.pos += 1;
229        let body_start = self.pos;
230        let mut depth = 1usize;
231        while self.pos < self.bytes.len() {
232            match self.bytes[self.pos] {
233                b'(' => depth += 1,
234                b')' => {
235                    depth -= 1;
236                    if depth == 0 {
237                        let body = self.src[body_start..self.pos].trim().to_string();
238                        self.pos += 1;
239                        return Some(body);
240                    }
241                }
242                b'\n' => return None,
243                _ => {}
244            }
245            self.pos += 1;
246        }
247        None
248    }
249}
250
251fn is_ident_start(c: u8) -> bool {
252    c == b'_' || c.is_ascii_alphabetic()
253}
254
255fn is_ident_continue(c: u8) -> bool {
256    c == b'_' || c.is_ascii_alphanumeric()
257}