Compare commits
2
Commits
1a6df012cd
...
73c9a40abd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73c9a40abd | ||
|
|
b8e2bba140 |
No files matched your search
+43
-13
@@ -361,6 +361,10 @@ static spl_ir_node_ref_t ast2ir_intrin_cast(spl_ast2ir_t *a, spl_ast_node_t *n,
|
||||
static spl_ir_node_ref_t ast2ir_intrin_call(spl_ast2ir_t *a, spl_ast_node_t *n, spl_intrin_t in,
|
||||
spl_ast_node_ref_vec_t *args) {
|
||||
switch (in) {
|
||||
case SPL_INTRIN_BITSIZEOF: {
|
||||
spl_ast_node_t *arg = spl_ast_node(a->ast, vec_at(*args, 0));
|
||||
return spl_ir_builder_type_bitsizeof(a->ir, node_ir(a, arg));
|
||||
}
|
||||
case SPL_INTRIN_SIZEOF: {
|
||||
spl_ast_node_t *arg = spl_ast_node(a->ast, vec_at(*args, 0));
|
||||
return spl_ir_builder_type_sizeof(a->ir, node_ir(a, arg));
|
||||
@@ -501,9 +505,6 @@ static spl_ir_node_ref_t emit_lvalue(spl_ast2ir_t *a, spl_ast_node_ref_t ref) {
|
||||
spl_lang_type_kind_t obj_kind = obj_ln->kind;
|
||||
spl_lang_type_id_t obj_elem = (obj_kind == SPL_LANG_TYPE_PTR) ? obj_ln->extra.elem : 0;
|
||||
|
||||
if (spl_intrin_lookup_field(obj_kind, n->field.field) != SPL_INTRIN_NONE)
|
||||
return 0;
|
||||
|
||||
spl_ir_node_ref_t base = 0;
|
||||
spl_lang_type_id_t agg_lang = 0;
|
||||
if (obj_kind == SPL_LANG_TYPE_PTR) {
|
||||
@@ -764,6 +765,7 @@ static spl_ir_node_ref_t emit_expr_raw(spl_ast2ir_t *a, spl_ast_node_ref_t ref)
|
||||
}
|
||||
case SPL_AST_EXPR_BINARY: {
|
||||
spl_binary_op_t op = n->binary.op;
|
||||
|
||||
if (op == SPL_OP_ASSIGN) {
|
||||
spl_ir_node_ref_t ptr = emit_lvalue(a, n->binary.left);
|
||||
spl_ir_node_ref_t v = emit_expr(a, n->binary.right);
|
||||
@@ -774,6 +776,7 @@ static spl_ir_node_ref_t emit_expr_raw(spl_ast2ir_t *a, spl_ast_node_ref_t ref)
|
||||
spl_ir_builder_mem_store(a->ir, t, ptr, v);
|
||||
return v;
|
||||
}
|
||||
|
||||
if (op >= SPL_OP_ASSIGN_ADD && op <= SPL_OP_ASSIGN_RSHIFT) {
|
||||
spl_ir_node_ref_t ptr = emit_lvalue(a, n->binary.left);
|
||||
if (!ptr)
|
||||
@@ -824,6 +827,43 @@ static spl_ir_node_ref_t emit_expr_raw(spl_ast2ir_t *a, spl_ast_node_ref_t ref)
|
||||
spl_ir_builder_mem_store(a->ir, t, ptr, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
if (op == SPL_OP_BOOL_OR || op == SPL_OP_BOOL_AND) {
|
||||
spl_lang_type_id_t b_lang = spl_lang_make_bool(a->sema->type);
|
||||
spl_type_id_t b_ir = to_ir_type(a, b_lang);
|
||||
spl_ir_node_ref_t slot = spl_ir_builder_mem_alloca(a->ir, b_ir, const_usize(a, 1));
|
||||
|
||||
spl_ir_node_ref_t l = emit_expr(a, n->binary.left);
|
||||
if (!l)
|
||||
return 0;
|
||||
spl_ir_builder_mem_store(a->ir, b_ir, slot, l);
|
||||
|
||||
spl_ir_node_ref_t br = spl_ir_builder_control_br(a->ir, l);
|
||||
spl_ir_builder_block_end(a->ir);
|
||||
|
||||
usize rhs_blk = spl_ir_builder_block_begin(a->ir);
|
||||
spl_ir_node_ref_t r = emit_expr(a, n->binary.right);
|
||||
if (r)
|
||||
spl_ir_builder_mem_store(a->ir, b_ir, slot, r);
|
||||
/* 右侧可能以 ret / trap 终止,此时不能再补 jmp */
|
||||
spl_ir_node_ref_t rhs_end = 0;
|
||||
if (!a->ir->terminated)
|
||||
rhs_end = spl_ir_builder_control_jmp(a->ir);
|
||||
spl_ir_builder_block_end(a->ir);
|
||||
|
||||
usize end_blk = spl_ir_builder_block_begin(a->ir);
|
||||
/* control.br: cond != 0 -> true_label。AND 左侧真才进 rhs,
|
||||
OR 左侧假才进 rhs,所以 true/false 标签顺序相反。 */
|
||||
if (op == SPL_OP_BOOL_AND)
|
||||
spl_ir_builder_control_br_fixup(a->ir, br, rhs_blk, end_blk);
|
||||
else
|
||||
spl_ir_builder_control_br_fixup(a->ir, br, end_blk, rhs_blk);
|
||||
if (rhs_end)
|
||||
spl_ir_builder_control_jmp_fixup(a->ir, rhs_end, end_blk);
|
||||
|
||||
return spl_ir_builder_mem_load(a->ir, b_ir, slot);
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t l = emit_expr(a, n->binary.left);
|
||||
spl_ir_node_ref_t r = emit_expr(a, n->binary.right);
|
||||
if (!l || !r)
|
||||
@@ -892,16 +932,6 @@ static spl_ir_node_ref_t emit_expr_raw(spl_ast2ir_t *a, spl_ast_node_ref_t ref)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
case SPL_OP_BOOL_OR: {
|
||||
spl_ast_node_t *bmln = spl_ast_node(a->ast, n->binary.left);
|
||||
spl_type_id_t op_tid = bmln ? node_ir(a, bmln) : 0;
|
||||
return spl_ir_builder_arith_or(a->ir, op_tid, l, r);
|
||||
}
|
||||
case SPL_OP_BOOL_AND: {
|
||||
spl_ast_node_t *bmln = spl_ast_node(a->ast, n->binary.left);
|
||||
spl_type_id_t op_tid = bmln ? node_ir(a, bmln) : 0;
|
||||
return spl_ir_builder_arith_and(a->ir, op_tid, l, r);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
static const spl_intrin_info_t g_intrin_table[] = {
|
||||
[SPL_INTRIN_NONE] = {0},
|
||||
[SPL_INTRIN_BITSIZEOF] = {"@bitsizeof", SPL_INTRIN_BITSIZEOF, SPL_INTRIN_FORM_CALL, 1, 1,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_SIZEOF] = {"@sizeof", SPL_INTRIN_SIZEOF, SPL_INTRIN_FORM_CALL, 1, 1,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_ALIGNOF] = {"@alignof", SPL_INTRIN_ALIGNOF, SPL_INTRIN_FORM_CALL, 1, 1,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
typedef enum {
|
||||
SPL_INTRIN_NONE = 0,
|
||||
SPL_INTRIN_BITSIZEOF,
|
||||
SPL_INTRIN_SIZEOF,
|
||||
SPL_INTRIN_ALIGNOF,
|
||||
SPL_INTRIN_ASSERT,
|
||||
|
||||
+142
-3
@@ -746,8 +746,6 @@ static void sema_collect_types(spl_sema_t *sema, spl_ast_node_ref_t ref) {
|
||||
if (pat->kind == SPL_AST_PATTERN_IDENT) {
|
||||
if (d->resolved_def_id)
|
||||
sema_eval_decl(sema, d->resolved_def_id);
|
||||
} else {
|
||||
sema_do_type_unpack(sema, d, pat, d->type_decl.type_expr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -772,6 +770,144 @@ static void sema_collect_signatures(spl_sema_t *sema) {
|
||||
}
|
||||
}
|
||||
|
||||
static int sema_def_is_type(const spl_def_node_t *def) {
|
||||
if (!def)
|
||||
return 0;
|
||||
switch (def->kind) {
|
||||
case SPL_DEF_SCALAR:
|
||||
case SPL_DEF_AGG:
|
||||
case SPL_DEF_DISTINCT:
|
||||
case SPL_DEF_ALIAS:
|
||||
case SPL_DEF_BUILTIN:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 解包声明本身不会在 collect_names 阶段建立符号,因此必须在构建模块根
|
||||
* 聚合之前处理。否则根字段无法引用通过 `type .{ ... } = source` 导入的类型。 */
|
||||
static void sema_collect_type_unpacks(spl_sema_t *sema, spl_ast_node_ref_t ref) {
|
||||
spl_ast_node_t *n = spl_ast_node(sema->ast, ref);
|
||||
if (!n)
|
||||
return;
|
||||
switch (n->kind) {
|
||||
case SPL_AST_CONTAINER_MEMBERS:
|
||||
for (usize i = 0; i < vec_size(n->container_members.declarations); ++i) {
|
||||
spl_ast_node_ref_t dref = vec_at(n->container_members.declarations, i);
|
||||
spl_ast_node_t *d = spl_ast_node(sema->ast, dref);
|
||||
if (!d || d->kind != SPL_AST_TYPE_DECL)
|
||||
continue;
|
||||
spl_ast_node_t *pat = spl_ast_node(sema->ast, d->type_decl.pattern);
|
||||
if (pat && pat->kind != SPL_AST_PATTERN_IDENT)
|
||||
sema_do_type_unpack(sema, d, pat, d->type_decl.type_expr);
|
||||
}
|
||||
break;
|
||||
case SPL_AST_TOP_LEVEL_BLOCK:
|
||||
if (n->top_level_block.block)
|
||||
sema_collect_type_unpacks(sema, n->top_level_block.block);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* @sizeof/@bitsizeof/@alignof 的参数在语法层仍是普通 Expr。
|
||||
intrinsic 的类型上下文在这里解释表达式形态,不要求 parser 识别 intrinsic。 */
|
||||
static spl_lang_type_id_t sema_infer_type_context(spl_sema_t *sema, spl_ast_node_ref_t ref) {
|
||||
spl_ast_node_t *n = spl_ast_node(sema->ast, ref);
|
||||
if (!n)
|
||||
return 0;
|
||||
|
||||
switch (n->kind) {
|
||||
case SPL_AST_EXPR_IDENT: {
|
||||
spl_lang_type_id_t builtin = spl_type_lookup_builtin(sema->type, n->ident.name);
|
||||
if (builtin) {
|
||||
n->resolved_lang_tid = builtin;
|
||||
return builtin;
|
||||
}
|
||||
|
||||
spl_symbol_t symbol;
|
||||
if (!spl_scope_find(sema->scope, n->ident.name, &symbol) ||
|
||||
symbol.kind != SPL_SYMBOL_KIND_TYPE) {
|
||||
spl_diag_error(sema->diag, SPL_DIAG_KIND_TYPE_MISMATCH, &n->dbg,
|
||||
"`%s` is not a type", n->ident.name);
|
||||
return 0;
|
||||
}
|
||||
spl_def_node_t *def = spl_def_node(sema->type, symbol.node);
|
||||
if (!sema_def_is_type(def)) {
|
||||
spl_diag_error(sema->diag, SPL_DIAG_KIND_TYPE_MISMATCH, &n->dbg,
|
||||
"`%s` is not a type", n->ident.name);
|
||||
return 0;
|
||||
}
|
||||
n->resolved_def_id = symbol.node;
|
||||
spl_lang_type_id_t result = sema_eval_decl(sema, symbol.node);
|
||||
n->resolved_lang_tid = result;
|
||||
return result;
|
||||
}
|
||||
case SPL_AST_EXPR_FIELD: {
|
||||
spl_lang_type_id_t owner_id = sema_infer_type_context(sema, n->field.obj);
|
||||
spl_lang_type_node_t *owner = spl_lang_type_node(sema->type, owner_id);
|
||||
if (!owner)
|
||||
return 0;
|
||||
for (usize i = 0; i < vec_size(owner->methods); ++i) {
|
||||
spl_lang_method_t *member = &vec_at(owner->methods, i);
|
||||
if (strcmp(member->name, n->field.field) != 0)
|
||||
continue;
|
||||
spl_def_node_t *def = spl_def_node(sema->type, member->def_id);
|
||||
if (!sema_def_is_type(def))
|
||||
break;
|
||||
n->resolved_def_id = member->def_id;
|
||||
spl_lang_type_id_t result = sema_eval_decl(sema, member->def_id);
|
||||
n->resolved_lang_tid = result;
|
||||
return result;
|
||||
}
|
||||
spl_diag_error(sema->diag, SPL_DIAG_KIND_TYPE_MISMATCH, &n->dbg,
|
||||
"`%s` is not a type member", n->field.field);
|
||||
return 0;
|
||||
}
|
||||
case SPL_AST_EXPR_UNARY:
|
||||
if (n->unary.op == SPL_UNARY_OP_DEREF) {
|
||||
spl_lang_type_id_t pointee = sema_infer_type_context(sema, n->unary.operand);
|
||||
if (!pointee)
|
||||
return 0;
|
||||
spl_lang_type_id_t result = spl_lang_make_ptr(sema->type, pointee);
|
||||
n->resolved_lang_tid = result;
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
case SPL_AST_EXPR_TUPLE: {
|
||||
spl_lang_type_id_vec_t elements;
|
||||
vec_init(elements);
|
||||
for (usize i = 0; i < vec_size(n->tuple.elems); ++i) {
|
||||
spl_lang_type_id_t element =
|
||||
sema_infer_type_context(sema, vec_at(n->tuple.elems, i));
|
||||
if (!element) {
|
||||
vec_free(elements);
|
||||
return 0;
|
||||
}
|
||||
vec_push(elements, element);
|
||||
}
|
||||
spl_lang_type_id_t result = spl_lang_make_tuple(
|
||||
sema->type, vec_unsafe_get_data(elements), vec_size(elements));
|
||||
vec_free(elements);
|
||||
n->resolved_lang_tid = result;
|
||||
return result;
|
||||
}
|
||||
case SPL_AST_EXPR_COMPTIME_CALL: {
|
||||
spl_lang_type_id_t result = sema_infer(sema, ref);
|
||||
n->resolved_lang_tid = result;
|
||||
return result;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
spl_diag_error(sema->diag, SPL_DIAG_KIND_TYPE_MISMATCH, &n->dbg,
|
||||
"intrinsic argument is not a type expression");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static spl_lang_type_id_t sema_intrin_call(spl_sema_t *sema, spl_ast_node_t *n, spl_intrin_t in,
|
||||
spl_ast_node_ref_vec_t *args) {
|
||||
const spl_intrin_info_t *info = spl_intrin_info(in);
|
||||
@@ -784,9 +920,11 @@ static spl_lang_type_id_t sema_intrin_call(spl_sema_t *sema, spl_ast_node_t *n,
|
||||
}
|
||||
|
||||
switch (in) {
|
||||
case SPL_INTRIN_BITSIZEOF:
|
||||
case SPL_INTRIN_SIZEOF:
|
||||
case SPL_INTRIN_ALIGNOF: {
|
||||
sema_infer(sema, vec_at(*args, 0));
|
||||
if (!sema_infer_type_context(sema, vec_at(*args, 0)))
|
||||
return 0;
|
||||
return spl_lang_make_int(sema->type, sizeof(void *) * 8, 0);
|
||||
}
|
||||
case SPL_INTRIN_ASSERT: {
|
||||
@@ -2600,6 +2738,7 @@ void spl_sema_run_module(spl_sema_t *sema, spl_ast_node_ref_t module_ref) {
|
||||
spl_lang_type_id_t saved_agg = sema->current_aggregate_lang;
|
||||
|
||||
sema_collect_names(sema, module_ref);
|
||||
sema_collect_type_unpacks(sema, module_ref);
|
||||
|
||||
spl_lang_type_id_t container_lang = 0;
|
||||
if (mod->resolved_def_id)
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
// Expect retcode = 0
|
||||
// ASCII 字符工具。输入和输出都是单个字节,不解释 UTF-8 或本地化规则。
|
||||
|
||||
type .{ .Error, .make } = @import("error.spl");
|
||||
|
||||
type DigitResult = enum {
|
||||
Ok: u8,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn is_ascii(c: u8): bool {
|
||||
ret c <= 127;
|
||||
}
|
||||
|
||||
fn is_control(c: u8): bool {
|
||||
ret c <= 31 || c == 127;
|
||||
}
|
||||
|
||||
fn is_space(c: u8): bool {
|
||||
ret c == 32 || (c >= 9 && c <= 13);
|
||||
}
|
||||
|
||||
fn is_blank(c: u8): bool {
|
||||
ret c == 32 || c == 9;
|
||||
}
|
||||
|
||||
fn is_digit(c: u8): bool {
|
||||
ret c >= 48 && c <= 57;
|
||||
}
|
||||
|
||||
fn is_lower(c: u8): bool {
|
||||
ret c >= 97 && c <= 122;
|
||||
}
|
||||
|
||||
fn is_upper(c: u8): bool {
|
||||
ret c >= 65 && c <= 90;
|
||||
}
|
||||
|
||||
fn is_alpha(c: u8): bool {
|
||||
ret is_lower(c) || is_upper(c);
|
||||
}
|
||||
|
||||
fn is_alnum(c: u8): bool {
|
||||
ret is_alpha(c) || is_digit(c);
|
||||
}
|
||||
|
||||
fn is_hex_digit(c: u8): bool {
|
||||
ret is_digit(c) || (c >= 65 && c <= 70) || (c >= 97 && c <= 102);
|
||||
}
|
||||
|
||||
fn is_print(c: u8): bool {
|
||||
ret c >= 32 && c <= 126;
|
||||
}
|
||||
|
||||
fn is_graph(c: u8): bool {
|
||||
ret c >= 33 && c <= 126;
|
||||
}
|
||||
|
||||
fn is_punct(c: u8): bool {
|
||||
ret is_graph(c) && !is_alnum(c);
|
||||
}
|
||||
|
||||
fn to_lower(c: u8): u8 {
|
||||
if is_upper(c) { ret c + 32; }
|
||||
ret c;
|
||||
}
|
||||
|
||||
fn to_upper(c: u8): u8 {
|
||||
if is_lower(c) { ret c - 32; }
|
||||
ret c;
|
||||
}
|
||||
|
||||
// radix 必须位于 2..36。大小写字母均表示 10..35。
|
||||
fn digit_value(c: u8, radix: u8): DigitResult {
|
||||
if radix < 2 || radix > 36 {
|
||||
ret .Err(make(.InvalidArgument));
|
||||
}
|
||||
|
||||
var value: u8 = 0;
|
||||
if is_digit(c) {
|
||||
value = c - 48;
|
||||
} else if is_upper(c) {
|
||||
value = c - 65 + 10;
|
||||
} else if is_lower(c) {
|
||||
value = c - 97 + 10;
|
||||
} else {
|
||||
ret .Err(make(.InvalidDigit));
|
||||
}
|
||||
|
||||
if value >= radix { ret .Err(make(.InvalidDigit)); }
|
||||
ret .Ok(value);
|
||||
}
|
||||
|
||||
// value 必须位于 0..35。uppercase 只影响 10..35 的字母形式。
|
||||
fn digit_char(value: u8, uppercase: bool): DigitResult {
|
||||
if value >= 36 { ret .Err(make(.OutOfRange)); }
|
||||
if value < 10 { ret .Ok(value + 48); }
|
||||
if uppercase { ret .Ok(value - 10 + 65); }
|
||||
ret .Ok(value - 10 + 97);
|
||||
}
|
||||
|
||||
fn expect_digit(result: DigitResult, expected: u8) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(value == expected, "digit value"); },
|
||||
.Err(error) => { @assert(false, "unexpected digit error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_error(result: DigitResult, expected_invalid_argument: bool) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(false, "expected digit error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.InvalidArgument => {
|
||||
@assert(expected_invalid_argument, "unexpected invalid radix");
|
||||
},
|
||||
.InvalidDigit => {
|
||||
@assert(!expected_invalid_argument, "unexpected invalid digit");
|
||||
},
|
||||
_ => { @assert(false, "unexpected digit error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_range_error(result: DigitResult) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(false, "expected digit range error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.OutOfRange => {},
|
||||
_ => { @assert(false, "unexpected digit range error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn test_all_bytes() {
|
||||
var i: usize = 0;
|
||||
while i < 256 {
|
||||
var c = i as u8;
|
||||
@assert(is_ascii(c) == (i <= 127), "ASCII boundary");
|
||||
@assert(is_control(c) == (i <= 31 || i == 127), "control boundary");
|
||||
@assert(is_print(c) == (i >= 32 && i <= 126), "print boundary");
|
||||
@assert(is_graph(c) == (i >= 33 && i <= 126), "graph boundary");
|
||||
@assert(is_blank(c) == (i == 9 || i == 32), "blank set");
|
||||
@assert(is_space(c) == (i == 9 || i == 10 || i == 11 || i == 12 || i == 13 || i == 32),
|
||||
"space set");
|
||||
@assert(is_digit(c) == (i >= 48 && i <= 57), "digit set");
|
||||
@assert(is_upper(c) == (i >= 65 && i <= 90), "upper set");
|
||||
@assert(is_lower(c) == (i >= 97 && i <= 122), "lower set");
|
||||
@assert(is_hex_digit(c) == ((i >= 48 && i <= 57) ||
|
||||
(i >= 65 && i <= 70) ||
|
||||
(i >= 97 && i <= 102)),
|
||||
"hex digit set");
|
||||
@assert(is_alpha(c) == (is_upper(c) || is_lower(c)), "alpha relation");
|
||||
@assert(is_alnum(c) == (is_alpha(c) || is_digit(c)), "alnum relation");
|
||||
@assert(is_punct(c) == (is_graph(c) && !is_alnum(c)), "punct relation");
|
||||
|
||||
if is_upper(c) {
|
||||
@assert(to_lower(c) == c + 32, "upper to lower");
|
||||
@assert(to_upper(to_lower(c)) == c, "case round trip");
|
||||
} else if is_lower(c) {
|
||||
@assert(to_upper(c) == c - 32, "lower to upper");
|
||||
@assert(to_lower(to_upper(c)) == c, "case round trip");
|
||||
} else {
|
||||
@assert(to_lower(c) == c, "lower preserves non-letter");
|
||||
@assert(to_upper(c) == c, "upper preserves non-letter");
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_digits() {
|
||||
var radix: usize = 2;
|
||||
while radix <= 36 {
|
||||
var value: usize = 0;
|
||||
while value < radix {
|
||||
var lower: u8 = 0;
|
||||
match digit_char(value as u8, false) {
|
||||
.Ok(c) => { lower = c; },
|
||||
.Err(error) => { @assert(false, "lower digit generation"); },
|
||||
}
|
||||
expect_digit(digit_value(lower, radix as u8), value as u8);
|
||||
|
||||
var upper: u8 = 0;
|
||||
match digit_char(value as u8, true) {
|
||||
.Ok(c) => { upper = c; },
|
||||
.Err(error) => { @assert(false, "upper digit generation"); },
|
||||
}
|
||||
expect_digit(digit_value(upper, radix as u8), value as u8);
|
||||
if value < 10 {
|
||||
@assert(lower == upper, "decimal digits ignore case");
|
||||
} else {
|
||||
@assert(lower == upper + 32, "letter digit case distance");
|
||||
}
|
||||
value = value + 1;
|
||||
}
|
||||
radix = radix + 1;
|
||||
}
|
||||
|
||||
expect_error(digit_value(48, 0), true);
|
||||
expect_error(digit_value(48, 1), true);
|
||||
expect_error(digit_value(48, 37), true);
|
||||
expect_error(digit_value(47, 10), false);
|
||||
expect_error(digit_value(58, 10), false);
|
||||
expect_error(digit_value(50, 2), false);
|
||||
expect_error(digit_value(103, 16), false);
|
||||
expect_range_error(digit_char(36, false));
|
||||
expect_range_error(digit_char(255, true));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_all_bytes();
|
||||
test_digits();
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
// Expect retcode = 0
|
||||
// 拥有字节存储的动态缓冲区。普通赋值会复制所有权,转移必须使用 take。
|
||||
// 借用切片和 z_ptr 返回的指针在下一次可能扩容的操作或 drop 后失效。
|
||||
|
||||
type .{ .Error, .Status } = @import("error.spl");
|
||||
type .{ .checked_add, .move } = @import("memory.spl");
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
|
||||
type ByteBuffer = @self;
|
||||
|
||||
storage: Vec,
|
||||
|
||||
type PtrResult = enum {
|
||||
Ok: *u8,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn new(): ByteBuffer {
|
||||
match Vec.new(1, 1) {
|
||||
.Ok(value) => { ret ByteBuffer { .storage = value }; },
|
||||
.Err(error) => { @assert(false, "fixed byte layout is valid"); },
|
||||
}
|
||||
ret ByteBuffer { .storage = Vec.blank(1, 1) };
|
||||
}
|
||||
|
||||
fn size(self: ByteBuffer): usize {
|
||||
ret self.storage.len;
|
||||
}
|
||||
|
||||
fn capacity(self: ByteBuffer): usize {
|
||||
ret self.storage.cap;
|
||||
}
|
||||
|
||||
fn is_empty(self: ByteBuffer): bool {
|
||||
ret self.storage.len == 0;
|
||||
}
|
||||
|
||||
fn as_slice(self: ByteBuffer): []u8 {
|
||||
ret .{ .ptr = self.storage.data, .len = self.storage.len };
|
||||
}
|
||||
|
||||
fn reserve(self: *ByteBuffer, capacity_needed: usize): Status {
|
||||
ret Vec.reserve(&self.storage, capacity_needed);
|
||||
}
|
||||
|
||||
fn append(self: *ByteBuffer, bytes: []u8): Status {
|
||||
ret Vec.append(&self.storage, bytes.ptr, bytes.len);
|
||||
}
|
||||
|
||||
fn append_byte(self: *ByteBuffer, byte: u8): Status {
|
||||
ret Vec.push(&self.storage, &byte);
|
||||
}
|
||||
|
||||
fn append_repeat(self: *ByteBuffer, byte: u8, count: usize): Status {
|
||||
if count == 0 { ret .Ok; }
|
||||
|
||||
var new_len: usize = 0;
|
||||
match checked_add(self.storage.len, count) {
|
||||
.Ok(value) => { new_len = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match reserve(self, new_len) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var i = self.storage.len;
|
||||
while i < new_len {
|
||||
self.storage.data[i] = byte;
|
||||
i = i + 1;
|
||||
}
|
||||
self.storage.len = new_len;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 扩展时用 fill 初始化新增字节;缩短只改变逻辑长度。
|
||||
fn resize(self: *ByteBuffer, new_len: usize, fill: u8): Status {
|
||||
if new_len <= self.storage.len {
|
||||
self.storage.len = new_len;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
var count = new_len - self.storage.len;
|
||||
ret append_repeat(self, fill, count);
|
||||
}
|
||||
|
||||
fn clear(self: *ByteBuffer) {
|
||||
Vec.clear(&self.storage);
|
||||
}
|
||||
|
||||
// 保证 data[len] 为零并返回 data。终止零不属于逻辑内容。
|
||||
// 缓冲区内容可以包含更早的零字节,调用方决定是否适合作为 C 字符串。
|
||||
fn z_ptr(self: *ByteBuffer): PtrResult {
|
||||
var needed: usize = 0;
|
||||
match checked_add(self.storage.len, 1) {
|
||||
.Ok(value) => { needed = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match reserve(self, needed) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
self.storage.data[self.storage.len] = 0;
|
||||
ret .Ok(self.storage.data);
|
||||
}
|
||||
|
||||
// 成功时替换 out 的旧内容;失败保持 out 不变。允许 bytes 借用 out 自身。
|
||||
fn assign(out: *ByteBuffer, bytes: []u8): Status {
|
||||
if bytes.len == 0 {
|
||||
clear(out);
|
||||
ret .Ok;
|
||||
}
|
||||
if bytes.ptr == null { ret .Err(Error.make(.InvalidArgument)); }
|
||||
|
||||
var used = out.storage.len;
|
||||
if used != 0 {
|
||||
var base = out.storage.data as usize;
|
||||
var source = bytes.ptr as usize;
|
||||
if source >= base && source - base < used {
|
||||
var offset = source - base;
|
||||
if bytes.len > used - offset {
|
||||
ret .Err(Error.make(.OutOfRange));
|
||||
}
|
||||
match move(out.storage.data[0..bytes.len], bytes) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
out.storage.len = bytes.len;
|
||||
ret .Ok;
|
||||
}
|
||||
}
|
||||
|
||||
var fresh = new();
|
||||
match append(&fresh, bytes) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
drop(&fresh);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
drop(out);
|
||||
out.storage = Vec.take(&fresh.storage);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 深复制;允许 out 与 source 是同一对象。
|
||||
fn clone(out: *ByteBuffer, source: ByteBuffer): Status {
|
||||
ret Vec.clone(&out.storage, source.storage);
|
||||
}
|
||||
|
||||
fn take(source: *ByteBuffer): ByteBuffer {
|
||||
ret ByteBuffer { .storage = Vec.take(&source.storage) };
|
||||
}
|
||||
|
||||
fn drop(self: *ByteBuffer) {
|
||||
Vec.drop(&self.storage);
|
||||
}
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.InvalidArgument => { @assert(false, "unexpected invalid argument"); },
|
||||
.OutOfRange => { @assert(false, "unexpected out of range"); },
|
||||
.Overflow => { @assert(false, "unexpected overflow"); },
|
||||
_ => { @assert(false, "unexpected byte buffer error"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_overflow(result: Status) {
|
||||
match result {
|
||||
.Ok => { @assert(false, "expected byte buffer overflow"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.Overflow => {},
|
||||
_ => { @assert(false, "unexpected byte buffer error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_bytes(self: ByteBuffer, expected: []u8) {
|
||||
@assert(self.storage.len == expected.len, "byte buffer length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(self.storage.data[i] == expected.ptr[i], "byte buffer content");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_z(self: *ByteBuffer) {
|
||||
var old_len = self.storage.len;
|
||||
match z_ptr(self) {
|
||||
.Ok(ptr) => {
|
||||
@assert(ptr != null, "zero terminated pointer");
|
||||
@assert(ptr[old_len] == 0, "zero terminator");
|
||||
},
|
||||
.Err(error) => { @assert(false, "zero termination"); },
|
||||
}
|
||||
@assert(self.storage.len == old_len, "terminator is outside logical content");
|
||||
}
|
||||
|
||||
fn test_append_and_resize() {
|
||||
var buffer = new();
|
||||
@assert(is_empty(buffer));
|
||||
@assert(size(buffer) == 0 && capacity(buffer) == 0);
|
||||
expect_z(&buffer);
|
||||
@assert(capacity(buffer) >= 1);
|
||||
|
||||
expect_ok(append(&buffer, "abc"));
|
||||
expect_ok(append_byte(&buffer, 0));
|
||||
expect_ok(append(&buffer, "de"));
|
||||
@assert(buffer.storage.len == 6, "append logical length");
|
||||
var embedded: [6]u8 = .[97, 98, 99, 0, 100, 101];
|
||||
expect_bytes(buffer, embedded[0..6]);
|
||||
expect_z(&buffer);
|
||||
|
||||
expect_ok(resize(&buffer, 9, 255));
|
||||
@assert(buffer.storage.len == 9, "grow logical length");
|
||||
var grown: [9]u8 = .[97, 98, 99, 0, 100, 101, 255, 255, 255];
|
||||
expect_bytes(buffer, grown[0..9]);
|
||||
expect_ok(resize(&buffer, 2, 17));
|
||||
@assert(buffer.storage.len == 2, "shrink logical length");
|
||||
expect_bytes(buffer, "ab");
|
||||
expect_ok(append_repeat(&buffer, 120, 4));
|
||||
@assert(buffer.storage.len == 6, "repeat logical length");
|
||||
expect_bytes(buffer, "abxxxx");
|
||||
|
||||
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();
|
||||
var i: usize = 0;
|
||||
while i < 8 {
|
||||
expect_ok(append_byte(&buffer, (65 + i) as u8));
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(buffer.storage.cap == 8, "prepare byte buffer growth boundary");
|
||||
|
||||
var own = as_slice(buffer);
|
||||
expect_ok(append(&buffer, own));
|
||||
@assert(buffer.storage.len == 16);
|
||||
i = 0;
|
||||
while i < 16 {
|
||||
@assert(buffer.storage.data[i] == (65 + i % 8) as u8, "self append content");
|
||||
i = i + 1;
|
||||
}
|
||||
expect_z(&buffer);
|
||||
drop(&buffer);
|
||||
}
|
||||
|
||||
fn test_ownership_and_failures() {
|
||||
var source = new();
|
||||
expect_ok(append(&source, "owned"));
|
||||
|
||||
var copy = new();
|
||||
expect_ok(clone(©, source));
|
||||
@assert(copy.storage.len == 5, "clone logical length");
|
||||
source.storage.data[0] = 79;
|
||||
expect_bytes(copy, "owned");
|
||||
|
||||
var before_self_clone = copy.storage.raw;
|
||||
expect_ok(clone(©, copy));
|
||||
@assert(copy.storage.raw != before_self_clone, "self clone replaces nested allocation");
|
||||
expect_bytes(copy, "owned");
|
||||
|
||||
var before_external_assign = copy.storage.data;
|
||||
expect_ok(assign(©, "xyz"));
|
||||
@assert(copy.storage.len == 3, "external assign logical length");
|
||||
@assert(copy.storage.data != before_external_assign, "external assign replaces data");
|
||||
expect_bytes(copy, "xyz");
|
||||
before_external_assign = copy.storage.data;
|
||||
expect_ok(assign(©, "owned"));
|
||||
@assert(copy.storage.len == 5, "second external assign logical length");
|
||||
@assert(copy.storage.data != before_external_assign, "second external assign replaces data");
|
||||
|
||||
// assign 的输入借用目标自身,提交前必须保持旧分配有效。
|
||||
var middle = as_slice(copy);
|
||||
middle.ptr = middle.ptr + 1;
|
||||
middle.len = 3;
|
||||
match assign(©, middle) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "self assign failed"); },
|
||||
}
|
||||
@assert(copy.storage.len == 3, "assign logical length");
|
||||
expect_bytes(copy, "wne");
|
||||
|
||||
var moved = take(©);
|
||||
@assert(copy.storage.raw == null && copy.storage.len == 0 && copy.storage.cap == 0);
|
||||
expect_bytes(moved, "wne");
|
||||
|
||||
var old_raw = moved.storage.raw;
|
||||
var old_len = moved.storage.len;
|
||||
var old_cap = moved.storage.cap;
|
||||
expect_overflow(append_repeat(&moved, 1, ~(0 as usize)));
|
||||
@assert(moved.storage.raw == old_raw && moved.storage.len == old_len &&
|
||||
moved.storage.cap == old_cap,
|
||||
"overflow preserves byte buffer");
|
||||
expect_bytes(moved, "wne");
|
||||
|
||||
drop(&moved);
|
||||
drop(©);
|
||||
drop(&source);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_append_and_resize();
|
||||
test_growth_and_self_append();
|
||||
test_ownership_and_failures();
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
// Expect retcode = 0
|
||||
// 借用的字节字符串视图;不拥有、不分配也不释放 ptr 指向的存储。
|
||||
// 非空视图要求 ptr 在整个借用期内可读。空视图统一表示为 null/0。
|
||||
|
||||
type .{ .Error, .make } = @import("error.spl");
|
||||
|
||||
type ByteView = @self;
|
||||
|
||||
ptr: *u8,
|
||||
len: usize,
|
||||
|
||||
type ByteResult = enum {
|
||||
Ok: u8,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
type ViewResult = enum {
|
||||
Ok: ByteView,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
type FindResult = enum {
|
||||
Found: usize,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
fn empty(): ByteView {
|
||||
ret ByteView { .ptr = null, .len = 0 };
|
||||
}
|
||||
|
||||
// 输入切片的存储仍由调用方拥有。零长度输入被规范化为空视图。
|
||||
fn from_slice(bytes: []u8): ByteView {
|
||||
if bytes.len == 0 { ret empty(); }
|
||||
ret ByteView { .ptr = bytes.ptr, .len = bytes.len };
|
||||
}
|
||||
|
||||
fn as_slice(self: ByteView): []u8 {
|
||||
ret .{ .ptr = self.ptr, .len = self.len };
|
||||
}
|
||||
|
||||
fn size(self: ByteView): usize {
|
||||
ret self.len;
|
||||
}
|
||||
|
||||
fn is_empty(self: ByteView): bool {
|
||||
ret self.len == 0;
|
||||
}
|
||||
|
||||
fn byte_at(self: ByteView, index: usize): ByteResult {
|
||||
if index >= self.len { ret .Err(make(.OutOfRange)); }
|
||||
if self.ptr == null { ret .Err(make(.InvalidArgument)); }
|
||||
ret .Ok(self.ptr[index]);
|
||||
}
|
||||
|
||||
fn eq(a: ByteView, b: ByteView): bool {
|
||||
if a.len != b.len { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < a.len {
|
||||
if a.ptr[i] != b.ptr[i] { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
// 按无符号字节进行字典序比较,结果只承诺小于、等于或大于零。
|
||||
fn cmp(a: ByteView, b: ByteView): i32 {
|
||||
var count = a.len;
|
||||
if b.len < count { count = b.len; }
|
||||
|
||||
var i: usize = 0;
|
||||
while i < count {
|
||||
var av = a.ptr[i];
|
||||
var bv = b.ptr[i];
|
||||
if av < bv { ret -1; }
|
||||
if av > bv { ret 1; }
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
if a.len < b.len { ret -1; }
|
||||
if a.len > b.len { ret 1; }
|
||||
ret 0;
|
||||
}
|
||||
|
||||
// 返回半开区间 [start, end)。失败不会产生部分视图。
|
||||
fn slice(self: ByteView, start: usize, end: usize): ViewResult {
|
||||
if start > end || end > self.len {
|
||||
ret .Err(make(.OutOfRange));
|
||||
}
|
||||
if start == end { ret .Ok(empty()); }
|
||||
if self.ptr == null { ret .Err(make(.InvalidArgument)); }
|
||||
|
||||
var address = self.ptr as usize;
|
||||
var limit: usize = ~(0 as usize);
|
||||
if start > limit - address { ret .Err(make(.Overflow)); }
|
||||
ret .Ok(ByteView { .ptr = (address + start) as *u8, .len = end - start });
|
||||
}
|
||||
|
||||
fn find(haystack: ByteView, needle: ByteView): FindResult {
|
||||
if needle.len == 0 { ret .Found(0); }
|
||||
if needle.len > haystack.len { ret .NotFound; }
|
||||
|
||||
var last = haystack.len - needle.len;
|
||||
var start: usize = 0;
|
||||
while start <= last {
|
||||
var i: usize = 0;
|
||||
while i < needle.len && haystack.ptr[start + i] == needle.ptr[i] {
|
||||
i = i + 1;
|
||||
}
|
||||
if i == needle.len { ret .Found(start); }
|
||||
start = start + 1;
|
||||
}
|
||||
ret .NotFound;
|
||||
}
|
||||
|
||||
fn contains(haystack: ByteView, needle: ByteView): bool {
|
||||
match find(haystack, needle) {
|
||||
.Found(index) => { ret true; },
|
||||
.NotFound => { ret false; },
|
||||
}
|
||||
}
|
||||
|
||||
fn starts_with(self: ByteView, prefix: ByteView): bool {
|
||||
if prefix.len > self.len { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < prefix.len {
|
||||
if self.ptr[i] != prefix.ptr[i] { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn ends_with(self: ByteView, suffix: ByteView): bool {
|
||||
if suffix.len > self.len { ret false; }
|
||||
var start = self.len - suffix.len;
|
||||
var i: usize = 0;
|
||||
while i < suffix.len {
|
||||
if self.ptr[start + i] != suffix.ptr[i] { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn expect_byte(result: ByteResult, expected: u8) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(value == expected, "byte result"); },
|
||||
.Err(error) => { @assert(false, "unexpected byte error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_byte_error(result: ByteResult, out_of_range: bool) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(false, "expected byte error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.OutOfRange => { @assert(out_of_range, "unexpected byte range error"); },
|
||||
.InvalidArgument => { @assert(!out_of_range, "unexpected invalid view"); },
|
||||
_ => { @assert(false, "unexpected byte error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_slice(result: ViewResult, expected: ByteView) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(eq(value, expected), "slice result"); },
|
||||
.Err(error) => { @assert(false, "unexpected slice error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_slice_error(result: ViewResult) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(false, "expected slice error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.OutOfRange => {},
|
||||
_ => { @assert(false, "unexpected slice error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_found(result: FindResult, expected: usize) {
|
||||
match result {
|
||||
.Found(index) => { @assert(index == expected, "find position"); },
|
||||
.NotFound => { @assert(false, "expected match"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_not_found(result: FindResult) {
|
||||
match result {
|
||||
.Found(index) => { @assert(false, "unexpected match"); },
|
||||
.NotFound => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn test_construction_and_access() {
|
||||
var canonical = empty();
|
||||
@assert(canonical.ptr == null && canonical.len == 0, "canonical empty");
|
||||
|
||||
var data: [5]u8 = .[104, 101, 0, 108, 111];
|
||||
var text = from_slice(data[0..5]);
|
||||
@assert(size(text) == 5);
|
||||
@assert(!is_empty(text));
|
||||
expect_byte(byte_at(text, 0), 104);
|
||||
expect_byte(byte_at(text, 2), 0);
|
||||
expect_byte(byte_at(text, 4), 111);
|
||||
expect_byte_error(byte_at(text, 5), true);
|
||||
|
||||
var normalized = from_slice(data[3..3]);
|
||||
@assert(normalized.ptr == null && normalized.len == 0, "empty input normalized");
|
||||
expect_byte_error(byte_at(ByteView { .ptr = null, .len = 1 }, 0), false);
|
||||
|
||||
var bytes = as_slice(text);
|
||||
@assert(bytes.ptr == text.ptr && bytes.len == text.len, "slice conversion borrows");
|
||||
}
|
||||
|
||||
fn test_compare_and_slice() {
|
||||
var hello = from_slice("hello");
|
||||
var same = from_slice("hello");
|
||||
var help = from_slice("help");
|
||||
var high_data: [1]u8 = .[255];
|
||||
var low_data: [1]u8 = .[127];
|
||||
|
||||
@assert(eq(hello, same));
|
||||
@assert(!eq(hello, help));
|
||||
@assert(eq(empty(), from_slice("")));
|
||||
@assert(cmp(hello, same) == 0);
|
||||
@assert(cmp(hello, help) < 0);
|
||||
@assert(cmp(help, hello) > 0);
|
||||
@assert(cmp(from_slice(low_data[0..1]), from_slice(high_data[0..1])) < 0,
|
||||
"comparison uses unsigned bytes");
|
||||
|
||||
expect_slice(slice(hello, 0, 5), hello);
|
||||
expect_slice(slice(hello, 1, 4), from_slice("ell"));
|
||||
match slice(hello, 2, 2) {
|
||||
.Ok(value) => {
|
||||
@assert(value.ptr == null && value.len == 0, "empty subslice normalized");
|
||||
},
|
||||
.Err(error) => { @assert(false, "empty subslice"); },
|
||||
}
|
||||
expect_slice_error(slice(hello, 4, 3));
|
||||
expect_slice_error(slice(hello, 0, 6));
|
||||
}
|
||||
|
||||
fn test_search() {
|
||||
var text = from_slice("ababa");
|
||||
expect_found(find(text, from_slice("aba")), 0);
|
||||
expect_found(find(text, from_slice("bab")), 1);
|
||||
expect_found(find(text, from_slice("ba")), 1);
|
||||
expect_found(find(text, from_slice("")), 0);
|
||||
expect_not_found(find(text, from_slice("abb")));
|
||||
expect_not_found(find(text, from_slice("ababaa")));
|
||||
|
||||
@assert(contains(text, from_slice("aba")));
|
||||
@assert(!contains(text, from_slice("xyz")));
|
||||
@assert(starts_with(text, from_slice("ab")));
|
||||
@assert(starts_with(text, empty()));
|
||||
@assert(!starts_with(text, from_slice("ba")));
|
||||
@assert(ends_with(text, from_slice("ba")));
|
||||
@assert(ends_with(text, empty()));
|
||||
@assert(!ends_with(text, from_slice("ab")));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_construction_and_access();
|
||||
test_compare_and_slice();
|
||||
test_search();
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Expect retcode = 0
|
||||
// 公共错误值不持有资源,可按值复制;不分配错误消息字符串。
|
||||
// position 是调用方输入中的从零开始的位置;仅在 has_position 时有效。
|
||||
// 未定位错误的 position 规范为 0,不能用 position == 0 判断是否有位置。
|
||||
// 数值解析位置按输入字节计数。可用内存耗尽仍由现有 VM 终止处理。
|
||||
|
||||
type Error = @self;
|
||||
|
||||
type Kind = enum {
|
||||
InvalidArgument,
|
||||
OutOfRange,
|
||||
Overflow,
|
||||
DivisionByZero,
|
||||
InvalidDigit,
|
||||
InvalidFormat,
|
||||
UnexpectedEnd,
|
||||
InvalidAlignment,
|
||||
WidthMismatch,
|
||||
Io,
|
||||
}
|
||||
|
||||
kind: Kind,
|
||||
has_position: bool,
|
||||
position: usize,
|
||||
|
||||
// 无成功载荷的结果。带成功值的模块定义自己的 Ok: T / Err: Error 枚举。
|
||||
type Status = enum {
|
||||
Ok,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn make(kind: Kind): Error {
|
||||
ret Error { .kind = kind, .has_position = false, .position = 0 };
|
||||
}
|
||||
|
||||
fn at(kind: Kind, position: usize): Error {
|
||||
ret Error { .kind = kind, .has_position = true, .position = position };
|
||||
}
|
||||
|
||||
fn test_status(result: Status, expected_error: bool) {
|
||||
match result {
|
||||
.Ok => {
|
||||
@assert(!expected_error, "success variant");
|
||||
},
|
||||
.Err(error) => {
|
||||
@assert(expected_error, "error variant");
|
||||
@assert(!error.has_position, "unlocated error");
|
||||
@assert(error.position == 0, "canonical absent position");
|
||||
match error.kind {
|
||||
.Overflow => {},
|
||||
_ => { @assert(false, "error kind preserved in result"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
var success: Status = .Ok;
|
||||
test_status(success, false);
|
||||
var failure: Status = .Err(make(.Overflow));
|
||||
test_status(failure, true);
|
||||
|
||||
var first = at(.InvalidDigit, 0);
|
||||
@assert(first.has_position, "byte zero is a valid position");
|
||||
@assert(first.position == 0);
|
||||
match first.kind {
|
||||
.InvalidDigit => {},
|
||||
_ => { @assert(false, "positioned error kind"); },
|
||||
}
|
||||
|
||||
var original = at(.UnexpectedEnd, 17);
|
||||
var copy = original;
|
||||
copy.position = 3;
|
||||
@assert(original.position == 17, "error copy has independent fields");
|
||||
@assert(copy.position == 3);
|
||||
@assert(copy.has_position);
|
||||
|
||||
var max_position: usize = ~0 as usize;
|
||||
var last = at(.OutOfRange, max_position);
|
||||
@assert(last.has_position, "no reserved position sentinel");
|
||||
@assert(last.position == max_position);
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// Expect retcode = 0
|
||||
// 尺寸计算和字节移动;不分配、不释放内存,不改变任何资源的所有权。
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
|
||||
type SizeResult = enum {
|
||||
Ok: usize,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn checked_add(a: usize, b: usize): SizeResult {
|
||||
var limit: usize = ~(0 as usize);
|
||||
if b > limit - a { ret .Err(make(.Overflow)); }
|
||||
ret .Ok(a + b);
|
||||
}
|
||||
|
||||
fn checked_mul(a: usize, b: usize): SizeResult {
|
||||
if a == 0 { ret .Ok(0); }
|
||||
var limit: usize = ~(0 as usize);
|
||||
if b > limit / a { ret .Err(make(.Overflow)); }
|
||||
ret .Ok(a * b);
|
||||
}
|
||||
|
||||
// alignment 必须是非零的二次幂。已对齐的值保持不变。
|
||||
fn align_up(size: usize, alignment: usize): SizeResult {
|
||||
if alignment == 0 { ret .Err(make(.InvalidAlignment)); }
|
||||
var mask = alignment - 1;
|
||||
if (alignment & mask) != 0 { ret .Err(make(.InvalidAlignment)); }
|
||||
var limit: usize = ~(0 as usize);
|
||||
if size > limit - mask { ret .Err(make(.Overflow)); }
|
||||
ret .Ok((size + mask) & ~mask);
|
||||
}
|
||||
|
||||
// 将 src.len 个字节复制到 dst 的开头,允许两个切片任意重叠。
|
||||
// 零长度源不访问内存,允许空指针。所有可恢复错误均在首次写入前返回。
|
||||
// 非空源要求可读,目标前 src.len 字节要求可写,生命周期由调用方保证。
|
||||
// 长度和地址计算检查不能证明底层分配大小,也不能识别悬空指针。
|
||||
fn move(dst: []u8, src: []u8): Status {
|
||||
var count = src.len;
|
||||
if count == 0 { ret .Ok; }
|
||||
if dst.len < count { ret .Err(make(.OutOfRange)); }
|
||||
if dst.ptr == null || src.ptr == null {
|
||||
ret .Err(make(.InvalidArgument));
|
||||
}
|
||||
|
||||
var dst_addr = dst.ptr as usize;
|
||||
var src_addr = src.ptr as usize;
|
||||
var limit: usize = ~(0 as usize);
|
||||
var last_offset = count - 1;
|
||||
if last_offset > limit - dst_addr || last_offset > limit - src_addr {
|
||||
ret .Err(make(.Overflow));
|
||||
}
|
||||
if dst_addr == src_addr { ret .Ok; }
|
||||
|
||||
if dst_addr < src_addr {
|
||||
var i: usize = 0;
|
||||
while i < count {
|
||||
dst.ptr[i] = src.ptr[i];
|
||||
i = i + 1;
|
||||
}
|
||||
} else {
|
||||
var i = count;
|
||||
while i > 0 {
|
||||
i = i - 1;
|
||||
dst.ptr[i] = src.ptr[i];
|
||||
}
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn expect_size(result: SizeResult, expected: usize) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(value == expected, "checked size result"); },
|
||||
.Err(error) => { @assert(false, "unexpected size error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_overflow(result: SizeResult) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(false, "expected overflow"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.Overflow => {},
|
||||
_ => { @assert(false, "expected overflow kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_alignment_error(result: SizeResult) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(false, "expected invalid alignment"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.InvalidAlignment => {},
|
||||
_ => { @assert(false, "expected invalid alignment kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "unexpected move error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_invalid_move(result: Status) {
|
||||
match result {
|
||||
.Ok => { @assert(false, "expected invalid pointer"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.InvalidArgument => {},
|
||||
_ => { @assert(false, "expected invalid argument kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_move_overflow(result: Status) {
|
||||
match result {
|
||||
.Ok => { @assert(false, "expected address overflow"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.Overflow => {},
|
||||
_ => { @assert(false, "expected address overflow kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn test_sizes() {
|
||||
var limit: usize = ~(0 as usize);
|
||||
expect_size(checked_add(0, 0), 0);
|
||||
expect_size(checked_add(17, 25), 42);
|
||||
expect_size(checked_add(limit, 0), limit);
|
||||
expect_size(checked_add(limit - 1, 1), limit);
|
||||
expect_overflow(checked_add(limit, 1));
|
||||
expect_overflow(checked_add(1, limit));
|
||||
|
||||
expect_size(checked_mul(6, 7), 42);
|
||||
expect_size(checked_mul(0, limit), 0);
|
||||
expect_size(checked_mul(limit, 0), 0);
|
||||
expect_size(checked_mul(limit, 1), limit);
|
||||
expect_size(checked_mul(limit / 2, 2), limit - 1);
|
||||
expect_overflow(checked_mul(limit / 2 + 1, 2));
|
||||
expect_overflow(checked_mul(limit, limit));
|
||||
|
||||
expect_size(align_up(0, 8), 0);
|
||||
expect_size(align_up(1, 8), 8);
|
||||
expect_size(align_up(8, 8), 8);
|
||||
expect_size(align_up(9, 8), 16);
|
||||
expect_size(align_up(limit, 1), limit);
|
||||
expect_size(align_up(limit - 7, 8), limit - 7);
|
||||
expect_overflow(align_up(limit - 6, 8));
|
||||
expect_overflow(align_up(limit, 2));
|
||||
expect_alignment_error(align_up(0, 0));
|
||||
expect_alignment_error(align_up(0, 3));
|
||||
expect_alignment_error(align_up(7, 6));
|
||||
|
||||
var highest_power = limit / 2 + 1;
|
||||
expect_size(align_up(0, highest_power), 0);
|
||||
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));
|
||||
}
|
||||
|
||||
// 穷举同一 8 字节数组内的所有合法源、目标和长度组合。
|
||||
// expected 从原始字节值建立快照,不使用被测 move 构造期望值。
|
||||
fn test_overlap() {
|
||||
var src_start: usize = 0;
|
||||
while src_start <= 8 {
|
||||
var dst_start: usize = 0;
|
||||
while dst_start <= 8 {
|
||||
var count: usize = 0;
|
||||
while count <= 8 - src_start && count <= 8 - dst_start {
|
||||
var data: [8]u8 = .[0, 1, 2, 3, 4, 5, 6, 7];
|
||||
var expected: [8]u8 = .[0, 1, 2, 3, 4, 5, 6, 7];
|
||||
var i: usize = 0;
|
||||
while i < count {
|
||||
expected[dst_start + i] = (src_start + i) as u8;
|
||||
i = i + 1;
|
||||
}
|
||||
expect_ok(move(data[dst_start..8], data[src_start..src_start + count]));
|
||||
i = 0;
|
||||
while i < 8 {
|
||||
@assert(data[i] == expected[i], "overlap and surrounding bytes");
|
||||
i = i + 1;
|
||||
}
|
||||
count = count + 1;
|
||||
}
|
||||
dst_start = dst_start + 1;
|
||||
}
|
||||
src_start = src_start + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_move_errors() {
|
||||
var data: [4]u8 = .[10, 20, 30, 40];
|
||||
var empty: []u8 = .{ .ptr = null, .len = 0 };
|
||||
expect_ok(move(empty, empty));
|
||||
expect_ok(move(data[0..4], empty));
|
||||
|
||||
match move(data[0..1], data[2..4]) {
|
||||
.Ok => { @assert(false, "expected short destination"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.OutOfRange => {},
|
||||
_ => { @assert(false, "expected out of range kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var invalid: []u8 = .{ .ptr = null, .len = 2 };
|
||||
expect_invalid_move(move(data[0..4], invalid));
|
||||
expect_invalid_move(move(invalid, data[0..2]));
|
||||
|
||||
// 伪造仅用于验证拒绝路径的地址,必须在任何解引用之前报告错误。
|
||||
var limit: usize = ~(0 as usize);
|
||||
var overflowing: []u8 = .{ .ptr = limit as *u8, .len = 2 };
|
||||
expect_move_overflow(move(data[0..4], overflowing));
|
||||
expect_move_overflow(move(overflowing, data[0..2]));
|
||||
expect_move_overflow(move(overflowing, overflowing));
|
||||
|
||||
@assert(data[0] == 10, "failure preserves destination byte 0");
|
||||
@assert(data[1] == 20, "failure preserves destination byte 1");
|
||||
@assert(data[2] == 30, "failure preserves destination byte 2");
|
||||
@assert(data[3] == 40, "failure preserves destination byte 3");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_sizes();
|
||||
test_overlap();
|
||||
test_move_errors();
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
// Expect retcode = 0
|
||||
// 类型擦除的动态数组。元素按值复制,数组只释放自身存储,不释放元素内部资源。
|
||||
// Vec 拥有 raw 指向的分配;普通赋值会复制所有权,所有权转移必须使用 take。
|
||||
|
||||
type .{ .Error, .Kind, .Status } = @import("error.spl");
|
||||
type .{ .SizeResult, .checked_add, .checked_mul, .move } = @import("memory.spl");
|
||||
type .{ .alloc, .copy, .free } = @import("../sys/mem.spl");
|
||||
|
||||
type Vec = @self;
|
||||
|
||||
raw: *u8,
|
||||
data: *u8,
|
||||
len: usize,
|
||||
cap: usize,
|
||||
elem_size: usize,
|
||||
elem_align: usize,
|
||||
|
||||
type VecResult = enum {
|
||||
Ok: Vec,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
type PtrResult = enum {
|
||||
Ok: *u8,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn blank(elem_size: usize, elem_align: usize): Vec {
|
||||
ret Vec {
|
||||
.raw = null,
|
||||
.data = null,
|
||||
.len = 0,
|
||||
.cap = 0,
|
||||
.elem_size = elem_size,
|
||||
.elem_align = elem_align,
|
||||
};
|
||||
}
|
||||
|
||||
fn valid_alignment(alignment: usize): bool {
|
||||
ret alignment != 0 && (alignment & (alignment - 1)) == 0;
|
||||
}
|
||||
|
||||
fn new(elem_size: usize, elem_align: usize): VecResult {
|
||||
if elem_size == 0 { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if !valid_alignment(elem_align) {
|
||||
ret .Err(Error.make(.InvalidAlignment));
|
||||
}
|
||||
ret .Ok(blank(elem_size, elem_align));
|
||||
}
|
||||
|
||||
fn valid(self: Vec): bool {
|
||||
if self.elem_size == 0 || !valid_alignment(self.elem_align) { ret false; }
|
||||
if self.len > self.cap { ret false; }
|
||||
if self.cap == 0 {
|
||||
ret self.raw == null && self.data == null && self.len == 0;
|
||||
}
|
||||
if self.raw == null || self.data == null { ret false; }
|
||||
ret (self.data as usize & (self.elem_align - 1)) == 0;
|
||||
}
|
||||
|
||||
fn status_error(kind: Kind): Status {
|
||||
ret .Err(Error.make(kind));
|
||||
}
|
||||
|
||||
// 计算元素区字节数。错误时不修改任何 Vec。
|
||||
fn byte_count(count: usize, elem_size: usize): SizeResult {
|
||||
ret checked_mul(count, elem_size);
|
||||
}
|
||||
|
||||
fn allocate(capacity: usize, elem_size: usize, elem_align: usize): VecResult {
|
||||
if elem_size == 0 { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if !valid_alignment(elem_align) {
|
||||
ret .Err(Error.make(.InvalidAlignment));
|
||||
}
|
||||
if capacity == 0 { ret .Ok(blank(elem_size, elem_align)); }
|
||||
|
||||
var bytes: usize = 0;
|
||||
match byte_count(capacity, elem_size) {
|
||||
.Ok(value) => { bytes = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var total: usize = 0;
|
||||
match checked_add(bytes, elem_align - 1) {
|
||||
.Ok(value) => { total = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var raw = alloc(total) as *u8;
|
||||
var address = raw as usize;
|
||||
var mask = elem_align - 1;
|
||||
if address > (~(0 as usize)) - mask {
|
||||
free(raw as *any);
|
||||
ret .Err(Error.make(.Overflow));
|
||||
}
|
||||
var aligned = (address + mask) & ~mask;
|
||||
ret .Ok(Vec {
|
||||
.raw = raw,
|
||||
.data = aligned as *u8,
|
||||
.len = 0,
|
||||
.cap = capacity,
|
||||
.elem_size = elem_size,
|
||||
.elem_align = elem_align,
|
||||
});
|
||||
}
|
||||
|
||||
// 保证至少可容纳 need 个元素。所有检查在原对象改变前完成。
|
||||
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 fresh: Vec = undefined;
|
||||
match allocate(new_cap, self.elem_size, self.elem_align) {
|
||||
.Ok(value) => { fresh = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var used_bytes: usize = 0;
|
||||
match byte_count(self.len, self.elem_size) {
|
||||
.Ok(value) => { used_bytes = value; },
|
||||
.Err(error) => {
|
||||
drop(&fresh);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
if used_bytes != 0 {
|
||||
copy(fresh.data as *any, self.data as *any, used_bytes);
|
||||
}
|
||||
fresh.len = self.len;
|
||||
|
||||
var old_raw = self.raw;
|
||||
self.* = fresh;
|
||||
free(old_raw as *any);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn get(self: *Vec, index: usize): PtrResult {
|
||||
if !valid(self.*) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if index >= self.len { ret .Err(Error.make(.OutOfRange)); }
|
||||
ret .Ok(self.data + index * self.elem_size);
|
||||
}
|
||||
|
||||
fn set(self: *Vec, index: usize, elem: *u8): Status {
|
||||
if !valid(self.*) || elem == null { ret status_error(.InvalidArgument); }
|
||||
if index >= self.len { ret status_error(.OutOfRange); }
|
||||
var dst = self.data + index * self.elem_size;
|
||||
ret move(dst[0..self.elem_size], elem[0..self.elem_size]);
|
||||
}
|
||||
|
||||
// 追加 count 个连续元素。elems 可以指向 self 当前已初始化的元素区。
|
||||
fn append(self: *Vec, elems: *u8, count: usize): Status {
|
||||
if !valid(self.*) { ret status_error(.InvalidArgument); }
|
||||
if count == 0 { ret .Ok; }
|
||||
if elems == null { ret status_error(.InvalidArgument); }
|
||||
|
||||
var copy_bytes: usize = 0;
|
||||
match byte_count(count, self.elem_size) {
|
||||
.Ok(value) => { copy_bytes = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var new_len: usize = 0;
|
||||
match checked_add(self.len, count) {
|
||||
.Ok(value) => { new_len = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var used_bytes: usize = 0;
|
||||
match byte_count(self.len, self.elem_size) {
|
||||
.Ok(value) => { used_bytes = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var source_inside = false;
|
||||
var source_offset: usize = 0;
|
||||
if used_bytes != 0 {
|
||||
var base = self.data as usize;
|
||||
var source = elems as usize;
|
||||
if source >= base && source - base < used_bytes {
|
||||
source_offset = source - base;
|
||||
if copy_bytes > used_bytes - source_offset {
|
||||
ret status_error(.OutOfRange);
|
||||
}
|
||||
source_inside = true;
|
||||
}
|
||||
}
|
||||
|
||||
match reserve(self, new_len) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if source_inside { elems = self.data + source_offset; }
|
||||
|
||||
var dst = self.data + used_bytes;
|
||||
match move(dst[0..copy_bytes], elems[0..copy_bytes]) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
self.len = new_len;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn push(self: *Vec, elem: *u8): Status {
|
||||
ret append(self, elem, 1);
|
||||
}
|
||||
|
||||
// 将末元素复制到 out,然后缩短数组。out 可以与元素区重叠。
|
||||
fn pop(self: *Vec, out: *u8): Status {
|
||||
if !valid(self.*) || out == null { ret status_error(.InvalidArgument); }
|
||||
if self.len == 0 { ret status_error(.OutOfRange); }
|
||||
var source = self.data + (self.len - 1) * self.elem_size;
|
||||
match move(out[0..self.elem_size], source[0..self.elem_size]) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
self.len = self.len - 1;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn clear(self: *Vec) {
|
||||
self.len = 0;
|
||||
}
|
||||
|
||||
// 成功时先释放 out 原有存储再提交副本;失败保持 out 不变。
|
||||
// out 与 src 指向同一个 Vec 也允许。
|
||||
fn clone(out: *Vec, src: Vec): Status {
|
||||
if !valid(out.*) || !valid(src) { ret status_error(.InvalidArgument); }
|
||||
if out.elem_size != src.elem_size || out.elem_align != src.elem_align {
|
||||
ret status_error(.InvalidArgument);
|
||||
}
|
||||
|
||||
var fresh = blank(src.elem_size, src.elem_align);
|
||||
match reserve(&fresh, src.len) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var bytes: usize = 0;
|
||||
match byte_count(src.len, src.elem_size) {
|
||||
.Ok(value) => { bytes = value; },
|
||||
.Err(error) => {
|
||||
drop(&fresh);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
if bytes != 0 {
|
||||
copy(fresh.data as *any, src.data as *any, bytes);
|
||||
}
|
||||
fresh.len = src.len;
|
||||
drop(out);
|
||||
out.* = fresh;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 显式转移所有权,返回值必须由接收方最终 drop。
|
||||
fn take(source: *Vec): Vec {
|
||||
var result = source.*;
|
||||
source.* = blank(result.elem_size, result.elem_align);
|
||||
ret result;
|
||||
}
|
||||
|
||||
fn drop(self: *Vec) {
|
||||
var elem_size = self.elem_size;
|
||||
var elem_align = self.elem_align;
|
||||
free(self.raw as *any);
|
||||
self.* = blank(elem_size, elem_align);
|
||||
}
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "unexpected Vec error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_error(result: Status, overflow: bool) {
|
||||
match result {
|
||||
.Ok => { @assert(false, "expected Vec error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.Overflow => { @assert(overflow, "unexpected overflow"); },
|
||||
.OutOfRange => { @assert(!overflow, "unexpected range error"); },
|
||||
_ => { @assert(false, "unexpected Vec error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn make_vec(elem_size: usize, elem_align: usize): Vec {
|
||||
match new(elem_size, elem_align) {
|
||||
.Ok(value) => { ret value; },
|
||||
.Err(error) => { @assert(false, "Vec construction"); },
|
||||
}
|
||||
ret blank(1, 1);
|
||||
}
|
||||
|
||||
fn expect_i32(self: *Vec, index: usize, expected: i32) {
|
||||
match get(self, index) {
|
||||
.Ok(ptr) => { @assert((ptr as *i32).* == expected, "Vec element value"); },
|
||||
.Err(error) => { @assert(false, "Vec element access"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn test_construction_and_alignment() {
|
||||
match new(0, 1) {
|
||||
.Ok(value) => { @assert(false, "zero element size"); },
|
||||
.Err(error) => {
|
||||
match error.kind { .InvalidArgument => {}, _ => { @assert(false); }, }
|
||||
},
|
||||
}
|
||||
match new(1, 3) {
|
||||
.Ok(value) => { @assert(false, "invalid element alignment"); },
|
||||
.Err(error) => {
|
||||
match error.kind { .InvalidAlignment => {}, _ => { @assert(false); }, }
|
||||
},
|
||||
}
|
||||
|
||||
var alignment: usize = 1;
|
||||
while alignment <= 64 {
|
||||
var v = make_vec(3, alignment);
|
||||
expect_ok(reserve(&v, 9));
|
||||
@assert((v.data as usize & (alignment - 1)) == 0, "requested alignment");
|
||||
@assert(v.cap >= 9 && v.len == 0);
|
||||
drop(&v);
|
||||
@assert(v.raw == null && v.data == null && v.cap == 0);
|
||||
alignment = alignment * 2;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_growth_and_access() {
|
||||
var v = make_vec(@sizeof(i32), @alignof(i32));
|
||||
var i: i32 = 0;
|
||||
while i < 1000 {
|
||||
var value = i * 3 - 7;
|
||||
expect_ok(push(&v, &value as *u8));
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(v.len == 1000 && v.cap >= v.len);
|
||||
i = 0;
|
||||
while i < 1000 {
|
||||
expect_i32(&v, i as usize, i * 3 - 7);
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
var replacement: i32 = 123456;
|
||||
expect_ok(set(&v, 500, &replacement as *u8));
|
||||
expect_i32(&v, 500, replacement);
|
||||
expect_error(set(&v, 1000, &replacement as *u8), false);
|
||||
|
||||
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));
|
||||
var i: i32 = 0;
|
||||
while i < 8 {
|
||||
expect_ok(push(&v, &i as *u8));
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(v.len == 8 && v.cap == 8, "prepare growth boundary");
|
||||
|
||||
// 源指向旧分配,append 必须在扩容后重新定位它。
|
||||
expect_ok(append(&v, v.data, v.len));
|
||||
@assert(v.len == 16);
|
||||
i = 0;
|
||||
while i < 16 {
|
||||
expect_i32(&v, i as usize, i % 8);
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
var preserved_len = v.len;
|
||||
expect_error(append(&v, v.data + (v.len - 1) * v.elem_size, 2), false);
|
||||
@assert(v.len == preserved_len, "invalid self range preserves Vec");
|
||||
expect_i32(&v, 15, 7);
|
||||
|
||||
var last_ptr: *u8 = null;
|
||||
match get(&v, 15) {
|
||||
.Ok(ptr) => { last_ptr = ptr; },
|
||||
.Err(error) => { @assert(false); },
|
||||
}
|
||||
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));
|
||||
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));
|
||||
expect_ok(clone(©, source));
|
||||
expect_i32(©, 0, 1);
|
||||
one = 99;
|
||||
expect_ok(set(&source, 0, &one as *u8));
|
||||
expect_i32(©, 0, 1);
|
||||
|
||||
// out 与 src 为同一对象仍产生内容相同的新分配。
|
||||
var old_data = copy.data;
|
||||
expect_ok(clone(©, copy));
|
||||
@assert(copy.data != old_data, "self clone replaces allocation");
|
||||
expect_i32(©, 1, 2);
|
||||
|
||||
var moved = take(©);
|
||||
@assert(copy.raw == null && copy.data == null && copy.len == 0 && copy.cap == 0);
|
||||
expect_i32(&moved, 0, 1);
|
||||
|
||||
var old_raw = moved.raw;
|
||||
var old_len = moved.len;
|
||||
var old_cap = moved.cap;
|
||||
expect_error(reserve(&moved, (~(0 as usize)) / moved.elem_size + 1), true);
|
||||
@assert(moved.raw == old_raw && moved.len == old_len && moved.cap == old_cap,
|
||||
"reserve failure preserves Vec");
|
||||
|
||||
clear(&moved);
|
||||
@assert(moved.len == 0 && moved.cap == old_cap, "clear retains capacity");
|
||||
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() {
|
||||
test_construction_and_alignment();
|
||||
test_growth_and_access();
|
||||
test_self_reference();
|
||||
test_clone_take_and_failures();
|
||||
}
|
||||
Reference in new issue
Block a user