Skip to main content

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}