From b3e38ba7e4cf34d14d53df97bb5fe30815ba94e1 Mon Sep 17 00:00:00 2001 From: zzy <2450266535@qq.com> Date: Fri, 18 Sep 2026 10:10:53 +0800 Subject: [PATCH] =?UTF-8?q?stage2=20=E6=9B=B4=E5=A4=9A=E7=9A=84=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E5=BA=93=20stage1=20=E4=BF=AE=E5=A4=8D=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- stage1/spl_ast2ir.c | 79 ++- stage1/spl_lexer.c | 3 +- stage1/spl_sema.c | 14 + stage2/diag.spl | 791 ++++++++++++++++++++++ stage2/lexer.spl | 634 +++++++++++++++++ stage2/sys/exit.spl | 2 +- stage2/sys/file.spl | 6 +- stage2/sys/mem.spl | 2 +- stage2/token.spl | 190 ++++++ stage2/utils/arena.spl | 105 ++- stage2/utils/ascii.spl | 1 + stage2/utils/big_int.spl | 63 +- stage2/utils/byte_buffer.spl | 12 +- stage2/utils/byte_view.spl | 1 + stage2/utils/diag.spl | 243 ------- stage2/utils/error.spl | 1 + stage2/utils/fmt.spl | 13 +- stage2/utils/hash.spl | 13 +- stage2/utils/io.spl | 1 + stage2/utils/json.spl | 98 ++- stage2/utils/{frame.spl => lsp_frame.spl} | 41 +- stage2/utils/map.spl | 183 +++-- stage2/utils/memory.spl | 24 + stage2/utils/path.spl | 90 ++- stage2/utils/source.spl | 5 +- stage2/utils/str.spl | 18 +- stage2/utils/utf8.spl | 137 ++-- stage2/utils/vec.spl | 27 +- 28 files changed, 2274 insertions(+), 523 deletions(-) create mode 100644 stage2/diag.spl create mode 100644 stage2/lexer.spl create mode 100644 stage2/token.spl delete mode 100644 stage2/utils/diag.spl rename stage2/utils/{frame.spl => lsp_frame.spl} (82%) diff --git a/stage1/spl_ast2ir.c b/stage1/spl_ast2ir.c index 29ae373..079a451 100644 --- a/stage1/spl_ast2ir.c +++ b/stage1/spl_ast2ir.c @@ -691,6 +691,69 @@ static spl_ir_node_ref_t emit_cast(spl_ast2ir_t *a, spl_ast_node_t *n) { return ast2ir_emit_cast_builtin(a, n, in, ft, tt, v); } +/* 字段访问的基对象是 rvalue(如函数调用结果)时的回退路径: + * 先求值整个聚合值,再用 agg.extract 取出字段值(无需左值/临时槽)。 */ +static spl_ir_node_ref_t ast2ir_field_from_value(spl_ast2ir_t *a, spl_ast_node_t *n) { + spl_ast_node_t *obj = spl_ast_node(a->ast, n->field.obj); + if (!obj) + return 0; + spl_lang_type_id_t obj_lang = node_lang(a, obj); + spl_lang_type_node_t *obj_ln = spl_lang_type_node(a->sema->type, obj_lang); + if (!obj_ln) + return 0; + + spl_type_id_t agg_ir = to_ir_type(a, obj_lang); + if (!agg_ir) + return 0; + usize idx = 0; + if (!spl_type_agg_member_idx(&a->sema->type->ir, agg_ir, n->field.field, &idx)) + return 0; + + usize field_idx = idx; + if (obj_ln->kind == SPL_LANG_TYPE_ENUM) + field_idx = idx + 1; /* enum: field 0 是 tag */ + + spl_type_id_t field_ir = 0; + spl_type_node_t *agg_tn = spl_type_node(&a->sema->type->ir, agg_ir); + if (agg_tn && field_idx < vec_size(agg_tn->agg_type.variants)) + field_ir = vec_at(agg_tn->agg_type.variants, field_idx).type_id; + + spl_ir_node_ref_t val = emit_expr(a, n->field.obj); + if (!val) + return 0; + return spl_ir_builder_agg_extract(a->ir, agg_ir, field_ir, field_idx, val); +} + +/* 限定枚举变体 Type.Variant:产出枚举常量(tag=变体下标;无载荷变体不带 payload)。 */ +static spl_ir_node_ref_t ast2ir_enum_variant(spl_ast2ir_t *a, spl_ast_node_t *n) { + spl_type_id_t t = node_ir(a, n); + if (!t) + return 0; + spl_type_node_t *tn = spl_type_node(&a->sema->type->ir, t); + if (!tn) + return 0; + + usize vidx = 0; + if (!spl_type_agg_member_idx(&a->sema->type->ir, t, n->field.field, &vidx)) + return 0; + + spl_type_id_t tag_ir = tn->agg_type.tag_type; + spl_ir_node_ref_t eptr = spl_ir_builder_mem_alloca(a->ir, t, const_usize(a, 1)); + spl_ir_node_ref_t tptr = spl_ir_builder_mem_field(a->ir, t, eptr, SPL_ENUM_TAG_FIELD_IDX); + spl_ir_node_ref_t tag_val = spl_ir_builder_type_const_int(a->ir, tag_ir, vidx); + spl_ir_builder_mem_store(a->ir, tag_ir, tptr, tag_val); + + if (vidx < vec_size(tn->agg_type.variants)) { + spl_type_id_t payload_ir = vec_at(tn->agg_type.variants, vidx).type_id; + if (payload_ir) { + spl_ir_node_ref_t pptr = spl_ir_builder_mem_field(a->ir, t, eptr, vidx + 1); + spl_ir_node_ref_t zero = emit_zero_value(a, payload_ir); + spl_ir_builder_mem_store(a->ir, payload_ir, pptr, zero); + } + } + return eptr; +} + static spl_ir_node_ref_t emit_expr(spl_ast2ir_t *a, spl_ast_node_ref_t ref) { spl_ir_node_ref_t val = emit_expr_raw(a, ref); if (!val) @@ -1022,10 +1085,20 @@ static spl_ir_node_ref_t emit_expr_raw(spl_ast2ir_t *a, spl_ast_node_ref_t ref) } } + /* 限定枚举变体 Type.Variant:基对象是聚合类型名且为 enum。 */ + spl_ast_node_t *objn = spl_ast_node(a->ast, n->field.obj); + if (objn && objn->kind == SPL_AST_EXPR_IDENT) { + spl_def_node_t *od = spl_def_node(a->sema->type, objn->resolved_def_id); + spl_lang_type_id_t et = node_lang(a, objn); + spl_lang_type_node_t *etn = spl_lang_type_node(a->sema->type, et); + if (od && od->kind == SPL_DEF_AGG && etn && etn->kind == SPL_LANG_TYPE_ENUM) + return ast2ir_enum_variant(a, n); + } + spl_ir_node_ref_t ptr = emit_lvalue(a, ref); - if (!ptr) - return 0; - return spl_ir_builder_mem_load(a->ir, node_ir(a, n), ptr); + if (ptr) + return spl_ir_builder_mem_load(a->ir, node_ir(a, n), ptr); + return ast2ir_field_from_value(a, n); } case SPL_AST_EXPR_INDEX: { spl_ir_node_ref_t ptr = emit_lvalue(a, ref); diff --git a/stage1/spl_lexer.c b/stage1/spl_lexer.c index 88be674..0493ddf 100644 --- a/stage1/spl_lexer.c +++ b/stage1/spl_lexer.c @@ -411,7 +411,7 @@ spl_tok_vec_t spl_lex(const char *source, const char *fname) { } /* Three-char operators first */ - TRY_OP3('<', '-', '>', TOK_ARROW_BOTH, TOK_RIGHT_ARROW, TOK_LT) + TRY_OP3('<', '-', '>', TOK_ARROW_BOTH, TOK_LEFT_ARROW, TOK_LT) TRY_OP3('<', '=', '>', TOK_FAT_ARROW_BOTH, TOK_LE, TOK_LT) TRY_OP3('<', '<', '=', TOK_ASSIGN_L_SH, TOK_L_SH, TOK_LT) TRY_OP3('>', '>', '=', TOK_ASSIGN_R_SH, TOK_R_SH, TOK_GT) @@ -433,7 +433,6 @@ spl_tok_vec_t spl_lex(const char *source, const char *fname) { TRY_OP2('&', '=', TOK_ASSIGN_AND, TOK_AND) TRY_OP2('|', '=', TOK_ASSIGN_OR, TOK_OR) TRY_OP2('^', '=', TOK_ASSIGN_XOR, TOK_XOR) - TRY_OP2('<', '-', TOK_LEFT_ARROW, TOK_LT) TRY_OP2('-', '>', TOK_RIGHT_ARROW, TOK_SUB) TRY_OP2(':', '=', TOK_COLON_ASSIGN, TOK_COLON) diff --git a/stage1/spl_sema.c b/stage1/spl_sema.c index 20ed1d1..9759792 100644 --- a/stage1/spl_sema.c +++ b/stage1/spl_sema.c @@ -2024,6 +2024,20 @@ static spl_lang_type_id_t sema_infer(spl_sema_t *sema, spl_ast_node_ref_t ref) { if (!obj) return 0; + /* 限定枚举变体 Type.Variant:基对象是聚合类型名且类型为 enum。 */ + spl_ast_node_t *objn = spl_ast_node(sema->ast, n->field.obj); + if (objn && objn->kind == SPL_AST_EXPR_IDENT) { + spl_def_node_t *od = spl_def_node(sema->type, objn->resolved_def_id); + if (od && od->kind == SPL_DEF_AGG && obj->kind == SPL_LANG_TYPE_ENUM) { + spl_type_id_t eir = spl_lang_type_get_ir_type(sema->type, obj_tid); + usize vidx = 0; + if (eir && spl_type_agg_member_idx(&sema->type->ir, eir, n->field.field, &vidx)) { + n->resolved_lang_tid = obj_tid; + return obj_tid; + } + } + } + spl_intrin_t in = spl_intrin_lookup_field(obj->kind, n->field.field); if (in != SPL_INTRIN_NONE) { spl_lang_type_id_t t = sema_intrin_field(sema, n, in); diff --git a/stage2/diag.spl b/stage2/diag.spl new file mode 100644 index 0000000..cdf49db --- /dev/null +++ b/stage2/diag.spl @@ -0,0 +1,791 @@ +// Expect retcode = 0 + +// 丰富诊断(参考 rustc / Clang / Swift 的设计,不照搬 stage1 C 实现): +// Severity Note/Help/Warning/Error/Fatal +// Code 固定语义枚举 + 稳定 E 码;集中管理,利于 diff +// Span 字节区间 {file,start,end};file 是 Sources 的下标,NO_FILE 表示无位置 +// Label 主/次标签(span + 文本),可多个 +// Child 子诊断(note/help,可带 span),挂在条目上 +// 渲染:人类可读(源码行 + caret 下划线)与 JSON(字段稳定)双格式。 +// 位置只存下标,渲染时经 Sources 解析行列/取行,避免复制文件名。 +// 消息按拥有型 Str 保存;条目拥有其 labels/children;clear/drop 释放全部内部资源。 + +type .{ .Error, .Status, .make } = @import("./utils/error.spl"); +type .{ .Vec } = @import("./utils/vec.spl"); +type .{ .ByteBuffer } = @import("./utils/byte_buffer.spl"); +type .{ .Str } = @import("./utils/str.spl"); +type .{ .ByteView } = @import("./utils/byte_view.spl"); +type .{ .append_usize } = @import("./utils/fmt.spl"); +type .{ .Sources, .Source, .set_new, .set_add, .set_at, .set_drop } = @import("./utils/source.spl"); + +type Severity = enum { + Note, + Help, + Warning, + Error, + Fatal, +} + +type Stage = enum { + None, + Lex, + Parse, + Sema, + Lower, + Vm, + Driver, +} + +type Code = enum { + None, + // 词法 / 语法 + LexUnexpected, + ParseExpected, + ParseUnrecognized, + // sema + UndefinedSymbol, + DuplicateSymbol, + TypeMismatch, + ArgCountMismatch, + NotAFunction, + NoField, + NoMethod, + IndexBaseInvalid, + TupleIndexOob, + FitsOverflow, + CircularDep, + BreakOutsideLoop, + ContinueOutsideLoop, + UndefinedLabel, + CastInvalid, + UnknownAttr, + UnknownIntrin, + IntrinArgCount, + VarUninitialized, + CannotInfer, + MustReturnValue, + // lowering + UnhandledNode, + ImplicitCastFail, + NoLvalue, + BadFieldIndex, + BadBlock, + // vm + IrUnknownKind, + FrameOverflow, + // 通用 + Internal, +} + +// file 下标不可用时的哨兵。 +fn no_file(): usize { + ret ~(0 as usize); +} + +type Span = struct { + file: usize, + start: usize, + end: usize, +} + +fn span_none(): Span { + ret Span { .file = no_file(), .start = 0, .end = 0 }; +} + +fn span_make(file: usize, start: usize, end: usize): Span { + ret Span { .file = file, .start = start, .end = end }; +} + +fn span_is_none(span: Span): bool { + ret span.file == no_file(); +} + +type Label = struct { + span: Span, + message: Str, + primary: bool, +} + +type Child = struct { + severity: Severity, + span: Span, + message: Str, +} + +type Item = struct { + severity: Severity, + stage: Stage, + code: Code, + span: Span, + message: Str, + labels: Vec, // Label + children: Vec, // Child +} + +type Diag = @self; + +items: Vec, // Item +sources: *Sources, // 借用;可为 null(则只输出消息) +errors: usize, +warnings: usize, +fatals: usize, + +fn new(sources: *Sources): Diag { + ret Diag { + .items = Vec.blank(@sizeof(Item), @alignof(Item)), + .sources = sources, + .errors = 0, + .warnings = 0, + .fatals = 0, + }; +} + +fn item_at(self: *Diag, index: usize): *Item { + match Vec.get(&self.items, index) { + .Ok(ptr) => { ret ptr as *Item; }, + .Err(error) => { ret null; }, + } + ret null; +} + +fn label_at(it: *Item, index: usize): *Label { + match Vec.get(&it.labels, index) { + .Ok(ptr) => { ret ptr as *Label; }, + .Err(error) => { ret null; }, + } + ret null; +} + +fn child_at(it: *Item, index: usize): *Child { + match Vec.get(&it.children, index) { + .Ok(ptr) => { ret ptr as *Child; }, + .Err(error) => { ret null; }, + } + ret null; +} + +fn last(self: *Diag): usize { + if self.items.len == 0 { ret no_file(); } + ret self.items.len - 1; +} + +fn count(self: Diag): usize { ret self.items.len; } + +fn error_count(self: Diag): usize { ret self.errors; } + +fn warning_count(self: Diag): usize { ret self.warnings; } + +fn fatal_count(self: Diag): usize { ret self.fatals; } + +fn has_error(self: Diag): bool { ret self.errors > 0; } + +fn has_fatal(self: Diag): bool { ret self.fatals > 0; } + +// ---- 采集 ---- + +// 追加一条诊断,返回其下标(失败返回 no_file())。span 为主位置。 +fn emit(self: *Diag, severity: Severity, stage: Stage, code: Code, span: Span, + msg: []u8): usize { + var message = Str.new(); + match Str.from_slice(msg) { + .Ok(v) => { message = v; }, + .Err(error) => { ret no_file(); }, + } + var item = Item { + .severity = severity, + .stage = stage, + .code = code, + .span = span, + .message = message, + .labels = Vec.blank(@sizeof(Label), @alignof(Label)), + .children = Vec.blank(@sizeof(Child), @alignof(Child)), + }; + match Vec.push(&self.items, &item as *u8) { + .Ok => {}, + .Err(error) => { Str.drop(&item.message); ret no_file(); }, + } + match severity { + .Error => { self.errors = self.errors + 1; }, + .Fatal => { self.errors = self.errors + 1; self.fatals = self.fatals + 1; }, + .Warning => { self.warnings = self.warnings + 1; }, + _ => {}, + } + ret self.items.len - 1; +} + +fn err(self: *Diag, span: Span, code: Code, msg: []u8): usize { + ret emit(self, .Error, .None, code, span, msg); +} + +fn warn(self: *Diag, span: Span, code: Code, msg: []u8): usize { + ret emit(self, .Warning, .None, code, span, msg); +} + +fn note(self: *Diag, span: Span, code: Code, msg: []u8): usize { + ret emit(self, .Note, .None, code, span, msg); +} + +// 给最近一条诊断附加标签(primary 决定主/次)。 +fn attach_label(self: *Diag, span: Span, msg: []u8, primary: bool): Status { + var idx = last(self); + if idx == no_file() { ret .Err(make(.OutOfRange)); } + var it = item_at(self, idx); + if it == null { ret .Err(make(.OutOfRange)); } + + var message = Str.new(); + match Str.from_slice(msg) { + .Ok(v) => { message = v; }, + .Err(error) => { ret .Err(error); }, + } + var lab = Label { .span = span, .message = message, .primary = primary }; + match Vec.push(&it.labels, &lab as *u8) { + .Ok => {}, + .Err(error) => { Str.drop(&lab.message); ret .Err(error); }, + } + ret .Ok; +} + +// 给最近一条诊断附加子诊断(note/help 等)。 +fn attach_child(self: *Diag, severity: Severity, span: Span, msg: []u8): Status { + var idx = last(self); + if idx == no_file() { ret .Err(make(.OutOfRange)); } + var it = item_at(self, idx); + if it == null { ret .Err(make(.OutOfRange)); } + + var message = Str.new(); + match Str.from_slice(msg) { + .Ok(v) => { message = v; }, + .Err(error) => { ret .Err(error); }, + } + var ch = Child { .severity = severity, .span = span, .message = message }; + match Vec.push(&it.children, &ch as *u8) { + .Ok => {}, + .Err(error) => { Str.drop(&ch.message); ret .Err(error); }, + } + ret .Ok; +} + +// ---- 名称 ---- + +fn severity_name(severity: Severity): []u8 { + match severity { + .Note => { ret "note"; }, + .Help => { ret "help"; }, + .Warning => { ret "warning"; }, + .Error => { ret "error"; }, + .Fatal => { ret "fatal"; }, + } + ret "?"; +} + +fn stage_name(stage: Stage): []u8 { + match stage { + .None => { ret "none"; }, + .Lex => { ret "lex"; }, + .Parse => { ret "parse"; }, + .Sema => { ret "sema"; }, + .Lower => { ret "lower"; }, + .Vm => { ret "vm"; }, + .Driver => { ret "driver"; }, + } + ret "?"; +} + +// 稳定 E 码(diff 依赖;新增 Code 时分配新码,勿改旧值)。 +fn code_str(code: Code): []u8 { + match code { + .None => { ret "E0000"; }, + .LexUnexpected => { ret "E0001"; }, + .ParseExpected => { ret "E0002"; }, + .ParseUnrecognized => { ret "E0003"; }, + .UndefinedSymbol => { ret "E0101"; }, + .DuplicateSymbol => { ret "E0102"; }, + .TypeMismatch => { ret "E0103"; }, + .ArgCountMismatch => { ret "E0104"; }, + .NotAFunction => { ret "E0105"; }, + .NoField => { ret "E0106"; }, + .NoMethod => { ret "E0107"; }, + .IndexBaseInvalid => { ret "E0108"; }, + .TupleIndexOob => { ret "E0109"; }, + .FitsOverflow => { ret "E0110"; }, + .CircularDep => { ret "E0111"; }, + .BreakOutsideLoop => { ret "E0112"; }, + .ContinueOutsideLoop => { ret "E0113"; }, + .UndefinedLabel => { ret "E0114"; }, + .CastInvalid => { ret "E0115"; }, + .UnknownAttr => { ret "E0116"; }, + .UnknownIntrin => { ret "E0117"; }, + .IntrinArgCount => { ret "E0118"; }, + .VarUninitialized => { ret "E0119"; }, + .CannotInfer => { ret "E0120"; }, + .MustReturnValue => { ret "E0121"; }, + .UnhandledNode => { ret "E0201"; }, + .ImplicitCastFail => { ret "E0202"; }, + .NoLvalue => { ret "E0203"; }, + .BadFieldIndex => { ret "E0204"; }, + .BadBlock => { ret "E0205"; }, + .IrUnknownKind => { ret "E0301"; }, + .FrameOverflow => { ret "E0302"; }, + .Internal => { ret "E9000"; }, + } + ret "E0000"; +} + +fn code_name(code: Code): []u8 { + match code { + .None => { ret "none"; }, + .LexUnexpected => { ret "lex_unexpected"; }, + .ParseExpected => { ret "parse_expected"; }, + .ParseUnrecognized => { ret "parse_unrecognized"; }, + .UndefinedSymbol => { ret "undefined_symbol"; }, + .DuplicateSymbol => { ret "duplicate_symbol"; }, + .TypeMismatch => { ret "type_mismatch"; }, + .ArgCountMismatch => { ret "arg_count_mismatch"; }, + .NotAFunction => { ret "not_a_function"; }, + .NoField => { ret "no_field"; }, + .NoMethod => { ret "no_method"; }, + .IndexBaseInvalid => { ret "index_base_invalid"; }, + .TupleIndexOob => { ret "tuple_index_oob"; }, + .FitsOverflow => { ret "fits_overflow"; }, + .CircularDep => { ret "circular_dep"; }, + .BreakOutsideLoop => { ret "break_outside_loop"; }, + .ContinueOutsideLoop => { ret "continue_outside_loop"; }, + .UndefinedLabel => { ret "undefined_label"; }, + .CastInvalid => { ret "cast_invalid"; }, + .UnknownAttr => { ret "unknown_attr"; }, + .UnknownIntrin => { ret "unknown_intrin"; }, + .IntrinArgCount => { ret "intrin_arg_count"; }, + .VarUninitialized => { ret "var_uninitialized"; }, + .CannotInfer => { ret "cannot_infer"; }, + .MustReturnValue => { ret "must_return_value"; }, + .UnhandledNode => { ret "unhandled_node"; }, + .ImplicitCastFail => { ret "implicit_cast_fail"; }, + .NoLvalue => { ret "no_lvalue"; }, + .BadFieldIndex => { ret "bad_field_index"; }, + .BadBlock => { ret "bad_block"; }, + .IrUnknownKind => { ret "ir_unknown_kind"; }, + .FrameOverflow => { ret "frame_overflow"; }, + .Internal => { ret "internal"; }, + } + ret "none"; +} + +// ---- 渲染:公共小工具 ---- + +fn append_num(out: *ByteBuffer, value: usize): Status { + ret append_usize(out, value, 10, false); +} + +fn append_str(out: *ByteBuffer, s: *Str): Status { + ret ByteBuffer.append(out, Str.as_slice(s)); +} + +fn append_view(out: *ByteBuffer, view: ByteView): Status { + ret ByteBuffer.append(out, ByteView.as_slice(view)); +} + +type Resolved = struct { + ok: bool, + fname: ByteView, + line: usize, + col: usize, + text: ByteView, + width: usize, +} + +fn resolve(self: *Diag, span: Span): Resolved { + var r = Resolved { + .ok = false, + .fname = ByteView.empty(), + .line = 0, + .col = 0, + .text = ByteView.empty(), + .width = 1, + }; + if self.sources == null { ret r; } + if span_is_none(span) { ret r; } + + var src = set_at(self.sources, span.file); + if src == null { ret r; } + + var pos = Source.line_col(src.*, span.start); + var width: usize = 1; + if span.end > span.start { width = span.end - span.start; } + r.ok = true; + r.fname = src.fname; + r.line = pos.line; + r.col = pos.col; + r.text = Source.line_text(src.*, pos.line); + r.width = width; + ret r; +} + +fn render_header(it: *Item, r: *Resolved, out: *ByteBuffer): Status { + if r.ok { + match append_view(out, r.fname) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_num(out, r.line) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_num(out, r.col) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + match ByteBuffer.append(out, severity_name(it.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append_byte(out, '[') { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, code_str(it.code)) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, "]: ") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_str(out, &it.message) { .Ok => {}, .Err(e) => { ret .Err(e); } } + ret ByteBuffer.append_byte(out, '\n'); +} + +// 输出一行源码 + caret 下划线(列范围按字节;制表符不特殊处理)。 +fn render_span_visual(self: *Diag, span: Span, out: *ByteBuffer): Status { + var r = resolve(self, span); + if !r.ok { ret .Ok; } + + match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_view(out, r.text) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } } + + match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } } + var pad: usize = 0; + if r.col > 0 { pad = r.col - 1; } + match ByteBuffer.append_repeat(out, ' ', pad) { .Ok => {}, .Err(e) => { ret .Err(e); } } + + var width = r.width; + if width == 0 { width = 1; } + if r.col > 0 { + var room: usize = 0; + if r.text.len >= pad { room = r.text.len - pad; } + if room == 0 { room = 1; } + if width > room { width = room; } + } + var k: usize = 0; + while k < width { + var ch: u8 = '~'; + if k == 0 { ch = '^'; } + match ByteBuffer.append_byte(out, ch) { .Ok => {}, .Err(e) => { ret .Err(e); } } + k = k + 1; + } + ret ByteBuffer.append_byte(out, '\n'); +} + +fn render_child_line(ch: *Child, out: *ByteBuffer): Status { + match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, severity_name(ch.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_str(out, &ch.message) { .Ok => {}, .Err(e) => { ret .Err(e); } } + ret ByteBuffer.append_byte(out, '\n'); +} + +fn render_human(self: *Diag, out: *ByteBuffer): Status { + var i: usize = 0; + while i < self.items.len { + var it = item_at(self, i); + if it == null { ret .Err(make(.OutOfRange)); } + + var r = resolve(self, it.span); + match render_header(it, &r, out) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match render_span_visual(self, it.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } } + + var j: usize = 0; + while j < it.labels.len { + var lab = label_at(it, j); + if lab != null { + match render_span_visual(self, lab.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } } + if Str.size(lab.message) != 0 { + match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_str(out, &lab.message) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + } + j = j + 1; + } + + var c: usize = 0; + while c < it.children.len { + var ch = child_at(it, c); + if ch != null { + match render_child_line(ch, out) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match render_span_visual(self, ch.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + c = c + 1; + } + + if i + 1 < self.items.len { + match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + i = i + 1; + } + ret .Ok; +} + +// ---- JSON 渲染(字段稳定,供 LSP / diff 工具)---- + +fn append_json_hex4(out: *ByteBuffer, b: u8): Status { + var hex: []u8 = "0123456789abcdef"; + match ByteBuffer.append(out, "\\u00") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append_byte(out, hex[(b >> 4) as usize]) { .Ok => {}, .Err(e) => { ret .Err(e); } } + ret ByteBuffer.append_byte(out, hex[(b & 15) as usize]); +} + +fn append_json_str(out: *ByteBuffer, bytes: []u8): Status { + match ByteBuffer.append_byte(out, '"') { .Ok => {}, .Err(e) => { ret .Err(e); } } + var i: usize = 0; + while i < bytes.len { + var b = bytes[i]; + if b == '"' { + match ByteBuffer.append(out, "\\\"") { .Ok => {}, .Err(e) => { ret .Err(e); } } + } else if b == '\\' { + match ByteBuffer.append(out, "\\\\") { .Ok => {}, .Err(e) => { ret .Err(e); } } + } else if b == '\n' { + match ByteBuffer.append(out, "\\n") { .Ok => {}, .Err(e) => { ret .Err(e); } } + } else if b == '\r' { + match ByteBuffer.append(out, "\\r") { .Ok => {}, .Err(e) => { ret .Err(e); } } + } else if b == '\t' { + match ByteBuffer.append(out, "\\t") { .Ok => {}, .Err(e) => { ret .Err(e); } } + } else if b < ' ' { + match append_json_hex4(out, b) { .Ok => {}, .Err(e) => { ret .Err(e); } } + } else { + match ByteBuffer.append_byte(out, b) { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + i = i + 1; + } + ret ByteBuffer.append_byte(out, '"'); +} + +fn append_str_json(out: *ByteBuffer, s: *Str): Status { + ret append_json_str(out, Str.as_slice(s)); +} + +fn append_bool(out: *ByteBuffer, value: bool): Status { + if value { ret ByteBuffer.append(out, "true"); } + ret ByteBuffer.append(out, "false"); +} + +fn append_loc_json(self: *Diag, span: Span, out: *ByteBuffer): Status { + var r = resolve(self, span); + match ByteBuffer.append(out, "{\"file\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + if r.ok { + match append_json_str(out, ByteView.as_slice(r.fname)) { .Ok => {}, .Err(e) => { ret .Err(e); } } + } else { + match ByteBuffer.append(out, "null") { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + match ByteBuffer.append(out, ",\"start\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_num(out, span.start) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, ",\"end\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_num(out, span.end) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, ",\"line\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + if r.ok { + match append_num(out, r.line) { .Ok => {}, .Err(e) => { ret .Err(e); } } + } else { + match ByteBuffer.append(out, "null") { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + match ByteBuffer.append(out, ",\"col\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + if r.ok { + match append_num(out, r.col) { .Ok => {}, .Err(e) => { ret .Err(e); } } + } else { + match ByteBuffer.append(out, "null") { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + ret ByteBuffer.append_byte(out, '}'); +} + +fn render_item_json(self: *Diag, it: *Item, out: *ByteBuffer): Status { + match ByteBuffer.append(out, "{\"severity\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_json_str(out, severity_name(it.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, ",\"stage\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_json_str(out, stage_name(it.stage)) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, ",\"code\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_json_str(out, code_str(it.code)) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, ",\"name\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_json_str(out, code_name(it.code)) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, ",\"message\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_str_json(out, &it.message) { .Ok => {}, .Err(e) => { ret .Err(e); } } + + match ByteBuffer.append(out, ",\"span\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_loc_json(self, it.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } } + + match ByteBuffer.append(out, ",\"labels\":[") { .Ok => {}, .Err(e) => { ret .Err(e); } } + var j: usize = 0; + while j < it.labels.len { + var lab = label_at(it, j); + if lab != null { + if j != 0 { + match ByteBuffer.append_byte(out, ',') { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + match ByteBuffer.append(out, "{\"message\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_str_json(out, &lab.message) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, ",\"primary\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_bool(out, lab.primary) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, ",\"span\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_loc_json(self, lab.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append_byte(out, '}') { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + j = j + 1; + } + match ByteBuffer.append_byte(out, ']') { .Ok => {}, .Err(e) => { ret .Err(e); } } + + match ByteBuffer.append(out, ",\"children\":[") { .Ok => {}, .Err(e) => { ret .Err(e); } } + var c: usize = 0; + while c < it.children.len { + var ch = child_at(it, c); + if ch != null { + if c != 0 { + match ByteBuffer.append_byte(out, ',') { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + match ByteBuffer.append(out, "{\"severity\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_json_str(out, severity_name(ch.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, ",\"message\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_str_json(out, &ch.message) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(out, ",\"span\":") { .Ok => {}, .Err(e) => { ret .Err(e); } } + match append_loc_json(self, ch.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append_byte(out, '}') { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + c = c + 1; + } + match ByteBuffer.append_byte(out, ']') { .Ok => {}, .Err(e) => { ret .Err(e); } } + ret ByteBuffer.append_byte(out, '}'); +} + +fn render_json(self: *Diag, out: *ByteBuffer): Status { + match ByteBuffer.append_byte(out, '[') { .Ok => {}, .Err(e) => { ret .Err(e); } } + var i: usize = 0; + while i < self.items.len { + var it = item_at(self, i); + if it == null { ret .Err(make(.OutOfRange)); } + if i != 0 { + match ByteBuffer.append_byte(out, ',') { .Ok => {}, .Err(e) => { ret .Err(e); } } + } + match render_item_json(self, it, out) { .Ok => {}, .Err(e) => { ret .Err(e); } } + i = i + 1; + } + ret ByteBuffer.append_byte(out, ']'); +} + +// ---- 生命周期 ---- + +fn drop_item(it: *Item) { + Str.drop(&it.message); + var j: usize = 0; + while j < it.labels.len { + var lab = label_at(it, j); + if lab != null { Str.drop(&lab.message); } + j = j + 1; + } + Vec.drop(&it.labels); + var c: usize = 0; + while c < it.children.len { + var ch = child_at(it, c); + if ch != null { Str.drop(&ch.message); } + c = c + 1; + } + Vec.drop(&it.children); +} + +fn clear(self: *Diag) { + var i: usize = 0; + while i < self.items.len { + var it = item_at(self, i); + if it != null { drop_item(it); } + i = i + 1; + } + Vec.clear(&self.items); + self.errors = 0; + self.warnings = 0; + self.fatals = 0; +} + +fn drop(self: *Diag) { + clear(self); + Vec.drop(&self.items); + self.sources = null; +} + +// ---- 测试辅助 ---- + +fn view(text: []u8): ByteView { + ret ByteView.from_slice(text); +} + +fn expect_ok(result: Status) { + match result { + .Ok => {}, + .Err(error) => { @assert(false, "unexpected diag error"); }, + } +} + +fn has_bytes(out: *ByteBuffer, needle: []u8): bool { + ret ByteView.contains(ByteView.from_slice(ByteBuffer.as_slice(out.*)), view(needle)); +} + +fn test_collect_render_human() { + var sources = set_new(); + match set_add(&sources, view("a.spl"), view("fn main() {\n y;\n}\n")) { + .Ok(index) => {}, + .Err(error) => { @assert(false, "add source"); }, + } + + var d = new(&sources); + var idx = err(&d, span_make(0, 16, 17), .UndefinedSymbol, "undefined symbol `y`"); + @assert(idx != no_file(), "emit"); + expect_ok(attach_label(&d, span_make(0, 16, 17), "not found in scope", false)); + expect_ok(attach_child(&d, .Note, span_none(), "did you mean `x`?")); + + @assert(count(d) == 1, "count"); + @assert(error_count(d) == 1 && has_error(d), "errors"); + @assert(warning_count(d) == 0 && !has_fatal(d), "warnings/fatal"); + + var out = ByteBuffer.new(); + expect_ok(render_human(&d, &out)); + @assert(has_bytes(&out, "a.spl:2:5: error[E0101]: undefined symbol `y`"), "human header"); + @assert(has_bytes(&out, " y;"), "human source line"); + @assert(has_bytes(&out, " ^"), "human caret"); + @assert(has_bytes(&out, "not found in scope"), "human label"); + @assert(has_bytes(&out, "note: did you mean `x`?"), "human child"); + ByteBuffer.drop(&out); + + clear(&d); + @assert(count(d) == 0 && error_count(d) == 0, "clear"); + drop(&d); + set_drop(&sources); +} + +fn test_render_json() { + var sources = set_new(); + match set_add(&sources, view("b.spl"), view("let z = 1;\n")) { + .Ok(index) => {}, + .Err(error) => { @assert(false, "add source"); }, + } + + var d = new(&sources); + err(&d, span_make(0, 4, 5), .TypeMismatch, "expected i32, got bool"); + expect_ok(attach_child(&d, .Help, span_none(), "annotate the type")); + + var out = ByteBuffer.new(); + expect_ok(render_json(&d, &out)); + @assert(has_bytes(&out, "\"severity\":\"error\""), "json severity"); + @assert(has_bytes(&out, "\"code\":\"E0103\""), "json code"); + @assert(has_bytes(&out, "\"name\":\"type_mismatch\""), "json name"); + @assert(has_bytes(&out, "\"line\":1"), "json line"); + @assert(has_bytes(&out, "\"col\":5"), "json col"); + @assert(has_bytes(&out, "\"children\":[{\"severity\":\"help\""), "json child"); + ByteBuffer.drop(&out); + + drop(&d); + set_drop(&sources); +} + +fn test_empty_and_headerless() { + var d = new(null); + err(&d, span_none(), .Internal, "no location"); + var out = ByteBuffer.new(); + expect_ok(render_human(&d, &out)); + @assert(has_bytes(&out, "error[E9000]: no location"), "headerless"); + ByteBuffer.drop(&out); + drop(&d); +} + +fn main() { + test_collect_render_human(); + test_render_json(); + test_empty_and_headerless(); +} diff --git a/stage2/lexer.spl b/stage2/lexer.spl new file mode 100644 index 0000000..51fa0fc --- /dev/null +++ b/stage2/lexer.spl @@ -0,0 +1,634 @@ +// 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(); +} diff --git a/stage2/sys/exit.spl b/stage2/sys/exit.spl index 904fd7d..f74fbe4 100644 --- a/stage2/sys/exit.spl +++ b/stage2/sys/exit.spl @@ -1,7 +1,7 @@ // Expect retcode = 6 // exit.spl -// usage: type exit = @import("exit.spl"); exit.ok(); +// usage: type exit = @import("exit.spl"); exit.ok(); #extern(vm) fn vm_exit(code: i32); diff --git a/stage2/sys/file.spl b/stage2/sys/file.spl index 843f99a..7c8bafa 100644 --- a/stage2/sys/file.spl +++ b/stage2/sys/file.spl @@ -34,12 +34,12 @@ fn write_byte(f: *File, b: u8): u64 { ret write(f, &tmp, 1); } -fn read(f: *File, buf: *u8, n: u64): u64 { +fn read(f: *File, buf: *u8, n: usize): usize { if f.handle == null { ret 0; } ret vm_fread(buf as *any, 1, n, f.handle); } -fn size(f: *File): u64 { +fn size(f: *File): usize { if f.handle == null { ret 0; } ret vm_fsize(f.handle); } @@ -67,4 +67,4 @@ fn main(): void { @assert(n == msg.len, "write"); putchar(46 as u8); putchar(10 as u8); -} \ No newline at end of file +} diff --git a/stage2/sys/mem.spl b/stage2/sys/mem.spl index 8ed08d7..c41312d 100644 --- a/stage2/sys/mem.spl +++ b/stage2/sys/mem.spl @@ -76,4 +76,4 @@ fn main(): void { free(q); free(buf); free(null); -} \ No newline at end of file +} diff --git a/stage2/token.spl b/stage2/token.spl new file mode 100644 index 0000000..893bdc5 --- /dev/null +++ b/stage2/token.spl @@ -0,0 +1,190 @@ +// 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; +} + +// ---- 测试辅助 ---- + +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 main() { + test_keywords(); + test_adt_payloads(); + test_trivia(); +} diff --git a/stage2/utils/arena.spl b/stage2/utils/arena.spl index 816b7a7..32f4d0d 100644 --- a/stage2/utils/arena.spl +++ b/stage2/utils/arena.spl @@ -1,9 +1,13 @@ // Expect retcode = 0 + // 分块 bump 分配器:按块分配、块内顺序推进,只在 drop 时整体释放。 // 适合 AST/IR 这类生命周期一致的批量小对象;不支持单独 free。 -// 失败(内存不足/参数非法)返回 null,不改变已有分配。 +// 分配失败通过 ArenaResult 区分原因:size==0/非法对齐/内存不足/地址溢出。 +// 失败的 alloc 不改变已有分配。 type .{ .Vec } = @import("vec.spl"); +type .{ .Error, .Kind, .Status } = @import("error.spl"); +type .{ .checked_add, .align_up } = @import("memory.spl"); type Mem = @import("../sys/mem.spl"); type Chunk = struct { @@ -12,6 +16,11 @@ type Chunk = struct { used: usize, } +type ArenaResult = enum { + Ok: *u8, + Err: Error, +} + type Arena = @self; chunk_size: usize, @@ -28,61 +37,68 @@ fn new(chunk_size: usize): Arena { }; } -fn is_pow2(x: usize): bool { - ret x != 0 && (x & (x - 1)) == 0; -} - -fn align_up(x: usize, align: usize): usize { - var mask = align - 1; - ret (x + mask) & ~mask; -} - fn chunk_at(self: *Arena, index: usize): *Chunk { match Vec.get(&self.chunks, index) { .Ok(ptr) => { ret ptr as *Chunk; }, + // 越界/无效 Arena 统一返回 null;调用方负责判空,无资源需要释放。 .Err(error) => { ret null; }, } ret null; } -fn push_chunk(self: *Arena, size: usize): bool { +fn push_chunk(self: *Arena, size: usize): Status { var base = Mem.alloc(size) as *u8; - if base == null { ret false; } + if base == null { ret .Err(Error.make(.Overflow)); } var c = Chunk { .base = base, .size = size, .used = 0 }; match Vec.push(&self.chunks, &c as *u8) { .Ok => {}, - .Err(error) => { Mem.free(base as *any); ret false; }, + .Err(error) => { Mem.free(base as *any); ret .Err(error); }, } self.current = self.chunks.len - 1; - ret true; + ret .Ok; } -// 分配 size 字节,地址按 align(非零二次幂)对齐。失败返回 null。 -fn alloc(self: *Arena, size: usize, align: usize): *u8 { - if size == 0 { ret null; } +// 分配 size 字节,地址按 align(非零二次幂)对齐。失败返回 ArenaResult.Err。 +fn alloc(self: *Arena, size: usize, align: usize): ArenaResult { + if size == 0 { ret .Err(Error.make(.InvalidArgument)); } if align == 0 { align = 1; } - if !is_pow2(align) { ret null; } while true { if self.current >= self.chunks.len { var want = self.chunk_size; - var need = size + align; + var need: usize = 0; + match checked_add(size, align) { + .Ok(value) => { need = value; }, + .Err(error) => { ret .Err(error); }, + } if need > want { want = need; } - if !push_chunk(self, want) { ret null; } + match push_chunk(self, want) { + .Ok => {}, + .Err(error) => { ret .Err(error); }, + } } var c = chunk_at(self, self.current); - if c == null { ret null; } + if c == null { ret .Err(Error.make(.InvalidArgument)); } var addr = c.base as usize; - var aligned = align_up(addr + c.used, align); + var used_addr: usize = 0; + match checked_add(addr, c.used) { + .Ok(value) => { used_addr = value; }, + .Err(error) => { ret .Err(error); }, + } + var aligned: usize = 0; + match align_up(used_addr, align) { + .Ok(value) => { aligned = value; }, + .Err(error) => { ret .Err(error); }, // 非二次幂对齐或地址溢出 + } var offset = aligned - addr; if offset <= c.size && size <= c.size - offset { c.used = offset + size; - ret aligned as *u8; + ret .Ok(aligned as *u8); } self.current = self.current + 1; } - ret null; + ret .Err(Error.make(.OutOfRange)); // 不可达:循环内所有路径均已返回 } fn chunk_count(self: Arena): usize { @@ -117,13 +133,31 @@ fn is_aligned(p: *u8, align: usize): bool { ret ((p as usize) & (align - 1)) == 0; } +fn alloc_ok(a: *Arena, size: usize, align: usize): *u8 { + match alloc(a, size, align) { + .Ok(ptr) => { ret ptr; }, + .Err(error) => { @assert(false, "unexpected arena alloc error"); }, + } + ret null; +} + +fn expect_alloc_error(result: ArenaResult, expected: Kind) { + match result { + .Ok(ptr) => { @assert(false, "expected arena alloc error"); }, + .Err(error) => { + var want: Kind = expected; + @assert(@enum2int(error.kind) == @enum2int(want), "arena alloc error kind"); + }, + } +} + fn test_alloc_and_alignment() { var a = new(64); @assert(chunk_count(a) == 0, "lazy"); - var p1 = alloc(&a, 1, 1); - var p8 = alloc(&a, 16, 8); - var p16 = alloc(&a, 1, 16); + var p1 = alloc_ok(&a, 1, 1); + var p8 = alloc_ok(&a, 16, 8); + var p16 = alloc_ok(&a, 1, 16); @assert(p1 != null && p8 != null && p16 != null, "alloc"); @assert(p1 != p8 && p8 != p16 && p1 != p16, "distinct"); @assert(is_aligned(p8, 8), "align 8"); @@ -136,18 +170,20 @@ fn test_alloc_and_alignment() { @assert(chunk_count(a) == 1, "one chunk"); reset(&a); @assert(chunk_count(a) == 1, "reset keeps chunks"); - var q = alloc(&a, 1, 1); + var q = alloc_ok(&a, 1, 1); @assert(q == p1, "reset reuses first chunk"); + // drop 后还有断言,故保持手动释放,不改为 defer。 drop(&a); @assert(chunk_count(a) == 0, "drop empties"); } fn test_growth() { var a = new(64); + defer { drop(&a); } var i: i32 = 0; while i < 100 { - var p = alloc(&a, 16, 8); + var p = alloc_ok(&a, 16, 8); @assert(p != null, "grow alloc"); @assert(is_aligned(p, 8), "grow align"); p[0] = i as u8; @@ -156,18 +192,17 @@ fn test_growth() { @assert(chunk_count(a) > 1, "grew multiple chunks"); // 超过块大小的单次分配也能成功(新块按需放大)。 - var big = alloc(&a, 1024, 16); + var big = alloc_ok(&a, 1024, 16); @assert(big != null && is_aligned(big, 16), "oversized alloc"); - drop(&a); } fn test_failures() { var a = new(64); - @assert(alloc(&a, 0, 1) == null, "zero size"); - var d = alloc(&a, 8, 0); + defer { drop(&a); } + expect_alloc_error(alloc(&a, 0, 1), .InvalidArgument); + var d = alloc_ok(&a, 8, 0); @assert(d != null && is_aligned(d, 1), "align 0 defaults to 1"); - @assert(alloc(&a, 8, 3) == null, "non-pow2 align"); - drop(&a); + expect_alloc_error(alloc(&a, 8, 3), .InvalidAlignment); } fn main() { diff --git a/stage2/utils/ascii.spl b/stage2/utils/ascii.spl index a4cd82f..c29252b 100644 --- a/stage2/utils/ascii.spl +++ b/stage2/utils/ascii.spl @@ -1,4 +1,5 @@ // Expect retcode = 0 + // ASCII 字符工具。输入和输出都是单个字节,不解释 UTF-8 或本地化规则。 type .{ .Error, .make } = @import("error.spl"); diff --git a/stage2/utils/big_int.spl b/stage2/utils/big_int.spl index 18a491c..e6d5627 100644 --- a/stage2/utils/big_int.spl +++ b/stage2/utils/big_int.spl @@ -1,4 +1,5 @@ // Expect retcode = 0 + // 任意精度有符号整数。limbs 为小端 u32;零规范为 len=0 且 negative=false。 // 普通赋值只借用同一存储;深复制使用 clone,所有权转移使用 take。 type .{ .Error, .Status } = @import("error.spl"); @@ -64,6 +65,9 @@ fn reserve(self: *BigInt, need: usize): Status { .Err(error) => { ret .Err(error); }, } var fresh = alloc(byte_count) as *u32; + if fresh == null { + ret .Err(Error.make(.Overflow)); + } if self.len != 0 { copy(fresh as *any, self.limbs as *any, self.len * @sizeof(u32)); } @@ -203,6 +207,7 @@ fn sub_abs_into(out: *BigInt, a: BigInt, b: BigInt): Status { .Err(error) => { ret .Err(error); }, } var borrow: u64 = 0; + var base: u64 = 4294967296; // 2^32 var i: usize = 0; while i < a.len { var av = a.limbs[i] as u64; @@ -210,7 +215,7 @@ fn sub_abs_into(out: *BigInt, a: BigInt, b: BigInt): Status { if i < b.len { bv = b.limbs[i] as u64; } var subtrahend = bv + borrow; if av < subtrahend { - out.limbs[i] = (4294967296 + av - subtrahend) as u32; + out.limbs[i] = (base + av - subtrahend) as u32; borrow = 1; } else { out.limbs[i] = (av - subtrahend) as u32; @@ -407,6 +412,7 @@ fn shift_one(self: *BigInt) { fn sub_abs_in_place(a: *BigInt, b: BigInt) { var borrow: u64 = 0; + var base: u64 = 4294967296; // 2^32 var i: usize = 0; while i < a.len { var av = a.limbs[i] as u64; @@ -414,7 +420,7 @@ fn sub_abs_in_place(a: *BigInt, b: BigInt) { if i < b.len { bv = b.limbs[i] as u64; } var subtrahend = bv + borrow; if av < subtrahend { - a.limbs[i] = (4294967296 + av - subtrahend) as u32; + a.limbs[i] = (base + av - subtrahend) as u32; borrow = 1; } else { a.limbs[i] = (av - subtrahend) as u32; @@ -639,13 +645,15 @@ fn to_i64(out: *i64, source: BigInt): Status { var magnitude: u64 = 0; if source.len > 0 { magnitude = source.limbs[0] as u64; } if source.len > 1 { magnitude = magnitude | ((source.limbs[1] as u64) << 32); } + var max_positive: u64 = 9223372036854775807; // i64::MAX + var min_magnitude: u64 = 9223372036854775808; // |i64::MIN| if !source.negative { - if magnitude > 9223372036854775807 { ret .Err(Error.make(.Overflow)); } + if magnitude > max_positive { ret .Err(Error.make(.Overflow)); } out.* = magnitude as i64; ret .Ok; } - if magnitude > 9223372036854775808 { ret .Err(Error.make(.Overflow)); } - if magnitude == 9223372036854775808 { + if magnitude > min_magnitude { ret .Err(Error.make(.Overflow)); } + if magnitude == min_magnitude { out.* = magnitude as i64; } else { out.* = 0 - magnitude as i64; @@ -693,12 +701,15 @@ fn parsed(text: []u8): BigInt { fn test_arithmetic() { var a = parsed("4294967295"); + defer { drop(&a); } var one = zero(); + defer { drop(&one); } expect_ok(from_u64(&one, 1)); expect_ok(add(&a, a, one)); expect_text(a, "4294967296"); var all_ones = zero(); + defer { drop(&all_ones); } expect_ok(parse(&all_ones, "ffffffffffffffffffffffffffffffff", 16)); expect_ok(add(&all_ones, all_ones, one)); expect_text(all_ones, "340282366920938463463374607431768211456"); @@ -706,43 +717,52 @@ fn test_arithmetic() { expect_text_radix(all_ones, 16, false, "ffffffffffffffffffffffffffffffff"); var two_64 = parsed("18446744073709551616"); + defer { drop(&two_64); } var product = zero(); + defer { drop(&product); } expect_ok(mul(&product, two_64, two_64)); expect_text(product, "340282366920938463463374607431768211456"); var difference = zero(); + defer { drop(&difference); } expect_ok(sub(&difference, product, two_64)); expect_text(difference, "340282366920938463444927863358058659840"); var shifted = zero(); + defer { drop(&shifted); } expect_ok(mul_pow2(&shifted, one, 257)); var bits: usize = 0; expect_ok(bit_length(&bits, shifted)); @assert(bits == 258); expect_ok(div_pow2(&shifted, shifted, 257)); @assert(eq(shifted, one)); - - drop(&shifted); drop(&difference); drop(&product); drop(&two_64); - drop(&all_ones); drop(&one); drop(&a); } fn test_division_and_sign() { var dividend = parsed("340282366920938463463374607431768211456"); + defer { drop(÷nd); } var divisor = parsed("18446744073709551616"); + defer { drop(&divisor); } var quotient = zero(); + defer { drop("ient); } var remainder = zero(); + defer { drop(&remainder); } expect_ok(div_rem("ient, &remainder, dividend, divisor)); @assert(eq(quotient, divisor)); @assert(is_zero(remainder)); var long_dividend = parsed("123456789012345678901234567890"); + defer { drop(&long_dividend); } var long_divisor = parsed("9876543210"); + defer { drop(&long_divisor); } expect_ok(div_rem("ient, &remainder, long_dividend, long_divisor)); expect_text(quotient, "12499999887343749990"); expect_text(remainder, "1562499990"); var negative = zero(); + defer { drop(&negative); } var seven = zero(); + defer { drop(&seven); } expect_ok(from_i64(&negative, -100)); expect_ok(from_u64(&seven, 7)); expect_ok(div_rem(&negative, &remainder, negative, seven)); @@ -750,37 +770,40 @@ fn test_division_and_sign() { expect_text(remainder, "-2"); var reconstructed = zero(); + defer { drop(&reconstructed); } expect_ok(mul(&reconstructed, negative, seven)); expect_ok(add(&reconstructed, reconstructed, remainder)); expect_text(reconstructed, "-100"); var saved = parsed("123"); + defer { drop(&saved); } var zero_divisor = zero(); + defer { drop(&zero_divisor); } match div_rem(&saved, &remainder, dividend, zero_divisor) { .Ok => { @assert(false, "division by zero"); }, .Err(error) => { match error.kind { .DivisionByZero => {}, _ => { @assert(false); }, } }, } expect_text(saved, "123"); - - drop(&zero_divisor); drop(&saved); drop(&reconstructed); drop(&seven); drop(&negative); - drop(&long_divisor); drop(&long_dividend); - drop(&remainder); drop("ient); drop(&divisor); drop(÷nd); } fn test_parse_format_and_conversion() { var value = zero(); + defer { drop(&value); } expect_ok(parse(&value, "-deadBEEF", 16)); var output = ByteBuffer.new(); + defer { ByteBuffer.drop(&output); } expect_ok(format(&output, value, 16, false)); var text = ByteBuffer.as_slice(output); @assert(text.len == 9 && text.ptr[0] == '-' && text.ptr[1] == 'd' && text.ptr[8] == 'f'); expect_text_radix(value, 16, true, "-DEADBEEF"); var base36 = zero(); + defer { drop(&base36); } expect_ok(parse(&base36, "zzzzzzzzzz", 36)); expect_text_radix(base36, 36, false, "zzzzzzzzzz"); var preserved = parsed("77"); + defer { drop(&preserved); } match parse(&preserved, "12x", 10) { .Ok => { @assert(false, "invalid digit"); }, .Err(error) => { @@ -791,10 +814,12 @@ fn test_parse_format_and_conversion() { expect_text(preserved, "77"); var max = parsed("18446744073709551615"); + defer { drop(&max); } var converted: u64 = 0; expect_ok(to_u64(&converted, max)); @assert(converted == 18446744073709551615); var over = parsed("18446744073709551616"); + defer { drop(&over); } converted = 17; match to_u64(&converted, over) { .Ok => { @assert(false); }, @@ -803,10 +828,12 @@ fn test_parse_format_and_conversion() { @assert(converted == 17, "failed conversion preserves output"); var minimum = parsed("-9223372036854775808"); + defer { drop(&minimum); } var signed_value: i64 = 0; expect_ok(to_i64(&signed_value, minimum)); @assert(signed_value == (9223372036854775808 as u64) as i64); var signed_over = parsed("9223372036854775808"); + defer { drop(&signed_over); } signed_value = 23; match to_i64(&signed_value, signed_over) { .Ok => { @assert(false); }, @@ -815,17 +842,16 @@ fn test_parse_format_and_conversion() { @assert(signed_value == 23, "failed signed conversion preserves output"); var copied = zero(); + defer { drop(&copied); } expect_ok(clone(&copied, max)); var one_more = parsed("1"); + defer { drop(&one_more); } expect_ok(add(&max, max, one_more)); expect_text(copied, "18446744073709551615"); var moved = take(&copied); + defer { drop(&moved); } @assert(copied.limbs == null && copied.len == 0 && copied.cap == 0); expect_text(moved, "18446744073709551615"); - - drop(&moved); drop(&copied); drop(&one_more); drop(&signed_over); drop(&minimum); - drop(&over); drop(&max); drop(&preserved); drop(&base36); - ByteBuffer.drop(&output); drop(&value); } fn expect_i64_value(value: BigInt, expected: i64) { @@ -836,9 +862,13 @@ fn expect_i64_value(value: BigInt, expected: i64) { fn test_small_reference() { var left = zero(); + defer { drop(&left); } var right = zero(); + defer { drop(&right); } var result = zero(); + defer { drop(&result); } var remainder = zero(); + defer { drop(&remainder); } var a: i64 = 0; while a <= 0 { var b: i64 = 2; @@ -860,7 +890,6 @@ fn test_small_reference() { } a = a + 1; } - drop(&remainder); drop(&result); drop(&right); drop(&left); } fn main() { diff --git a/stage2/utils/byte_buffer.spl b/stage2/utils/byte_buffer.spl index e825876..a7bc8ff 100644 --- a/stage2/utils/byte_buffer.spl +++ b/stage2/utils/byte_buffer.spl @@ -1,4 +1,5 @@ // Expect retcode = 0 + // 拥有字节存储的动态缓冲区。普通赋值会复制所有权,转移必须使用 take。 // 借用切片和 z_ptr 返回的指针在下一次可能扩容的操作或 drop 后失效。 @@ -205,6 +206,7 @@ fn expect_z(self: *ByteBuffer) { fn test_append_and_resize() { var buffer = new(); + defer { drop(&buffer); } @assert(is_empty(buffer)); @assert(size(buffer) == 0 && capacity(buffer) == 0); expect_z(&buffer); @@ -232,11 +234,11 @@ fn test_append_and_resize() { clear(&buffer); @assert(is_empty(buffer) && capacity(buffer) != 0, "clear retains allocation"); expect_z(&buffer); - drop(&buffer); } fn test_growth_and_self_append() { var buffer = new(); + defer { drop(&buffer); } var i: usize = 0; while i < 8 { expect_ok(append_byte(&buffer, (65 + i) as u8)); @@ -253,14 +255,15 @@ fn test_growth_and_self_append() { i = i + 1; } expect_z(&buffer); - drop(&buffer); } fn test_ownership_and_failures() { var source = new(); + defer { drop(&source); } expect_ok(append(&source, "owned")); var copy = new(); + defer { drop(©); } expect_ok(clone(©, source)); @assert(copy.storage.len == 5, "clone logical length"); source.storage.data[0] = 79; @@ -293,6 +296,7 @@ fn test_ownership_and_failures() { expect_bytes(copy, "wne"); var moved = take(©); + defer { drop(&moved); } @assert(copy.storage.raw == null && copy.storage.len == 0 && copy.storage.cap == 0); expect_bytes(moved, "wne"); @@ -304,10 +308,6 @@ fn test_ownership_and_failures() { moved.storage.cap == old_cap, "overflow preserves byte buffer"); expect_bytes(moved, "wne"); - - drop(&moved); - drop(©); - drop(&source); } fn main() { diff --git a/stage2/utils/byte_view.spl b/stage2/utils/byte_view.spl index 1f6596f..f9c1ea3 100644 --- a/stage2/utils/byte_view.spl +++ b/stage2/utils/byte_view.spl @@ -1,4 +1,5 @@ // Expect retcode = 0 + // 借用的字节字符串视图;不拥有、不分配也不释放 ptr 指向的存储。 // 非空视图要求 ptr 在整个借用期内可读。空视图统一表示为 null/0。 diff --git a/stage2/utils/diag.spl b/stage2/utils/diag.spl deleted file mode 100644 index 74c4a04..0000000 --- a/stage2/utils/diag.spl +++ /dev/null @@ -1,243 +0,0 @@ -// Expect retcode = 0 -// 诊断收集与渲染:severity + 文件/行/列 + 消息;统计错误/警告数。 -// 消息与文件名按拥有型 Str 保存(深拷贝),Diag 拥有它们直到 clear/drop。 -// 渲染输出到 ByteBuffer;不直接写文件,便于 LSP/测试。 - -type .{ .Error, .Status, .make } = @import("error.spl"); -type .{ .Vec } = @import("vec.spl"); -type .{ .ByteBuffer } = @import("byte_buffer.spl"); -type .{ .Str } = @import("str.spl"); -type .{ .append_usize } = @import("fmt.spl"); - -type Severity = enum { - Debug, - Info, - Warning, - Error, - Fatal, -} - -type DiagItem = struct { - severity: Severity, - line: usize, - col: usize, - fname: Str, - message: Str, -} - -type Diag = @self; - -items: Vec, // 元素类型 DiagItem -errors: usize, -warnings: usize, - -fn new(): Diag { - ret Diag { - .items = Vec.blank(@sizeof(DiagItem), 8), - .errors = 0, - .warnings = 0, - }; -} - -fn item_at(self: *Diag, index: usize): *DiagItem { - match Vec.get(&self.items, index) { - .Ok(ptr) => { ret ptr as *DiagItem; }, - .Err(error) => { ret null; }, - } - ret null; -} - -fn count(self: Diag): usize { - ret self.items.len; -} - -fn error_count(self: Diag): usize { - ret self.errors; -} - -fn warning_count(self: Diag): usize { - ret self.warnings; -} - -fn has_error(self: Diag): bool { - ret self.errors > 0; -} - -// 添加一条诊断;失败不改变已有内容。 -fn emit(self: *Diag, fname: []u8, line: usize, col: usize, severity: Severity, - msg: []u8): Status { - var item = DiagItem { - .severity = severity, - .line = line, - .col = col, - .fname = Str.new(), - .message = Str.new(), - }; - - match Str.from_slice(fname) { - .Ok(v) => { item.fname = v; }, - .Err(error) => { ret .Err(error); }, - } - match Str.from_slice(msg) { - .Ok(v) => { item.message = v; }, - .Err(error) => { - Str.drop(&item.fname); - ret .Err(error); - }, - } - match Vec.push(&self.items, &item as *u8) { - .Ok => {}, - .Err(error) => { - Str.drop(&item.fname); - Str.drop(&item.message); - ret .Err(error); - }, - } - - match severity { - .Error => { self.errors = self.errors + 1; }, - .Fatal => { self.errors = self.errors + 1; }, - .Warning => { self.warnings = self.warnings + 1; }, - _ => {}, - } - ret .Ok; -} - -fn err(self: *Diag, fname: []u8, line: usize, col: usize, msg: []u8): Status { - ret emit(self, fname, line, col, .Error, msg); -} - -fn warn(self: *Diag, fname: []u8, line: usize, col: usize, msg: []u8): Status { - ret emit(self, fname, line, col, .Warning, msg); -} - -fn info(self: *Diag, fname: []u8, line: usize, col: usize, msg: []u8): Status { - ret emit(self, fname, line, col, .Info, msg); -} - -fn level_name(severity: Severity): []u8 { - match severity { - .Debug => { ret "debug"; }, - .Info => { ret "info"; }, - .Warning => { ret "warning"; }, - .Error => { ret "error"; }, - .Fatal => { ret "fatal"; }, - } - ret "?"; -} - -fn append_str(out: *ByteBuffer, s: *Str): Status { - ret ByteBuffer.append(out, Str.as_slice(s)); -} - -// 渲染为 "file:line:col: level: message\n"(file 为空时省略前缀)。 -fn render(self: *Diag, out: *ByteBuffer): Status { - var i: usize = 0; - while i < self.items.len { - var it = item_at(self, i); - if it == null { ret .Err(make(.OutOfRange)); } - - if Str.size(it.fname) != 0 { - match append_str(out, &it.fname) { .Ok => {}, .Err(e) => { ret .Err(e); } } - match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } } - } - match append_usize(out, it.line, 10, false) { .Ok => {}, .Err(e) => { ret .Err(e); } } - match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } } - match append_usize(out, it.col, 10, false) { .Ok => {}, .Err(e) => { ret .Err(e); } } - match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } } - match ByteBuffer.append(out, level_name(it.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } } - match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } } - match append_str(out, &it.message) { .Ok => {}, .Err(e) => { ret .Err(e); } } - match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } } - i = i + 1; - } - ret .Ok; -} - -// 丢弃所有条目内容但保留缓冲区。 -fn clear(self: *Diag) { - var i: usize = 0; - while i < self.items.len { - var it = item_at(self, i); - if it != null { - Str.drop(&it.fname); - Str.drop(&it.message); - } - i = i + 1; - } - Vec.clear(&self.items); - self.errors = 0; - self.warnings = 0; -} - -fn drop(self: *Diag) { - var i: usize = 0; - while i < self.items.len { - var it = item_at(self, i); - if it != null { - Str.drop(&it.fname); - Str.drop(&it.message); - } - i = i + 1; - } - Vec.drop(&self.items); - self.errors = 0; - self.warnings = 0; -} - -// ---- 测试辅助 ---- - -fn expect_ok(result: Status) { - match result { - .Ok => {}, - .Err(error) => { @assert(false, "unexpected diag error"); }, - } -} - -fn expect_bytes(out: *ByteBuffer, expected: []u8) { - var actual = ByteBuffer.as_slice(out.*); - @assert(actual.len == expected.len, "render length"); - var i: usize = 0; - while i < expected.len { - @assert(actual.ptr[i] == expected.ptr[i], "render byte"); - i = i + 1; - } -} - -fn test_collect_and_count() { - var d = new(); - @assert(count(d) == 0 && !has_error(d), "empty"); - - expect_ok(err(&d, "a.spl", 3, 5, "undefined identifier `x`")); - expect_ok(warn(&d, "a.spl", 7, 1, "unused variable")); - expect_ok(info(&d, "a.spl", 9, 2, "note")); - expect_ok(err(&d, "", 1, 1, "fatal-ish")); - - @assert(count(d) == 4, "count"); - @assert(error_count(d) == 2, "errors"); - @assert(warning_count(d) == 1, "warnings"); - @assert(has_error(d), "has_error"); - - var out = ByteBuffer.new(); - expect_ok(render(&d, &out)); - expect_bytes(&out, "a.spl:3:5: error: undefined identifier `x`\na.spl:7:1: warning: unused variable\na.spl:9:2: info: note\n1:1: error: fatal-ish\n"); - ByteBuffer.drop(&out); - drop(&d); -} - -fn test_clear_and_reuse() { - var d = new(); - expect_ok(err(&d, "f", 1, 1, "one")); - expect_ok(warn(&d, "f", 2, 2, "two")); - clear(&d); - @assert(count(d) == 0 && !has_error(d) && warning_count(d) == 0, "cleared"); - - expect_ok(err(&d, "f", 3, 3, "three")); - @assert(count(d) == 1 && error_count(d) == 1, "reuse"); - drop(&d); -} - -fn main() { - test_collect_and_count(); - test_clear_and_reuse(); -} diff --git a/stage2/utils/error.spl b/stage2/utils/error.spl index 5d0cf86..d1057c5 100644 --- a/stage2/utils/error.spl +++ b/stage2/utils/error.spl @@ -1,4 +1,5 @@ // Expect retcode = 0 + // 公共错误值不持有资源,可按值复制;不分配错误消息字符串。 // position 是调用方输入中的从零开始的位置;仅在 has_position 时有效。 // 未定位错误的 position 规范为 0,不能用 position == 0 判断是否有位置。 diff --git a/stage2/utils/fmt.spl b/stage2/utils/fmt.spl index dc93fe9..6a5e082 100644 --- a/stage2/utils/fmt.spl +++ b/stage2/utils/fmt.spl @@ -1,4 +1,5 @@ // Expect retcode = 0 + // 数值/布尔格式化,写入 ByteBuffer。radix 取值 2..36;uppercase 只影响 10..35 的字母形式。 // 不分配额外内存(除目标 ByteBuffer 自身扩容),失败不产生部分输出。 @@ -107,6 +108,7 @@ fn expect_text(buffer: *ByteBuffer, expected: []u8) { fn test_unsigned() { var b = ByteBuffer.new(); + defer { ByteBuffer.drop(&b); } expect_ok(append_u64(&b, 0, 10, false)); expect_text(&b, "0"); @@ -147,12 +149,11 @@ fn test_unsigned() { expect_ok(append_u64(&b, 18446744073709551615, 2, false)); expect_text(&b, "1111111111111111111111111111111111111111111111111111111111111111"); ByteBuffer.clear(&b); - - ByteBuffer.drop(&b); } fn test_signed() { var b = ByteBuffer.new(); + defer { ByteBuffer.drop(&b); } expect_ok(append_i64(&b, 0, 10, false)); expect_text(&b, "0"); @@ -177,12 +178,11 @@ fn test_signed() { expect_ok(append_u64(&b, 48879, 16, true)); expect_text(&b, "BEEF"); ByteBuffer.clear(&b); - - ByteBuffer.drop(&b); } fn test_usize_bool_and_errors() { var b = ByteBuffer.new(); + defer { ByteBuffer.drop(&b); } expect_ok(append_usize(&b, 1000 as usize, 10, false)); expect_text(&b, "1000"); @@ -201,12 +201,11 @@ fn test_usize_bool_and_errors() { expect_invalid(append_u64(&b, 1, 37, false)); expect_invalid(append_i64(&b, -1, 0, false)); @assert(ByteBuffer.size(b) == 0, "invalid radix writes nothing"); - - ByteBuffer.drop(&b); } fn test_composition_and_growth() { var b = ByteBuffer.new(); + defer { ByteBuffer.drop(&b); } expect_ok(ByteBuffer.append(&b, "x=")); expect_ok(append_i64(&b, -42, 10, false)); @@ -229,8 +228,6 @@ fn test_composition_and_growth() { var s = ByteBuffer.as_slice(b); @assert(s.ptr[0] == '0', "starts with 0"); @assert(s.ptr[1] == ',', "0 then comma"); - - ByteBuffer.drop(&b); } fn main() { diff --git a/stage2/utils/hash.spl b/stage2/utils/hash.spl index 6b31505..911371c 100644 --- a/stage2/utils/hash.spl +++ b/stage2/utils/hash.spl @@ -1,4 +1,5 @@ // Expect retcode = 0 + // 哈希工具:FNV-1a 字节哈希 + 整型混合;并给出可直接交给 map 的回调 // (map 的 hash/eq 回调接收指向键字节的指针,这里按 usize / ByteView 解释)。 // 仅提供确定性哈希与等值,不承诺密码学性质。 @@ -7,10 +8,11 @@ type .{ .ByteView } = @import("byte_view.spl"); // FNV-1a 64 位,逐字节;空输入返回 offset basis。 fn hash_bytes(data: *u8, len: usize): usize { - var h: usize = 14695981039346656037; + var h: usize = 14695981039346656037; // FNV-1a 64 位 offset basis + var prime: usize = 1099511628211; // FNV 质数 var i: usize = 0; while i < len { - h = (h ^ (data[i] as usize)) * 1099511628211; + h = (h ^ (data[i] as usize)) * prime; i = i + 1; } ret h; @@ -22,12 +24,13 @@ fn hash_byte_view(view: ByteView): usize { // 整型混合(splitmix 风格的移位-乘法),保证非零与良好扩散。 fn hash_usize_value(value: usize): usize { + var prime: usize = 1099511628211; // splitmix 风格的混合乘子 var h = value; - h = (h ^ (h >> 30)) * 1099511628211; + h = (h ^ (h >> 30)) * prime; h = h ^ (h >> 27); - h = h * 1099511628211; + h = h * prime; h = h ^ (h >> 31); - if h == 0 { ret 1099511628211; } // 0 映射到非零 + if h == 0 { ret prime; } // 0 映射到非零 ret h; } diff --git a/stage2/utils/io.spl b/stage2/utils/io.spl index 90058b3..d322188 100644 --- a/stage2/utils/io.spl +++ b/stage2/utils/io.spl @@ -1,4 +1,5 @@ // Expect retcode = 0 + // I/O 便利层(构建在 sys/file 之上): // - C 字符串路径/模式的 open 包装 // - 整块读写(write_all / read_all) diff --git a/stage2/utils/json.spl b/stage2/utils/json.spl index bfaf081..00eea34 100644 --- a/stage2/utils/json.spl +++ b/stage2/utils/json.spl @@ -4,13 +4,14 @@ // - 输入 ByteView,字符串/数字以“源切片”借用,不做解码,round-trip 保真; // - 节点池 Vec[JsonNode];数组/对象子项存 Vec[JsonChild],保留插入顺序; // - 错误带字节位置(error.spl);输出写 ByteBuffer;缩进/数字用 ByteBuffer/fmt。 -// 限制:不做字符串转义解码、无递归深度保护、数字不做数值化(原样保存)。 +// 限制:不做字符串转义解码、数字不做数值化(原样保存);递归/输出深度上限见 max_depth()。 type .{ .Error, .Status, .make, .at } = @import("error.spl"); type .{ .ByteView, .ViewResult, .FindResult } = @import("byte_view.spl"); type .{ .Vec } = @import("vec.spl"); type .{ .ByteBuffer } = @import("byte_buffer.spl"); type .{ .is_digit, .is_hex_digit, .is_space } = @import("ascii.spl"); +type .{ .checked_mul } = @import("memory.spl"); type .{ .append_i64, .append_u64 } = @import("fmt.spl"); type JsonKind = enum { @@ -29,6 +30,10 @@ type JsonChild = struct { val: usize, } +// 节点:kind 决定有效载荷字段,其余字段保持空。访问方必须按 kind 读取: +// Null/True/False -> text 空、children 空; +// Number/String -> text 为源切片、children 空; +// Array/Object -> children 保存子项、text 空。 type JsonNode = struct { kind: JsonKind, text: ByteView, @@ -57,6 +62,11 @@ type Parser = struct { nodes: Vec, } +// 递归/输出深度上限:防止病态输入耗尽调用栈。256 层足够覆盖正常 JSON。 +fn max_depth(): usize { + ret 256; +} + fn blank_node(kind: JsonKind): JsonNode { ret JsonNode { .kind = kind, @@ -78,7 +88,7 @@ fn intern_node(p: *Parser, node: JsonNode): NodeResult { fn node_at(doc: *Json, index: usize): *JsonNode { match Vec.get(&doc.nodes, index) { .Ok(ptr) => { ret ptr as *JsonNode; }, - .Err(error) => { ret null; }, + .Err(error) => { ret null; }, // 越界/无效池统一返回 null,调用方判空 } ret null; } @@ -122,6 +132,7 @@ fn parse_string_raw(p: *Parser): ViewResult { } } } else if c < 32 { + // JSON 只禁止未转义的 U+0000..U+001F;0x7F 合法,不能用 ascii.is_control。 ret .Err(at(.InvalidFormat, p.pos)); } p.pos = p.pos + 1; @@ -192,7 +203,8 @@ fn parse_number(p: *Parser): NodeResult { } } -fn parse_array(p: *Parser): NodeResult { +fn parse_array(p: *Parser, depth: usize): NodeResult { + if depth >= max_depth() { ret .Err(at(.OutOfRange, p.pos)); } p.pos = p.pos + 1; // '[' var kids = Vec.blank(@sizeof(JsonChild), 8); @@ -201,11 +213,14 @@ fn parse_array(p: *Parser): NodeResult { p.pos = p.pos + 1; var empty = blank_node(.Array); empty.children = kids; - ret intern_node(p, empty); + match intern_node(p, empty) { + .Ok(index) => { ret .Ok(index); }, + .Err(error) => { Vec.drop(&empty.children); ret .Err(error); }, + } } while true { - var element = parse_value(p); + var element = parse_value(p, depth + 1); var index: usize = 0; match element { .Ok(v) => { index = v; }, @@ -232,10 +247,14 @@ fn parse_array(p: *Parser): NodeResult { var node = blank_node(.Array); node.children = kids; - ret intern_node(p, node); + match intern_node(p, node) { + .Ok(index) => { ret .Ok(index); }, + .Err(error) => { Vec.drop(&node.children); ret .Err(error); }, + } } -fn parse_object(p: *Parser): NodeResult { +fn parse_object(p: *Parser, depth: usize): NodeResult { + if depth >= max_depth() { ret .Err(at(.OutOfRange, p.pos)); } p.pos = p.pos + 1; // '{' var kids = Vec.blank(@sizeof(JsonChild), 8); @@ -244,7 +263,10 @@ fn parse_object(p: *Parser): NodeResult { p.pos = p.pos + 1; var empty = blank_node(.Object); empty.children = kids; - ret intern_node(p, empty); + match intern_node(p, empty) { + .Ok(index) => { ret .Ok(index); }, + .Err(error) => { Vec.drop(&empty.children); ret .Err(error); }, + } } while true { @@ -262,7 +284,7 @@ fn parse_object(p: *Parser): NodeResult { } p.pos = p.pos + 1; - var value = parse_value(p); + var value = parse_value(p, depth + 1); var index: usize = 0; match value { .Ok(v) => { index = v; }, @@ -290,10 +312,13 @@ fn parse_object(p: *Parser): NodeResult { var node = blank_node(.Object); node.children = kids; - ret intern_node(p, node); + match intern_node(p, node) { + .Ok(index) => { ret .Ok(index); }, + .Err(error) => { Vec.drop(&node.children); ret .Err(error); }, + } } -fn parse_value(p: *Parser): NodeResult { +fn parse_value(p: *Parser, depth: usize): NodeResult { skip_ws(p); var c = peek(p); if c < 0 { ret .Err(at(.UnexpectedEnd, p.pos)); } @@ -309,8 +334,8 @@ fn parse_value(p: *Parser): NodeResult { node.text = view; ret intern_node(p, node); } - if ch == '{' { ret parse_object(p); } // '{' - if ch == '[' { ret parse_array(p); } // '[' + if ch == '{' { ret parse_object(p, depth); } // '{' + if ch == '[' { ret parse_array(p, depth); } // '[' if ch == 't' { ret parse_literal(p, "true", .True); } if ch == 'f' { ret parse_literal(p, "false", .False); } if ch == 'n' { ret parse_literal(p, "null", .Null); } @@ -325,6 +350,7 @@ fn drop_node_pool(pool: *Vec) { var n = ptr as *JsonNode; Vec.drop(&n.children); }, + // i < pool.len 且池有效,get 不会失败;失败时无额外资源可释放。 .Err(error) => {}, } i = i + 1; @@ -341,7 +367,7 @@ fn parse(src: ByteView): JsonResult { skip_ws(&p); var root: usize = 0; - var result = parse_value(&p); + var result = parse_value(&p, 0); match result { .Ok(index) => { root = index; }, .Err(error) => { @@ -367,12 +393,6 @@ fn root_index(doc: *Json): usize { ret doc.root; } -fn kind_of(doc: *Json, index: usize): JsonKind { - var n = node_at(doc, index); - if n == null { ret .Null; } - ret n.kind; -} - // 枚举 tag 取整(stage1 枚举 == 有缺陷,用 @enum2int 比较更稳妥)。 fn kind_code(kind: JsonKind): usize { ret @enum2int(kind) as usize; @@ -424,6 +444,7 @@ fn object_find(doc: *Json, index: usize, key: ByteView): FindResult { var child = ptr as *JsonChild; if ByteView.eq(child.key, key) { ret .Found(child.val); } }, + // i < count == children.len,get 不会失败。 .Err(error) => {}, } i = i + 1; @@ -452,10 +473,16 @@ fn emit_indent(out: *ByteBuffer, indent: usize, depth: usize): Status { .Ok => {}, .Err(error) => { ret .Err(error); }, } - ret ByteBuffer.append_repeat(out, 32, depth * indent); + var spaces: usize = 0; + match checked_mul(depth, indent) { + .Ok(value) => { spaces = value; }, + .Err(error) => { ret .Err(error); }, + } + ret ByteBuffer.append_repeat(out, 32, spaces); } fn emit(out: *ByteBuffer, doc: *Json, index: usize, indent: usize, depth: usize): Status { + if depth >= max_depth() { ret .Err(make(.OutOfRange)); } var n = node_at(doc, index); if n == null { ret .Err(make(.OutOfRange)); } @@ -738,19 +765,44 @@ fn test_errors() { expect_parse_error(parse(ByteView.from_slice("01")), 1); } +fn test_depth_limit() { + var out = ByteBuffer.new(); + defer { ByteBuffer.drop(&out); } + var i: usize = 0; + while i < max_depth() + 8 { + expect_ok(ByteBuffer.append_byte(&out, '[')); + i = i + 1; + } + i = 0; + while i < max_depth() + 8 { + expect_ok(ByteBuffer.append_byte(&out, ']')); + i = i + 1; + } + match parse(ByteView.from_slice(ByteBuffer.as_slice(out))) { + .Ok(doc) => { drop(&doc); @assert(false, "deep input should be rejected"); }, + .Err(error) => { + match error.kind { + .OutOfRange => {}, + _ => { @assert(false, "depth limit error kind"); }, + } + }, + } +} + fn test_construction_helpers() { var out = ByteBuffer.new(); + defer { ByteBuffer.drop(&out); } expect_ok(ByteBuffer.append(&out, "{\"id\":")); expect_ok(write_u64(&out, 18446744073709551615)); expect_ok(ByteBuffer.append(&out, ",\"n\":")); expect_ok(write_int(&out, -7)); expect_ok(ByteBuffer.append(&out, "}")); expect_bytes(&out, "{\"id\":18446744073709551615,\"n\":-7}"); - ByteBuffer.drop(&out); } fn test_large_array() { var out = ByteBuffer.new(); + defer { ByteBuffer.drop(&out); } expect_ok(ByteBuffer.append_byte(&out, '[')); // '[' var i: usize = 0; while i < 1000 { @@ -771,7 +823,6 @@ fn test_large_array() { }, .Err(error) => { @assert(false, "parse large array"); }, } - ByteBuffer.drop(&out); } fn main() { @@ -780,6 +831,7 @@ fn main() { test_roundtrip_compact(); test_pretty(); test_errors(); + test_depth_limit(); test_construction_helpers(); test_large_array(); } diff --git a/stage2/utils/frame.spl b/stage2/utils/lsp_frame.spl similarity index 82% rename from stage2/utils/frame.spl rename to stage2/utils/lsp_frame.spl index e0cc6b5..088a29a 100644 --- a/stage2/utils/frame.spl +++ b/stage2/utils/lsp_frame.spl @@ -1,4 +1,5 @@ // Expect retcode = 0 + // LSP 传输帧:Content-Length 头 + 空行 + 正文。 // 这里只做纯逻辑(写入/解析 ByteBuffer/ByteView),真正的 stdin/stdout I/O 由上层负责。 @@ -6,6 +7,8 @@ type .{ .Error, .Status, .make } = @import("error.spl"); type .{ .ByteView } = @import("byte_view.spl"); type .{ .ByteBuffer } = @import("byte_buffer.spl"); type .{ .append_usize } = @import("fmt.spl"); +type .{ .checked_add, .checked_mul } = @import("memory.spl"); +type .{ .to_lower } = @import("ascii.spl"); type FrameResult = enum { Ok: usize, // 消耗的字节数 @@ -13,17 +16,12 @@ type FrameResult = enum { Err: Error, } -fn ascii_lower(c: u8): u8 { - if c >= 'A' && c <= 'Z' { ret c + ('a' - 'A'); } - ret c; -} - // data[j..] 是否以 word 开头(ASCII 大小写不敏感)。 fn ci_prefix(data: *u8, len: usize, j: usize, word: []u8): bool { if j + word.len > len { ret false; } var i: usize = 0; while i < word.len { - if ascii_lower(data[j + i]) != ascii_lower(word.ptr[i]) { ret false; } + if to_lower(data[j + i]) != to_lower(word.ptr[i]) { ret false; } i = i + 1; } ret true; @@ -64,7 +62,16 @@ fn parse_frame(input: ByteView, body_out: *ByteBuffer): FrameResult { var n: usize = 0; var digits = 0; while k < term && data[k] >= '0' && data[k] <= '9' { - n = n * 10 + (data[k] - '0') as usize; + var digit = (data[k] - '0') as usize; + var scaled: usize = 0; + match checked_mul(n, 10) { + .Ok(value) => { scaled = value; }, + .Err(error) => { ret .Err(error); }, + } + match checked_add(scaled, digit) { + .Ok(value) => { n = value; }, + .Err(error) => { ret .Err(error); }, + } digits = digits + 1; k = k + 1; } @@ -80,9 +87,15 @@ fn parse_frame(input: ByteView, body_out: *ByteBuffer): FrameResult { var body_start = term + 4; if len < body_start || content_len > len - body_start { ret .Incomplete; } - ByteBuffer.clear(body_out); + // 先写入临时缓冲区,成功后再替换 body_out,失败不产生部分输出。 + var fresh = ByteBuffer.new(); var body = data[body_start..body_start + content_len]; - match ByteBuffer.append(body_out, body) { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append(&fresh, body) { + .Ok => {}, + .Err(e) => { ByteBuffer.drop(&fresh); ret .Err(e); }, + } + ByteBuffer.drop(body_out); + body_out.* = ByteBuffer.take(&fresh); ret .Ok(body_start + content_len); } @@ -107,11 +120,13 @@ fn expect_body(out: *ByteBuffer, expected: []u8) { fn test_roundtrip() { var wire = ByteBuffer.new(); + defer { ByteBuffer.drop(&wire); } expect_ok(write_frame(&wire, "{\"jsonrpc\":\"2.0\"}")); var bytes = ByteBuffer.as_slice(wire); @assert(bytes.len == 16 + 2 + 4 + 17, "wire length"); // "Content-Length: 17\r\n\r\n" + body var body = ByteBuffer.new(); + defer { ByteBuffer.drop(&body); } match parse_frame(ByteView.from_slice(bytes), &body) { .Ok(used) => { @assert(used == bytes.len, "consumed all"); @@ -120,17 +135,17 @@ fn test_roundtrip() { .Incomplete => { @assert(false, "unexpected incomplete"); }, .Err(error) => { @assert(false, "unexpected err"); }, } - ByteBuffer.drop(&body); - ByteBuffer.drop(&wire); } fn test_multiple_and_leftover() { var wire = ByteBuffer.new(); + defer { ByteBuffer.drop(&wire); } expect_ok(write_frame(&wire, "abcd")); expect_ok(write_frame(&wire, "ef")); var bytes = ByteBuffer.as_slice(wire); var body = ByteBuffer.new(); + defer { ByteBuffer.drop(&body); } match parse_frame(ByteView.from_slice(bytes), &body) { .Ok(used) => { expect_body(&body, "abcd"); @@ -142,12 +157,11 @@ fn test_multiple_and_leftover() { }, _ => { @assert(false, "first frame"); }, } - ByteBuffer.drop(&body); - ByteBuffer.drop(&wire); } fn test_incomplete_and_errors() { var body = ByteBuffer.new(); + defer { ByteBuffer.drop(&body); } // 头未结束 var h1 = "Content-Length: 3\r\n"; @@ -174,7 +188,6 @@ fn test_incomplete_and_errors() { .Ok(used) => { expect_body(&body, "hi"); }, _ => { @assert(false, "h4"); }, } - ByteBuffer.drop(&body); } fn main() { diff --git a/stage2/utils/map.spl b/stage2/utils/map.spl index ed9670e..90c165e 100644 --- a/stage2/utils/map.spl +++ b/stage2/utils/map.spl @@ -1,4 +1,5 @@ // Expect retcode = 0 + // 开放寻址 + tombstone 哈希表。键和值都按字节复制到表自己的存储。 // 键类型由 hash_fn / eq_fn 解释:回调收到指向键字节的指针(如 *usize / *ByteView)。 // 普通赋值只复制句柄(与 Vec 一样),所有权转移必须使用 take。 @@ -7,11 +8,29 @@ type .{ .Error, .Status, .make } = @import("error.spl"); type .{ .Vec } = @import("vec.spl"); type .{ .ByteView } = @import("byte_view.spl"); type .{ .hash_usize, .eq_usize, .hash_view, .eq_view } = @import("hash.spl"); +type .{ .checked_add, .checked_mul, .grow_capacity } = @import("memory.spl"); type .{ .copy } = @import("../sys/mem.spl"); -// 槽位状态:0=EMPTY,1=OCCUPIED,2=DELETED(墓碑,仅在扩容重哈希时清除)。 +// 槽位状态:Empty/Occupied/Deleted(墓碑,仅在扩容重哈希时清除)。 // 负载因子 70/128。 +type SlotState = enum { + Empty, + Occupied, + Deleted, +} + +fn state_code(state: SlotState): u8 { + var s: SlotState = state; + ret @enum2int(s) as u8; +} + +// 查找/删除结果:显式区分命中与缺失,替代 bool 哨兵。 +type LookupResult = enum { + Found, + NotFound, +} + type Map = @self; keys: Vec, @@ -64,12 +83,22 @@ fn slot_count(self: Map): usize { ret self.states.len; } -fn slot_state(self: Map, index: usize): u8 { - ret self.states.data[index]; +fn slot_state(self: Map, index: usize): SlotState { + var code = self.states.data[index]; + var occupied: SlotState = .Occupied; + var deleted_state: SlotState = .Deleted; + if code == state_code(occupied) { ret .Occupied; } + if code == state_code(deleted_state) { ret .Deleted; } + ret .Empty; } -fn slot_occupied(state: u8): bool { - ret state == 1; +fn slot_occupied(state: SlotState): bool { + match state { + .Occupied => { ret true; }, + .Empty => { ret false; }, + .Deleted => { ret false; }, + } + ret false; } fn key_at(self: *Map, index: usize): *u8 { @@ -85,8 +114,11 @@ fn find_slot(self: *Map, key: *u8): usize { if self.states.cap == 0 { ret ~(0 as usize); } var mask = self.states.cap - 1; var i = self.hash_fn(key) & mask; - while self.states.data[i] != 0 { - if self.states.data[i] == 1 && self.eq_fn(self.keys.data + i * self.key_size, key) { + var empty: SlotState = .Empty; + var occupied: SlotState = .Occupied; + while self.states.data[i] != state_code(empty) { + if self.states.data[i] == state_code(occupied) && + self.eq_fn(self.keys.data + i * self.key_size, key) { ret i; } i = (i + 1) & mask; @@ -98,12 +130,14 @@ fn find_slot(self: *Map, key: *u8): usize { fn place(self: *Map, key: *u8, val: *u8) { var mask = self.states.cap - 1; var i = self.hash_fn(key) & mask; - while self.states.data[i] != 0 { + var empty: SlotState = .Empty; + var occupied: SlotState = .Occupied; + while self.states.data[i] != state_code(empty) { i = (i + 1) & mask; } copy((self.keys.data + i * self.key_size) as *any, key as *any, self.key_size); copy((self.vals.data + i * self.val_size) as *any, val as *any, self.val_size); - self.states.data[i] = 1; + self.states.data[i] = state_code(occupied); self.size = self.size + 1; } @@ -112,15 +146,7 @@ fn reserve(self: *Map, need: usize): Status { if !valid(self.*) { ret .Err(make(.InvalidArgument)); } if need <= self.states.cap { ret .Ok; } - var new_cap = self.states.cap; - if new_cap < 8 { new_cap = 8; } - while new_cap < need { - if new_cap > (~(0 as usize)) / 2 { - new_cap = need; - break; - } - new_cap = new_cap * 2; - } + var new_cap = grow_capacity(self.states.cap, 8, need); var fresh = self.*; fresh.keys = Vec.blank(self.key_size, self.keys.elem_align); @@ -145,14 +171,16 @@ fn reserve(self: *Map, need: usize): Status { fresh.states.len = new_cap; var i: usize = 0; + var empty: SlotState = .Empty; + var occupied: SlotState = .Occupied; while i < new_cap { - fresh.states.data[i] = 0; + fresh.states.data[i] = state_code(empty); i = i + 1; } i = 0; while i < self.states.len { - if self.states.data[i] == 1 { + if self.states.data[i] == state_code(occupied) { place(&fresh, self.keys.data + i * self.key_size, self.vals.data + i * self.val_size); } @@ -181,42 +209,64 @@ fn put(self: *Map, key: *u8, val: *u8): Status { ret .Ok; } - if self.states.cap == 0 || (self.size + 1) * 128 / self.states.cap >= 70 { - var need = self.states.cap * 2; - if self.states.cap == 0 { need = 8; } - match reserve(self, need) { + if self.states.cap == 0 { + match reserve(self, 8) { .Ok => {}, .Err(error) => { ret .Err(error); }, } + } else { + var next_size: usize = 0; + match checked_add(self.size, 1) { + .Ok(value) => { next_size = value; }, + .Err(error) => { ret .Err(error); }, + } + var scaled: usize = 0; + match checked_mul(next_size, 128) { + .Ok(value) => { scaled = value; }, + .Err(error) => { ret .Err(error); }, + } + if scaled / self.states.cap >= 70 { + var need: usize = 0; + match checked_mul(self.states.cap, 2) { + .Ok(value) => { need = value; }, + .Err(error) => { ret .Err(error); }, + } + match reserve(self, need) { + .Ok => {}, + .Err(error) => { ret .Err(error); }, + } + } } place(self, key, val); ret .Ok; } -// 找到键则把值复制到 out 并返回 true;否则 out 不变并返回 false。 -fn get(self: *Map, key: *u8, out: *u8): bool { - if !valid(self.*) || key == null || out == null { ret false; } +// 找到键则把值复制到 out 并返回 Found;否则 out 不变并返回 NotFound。 +fn get(self: *Map, key: *u8, out: *u8): LookupResult { + if !valid(self.*) || key == null || out == null { ret .NotFound; } var i = find_slot(self, key); - if i == ~(0 as usize) { ret false; } + if i == ~(0 as usize) { ret .NotFound; } copy(out as *any, (self.vals.data + i * self.val_size) as *any, self.val_size); - ret true; + ret .Found; } -fn del(self: *Map, key: *u8): bool { - if !valid(self.*) || key == null { ret false; } +fn del(self: *Map, key: *u8): LookupResult { + if !valid(self.*) || key == null { ret .NotFound; } var i = find_slot(self, key); - if i == ~(0 as usize) { ret false; } - self.states.data[i] = 2; + if i == ~(0 as usize) { ret .NotFound; } + var deleted_state: SlotState = .Deleted; + self.states.data[i] = state_code(deleted_state); self.size = self.size - 1; - ret true; + ret .Found; } // 清空内容但保留容量。墓碑与负载计数一并复位。 fn clear(self: *Map) { var i: usize = 0; + var empty: SlotState = .Empty; while i < self.states.len { - self.states.data[i] = 0; + self.states.data[i] = state_code(empty); i = i + 1; } self.size = 0; @@ -269,6 +319,22 @@ fn count_occupied(m: *Map): usize { ret n; } +fn found(m: *Map, key: *u8, out: *u8): bool { + match get(m, key, out) { + .Found => { ret true; }, + .NotFound => { ret false; }, + } + ret false; +} + +fn deleted(m: *Map, key: *u8): bool { + match del(m, key) { + .Found => { ret true; }, + .NotFound => { ret false; }, + } + ret false; +} + fn make_int_map(): Map { ret new(@sizeof(usize), @alignof(usize), @sizeof(i32), @alignof(i32), hash_usize, eq_usize); } @@ -292,24 +358,26 @@ fn test_put_get_update() { @assert(size(m) == 1 && valid(m)); out = -1; - @assert(get(&m, &key as *u8, &out as *u8) && out == 70, "insert then get"); + @assert(found(&m, &key as *u8, &out as *u8) && out == 70, "insert then get"); value = 71; expect_ok(put(&m, &key as *u8, &value as *u8)); @assert(size(m) == 1, "update does not grow size"); out = -1; - @assert(get(&m, &key as *u8, &out as *u8) && out == 71, "update overwrites"); + @assert(found(&m, &key as *u8, &out as *u8) && out == 71, "update overwrites"); key = 8; out = 123; - @assert(!get(&m, &key as *u8, &out as *u8) && out == 123, "missing key preserves out"); + @assert(!found(&m, &key as *u8, &out as *u8) && out == 123, "missing key preserves out"); + // drop 后还有断言,故保持手动释放,不改为 defer。 drop(&m); @assert(size(m) == 0 && capacity(m) == 0, "drop resets map"); } fn test_growth_and_delete() { var m = make_int_map(); + defer { drop(&m); } var i: i32 = 0; while i < 1000 { var key: usize = i as usize; @@ -324,7 +392,7 @@ fn test_growth_and_delete() { while i < 1000 { var key: usize = i as usize; var out: i32 = 0; - @assert(get(&m, &key as *u8, &out as *u8), "get after growth"); + @assert(found(&m, &key as *u8, &out as *u8), "get after growth"); @assert(out == i * 3 - 7, "value after growth"); i = i + 1; } @@ -332,7 +400,7 @@ fn test_growth_and_delete() { i = 0; while i < 500 { var key: usize = i as usize; - @assert(del(&m, &key as *u8)); + @assert(deleted(&m, &key as *u8)); i = i + 1; } @assert(size(m) == 500 && count_occupied(&m) == 500, "deleted slots not occupied"); @@ -341,7 +409,7 @@ fn test_growth_and_delete() { while i < 500 { var key: usize = i as usize; var out: i32 = 0; - @assert(!get(&m, &key as *u8, &out as *u8), "deleted key missing"); + @assert(!found(&m, &key as *u8, &out as *u8), "deleted key missing"); i = i + 1; } @@ -351,41 +419,40 @@ fn test_growth_and_delete() { expect_ok(put(&m, &key as *u8, &value as *u8)); @assert(size(m) == 501); var out: i32 = 0; - @assert(get(&m, &key as *u8, &out as *u8) && out == 99, "reuse tombstone"); + @assert(found(&m, &key as *u8, &out as *u8) && out == 99, "reuse tombstone"); - @assert(del(&m, &key as *u8), "delete key"); + @assert(deleted(&m, &key as *u8), "delete key"); @assert(size(m) == 500); - @assert(!get(&m, &key as *u8, &out as *u8)); + @assert(!found(&m, &key as *u8, &out as *u8)); clear(&m); @assert(size(m) == 0 && count_occupied(&m) == 0, "clear empties"); - - drop(&m); } fn test_take_and_self_put() { var m = make_int_map(); + defer { drop(&m); } var key: usize = 5; var value: i32 = 55; expect_ok(put(&m, &key as *u8, &value as *u8)); var moved = take(&m); + defer { drop(&moved); } @assert(size(m) == 0 && capacity(m) == 0, "take leaves source empty"); var out: i32 = 0; - @assert(get(&moved, &key as *u8, &out as *u8) && out == 55, "moved keeps content"); + @assert(found(&moved, &key as *u8, &out as *u8) && out == 55, "moved keeps content"); // 值指针可以指向表内已有的值(按字节移动,允许重叠)。 var alias: i32 = 0; - @assert(get(&moved, &key as *u8, &alias as *u8)); + @assert(found(&moved, &key as *u8, &alias as *u8)); expect_ok(put(&moved, &key as *u8, &alias as *u8)); - @assert(get(&moved, &key as *u8, &out as *u8) && out == 55, "self value put"); - - drop(&moved); + @assert(found(&moved, &key as *u8, &out as *u8) && out == 55, "self value put"); } fn test_byte_view_keys() { var m = new(@sizeof(ByteView), @alignof(ByteView), @sizeof(i32), @alignof(i32), hash_view, eq_view); + defer { drop(&m); } var alpha = ByteView.from_slice("alpha"); var beta = ByteView.from_slice("beta"); @@ -400,21 +467,19 @@ fn test_byte_view_keys() { @assert(size(m) == 3); var out: i32 = 0; - @assert(get(&m, &alpha as *u8, &out as *u8) && out == 1, "alpha"); - @assert(get(&m, &beta as *u8, &out as *u8) && out == 2, "beta"); - @assert(get(&m, &gamma as *u8, &out as *u8) && out == 3, "gamma"); + @assert(found(&m, &alpha as *u8, &out as *u8) && out == 1, "alpha"); + @assert(found(&m, &beta as *u8, &out as *u8) && out == 2, "beta"); + @assert(found(&m, &gamma as *u8, &out as *u8) && out == 3, "gamma"); // 内容相同的不同 ByteView 视为同一个键。 var alpha2 = ByteView.from_slice("alpha"); out = 0; - @assert(get(&m, &alpha2 as *u8, &out as *u8) && out == 1, "equal content key"); + @assert(found(&m, &alpha2 as *u8, &out as *u8) && out == 1, "equal content key"); @assert(size(m) == 3, "equal content does not insert"); - @assert(del(&m, &beta as *u8), "delete beta"); + @assert(deleted(&m, &beta as *u8), "delete beta"); @assert(size(m) == 2); - @assert(!get(&m, &beta as *u8, &out as *u8)); - - drop(&m); + @assert(!found(&m, &beta as *u8, &out as *u8)); } fn main() { diff --git a/stage2/utils/memory.spl b/stage2/utils/memory.spl index 6fa23b3..31d5bc2 100644 --- a/stage2/utils/memory.spl +++ b/stage2/utils/memory.spl @@ -1,4 +1,5 @@ // Expect retcode = 0 + // 尺寸计算和字节移动;不分配、不释放内存,不改变任何资源的所有权。 type .{ .Error, .Status, .make } = @import("error.spl"); @@ -31,6 +32,22 @@ fn align_up(size: usize, alignment: usize): SizeResult { ret .Ok((size + mask) & ~mask); } +// 计算至少 need 的容量:从 current 起(不低于 min_cap)按倍增增长; +// 无法再倍增(会溢出)时直接取 need。need 可表示时不溢出。 +fn grow_capacity(current: usize, min_cap: usize, need: usize): usize { + var cap = current; + if cap < min_cap { cap = min_cap; } + var limit: usize = ~(0 as usize); + while cap < need { + if cap > limit / 2 { + cap = need; + break; + } + cap = cap * 2; + } + ret cap; +} + // 将 src.len 个字节复制到 dst 的开头,允许两个切片任意重叠。 // 零长度源不访问内存,允许空指针。所有可恢复错误均在首次写入前返回。 // 非空源要求可读,目标前 src.len 字节要求可写,生命周期由调用方保证。 @@ -164,6 +181,13 @@ fn test_sizes() { expect_size(align_up(1, highest_power), highest_power); expect_size(align_up(highest_power, highest_power), highest_power); expect_overflow(align_up(highest_power + 1, highest_power)); + + @assert(grow_capacity(0, 8, 0) == 8, "grow respects minimum"); + @assert(grow_capacity(0, 8, 5) == 8, "grow minimum covers need"); + @assert(grow_capacity(8, 8, 9) == 16, "grow doubles"); + @assert(grow_capacity(8, 8, 100) == 128, "grow repeats"); + @assert(grow_capacity(limit / 2, 8, limit) == limit, "grow exact to limit"); + @assert(grow_capacity(limit / 2 + 1, 8, limit) == limit, "grow clamps to need"); } // 穷举同一 8 字节数组内的所有合法源、目标和长度组合。 diff --git a/stage2/utils/path.spl b/stage2/utils/path.spl index 3ba5cbe..be857f4 100644 --- a/stage2/utils/path.spl +++ b/stage2/utils/path.spl @@ -1,4 +1,5 @@ // Expect retcode = 0 + // 路径工具(不访问文件系统):同时接受 '/' 与 '\\';支持 Windows 盘符 "C:"。 // 输入为借用 ByteView,输出写入调用方的 ByteBuffer。 // 注意:normalize 对相对路径的前导 ".." 采取丢弃(不保留),适用于导入路径场景。 @@ -7,13 +8,14 @@ type .{ .Error, .Status, .make } = @import("error.spl"); type .{ .ByteView } = @import("byte_view.spl"); type .{ .ByteBuffer } = @import("byte_buffer.spl"); type .{ .Vec } = @import("vec.spl"); +type .{ .is_alpha } = @import("ascii.spl"); fn is_sep(c: u8): bool { ret c == '/' || c == '\\'; // '/' '\\' } fn is_drive(path: ByteView): bool { - ret path.len >= 2 && path.ptr[1] == ':'; // ':' + ret path.len >= 2 && is_alpha(path.ptr[0]) && path.ptr[1] == ':'; // 盘符首字符必须是字母 } fn is_absolute(path: ByteView): bool { @@ -33,6 +35,7 @@ fn last_sep(path: ByteView): usize { } // 目录部分(不含末尾分隔符);根 "/" 返回 "/";无分隔符返回空。 +// 下标均由 last_sep 保证在界内,slice 不会失败;Err 分支仅作防御。 fn dirname(path: ByteView): ByteView { var idx = last_sep(path); if idx == ~(0 as usize) { ret ByteView.empty(); } @@ -40,13 +43,13 @@ fn dirname(path: ByteView): ByteView { var r = ByteView.slice(path, 0, 1); match r { .Ok(v) => { ret v; }, - .Err(error) => { ret ByteView.empty(); }, + .Err(error) => { ret ByteView.empty(); }, // 防御:索引已界内 } } var r = ByteView.slice(path, 0, idx); match r { .Ok(v) => { ret v; }, - .Err(error) => { ret ByteView.empty(); }, + .Err(error) => { ret ByteView.empty(); }, // 防御:索引已界内 } ret ByteView.empty(); } @@ -57,7 +60,7 @@ fn basename(path: ByteView): ByteView { var r = ByteView.slice(path, idx + 1, path.len); match r { .Ok(v) => { ret v; }, - .Err(error) => { ret ByteView.empty(); }, + .Err(error) => { ret ByteView.empty(); }, // 防御:idx+1 <= len } ret ByteView.empty(); } @@ -73,39 +76,68 @@ fn extension(path: ByteView): ByteView { var r = ByteView.slice(base, i + 1, base.len); match r { .Ok(v) => { ret v; }, - .Err(error) => { ret ByteView.empty(); }, + .Err(error) => { ret ByteView.empty(); }, // 防御:i+1 <= base.len } } } ret ByteView.empty(); } -// out = dir + sep + rel(rel 绝对时直接使用 rel);先清空 out。 +// out = dir + sep + rel(rel 绝对时直接使用 rel);成功后才替换 out,失败保持 out 不变。 fn join_into(out: *ByteBuffer, dir: ByteView, rel: ByteView): Status { - ByteBuffer.clear(out); + var fresh = ByteBuffer.new(); if is_absolute(rel) { - ret ByteBuffer.append(out, ByteView.as_slice(rel)); + match ByteBuffer.append(&fresh, ByteView.as_slice(rel)) { + .Ok => {}, + .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); }, + } + ByteBuffer.drop(out); + out.* = ByteBuffer.take(&fresh); + ret .Ok; + } + match ByteBuffer.append(&fresh, ByteView.as_slice(dir)) { + .Ok => {}, + .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); }, } - match ByteBuffer.append(out, ByteView.as_slice(dir)) { .Ok => {}, .Err(e) => { ret .Err(e); } } if dir.len != 0 && !is_sep(dir.ptr[dir.len - 1]) { - match ByteBuffer.append_byte(out, '/') { .Ok => {}, .Err(e) => { ret .Err(e); } } + match ByteBuffer.append_byte(&fresh, '/') { + .Ok => {}, + .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); }, + } } - ret ByteBuffer.append(out, ByteView.as_slice(rel)); + match ByteBuffer.append(&fresh, ByteView.as_slice(rel)) { + .Ok => {}, + .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); }, + } + ByteBuffer.drop(out); + out.* = ByteBuffer.take(&fresh); + ret .Ok; } // 折叠重复分隔符、'.',及'..'(回退上一段;相对路径前导 '..' 丢弃)。 +// 成功后才替换 out,失败保持 out 不变。 fn normalize_into(out: *ByteBuffer, path: ByteView): Status { - ByteBuffer.clear(out); - if path.len == 0 { ret .Ok; } + var fresh = ByteBuffer.new(); + if path.len == 0 { + ByteBuffer.drop(out); + out.* = ByteBuffer.take(&fresh); + ret .Ok; + } var starts = Vec.blank(@sizeof(usize), 8); var i: usize = 0; if is_sep(path.ptr[0]) { - match ByteBuffer.append_byte(out, '/') { .Ok => {}, .Err(e) => { Vec.drop(&starts); ret .Err(e); } } + match ByteBuffer.append_byte(&fresh, '/') { + .Ok => {}, .Err(e) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(e); }, + } } else if is_drive(path) { - match ByteBuffer.append_byte(out, path.ptr[0]) { .Ok => {}, .Err(e) => { Vec.drop(&starts); ret .Err(e); } } - match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { Vec.drop(&starts); ret .Err(e); } } + match ByteBuffer.append_byte(&fresh, path.ptr[0]) { + .Ok => {}, .Err(e) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(e); }, + } + match ByteBuffer.append_byte(&fresh, ':') { + .Ok => {}, .Err(e) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(e); }, + } i = 2; // 跳过盘符 } @@ -119,7 +151,7 @@ fn normalize_into(out: *ByteBuffer, path: ByteView): Status { var seg = ByteView.empty(); match seg_result { .Ok(v) => { seg = v; }, - .Err(error) => { Vec.drop(&starts); ret .Err(error); }, + .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); }, } if ByteView.eq(seg, ByteView.from_slice(".")) { continue; } @@ -127,30 +159,34 @@ fn normalize_into(out: *ByteBuffer, path: ByteView): Status { if starts.len > 0 { var idx = starts.len - 1; match Vec.get(&starts, idx) { - .Ok(p) => { out.storage.len = (p as *usize).*; }, - .Err(error) => {}, + .Ok(p) => { fresh.storage.len = (p as *usize).*; }, + .Err(error) => {}, // starts.len 已保证 idx 在界内,get 不会失败 } starts.len = starts.len - 1; } continue; } - var comp_start = out.storage.len; - if comp_start > 0 && !is_sep(out.storage.data[comp_start - 1]) { - match ByteBuffer.append_byte(out, '/') { .Ok => {}, .Err(e) => { Vec.drop(&starts); ret .Err(e); } } + var comp_start = fresh.storage.len; + if comp_start > 0 && !is_sep(fresh.storage.data[comp_start - 1]) { + match ByteBuffer.append_byte(&fresh, '/') { + .Ok => {}, .Err(e) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(e); }, + } } var recorded = comp_start; match Vec.push(&starts, &recorded as *u8) { .Ok => {}, - .Err(error) => { Vec.drop(&starts); ret .Err(error); }, + .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); }, } - match ByteBuffer.append(out, ByteView.as_slice(seg)) { + match ByteBuffer.append(&fresh, ByteView.as_slice(seg)) { .Ok => {}, - .Err(error) => { Vec.drop(&starts); ret .Err(error); }, + .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); }, } } Vec.drop(&starts); + ByteBuffer.drop(out); + out.* = ByteBuffer.take(&fresh); ret .Ok; } @@ -200,17 +236,18 @@ fn test_parts() { fn test_join() { var out = ByteBuffer.new(); + defer { ByteBuffer.drop(&out); } expect_ok(join_into(&out, view("stage2/utils"), view("vec.spl"))); expect_out(&out, "stage2/utils/vec.spl"); expect_ok(join_into(&out, view("a/"), view("b.spl"))); expect_out(&out, "a/b.spl"); expect_ok(join_into(&out, view("ignored"), view("/abs/x"))); expect_out(&out, "/abs/x"); - ByteBuffer.drop(&out); } fn test_normalize() { var out = ByteBuffer.new(); + defer { ByteBuffer.drop(&out); } expect_ok(normalize_into(&out, view("a/b/../c"))); expect_out(&out, "a/c"); expect_ok(normalize_into(&out, view("./a//b"))); @@ -221,7 +258,6 @@ fn test_normalize() { expect_out(&out, "C:/b"); expect_ok(normalize_into(&out, view("a/.."))); expect_out(&out, ""); - ByteBuffer.drop(&out); } fn main() { diff --git a/stage2/utils/source.spl b/stage2/utils/source.spl index 7a7c388..ad1903a 100644 --- a/stage2/utils/source.spl +++ b/stage2/utils/source.spl @@ -173,7 +173,10 @@ fn set_at(set: *Sources, index: usize): *Source { fn set_find(set: *Sources, fname: ByteView): FindResult { var out: usize = 0; - if Map.get(&set.by_name, &fname as *u8, &out as *u8) { ret .Found(out); } + match Map.get(&set.by_name, &fname as *u8, &out as *u8) { + .Found => { ret .Found(out); }, + .NotFound => {}, + } ret .NotFound; } diff --git a/stage2/utils/str.spl b/stage2/utils/str.spl index 889126b..be08213 100644 --- a/stage2/utils/str.spl +++ b/stage2/utils/str.spl @@ -138,6 +138,7 @@ fn expect_text(s: *Str, expected: []u8) { fn test_construction() { var s = new(); + defer { drop(&s); } @assert(is_empty(s) && size(s) == 0, "empty new"); expect_ok(append(&s, "hello")); expect_ok(append_byte(&s, ' ')); @@ -154,18 +155,19 @@ fn test_construction() { }, .Err(error) => { @assert(false, "from_slice"); }, } - drop(&s); } fn test_compare_and_search() { var hello_r = from_slice("hello"); var hello: Str = new(); + defer { drop(&hello); } match hello_r { .Ok(v) => { hello = v; }, .Err(error) => { @assert(false, "hello"); }, } var help_r = from_slice("help"); var help: Str = new(); + defer { drop(&help); } match help_r { .Ok(v) => { help = v; }, .Err(error) => { @assert(false, "help"); }, @@ -181,12 +183,14 @@ fn test_compare_and_search() { @assert(ends_with(hello, hello), "ends_with"); var pre_r = from_slice("he"); var pre: Str = new(); + defer { drop(&pre); } match pre_r { .Ok(v) => { pre = v; }, .Err(e) => { @assert(false, "pre"); } } @assert(starts_with(hello, pre), "starts he"); @assert(!ends_with(hello, pre), "!ends he"); var needle_r = from_slice("ll"); var needle: Str = new(); + defer { drop(&needle); } match needle_r { .Ok(v) => { needle = v; }, .Err(e) => { @assert(false, "needle"); } } match find(hello, needle) { .Found(index) => { @assert(index == 2, "find ll"); }, @@ -196,18 +200,15 @@ fn test_compare_and_search() { .Found(index) => { @assert(index == 0, "find he"); }, .NotFound => { @assert(false, "he not found"); }, } - - drop(&needle); - drop(&pre); - drop(&help); - drop(&hello); } fn test_clone_take_and_failures() { var source = new(); + defer { drop(&source); } expect_ok(append(&source, "owned")); var copy = new(); + defer { drop(©); } expect_ok(clone(©, source)); expect_text(©, "owned"); expect_ok(append_byte(&source, '!')); // '!' @@ -219,16 +220,13 @@ fn test_clone_take_and_failures() { expect_text(©, "owned"); var moved = take(©); + defer { drop(&moved); } @assert(ByteBuffer.size(copy.storage) == 0 && ByteBuffer.capacity(copy.storage) == 0, "take empties source"); expect_text(&moved, "owned"); clear(&moved); @assert(is_empty(moved) && ByteBuffer.capacity(moved.storage) != 0, "clear keeps capacity"); - - drop(&moved); - drop(©); - drop(&source); } fn main() { diff --git a/stage2/utils/utf8.spl b/stage2/utils/utf8.spl index e4ba7ec..89732dc 100644 --- a/stage2/utils/utf8.spl +++ b/stage2/utils/utf8.spl @@ -1,60 +1,70 @@ // Expect retcode = 0 + // UTF-8 工具:解码/编码、校验、码点数、以及 UTF-8 字节偏移 <-> UTF-16 code unit 偏移 // (LSP 位置基于 UTF-16)。严格校验过长编码与代理区。 type .{ .Error, .Status, .make } = @import("error.spl"); type .{ .ByteBuffer } = @import("byte_buffer.spl"); -// 由首字节推断序列长度;非法/续字节返回 0。 -fn seq_len(b: u8): usize { - if b < 0x80 { ret 1; } - if b < 0xC0 { ret 0; } // 续字节 - if b < 0xE0 { ret 2; } - if b < 0xF0 { ret 3; } - if b < 0xF8 { ret 4; } - ret 0; +// 解码结果:Ok 携带消耗字节数;明确区分越界/截断/非法,避免 0 哨兵歧义。 +type DecodeResult = enum { + Ok: usize, + Eof, // index 已越过输入 + Truncated, // 序列尾部字节不足 + Invalid, // 续字节/过长编码/代理区/超出码点范围 } -// 从 index 解码一个码点到 out_cp,返回消耗字节数;非法返回 0。 -fn decode(data: *u8, len: usize, index: usize, out_cp: *u32): usize { - if index >= len { ret 0; } +// 从 index 解码一个码点到 out_cp;返回 DecodeResult。 +fn decode(data: *u8, len: usize, index: usize, out_cp: *u32): DecodeResult { + if index >= len { ret .Eof; } var b0 = data[index]; if b0 < 0x80 { out_cp.* = b0 as u32; - ret 1; + ret .Ok(1); } - if b0 < 0xC2 { ret 0; } // 续字节或过长编码的前导 + if b0 < 0xC2 { ret .Invalid; } // 续字节或过长编码的前导 if b0 < 0xE0 { - if index + 1 >= len { ret 0; } + if index + 1 >= len { ret .Truncated; } var b1 = data[index + 1]; - if (b1 & 0xC0) != 0x80 { ret 0; } + if (b1 & 0xC0) != 0x80 { ret .Invalid; } out_cp.* = (((b0 & 0x1F) as u32) << 6) | ((b1 & 0x3F) as u32); - ret 2; + ret .Ok(2); } if b0 < 0xF0 { - if index + 2 >= len { ret 0; } + if index + 2 >= len { ret .Truncated; } var b1 = data[index + 1]; var b2 = data[index + 2]; - if (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 { ret 0; } + if (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 { ret .Invalid; } var cp = (((b0 & 0x0F) as u32) << 12) | (((b1 & 0x3F) as u32) << 6) | ((b2 & 0x3F) as u32); - if cp < 0x800 { ret 0; } // 过长 - if cp >= 0xD800 && cp <= 0xDFFF { ret 0; } // 代理区 + if cp < 0x800 { ret .Invalid; } // 过长 + if cp >= 0xD800 && cp <= 0xDFFF { ret .Invalid; } // 代理区 out_cp.* = cp; - ret 3; + ret .Ok(3); } if b0 < 0xF5 { - if index + 3 >= len { ret 0; } + if index + 3 >= len { ret .Truncated; } var b1 = data[index + 1]; var b2 = data[index + 2]; var b3 = data[index + 3]; - if (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80 { ret 0; } + if (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80 { ret .Invalid; } var cp = (((b0 & 0x07) as u32) << 18) | (((b1 & 0x3F) as u32) << 12) | (((b2 & 0x3F) as u32) << 6) | ((b3 & 0x3F) as u32); - if cp < 0x10000 || cp > 0x10FFFF { ret 0; } + if cp < 0x10000 || cp > 0x10FFFF { ret .Invalid; } out_cp.* = cp; - ret 4; + ret .Ok(4); + } + ret .Invalid; +} + +// 测试辅助:把 DecodeResult 归约为“消耗字节数”,非 Ok 视为 0。 +fn decode_used(data: *u8, len: usize, index: usize, out_cp: *u32): usize { + match decode(data, len, index, out_cp) { + .Ok(used) => { ret used; }, + .Eof => { ret 0; }, + .Truncated => { ret 0; }, + .Invalid => { ret 0; }, } ret 0; } @@ -63,9 +73,10 @@ fn valid(data: *u8, len: usize): bool { var i: usize = 0; var cp: u32 = 0; while i < len { - var n = decode(data, len, i, &cp); - if n == 0 { ret false; } - i = i + n; + match decode(data, len, i, &cp) { + .Ok(used) => { i = i + used; }, + _ => { ret false; }, + } } ret true; } @@ -107,10 +118,10 @@ fn count_codepoints(data: *u8, len: usize): usize { var n: usize = 0; var cp: u32 = 0; while i < len { - var used = decode(data, len, i, &cp); - if used == 0 { ret n; } - i = i + used; - n = n + 1; + match decode(data, len, i, &cp) { + .Ok(used) => { i = i + used; n = n + 1; }, + _ => { ret n; }, + } } ret n; } @@ -121,10 +132,13 @@ fn utf16_units(data: *u8, len: usize): usize { var n: usize = 0; var cp: u32 = 0; while i < len { - var used = decode(data, len, i, &cp); - if used == 0 { ret n; } - if cp >= 0x10000 { n = n + 2; } else { n = n + 1; } - i = i + used; + match decode(data, len, i, &cp) { + .Ok(used) => { + if cp >= 0x10000 { n = n + 2; } else { n = n + 1; } + i = i + used; + }, + _ => { ret n; }, + } } ret n; } @@ -135,10 +149,13 @@ fn byte_to_utf16(data: *u8, len: usize, byte_off: usize): usize { var n: usize = 0; var cp: u32 = 0; while i < len && i < byte_off { - var used = decode(data, len, i, &cp); - if used == 0 { ret n; } - if cp >= 0x10000 { n = n + 2; } else { n = n + 1; } - i = i + used; + match decode(data, len, i, &cp) { + .Ok(used) => { + if cp >= 0x10000 { n = n + 2; } else { n = n + 1; } + i = i + used; + }, + _ => { ret n; }, + } } ret n; } @@ -150,8 +167,11 @@ fn utf16_to_byte(data: *u8, len: usize, utf16_off: usize): usize { var cp: u32 = 0; while i < len { if n >= utf16_off { ret i; } - var used = decode(data, len, i, &cp); - if used == 0 { ret i; } + var used: usize = 0; + match decode(data, len, i, &cp) { + .Ok(value) => { used = value; }, + _ => { ret i; }, + } var units: usize = 1; if cp >= 0x10000 { units = 2; } if n + units > utf16_off { ret i; } @@ -172,6 +192,7 @@ fn expect_ok(result: Status) { fn test_decode_encode_roundtrip() { var out = ByteBuffer.new(); + defer { ByteBuffer.drop(&out); } expect_ok(encode_into(&out, 0x41)); // 'A' expect_ok(encode_into(&out, 0xE9)); // é expect_ok(encode_into(&out, 0x20AC)); // € @@ -188,11 +209,10 @@ fn test_decode_encode_roundtrip() { @assert(utf16_units(bytes.ptr, bytes.len) == 1 + 1 + 1 + 2, "utf16 units"); var cp: u32 = 0; - @assert(decode(bytes.ptr, bytes.len, 0, &cp) == 1 && cp == 0x41, "dec A"); - @assert(decode(bytes.ptr, bytes.len, 1, &cp) == 2 && cp == 0xE9, "dec e"); - @assert(decode(bytes.ptr, bytes.len, 3, &cp) == 3 && cp == 0x20AC, "dec euro"); - @assert(decode(bytes.ptr, bytes.len, 6, &cp) == 4 && cp == 0x1F600, "dec emoji"); - ByteBuffer.drop(&out); + @assert(decode_used(bytes.ptr, bytes.len, 0, &cp) == 1 && cp == 0x41, "dec A"); + @assert(decode_used(bytes.ptr, bytes.len, 1, &cp) == 2 && cp == 0xE9, "dec e"); + @assert(decode_used(bytes.ptr, bytes.len, 3, &cp) == 3 && cp == 0x20AC, "dec euro"); + @assert(decode_used(bytes.ptr, bytes.len, 6, &cp) == 4 && cp == 0x1F600, "dec emoji"); } fn test_invalid() { @@ -212,7 +232,27 @@ fn test_invalid() { @assert(!valid(s4.ptr, s4.len), "lone cont"); @assert(!valid(s5.ptr, s5.len), "truncated"); + // 新 ADT:越界/截断/非法可区分。 + var cp: u32 = 0; + match decode(s5.ptr, s5.len, 0, &cp) { + .Truncated => {}, + _ => { @assert(false, "truncated sequence should be Truncated"); }, + } + match decode(s5.ptr, s5.len, s5.len, &cp) { + .Eof => {}, + _ => { @assert(false, "past end should be Eof"); }, + } + match decode(s1.ptr, s1.len, 0, &cp) { + .Invalid => {}, + _ => { @assert(false, "overlong should be Invalid"); }, + } + match decode(s4.ptr, s4.len, 0, &cp) { + .Invalid => {}, + _ => { @assert(false, "lone continuation should be Invalid"); }, + } + var out = ByteBuffer.new(); + defer { ByteBuffer.drop(&out); } match encode_into(&out, 0xD800) { .Ok => { @assert(false, "surrogate encode"); }, .Err(error) => {}, @@ -221,11 +261,11 @@ fn test_invalid() { .Ok => { @assert(false, "out of range encode"); }, .Err(error) => {}, } - ByteBuffer.drop(&out); } fn test_offsets() { var out = ByteBuffer.new(); + defer { ByteBuffer.drop(&out); } expect_ok(encode_into(&out, 0x41)); // byte 0, utf16 0 expect_ok(encode_into(&out, 0x1F600)); // bytes 1..4, utf16 1..2 expect_ok(encode_into(&out, 0x42)); // byte 5, utf16 3 @@ -241,7 +281,6 @@ fn test_offsets() { @assert(utf16_to_byte(b.ptr, b.len, 2) == 1, "u2 stays in emoji"); @assert(utf16_to_byte(b.ptr, b.len, 3) == 5, "u3"); @assert(utf16_to_byte(b.ptr, b.len, 99) == 6, "clamp"); - ByteBuffer.drop(&out); } fn main() { diff --git a/stage2/utils/vec.spl b/stage2/utils/vec.spl index 28d8b02..2f62caf 100644 --- a/stage2/utils/vec.spl +++ b/stage2/utils/vec.spl @@ -1,9 +1,10 @@ // Expect retcode = 0 + // 类型擦除的动态数组。元素按值复制,数组只释放自身存储,不释放元素内部资源。 // Vec 拥有 raw 指向的分配;普通赋值会复制所有权,所有权转移必须使用 take。 type .{ .Error, .Kind, .Status } = @import("error.spl"); -type .{ .SizeResult, .checked_add, .checked_mul, .move } = @import("memory.spl"); +type .{ .SizeResult, .checked_add, .checked_mul, .grow_capacity, .move } = @import("memory.spl"); type .{ .alloc, .copy, .free } = @import("../sys/mem.spl"); type Vec = @self; @@ -87,6 +88,9 @@ fn allocate(capacity: usize, elem_size: usize, elem_align: usize): VecResult { } var raw = alloc(total) as *u8; + if raw == null { + ret .Err(Error.make(.Overflow)); + } var address = raw as usize; var mask = elem_align - 1; if address > (~(0 as usize)) - mask { @@ -109,15 +113,7 @@ fn reserve(self: *Vec, need: usize): Status { if !valid(self.*) { ret status_error(.InvalidArgument); } if need <= self.cap { ret .Ok; } - var new_cap = self.cap; - if new_cap < 8 { new_cap = 8; } - while new_cap < need { - if new_cap > (~(0 as usize)) / 2 { - new_cap = need; - break; - } - new_cap = new_cap * 2; - } + var new_cap = grow_capacity(self.cap, 8, need); var fresh: Vec = undefined; match allocate(new_cap, self.elem_size, self.elem_align) { @@ -337,6 +333,7 @@ fn test_construction_and_alignment() { fn test_growth_and_access() { var v = make_vec(@sizeof(i32), @alignof(i32)); + defer { drop(&v); } var i: i32 = 0; while i < 1000 { var value = i * 3 - 7; @@ -358,11 +355,11 @@ fn test_growth_and_access() { var popped: i32 = 0; expect_ok(pop(&v, &popped as *u8)); @assert(popped == 2990 && v.len == 999, "pop copies then removes"); - drop(&v); } fn test_self_reference() { var v = make_vec(@sizeof(i32), @alignof(i32)); + defer { drop(&v); } var i: i32 = 0; while i < 8 { expect_ok(push(&v, &i as *u8)); @@ -391,17 +388,18 @@ fn test_self_reference() { } expect_ok(set(&v, 0, last_ptr)); expect_i32(&v, 0, 7); - drop(&v); } fn test_clone_take_and_failures() { var source = make_vec(@sizeof(i32), @alignof(i32)); + defer { drop(&source); } var one: i32 = 1; var two: i32 = 2; expect_ok(push(&source, &one as *u8)); expect_ok(push(&source, &two as *u8)); var copy = make_vec(@sizeof(i32), @alignof(i32)); + defer { drop(©); } expect_ok(clone(©, source)); expect_i32(©, 0, 1); one = 99; @@ -415,6 +413,7 @@ fn test_clone_take_and_failures() { expect_i32(©, 1, 2); var moved = take(©); + defer { drop(&moved); } @assert(copy.raw == null && copy.data == null && copy.len == 0 && copy.cap == 0); expect_i32(&moved, 0, 1); @@ -430,10 +429,6 @@ fn test_clone_take_and_failures() { var output: i32 = 55; expect_error(pop(&moved, &output as *u8), false); @assert(output == 55, "failed pop preserves output"); - - drop(&moved); - drop(©); - drop(&source); } fn main() {