1use crate::diag::Diagnostic;
2use crate::span::{Span, Spanned};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum Tok {
6 Ident(String),
7 Str(String),
9 Num(String),
10 Eval(String),
12 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
50pub 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
62pub 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 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 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}