409 lines
15 KiB
Plaintext
409 lines
15 KiB
Plaintext
// Expect retcode = 0
|
|
|
|
// 词法单元:用带载荷 ADT 表达 TokenKind;关键词/算符各自独立 enum。
|
|
// 整型与字符在词法期解码并随载荷携带;文本一律借用源切片(不复制)。
|
|
|
|
type .{ .ByteView } = @import("utils/byte_view.spl");
|
|
|
|
type Keyword = enum {
|
|
Any, As, Bool, Break, Catch, Comptime, Const, Continue, Defer, Else, Enum,
|
|
Errdefer, False, Fn, For, If, Loop, Match, Null, Ret, Shape, Struct, True,
|
|
Try, Type, Undefined, Union, Var, While, Underscore,
|
|
}
|
|
|
|
type KeywordResult = enum {
|
|
Yes: Keyword,
|
|
No,
|
|
}
|
|
|
|
type Punct = enum {
|
|
Eq, Assign, AddAssign, Add, SubAssign, Sub, MulAssign, Mul, DivAssign, Div,
|
|
ModAssign, Mod, RightArrow, LeftArrow, ArrowBoth, FatArrowBoth, RightFatArrow,
|
|
AndAnd, AndAssign, And, OrOr, OrAssign, Or, XorAssign, Xor,
|
|
LShAssign, LSh, Le, Lt, RShAssign, RSh, Ge, Gt, Not, Neq, BitNot,
|
|
LBracket, RBracket, LParen, RParen, LBrace, RBrace,
|
|
Semicolon, Comma, Colon, ColonAssign, Dot, Range, Ellipsis, Cond, Sharp, At, Dollar,
|
|
}
|
|
|
|
type Radix = enum {
|
|
Dec,
|
|
Bin,
|
|
Oct,
|
|
Hex,
|
|
}
|
|
|
|
type IntValue = struct {
|
|
value: u64,
|
|
radix: Radix,
|
|
}
|
|
|
|
type TokenKind = enum {
|
|
Keyword: Keyword,
|
|
Punct: Punct,
|
|
Ident,
|
|
Int: IntValue,
|
|
Float,
|
|
Char: u8,
|
|
Str,
|
|
LineComment,
|
|
Endline,
|
|
Eof,
|
|
Unknown: u8,
|
|
}
|
|
|
|
type Token = @self;
|
|
|
|
kind: TokenKind,
|
|
text: ByteView,
|
|
line: usize,
|
|
col: usize,
|
|
|
|
fn make(kind: TokenKind, text: ByteView, line: usize, col: usize): Token {
|
|
ret Token { .kind = kind, .text = text, .line = line, .col = col };
|
|
}
|
|
|
|
fn int_value(value: u64, radix: Radix): IntValue {
|
|
ret IntValue { .value = value, .radix = radix };
|
|
}
|
|
|
|
fn is_trivia(kind: TokenKind): bool {
|
|
match kind {
|
|
.Endline => { ret true; },
|
|
.LineComment => { ret true; },
|
|
_ => { ret false; },
|
|
}
|
|
}
|
|
|
|
fn keyword_of(text: ByteView): KeywordResult {
|
|
if is(text, "any") { ret .Yes(.Any); }
|
|
if is(text, "as") { ret .Yes(.As); }
|
|
if is(text, "bool") { ret .Yes(.Bool); }
|
|
if is(text, "break") { ret .Yes(.Break); }
|
|
if is(text, "catch") { ret .Yes(.Catch); }
|
|
if is(text, "comptime") { ret .Yes(.Comptime); }
|
|
if is(text, "const") { ret .Yes(.Const); }
|
|
if is(text, "continue") { ret .Yes(.Continue); }
|
|
if is(text, "defer") { ret .Yes(.Defer); }
|
|
if is(text, "else") { ret .Yes(.Else); }
|
|
if is(text, "enum") { ret .Yes(.Enum); }
|
|
if is(text, "errdefer") { ret .Yes(.Errdefer); }
|
|
if is(text, "false") { ret .Yes(.False); }
|
|
if is(text, "fn") { ret .Yes(.Fn); }
|
|
if is(text, "for") { ret .Yes(.For); }
|
|
if is(text, "if") { ret .Yes(.If); }
|
|
if is(text, "loop") { ret .Yes(.Loop); }
|
|
if is(text, "match") { ret .Yes(.Match); }
|
|
if is(text, "null") { ret .Yes(.Null); }
|
|
if is(text, "ret") { ret .Yes(.Ret); }
|
|
if is(text, "shape") { ret .Yes(.Shape); }
|
|
if is(text, "struct") { ret .Yes(.Struct); }
|
|
if is(text, "true") { ret .Yes(.True); }
|
|
if is(text, "try") { ret .Yes(.Try); }
|
|
if is(text, "type") { ret .Yes(.Type); }
|
|
if is(text, "undefined") { ret .Yes(.Undefined); }
|
|
if is(text, "union") { ret .Yes(.Union); }
|
|
if is(text, "var") { ret .Yes(.Var); }
|
|
if is(text, "while") { ret .Yes(.While); }
|
|
if is(text, "_") { ret .Yes(.Underscore); }
|
|
ret .No;
|
|
}
|
|
|
|
// ---- token 类型名(与 stage1 spl_tok_type_name 逐字一致,供 dump/diff 使用)----
|
|
|
|
fn keyword_name(kw: Keyword): []u8 {
|
|
match kw {
|
|
.Any => { ret "KW_ANY"; },
|
|
.As => { ret "KW_AS"; },
|
|
.Bool => { ret "KW_BOOL"; },
|
|
.Break => { ret "KW_BREAK"; },
|
|
.Catch => { ret "KW_CATCH"; },
|
|
.Comptime => { ret "KW_COMPTIME"; },
|
|
.Const => { ret "KW_CONST"; },
|
|
.Continue => { ret "KW_CONTINUE"; },
|
|
.Defer => { ret "KW_DEFER"; },
|
|
.Else => { ret "KW_ELSE"; },
|
|
.Enum => { ret "KW_ENUM"; },
|
|
.Errdefer => { ret "KW_ERRDEFER"; },
|
|
.False => { ret "KW_FALSE"; },
|
|
.Fn => { ret "KW_FN"; },
|
|
.For => { ret "KW_FOR"; },
|
|
.If => { ret "KW_IF"; },
|
|
.Loop => { ret "KW_LOOP"; },
|
|
.Match => { ret "KW_MATCH"; },
|
|
.Null => { ret "KW_NULL"; },
|
|
.Ret => { ret "KW_RET"; },
|
|
.Shape => { ret "KW_SHAPE"; },
|
|
.Struct => { ret "KW_STRUCT"; },
|
|
.True => { ret "KW_TRUE"; },
|
|
.Try => { ret "KW_TRY"; },
|
|
.Type => { ret "KW_TYPE"; },
|
|
.Undefined => { ret "KW_UNDEFINED"; },
|
|
.Union => { ret "KW_UNION"; },
|
|
.Var => { ret "KW_VAR"; },
|
|
.While => { ret "KW_WHILE"; },
|
|
.Underscore => { ret "KW_UNDERSCORE"; },
|
|
}
|
|
ret "?";
|
|
}
|
|
|
|
fn punct_name(punct: Punct): []u8 {
|
|
match punct {
|
|
.Eq => { ret "TOK_EQ"; },
|
|
.Assign => { ret "TOK_ASSIGN"; },
|
|
.AddAssign => { ret "TOK_ASSIGN_ADD"; },
|
|
.Add => { ret "TOK_ADD"; },
|
|
.SubAssign => { ret "TOK_ASSIGN_SUB"; },
|
|
.Sub => { ret "TOK_SUB"; },
|
|
.MulAssign => { ret "TOK_ASSIGN_MUL"; },
|
|
.Mul => { ret "TOK_MUL"; },
|
|
.DivAssign => { ret "TOK_ASSIGN_DIV"; },
|
|
.Div => { ret "TOK_DIV"; },
|
|
.ModAssign => { ret "TOK_ASSIGN_MOD"; },
|
|
.Mod => { ret "TOK_MOD"; },
|
|
.RightArrow => { ret "TOK_RIGHT_ARROW"; },
|
|
.LeftArrow => { ret "TOK_LEFT_ARROW"; },
|
|
.ArrowBoth => { ret "TOK_ARROW_BOTH"; },
|
|
.FatArrowBoth => { ret "TOK_FAT_ARROW_BOTH"; },
|
|
.RightFatArrow => { ret "TOK_RIGHT_FAT_ARROW"; },
|
|
.AndAnd => { ret "TOK_AND_AND"; },
|
|
.AndAssign => { ret "TOK_ASSIGN_AND"; },
|
|
.And => { ret "TOK_AND"; },
|
|
.OrOr => { ret "TOK_OR_OR"; },
|
|
.OrAssign => { ret "TOK_ASSIGN_OR"; },
|
|
.Or => { ret "TOK_OR"; },
|
|
.XorAssign => { ret "TOK_ASSIGN_XOR"; },
|
|
.Xor => { ret "TOK_XOR"; },
|
|
.LShAssign => { ret "TOK_ASSIGN_L_SH"; },
|
|
.LSh => { ret "TOK_L_SH"; },
|
|
.Le => { ret "TOK_LE"; },
|
|
.Lt => { ret "TOK_LT"; },
|
|
.RShAssign => { ret "TOK_ASSIGN_R_SH"; },
|
|
.RSh => { ret "TOK_R_SH"; },
|
|
.Ge => { ret "TOK_GE"; },
|
|
.Gt => { ret "TOK_GT"; },
|
|
.Not => { ret "TOK_NOT"; },
|
|
.Neq => { ret "TOK_NEQ"; },
|
|
.BitNot => { ret "TOK_BIT_NOT"; },
|
|
.LBracket => { ret "TOK_L_BRACKET"; },
|
|
.RBracket => { ret "TOK_R_BRACKET"; },
|
|
.LParen => { ret "TOK_L_PAREN"; },
|
|
.RParen => { ret "TOK_R_PAREN"; },
|
|
.LBrace => { ret "TOK_L_BRACE"; },
|
|
.RBrace => { ret "TOK_R_BRACE"; },
|
|
.Semicolon => { ret "TOK_SEMICOLON"; },
|
|
.Comma => { ret "TOK_COMMA"; },
|
|
.Colon => { ret "TOK_COLON"; },
|
|
.ColonAssign => { ret "TOK_COLON_ASSIGN"; },
|
|
.Dot => { ret "TOK_DOT"; },
|
|
.Range => { ret "TOK_RANGE"; },
|
|
.Ellipsis => { ret "TOK_ELLIPSIS"; },
|
|
.Cond => { ret "TOK_COND"; },
|
|
.Sharp => { ret "TOK_SHARP"; },
|
|
.At => { ret "TOK_AT"; },
|
|
.Dollar => { ret "TOK_DOLLAR"; },
|
|
}
|
|
ret "?";
|
|
}
|
|
|
|
fn kind_name(kind: TokenKind): []u8 {
|
|
match kind {
|
|
.Keyword(kw) => { ret keyword_name(kw); },
|
|
.Punct(punct) => { ret punct_name(punct); },
|
|
.Ident => { ret "TOK_IDENT"; },
|
|
.Int(value) => { ret "TOK_INT_LITERAL"; },
|
|
.Float => { ret "TOK_FLOAT_LITERAL"; },
|
|
.Char(value) => { ret "TOK_CHAR_LITERAL"; },
|
|
.Str => { ret "TOK_STRING_LITERAL"; },
|
|
.LineComment => { ret "TOK_LINE_COMMENT"; },
|
|
.Endline => { ret "TOK_ENDLINE"; },
|
|
.Eof => { ret "TOK_EOF"; },
|
|
.Unknown(value) => { ret "TOK_UNKNOWN"; },
|
|
}
|
|
ret "?";
|
|
}
|
|
|
|
// ---- 测试辅助 ----
|
|
|
|
fn view(text: []u8): ByteView {
|
|
ret ByteView.from_slice(text);
|
|
}
|
|
|
|
fn is(text: ByteView, literal: []u8): bool {
|
|
ret ByteView.eq(text, view(literal));
|
|
}
|
|
|
|
fn expect_keyword(result: KeywordResult, expected: Keyword) {
|
|
match result {
|
|
.Yes(kw) => { @assert(@enum2int(kw) == @enum2int(expected), "keyword"); },
|
|
.No => { @assert(false, "expected keyword"); },
|
|
}
|
|
}
|
|
|
|
fn test_keywords() {
|
|
expect_keyword(keyword_of(view("fn")), .Fn);
|
|
expect_keyword(keyword_of(view("while")), .While);
|
|
expect_keyword(keyword_of(view("_")), .Underscore);
|
|
match keyword_of(view("foo")) {
|
|
.Yes(kw) => { @assert(false, "foo is not a keyword"); },
|
|
.No => {},
|
|
}
|
|
match keyword_of(view("")) {
|
|
.Yes(kw) => { @assert(false, "empty is not a keyword"); },
|
|
.No => {},
|
|
}
|
|
}
|
|
|
|
fn test_adt_payloads() {
|
|
var t = make(.Int(int_value(255, .Hex)), view("0xFF"), 3, 5);
|
|
@assert(t.line == 3 && t.col == 5, "position");
|
|
match t.kind {
|
|
.Int(iv) => {
|
|
@assert(iv.value == 255, "int value");
|
|
var hex: Radix = .Hex;
|
|
@assert(@enum2int(iv.radix) == @enum2int(hex), "int radix");
|
|
},
|
|
_ => { @assert(false, "expected int"); },
|
|
}
|
|
|
|
var c = make(.Char('a'), view("'a'"), 1, 1);
|
|
match c.kind {
|
|
.Char(value) => { @assert(value == 'a', "char value"); },
|
|
_ => { @assert(false, "expected char"); },
|
|
}
|
|
|
|
var p = make(.Punct(.ArrowBoth), view("<->"), 1, 1);
|
|
match p.kind {
|
|
.Punct(punct) => {
|
|
var both: Punct = .ArrowBoth;
|
|
@assert(@enum2int(punct) == @enum2int(both), "punct");
|
|
},
|
|
_ => { @assert(false, "expected punct"); },
|
|
}
|
|
|
|
var k = make(.Keyword(.Ret), view("ret"), 1, 1);
|
|
match k.kind {
|
|
.Keyword(kw) => {
|
|
var ret_kw: Keyword = .Ret;
|
|
@assert(@enum2int(kw) == @enum2int(ret_kw), "keyword kind");
|
|
},
|
|
_ => { @assert(false, "expected keyword"); },
|
|
}
|
|
}
|
|
|
|
fn test_trivia() {
|
|
@assert(is_trivia(.Endline), "endline trivia");
|
|
@assert(is_trivia(.LineComment), "comment trivia");
|
|
@assert(!is_trivia(.Ident), "ident not trivia");
|
|
@assert(!is_trivia(.Eof), "eof not trivia");
|
|
}
|
|
|
|
fn expect_kind_name(kind: TokenKind, expected: []u8) {
|
|
@assert(ByteView.eq(ByteView.from_slice(kind_name(kind)),
|
|
ByteView.from_slice(expected)),
|
|
"token kind name");
|
|
}
|
|
|
|
fn test_kind_names() {
|
|
expect_kind_name(.Keyword(.Any), "KW_ANY");
|
|
expect_kind_name(.Keyword(.As), "KW_AS");
|
|
expect_kind_name(.Keyword(.Bool), "KW_BOOL");
|
|
expect_kind_name(.Keyword(.Break), "KW_BREAK");
|
|
expect_kind_name(.Keyword(.Catch), "KW_CATCH");
|
|
expect_kind_name(.Keyword(.Comptime), "KW_COMPTIME");
|
|
expect_kind_name(.Keyword(.Const), "KW_CONST");
|
|
expect_kind_name(.Keyword(.Continue), "KW_CONTINUE");
|
|
expect_kind_name(.Keyword(.Defer), "KW_DEFER");
|
|
expect_kind_name(.Keyword(.Else), "KW_ELSE");
|
|
expect_kind_name(.Keyword(.Enum), "KW_ENUM");
|
|
expect_kind_name(.Keyword(.Errdefer), "KW_ERRDEFER");
|
|
expect_kind_name(.Keyword(.False), "KW_FALSE");
|
|
expect_kind_name(.Keyword(.Fn), "KW_FN");
|
|
expect_kind_name(.Keyword(.For), "KW_FOR");
|
|
expect_kind_name(.Keyword(.If), "KW_IF");
|
|
expect_kind_name(.Keyword(.Loop), "KW_LOOP");
|
|
expect_kind_name(.Keyword(.Match), "KW_MATCH");
|
|
expect_kind_name(.Keyword(.Null), "KW_NULL");
|
|
expect_kind_name(.Keyword(.Ret), "KW_RET");
|
|
expect_kind_name(.Keyword(.Shape), "KW_SHAPE");
|
|
expect_kind_name(.Keyword(.Struct), "KW_STRUCT");
|
|
expect_kind_name(.Keyword(.True), "KW_TRUE");
|
|
expect_kind_name(.Keyword(.Try), "KW_TRY");
|
|
expect_kind_name(.Keyword(.Type), "KW_TYPE");
|
|
expect_kind_name(.Keyword(.Undefined), "KW_UNDEFINED");
|
|
expect_kind_name(.Keyword(.Union), "KW_UNION");
|
|
expect_kind_name(.Keyword(.Var), "KW_VAR");
|
|
expect_kind_name(.Keyword(.While), "KW_WHILE");
|
|
expect_kind_name(.Keyword(.Underscore), "KW_UNDERSCORE");
|
|
|
|
expect_kind_name(.Punct(.Eq), "TOK_EQ");
|
|
expect_kind_name(.Punct(.Assign), "TOK_ASSIGN");
|
|
expect_kind_name(.Punct(.AddAssign), "TOK_ASSIGN_ADD");
|
|
expect_kind_name(.Punct(.Add), "TOK_ADD");
|
|
expect_kind_name(.Punct(.SubAssign), "TOK_ASSIGN_SUB");
|
|
expect_kind_name(.Punct(.Sub), "TOK_SUB");
|
|
expect_kind_name(.Punct(.MulAssign), "TOK_ASSIGN_MUL");
|
|
expect_kind_name(.Punct(.Mul), "TOK_MUL");
|
|
expect_kind_name(.Punct(.DivAssign), "TOK_ASSIGN_DIV");
|
|
expect_kind_name(.Punct(.Div), "TOK_DIV");
|
|
expect_kind_name(.Punct(.ModAssign), "TOK_ASSIGN_MOD");
|
|
expect_kind_name(.Punct(.Mod), "TOK_MOD");
|
|
expect_kind_name(.Punct(.RightArrow), "TOK_RIGHT_ARROW");
|
|
expect_kind_name(.Punct(.LeftArrow), "TOK_LEFT_ARROW");
|
|
expect_kind_name(.Punct(.ArrowBoth), "TOK_ARROW_BOTH");
|
|
expect_kind_name(.Punct(.FatArrowBoth), "TOK_FAT_ARROW_BOTH");
|
|
expect_kind_name(.Punct(.RightFatArrow), "TOK_RIGHT_FAT_ARROW");
|
|
expect_kind_name(.Punct(.AndAnd), "TOK_AND_AND");
|
|
expect_kind_name(.Punct(.AndAssign), "TOK_ASSIGN_AND");
|
|
expect_kind_name(.Punct(.And), "TOK_AND");
|
|
expect_kind_name(.Punct(.OrOr), "TOK_OR_OR");
|
|
expect_kind_name(.Punct(.OrAssign), "TOK_ASSIGN_OR");
|
|
expect_kind_name(.Punct(.Or), "TOK_OR");
|
|
expect_kind_name(.Punct(.XorAssign), "TOK_ASSIGN_XOR");
|
|
expect_kind_name(.Punct(.Xor), "TOK_XOR");
|
|
expect_kind_name(.Punct(.LShAssign), "TOK_ASSIGN_L_SH");
|
|
expect_kind_name(.Punct(.LSh), "TOK_L_SH");
|
|
expect_kind_name(.Punct(.Le), "TOK_LE");
|
|
expect_kind_name(.Punct(.Lt), "TOK_LT");
|
|
expect_kind_name(.Punct(.RShAssign), "TOK_ASSIGN_R_SH");
|
|
expect_kind_name(.Punct(.RSh), "TOK_R_SH");
|
|
expect_kind_name(.Punct(.Ge), "TOK_GE");
|
|
expect_kind_name(.Punct(.Gt), "TOK_GT");
|
|
expect_kind_name(.Punct(.Not), "TOK_NOT");
|
|
expect_kind_name(.Punct(.Neq), "TOK_NEQ");
|
|
expect_kind_name(.Punct(.BitNot), "TOK_BIT_NOT");
|
|
expect_kind_name(.Punct(.LBracket), "TOK_L_BRACKET");
|
|
expect_kind_name(.Punct(.RBracket), "TOK_R_BRACKET");
|
|
expect_kind_name(.Punct(.LParen), "TOK_L_PAREN");
|
|
expect_kind_name(.Punct(.RParen), "TOK_R_PAREN");
|
|
expect_kind_name(.Punct(.LBrace), "TOK_L_BRACE");
|
|
expect_kind_name(.Punct(.RBrace), "TOK_R_BRACE");
|
|
expect_kind_name(.Punct(.Semicolon), "TOK_SEMICOLON");
|
|
expect_kind_name(.Punct(.Comma), "TOK_COMMA");
|
|
expect_kind_name(.Punct(.Colon), "TOK_COLON");
|
|
expect_kind_name(.Punct(.ColonAssign), "TOK_COLON_ASSIGN");
|
|
expect_kind_name(.Punct(.Dot), "TOK_DOT");
|
|
expect_kind_name(.Punct(.Range), "TOK_RANGE");
|
|
expect_kind_name(.Punct(.Ellipsis), "TOK_ELLIPSIS");
|
|
expect_kind_name(.Punct(.Cond), "TOK_COND");
|
|
expect_kind_name(.Punct(.Sharp), "TOK_SHARP");
|
|
expect_kind_name(.Punct(.At), "TOK_AT");
|
|
expect_kind_name(.Punct(.Dollar), "TOK_DOLLAR");
|
|
|
|
expect_kind_name(.Ident, "TOK_IDENT");
|
|
expect_kind_name(.Int(int_value(0, .Dec)), "TOK_INT_LITERAL");
|
|
expect_kind_name(.Float, "TOK_FLOAT_LITERAL");
|
|
expect_kind_name(.Char('a'), "TOK_CHAR_LITERAL");
|
|
expect_kind_name(.Str, "TOK_STRING_LITERAL");
|
|
expect_kind_name(.LineComment, "TOK_LINE_COMMENT");
|
|
expect_kind_name(.Endline, "TOK_ENDLINE");
|
|
expect_kind_name(.Eof, "TOK_EOF");
|
|
expect_kind_name(.Unknown(0), "TOK_UNKNOWN");
|
|
}
|
|
|
|
fn main() {
|
|
test_keywords();
|
|
test_adt_payloads();
|
|
test_trivia();
|
|
test_kind_names();
|
|
}
|