atmos/os_lib/aura/lexer.rs
1//! # Aura プログラミング言語用レキサー(字句解析器)
2//!
3//! このモジュールは、Aura 言語のソースコード文字列をトークン列(字句)に変換する処理を提供します。
4//! コメント、識別子、数値(整数および有理数/小数)、文字列リテラル、テンプレート文字列などを解釈します。
5
6use super::number::{BigInt, Ratio};
7use alloc::string::String;
8use alloc::vec::Vec;
9
10/// Aura 言語における最小のトークン(字句)の定義。
11#[derive(Debug, Clone, PartialEq)]
12pub enum Token {
13 /// 識別子 (変数名、関数名、記号など)
14 Ident(String),
15 /// 多倍長整数
16 Num(BigInt),
17 /// 多倍長有理数 (小数を含む)
18 Ratio(Ratio),
19 /// 文字列リテラル
20 Str(String),
21 /// テンプレート文字列
22 TemplateStr(String),
23 /// 左括弧 `(`。括弧の手前に空白があったかどうかの情報を含みます(Auraの構文特性のため)
24 LParen {
25 /// 括弧の直前にスペースや改行が存在したかどうか
26 leading_space: bool,
27 },
28 /// 右括弧 `)`
29 RParen,
30 /// 左中括弧 `{`
31 LBrace,
32 /// 右中括弧 `}`
33 RBrace,
34 /// コロン `:`
35 Colon,
36}
37
38/// ソースコード文字列を入力として受け取り、トークンの配列(`Vec<Token>`)に分解します。
39///
40/// # コメントの処理
41/// - `#` : 行末までの1行コメント(インデントを継承する設計用情報)
42/// - `##` : インデントを無視して行頭から始まる1行コメント
43/// - `#|` と `|#` : ブロックコメント(インデントを継承)
44/// - `##|` と `|##` : ブロックコメント(インデントを無視)
45///
46/// ※ レキサー内部では、コメント領域内の文字列は単にスキップされ、トークンとして出力されません。
47///
48/// # 引数
49/// * `input` - 解析対象のソースコード文字列
50///
51/// # 戻り値
52/// 字句解析によって得られたトークンの配列
53pub fn tokenize(input: &str) -> Vec<Token> {
54 let mut tokens = Vec::new();
55 let mut chars = input.chars().peekable();
56 let mut has_space = false;
57
58 while let Some(&ch) = chars.peek() {
59 if ch == '#' {
60 let mut iter = chars.clone();
61 iter.next(); // consume first '#'
62
63 let mut is_block = false;
64 let mut is_double = false;
65
66 if let Some(&next_ch) = iter.peek() {
67 if next_ch == '#' {
68 is_double = true;
69 iter.next(); // consume second '#'
70 if let Some(&next_next_ch) = iter.peek() {
71 if next_next_ch == '|' {
72 is_block = true;
73 }
74 }
75 } else if next_ch == '|' {
76 is_block = true;
77 }
78 }
79
80 // advance chars iterator for the pattern prefix
81 chars.next();
82 if is_double {
83 chars.next();
84 }
85 if is_block {
86 chars.next();
87 }
88
89 if is_block {
90 let target_end = if is_double { "|##" } else { "|#" };
91 loop {
92 let mut temp = chars.clone();
93 let mut matched = true;
94 for tc in target_end.chars() {
95 if temp.next() != Some(tc) {
96 matched = false;
97 break;
98 }
99 }
100 if matched {
101 for _ in 0..target_end.len() {
102 chars.next();
103 }
104 break;
105 }
106 if chars.next().is_none() {
107 break; // EOF
108 }
109 }
110 } else {
111 while let Some(&c) = chars.peek() {
112 if c == '\n' {
113 break;
114 }
115 chars.next();
116 }
117 }
118
119 has_space = true;
120 continue;
121 }
122
123 let old_len = tokens.len();
124 match ch {
125 ' ' | '\t' | '\r' | '\n' | ',' => {
126 has_space = true;
127 chars.next();
128 }
129 '(' => {
130 tokens.push(Token::LParen {
131 leading_space: has_space,
132 });
133 chars.next();
134 }
135 ')' => {
136 tokens.push(Token::RParen);
137 chars.next();
138 }
139 '{' => {
140 tokens.push(Token::LBrace);
141 chars.next();
142 }
143 '}' => {
144 tokens.push(Token::RBrace);
145 chars.next();
146 }
147 ':' => {
148 tokens.push(Token::Colon);
149 chars.next();
150 }
151 '"' => {
152 chars.next(); // consume opening quote
153 let mut s = String::new();
154 while let Some(&c) = chars.peek() {
155 if c == '"' {
156 chars.next();
157 break;
158 }
159 s.push(c);
160 chars.next();
161 }
162 tokens.push(Token::Str(s));
163 }
164 '\'' => {
165 chars.next(); // consume opening quote
166 let mut s = String::new();
167 while let Some(&c) = chars.peek() {
168 if c == '\'' {
169 chars.next();
170 break;
171 }
172 s.push(c);
173 chars.next();
174 }
175 tokens.push(Token::Str(s));
176 }
177 '`' => {
178 chars.next(); // consume opening backtick
179 let mut s = String::new();
180 while let Some(&c) = chars.peek() {
181 if c == '`' {
182 chars.next();
183 break;
184 }
185 s.push(c);
186 chars.next();
187 }
188 tokens.push(Token::TemplateStr(s));
189 }
190 _ => {
191 if ch.is_ascii_digit()
192 || (ch == '-' && is_digit_next(&mut chars))
193 || (ch == '+' && is_digit_next(&mut chars))
194 {
195 // Number parsing
196 let mut sign_char = None;
197 if ch == '-' || ch == '+' {
198 if let Some(c) = chars.next() {
199 sign_char = Some(c);
200 }
201 }
202
203 // 次の文字が '0' で、さらにその次が 'b', 'o', 'x' のいずれかか?
204 let mut is_radix = false;
205 let mut radix_prefix = String::new();
206
207 if let Some(&'0') = chars.peek() {
208 let mut temp_chars = chars.clone();
209 temp_chars.next(); // skip '0'
210 if let Some(&p) = temp_chars.peek() {
211 if p == 'b' || p == 'o' || p == 'x' {
212 is_radix = true;
213 radix_prefix.push('0');
214 radix_prefix.push(p);
215 chars.next(); // consume '0'
216 chars.next(); // consume 'b'/'o'/'x'
217 }
218 }
219 }
220
221 if is_radix {
222 // 基数に応じた文字種を読み込む
223 let mut body = String::new();
224 while let Some(&c) = chars.peek() {
225 let valid = match radix_prefix.as_str() {
226 "0b" => c == '0' || c == '1',
227 "0o" => ('0'..='7').contains(&c),
228 "0x" => {
229 c.is_ascii_digit()
230 || ('a'..='f').contains(&c)
231 || ('A'..='F').contains(&c)
232 }
233 _ => false,
234 };
235 if valid {
236 body.push(c);
237 chars.next();
238 } else {
239 break;
240 }
241 }
242
243 let radix = match radix_prefix.as_str() {
244 "0b" => 2,
245 "0o" => 8,
246 "0x" => 16,
247 _ => 10,
248 };
249
250 if let Some(mut b) = BigInt::from_str_radix(&body, radix) {
251 if let Some('-') = sign_char {
252 b.sign = false;
253 }
254 tokens.push(Token::Num(b));
255 }
256 } else {
257 // 通常の10進数、または小数(Ratio)
258 let mut s = String::new();
259 if let Some(sc) = sign_char {
260 s.push(sc);
261 }
262 while let Some(&c) = chars.peek() {
263 if c.is_ascii_digit() {
264 s.push(c);
265 chars.next();
266 } else {
267 break;
268 }
269 }
270
271 // 小数点判定: 次の文字が '.' かつその次の文字が数字か?
272 let mut is_float = false;
273 if let Some(&'.') = chars.peek() {
274 let mut temp_chars = chars.clone();
275 temp_chars.next(); // skip '.'
276 if let Some(&c) = temp_chars.peek() {
277 if c.is_ascii_digit() {
278 is_float = true;
279 }
280 }
281 }
282
283 if is_float {
284 chars.next(); // consume '.'
285
286 let mut decimal_part = String::new();
287 while let Some(&c) = chars.peek() {
288 if c.is_ascii_digit() {
289 decimal_part.push(c);
290 chars.next();
291 } else {
292 break;
293 }
294 }
295
296 // 分子の文字列: 整数部 + 小数部
297 let mut num_str = s.clone();
298 num_str.push_str(&decimal_part);
299
300 if let Some(num) = BigInt::from_string(&num_str) {
301 // 分母: 10^(小数部の桁数)
302 let n = decimal_part.len();
303 let mut den_digits = alloc::vec![0; n];
304 den_digits.push(1);
305 let den = BigInt {
306 sign: true,
307 digits: den_digits,
308 };
309
310 if let Some(ratio) = Ratio::new(num, den) {
311 tokens.push(Token::Ratio(ratio));
312 }
313 }
314 } else {
315 if let Some(b) = BigInt::from_string(&s) {
316 tokens.push(Token::Num(b));
317 }
318 }
319 }
320 } else {
321 // Identifier parsing
322 let mut s = String::new();
323 while let Some(&c) = chars.peek() {
324 if c.is_whitespace()
325 || c == '('
326 || c == ')'
327 || c == '{'
328 || c == '}'
329 || c == ':'
330 || c == ','
331 || c == '"'
332 || c == '\''
333 || c == '`'
334 {
335 break;
336 }
337 s.push(c);
338 chars.next();
339 }
340 if !s.is_empty() {
341 tokens.push(Token::Ident(s));
342 }
343 }
344 }
345 }
346 if tokens.len() > old_len {
347 has_space = false;
348 }
349 }
350
351 tokens
352}
353
354/// 次の文字(現在の文字の1つ先)がアスキー数字であるかどうかを判定します。
355/// 符号(`+`, `-`)の直後に数値が続いているかを判別するために使用されます。
356fn is_digit_next(chars: &mut core::iter::Peekable<core::str::Chars>) -> bool {
357 let mut clone = chars.clone();
358 clone.next();
359 if let Some(&c) = clone.peek() {
360 c.is_ascii_digit()
361 } else {
362 false
363 }
364}