// Expect retcode = 0 // 词法分析器:字节流 -> Vec。镜像 stage1/spl_lexer.c 的行为: // 行注释作为 token 产出;块注释(可嵌套)整体跳过;换行产出 Endline;EOF 产出 Eof。 // 复用 utils/ascii 做字符分类;整型/字符在词法期解码后放进 TokenKind 载荷。 // Token.text 借用输入字节,不复制。 type .{ .Vec, .VecResult } = @import("utils/vec.spl"); type .{ .ByteView } = @import("utils/byte_view.spl"); type .{ .is_digit, .is_alpha, .is_alnum, .is_hex_digit, .digit_value } = @import("utils/ascii.spl"); type .{ .Token, .TokenKind, .Radix, .Keyword, .Punct, .int_value, .keyword_of } = @import("token.spl"); type Lexer = @self; src: ByteView, pos: usize, line: usize, col: usize, fn new(src: ByteView): Lexer { ret Lexer { .src = src, .pos = 0, .line = 1, .col = 1 }; } fn is_ident_start(c: u8): bool { ret is_alpha(c) || c == '_'; } fn is_ident_cont(c: u8): bool { ret is_alnum(c) || c == '_'; } fn hex_val(c: u8): u8 { match digit_value(c, 16) { .Ok(v) => { ret v; }, .Err(error) => { ret 0; }, } ret 0; } // 跳过下划线,按 radix 折叠;非法字符忽略(前缀 x/b/o 自然被忽略)。 fn decode_digits(text: ByteView, radix: u8): u64 { var acc: u64 = 0; var i: usize = 0; while i < text.len { var b = text.ptr[i]; if b != '_' { match digit_value(b, radix) { .Ok(v) => { acc = acc * (radix as u64) + (v as u64); }, .Err(error) => {}, } } i = i + 1; } ret acc; } // 文本形如 'a' / '\n' / '\x41';返回解码后的字节。 fn decode_char(text: ByteView): u8 { var start: usize = 1; var end = text.len; if text.len >= 2 && text.ptr[text.len - 1] == '\'' { end = text.len - 1; } if end <= start { ret 0; } var b = text.ptr[start]; if b != '\\' { ret b; } start = start + 1; if start >= end { ret 0; } var e = text.ptr[start]; if e == 'n' { ret '\n'; } if e == 't' { ret '\t'; } if e == 'r' { ret '\r'; } if e == '\\' { ret '\\'; } if e == '"' { ret '"'; } if e == '\'' { ret '\''; } if e == '0' { ret 0; } if e == 'x' || e == 'X' { var q = start + 1; var value: u8 = 0; var digits: usize = 0; while q < end && digits < 2 && is_hex_digit(text.ptr[q]) { value = value * 16 + hex_val(text.ptr[q]); q = q + 1; digits = digits + 1; } if digits == 0 { ret e; } ret value; } ret e; } // ---- 读取辅助 ---- fn has2(self: *Lexer, ch: u8): bool { ret self.pos + 1 < self.src.len && self.src.ptr[self.pos + 1] == ch; } fn has3(self: *Lexer, ch1: u8, ch2: u8): bool { ret self.pos + 2 < self.src.len && self.src.ptr[self.pos + 1] == ch1 && self.src.ptr[self.pos + 2] == ch2; } fn sub(self: *Lexer, start: usize, end: usize): ByteView { match ByteView.slice(self.src, start, end) { .Ok(v) => { ret v; }, .Err(error) => { ret ByteView.empty(); }, } ret ByteView.empty(); } // 产出 token 并把 pos/col 推进到 end。Vec.push 失败视为致命(同 vec.make_vec 风格)。 fn emit_to(self: *Lexer, out: *Vec, kind: TokenKind, start: usize, end: usize, line: usize, col: usize) { var t = Token.make(kind, sub(self, start, end), line, col); match Vec.push(out, &t as *u8) { .Ok => {}, .Err(error) => { @assert(false, "lexer: Vec.push failed"); }, } self.pos = end; self.col = col + (end - start); } fn op1(self: *Lexer, out: *Vec, punct: Punct, start: usize, line: usize, col: usize) { emit_to(self, out, .Punct(punct), start, start + 1, line, col); } fn op2(self: *Lexer, out: *Vec, punct: Punct, start: usize, line: usize, col: usize) { emit_to(self, out, .Punct(punct), start, start + 2, line, col); } fn op3(self: *Lexer, out: *Vec, punct: Punct, start: usize, line: usize, col: usize) { emit_to(self, out, .Punct(punct), start, start + 3, line, col); } // ---- 复合扫描 ---- fn scan_int(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) { var p = self.pos; if self.src.ptr[p] == '0' && p + 1 < self.src.len { var nc = self.src.ptr[p + 1]; if nc == 'x' || nc == 'X' { p = p + 2; while p < self.src.len && (is_hex_digit(self.src.ptr[p]) || self.src.ptr[p] == '_') { p = p + 1; } emit_to(self, out, .Int(int_value(decode_digits(sub(self, start, p), 16), .Hex)), start, p, line, col); ret; } if nc == 'b' || nc == 'B' { p = p + 2; while p < self.src.len && (self.src.ptr[p] == '0' || self.src.ptr[p] == '1' || self.src.ptr[p] == '_') { p = p + 1; } emit_to(self, out, .Int(int_value(decode_digits(sub(self, start, p), 2), .Bin)), start, p, line, col); ret; } if nc == 'o' || nc == 'O' { p = p + 2; while p < self.src.len && ((self.src.ptr[p] >= '0' && self.src.ptr[p] <= '7') || self.src.ptr[p] == '_') { p = p + 1; } emit_to(self, out, .Int(int_value(decode_digits(sub(self, start, p), 8), .Oct)), start, p, line, col); ret; } } while p < self.src.len && (is_digit(self.src.ptr[p]) || self.src.ptr[p] == '_') { p = p + 1; } if p + 1 < self.src.len && self.src.ptr[p] == '.' && is_digit(self.src.ptr[p + 1]) { p = p + 1; while p < self.src.len && is_digit(self.src.ptr[p]) { p = p + 1; } emit_to(self, out, .Float, start, p, line, col); ret; } emit_to(self, out, .Int(int_value(decode_digits(sub(self, start, p), 10), .Dec)), start, p, line, col); } fn scan_char(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) { var p = self.pos + 1; if p < self.src.len { if self.src.ptr[p] == '\\' { p = p + 1; if p < self.src.len && (self.src.ptr[p] == 'x' || self.src.ptr[p] == 'X') { p = p + 1; var digits: usize = 0; while p < self.src.len && digits < 2 && is_hex_digit(self.src.ptr[p]) { p = p + 1; digits = digits + 1; } } else if p < self.src.len { p = p + 1; } } else { p = p + 1; } } if p < self.src.len && self.src.ptr[p] == '\'' { p = p + 1; } emit_to(self, out, .Char(decode_char(sub(self, start, p))), start, p, line, col); } fn scan_string(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) { var p = self.pos + 1; var l = line; var cc = col + 1; while p < self.src.len { var b = self.src.ptr[p]; if b == '\\' { p = p + 1; cc = cc + 1; if p < self.src.len { p = p + 1; cc = cc + 1; } } else if b == '"' { p = p + 1; cc = cc + 1; break; } else if b == '\n' { l = l + 1; cc = 1; p = p + 1; } else { p = p + 1; cc = cc + 1; } } emit_to(self, out, .Str, start, p, line, col); self.line = l; self.col = cc; } fn scan_ident(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) { var p = self.pos; while p < self.src.len && is_ident_cont(self.src.ptr[p]) { p = p + 1; } var text = sub(self, start, p); match keyword_of(text) { .Yes(kw) => { emit_to(self, out, .Keyword(kw), start, p, line, col); }, .No => { emit_to(self, out, .Ident, start, p, line, col); }, } } // 块注释:可嵌套,整体跳过(不产出 token)。 fn skip_block_comment(self: *Lexer) { self.pos = self.pos + 2; self.col = self.col + 2; var depth: usize = 1; while self.pos + 1 < self.src.len && depth > 0 { if self.src.ptr[self.pos] == '*' && self.src.ptr[self.pos + 1] == '/' { depth = depth - 1; self.pos = self.pos + 2; self.col = self.col + 2; } else if self.src.ptr[self.pos] == '/' && self.src.ptr[self.pos + 1] == '*' { depth = depth + 1; self.pos = self.pos + 2; self.col = self.col + 2; } else { if self.src.ptr[self.pos] == '\n' { self.line = self.line + 1; self.col = 1; } else { self.col = self.col + 1; } self.pos = self.pos + 1; } } } fn run(self: *Lexer, out: *Vec) { while self.pos < self.src.len { var start = self.pos; var line = self.line; var col = self.col; var c = self.src.ptr[self.pos]; if c == ' ' || c == '\t' || c == '\r' { self.pos = self.pos + 1; self.col = self.col + 1; continue; } if c == '\n' { emit_to(self, out, .Endline, start, start + 1, line, col); self.line = line + 1; self.col = 1; continue; } if c == '/' && has2(self, '/') { var p = self.pos; while p < self.src.len && self.src.ptr[p] != '\n' { p = p + 1; } emit_to(self, out, .LineComment, start, p, line, col); continue; } if c == '/' && has2(self, '*') { skip_block_comment(self); continue; } if c == '\'' { scan_char(self, out, start, line, col); continue; } if c == '"' { scan_string(self, out, start, line, col); continue; } if is_ident_start(c) { scan_ident(self, out, start, line, col); continue; } if is_digit(c) { scan_int(self, out, start, line, col); continue; } if c == '<' { if has3(self, '-', '>') { op3(self, out, .ArrowBoth, start, line, col); continue; } if has3(self, '=', '>') { op3(self, out, .FatArrowBoth, start, line, col); continue; } if has3(self, '<', '=') { op3(self, out, .LShAssign, start, line, col); continue; } if has2(self, '-') { op2(self, out, .LeftArrow, start, line, col); continue; } if has2(self, '=') { op2(self, out, .Le, start, line, col); continue; } op1(self, out, .Lt, start, line, col); continue; } if c == '>' { if has3(self, '>', '=') { op3(self, out, .RShAssign, start, line, col); continue; } if has2(self, '>') { op2(self, out, .RSh, start, line, col); continue; } if has2(self, '=') { op2(self, out, .Ge, start, line, col); continue; } op1(self, out, .Gt, start, line, col); continue; } if c == '.' { if has3(self, '.', '.') { op3(self, out, .Ellipsis, start, line, col); continue; } if has2(self, '.') { op2(self, out, .Range, start, line, col); continue; } op1(self, out, .Dot, start, line, col); continue; } if c == '=' { if has2(self, '=') { op2(self, out, .Eq, start, line, col); continue; } if has2(self, '>') { op2(self, out, .RightFatArrow, start, line, col); continue; } op1(self, out, .Assign, start, line, col); continue; } if c == '!' { if has2(self, '=') { op2(self, out, .Neq, start, line, col); continue; } op1(self, out, .Not, start, line, col); continue; } if c == '&' { if has2(self, '&') { op2(self, out, .AndAnd, start, line, col); continue; } if has2(self, '=') { op2(self, out, .AndAssign, start, line, col); continue; } op1(self, out, .And, start, line, col); continue; } if c == '|' { if has2(self, '|') { op2(self, out, .OrOr, start, line, col); continue; } if has2(self, '=') { op2(self, out, .OrAssign, start, line, col); continue; } op1(self, out, .Or, start, line, col); continue; } if c == '+' { if has2(self, '=') { op2(self, out, .AddAssign, start, line, col); continue; } op1(self, out, .Add, start, line, col); continue; } if c == '-' { if has2(self, '=') { op2(self, out, .SubAssign, start, line, col); continue; } if has2(self, '>') { op2(self, out, .RightArrow, start, line, col); continue; } op1(self, out, .Sub, start, line, col); continue; } if c == '*' { if has2(self, '=') { op2(self, out, .MulAssign, start, line, col); continue; } op1(self, out, .Mul, start, line, col); continue; } if c == '/' { if has2(self, '=') { op2(self, out, .DivAssign, start, line, col); continue; } op1(self, out, .Div, start, line, col); continue; } if c == '%' { if has2(self, '=') { op2(self, out, .ModAssign, start, line, col); continue; } op1(self, out, .Mod, start, line, col); continue; } if c == '^' { if has2(self, '=') { op2(self, out, .XorAssign, start, line, col); continue; } op1(self, out, .Xor, start, line, col); continue; } if c == ':' { if has2(self, '=') { op2(self, out, .ColonAssign, start, line, col); continue; } op1(self, out, .Colon, start, line, col); continue; } if c == '~' { op1(self, out, .BitNot, start, line, col); continue; } if c == ',' { op1(self, out, .Comma, start, line, col); continue; } if c == ';' { op1(self, out, .Semicolon, start, line, col); continue; } if c == '(' { op1(self, out, .LParen, start, line, col); continue; } if c == ')' { op1(self, out, .RParen, start, line, col); continue; } if c == '[' { op1(self, out, .LBracket, start, line, col); continue; } if c == ']' { op1(self, out, .RBracket, start, line, col); continue; } if c == '{' { op1(self, out, .LBrace, start, line, col); continue; } if c == '}' { op1(self, out, .RBrace, start, line, col); continue; } if c == '#' { op1(self, out, .Sharp, start, line, col); continue; } if c == '@' { op1(self, out, .At, start, line, col); continue; } if c == '$' { op1(self, out, .Dollar, start, line, col); continue; } if c == '?' { op1(self, out, .Cond, start, line, col); continue; } emit_to(self, out, .Unknown(c), start, start + 1, line, col); } var end = self.src.len; emit_to(self, out, .Eof, end, end, self.line, self.col); } fn lex(src: ByteView): VecResult { var out = Vec.blank(@sizeof(Token), @alignof(Token)); match Vec.new(@sizeof(Token), @alignof(Token)) { .Ok(v) => { out = v; }, .Err(error) => { ret .Err(error); }, } var self = new(src); run(&self, &out); ret .Ok(out); } // ---- 测试辅助 ---- fn view(text: []u8): ByteView { ret ByteView.from_slice(text); } fn lex_ok(src: []u8): Vec { var out = Vec.blank(@sizeof(Token), @alignof(Token)); match lex(view(src)) { .Ok(v) => { out = v; }, .Err(error) => { @assert(false, "lex failed"); }, } ret out; } fn at(v: *Vec, index: usize): Token { match Vec.get(v, index) { .Ok(ptr) => { ret (ptr as *Token).*; }, .Err(error) => { ret Token.make(.Unknown(0), ByteView.empty(), 0, 0); }, } ret Token.make(.Unknown(0), ByteView.empty(), 0, 0); } fn expect_punct(tok: Token, expected: Punct) { match tok.kind { .Punct(p) => { @assert(@enum2int(p) == @enum2int(expected), "punct kind"); }, _ => { @assert(false, "expected punct"); }, } } fn expect_keyword(tok: Token, expected: Keyword) { match tok.kind { .Keyword(k) => { @assert(@enum2int(k) == @enum2int(expected), "keyword kind"); }, _ => { @assert(false, "expected keyword"); }, } } fn expect_ident(tok: Token) { match tok.kind { .Ident => {}, _ => { @assert(false, "expected ident"); }, } } fn expect_int(tok: Token, value: u64, radix: Radix) { match tok.kind { .Int(iv) => { @assert(iv.value == value, "int value"); @assert(@enum2int(iv.radix) == @enum2int(radix), "int radix"); }, _ => { @assert(false, "expected int"); }, } } fn expect_char(tok: Token, value: u8) { match tok.kind { .Char(v) => { @assert(v == value, "char value"); }, _ => { @assert(false, "expected char"); }, } } fn expect_endline(tok: Token) { match tok.kind { .Endline => {}, _ => { @assert(false, "expected endline"); }, } } fn expect_eof(tok: Token) { match tok.kind { .Eof => {}, _ => { @assert(false, "expected eof"); }, } } fn expect_text(tok: Token, literal: []u8) { @assert(ByteView.eq(tok.text, view(literal)), "token text"); } fn expect_pos(tok: Token, line: usize, col: usize) { @assert(tok.line == line && tok.col == col, "token position"); } fn test_basic() { var v = lex_ok("fn main() {\n ret 0;\n}\n"); defer { Vec.drop(&v); } @assert(v.len == 13, "basic token count"); var t0 = at(&v, 0); expect_keyword(t0, .Fn); expect_text(t0, "fn"); expect_pos(t0, 1, 1); expect_ident(at(&v, 1)); expect_text(at(&v, 1), "main"); expect_punct(at(&v, 2), .LParen); expect_punct(at(&v, 3), .RParen); expect_punct(at(&v, 4), .LBrace); expect_endline(at(&v, 5)); expect_keyword(at(&v, 6), .Ret); expect_pos(at(&v, 6), 2, 5); expect_int(at(&v, 7), 0, .Dec); expect_punct(at(&v, 8), .Semicolon); expect_endline(at(&v, 9)); expect_punct(at(&v, 10), .RBrace); expect_endline(at(&v, 11)); expect_eof(at(&v, 12)); } fn test_operators() { var v = lex_ok("<-> <- -> <=> <<= >>= ... .. . := == != < <= > >= && || & | ^ + - * / % ~ ! ? # @ $ , ; : ( ) [ ] { }"); defer { Vec.drop(&v); } @assert(v.len == 42, "operator token count"); expect_punct(at(&v, 0), .ArrowBoth); expect_text(at(&v, 0), "<->"); expect_punct(at(&v, 1), .LeftArrow); expect_punct(at(&v, 2), .RightArrow); expect_punct(at(&v, 3), .FatArrowBoth); expect_punct(at(&v, 4), .LShAssign); expect_punct(at(&v, 5), .RShAssign); expect_punct(at(&v, 6), .Ellipsis); expect_punct(at(&v, 7), .Range); expect_punct(at(&v, 8), .Dot); expect_punct(at(&v, 9), .ColonAssign); expect_punct(at(&v, 10), .Eq); expect_punct(at(&v, 11), .Neq); expect_punct(at(&v, 12), .Lt); expect_punct(at(&v, 13), .Le); expect_punct(at(&v, 14), .Gt); expect_punct(at(&v, 15), .Ge); expect_punct(at(&v, 16), .AndAnd); expect_punct(at(&v, 17), .OrOr); expect_punct(at(&v, 18), .And); expect_punct(at(&v, 19), .Or); expect_punct(at(&v, 20), .Xor); expect_punct(at(&v, 21), .Add); expect_punct(at(&v, 22), .Sub); expect_punct(at(&v, 23), .Mul); expect_punct(at(&v, 24), .Div); expect_punct(at(&v, 25), .Mod); expect_punct(at(&v, 26), .BitNot); expect_punct(at(&v, 27), .Not); expect_punct(at(&v, 28), .Cond); expect_punct(at(&v, 29), .Sharp); expect_punct(at(&v, 30), .At); expect_punct(at(&v, 31), .Dollar); expect_punct(at(&v, 32), .Comma); expect_punct(at(&v, 33), .Semicolon); expect_punct(at(&v, 34), .Colon); expect_punct(at(&v, 35), .LParen); expect_punct(at(&v, 36), .RParen); expect_punct(at(&v, 37), .LBracket); expect_punct(at(&v, 38), .RBracket); expect_punct(at(&v, 39), .LBrace); expect_punct(at(&v, 40), .RBrace); expect_eof(at(&v, 41)); } fn test_comments_and_literals() { var v = lex_ok("// c\n/* a /* nested */ b */\n'a' '\\n' '\\x41' \"hi\\n\" 0xFF 0b1010 0o17 1_000 3.14"); defer { Vec.drop(&v); } @assert(v.len == 13, "literal token count"); var comment = at(&v, 0); match comment.kind { .LineComment => {}, _ => { @assert(false, "expected line comment"); }, } expect_text(comment, "// c"); expect_pos(comment, 1, 1); expect_endline(at(&v, 1)); expect_endline(at(&v, 2)); var ch3 = at(&v, 3); expect_char(ch3, 'a'); @assert(ByteView.eq(ch3.text, view("'a'")), "char text"); expect_pos(ch3, 3, 1); expect_char(at(&v, 4), '\n'); expect_char(at(&v, 5), 0x41); var s = at(&v, 6); match s.kind { .Str => {}, _ => { @assert(false, "expected string"); }, } expect_text(s, "\"hi\\n\""); expect_int(at(&v, 7), 255, .Hex); expect_int(at(&v, 8), 10, .Bin); expect_int(at(&v, 9), 15, .Oct); expect_int(at(&v, 10), 1000, .Dec); var fl = at(&v, 11); match fl.kind { .Float => {}, _ => { @assert(false, "expected float"); }, } expect_eof(at(&v, 12)); } fn main() { test_basic(); test_operators(); test_comments_and_literals(); }