stage2 提供词法分析器通过与stage1的差异测试 stage1提供数组到切片的隐式转换
This commit is contained in:
1 parent
c30143609a
commit
bb9d0a9f18
13 files changed
+1275
-52
No files matched your search
+3
-1
@@ -214,7 +214,9 @@ TypePostfixExpr = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TypePrimary = {
|
TypePrimary = {
|
||||||
IDENTIFIER
|
AT ~ IDENTIFIER // @self 等自引用/内置类型
|
||||||
|
| ComptimeCallExpr // @import("...") 等导入即类型
|
||||||
|
| IDENTIFIER
|
||||||
| KEYWORD_i8
|
| KEYWORD_i8
|
||||||
| KEYWORD_u8
|
| KEYWORD_u8
|
||||||
| KEYWORD_i16
|
| KEYWORD_i16
|
||||||
|
|||||||
@@ -133,12 +133,33 @@ static bool collect_headers(void) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stage2 的所有 .spl:作为 splc1.sir 的依赖(不解析 @import,保守全量重建)。
|
||||||
|
static Nob_Files g_spl2 = {0};
|
||||||
|
|
||||||
|
static bool collect_spl2_entry(Nob_Walk_Entry entry) {
|
||||||
|
if (entry.type != NOB_FILE_REGULAR)
|
||||||
|
return true;
|
||||||
|
const char *dot = strrchr(entry.path, '.');
|
||||||
|
if (!dot || strcmp(dot, ".spl") != 0)
|
||||||
|
return true;
|
||||||
|
da_append(&g_spl2, nob_temp_strdup(entry.path));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool collect_spl2(void) {
|
||||||
|
if (g_spl2.count > 0)
|
||||||
|
return true;
|
||||||
|
return nob_walk_dir("stage2", collect_spl2_entry);
|
||||||
|
}
|
||||||
|
|
||||||
static bool prepare_build(void) {
|
static bool prepare_build(void) {
|
||||||
if (!nob_mkdir_if_not_exists(BUILD_FOLDER))
|
if (!nob_mkdir_if_not_exists(BUILD_FOLDER))
|
||||||
return false;
|
return false;
|
||||||
if (!sync_cflags_stamp())
|
if (!sync_cflags_stamp())
|
||||||
return false;
|
return false;
|
||||||
return collect_headers();
|
if (!collect_headers())
|
||||||
|
return false;
|
||||||
|
return collect_spl2();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool build_object(const char *src) {
|
static bool build_object(const char *src) {
|
||||||
@@ -320,6 +341,105 @@ static bool test_once_with_dump(const char *fname, void *user_data) {
|
|||||||
return nob_cmd_run(&cmd);
|
return nob_cmd_run(&cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用 splc0 编译 stage2/splc1.spl -> build/splc1.sir(依赖 stage2 全部 .spl)。
|
||||||
|
static bool build_splc1(void) {
|
||||||
|
if (!build_target("splc0"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const char *src = "stage2/splc1.spl";
|
||||||
|
const char *sir = BUILD_FOLDER "splc1.sir";
|
||||||
|
|
||||||
|
size_t ninputs = g_spl2.count + 2;
|
||||||
|
const char **inputs = (const char **)nob_temp_alloc(sizeof(const char *) * ninputs);
|
||||||
|
inputs[0] = src;
|
||||||
|
for (size_t i = 0; i < g_spl2.count; ++i)
|
||||||
|
inputs[i + 1] = g_spl2.items[i];
|
||||||
|
inputs[ninputs - 1] = CFLAGS_STAMP;
|
||||||
|
|
||||||
|
int needs = nob_needs_rebuild(sir, inputs, ninputs);
|
||||||
|
if (needs < 0)
|
||||||
|
return false;
|
||||||
|
if (needs == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Nob_Cmd cmd = {0};
|
||||||
|
nob_cmd_append(&cmd, exe_path("splc0"), src, sir);
|
||||||
|
return nob_cmd_run(&cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool run_capture(Nob_Cmd *cmd, Nob_Cmd_Result *result) {
|
||||||
|
Nob_String_Builder rendered = {0};
|
||||||
|
nob_cmd_render(*cmd, &rendered);
|
||||||
|
Nob_String_View sv = nob_sb_to_sv(rendered);
|
||||||
|
bool ok = nob_cmd_capture_popen(nob_temp_sv_to_cstr(sv), result);
|
||||||
|
nob_sb_free(rendered);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// diff 测试:对同一文件跑 `splc0 --dump <mode>` 与 `spl_cli splc1.sir --dump <mode>`,
|
||||||
|
// 逐字节比较 stdout。
|
||||||
|
static bool test_once_diff(const char *fname, void *user_data) {
|
||||||
|
const char *mode = (const char *)user_data;
|
||||||
|
if (!mode) {
|
||||||
|
nob_log(NOB_ERROR, "diff test requires a mode: lex|ast|sema|ir|vm");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!build_target("splc0"))
|
||||||
|
return false;
|
||||||
|
if (!build_target("spl_cli"))
|
||||||
|
return false;
|
||||||
|
if (!build_splc1())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Nob_Cmd c_cmd = {0};
|
||||||
|
nob_cmd_append(&c_cmd, exe_path("splc0"), "--dump", (char *)mode, (char *)fname,
|
||||||
|
BUILD_FOLDER "diff_c.sir");
|
||||||
|
Nob_Cmd_Result c_res = {0};
|
||||||
|
if (!run_capture(&c_cmd, &c_res)) {
|
||||||
|
nob_cmd_result_free(c_res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Nob_Cmd s_cmd = {0};
|
||||||
|
nob_cmd_append(&s_cmd, exe_path("spl_cli"), BUILD_FOLDER "splc1.sir", "--dump",
|
||||||
|
(char *)mode, (char *)fname);
|
||||||
|
Nob_Cmd_Result s_res = {0};
|
||||||
|
if (!run_capture(&s_cmd, &s_res)) {
|
||||||
|
nob_cmd_result_free(c_res);
|
||||||
|
nob_cmd_result_free(s_res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Nob_String_View c_out = nob_sb_to_sv(c_res.out);
|
||||||
|
Nob_String_View s_out = nob_sb_to_sv(s_res.out);
|
||||||
|
bool same = nob_sv_eq(c_out, s_out);
|
||||||
|
if (!same) {
|
||||||
|
nob_log(NOB_ERROR, "diff mismatch: %s (c=%zu bytes, spl=%zu bytes)", fname,
|
||||||
|
c_out.count, s_out.count);
|
||||||
|
// 打印首个不相同行(按行比较)。
|
||||||
|
Nob_String_View ca = c_out;
|
||||||
|
Nob_String_View sa = s_out;
|
||||||
|
size_t lineno = 1;
|
||||||
|
while (ca.count > 0 || sa.count > 0) {
|
||||||
|
Nob_String_View cl = nob_sv_chop_by_delim(&ca, '\n');
|
||||||
|
Nob_String_View sl = nob_sv_chop_by_delim(&sa, '\n');
|
||||||
|
if (!nob_sv_eq(cl, sl)) {
|
||||||
|
nob_log(NOB_ERROR, " line %zu:", lineno);
|
||||||
|
nob_log(NOB_ERROR, " c : %.*s", (int)cl.count, cl.data);
|
||||||
|
nob_log(NOB_ERROR, " spl: %.*s", (int)sl.count, sl.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lineno++;
|
||||||
|
}
|
||||||
|
nob_log(NOB_ERROR, "--- c stderr/stdout merged above; exit=%d vs spl exit=%d ---",
|
||||||
|
c_res.exit_code, s_res.exit_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
nob_cmd_result_free(c_res);
|
||||||
|
nob_cmd_result_free(s_res);
|
||||||
|
return same;
|
||||||
|
}
|
||||||
|
|
||||||
static bool is_stage_name(const char *s) {
|
static bool is_stage_name(const char *s) {
|
||||||
return strcmp(s, "stage1") == 0 || strcmp(s, "stage2") == 0 || strcmp(s, "diff") == 0;
|
return strcmp(s, "stage1") == 0 || strcmp(s, "stage2") == 0 || strcmp(s, "diff") == 0;
|
||||||
}
|
}
|
||||||
@@ -481,6 +601,15 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// diff 需要显式 mode,且使用专门的双编译器比较函数。
|
||||||
|
if (strcmp(stage, "diff") == 0) {
|
||||||
|
if (!mode_name) {
|
||||||
|
nob_log(NOB_ERROR, "diff requires a mode: lex|ast|sema|ir|vm");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
tfn = test_once_diff;
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 剩下的都当 pattern
|
// 3. 剩下的都当 pattern
|
||||||
Nob_Files files = {0};
|
Nob_Files files = {0};
|
||||||
while (argc > 0) {
|
while (argc > 0) {
|
||||||
|
|||||||
+8
-2
@@ -724,9 +724,15 @@ int spl_vm_prepare(spl_vm_t *vm, const char *entry, int argc, const char **argv,
|
|||||||
if (!vm || !vm->prog) {
|
if (!vm || !vm->prog) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
spl_vm_func_t *fn = spl_prog_get_func(vm->prog, entry ? entry : "main");
|
spl_vm_func_t *fn = NULL;
|
||||||
|
const char *entry_point = "_start";
|
||||||
|
fn = spl_prog_get_func(vm->prog, entry_point);
|
||||||
|
if (fn == NULL) {
|
||||||
|
entry_point = entry ? entry : "main";
|
||||||
|
fn = spl_prog_get_func(vm->prog, entry_point);
|
||||||
|
}
|
||||||
if (!fn) {
|
if (!fn) {
|
||||||
fprintf(stderr, "vm: entry point '%s' not found\n", entry ? entry : "main");
|
fprintf(stderr, "vm: entry point '%s' not found\n", entry_point);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
vm->fp = 0;
|
vm->fp = 0;
|
||||||
|
|||||||
@@ -1138,6 +1138,46 @@ static spl_ir_node_ref_t emit_expr_raw(spl_ast2ir_t *a, spl_ast_node_ref_t ref)
|
|||||||
|
|
||||||
usize nelems = vec_size(n->array_literal.elems);
|
usize nelems = vec_size(n->array_literal.elems);
|
||||||
|
|
||||||
|
/* [N]T -> []T:物化数组到局部存储,再构造 {ptr, len} 切片。 */
|
||||||
|
spl_lang_type_node_t *sln = spl_lang_type_node(a->sema->type, node_lang(a, n));
|
||||||
|
if (sln && sln->kind == SPL_LANG_TYPE_SLICE) {
|
||||||
|
spl_type_id_t elem_ir = to_ir_type(a, sln->extra.elem);
|
||||||
|
if (!elem_ir)
|
||||||
|
Panic("array literal slice element has no ir type");
|
||||||
|
spl_lang_type_id_t usize_lang = spl_lang_make_int(a->sema->type, sizeof(void *) * 8, 0);
|
||||||
|
spl_type_id_t usize_ir = to_ir_type(a, usize_lang);
|
||||||
|
spl_type_id_t ptr_ir = spl_type_builder_ptr(&a->sema->type->ir, elem_ir);
|
||||||
|
|
||||||
|
spl_ir_node_ref_t base = 0;
|
||||||
|
if (nelems == 0) {
|
||||||
|
base = spl_ir_builder_type_const_ptr(a->ir, ptr_ir, NULL);
|
||||||
|
} else {
|
||||||
|
spl_type_id_t arr_ir =
|
||||||
|
spl_type_builder_array(&a->sema->type->ir, elem_ir, nelems);
|
||||||
|
spl_ir_node_ref_t slot =
|
||||||
|
spl_ir_builder_mem_alloca(a->ir, arr_ir, const_usize(a, 1));
|
||||||
|
for (usize i = 0; i < nelems; ++i) {
|
||||||
|
spl_ir_node_ref_t fptr =
|
||||||
|
spl_ir_builder_mem_field(a->ir, arr_ir, slot, i);
|
||||||
|
spl_ir_node_ref_t v = emit_expr(a, vec_at(n->array_literal.elems, i));
|
||||||
|
if (!v)
|
||||||
|
Panic("array element emit failed");
|
||||||
|
spl_ir_builder_mem_store(a->ir, elem_ir, fptr, v);
|
||||||
|
}
|
||||||
|
base = spl_ir_builder_mem_field(a->ir, arr_ir, slot, 0);
|
||||||
|
}
|
||||||
|
if (!base)
|
||||||
|
Panic("array literal slice base failed");
|
||||||
|
|
||||||
|
spl_ir_node_ref_t len = spl_ir_builder_type_const_int(a->ir, usize_ir, nelems);
|
||||||
|
spl_ir_node_ref_t node = spl_ir_builder_agg_construct(a->ir, t);
|
||||||
|
if (!node)
|
||||||
|
Panic("agg_construct failed for array-to-slice");
|
||||||
|
spl_ir_builder_agg_construct_field(a->ir, node, base);
|
||||||
|
spl_ir_builder_agg_construct_field(a->ir, node, len);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
spl_ir_node_ref_vec_t field_refs;
|
spl_ir_node_ref_vec_t field_refs;
|
||||||
vec_init(field_refs);
|
vec_init(field_refs);
|
||||||
for (usize i = 0; i < nelems; ++i) {
|
for (usize i = 0; i < nelems; ++i) {
|
||||||
|
|||||||
@@ -1520,6 +1520,15 @@ static spl_lang_type_id_t sema_coerce(spl_sema_t *sema, spl_ast_node_ref_t ref,
|
|||||||
n->resolved_lang_tid = expected;
|
n->resolved_lang_tid = expected;
|
||||||
return expected;
|
return expected;
|
||||||
}
|
}
|
||||||
|
/* [N]T -> []T:数组字面量在需要切片处隐式物化为切片。 */
|
||||||
|
if (en && en->kind == SPL_LANG_TYPE_SLICE) {
|
||||||
|
spl_lang_type_id_t et = en->extra.elem;
|
||||||
|
for (usize i = 0; i < vec_size(n->array_literal.elems); ++i)
|
||||||
|
if (!sema_coerce(sema, vec_at(n->array_literal.elems, i), et))
|
||||||
|
return 0;
|
||||||
|
n->resolved_lang_tid = expected;
|
||||||
|
return expected;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,39 @@ static void dump_tokens(spl_tok_vec_t toks) {
|
|||||||
vec_free(toks);
|
vec_free(toks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 折叠重复分隔符并去掉 "." 路径分量,使同一模块的 import 解析结果唯一。
|
||||||
|
就地重写,'\' 统一成 '/';保留前导分隔符(绝对路径)。 */
|
||||||
|
static void normalize_import_path(char *p) {
|
||||||
|
usize n = strlen(p);
|
||||||
|
char *out = malloc(n + 1);
|
||||||
|
if (!out)
|
||||||
|
return;
|
||||||
|
char *w = out;
|
||||||
|
if (p[0] == '/' || p[0] == '\\')
|
||||||
|
*w++ = '/';
|
||||||
|
|
||||||
|
char *r = p;
|
||||||
|
while (*r) {
|
||||||
|
while (*r == '/' || *r == '\\')
|
||||||
|
r++;
|
||||||
|
if (!*r)
|
||||||
|
break;
|
||||||
|
char *seg = r;
|
||||||
|
while (*r && *r != '/' && *r != '\\')
|
||||||
|
r++;
|
||||||
|
usize len = (usize)(r - seg);
|
||||||
|
if (len == 1 && seg[0] == '.')
|
||||||
|
continue;
|
||||||
|
if (w != out && w[-1] != '/')
|
||||||
|
*w++ = '/';
|
||||||
|
memcpy(w, seg, len);
|
||||||
|
w += len;
|
||||||
|
}
|
||||||
|
*w = 0;
|
||||||
|
strcpy(p, out);
|
||||||
|
free(out);
|
||||||
|
}
|
||||||
|
|
||||||
/* 把 import_path 拼接到 importing_file 所在目录。
|
/* 把 import_path 拼接到 importing_file 所在目录。
|
||||||
from_fname 为空时按相对 cwd 处理。绝对路径直接透传。 */
|
from_fname 为空时按相对 cwd 处理。绝对路径直接透传。 */
|
||||||
static void resolve_relative(const char *from, const char *path, char *out, usize cap) {
|
static void resolve_relative(const char *from, const char *path, char *out, usize cap) {
|
||||||
@@ -91,6 +124,7 @@ static void resolve_relative(const char *from, const char *path, char *out, usiz
|
|||||||
}
|
}
|
||||||
if (path[0] == '/' || path[0] == '\\' || (path[0] && path[1] == ':')) {
|
if (path[0] == '/' || path[0] == '\\' || (path[0] && path[1] == ':')) {
|
||||||
snprintf(out, cap, "%s", path);
|
snprintf(out, cap, "%s", path);
|
||||||
|
normalize_import_path(out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const char *last_sep = NULL;
|
const char *last_sep = NULL;
|
||||||
@@ -99,10 +133,12 @@ static void resolve_relative(const char *from, const char *path, char *out, usiz
|
|||||||
last_sep = p;
|
last_sep = p;
|
||||||
if (!last_sep) {
|
if (!last_sep) {
|
||||||
snprintf(out, cap, "%s", path);
|
snprintf(out, cap, "%s", path);
|
||||||
|
normalize_import_path(out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
usize dir_len = (usize)(last_sep - from) + 1;
|
usize dir_len = (usize)(last_sep - from) + 1;
|
||||||
snprintf(out, cap, "%.*s%s", (int)dir_len, from, path);
|
snprintf(out, cap, "%.*s%s", (int)dir_len, from, path);
|
||||||
|
normalize_import_path(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
static spl_ast_t *driver_load(void *user, const char *from_fname, const char *path,
|
static spl_ast_t *driver_load(void *user, const char *from_fname, const char *path,
|
||||||
|
|||||||
+107
-48
@@ -11,6 +11,8 @@ type .{ .is_digit, .is_alpha, .is_alnum, .is_hex_digit, .digit_value } = @import
|
|||||||
type .{
|
type .{
|
||||||
.Token, .TokenKind, .Radix, .Keyword, .Punct, .int_value, .keyword_of
|
.Token, .TokenKind, .Radix, .Keyword, .Punct, .int_value, .keyword_of
|
||||||
} = @import("token.spl");
|
} = @import("token.spl");
|
||||||
|
type .{ .Span, .span_make } = @import("./diag.spl");
|
||||||
|
type Diag = @import("./diag.spl");
|
||||||
|
|
||||||
type Lexer = @self;
|
type Lexer = @self;
|
||||||
|
|
||||||
@@ -18,9 +20,17 @@ src: ByteView,
|
|||||||
pos: usize,
|
pos: usize,
|
||||||
line: usize,
|
line: usize,
|
||||||
col: usize,
|
col: usize,
|
||||||
|
diag: *Diag, // 借用;可为 null(合法输入不产生诊断)
|
||||||
|
file: usize,
|
||||||
|
|
||||||
fn new(src: ByteView): Lexer {
|
fn new(src: ByteView, diag: *Diag, file: usize): Lexer {
|
||||||
ret Lexer { .src = src, .pos = 0, .line = 1, .col = 1 };
|
ret Lexer { .src = src, .pos = 0, .line = 1, .col = 1, .diag = diag, .file = file };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录一条词法诊断(非法输入路径)。合法输入不应触发。
|
||||||
|
fn report(self: *Lexer, start: usize, end: usize, message: []u8) {
|
||||||
|
if self.diag == null { ret; }
|
||||||
|
Diag.err(self.diag, span_make(self.file, start, end), .LexUnexpected, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_ident_start(c: u8): bool { ret is_alpha(c) || c == '_'; }
|
fn is_ident_start(c: u8): bool { ret is_alpha(c) || c == '_'; }
|
||||||
@@ -181,6 +191,8 @@ fn scan_int(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
|
|||||||
|
|
||||||
fn scan_char(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
|
fn scan_char(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
|
||||||
var p = self.pos + 1;
|
var p = self.pos + 1;
|
||||||
|
var closed = false;
|
||||||
|
var escape_ok = true;
|
||||||
if p < self.src.len {
|
if p < self.src.len {
|
||||||
if self.src.ptr[p] == '\\' {
|
if self.src.ptr[p] == '\\' {
|
||||||
p = p + 1;
|
p = p + 1;
|
||||||
@@ -191,14 +203,30 @@ fn scan_char(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
|
|||||||
p = p + 1;
|
p = p + 1;
|
||||||
digits = digits + 1;
|
digits = digits + 1;
|
||||||
}
|
}
|
||||||
|
if digits == 0 { escape_ok = false; }
|
||||||
} else if p < self.src.len {
|
} else if p < self.src.len {
|
||||||
|
var e = self.src.ptr[p];
|
||||||
|
if !(e == 'n' || e == 't' || e == 'r' || e == '\\' || e == '"' || e == '\'' ||
|
||||||
|
e == '0') {
|
||||||
|
escape_ok = false;
|
||||||
|
}
|
||||||
p = p + 1;
|
p = p + 1;
|
||||||
|
} else {
|
||||||
|
escape_ok = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p = p + 1;
|
p = p + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p < self.src.len && self.src.ptr[p] == '\'' { p = p + 1; }
|
if p < self.src.len && self.src.ptr[p] == '\'' {
|
||||||
|
p = p + 1;
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
if !closed {
|
||||||
|
report(self, start, p, "unterminated character literal");
|
||||||
|
} else if !escape_ok {
|
||||||
|
report(self, start, p, "invalid escape in character literal");
|
||||||
|
}
|
||||||
emit_to(self, out, .Char(decode_char(sub(self, start, p))), start, p, line, col);
|
emit_to(self, out, .Char(decode_char(sub(self, start, p))), start, p, line, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +234,7 @@ fn scan_string(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
|
|||||||
var p = self.pos + 1;
|
var p = self.pos + 1;
|
||||||
var l = line;
|
var l = line;
|
||||||
var cc = col + 1;
|
var cc = col + 1;
|
||||||
|
var closed = false;
|
||||||
while p < self.src.len {
|
while p < self.src.len {
|
||||||
var b = self.src.ptr[p];
|
var b = self.src.ptr[p];
|
||||||
if b == '\\' {
|
if b == '\\' {
|
||||||
@@ -218,6 +247,7 @@ fn scan_string(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
|
|||||||
} else if b == '"' {
|
} else if b == '"' {
|
||||||
p = p + 1;
|
p = p + 1;
|
||||||
cc = cc + 1;
|
cc = cc + 1;
|
||||||
|
closed = true;
|
||||||
break;
|
break;
|
||||||
} else if b == '\n' {
|
} else if b == '\n' {
|
||||||
l = l + 1;
|
l = l + 1;
|
||||||
@@ -228,6 +258,7 @@ fn scan_string(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
|
|||||||
cc = cc + 1;
|
cc = cc + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !closed { report(self, start, p, "unterminated string literal"); }
|
||||||
emit_to(self, out, .Str, start, p, line, col);
|
emit_to(self, out, .Str, start, p, line, col);
|
||||||
self.line = l;
|
self.line = l;
|
||||||
self.col = cc;
|
self.col = cc;
|
||||||
@@ -243,8 +274,8 @@ fn scan_ident(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 块注释:可嵌套,整体跳过(不产出 token)。
|
// 块注释:可嵌套,整体跳过(不产出 token)。未闭合时报诊断。
|
||||||
fn skip_block_comment(self: *Lexer) {
|
fn skip_block_comment(self: *Lexer, start: usize) {
|
||||||
self.pos = self.pos + 2;
|
self.pos = self.pos + 2;
|
||||||
self.col = self.col + 2;
|
self.col = self.col + 2;
|
||||||
var depth: usize = 1;
|
var depth: usize = 1;
|
||||||
@@ -267,6 +298,9 @@ fn skip_block_comment(self: *Lexer) {
|
|||||||
self.pos = self.pos + 1;
|
self.pos = self.pos + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if depth > 0 {
|
||||||
|
report(self, start, self.src.len, "unterminated block comment");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(self: *Lexer, out: *Vec) {
|
fn run(self: *Lexer, out: *Vec) {
|
||||||
@@ -294,7 +328,7 @@ fn run(self: *Lexer, out: *Vec) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if c == '/' && has2(self, '*') {
|
if c == '/' && has2(self, '*') {
|
||||||
skip_block_comment(self);
|
skip_block_comment(self, start);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if c == '\'' {
|
if c == '\'' {
|
||||||
@@ -318,6 +352,7 @@ fn run(self: *Lexer, out: *Vec) {
|
|||||||
if has3(self, '-', '>') { op3(self, out, .ArrowBoth, start, line, col); continue; }
|
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, .FatArrowBoth, start, line, col); continue; }
|
||||||
if has3(self, '<', '=') { op3(self, out, .LShAssign, start, line, col); continue; }
|
if has3(self, '<', '=') { op3(self, out, .LShAssign, start, line, col); continue; }
|
||||||
|
if has2(self, '<') { op2(self, out, .LSh, start, line, col); continue; }
|
||||||
if has2(self, '-') { op2(self, out, .LeftArrow, 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; }
|
if has2(self, '=') { op2(self, out, .Le, start, line, col); continue; }
|
||||||
op1(self, out, .Lt, start, line, col);
|
op1(self, out, .Lt, start, line, col);
|
||||||
@@ -409,6 +444,7 @@ fn run(self: *Lexer, out: *Vec) {
|
|||||||
if c == '$' { op1(self, out, .Dollar, start, line, col); continue; }
|
if c == '$' { op1(self, out, .Dollar, start, line, col); continue; }
|
||||||
if c == '?' { op1(self, out, .Cond, start, line, col); continue; }
|
if c == '?' { op1(self, out, .Cond, start, line, col); continue; }
|
||||||
|
|
||||||
|
report(self, start, start + 1, "unexpected character");
|
||||||
emit_to(self, out, .Unknown(c), start, start + 1, line, col);
|
emit_to(self, out, .Unknown(c), start, start + 1, line, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,13 +452,13 @@ fn run(self: *Lexer, out: *Vec) {
|
|||||||
emit_to(self, out, .Eof, end, end, self.line, self.col);
|
emit_to(self, out, .Eof, end, end, self.line, self.col);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lex(src: ByteView): VecResult {
|
fn lex(src: ByteView, diag: *Diag, file: usize): VecResult {
|
||||||
var out = Vec.blank(@sizeof(Token), @alignof(Token));
|
var out = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||||
match Vec.new(@sizeof(Token), @alignof(Token)) {
|
match Vec.new(@sizeof(Token), @alignof(Token)) {
|
||||||
.Ok(v) => { out = v; },
|
.Ok(v) => { out = v; },
|
||||||
.Err(error) => { ret .Err(error); },
|
.Err(error) => { ret .Err(error); },
|
||||||
}
|
}
|
||||||
var self = new(src);
|
var self = new(src, diag, file);
|
||||||
run(&self, &out);
|
run(&self, &out);
|
||||||
ret .Ok(out);
|
ret .Ok(out);
|
||||||
}
|
}
|
||||||
@@ -435,7 +471,7 @@ fn view(text: []u8): ByteView {
|
|||||||
|
|
||||||
fn lex_ok(src: []u8): Vec {
|
fn lex_ok(src: []u8): Vec {
|
||||||
var out = Vec.blank(@sizeof(Token), @alignof(Token));
|
var out = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||||
match lex(view(src)) {
|
match lex(view(src), null, 0) {
|
||||||
.Ok(v) => { out = v; },
|
.Ok(v) => { out = v; },
|
||||||
.Err(error) => { @assert(false, "lex failed"); },
|
.Err(error) => { @assert(false, "lex failed"); },
|
||||||
}
|
}
|
||||||
@@ -536,9 +572,9 @@ fn test_basic() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_operators() {
|
fn test_operators() {
|
||||||
var v = lex_ok("<-> <- -> <=> <<= >>= ... .. . := == != < <= > >= && || & | ^ + - * / % ~ ! ? # @ $ , ; : ( ) [ ] { }");
|
var v = lex_ok("<-> <- -> <=> <<= << >>= ... .. . := == != < <= > >= && || & | ^ + - * / % ~ ! ? # @ $ , ; : ( ) [ ] { }");
|
||||||
defer { Vec.drop(&v); }
|
defer { Vec.drop(&v); }
|
||||||
@assert(v.len == 42, "operator token count");
|
@assert(v.len == 43, "operator token count");
|
||||||
|
|
||||||
expect_punct(at(&v, 0), .ArrowBoth);
|
expect_punct(at(&v, 0), .ArrowBoth);
|
||||||
expect_text(at(&v, 0), "<->");
|
expect_text(at(&v, 0), "<->");
|
||||||
@@ -546,43 +582,45 @@ fn test_operators() {
|
|||||||
expect_punct(at(&v, 2), .RightArrow);
|
expect_punct(at(&v, 2), .RightArrow);
|
||||||
expect_punct(at(&v, 3), .FatArrowBoth);
|
expect_punct(at(&v, 3), .FatArrowBoth);
|
||||||
expect_punct(at(&v, 4), .LShAssign);
|
expect_punct(at(&v, 4), .LShAssign);
|
||||||
expect_punct(at(&v, 5), .RShAssign);
|
expect_punct(at(&v, 5), .LSh);
|
||||||
expect_punct(at(&v, 6), .Ellipsis);
|
expect_text(at(&v, 5), "<<");
|
||||||
expect_punct(at(&v, 7), .Range);
|
expect_punct(at(&v, 6), .RShAssign);
|
||||||
expect_punct(at(&v, 8), .Dot);
|
expect_punct(at(&v, 7), .Ellipsis);
|
||||||
expect_punct(at(&v, 9), .ColonAssign);
|
expect_punct(at(&v, 8), .Range);
|
||||||
expect_punct(at(&v, 10), .Eq);
|
expect_punct(at(&v, 9), .Dot);
|
||||||
expect_punct(at(&v, 11), .Neq);
|
expect_punct(at(&v, 10), .ColonAssign);
|
||||||
expect_punct(at(&v, 12), .Lt);
|
expect_punct(at(&v, 11), .Eq);
|
||||||
expect_punct(at(&v, 13), .Le);
|
expect_punct(at(&v, 12), .Neq);
|
||||||
expect_punct(at(&v, 14), .Gt);
|
expect_punct(at(&v, 13), .Lt);
|
||||||
expect_punct(at(&v, 15), .Ge);
|
expect_punct(at(&v, 14), .Le);
|
||||||
expect_punct(at(&v, 16), .AndAnd);
|
expect_punct(at(&v, 15), .Gt);
|
||||||
expect_punct(at(&v, 17), .OrOr);
|
expect_punct(at(&v, 16), .Ge);
|
||||||
expect_punct(at(&v, 18), .And);
|
expect_punct(at(&v, 17), .AndAnd);
|
||||||
expect_punct(at(&v, 19), .Or);
|
expect_punct(at(&v, 18), .OrOr);
|
||||||
expect_punct(at(&v, 20), .Xor);
|
expect_punct(at(&v, 19), .And);
|
||||||
expect_punct(at(&v, 21), .Add);
|
expect_punct(at(&v, 20), .Or);
|
||||||
expect_punct(at(&v, 22), .Sub);
|
expect_punct(at(&v, 21), .Xor);
|
||||||
expect_punct(at(&v, 23), .Mul);
|
expect_punct(at(&v, 22), .Add);
|
||||||
expect_punct(at(&v, 24), .Div);
|
expect_punct(at(&v, 23), .Sub);
|
||||||
expect_punct(at(&v, 25), .Mod);
|
expect_punct(at(&v, 24), .Mul);
|
||||||
expect_punct(at(&v, 26), .BitNot);
|
expect_punct(at(&v, 25), .Div);
|
||||||
expect_punct(at(&v, 27), .Not);
|
expect_punct(at(&v, 26), .Mod);
|
||||||
expect_punct(at(&v, 28), .Cond);
|
expect_punct(at(&v, 27), .BitNot);
|
||||||
expect_punct(at(&v, 29), .Sharp);
|
expect_punct(at(&v, 28), .Not);
|
||||||
expect_punct(at(&v, 30), .At);
|
expect_punct(at(&v, 29), .Cond);
|
||||||
expect_punct(at(&v, 31), .Dollar);
|
expect_punct(at(&v, 30), .Sharp);
|
||||||
expect_punct(at(&v, 32), .Comma);
|
expect_punct(at(&v, 31), .At);
|
||||||
expect_punct(at(&v, 33), .Semicolon);
|
expect_punct(at(&v, 32), .Dollar);
|
||||||
expect_punct(at(&v, 34), .Colon);
|
expect_punct(at(&v, 33), .Comma);
|
||||||
expect_punct(at(&v, 35), .LParen);
|
expect_punct(at(&v, 34), .Semicolon);
|
||||||
expect_punct(at(&v, 36), .RParen);
|
expect_punct(at(&v, 35), .Colon);
|
||||||
expect_punct(at(&v, 37), .LBracket);
|
expect_punct(at(&v, 36), .LParen);
|
||||||
expect_punct(at(&v, 38), .RBracket);
|
expect_punct(at(&v, 37), .RParen);
|
||||||
expect_punct(at(&v, 39), .LBrace);
|
expect_punct(at(&v, 38), .LBracket);
|
||||||
expect_punct(at(&v, 40), .RBrace);
|
expect_punct(at(&v, 39), .RBracket);
|
||||||
expect_eof(at(&v, 41));
|
expect_punct(at(&v, 40), .LBrace);
|
||||||
|
expect_punct(at(&v, 41), .RBrace);
|
||||||
|
expect_eof(at(&v, 42));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_comments_and_literals() {
|
fn test_comments_and_literals() {
|
||||||
@@ -627,8 +665,29 @@ fn test_comments_and_literals() {
|
|||||||
expect_eof(at(&v, 12));
|
expect_eof(at(&v, 12));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 非法输入走 diag:仍产出 token 以便继续收集错误,但 diag 必须记录。
|
||||||
|
fn test_diag_errors() {
|
||||||
|
var diag = Diag.new(null);
|
||||||
|
defer { Diag.drop(&diag); }
|
||||||
|
|
||||||
|
match lex(view("'\\q' \"abc"), &diag, 0) {
|
||||||
|
.Ok(v) => { Vec.drop(&v); },
|
||||||
|
.Err(error) => { @assert(false, "lex should return tokens"); },
|
||||||
|
}
|
||||||
|
@assert(Diag.has_error(diag), "lex errors recorded");
|
||||||
|
|
||||||
|
var clean = Diag.new(null);
|
||||||
|
defer { Diag.drop(&clean); }
|
||||||
|
match lex(view("fn main() { ret 0; }"), &clean, 0) {
|
||||||
|
.Ok(v) => { Vec.drop(&v); },
|
||||||
|
.Err(error) => { @assert(false, "legal lex failed"); },
|
||||||
|
}
|
||||||
|
@assert(!Diag.has_error(clean), "legal input has no diagnostics");
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
test_basic();
|
test_basic();
|
||||||
test_operators();
|
test_operators();
|
||||||
test_comments_and_literals();
|
test_comments_and_literals();
|
||||||
|
test_diag_errors();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
// Expect retcode = 0
|
||||||
|
|
||||||
|
// splc1 (stage2 自举编译器入口)。
|
||||||
|
// 当前仅实现 `--dump lex <file> [out]`:读取源文件,词法分析,按与 stage1
|
||||||
|
// splc0 完全一致的格式打印 token 列表,供 diff 测试逐字节比对。
|
||||||
|
|
||||||
|
type .{ .ByteView } = @import("./utils/byte_view.spl");
|
||||||
|
type .{ .ByteBuffer } = @import("./utils/byte_buffer.spl");
|
||||||
|
type .{ .Vec } = @import("./utils/vec.spl");
|
||||||
|
type .{ .Token, .kind_name } = @import("./token.spl");
|
||||||
|
type .{ .Lexer } = @import("./lexer.spl");
|
||||||
|
type .{ .append_usize } = @import("./utils/fmt.spl");
|
||||||
|
type .{ .read_all, .print, .eprint } = @import("./utils/io.spl");
|
||||||
|
type .{ .set_new, .set_add, .set_drop } = @import("./utils/source.spl");
|
||||||
|
type Diag = @import("./diag.spl");
|
||||||
|
type Logger = @import("./utils/log.spl");
|
||||||
|
|
||||||
|
fn token_at(tokens: *Vec, index: usize): Token {
|
||||||
|
match Vec.get(tokens, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 追加成功返回 true;失败(仅 OOM)返回 false。
|
||||||
|
fn push(out: *ByteBuffer, bytes: []u8): bool {
|
||||||
|
match ByteBuffer.append(out, bytes) {
|
||||||
|
.Ok => { ret true; },
|
||||||
|
.Err(error) => { ret false; },
|
||||||
|
}
|
||||||
|
ret false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_byte(out: *ByteBuffer, byte: u8): bool {
|
||||||
|
match ByteBuffer.append_byte(out, byte) {
|
||||||
|
.Ok => { ret true; },
|
||||||
|
.Err(error) => { ret false; },
|
||||||
|
}
|
||||||
|
ret false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_usize(out: *ByteBuffer, value: usize): bool {
|
||||||
|
match append_usize(out, value, 10, false) {
|
||||||
|
.Ok => { ret true; },
|
||||||
|
.Err(error) => { ret false; },
|
||||||
|
}
|
||||||
|
ret false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出格式与 splc0.c 的 dump_tokens 一致:
|
||||||
|
// tokens got (N)\n
|
||||||
|
// [TOK_TYPE] <lexeme> (line:col)\n
|
||||||
|
fn dump_tokens(tokens: *Vec): bool {
|
||||||
|
var out = ByteBuffer.new();
|
||||||
|
defer { ByteBuffer.drop(&out); }
|
||||||
|
|
||||||
|
if !push(&out, "tokens got (") { ret false; }
|
||||||
|
if !push_usize(&out, tokens.len) { ret false; }
|
||||||
|
if !push(&out, ")\n") { ret false; }
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
while i < tokens.len {
|
||||||
|
var tok = token_at(tokens, i);
|
||||||
|
if !push(&out, "[") { ret false; }
|
||||||
|
if !push(&out, kind_name(tok.kind)) { ret false; }
|
||||||
|
if !push(&out, "] ") { ret false; }
|
||||||
|
if !push(&out, ByteView.as_slice(tok.text)) { ret false; }
|
||||||
|
if !push(&out, " (") { ret false; }
|
||||||
|
if !push_usize(&out, tok.line) { ret false; }
|
||||||
|
if !push_byte(&out, ':') { ret false; }
|
||||||
|
if !push_usize(&out, tok.col) { ret false; }
|
||||||
|
if !push(&out, ")\n") { ret false; }
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
match print(ByteBuffer.as_slice(out)) {
|
||||||
|
.Ok => { ret true; },
|
||||||
|
.Err(error) => { ret false; },
|
||||||
|
}
|
||||||
|
ret false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump_lex(log: *Logger, path: ByteView): i32 {
|
||||||
|
var content = ByteBuffer.new();
|
||||||
|
defer { ByteBuffer.drop(&content); }
|
||||||
|
match read_all(path, &content) {
|
||||||
|
.Ok => {},
|
||||||
|
.Err(error) => {
|
||||||
|
Logger.error(log, "cannot read input file");
|
||||||
|
ret 1;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = ByteView.from_slice(ByteBuffer.as_slice(content));
|
||||||
|
|
||||||
|
var sources = set_new();
|
||||||
|
defer { set_drop(&sources); }
|
||||||
|
match set_add(&sources, path, text) {
|
||||||
|
.Ok(index) => {},
|
||||||
|
.Err(error) => {
|
||||||
|
Logger.error(log, "cannot register source");
|
||||||
|
ret 1;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var diag = Diag.new(&sources);
|
||||||
|
defer { Diag.drop(&diag); }
|
||||||
|
|
||||||
|
var tokens = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||||
|
match Lexer.lex(text, &diag, 0) {
|
||||||
|
.Ok(v) => { tokens = v; },
|
||||||
|
.Err(error) => {
|
||||||
|
Logger.error(log, "lex failed");
|
||||||
|
ret 1;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer { Vec.drop(&tokens); }
|
||||||
|
|
||||||
|
// 非法输入:走诊断(stage1 在此处是宽容无视,stage2 显式报错)。
|
||||||
|
if Diag.has_error(diag) {
|
||||||
|
var rendered = ByteBuffer.new();
|
||||||
|
defer { ByteBuffer.drop(&rendered); }
|
||||||
|
match Diag.render_human(&diag, &rendered) {
|
||||||
|
.Ok => {},
|
||||||
|
.Err(error) => {},
|
||||||
|
}
|
||||||
|
eprint(ByteBuffer.as_slice(rendered));
|
||||||
|
ret 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dump_tokens(&tokens) {
|
||||||
|
Logger.fatal(log, "out of memory while dumping tokens");
|
||||||
|
ret 1;
|
||||||
|
}
|
||||||
|
ret 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(argc: i32, argv: **u8): i32 {
|
||||||
|
// 无参数:自测运行(`nob test stage2`)视为成功。
|
||||||
|
if argc <= 1 { ret 0; }
|
||||||
|
|
||||||
|
var log = Logger.new(ByteView.from_slice("splc1"));
|
||||||
|
|
||||||
|
if argc < 4 {
|
||||||
|
Logger.fatal(&log, "usage: splc1 --dump <lex> <file> [out]");
|
||||||
|
ret 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var arg1 = ByteView.from_cstr(argv[1]);
|
||||||
|
var mode = ByteView.from_cstr(argv[2]);
|
||||||
|
var path = ByteView.from_cstr(argv[3]);
|
||||||
|
|
||||||
|
if !ByteView.eq(arg1, ByteView.from_slice("--dump")) {
|
||||||
|
Logger.fatal(&log, "usage: splc1 --dump <lex> <file> [out]");
|
||||||
|
ret 1;
|
||||||
|
}
|
||||||
|
if !ByteView.eq(mode, ByteView.from_slice("lex")) {
|
||||||
|
Logger.fatal(&log, "dump mode not implemented");
|
||||||
|
ret 1;
|
||||||
|
}
|
||||||
|
ret dump_lex(&log, path);
|
||||||
|
}
|
||||||
@@ -108,6 +108,120 @@ fn keyword_of(text: ByteView): KeywordResult {
|
|||||||
ret .No;
|
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 {
|
fn view(text: []u8): ByteView {
|
||||||
@@ -183,8 +297,112 @@ fn test_trivia() {
|
|||||||
@assert(!is_trivia(.Eof), "eof 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() {
|
fn main() {
|
||||||
test_keywords();
|
test_keywords();
|
||||||
test_adt_payloads();
|
test_adt_payloads();
|
||||||
test_trivia();
|
test_trivia();
|
||||||
|
test_kind_names();
|
||||||
}
|
}
|
||||||
@@ -35,6 +35,14 @@ fn from_slice(bytes: []u8): ByteView {
|
|||||||
ret ByteView { .ptr = bytes.ptr, .len = bytes.len };
|
ret ByteView { .ptr = bytes.ptr, .len = bytes.len };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 以 NUL 结尾的 C 字符串转为视图;不复制、不拥有。null 规范化为空视图。
|
||||||
|
fn from_cstr(text: *u8): ByteView {
|
||||||
|
if text == null { ret empty(); }
|
||||||
|
var len: usize = 0;
|
||||||
|
while text[len] != 0 { len = len + 1; }
|
||||||
|
ret from_slice(.{ .ptr = text, .len = len });
|
||||||
|
}
|
||||||
|
|
||||||
fn as_slice(self: ByteView): []u8 {
|
fn as_slice(self: ByteView): []u8 {
|
||||||
ret .{ .ptr = self.ptr, .len = self.len };
|
ret .{ .ptr = self.ptr, .len = self.len };
|
||||||
}
|
}
|
||||||
@@ -213,6 +221,14 @@ fn test_construction_and_access() {
|
|||||||
|
|
||||||
var bytes = as_slice(text);
|
var bytes = as_slice(text);
|
||||||
@assert(bytes.ptr == text.ptr && bytes.len == text.len, "slice conversion borrows");
|
@assert(bytes.ptr == text.ptr && bytes.len == text.len, "slice conversion borrows");
|
||||||
|
|
||||||
|
var cstr_data: [4]u8 = .[104, 105, 0, 88];
|
||||||
|
var cstr = from_cstr(&cstr_data[0]);
|
||||||
|
@assert(cstr.len == 2 && cstr.ptr[0] == 104 && cstr.ptr[1] == 105,
|
||||||
|
"from_cstr stops at NUL");
|
||||||
|
@assert(from_cstr(&cstr_data[2]).len == 0, "from_cstr at NUL is empty");
|
||||||
|
var none = from_cstr(null);
|
||||||
|
@assert(none.ptr == null && none.len == 0, "from_cstr null normalized");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_compare_and_slice() {
|
fn test_compare_and_slice() {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||||
|
type .{ .ByteView } = @import("byte_view.spl");
|
||||||
|
type .{ .Value, .int, .uint, .char, .str, .addr } = @import("value.spl");
|
||||||
type .{ .BigInt, .Bits } = @import("mp.spl");
|
type .{ .BigInt, .Bits } = @import("mp.spl");
|
||||||
|
|
||||||
// 注意:radix 边界直接用字面量。stage1 刻意不做常量求值/折叠,
|
// 注意:radix 边界直接用字面量。stage1 刻意不做常量求值/折叠,
|
||||||
@@ -69,6 +71,158 @@ fn append_bool(out: *ByteBuffer, value: bool): Status {
|
|||||||
ret ByteBuffer.append(out, "false");
|
ret ByteBuffer.append(out, "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 显式装箱值格式化 ----
|
||||||
|
|
||||||
|
// Int/Uint 落到整数;signed 仅对 Int 的十进制生效,其余按无符号位模式输出。
|
||||||
|
fn append_value_int(out: *ByteBuffer, value: Value, radix: u8, uppercase: bool,
|
||||||
|
signed: bool): Status {
|
||||||
|
match value {
|
||||||
|
.Int(v) => {
|
||||||
|
if signed && radix == 10 { ret append_i64(out, v, radix, uppercase); }
|
||||||
|
ret append_u64(out, v as u64, radix, uppercase);
|
||||||
|
},
|
||||||
|
.Uint(v) => { ret append_u64(out, v, radix, uppercase); },
|
||||||
|
_ => { ret .Err(make(.InvalidFormat)); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_value_char(out: *ByteBuffer, value: Value): Status {
|
||||||
|
match value {
|
||||||
|
.Char(c) => { ret ByteBuffer.append_byte(out, c); },
|
||||||
|
_ => { ret .Err(make(.InvalidFormat)); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_value_str(out: *ByteBuffer, value: Value): Status {
|
||||||
|
match value {
|
||||||
|
.Str(view) => { ret ByteBuffer.append(out, ByteView.as_slice(view)); },
|
||||||
|
_ => { ret .Err(make(.InvalidFormat)); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_value_ptr(out: *ByteBuffer, value: Value): Status {
|
||||||
|
match value {
|
||||||
|
.Ptr(p) => {
|
||||||
|
match ByteBuffer.append(out, "0x") {
|
||||||
|
.Ok => {},
|
||||||
|
.Err(error) => { ret .Err(error); },
|
||||||
|
}
|
||||||
|
ret append_u64(out, p as usize, 16, false);
|
||||||
|
},
|
||||||
|
_ => { ret .Err(make(.InvalidFormat)); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {} 自动格式化:按 Value 变体选择默认表示。
|
||||||
|
fn append_value_auto(out: *ByteBuffer, value: Value): Status {
|
||||||
|
match value {
|
||||||
|
.Int(v) => { ret append_i64(out, v, 10, false); },
|
||||||
|
.Uint(v) => { ret append_u64(out, v, 10, false); },
|
||||||
|
.Bool(v) => { ret append_bool(out, v); },
|
||||||
|
.Char(c) => { ret ByteBuffer.append_byte(out, c); },
|
||||||
|
.Str(view) => { ret ByteBuffer.append(out, ByteView.as_slice(view)); },
|
||||||
|
.Ptr(p) => {
|
||||||
|
match ByteBuffer.append(out, "0x") {
|
||||||
|
.Ok => {},
|
||||||
|
.Err(error) => { ret .Err(error); },
|
||||||
|
}
|
||||||
|
ret append_u64(out, p as usize, 16, false);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ret .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// {} 风格:{} 自动;{:d} {:i} {:u} {:x} {:X} {:o} {:b} {:c} {:s} {:p} 指定。
|
||||||
|
// {{ 与 }} 输出字面量括号。参数按序从 args 消费;数量不足、类型不符、未知转换
|
||||||
|
// 或括号不配返回 Err(.InvalidFormat)。失败不产生部分输出:先在临时缓冲完成。
|
||||||
|
fn format(out: *ByteBuffer, fmt: []u8, args: []Value): Status {
|
||||||
|
var temp = ByteBuffer.new();
|
||||||
|
defer { ByteBuffer.drop(&temp); }
|
||||||
|
|
||||||
|
var argi: usize = 0;
|
||||||
|
var i: usize = 0;
|
||||||
|
while i < fmt.len {
|
||||||
|
var c = fmt.ptr[i];
|
||||||
|
|
||||||
|
if c == '{' {
|
||||||
|
if i + 1 < fmt.len && fmt.ptr[i + 1] == '{' {
|
||||||
|
match ByteBuffer.append_byte(&temp, '{') {
|
||||||
|
.Ok => {},
|
||||||
|
.Err(error) => { ret .Err(error); },
|
||||||
|
}
|
||||||
|
i = i + 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var j = i + 1;
|
||||||
|
if j < fmt.len && fmt.ptr[j] == ':' { j = j + 1; }
|
||||||
|
var has_spec = false;
|
||||||
|
var spec: u8 = 0;
|
||||||
|
if j < fmt.len && fmt.ptr[j] != '}' {
|
||||||
|
has_spec = true;
|
||||||
|
spec = fmt.ptr[j];
|
||||||
|
j = j + 1;
|
||||||
|
}
|
||||||
|
if j >= fmt.len || fmt.ptr[j] != '}' { ret .Err(make(.InvalidFormat)); }
|
||||||
|
i = j + 1;
|
||||||
|
|
||||||
|
if argi >= args.len { ret .Err(make(.InvalidFormat)); }
|
||||||
|
var value = args.ptr[argi];
|
||||||
|
argi = argi + 1;
|
||||||
|
|
||||||
|
var step: Status = .Ok;
|
||||||
|
if !has_spec {
|
||||||
|
step = append_value_auto(&temp, value);
|
||||||
|
} else if spec == 'd' || spec == 'i' {
|
||||||
|
step = append_value_int(&temp, value, 10, false, true);
|
||||||
|
} else if spec == 'u' {
|
||||||
|
step = append_value_int(&temp, value, 10, false, false);
|
||||||
|
} else if spec == 'x' {
|
||||||
|
step = append_value_int(&temp, value, 16, false, false);
|
||||||
|
} else if spec == 'X' {
|
||||||
|
step = append_value_int(&temp, value, 16, true, false);
|
||||||
|
} else if spec == 'o' {
|
||||||
|
step = append_value_int(&temp, value, 8, false, false);
|
||||||
|
} else if spec == 'b' {
|
||||||
|
step = append_value_int(&temp, value, 2, false, false);
|
||||||
|
} else if spec == 'c' {
|
||||||
|
step = append_value_char(&temp, value);
|
||||||
|
} else if spec == 's' {
|
||||||
|
step = append_value_str(&temp, value);
|
||||||
|
} else if spec == 'p' {
|
||||||
|
step = append_value_ptr(&temp, value);
|
||||||
|
} else {
|
||||||
|
ret .Err(make(.InvalidFormat));
|
||||||
|
}
|
||||||
|
match step {
|
||||||
|
.Ok => {},
|
||||||
|
.Err(error) => { ret .Err(error); },
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == '}' {
|
||||||
|
if i + 1 < fmt.len && fmt.ptr[i + 1] == '}' {
|
||||||
|
match ByteBuffer.append_byte(&temp, '}') {
|
||||||
|
.Ok => {},
|
||||||
|
.Err(error) => { ret .Err(error); },
|
||||||
|
}
|
||||||
|
i = i + 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ret .Err(make(.InvalidFormat));
|
||||||
|
}
|
||||||
|
|
||||||
|
match ByteBuffer.append_byte(&temp, c) {
|
||||||
|
.Ok => {},
|
||||||
|
.Err(error) => { ret .Err(error); },
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret ByteBuffer.append(out, ByteBuffer.as_slice(temp));
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 测试辅助 ----
|
// ---- 测试辅助 ----
|
||||||
|
|
||||||
fn expect_ok(result: Status) {
|
fn expect_ok(result: Status) {
|
||||||
@@ -262,10 +416,96 @@ fn test_composition_and_growth() {
|
|||||||
@assert(s.ptr[1] == ',', "0 then comma");
|
@assert(s.ptr[1] == ',', "0 then comma");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn expect_format(fmt: []u8, args: []Value, expected: []u8) {
|
||||||
|
var b = ByteBuffer.new();
|
||||||
|
defer { ByteBuffer.drop(&b); }
|
||||||
|
expect_ok(format(&b, fmt, args));
|
||||||
|
expect_text(&b, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_invalid_format(fmt: []u8, args: []Value) {
|
||||||
|
var b = ByteBuffer.new();
|
||||||
|
defer { ByteBuffer.drop(&b); }
|
||||||
|
match format(&b, fmt, args) {
|
||||||
|
.Ok => { @assert(false, "expected invalid format"); },
|
||||||
|
.Err(error) => {
|
||||||
|
match error.kind {
|
||||||
|
.InvalidFormat => {},
|
||||||
|
_ => { @assert(false, "expected InvalidFormat kind"); },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_format_specs() {
|
||||||
|
var one: [1]Value = .[int(-5)];
|
||||||
|
expect_format("{}", one[0..1], "-5");
|
||||||
|
expect_format("{d}", one[0..1], "-5");
|
||||||
|
expect_format("{i}", one[0..1], "-5");
|
||||||
|
|
||||||
|
var u: [1]Value = .[uint(7)];
|
||||||
|
expect_format("{}", u[0..1], "7");
|
||||||
|
expect_format("{u}", u[0..1], "7");
|
||||||
|
expect_format("{x}", u[0..1], "7");
|
||||||
|
expect_format("{o}", u[0..1], "7");
|
||||||
|
expect_format("{b}", u[0..1], "111");
|
||||||
|
|
||||||
|
var hex: [1]Value = .[uint(255)];
|
||||||
|
expect_format("{x}", hex[0..1], "ff");
|
||||||
|
expect_format("{X}", hex[0..1], "FF");
|
||||||
|
|
||||||
|
var oct: [1]Value = .[uint(8)];
|
||||||
|
expect_format("{o}", oct[0..1], "10");
|
||||||
|
|
||||||
|
var bits: [1]Value = .[uint(5)];
|
||||||
|
expect_format("{b}", bits[0..1], "101");
|
||||||
|
|
||||||
|
var ch: [1]Value = .[char('A')];
|
||||||
|
expect_format("{}", ch[0..1], "A");
|
||||||
|
expect_format("{c}", ch[0..1], "A");
|
||||||
|
|
||||||
|
var text: [1]Value = .[str("hi")];
|
||||||
|
expect_format("{}", text[0..1], "hi");
|
||||||
|
expect_format("{s}", text[0..1], "hi");
|
||||||
|
|
||||||
|
var nul: [1]Value = .[addr(null)];
|
||||||
|
expect_format("{p}", nul[0..1], "0x0");
|
||||||
|
|
||||||
|
var none: [1]Value = .[int(0)];
|
||||||
|
expect_format("100%", none[0..0], "100%");
|
||||||
|
expect_format("{{}}", none[0..0], "{}");
|
||||||
|
expect_format("plain", none[0..0], "plain");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_format_mixed_and_failure() {
|
||||||
|
var args: [3]Value = .[int(42), str("x"), char('!')];
|
||||||
|
expect_format("n={} s={} c={}", args[0..3], "n=42 s=x c=!");
|
||||||
|
expect_format("n={} s={s} c={c}", args[0..3], "n=42 s=x c=!");
|
||||||
|
|
||||||
|
// 失败不改动输出,且不产生部分结果:前两个转换成功、第三个参数不足。
|
||||||
|
var b = ByteBuffer.new();
|
||||||
|
defer { ByteBuffer.drop(&b); }
|
||||||
|
expect_ok(ByteBuffer.append(&b, "prefix"));
|
||||||
|
var two: [2]Value = .[int(1), int(2)];
|
||||||
|
expect_invalid_format("a={} b={} c={}", two[0..2]);
|
||||||
|
expect_text(&b, "prefix");
|
||||||
|
|
||||||
|
var only_int: [1]Value = .[int(1)];
|
||||||
|
expect_invalid_format("{} {}", only_int[0..1]);
|
||||||
|
expect_invalid_format("{}", only_int[0..0]);
|
||||||
|
expect_invalid_format("trailing {", only_int[0..1]);
|
||||||
|
expect_invalid_format("dangling }", only_int[0..1]);
|
||||||
|
expect_invalid_format("{q}", only_int[0..1]);
|
||||||
|
expect_invalid_format("{s}", only_int[0..1]);
|
||||||
|
expect_invalid_format("{c}", only_int[0..1]);
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
test_unsigned();
|
test_unsigned();
|
||||||
test_signed();
|
test_signed();
|
||||||
test_usize_bool_and_errors();
|
test_usize_bool_and_errors();
|
||||||
test_mpa_integer_formatting();
|
test_mpa_integer_formatting();
|
||||||
test_composition_and_growth();
|
test_composition_and_growth();
|
||||||
|
test_format_specs();
|
||||||
|
test_format_mixed_and_failure();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// Expect retcode = 0
|
||||||
|
|
||||||
|
// 编译器自身日志(区别于 diag:diag 负责带源码位置的诊断)。
|
||||||
|
// 输出到 stderr,格式稳定:[LEVEL] name: message\n。
|
||||||
|
// 消息为借用;Logger 不拥有 name。fatal 打印后终止进程(退出码 1)。
|
||||||
|
// write 返回 Status;OOM 归类为 .Overflow(无独立内存耗尽错误码)。
|
||||||
|
|
||||||
|
type .{ .Status, .make } = @import("error.spl");
|
||||||
|
type .{ .ByteView } = @import("byte_view.spl");
|
||||||
|
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||||
|
type .{ .format } = @import("fmt.spl");
|
||||||
|
type .{ .Value, .int, .str } = @import("value.spl");
|
||||||
|
type .{ .eprint } = @import("io.spl");
|
||||||
|
type Exit = @import("../sys/exit.spl");
|
||||||
|
|
||||||
|
type Level = enum { Debug, Info, Warn, Error, Fatal }
|
||||||
|
|
||||||
|
fn level_name(level: Level): []u8 {
|
||||||
|
match level {
|
||||||
|
.Debug => { ret "DEBUG"; },
|
||||||
|
.Info => { ret "INFO "; },
|
||||||
|
.Warn => { ret "WARN "; },
|
||||||
|
.Error => { ret "ERROR"; },
|
||||||
|
.Fatal => { ret "FATAL"; },
|
||||||
|
}
|
||||||
|
ret "NOTSET";
|
||||||
|
}
|
||||||
|
|
||||||
|
type Logger = @self;
|
||||||
|
|
||||||
|
name: ByteView,
|
||||||
|
min_level: Level,
|
||||||
|
|
||||||
|
fn new(name: ByteView): Logger {
|
||||||
|
ret Logger { .name = name, .min_level = .Debug };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_level(self: *Logger, level: Level) {
|
||||||
|
self.min_level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enabled(self: Logger, level: Level): bool {
|
||||||
|
ret @enum2int(level) >= @enum2int(self.min_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 追加成功返回 true;失败(仅 OOM)返回 false。
|
||||||
|
fn push(line: *ByteBuffer, text: []u8): bool {
|
||||||
|
match ByteBuffer.append(line, text) {
|
||||||
|
.Ok => { ret true; },
|
||||||
|
.Err(error) => { ret false; },
|
||||||
|
}
|
||||||
|
ret false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 低于 min_level 的日志不写出,直接成功。
|
||||||
|
fn write(self: *Logger, level: Level, message: []u8): Status {
|
||||||
|
if !enabled(self.*, level) { ret .Ok; }
|
||||||
|
var line = ByteBuffer.new();
|
||||||
|
defer { ByteBuffer.drop(&line); }
|
||||||
|
if !push(&line, "[") { ret .Err(make(.Overflow)); }
|
||||||
|
if !push(&line, level_name(level)) { ret .Err(make(.Overflow)); }
|
||||||
|
if !push(&line, "] ") { ret .Err(make(.Overflow)); }
|
||||||
|
if !push(&line, ByteView.as_slice(self.name)) { ret .Err(make(.Overflow)); }
|
||||||
|
if !push(&line, ": ") { ret .Err(make(.Overflow)); }
|
||||||
|
if !push(&line, message) { ret .Err(make(.Overflow)); }
|
||||||
|
if !push(&line, "\n") { ret .Err(make(.Overflow)); }
|
||||||
|
ret eprint(ByteBuffer.as_slice(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug(self: *Logger, message: []u8): Status {
|
||||||
|
ret write(self, .Debug, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info(self: *Logger, message: []u8): Status {
|
||||||
|
ret write(self, .Info, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warn(self: *Logger, message: []u8): Status {
|
||||||
|
ret write(self, .Warn, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error(self: *Logger, message: []u8): Status {
|
||||||
|
ret write(self, .Error, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尽力写出后终止;写失败无法挽回,直接退出。
|
||||||
|
fn fatal(self: *Logger, message: []u8) {
|
||||||
|
write(self, .Fatal, message);
|
||||||
|
Exit.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 格式化入口(显式装箱,见 value.spl / fmt.format)----
|
||||||
|
|
||||||
|
// 先格式化到临时缓冲;格式错误在写出前返回 Err(.InvalidFormat)。
|
||||||
|
fn logf(self: *Logger, level: Level, fmt: []u8, args: []Value): Status {
|
||||||
|
if !enabled(self.*, level) { ret .Ok; }
|
||||||
|
var message = ByteBuffer.new();
|
||||||
|
defer { ByteBuffer.drop(&message); }
|
||||||
|
match format(&message, fmt, args) {
|
||||||
|
.Ok => {},
|
||||||
|
.Err(error) => { ret .Err(error); },
|
||||||
|
}
|
||||||
|
ret write(self, level, ByteBuffer.as_slice(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debugf(self: *Logger, fmt: []u8, args: []Value): Status {
|
||||||
|
ret logf(self, .Debug, fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn infof(self: *Logger, fmt: []u8, args: []Value): Status {
|
||||||
|
ret logf(self, .Info, fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warnf(self: *Logger, fmt: []u8, args: []Value): Status {
|
||||||
|
ret logf(self, .Warn, fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn errorf(self: *Logger, fmt: []u8, args: []Value): Status {
|
||||||
|
ret logf(self, .Error, fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化后终止;格式化失败也要退出(避免吞掉致命错误)。
|
||||||
|
fn fatalf(self: *Logger, fmt: []u8, args: []Value) {
|
||||||
|
logf(self, .Fatal, fmt, args);
|
||||||
|
Exit.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 测试 ----
|
||||||
|
|
||||||
|
fn expect_name(level: Level, expected: []u8) {
|
||||||
|
@assert(ByteView.eq(ByteView.from_slice(level_name(level)),
|
||||||
|
ByteView.from_slice(expected)),
|
||||||
|
"level name");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_ok(result: Status) {
|
||||||
|
match result {
|
||||||
|
.Ok => {},
|
||||||
|
.Err(error) => { @assert(false, "unexpected log error"); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_level_names() {
|
||||||
|
expect_name(.Debug, "DEBUG");
|
||||||
|
expect_name(.Info, "INFO ");
|
||||||
|
expect_name(.Warn, "WARN ");
|
||||||
|
expect_name(.Error, "ERROR");
|
||||||
|
expect_name(.Fatal, "FATAL");
|
||||||
|
|
||||||
|
var debug: Level = .Debug;
|
||||||
|
var info: Level = .Info;
|
||||||
|
var warn: Level = .Warn;
|
||||||
|
var err: Level = .Error;
|
||||||
|
var fatal: Level = .Fatal;
|
||||||
|
@assert(@enum2int(fatal) > @enum2int(err), "fatal above error");
|
||||||
|
@assert(@enum2int(err) > @enum2int(warn), "error above warn");
|
||||||
|
@assert(@enum2int(warn) > @enum2int(info), "warn above info");
|
||||||
|
@assert(@enum2int(info) > @enum2int(debug), "info above debug");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_filter_and_write() {
|
||||||
|
var log = new(ByteView.from_slice("splc1"));
|
||||||
|
@assert(enabled(log, .Debug), "default shows debug");
|
||||||
|
@assert(enabled(log, .Fatal), "default shows fatal");
|
||||||
|
|
||||||
|
set_level(&log, .Warn);
|
||||||
|
@assert(!enabled(log, .Debug), "filtered debug");
|
||||||
|
@assert(!enabled(log, .Info), "filtered info");
|
||||||
|
@assert(enabled(log, .Warn), "warn passes");
|
||||||
|
@assert(enabled(log, .Error), "error passes");
|
||||||
|
|
||||||
|
// 被过滤的写入不产生输出,且返回成功。
|
||||||
|
expect_ok(debug(&log, "hidden"));
|
||||||
|
expect_ok(write(&log, .Info, "hidden too"));
|
||||||
|
expect_ok(error(&log, "visible"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_logf() {
|
||||||
|
var log = new(ByteView.from_slice("splc1"));
|
||||||
|
set_level(&log, .Error);
|
||||||
|
|
||||||
|
var args: [2]Value = .[int(3), str("x")];
|
||||||
|
|
||||||
|
// 被过滤的级别不写出,直接成功。
|
||||||
|
expect_ok(infof(&log, "n={} s={}", args[0..2]));
|
||||||
|
// 通过级别正常写出。
|
||||||
|
expect_ok(errorf(&log, "n={} s={}", args[0..2]));
|
||||||
|
|
||||||
|
// 格式化失败向上传播,且不产出消息。
|
||||||
|
match errorf(&log, "{} {}", args[0..1]) {
|
||||||
|
.Ok => { @assert(false, "expected invalid format"); },
|
||||||
|
.Err(error) => {
|
||||||
|
match error.kind {
|
||||||
|
.InvalidFormat => {},
|
||||||
|
_ => { @assert(false, "expected InvalidFormat"); },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test_level_names();
|
||||||
|
test_filter_and_write();
|
||||||
|
test_logf();
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
// Expect retcode = 0
|
||||||
|
|
||||||
|
// 显式装箱值:运行期格式化用的最小 ADT。调用方显式构造(如 Value.int(x)),
|
||||||
|
// 库内用 match 分派。不做隐式装箱、不带运行期类型系统。
|
||||||
|
// Str 借用 ByteView,不拥有存储;Ptr 是类型擦除指针。
|
||||||
|
|
||||||
|
type .{ .ByteView } = @import("byte_view.spl");
|
||||||
|
|
||||||
|
type Value = enum {
|
||||||
|
Int: i64,
|
||||||
|
Uint: u64,
|
||||||
|
Bool: bool,
|
||||||
|
Char: u8,
|
||||||
|
Str: ByteView,
|
||||||
|
Ptr: *any,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn int(value: i64): Value {
|
||||||
|
ret .Int(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uint(value: u64): Value {
|
||||||
|
ret .Uint(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boolean(value: bool): Value {
|
||||||
|
ret .Bool(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn char(value: u8): Value {
|
||||||
|
ret .Char(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn str(bytes: []u8): Value {
|
||||||
|
ret .Str(ByteView.from_slice(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn str_view(view: ByteView): Value {
|
||||||
|
ret .Str(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addr(value: *any): Value {
|
||||||
|
ret .Ptr(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 测试 ----
|
||||||
|
|
||||||
|
fn test_int_and_uint() {
|
||||||
|
match int(-5) {
|
||||||
|
.Int(v) => { @assert(v == -5, "int payload"); },
|
||||||
|
_ => { @assert(false, "expected int"); },
|
||||||
|
}
|
||||||
|
match uint(5) {
|
||||||
|
.Uint(v) => { @assert(v == 5, "uint payload"); },
|
||||||
|
_ => { @assert(false, "expected uint"); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalars() {
|
||||||
|
match boolean(true) {
|
||||||
|
.Bool(v) => { @assert(v, "bool payload"); },
|
||||||
|
_ => { @assert(false, "expected bool"); },
|
||||||
|
}
|
||||||
|
match char('A') {
|
||||||
|
.Char(v) => { @assert(v == 'A', "char payload"); },
|
||||||
|
_ => { @assert(false, "expected char"); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_borrowed_str() {
|
||||||
|
var text: [3]u8 = .[104, 105, 0];
|
||||||
|
var s = str(text[0..2]);
|
||||||
|
match s {
|
||||||
|
.Str(view) => {
|
||||||
|
@assert(view.len == 2 && view.ptr[0] == 104 && view.ptr[1] == 105,
|
||||||
|
"str payload borrows");
|
||||||
|
},
|
||||||
|
_ => { @assert(false, "expected str"); },
|
||||||
|
}
|
||||||
|
@assert(text[0] == 104, "source untouched");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_ptr() {
|
||||||
|
var x: i32 = 7;
|
||||||
|
match addr(&x as *any) {
|
||||||
|
.Ptr(p) => { @assert((p as *i32).* == 7, "ptr payload"); },
|
||||||
|
_ => { @assert(false, "expected ptr"); },
|
||||||
|
}
|
||||||
|
match addr(null) {
|
||||||
|
.Ptr(p) => { @assert(p == null, "null ptr payload"); },
|
||||||
|
_ => { @assert(false, "expected ptr"); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test_int_and_uint();
|
||||||
|
test_scalars();
|
||||||
|
test_borrowed_str();
|
||||||
|
test_ptr();
|
||||||
|
}
|
||||||
Reference in new issue
Block a user