lib/std/arch/rv64/asm/scanner.rad 9.1 KiB raw
1
//! Assembly-specific lexical scanner.
2
@test mod tests;
3
4
use std::char;
5
use std::lang::strings;
6
7
/// Token kinds recognized by the assembler scanner.
8
export union TokenKind: Copy {
9
    /// Special end-of-file token generated when the input is exhausted.
10
    Eof,
11
    /// Special invalid token carrying an error message in [`Token::source`].
12
    Invalid,
13
14
    LParen,     // (
15
    RParen,     // )
16
    Comma,      // ,
17
    Colon,      // :
18
    ColonColon, // ::
19
    Semicolon,  // ;
20
    Minus,      // -
21
    Plus,       // +
22
    Slash,      // /
23
    Star,       // *
24
25
    /// Bare identifier used for mnemonics, constants, CSR names, and symbol segments.
26
    Ident,
27
    /// Identifier-shaped label token including the leading `@`.
28
    Label,
29
    /// Quoted label token including the leading `@` and quote delimiters.
30
    QuotedLabel,
31
    /// Directive token including the leading `.`.
32
    Directive,
33
    /// Register token including the leading `%`.
34
    Register,
35
36
    /// String literal token including delimiters.
37
    String,
38
    /// Character literal token including delimiters.
39
    Char,
40
    /// Integer literal token.
41
    Number,
42
}
43
44
/// Describes where assembler source originated from.
45
export union SourceKind: Copy {
46
    /// Source loaded from a file at the given path.
47
    File { path: *[u8] },
48
    /// Source provided as an inline string.
49
    String,
50
}
51
52
/// Lexical scanner state for assembler source.
53
export record Scanner: Copy {
54
    /// Origin of the source being scanned.
55
    sourceKind: SourceKind,
56
    /// Source buffer.
57
    source: *[u8],
58
    /// Offset of the current token in `source`.
59
    token: u32,
60
    /// Offset of the current cursor in `source`.
61
    cursor: u32,
62
    /// Current token observed by the parser.
63
    current: Token,
64
    /// Previously consumed token observed by the parser.
65
    previous: Token,
66
}
67
68
/// Individual token with kind, source text, and byte offset.
69
export record Token: Copy {
70
    /// Token kind.
71
    kind: TokenKind,
72
    /// Token source text.
73
    source: *[u8],
74
    /// Byte offset of `source` in the input buffer.
75
    offset: u32,
76
}
77
78
/// Create a new assembler scanner.
79
export fn scanner(sourceKind: SourceKind, source: *[u8]) -> Scanner {
80
    let invalidToken = invalid(0, "");
81
    return Scanner {
82
        sourceKind,
83
        source,
84
        token: 0,
85
        cursor: 0,
86
        current: invalidToken,
87
        previous: invalidToken,
88
    };
89
}
90
91
/// Create an invalid token with the given message.
92
export fn invalid(offset: u32, message: *[u8]) -> Token {
93
    return Token { kind: TokenKind::Invalid, source: message, offset };
94
}
95
96
/// Return `true` when the scanner has consumed all input.
97
export fn isEof(s: &Scanner) -> bool {
98
    return s.cursor >= s.source.len;
99
}
100
101
/// Return the current character without advancing.
102
fn current(s: &Scanner) -> ?u8 {
103
    if isEof(s) {
104
        return nil;
105
    }
106
    return s.source[s.cursor];
107
}
108
109
/// Return the next character without advancing.
110
fn peek(s: &Scanner) -> ?u8 {
111
    if s.cursor + 1 >= s.source.len {
112
        return nil;
113
    }
114
    return s.source[s.cursor + 1];
115
}
116
117
/// Advance the scanner cursor and return the consumed character.
118
fn advance(s: &mut Scanner) -> u8 {
119
    set s.cursor += 1;
120
    return s.source[s.cursor - 1];
121
}
122
123
/// Consume `expected` when it is present at the current cursor.
124
fn consume(s: &mut Scanner, expected: u8) -> bool {
125
    if let ch = current(s); ch == expected {
126
        advance(s);
127
        return true;
128
    }
129
    return false;
130
}
131
132
/// Skip spaces, newlines, tabs, and `//` line comments.
133
fn skipWhitespace(s: &mut Scanner) {
134
    while let ch = current(s) {
135
        match ch {
136
            case ' ', '\n', '\r', '\t' => advance(s),
137
            case '/' => {
138
                if let nextCh = peek(s); nextCh == '/' {
139
                    while let lineCh = current(s); lineCh <> '\n' {
140
                        advance(s);
141
                    }
142
                } else {
143
                    return;
144
                }
145
            }
146
            else => return,
147
        }
148
    }
149
}
150
151
/// Return the next assembler token and intern identifier text in the pool.
152
export fn next(s: &mut Scanner, pool: &mut strings::Pool) -> Token {
153
    skipWhitespace(s);
154
    set s.token = s.cursor;
155
156
    if isEof(s) {
157
        return tok(s, TokenKind::Eof);
158
    }
159
    let ch = advance(s);
160
161
    if char::isDigit(ch) {
162
        return scanNumber(s);
163
    }
164
    if char::isAlpha(ch) or ch == '_' {
165
        return scanIdentToken(s, pool, TokenKind::Ident);
166
    }
167
168
    match ch {
169
        case '(' => return tok(s, TokenKind::LParen),
170
        case ')' => return tok(s, TokenKind::RParen),
171
        case ',' => return tok(s, TokenKind::Comma),
172
        case ';' => return tok(s, TokenKind::Semicolon),
173
        case ':' => {
174
            if consume(s, ':') {
175
                return tok(s, TokenKind::ColonColon);
176
            }
177
            return invalid(s.token, "unexpected `:`");
178
        }
179
        case '"' => return scanString(s),
180
        case '\'' => return scanChar(s),
181
        case '.' => return scanPrefixedToken(s, pool, TokenKind::Directive, "expected directive name after `.`"),
182
        case '@' => return scanLabelToken(s, pool),
183
        case '%' => return scanPrefixedToken(s, pool, TokenKind::Register, "expected register after `%`"),
184
        case '-' => return scanSignedNumberOrToken(s, TokenKind::Minus),
185
        case '+' => return scanSignedNumberOrToken(s, TokenKind::Plus),
186
        case '/' => return tok(s, TokenKind::Slash),
187
        case '*' => return tok(s, TokenKind::Star),
188
        else => return invalid(s.token, "unexpected character"),
189
    }
190
}
191
192
/// Create a token spanning the current scanner range.
193
fn tok(s: &Scanner, kind: TokenKind) -> Token {
194
    return Token { kind, source: &s.source[s.token..s.cursor], offset: s.token };
195
}
196
197
/// Scan the identifier continuation characters that follow the current token start.
198
fn scanIdentifierBody(s: &mut Scanner) {
199
    while let ch = current(s); char::isAlpha(ch) or char::isDigit(ch) or ch == '_' or ch == '.' {
200
        advance(s);
201
    }
202
}
203
204
/// Scan a signed number when `+` or `-` is followed by a digit, otherwise return the punctuation token.
205
fn scanSignedNumberOrToken(s: &mut Scanner, kind: TokenKind) -> Token {
206
    if let nextCh = current(s); char::isDigit(nextCh) {
207
        return scanNumber(s);
208
    }
209
    return tok(s, kind);
210
}
211
212
/// Scan a numeric literal.
213
fn scanNumber(s: &mut Scanner) -> Token {
214
    let first = s.source[s.cursor - 1];
215
    if first == '-' or first == '+' {
216
        advance(s);
217
    }
218
    if s.source[s.cursor - 1] == '0' {
219
        if let ch = current(s); ch == 'x' or ch == 'X' {
220
            advance(s);
221
            if let digit = current(s); not char::isHexDigit(digit) {
222
                return invalid(s.token, "invalid hex literal");
223
            }
224
            while let digit = current(s); char::isHexDigit(digit) {
225
                advance(s);
226
            }
227
            return tok(s, TokenKind::Number);
228
        }
229
    }
230
    while let digit = current(s); char::isDigit(digit) {
231
        advance(s);
232
    }
233
    return tok(s, TokenKind::Number);
234
}
235
236
/// Scan a printable token terminated by `delim`.
237
fn scanCharsUntil(s: &mut Scanner, delim: u8, kind: TokenKind) -> ?Token {
238
    while let ch = current(s); ch <> delim {
239
        if not char::isPrint(ch) {
240
            return invalid(s.token, "invalid character");
241
        }
242
        if consume(s, '\\') {
243
            if isEof(s) {
244
                return nil;
245
            }
246
        }
247
        advance(s);
248
    }
249
    if not consume(s, delim) {
250
        return nil;
251
    }
252
    return tok(s, kind);
253
}
254
255
/// Scan a string literal.
256
fn scanString(s: &mut Scanner) -> Token {
257
    if let token = scanCharsUntil(s, '"', TokenKind::String) {
258
        return token;
259
    }
260
    return invalid(s.token, "unterminated string");
261
}
262
263
/// Scan a character literal.
264
fn scanChar(s: &mut Scanner) -> Token {
265
    if let token = scanCharsUntil(s, '\'', TokenKind::Char) {
266
        return token;
267
    }
268
    return invalid(s.token, "unterminated character");
269
}
270
271
/// Scan an identifier-shaped token of the given kind.
272
fn scanIdentToken(s: &mut Scanner, pool: &mut strings::Pool, kind: TokenKind) -> Token {
273
    scanIdentifierBody(s);
274
    let source = &s.source[s.token..s.cursor];
275
276
    return Token {
277
        kind,
278
        source: strings::intern(pool, source),
279
        offset: s.token,
280
    };
281
}
282
283
/// Scan a sigil-prefixed identifier-shaped token.
284
fn scanPrefixedToken(s: &mut Scanner, pool: &mut strings::Pool, kind: TokenKind, message: *[u8]) -> Token {
285
    let ch = current(s) else {
286
        return invalid(s.token, message);
287
    };
288
    if not char::isAlpha(ch) and ch <> '_' {
289
        return invalid(s.token, message);
290
    }
291
    scanIdentifierBody(s);
292
    let source = &s.source[s.token..s.cursor];
293
294
    return Token {
295
        kind,
296
        source: strings::intern(pool, source),
297
        offset: s.token,
298
    };
299
}
300
301
/// Scan an assembler label token, accepting either `@name` or `@"quoted"` syntax.
302
fn scanLabelToken(s: &mut Scanner, pool: &mut strings::Pool) -> Token {
303
    let ch = current(s) else {
304
        return invalid(s.token, "expected label after `@`");
305
    };
306
    if ch == '"' {
307
        advance(s);
308
        if let token = scanCharsUntil(s, '"', TokenKind::QuotedLabel) {
309
            return token;
310
        }
311
        return invalid(s.token, "unterminated quoted label");
312
    }
313
    return scanPrefixedToken(s, pool, TokenKind::Label, "expected label after `@`");
314
}