stage2 更多底层函数 stage1 layout正确使用 内置函数重新编写
This commit is contained in:
1 parent
fb0ff9b663
commit
4141e7d574
15 files changed
+2583
-117
No files matched your search
+47
-11
@@ -373,6 +373,40 @@ static spl_ir_node_ref_t ast2ir_intrin_call(spl_ast2ir_t *a, spl_ast_node_t *n,
|
||||
spl_ast_node_t *arg = spl_ast_node(a->ast, vec_at(*args, 0));
|
||||
return spl_ir_builder_type_alignof(a->ir, node_ir(a, arg));
|
||||
}
|
||||
case SPL_INTRIN_ENUM2INT: {
|
||||
/* enum 值 -> 其 tag(判别值);field 0 即 tag。 */
|
||||
spl_ast_node_t *src = spl_ast_node(a->ast, vec_at(*args, 0));
|
||||
spl_ir_node_ref_t v = emit_expr(a, vec_at(*args, 0));
|
||||
if (!v)
|
||||
return 0;
|
||||
spl_type_id_t agg_t = node_ir(a, src);
|
||||
spl_type_id_t tag_t = node_ir(a, n);
|
||||
if (!agg_t || !tag_t)
|
||||
return 0;
|
||||
return spl_ir_builder_agg_extract(a->ir, agg_t, tag_t, 0, v);
|
||||
}
|
||||
case SPL_INTRIN_FIELD_COUNT: {
|
||||
spl_ast_node_t *arg = spl_ast_node(a->ast, vec_at(*args, 0));
|
||||
return spl_ir_builder_type_field_count(a->ir, node_ir(a, arg));
|
||||
}
|
||||
case SPL_INTRIN_OFFSETOF: {
|
||||
spl_ast_node_t *arg = spl_ast_node(a->ast, vec_at(*args, 0));
|
||||
spl_type_id_t agg_t = node_ir(a, arg);
|
||||
if (!agg_t)
|
||||
return 0;
|
||||
spl_ast_node_t *fnode = spl_ast_node(a->ast, vec_at(*args, 1));
|
||||
usize idx = 0;
|
||||
if (fnode && fnode->kind == SPL_AST_EXPR_INT_LIT) {
|
||||
idx = (usize)fnode->int_lit.bits;
|
||||
} else if (fnode && fnode->kind == SPL_AST_EXPR_IDENT) {
|
||||
if (!spl_type_agg_member_idx(&a->sema->type->ir, agg_t, fnode->ident.name, &idx))
|
||||
return 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
spl_ir_node_ref_t idx_node = const_usize(a, idx);
|
||||
return spl_ir_builder_type_offsetof(a->ir, agg_t, idx_node);
|
||||
}
|
||||
case SPL_INTRIN_ASSERT: {
|
||||
spl_ir_node_ref_t cond = emit_expr(a, vec_at(*args, 0));
|
||||
if (!cond)
|
||||
@@ -420,11 +454,11 @@ static spl_ir_node_ref_t ast2ir_intrin_call(spl_ast2ir_t *a, spl_ast_node_t *n,
|
||||
case SPL_INTRIN_TRUNCATE:
|
||||
case SPL_INTRIN_BIT_CAST:
|
||||
case SPL_INTRIN_FLOAT_CAST:
|
||||
case SPL_INTRIN_INT_FROM_FLOAT:
|
||||
case SPL_INTRIN_FLOAT_FROM_INT:
|
||||
case SPL_INTRIN_INT_FROM_BOOL:
|
||||
case SPL_INTRIN_INT_FROM_PTR:
|
||||
case SPL_INTRIN_PTR_FROM_INT:
|
||||
case SPL_INTRIN_FLOAT2INT:
|
||||
case SPL_INTRIN_INT2FLOAT:
|
||||
case SPL_INTRIN_BOOL2INT:
|
||||
case SPL_INTRIN_PTR2INT:
|
||||
case SPL_INTRIN_INT2PTR:
|
||||
case SPL_INTRIN_PTR_CAST:
|
||||
return ast2ir_intrin_cast(a, n, in, args);
|
||||
case SPL_INTRIN_BREAKPOINT:
|
||||
@@ -619,15 +653,15 @@ static spl_ir_node_ref_t ast2ir_emit_cast_builtin(spl_ast2ir_t *a, spl_ast_node_
|
||||
return spl_ir_builder_cast_ftrunc(a->ir, ft, tt, v);
|
||||
return spl_ir_builder_cast_fext(a->ir, ft, tt, v);
|
||||
}
|
||||
case SPL_INTRIN_INT_FROM_FLOAT:
|
||||
case SPL_INTRIN_FLOAT2INT:
|
||||
return spl_ir_builder_case_float2int(a->ir, ft, tt, v);
|
||||
case SPL_INTRIN_FLOAT_FROM_INT:
|
||||
case SPL_INTRIN_INT2FLOAT:
|
||||
return spl_ir_builder_case_int2float(a->ir, ft, tt, v);
|
||||
case SPL_INTRIN_INT_FROM_BOOL:
|
||||
case SPL_INTRIN_BOOL2INT:
|
||||
return spl_ir_builder_cast_bool2int(a->ir, ft, tt, v);
|
||||
case SPL_INTRIN_INT_FROM_PTR:
|
||||
case SPL_INTRIN_PTR2INT:
|
||||
return spl_ir_builder_cast_ptr2int(a->ir, ft, tt, v);
|
||||
case SPL_INTRIN_PTR_FROM_INT:
|
||||
case SPL_INTRIN_INT2PTR:
|
||||
return spl_ir_builder_cast_int2ptr(a->ir, ft, tt, v);
|
||||
case SPL_INTRIN_PTR_CAST:
|
||||
return spl_ir_builder_cast_bitcast(a->ir, ft, tt, v);
|
||||
@@ -1935,10 +1969,12 @@ static spl_ir_node_ref_t emit_implicit_cast(spl_ast2ir_t *a, spl_ast_node_t *n,
|
||||
static usize spl_int_map_hash(usize k) { return MAP_HASH_INT(k); }
|
||||
static int spl_int_map_cmp(usize a, usize b) { return MAP_CMP_INT(a, b); }
|
||||
|
||||
void spl_ast2ir_init(spl_ast2ir_t *ast2ir, spl_ir_builder_t *ir, const spl_sema_t *sema) {
|
||||
void spl_ast2ir_init(spl_ast2ir_t *ast2ir, spl_ir_builder_t *ir, spl_layout_t *layout,
|
||||
const spl_sema_t *sema) {
|
||||
ast2ir->sema = sema;
|
||||
ast2ir->ir = ir;
|
||||
ast2ir->ast = sema->ast;
|
||||
ast2ir->layout = layout;
|
||||
map_init(ast2ir->def2ir_map, spl_int_map_hash, spl_int_map_cmp);
|
||||
vec_init(ast2ir->loop_stack);
|
||||
vec_init(ast2ir->defer_stack);
|
||||
|
||||
+3
-1
@@ -23,6 +23,7 @@ typedef struct {
|
||||
const spl_sema_t *sema;
|
||||
spl_ir_builder_t *ir;
|
||||
spl_ast_t *ast;
|
||||
spl_layout_t *layout; /* 借用 splc0 的 layout */
|
||||
spl_def2ir_map_t def2ir_map;
|
||||
spl_loop_ctx_vec_t loop_stack;
|
||||
spl_defer_stack_t defer_stack;
|
||||
@@ -32,7 +33,8 @@ typedef struct {
|
||||
spl_diag_t *diag;
|
||||
} spl_ast2ir_t;
|
||||
|
||||
void spl_ast2ir_init(spl_ast2ir_t *ast2ir, spl_ir_builder_t *ir, const spl_sema_t *sema);
|
||||
void spl_ast2ir_init(spl_ast2ir_t *ast2ir, spl_ir_builder_t *ir, spl_layout_t *layout,
|
||||
const spl_sema_t *sema);
|
||||
void spl_ast2ir_drop(spl_ast2ir_t *ast2ir);
|
||||
|
||||
void spl_ast2ir_run(spl_ast2ir_t *ast2ir, spl_ast_ptr_vec_t ast_vec);
|
||||
|
||||
+21
-14
@@ -22,18 +22,24 @@ static const spl_intrin_info_t g_intrin_table[] = {
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_FLOAT_CAST] = {"@float_cast", SPL_INTRIN_FLOAT_CAST, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_INT_FROM_FLOAT] = {"@int_from_float", SPL_INTRIN_INT_FROM_FLOAT,
|
||||
SPL_INTRIN_FORM_CALL, 2, 2, SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_FLOAT_FROM_INT] = {"@float_from_int", SPL_INTRIN_FLOAT_FROM_INT,
|
||||
SPL_INTRIN_FORM_CALL, 2, 2, SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_INT_FROM_BOOL] = {"@int_from_bool", SPL_INTRIN_INT_FROM_BOOL, SPL_INTRIN_FORM_CALL,
|
||||
1, 2, SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_INT_FROM_PTR] = {"@int_from_ptr", SPL_INTRIN_INT_FROM_PTR, SPL_INTRIN_FORM_CALL, 1,
|
||||
2, SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_PTR_FROM_INT] = {"@ptr_from_int", SPL_INTRIN_PTR_FROM_INT, SPL_INTRIN_FORM_CALL, 2,
|
||||
2, SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_FLOAT2INT] = {"@float2int", SPL_INTRIN_FLOAT2INT, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_INT2FLOAT] = {"@int2float", SPL_INTRIN_INT2FLOAT, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_BOOL2INT] = {"@bool2int", SPL_INTRIN_BOOL2INT, SPL_INTRIN_FORM_CALL, 1, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_PTR2INT] = {"@ptr2int", SPL_INTRIN_PTR2INT, SPL_INTRIN_FORM_CALL, 1, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_INT2PTR] = {"@int2ptr", SPL_INTRIN_INT2PTR, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_PTR_CAST] = {"@ptr_cast", SPL_INTRIN_PTR_CAST, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_ENUM2INT] = {"@enum2int", SPL_INTRIN_ENUM2INT, SPL_INTRIN_FORM_CALL, 1, 1,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_OFFSETOF] = {"@offsetof", SPL_INTRIN_OFFSETOF, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_FIELD_COUNT] = {"@field_count", SPL_INTRIN_FIELD_COUNT, SPL_INTRIN_FORM_CALL, 1, 1,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
};
|
||||
|
||||
static const usize g_intrin_count = sizeof(g_intrin_table) / sizeof(g_intrin_table[0]);
|
||||
@@ -88,14 +94,15 @@ spl_intrin_t spl_intrin_lookup_cast(spl_type_t *type, spl_type_id_t ft, spl_type
|
||||
if (f->kind == SPL_TYPE_FLOAT && t->kind == SPL_TYPE_FLOAT)
|
||||
return SPL_INTRIN_FLOAT_CAST;
|
||||
if (f->kind == SPL_TYPE_BOOL && t->kind == SPL_TYPE_INT)
|
||||
return SPL_INTRIN_INT_FROM_BOOL;
|
||||
return SPL_INTRIN_BOOL2INT;
|
||||
if (f->kind == SPL_TYPE_PTR && t->kind == SPL_TYPE_INT)
|
||||
return SPL_INTRIN_INT_FROM_PTR;
|
||||
return SPL_INTRIN_PTR2INT;
|
||||
if (f->kind == SPL_TYPE_INT && t->kind == SPL_TYPE_PTR)
|
||||
return SPL_INTRIN_PTR_FROM_INT;
|
||||
return SPL_INTRIN_INT2PTR;
|
||||
if (f->kind == SPL_TYPE_PTR && t->kind == SPL_TYPE_PTR)
|
||||
return SPL_INTRIN_PTR_CAST;
|
||||
|
||||
/* int float 刻意不在这里:只能通过显式 builtin 调用。 */
|
||||
/* int float 刻意不在这里:只能通过显式 builtin 调用。
|
||||
enum<->int 也刻意禁止(用 @enum2int 取 tag)。 */
|
||||
return SPL_INTRIN_NONE;
|
||||
}
|
||||
+8
-5
@@ -16,12 +16,15 @@ typedef enum {
|
||||
SPL_INTRIN_TRUNCATE,
|
||||
SPL_INTRIN_BIT_CAST,
|
||||
SPL_INTRIN_FLOAT_CAST,
|
||||
SPL_INTRIN_INT_FROM_FLOAT,
|
||||
SPL_INTRIN_FLOAT_FROM_INT,
|
||||
SPL_INTRIN_INT_FROM_BOOL,
|
||||
SPL_INTRIN_INT_FROM_PTR,
|
||||
SPL_INTRIN_PTR_FROM_INT,
|
||||
SPL_INTRIN_FLOAT2INT,
|
||||
SPL_INTRIN_INT2FLOAT,
|
||||
SPL_INTRIN_BOOL2INT,
|
||||
SPL_INTRIN_PTR2INT,
|
||||
SPL_INTRIN_INT2PTR,
|
||||
SPL_INTRIN_PTR_CAST,
|
||||
SPL_INTRIN_ENUM2INT,
|
||||
SPL_INTRIN_OFFSETOF,
|
||||
SPL_INTRIN_FIELD_COUNT,
|
||||
} spl_intrin_t;
|
||||
|
||||
typedef enum {
|
||||
|
||||
+45
-62
@@ -3,49 +3,24 @@
|
||||
|
||||
static usize align_up(usize x, usize a);
|
||||
|
||||
/* size/align/offset 统一取自 layout(spl_layout 是唯一准确来源)。 */
|
||||
static const spl_layout_info_t *ir_layout(spl_ir2vm_t *ctx, spl_type_id_t tid) {
|
||||
return spl_layout_info_of(ctx->layout, tid);
|
||||
}
|
||||
|
||||
static usize ir_type_size(spl_ir2vm_t *ctx, spl_type_id_t tid) {
|
||||
spl_type_node_t *tn = spl_type_node(ctx->type, tid);
|
||||
if (!tn)
|
||||
return 8;
|
||||
switch (tn->kind) {
|
||||
case SPL_TYPE_UNIT:
|
||||
const spl_layout_info_t *info = ir_layout(ctx, tid);
|
||||
if (!info)
|
||||
return 0;
|
||||
case SPL_TYPE_BOOL:
|
||||
return (info->size_bits + 7) / 8;
|
||||
}
|
||||
|
||||
static usize ir_type_align(spl_ir2vm_t *ctx, spl_type_id_t tid) {
|
||||
const spl_layout_info_t *info = ir_layout(ctx, tid);
|
||||
if (!info)
|
||||
return 1;
|
||||
case SPL_TYPE_INT:
|
||||
return (tn->int_type.bits + 7) / 8;
|
||||
case SPL_TYPE_FLOAT:
|
||||
return (tn->float_type.bits + 7) / 8;
|
||||
case SPL_TYPE_PTR:
|
||||
return sizeof(void *);
|
||||
case SPL_TYPE_FN:
|
||||
return sizeof(void *);
|
||||
case SPL_TYPE_ARRAY:
|
||||
return ir_type_size(ctx, tn->array_type.element) * tn->array_type.len;
|
||||
case SPL_TYPE_ENUM: {
|
||||
usize tag_sz = ir_type_size(ctx, tn->agg_type.tag_type);
|
||||
usize payload_align = 1;
|
||||
usize payload_size = 0;
|
||||
for (usize i = 0; i < vec_size(tn->agg_type.variants); ++i) {
|
||||
usize vs = ir_type_size(ctx, vec_at(tn->agg_type.variants, i).type_id);
|
||||
if (vs > payload_align)
|
||||
payload_align = vs;
|
||||
if (vs > payload_size)
|
||||
payload_size = vs;
|
||||
}
|
||||
usize payload_off = align_up(tag_sz, payload_align);
|
||||
return align_up(payload_off + payload_size, payload_align);
|
||||
}
|
||||
case SPL_TYPE_STRUCT:
|
||||
case SPL_TYPE_UNION: {
|
||||
usize s = 0;
|
||||
for (usize i = 0; i < vec_size(tn->agg_type.variants); ++i)
|
||||
s += ir_type_size(ctx, vec_at(tn->agg_type.variants, i).type_id);
|
||||
return s;
|
||||
}
|
||||
default:
|
||||
return 8;
|
||||
}
|
||||
usize a = info->align_bits / 8;
|
||||
return a ? a : 1;
|
||||
}
|
||||
|
||||
static bool ir_is_aggregate(spl_ir2vm_t *ctx, spl_type_id_t tid) {
|
||||
@@ -98,27 +73,13 @@ static usize ir_agg_field_off(spl_ir2vm_t *ctx, spl_type_id_t tid, usize idx) {
|
||||
spl_type_node_t *tn = spl_type_node(ctx->type, tid);
|
||||
if (!tn)
|
||||
return 0;
|
||||
if (tn->kind == SPL_TYPE_STRUCT || tn->kind == SPL_TYPE_UNION) {
|
||||
usize off = 0;
|
||||
for (usize i = 0; i < idx && i < vec_size(tn->agg_type.variants); ++i)
|
||||
off += ir_type_size(ctx, vec_at(tn->agg_type.variants, i).type_id);
|
||||
return off;
|
||||
}
|
||||
if (tn->kind == SPL_TYPE_ARRAY)
|
||||
return idx * ir_type_size(ctx, tn->array_type.element);
|
||||
if (tn->kind == SPL_TYPE_ENUM) {
|
||||
if (idx == 0)
|
||||
return 0;
|
||||
usize tag_sz = ir_type_size(ctx, tn->agg_type.tag_type);
|
||||
usize payload_align = 1;
|
||||
for (usize i = 0; i < vec_size(tn->agg_type.variants); ++i) {
|
||||
usize vs = ir_type_size(ctx, vec_at(tn->agg_type.variants, i).type_id);
|
||||
if (vs > payload_align)
|
||||
payload_align = vs;
|
||||
}
|
||||
return align_up(tag_sz, payload_align);
|
||||
}
|
||||
|
||||
const spl_layout_info_t *info = ir_layout(ctx, tid);
|
||||
if (!info || idx >= vec_size(info->field_bit_offsets))
|
||||
return 0;
|
||||
return vec_at(info->field_bit_offsets, idx) / 8;
|
||||
}
|
||||
|
||||
static int get_or_add_native(spl_ir2vm_t *ctx, const char *name) {
|
||||
@@ -752,12 +713,31 @@ static void cg_typeop(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
case SPL_IR_TYPE_SIZEOF:
|
||||
v = ir_type_size(ctx, n->ir_sizeof.tid);
|
||||
break;
|
||||
case SPL_IR_TYPE_BITSIZEOF:
|
||||
v = ir_type_size(ctx, n->bitsizeof.tid) * 8;
|
||||
case SPL_IR_TYPE_BITSIZEOF: {
|
||||
const spl_layout_info_t *info = ir_layout(ctx, n->bitsizeof.tid);
|
||||
v = info ? info->size_bits : 0;
|
||||
break;
|
||||
}
|
||||
case SPL_IR_TYPE_ALIGNOF:
|
||||
v = ir_type_size(ctx, n->ir_alignof.tid);
|
||||
v = ir_type_align(ctx, n->ir_alignof.tid);
|
||||
break;
|
||||
case SPL_IR_TYPE_OFFSETOF: {
|
||||
usize idx = 0;
|
||||
if (n->ir_offsetof.field_idx < vec_size(cg->irf->nodes)) {
|
||||
const spl_ir_node_t *idx_node = &vec_at(cg->irf->nodes, n->ir_offsetof.field_idx);
|
||||
if (idx_node->kind == SPL_IR_TYPE_CONST)
|
||||
idx = (usize)idx_node->type_const.int_lit;
|
||||
}
|
||||
v = ir_agg_field_off(ctx, n->ir_offsetof.tid, idx);
|
||||
break;
|
||||
}
|
||||
case SPL_IR_TYPE_FIELD_COUNT: {
|
||||
spl_type_node_t *tn = spl_type_node(ctx->type, n->field_count.tid);
|
||||
if (tn && (tn->kind == SPL_TYPE_STRUCT || tn->kind == SPL_TYPE_UNION ||
|
||||
tn->kind == SPL_TYPE_ENUM || tn->kind == SPL_TYPE_SHAPE))
|
||||
v = vec_size(tn->agg_type.variants);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
@@ -1028,6 +1008,8 @@ static void cg_node(cgen_t *cg, usize ref, const spl_ir_node_t *n) {
|
||||
case SPL_IR_TYPE_SIZEOF:
|
||||
case SPL_IR_TYPE_BITSIZEOF:
|
||||
case SPL_IR_TYPE_ALIGNOF:
|
||||
case SPL_IR_TYPE_OFFSETOF:
|
||||
case SPL_IR_TYPE_FIELD_COUNT:
|
||||
cg_typeop(cg, ref, n);
|
||||
break;
|
||||
|
||||
@@ -1142,11 +1124,12 @@ static usize spl_map_hash(usize k) { return MAP_HASH_INT(k); }
|
||||
static int spl_map_cmp(usize a, usize b) { return MAP_CMP_INT(a, b); }
|
||||
|
||||
void spl_ir2vm_init(spl_ir2vm_t *ctx, const spl_ir_t *ir, const spl_type_t *type,
|
||||
const spl_attr_t *attr) {
|
||||
spl_layout_t *layout, const spl_attr_t *attr) {
|
||||
Assert(ir != NULL && type != NULL && attr != NULL);
|
||||
ctx->ir = ir;
|
||||
ctx->type = type;
|
||||
ctx->attr = attr;
|
||||
ctx->layout = layout;
|
||||
map_init(ctx->extern_native, spl_map_hash, spl_map_cmp);
|
||||
spl_prog_init(&ctx->prog);
|
||||
|
||||
|
||||
+3
-1
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "spl_diag.h"
|
||||
#include "spl_ir.h"
|
||||
#include "spl_layout.h"
|
||||
#include "spl_type.h"
|
||||
|
||||
#include "../stage0/spl_mcode.h"
|
||||
@@ -21,12 +22,13 @@ typedef struct {
|
||||
const spl_attr_t *attr;
|
||||
spl_extern_idx_map_t extern_native;
|
||||
spl_prog_t prog;
|
||||
spl_layout_t *layout; /* 借用 splc0 的 layout(size/align/offset 唯一来源) */
|
||||
|
||||
spl_diag_t *diag;
|
||||
} spl_ir2vm_t;
|
||||
|
||||
void spl_ir2vm_init(spl_ir2vm_t *ctx, const spl_ir_t *ir, const spl_type_t *type,
|
||||
const spl_attr_t *attr);
|
||||
spl_layout_t *layout, const spl_attr_t *attr);
|
||||
void spl_ir2vm_drop(spl_ir2vm_t *ctx);
|
||||
|
||||
int spl_ir2vm_run_func(spl_ir2vm_t *ctx, spl_ir_func_ref_t ref);
|
||||
|
||||
+35
-9
@@ -50,8 +50,9 @@ void spl_layout_init(spl_layout_t *layout, spl_layout_target_t *target) {
|
||||
spl_layout_add_scalar(layout, 4, 4); /* i32 / u32 / f32 */
|
||||
spl_layout_add_scalar(layout, 8, 8); /* i64 / u64 / f64 */
|
||||
|
||||
usize ps = sizeof(void *);
|
||||
spl_layout_add_ptr(layout, 0, ps, ps);
|
||||
usize ps = target && target->ptr_size ? target->ptr_size : sizeof(void *);
|
||||
usize pa = target && target->ptr_align ? target->ptr_align : sizeof(void *);
|
||||
spl_layout_add_ptr(layout, 0, ps, pa);
|
||||
}
|
||||
|
||||
void spl_layout_drop(spl_layout_t *layout) {
|
||||
@@ -145,6 +146,8 @@ static bool layout_agg(spl_layout_t *layout, spl_type_node_t *tn, spl_layout_inf
|
||||
|
||||
for (usize i = 0; i < n; ++i) {
|
||||
spl_type_member_t *m = &vec_at(tn->agg_type.variants, i);
|
||||
if (m->type_id == 0) /* 无载荷变体 */
|
||||
continue;
|
||||
spl_layout_info_t *fi = infos_at(layout, m->type_id);
|
||||
if (!fi || !fi->resolved)
|
||||
return false;
|
||||
@@ -160,6 +163,21 @@ static bool layout_agg(spl_layout_t *layout, spl_type_node_t *tn, spl_layout_inf
|
||||
tag_align_bits > payload_align_bits ? tag_align_bits : payload_align_bits;
|
||||
out->size_bits = round_up_bits(payload_off + payload_size_bits, final_align_bits);
|
||||
out->align_bits = final_align_bits;
|
||||
|
||||
/* 字段索引约定(与 ir2vm 一致):0 = tag,1..n = 各变体载荷(同一偏移)。 */
|
||||
vec_push(out->field_bit_offsets, 0);
|
||||
vec_push(out->field_bit_sizes, tag_info->size_bits);
|
||||
for (usize i = 0; i < n; ++i) {
|
||||
spl_type_member_t *m = &vec_at(tn->agg_type.variants, i);
|
||||
usize fsize = 0;
|
||||
if (m->type_id) {
|
||||
spl_layout_info_t *fi = infos_at(layout, m->type_id);
|
||||
if (fi)
|
||||
fsize = fi->size_bits;
|
||||
}
|
||||
vec_push(out->field_bit_offsets, payload_off);
|
||||
vec_push(out->field_bit_sizes, fsize);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -175,22 +193,27 @@ static bool layout_agg(spl_layout_t *layout, spl_type_node_t *tn, spl_layout_inf
|
||||
|
||||
for (usize i = 0; i < n; ++i) {
|
||||
spl_type_member_t *m = &vec_at(tn->agg_type.variants, i);
|
||||
usize fsize_bits = 0;
|
||||
usize fsize_align = 8;
|
||||
if (m->type_id) {
|
||||
spl_layout_info_t *fi = infos_at(layout, m->type_id);
|
||||
if (!fi || !fi->resolved)
|
||||
return false;
|
||||
|
||||
usize field_align_bits = fi->align_bits ? fi->align_bits : 8;
|
||||
fsize_bits = fi->size_bits;
|
||||
fsize_align = fi->align_bits ? fi->align_bits : 8;
|
||||
}
|
||||
usize field_align_bits = fsize_align;
|
||||
|
||||
if (is_union) {
|
||||
vec_push(out->field_bit_offsets, 0);
|
||||
vec_push(out->field_bit_sizes, fi->size_bits);
|
||||
if (fi->size_bits > out->size_bits)
|
||||
out->size_bits = fi->size_bits;
|
||||
vec_push(out->field_bit_sizes, fsize_bits);
|
||||
if (fsize_bits > out->size_bits)
|
||||
out->size_bits = fsize_bits;
|
||||
} else {
|
||||
offset_bits = round_up_bits(offset_bits, field_align_bits);
|
||||
vec_push(out->field_bit_offsets, offset_bits);
|
||||
vec_push(out->field_bit_sizes, fi->size_bits);
|
||||
offset_bits += fi->size_bits;
|
||||
vec_push(out->field_bit_sizes, fsize_bits);
|
||||
offset_bits += fsize_bits;
|
||||
}
|
||||
if (field_align_bits > max_align_bits)
|
||||
max_align_bits = field_align_bits;
|
||||
@@ -266,8 +289,11 @@ static void resolve_type(spl_layout_t *layout, spl_type_t *type, spl_type_id_t i
|
||||
case SPL_TYPE_UNION:
|
||||
case SPL_TYPE_ENUM:
|
||||
case SPL_TYPE_SHAPE: {
|
||||
if (tn->kind == SPL_TYPE_ENUM && tn->agg_type.tag_type)
|
||||
resolve_type(layout, type, tn->agg_type.tag_type);
|
||||
for (usize i = 0; i < vec_size(tn->agg_type.variants); ++i) {
|
||||
spl_type_member_t *m = &vec_at(tn->agg_type.variants, i);
|
||||
if (m->type_id)
|
||||
resolve_type(layout, type, m->type_id);
|
||||
}
|
||||
layout_agg(layout, tn, info);
|
||||
|
||||
+2
-1
@@ -32,7 +32,8 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
enum { SPL_BIG_ENDIAN, SPL_LITTLE_ENDIAN } endian; // TODO 更多位存储方式
|
||||
|
||||
usize ptr_size;
|
||||
usize ptr_align;
|
||||
} spl_layout_target_t;
|
||||
|
||||
typedef struct {
|
||||
|
||||
+55
-7
@@ -938,9 +938,9 @@ static spl_lang_type_id_t sema_intrin_call(spl_sema_t *sema, spl_ast_node_t *n,
|
||||
case SPL_INTRIN_TRUNCATE:
|
||||
case SPL_INTRIN_BIT_CAST:
|
||||
case SPL_INTRIN_FLOAT_CAST:
|
||||
case SPL_INTRIN_INT_FROM_FLOAT:
|
||||
case SPL_INTRIN_FLOAT_FROM_INT:
|
||||
case SPL_INTRIN_PTR_FROM_INT:
|
||||
case SPL_INTRIN_FLOAT2INT:
|
||||
case SPL_INTRIN_INT2FLOAT:
|
||||
case SPL_INTRIN_INT2PTR:
|
||||
case SPL_INTRIN_PTR_CAST: {
|
||||
spl_lang_type_id_t src_lang = sema_infer(sema, vec_at(*args, 0));
|
||||
if (!src_lang)
|
||||
@@ -959,8 +959,8 @@ static spl_lang_type_id_t sema_intrin_call(spl_sema_t *sema, spl_ast_node_t *n,
|
||||
n->resolved_lang_tid = dst_lang;
|
||||
return dst_lang;
|
||||
}
|
||||
case SPL_INTRIN_INT_FROM_BOOL:
|
||||
case SPL_INTRIN_INT_FROM_PTR: {
|
||||
case SPL_INTRIN_BOOL2INT:
|
||||
case SPL_INTRIN_PTR2INT: {
|
||||
sema_infer(sema, vec_at(*args, 0));
|
||||
if (nargs >= 2) {
|
||||
spl_lang_type_id_t dst = sema_infer(sema, vec_at(*args, 1));
|
||||
@@ -973,6 +973,53 @@ static spl_lang_type_id_t sema_intrin_call(spl_sema_t *sema, spl_ast_node_t *n,
|
||||
n->resolved_lang_tid = r;
|
||||
return r;
|
||||
}
|
||||
case SPL_INTRIN_ENUM2INT: {
|
||||
spl_lang_type_id_t src = sema_infer(sema, vec_at(*args, 0));
|
||||
if (!src)
|
||||
return 0;
|
||||
spl_lang_type_node_t *sn = spl_lang_type_node(sema->type, src);
|
||||
if (!sn || sn->kind != SPL_LANG_TYPE_ENUM) {
|
||||
spl_diag_error(sema->diag, SPL_DIAG_KIND_TYPE_MISMATCH, &n->dbg,
|
||||
"@enum2int requires an enum operand");
|
||||
return 0;
|
||||
}
|
||||
spl_type_node_t *sir = spl_type_node(&sema->type->ir, sn->ir_type_id);
|
||||
if (!sir || !sir->agg_type.tag_type) {
|
||||
spl_diag_error(sema->diag, SPL_DIAG_KIND_INTERNAL, &n->dbg,
|
||||
"@enum2int: enum has no tag type");
|
||||
return 0;
|
||||
}
|
||||
spl_lang_type_id_t tag = sema_lang_from_ir(sema, sir->agg_type.tag_type);
|
||||
n->resolved_lang_tid = tag;
|
||||
return tag;
|
||||
}
|
||||
case SPL_INTRIN_FIELD_COUNT: {
|
||||
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_OFFSETOF: {
|
||||
spl_lang_type_id_t agg_lang = sema_infer_type_context(sema, vec_at(*args, 0));
|
||||
if (!agg_lang)
|
||||
return 0;
|
||||
spl_type_id_t agg_ir = spl_lang_type_get_ir_type(sema->type, agg_lang);
|
||||
spl_ast_node_t *fnode = spl_ast_node(sema->ast, vec_at(*args, 1));
|
||||
if (!fnode)
|
||||
return 0;
|
||||
if (fnode->kind == SPL_AST_EXPR_IDENT) {
|
||||
usize idx = 0;
|
||||
if (!spl_type_agg_member_idx(&sema->type->ir, agg_ir, fnode->ident.name, &idx)) {
|
||||
spl_diag_error(sema->diag, SPL_DIAG_KIND_NO_FIELD, &n->dbg, "no field `%s`",
|
||||
fnode->ident.name);
|
||||
return 0;
|
||||
}
|
||||
} else if (fnode->kind != SPL_AST_EXPR_INT_LIT) {
|
||||
spl_diag_error(sema->diag, SPL_DIAG_KIND_TYPE_MISMATCH, &n->dbg,
|
||||
"@offsetof expects a field name or integer index");
|
||||
return 0;
|
||||
}
|
||||
return spl_lang_make_int(sema->type, sizeof(void *) * 8, 0);
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
@@ -2718,11 +2765,12 @@ static spl_lang_type_id_t sema_infer(spl_sema_t *sema, spl_ast_node_ref_t ref) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spl_sema_init(spl_sema_t *sema, spl_ast_t *ast, spl_lang_type_t *type, spl_scope_t *scope,
|
||||
spl_attr_t *attr) {
|
||||
void spl_sema_init(spl_sema_t *sema, spl_ast_t *ast, spl_lang_type_t *type, spl_layout_t *layout,
|
||||
spl_scope_t *scope, spl_attr_t *attr) {
|
||||
Assert(sema != NULL && ast != NULL && type != NULL && scope != NULL && attr != NULL);
|
||||
sema->ast = ast;
|
||||
sema->type = type;
|
||||
sema->layout = layout;
|
||||
sema->scope = scope;
|
||||
sema->current_fn_ret_tid = 0;
|
||||
sema->current_aggregate_lang = 0;
|
||||
|
||||
+4
-2
@@ -5,6 +5,7 @@
|
||||
#include "spl_attr.h"
|
||||
#include "spl_diag.h"
|
||||
#include "spl_lang_type.h"
|
||||
#include "spl_layout.h"
|
||||
#include "spl_mpa.h"
|
||||
#include "spl_scope.h"
|
||||
#include "spl_type.h"
|
||||
@@ -21,6 +22,7 @@ struct spl_module_loader {
|
||||
typedef struct {
|
||||
spl_ast_t *ast;
|
||||
spl_lang_type_t *type;
|
||||
spl_layout_t *layout; /* 由 splc0 拥有并解析;sema 仅借用 */
|
||||
spl_scope_t *scope;
|
||||
spl_symbol_t root;
|
||||
spl_attr_t *attr;
|
||||
@@ -32,8 +34,8 @@ typedef struct {
|
||||
spl_diag_t *diag;
|
||||
} spl_sema_t;
|
||||
|
||||
void spl_sema_init(spl_sema_t *sema, spl_ast_t *ast, spl_lang_type_t *type, spl_scope_t *scope,
|
||||
spl_attr_t *attr);
|
||||
void spl_sema_init(spl_sema_t *sema, spl_ast_t *ast, spl_lang_type_t *type,
|
||||
spl_layout_t *layout, spl_scope_t *scope, spl_attr_t *attr);
|
||||
void spl_sema_drop(spl_sema_t *sema);
|
||||
|
||||
void spl_sema_run_module(spl_sema_t *sema, spl_ast_node_ref_t module_ref);
|
||||
|
||||
+20
-4
@@ -182,6 +182,8 @@ static int compile_spl(const char *src, const char *fname, const char *outpath,
|
||||
|
||||
spl_attr_t attr;
|
||||
spl_lang_type_t lang_type;
|
||||
spl_layout_target_t layout_target;
|
||||
spl_layout_t layout;
|
||||
spl_scope_t scope;
|
||||
spl_sema_t sema;
|
||||
spl_ast2ir_t a2ir;
|
||||
@@ -189,7 +191,7 @@ static int compile_spl(const char *src, const char *fname, const char *outpath,
|
||||
spl_ir2vm_t ir2vm;
|
||||
spl_ast_ptr_vec_t all_asts;
|
||||
|
||||
int attr_ok = 0, lang_type_ok = 0, scope_ok = 0;
|
||||
int attr_ok = 0, lang_type_ok = 0, layout_ok = 0, scope_ok = 0;
|
||||
int sema_ok = 0, a2ir_ok = 0, irb_ok = 0, ir2vm_ok = 0;
|
||||
|
||||
spl_diag_set_stage(&diag, SPL_STAGE_PARSE);
|
||||
@@ -212,10 +214,19 @@ static int compile_spl(const char *src, const char *fname, const char *outpath,
|
||||
attr_ok = 1;
|
||||
spl_lang_type_init(&lang_type);
|
||||
lang_type_ok = 1;
|
||||
|
||||
/* 目标描述符:剥离目标机器细节,layout 是 size/align/offset 的唯一来源。 */
|
||||
memset(&layout_target, 0, sizeof layout_target);
|
||||
layout_target.endian = SPL_LITTLE_ENDIAN;
|
||||
layout_target.ptr_size = sizeof(void *);
|
||||
layout_target.ptr_align = sizeof(void *);
|
||||
spl_layout_init(&layout, &layout_target);
|
||||
layout_ok = 1;
|
||||
|
||||
spl_scope_init(&scope);
|
||||
scope_ok = 1;
|
||||
|
||||
spl_sema_init(&sema, &drv.main_ast, &lang_type, &scope, &attr);
|
||||
spl_sema_init(&sema, &drv.main_ast, &lang_type, &layout, &scope, &attr);
|
||||
sema_ok = 1;
|
||||
spl_module_loader_t loader = {.load = driver_load, .user = &drv};
|
||||
sema.loader = &loader;
|
||||
@@ -242,7 +253,7 @@ static int compile_spl(const char *src, const char *fname, const char *outpath,
|
||||
|
||||
spl_ir_builder_init(&ir_builder);
|
||||
irb_ok = 1;
|
||||
spl_ast2ir_init(&a2ir, &ir_builder, &sema);
|
||||
spl_ast2ir_init(&a2ir, &ir_builder, &layout, &sema);
|
||||
a2ir_ok = 1;
|
||||
spl_diag_set_stage(&diag, SPL_STAGE_AST2IR);
|
||||
a2ir.diag = &diag;
|
||||
@@ -257,7 +268,10 @@ static int compile_spl(const char *src, const char *fname, const char *outpath,
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
spl_ir2vm_init(&ir2vm, &a2ir.ir->ir, &lang_type.ir, &attr);
|
||||
/* 类型已全部就绪:解析 layout,供 ir2vm 的 size/align/offset 使用。 */
|
||||
spl_layout_resolve(&layout, &lang_type.ir);
|
||||
|
||||
spl_ir2vm_init(&ir2vm, &a2ir.ir->ir, &lang_type.ir, &layout, &attr);
|
||||
ir2vm_ok = 1;
|
||||
spl_diag_set_stage(&diag, SPL_STAGE_IR2VM);
|
||||
ir2vm.diag = &diag;
|
||||
@@ -285,6 +299,8 @@ cleanup:
|
||||
spl_sema_drop(&sema);
|
||||
if (scope_ok)
|
||||
spl_scope_drop(&scope);
|
||||
if (layout_ok)
|
||||
spl_layout_drop(&layout);
|
||||
if (lang_type_ok)
|
||||
spl_lang_type_drop(&lang_type);
|
||||
if (attr_ok)
|
||||
|
||||
@@ -0,0 +1,871 @@
|
||||
// Expect retcode = 0
|
||||
// 任意精度有符号整数。limbs 为小端 u32;零规范为 len=0 且 negative=false。
|
||||
// 普通赋值只借用同一存储;深复制使用 clone,所有权转移使用 take。
|
||||
type .{ .Error, .Status } = @import("error.spl");
|
||||
type .{ .checked_mul } = @import("memory.spl");
|
||||
type .{ .digit_char, .digit_value } = @import("ascii.spl");
|
||||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||
type .{ .alloc, .copy, .free } = @import("../sys/mem.spl");
|
||||
|
||||
type BigInt = @self;
|
||||
|
||||
limbs: *u32,
|
||||
len: usize,
|
||||
cap: usize,
|
||||
negative: bool,
|
||||
|
||||
fn zero(): BigInt {
|
||||
ret BigInt { .limbs = null, .len = 0, .cap = 0, .negative = false };
|
||||
}
|
||||
|
||||
fn valid(self: BigInt): bool {
|
||||
if self.len > self.cap { ret false; }
|
||||
if self.cap == 0 {
|
||||
ret self.limbs == null && self.len == 0 && !self.negative;
|
||||
}
|
||||
if self.limbs == null { ret false; }
|
||||
if self.len == 0 { ret !self.negative; }
|
||||
ret self.limbs[self.len - 1] != 0;
|
||||
}
|
||||
|
||||
fn is_zero(self: BigInt): bool {
|
||||
ret self.len == 0;
|
||||
}
|
||||
|
||||
fn is_negative(self: BigInt): bool {
|
||||
ret self.negative;
|
||||
}
|
||||
|
||||
fn normalize(self: *BigInt) {
|
||||
while self.len > 0 {
|
||||
if self.limbs[self.len - 1] != 0 { break; }
|
||||
self.len = self.len - 1;
|
||||
}
|
||||
if self.len == 0 { self.negative = false; }
|
||||
}
|
||||
|
||||
fn reserve(self: *BigInt, need: usize): Status {
|
||||
if !valid(self.*) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if need <= self.cap { ret .Ok; }
|
||||
|
||||
var new_cap = self.cap;
|
||||
if new_cap < 2 { new_cap = 2; }
|
||||
while new_cap < need {
|
||||
if new_cap > (~(0 as usize)) / 2 {
|
||||
new_cap = need;
|
||||
break;
|
||||
}
|
||||
new_cap = new_cap * 2;
|
||||
}
|
||||
|
||||
var byte_count: usize = 0;
|
||||
match checked_mul(new_cap, @sizeof(u32)) {
|
||||
.Ok(value) => { byte_count = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var fresh = alloc(byte_count) as *u32;
|
||||
if self.len != 0 {
|
||||
copy(fresh as *any, self.limbs as *any, self.len * @sizeof(u32));
|
||||
}
|
||||
free(self.limbs as *any);
|
||||
self.limbs = fresh;
|
||||
self.cap = new_cap;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn drop(self: *BigInt) {
|
||||
free(self.limbs as *any);
|
||||
self.* = zero();
|
||||
}
|
||||
|
||||
fn take(source: *BigInt): BigInt {
|
||||
var result = source.*;
|
||||
source.* = zero();
|
||||
ret result;
|
||||
}
|
||||
|
||||
fn replace(out: *BigInt, fresh: *BigInt) {
|
||||
drop(out);
|
||||
out.* = take(fresh);
|
||||
}
|
||||
|
||||
fn clone(out: *BigInt, source: BigInt): Status {
|
||||
if !valid(out.*) || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
var fresh = zero();
|
||||
match reserve(&fresh, source.len) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if source.len != 0 {
|
||||
copy(fresh.limbs as *any, source.limbs as *any, source.len * @sizeof(u32));
|
||||
}
|
||||
fresh.len = source.len;
|
||||
fresh.negative = source.negative;
|
||||
replace(out, &fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn from_u64(out: *BigInt, value: u64): Status {
|
||||
if !valid(out.*) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
var fresh = zero();
|
||||
if value != 0 {
|
||||
match reserve(&fresh, 2) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
fresh.limbs[0] = value as u32;
|
||||
fresh.limbs[1] = (value >> 32) as u32;
|
||||
fresh.len = 1;
|
||||
if fresh.limbs[1] != 0 { fresh.len = 2; }
|
||||
}
|
||||
replace(out, &fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn from_i64(out: *BigInt, value: i64): Status {
|
||||
var magnitude = value as u64;
|
||||
var negative = value < 0;
|
||||
if negative { magnitude = 0 - magnitude; }
|
||||
match from_u64(out, magnitude) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if out.len != 0 { out.negative = negative; }
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn cmp_abs(a: BigInt, b: BigInt): i32 {
|
||||
if a.len < b.len { ret -1; }
|
||||
if a.len > b.len { ret 1; }
|
||||
var i = a.len;
|
||||
while i > 0 {
|
||||
i = i - 1;
|
||||
if a.limbs[i] < b.limbs[i] { ret -1; }
|
||||
if a.limbs[i] > b.limbs[i] { ret 1; }
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn cmp(a: BigInt, b: BigInt): i32 {
|
||||
if a.negative != b.negative {
|
||||
if a.negative { ret -1; }
|
||||
ret 1;
|
||||
}
|
||||
var order = cmp_abs(a, b);
|
||||
if a.negative { ret 0 - order; }
|
||||
ret order;
|
||||
}
|
||||
|
||||
fn eq(a: BigInt, b: BigInt): bool {
|
||||
if a.negative != b.negative || a.len != b.len { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < a.len {
|
||||
if a.limbs[i] != b.limbs[i] { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn add_abs_into(out: *BigInt, a: BigInt, b: BigInt): Status {
|
||||
var count = a.len;
|
||||
if b.len > count { count = b.len; }
|
||||
if count == ~(0 as usize) { ret .Err(Error.make(.Overflow)); }
|
||||
match reserve(out, count + 1) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var carry: u64 = 0;
|
||||
var i: usize = 0;
|
||||
while i < count {
|
||||
var av: u64 = 0;
|
||||
var bv: u64 = 0;
|
||||
if i < a.len { av = a.limbs[i] as u64; }
|
||||
if i < b.len { bv = b.limbs[i] as u64; }
|
||||
var sum = av + bv + carry;
|
||||
out.limbs[i] = sum as u32;
|
||||
carry = sum >> 32;
|
||||
i = i + 1;
|
||||
}
|
||||
if carry != 0 {
|
||||
out.limbs[count] = carry as u32;
|
||||
count = count + 1;
|
||||
}
|
||||
out.len = count;
|
||||
out.negative = false;
|
||||
normalize(out);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 要求 |a| >= |b|。
|
||||
fn sub_abs_into(out: *BigInt, a: BigInt, b: BigInt): Status {
|
||||
match reserve(out, a.len) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var borrow: u64 = 0;
|
||||
var i: usize = 0;
|
||||
while i < a.len {
|
||||
var av = a.limbs[i] as u64;
|
||||
var bv: u64 = 0;
|
||||
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;
|
||||
borrow = 1;
|
||||
} else {
|
||||
out.limbs[i] = (av - subtrahend) as u32;
|
||||
borrow = 0;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
out.len = a.len;
|
||||
out.negative = false;
|
||||
normalize(out);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn add(out: *BigInt, a: BigInt, b: BigInt): Status {
|
||||
if !valid(out.*) || !valid(a) || !valid(b) {
|
||||
ret .Err(Error.make(.InvalidArgument));
|
||||
}
|
||||
var fresh = zero();
|
||||
if a.negative == b.negative {
|
||||
match add_abs_into(&fresh, a, b) {
|
||||
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
if fresh.len != 0 { fresh.negative = a.negative; }
|
||||
} else {
|
||||
var order = cmp_abs(a, b);
|
||||
if order >= 0 {
|
||||
match sub_abs_into(&fresh, a, b) {
|
||||
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
if fresh.len != 0 { fresh.negative = a.negative; }
|
||||
} else {
|
||||
match sub_abs_into(&fresh, b, a) {
|
||||
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
if fresh.len != 0 { fresh.negative = b.negative; }
|
||||
}
|
||||
}
|
||||
replace(out, &fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn sub(out: *BigInt, a: BigInt, b: BigInt): Status {
|
||||
var neg_b = b;
|
||||
if neg_b.len != 0 { neg_b.negative = !neg_b.negative; }
|
||||
ret add(out, a, neg_b);
|
||||
}
|
||||
|
||||
fn neg(out: *BigInt, source: BigInt): Status {
|
||||
match clone(out, source) {
|
||||
.Ok => {}, .Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if out.len != 0 { out.negative = !out.negative; }
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn abs(out: *BigInt, source: BigInt): Status {
|
||||
match clone(out, source) {
|
||||
.Ok => {}, .Err(error) => { ret .Err(error); },
|
||||
}
|
||||
out.negative = false;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn mul(out: *BigInt, a: BigInt, b: BigInt): Status {
|
||||
if !valid(out.*) || !valid(a) || !valid(b) {
|
||||
ret .Err(Error.make(.InvalidArgument));
|
||||
}
|
||||
var fresh = zero();
|
||||
if a.len == 0 || b.len == 0 {
|
||||
replace(out, &fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
if b.len > (~(0 as usize)) - a.len {
|
||||
ret .Err(Error.make(.Overflow));
|
||||
}
|
||||
var count = a.len + b.len;
|
||||
match reserve(&fresh, count) {
|
||||
.Ok => {}, .Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var z: usize = 0;
|
||||
while z < count { fresh.limbs[z] = 0; z = z + 1; }
|
||||
|
||||
var i: usize = 0;
|
||||
while i < a.len {
|
||||
var carry: u64 = 0;
|
||||
var j: usize = 0;
|
||||
while j < b.len {
|
||||
var at = i + j;
|
||||
var product = (a.limbs[i] as u64) * (b.limbs[j] as u64) +
|
||||
(fresh.limbs[at] as u64) + carry;
|
||||
fresh.limbs[at] = product as u32;
|
||||
carry = product >> 32;
|
||||
j = j + 1;
|
||||
}
|
||||
fresh.limbs[i + b.len] = carry as u32;
|
||||
i = i + 1;
|
||||
}
|
||||
fresh.len = count;
|
||||
fresh.negative = a.negative != b.negative;
|
||||
normalize(&fresh);
|
||||
replace(out, &fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn bit_length(out: *usize, self: BigInt): Status {
|
||||
if out == null || !valid(self) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if self.len == 0 { out.* = 0; ret .Ok; }
|
||||
if self.len - 1 > (~(0 as usize)) / 32 { ret .Err(Error.make(.Overflow)); }
|
||||
var top = self.limbs[self.len - 1];
|
||||
var bits = (self.len - 1) * 32;
|
||||
while top != 0 {
|
||||
bits = bits + 1;
|
||||
top = top >> 1;
|
||||
}
|
||||
out.* = bits;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn mul_pow2(out: *BigInt, source: BigInt, amount: usize): Status {
|
||||
if !valid(out.*) || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if source.len == 0 { ret from_u64(out, 0); }
|
||||
var words = amount / 32;
|
||||
var shift = amount % 32;
|
||||
if source.len == ~(0 as usize) || words > (~(0 as usize)) - source.len - 1 {
|
||||
ret .Err(Error.make(.Overflow));
|
||||
}
|
||||
var fresh = zero();
|
||||
var count = source.len + words + 1;
|
||||
match reserve(&fresh, count) {
|
||||
.Ok => {}, .Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var i: usize = 0;
|
||||
while i < words { fresh.limbs[i] = 0; i = i + 1; }
|
||||
var carry: u64 = 0;
|
||||
i = 0;
|
||||
while i < source.len {
|
||||
var value = ((source.limbs[i] as u64) << shift as u32) | carry;
|
||||
fresh.limbs[i + words] = value as u32;
|
||||
carry = value >> 32;
|
||||
i = i + 1;
|
||||
}
|
||||
fresh.len = source.len + words;
|
||||
if carry != 0 {
|
||||
fresh.limbs[fresh.len] = carry as u32;
|
||||
fresh.len = fresh.len + 1;
|
||||
}
|
||||
fresh.negative = source.negative;
|
||||
replace(out, &fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 按绝对值右移,等价于向零除以 2^amount。
|
||||
fn div_pow2(out: *BigInt, source: BigInt, amount: usize): Status {
|
||||
if !valid(out.*) || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
var words = amount / 32;
|
||||
var shift = amount % 32;
|
||||
if words >= source.len { ret from_u64(out, 0); }
|
||||
var count = source.len - words;
|
||||
var fresh = zero();
|
||||
match reserve(&fresh, count) {
|
||||
.Ok => {}, .Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var i: usize = 0;
|
||||
while i < count {
|
||||
var src = i + words;
|
||||
var value = (source.limbs[src] as u64) >> shift as u32;
|
||||
if shift != 0 && src + 1 < source.len {
|
||||
value = value | ((source.limbs[src + 1] << (32 - shift) as u32) as u64);
|
||||
}
|
||||
fresh.limbs[i] = value as u32;
|
||||
i = i + 1;
|
||||
}
|
||||
fresh.len = count;
|
||||
fresh.negative = source.negative;
|
||||
normalize(&fresh);
|
||||
replace(out, &fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn shift_one(self: *BigInt) {
|
||||
var carry: u64 = 0;
|
||||
var i: usize = 0;
|
||||
while i < self.len {
|
||||
var value = ((self.limbs[i] as u64) << 1) | carry;
|
||||
self.limbs[i] = value as u32;
|
||||
carry = value >> 32;
|
||||
i = i + 1;
|
||||
}
|
||||
if carry != 0 {
|
||||
self.limbs[self.len] = carry as u32;
|
||||
self.len = self.len + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn sub_abs_in_place(a: *BigInt, b: BigInt) {
|
||||
var borrow: u64 = 0;
|
||||
var i: usize = 0;
|
||||
while i < a.len {
|
||||
var av = a.limbs[i] as u64;
|
||||
var bv: u64 = 0;
|
||||
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;
|
||||
borrow = 1;
|
||||
} else {
|
||||
a.limbs[i] = (av - subtrahend) as u32;
|
||||
borrow = 0;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
normalize(a);
|
||||
}
|
||||
|
||||
fn div_rem(quotient: *BigInt, remainder: *BigInt, dividend: BigInt,
|
||||
divisor: BigInt): Status {
|
||||
if (quotient as usize) == (remainder as usize) {
|
||||
ret .Err(Error.make(.InvalidArgument));
|
||||
}
|
||||
if !valid(quotient.*) || !valid(remainder.*) || !valid(dividend) || !valid(divisor) {
|
||||
ret .Err(Error.make(.InvalidArgument));
|
||||
}
|
||||
if divisor.len == 0 { ret .Err(Error.make(.DivisionByZero)); }
|
||||
|
||||
var q = zero();
|
||||
var r = zero();
|
||||
match reserve(&q, dividend.len) {
|
||||
.Ok => {}, .Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if divisor.len == ~(0 as usize) { drop(&q); ret .Err(Error.make(.Overflow)); }
|
||||
match reserve(&r, divisor.len + 1) {
|
||||
.Ok => {}, .Err(error) => { drop(&q); ret .Err(error); },
|
||||
}
|
||||
var i: usize = 0;
|
||||
while i < dividend.len { q.limbs[i] = 0; i = i + 1; }
|
||||
q.len = dividend.len;
|
||||
|
||||
var bit: usize = 0;
|
||||
match bit_length(&bit, dividend) {
|
||||
.Ok => {},
|
||||
.Err(error) => { drop(&q); drop(&r); ret .Err(error); },
|
||||
}
|
||||
while bit > 0 {
|
||||
bit = bit - 1;
|
||||
shift_one(&r);
|
||||
var word = bit / 32;
|
||||
var offset = bit % 32;
|
||||
var incoming = (dividend.limbs[word] >> offset as u32) & 1;
|
||||
if incoming != 0 {
|
||||
if r.len == 0 { r.len = 1; r.limbs[0] = 0; }
|
||||
r.limbs[0] = r.limbs[0] | 1;
|
||||
}
|
||||
if cmp_abs(r, divisor) >= 0 {
|
||||
sub_abs_in_place(&r, divisor);
|
||||
q.limbs[word] = q.limbs[word] | ((1 as u32) << offset as u32);
|
||||
}
|
||||
}
|
||||
q.negative = q.len != 0 && dividend.negative != divisor.negative;
|
||||
r.negative = r.len != 0 && dividend.negative;
|
||||
normalize(&q);
|
||||
normalize(&r);
|
||||
replace(quotient, &q);
|
||||
replace(remainder, &r);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn mul_small(self: *BigInt, value: u32): Status {
|
||||
if self.len == 0 || value == 1 { ret .Ok; }
|
||||
if value == 0 { self.len = 0; self.negative = false; ret .Ok; }
|
||||
if self.len == ~(0 as usize) { ret .Err(Error.make(.Overflow)); }
|
||||
match reserve(self, self.len + 1) {
|
||||
.Ok => {}, .Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var carry: u64 = 0;
|
||||
var i: usize = 0;
|
||||
while i < self.len {
|
||||
var product = (self.limbs[i] as u64) * (value as u64) + carry;
|
||||
self.limbs[i] = product as u32;
|
||||
carry = product >> 32;
|
||||
i = i + 1;
|
||||
}
|
||||
if carry != 0 {
|
||||
self.limbs[self.len] = carry as u32;
|
||||
self.len = self.len + 1;
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn add_small(self: *BigInt, value: u32): Status {
|
||||
if value == 0 { ret .Ok; }
|
||||
if self.len == ~(0 as usize) { ret .Err(Error.make(.Overflow)); }
|
||||
match reserve(self, self.len + 1) {
|
||||
.Ok => {}, .Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var carry = value as u64;
|
||||
var i: usize = 0;
|
||||
while carry != 0 && i < self.len {
|
||||
var sum = (self.limbs[i] as u64) + carry;
|
||||
self.limbs[i] = sum as u32;
|
||||
carry = sum >> 32;
|
||||
i = i + 1;
|
||||
}
|
||||
if carry != 0 {
|
||||
self.limbs[self.len] = carry as u32;
|
||||
self.len = self.len + 1;
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn parse(out: *BigInt, bytes: []u8, radix: u8): Status {
|
||||
if !valid(out.*) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if radix < 2 || radix > 36 { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if bytes.len == 0 { ret .Err(Error.at(.UnexpectedEnd, 0)); }
|
||||
if bytes.ptr == null { ret .Err(Error.make(.InvalidArgument)); }
|
||||
|
||||
var i: usize = 0;
|
||||
var negative = false;
|
||||
if bytes.ptr[0] == 45 || bytes.ptr[0] == 43 {
|
||||
negative = bytes.ptr[0] == 45;
|
||||
i = 1;
|
||||
}
|
||||
if i == bytes.len { ret .Err(Error.at(.UnexpectedEnd, i)); }
|
||||
|
||||
var fresh = zero();
|
||||
while i < bytes.len {
|
||||
var digit: u8 = 0;
|
||||
match digit_value(bytes.ptr[i], radix) {
|
||||
.Ok(value) => { digit = value; },
|
||||
.Err(error) => { drop(&fresh); ret .Err(Error.at(.InvalidDigit, i)); },
|
||||
}
|
||||
match mul_small(&fresh, radix as u32) {
|
||||
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
match add_small(&fresh, digit as u32) {
|
||||
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
fresh.negative = fresh.len != 0 && negative;
|
||||
replace(out, &fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn div_small(self: *BigInt, radix: u32): u32 {
|
||||
var remainder: u64 = 0;
|
||||
var i = self.len;
|
||||
while i > 0 {
|
||||
i = i - 1;
|
||||
var current = (remainder << 32) | (self.limbs[i] as u64);
|
||||
self.limbs[i] = (current / radix as u64) as u32;
|
||||
remainder = current % radix as u64;
|
||||
}
|
||||
normalize(self);
|
||||
ret remainder as u32;
|
||||
}
|
||||
|
||||
fn format(out: *ByteBuffer, source: BigInt, radix: u8, uppercase: bool): Status {
|
||||
if !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if radix < 2 || radix > 36 { ret .Err(Error.make(.InvalidArgument)); }
|
||||
var reverse = ByteBuffer.new();
|
||||
var fresh = ByteBuffer.new();
|
||||
var magnitude = zero();
|
||||
match clone(&magnitude, source) {
|
||||
.Ok => {}, .Err(error) => { ret .Err(error); },
|
||||
}
|
||||
magnitude.negative = false;
|
||||
if magnitude.len == 0 {
|
||||
match ByteBuffer.append_byte(&reverse, 48) {
|
||||
.Ok => {}, .Err(error) => { drop(&magnitude); ret .Err(error); },
|
||||
}
|
||||
}
|
||||
while magnitude.len != 0 {
|
||||
var remainder = div_small(&magnitude, radix as u32);
|
||||
var byte: u8 = 0;
|
||||
match digit_char(remainder as u8, uppercase) {
|
||||
.Ok(value) => { byte = value; },
|
||||
.Err(error) => { drop(&magnitude); ByteBuffer.drop(&reverse); ret .Err(error); },
|
||||
}
|
||||
match ByteBuffer.append_byte(&reverse, byte) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
drop(&magnitude); ByteBuffer.drop(&reverse); ret .Err(error);
|
||||
},
|
||||
}
|
||||
}
|
||||
if source.negative {
|
||||
match ByteBuffer.append_byte(&fresh, 45) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
drop(&magnitude); ByteBuffer.drop(&reverse); ret .Err(error);
|
||||
},
|
||||
}
|
||||
}
|
||||
var i = ByteBuffer.size(reverse);
|
||||
var digits = ByteBuffer.as_slice(reverse);
|
||||
while i > 0 {
|
||||
i = i - 1;
|
||||
match ByteBuffer.append_byte(&fresh, digits.ptr[i]) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
drop(&magnitude); ByteBuffer.drop(&reverse); ByteBuffer.drop(&fresh);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
}
|
||||
drop(&magnitude);
|
||||
ByteBuffer.drop(&reverse);
|
||||
ByteBuffer.drop(out);
|
||||
out.* = ByteBuffer.take(&fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn to_u64(out: *u64, source: BigInt): Status {
|
||||
if out == null || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if source.negative || source.len > 2 { ret .Err(Error.make(.Overflow)); }
|
||||
var value: u64 = 0;
|
||||
if source.len > 0 { value = source.limbs[0] as u64; }
|
||||
if source.len > 1 { value = value | ((source.limbs[1] as u64) << 32); }
|
||||
out.* = value;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn to_i64(out: *i64, source: BigInt): Status {
|
||||
if out == null || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if source.len > 2 { ret .Err(Error.make(.Overflow)); }
|
||||
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); }
|
||||
if !source.negative {
|
||||
if magnitude > 9223372036854775807 { ret .Err(Error.make(.Overflow)); }
|
||||
out.* = magnitude as i64;
|
||||
ret .Ok;
|
||||
}
|
||||
if magnitude > 9223372036854775808 { ret .Err(Error.make(.Overflow)); }
|
||||
if magnitude == 9223372036854775808 {
|
||||
out.* = magnitude as i64;
|
||||
} else {
|
||||
out.* = 0 - magnitude as i64;
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn to_usize(out: *usize, source: BigInt): Status {
|
||||
if out == null { ret .Err(Error.make(.InvalidArgument)); }
|
||||
var value: u64 = 0;
|
||||
match to_u64(&value, source) {
|
||||
.Ok => {}, .Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if value > (~(0 as usize)) as u64 { ret .Err(Error.make(.Overflow)); }
|
||||
out.* = value as usize;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result { .Ok => {}, .Err(error) => { @assert(false, "unexpected BigInt error"); }, }
|
||||
}
|
||||
|
||||
fn expect_text_radix(value: BigInt, radix: u8, uppercase: bool, expected: []u8) {
|
||||
var buffer = ByteBuffer.new();
|
||||
expect_ok(format(&buffer, value, radix, uppercase));
|
||||
var actual = ByteBuffer.as_slice(buffer);
|
||||
@assert(actual.len == expected.len, "formatted length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(actual.ptr[i] == expected.ptr[i], "formatted byte");
|
||||
i = i + 1;
|
||||
}
|
||||
ByteBuffer.drop(&buffer);
|
||||
}
|
||||
|
||||
fn expect_text(value: BigInt, expected: []u8) {
|
||||
expect_text_radix(value, 10, false, expected);
|
||||
}
|
||||
|
||||
fn parsed(text: []u8): BigInt {
|
||||
var value = zero();
|
||||
expect_ok(parse(&value, text, 10));
|
||||
ret value;
|
||||
}
|
||||
|
||||
fn test_arithmetic() {
|
||||
var a = parsed("4294967295");
|
||||
var one = zero();
|
||||
expect_ok(from_u64(&one, 1));
|
||||
expect_ok(add(&a, a, one));
|
||||
expect_text(a, "4294967296");
|
||||
|
||||
var all_ones = zero();
|
||||
expect_ok(parse(&all_ones, "ffffffffffffffffffffffffffffffff", 16));
|
||||
expect_ok(add(&all_ones, all_ones, one));
|
||||
expect_text(all_ones, "340282366920938463463374607431768211456");
|
||||
expect_ok(sub(&all_ones, all_ones, one));
|
||||
expect_text_radix(all_ones, 16, false, "ffffffffffffffffffffffffffffffff");
|
||||
|
||||
var two_64 = parsed("18446744073709551616");
|
||||
var product = zero();
|
||||
expect_ok(mul(&product, two_64, two_64));
|
||||
expect_text(product, "340282366920938463463374607431768211456");
|
||||
|
||||
var difference = zero();
|
||||
expect_ok(sub(&difference, product, two_64));
|
||||
expect_text(difference, "340282366920938463444927863358058659840");
|
||||
|
||||
var shifted = zero();
|
||||
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");
|
||||
var divisor = parsed("18446744073709551616");
|
||||
var quotient = zero();
|
||||
var remainder = zero();
|
||||
expect_ok(div_rem("ient, &remainder, dividend, divisor));
|
||||
@assert(eq(quotient, divisor));
|
||||
@assert(is_zero(remainder));
|
||||
|
||||
var long_dividend = parsed("123456789012345678901234567890");
|
||||
var long_divisor = parsed("9876543210");
|
||||
expect_ok(div_rem("ient, &remainder, long_dividend, long_divisor));
|
||||
expect_text(quotient, "12499999887343749990");
|
||||
expect_text(remainder, "1562499990");
|
||||
|
||||
var negative = zero();
|
||||
var seven = zero();
|
||||
expect_ok(from_i64(&negative, -100));
|
||||
expect_ok(from_u64(&seven, 7));
|
||||
expect_ok(div_rem(&negative, &remainder, negative, seven));
|
||||
expect_text(negative, "-14");
|
||||
expect_text(remainder, "-2");
|
||||
|
||||
var reconstructed = zero();
|
||||
expect_ok(mul(&reconstructed, negative, seven));
|
||||
expect_ok(add(&reconstructed, reconstructed, remainder));
|
||||
expect_text(reconstructed, "-100");
|
||||
|
||||
var saved = parsed("123");
|
||||
var zero_divisor = zero();
|
||||
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();
|
||||
expect_ok(parse(&value, "-deadBEEF", 16));
|
||||
var output = ByteBuffer.new();
|
||||
expect_ok(format(&output, value, 16, false));
|
||||
var text = ByteBuffer.as_slice(output);
|
||||
@assert(text.len == 9 && text.ptr[0] == 45 && text.ptr[1] == 100 && text.ptr[8] == 102);
|
||||
expect_text_radix(value, 16, true, "-DEADBEEF");
|
||||
|
||||
var base36 = zero();
|
||||
expect_ok(parse(&base36, "zzzzzzzzzz", 36));
|
||||
expect_text_radix(base36, 36, false, "zzzzzzzzzz");
|
||||
|
||||
var preserved = parsed("77");
|
||||
match parse(&preserved, "12x", 10) {
|
||||
.Ok => { @assert(false, "invalid digit"); },
|
||||
.Err(error) => {
|
||||
@assert(error.has_position && error.position == 2);
|
||||
match error.kind { .InvalidDigit => {}, _ => { @assert(false); }, }
|
||||
},
|
||||
}
|
||||
expect_text(preserved, "77");
|
||||
|
||||
var max = parsed("18446744073709551615");
|
||||
var converted: u64 = 0;
|
||||
expect_ok(to_u64(&converted, max));
|
||||
@assert(converted == 18446744073709551615);
|
||||
var over = parsed("18446744073709551616");
|
||||
converted = 17;
|
||||
match to_u64(&converted, over) {
|
||||
.Ok => { @assert(false); },
|
||||
.Err(error) => { match error.kind { .Overflow => {}, _ => { @assert(false); }, } },
|
||||
}
|
||||
@assert(converted == 17, "failed conversion preserves output");
|
||||
|
||||
var minimum = parsed("-9223372036854775808");
|
||||
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");
|
||||
signed_value = 23;
|
||||
match to_i64(&signed_value, signed_over) {
|
||||
.Ok => { @assert(false); },
|
||||
.Err(error) => { match error.kind { .Overflow => {}, _ => { @assert(false); }, } },
|
||||
}
|
||||
@assert(signed_value == 23, "failed signed conversion preserves output");
|
||||
|
||||
var copied = zero();
|
||||
expect_ok(clone(&copied, max));
|
||||
var one_more = parsed("1");
|
||||
expect_ok(add(&max, max, one_more));
|
||||
expect_text(copied, "18446744073709551615");
|
||||
var moved = take(&copied);
|
||||
@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) {
|
||||
var actual: i64 = 0;
|
||||
expect_ok(to_i64(&actual, value));
|
||||
@assert(actual == expected, "small integer reference result");
|
||||
}
|
||||
|
||||
fn test_small_reference() {
|
||||
var left = zero();
|
||||
var right = zero();
|
||||
var result = zero();
|
||||
var remainder = zero();
|
||||
var a: i64 = 0;
|
||||
while a <= 0 {
|
||||
var b: i64 = 2;
|
||||
while b <= 2 {
|
||||
expect_ok(from_i64(&left, a));
|
||||
expect_ok(from_i64(&right, b));
|
||||
expect_ok(add(&result, left, right));
|
||||
expect_i64_value(result, a + b);
|
||||
expect_ok(sub(&result, left, right));
|
||||
expect_i64_value(result, a - b);
|
||||
expect_ok(mul(&result, left, right));
|
||||
expect_i64_value(result, a * b);
|
||||
if b != 0 {
|
||||
expect_ok(div_rem(&result, &remainder, left, right));
|
||||
expect_i64_value(result, a / b);
|
||||
expect_i64_value(remainder, a % b);
|
||||
}
|
||||
b = b + 1;
|
||||
}
|
||||
a = a + 1;
|
||||
}
|
||||
drop(&remainder); drop(&result); drop(&right); drop(&left);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_arithmetic();
|
||||
test_division_and_sign();
|
||||
test_parse_format_and_conversion();
|
||||
test_small_reference();
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
// Expect retcode = 0
|
||||
// 数值/布尔格式化,写入 ByteBuffer。radix 取值 2..36;uppercase 只影响 10..35 的字母形式。
|
||||
// 不分配额外内存(除目标 ByteBuffer 自身扩容),失败不产生部分输出。
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||
type .{ .digit_char } = @import("ascii.spl");
|
||||
|
||||
// 注意:radix 边界直接用字面量。stage1 刻意不做常量求值/折叠,
|
||||
// 顶层 const 不应用于运行时表达式。
|
||||
fn valid_radix(radix: u8): bool {
|
||||
ret radix >= 2 && radix <= 36;
|
||||
}
|
||||
|
||||
fn append_u64(out: *ByteBuffer, value: u64, radix: u8, uppercase: bool): Status {
|
||||
if !valid_radix(radix) { ret .Err(make(.InvalidArgument)); }
|
||||
|
||||
var digits: [64]u8 = undefined;
|
||||
var count: usize = 0;
|
||||
|
||||
if value == 0 {
|
||||
digits[0] = 48;
|
||||
count = 1;
|
||||
} else {
|
||||
var v = value;
|
||||
var base = radix as u64;
|
||||
while v != 0 {
|
||||
var d = (v % base) as u8;
|
||||
match digit_char(d, uppercase) {
|
||||
.Ok(c) => { digits[count] = c; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
count = count + 1;
|
||||
v = v / base;
|
||||
}
|
||||
}
|
||||
|
||||
// digits 是低位在前,反向写入得到正常顺序。
|
||||
var i = count;
|
||||
while i > 0 {
|
||||
i = i - 1;
|
||||
match ByteBuffer.append_byte(out, digits[i]) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn append_i64(out: *ByteBuffer, value: i64, radix: u8, uppercase: bool): Status {
|
||||
if !valid_radix(radix) { ret .Err(make(.InvalidArgument)); }
|
||||
if value < 0 {
|
||||
match ByteBuffer.append_byte(out, 45) { // '-'
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
// value 为补码,(0 - (value as u64)) 得到绝对值(含 i64 最小值)。
|
||||
var magnitude = 0 - (value as u64);
|
||||
ret append_u64(out, magnitude, radix, uppercase);
|
||||
}
|
||||
ret append_u64(out, value as u64, radix, uppercase);
|
||||
}
|
||||
|
||||
fn append_usize(out: *ByteBuffer, value: usize, radix: u8, uppercase: bool): Status {
|
||||
ret append_u64(out, value as u64, radix, uppercase);
|
||||
}
|
||||
|
||||
fn append_isize(out: *ByteBuffer, value: isize, radix: u8, uppercase: bool): Status {
|
||||
ret append_i64(out, value as i64, radix, uppercase);
|
||||
}
|
||||
|
||||
fn append_bool(out: *ByteBuffer, value: bool): Status {
|
||||
if value { ret ByteBuffer.append(out, "true"); }
|
||||
ret ByteBuffer.append(out, "false");
|
||||
}
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "unexpected fmt error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_invalid(result: Status) {
|
||||
match result {
|
||||
.Ok => { @assert(false, "expected fmt error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.InvalidArgument => {},
|
||||
_ => { @assert(false, "unexpected fmt error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_text(buffer: *ByteBuffer, expected: []u8) {
|
||||
var actual = ByteBuffer.as_slice(buffer.*);
|
||||
@assert(actual.len == expected.len, "formatted length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(actual.ptr[i] == expected.ptr[i], "formatted byte");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_unsigned() {
|
||||
var b = ByteBuffer.new();
|
||||
|
||||
expect_ok(append_u64(&b, 0, 10, false));
|
||||
expect_text(&b, "0");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 42, 10, false));
|
||||
expect_text(&b, "42");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 1234567890, 10, false));
|
||||
expect_text(&b, "1234567890");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 18446744073709551615, 10, false));
|
||||
expect_text(&b, "18446744073709551615");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 255, 16, false));
|
||||
expect_text(&b, "ff");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 255, 16, true));
|
||||
expect_text(&b, "FF");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 4096, 16, false));
|
||||
expect_text(&b, "1000");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 170, 2, false));
|
||||
expect_text(&b, "10101010");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 35, 36, false));
|
||||
expect_text(&b, "z");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
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();
|
||||
|
||||
expect_ok(append_i64(&b, 0, 10, false));
|
||||
expect_text(&b, "0");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_i64(&b, -123, 10, false));
|
||||
expect_text(&b, "-123");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_i64(&b, 9223372036854775807, 10, false));
|
||||
expect_text(&b, "9223372036854775807");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_i64(&b, -9223372036854775808, 10, false));
|
||||
expect_text(&b, "-9223372036854775808");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_isize(&b, -16, 16, false));
|
||||
expect_text(&b, "-10");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
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();
|
||||
|
||||
expect_ok(append_usize(&b, 1000 as usize, 10, false));
|
||||
expect_text(&b, "1000");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_bool(&b, true));
|
||||
expect_text(&b, "true");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_bool(&b, false));
|
||||
expect_text(&b, "false");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_invalid(append_u64(&b, 1, 0, false));
|
||||
expect_invalid(append_u64(&b, 1, 1, false));
|
||||
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();
|
||||
|
||||
expect_ok(ByteBuffer.append(&b, "x="));
|
||||
expect_ok(append_i64(&b, -42, 10, false));
|
||||
expect_ok(ByteBuffer.append(&b, " hex="));
|
||||
expect_ok(append_u64(&b, 255, 16, false));
|
||||
expect_ok(ByteBuffer.append(&b, " ok="));
|
||||
expect_ok(append_bool(&b, true));
|
||||
expect_text(&b, "x=-42 hex=ff ok=true");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
// 反复写入触发 ByteBuffer 扩容,校验累积内容。
|
||||
var i: i32 = 0;
|
||||
while i < 200 {
|
||||
expect_ok(append_i64(&b, i as i64, 10, false));
|
||||
expect_ok(ByteBuffer.append_byte(&b, 44)); // ','
|
||||
i = i + 1;
|
||||
}
|
||||
expect_ok(append_i64(&b, 200, 10, false));
|
||||
@assert(ByteBuffer.size(b) > 0, "growth content");
|
||||
var s = ByteBuffer.as_slice(b);
|
||||
@assert(s.ptr[0] == 48, "starts with 0");
|
||||
@assert(s.ptr[1] == 44, "0 then comma");
|
||||
|
||||
ByteBuffer.drop(&b);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_unsigned();
|
||||
test_signed();
|
||||
test_usize_bool_and_errors();
|
||||
test_composition_and_growth();
|
||||
}
|
||||
@@ -0,0 +1,780 @@
|
||||
// Expect retcode = 0
|
||||
// 轻量 JSON 解析 / 输出器(递归下降 + 节点池),用于验证底层库的拼装,未来服务 LSP。
|
||||
// 设计:
|
||||
// - 输入 ByteView,字符串/数字以“源切片”借用,不做解码,round-trip 保真;
|
||||
// - 节点池 Vec[JsonNode];数组/对象子项存 Vec[JsonChild],保留插入顺序;
|
||||
// - 错误带字节位置(error.spl);输出写 ByteBuffer;缩进/数字用 ByteBuffer/fmt。
|
||||
// 限制:不做字符串转义解码、无递归深度保护、数字不做数值化(原样保存)。
|
||||
|
||||
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 .{ .append_i64, .append_u64 } = @import("fmt.spl");
|
||||
|
||||
type JsonKind = enum {
|
||||
Null,
|
||||
True,
|
||||
False,
|
||||
Number,
|
||||
String,
|
||||
Array,
|
||||
Object,
|
||||
}
|
||||
|
||||
// 数组元素或对象成员。数组时 key 为空视图;对象时 key 为源中的原始键文本。
|
||||
type JsonChild = struct {
|
||||
key: ByteView,
|
||||
val: usize,
|
||||
}
|
||||
|
||||
type JsonNode = struct {
|
||||
kind: JsonKind,
|
||||
text: ByteView,
|
||||
children: Vec,
|
||||
}
|
||||
|
||||
// 解析结果文档:nodes 拥有全部节点;root 为根节点下标。
|
||||
type Json = struct {
|
||||
nodes: Vec,
|
||||
root: usize,
|
||||
}
|
||||
|
||||
type JsonResult = enum {
|
||||
Ok: Json,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
type NodeResult = enum {
|
||||
Ok: usize,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
type Parser = struct {
|
||||
src: ByteView,
|
||||
pos: usize,
|
||||
nodes: Vec,
|
||||
}
|
||||
|
||||
fn blank_node(kind: JsonKind): JsonNode {
|
||||
ret JsonNode {
|
||||
.kind = kind,
|
||||
.text = ByteView.empty(),
|
||||
.children = Vec.blank(@sizeof(JsonChild), 8),
|
||||
};
|
||||
}
|
||||
|
||||
fn intern_node(p: *Parser, node: JsonNode): NodeResult {
|
||||
var index = p.nodes.len;
|
||||
var n = node;
|
||||
match Vec.push(&p.nodes, &n as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
ret .Ok(index);
|
||||
}
|
||||
|
||||
fn node_at(doc: *Json, index: usize): *JsonNode {
|
||||
match Vec.get(&doc.nodes, index) {
|
||||
.Ok(ptr) => { ret ptr as *JsonNode; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn skip_ws(p: *Parser) {
|
||||
while p.pos < p.src.len && is_space(p.src.ptr[p.pos]) {
|
||||
p.pos = p.pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// -1 表示 EOF。
|
||||
fn peek(p: *Parser): i32 {
|
||||
if p.pos >= p.src.len { ret -1; }
|
||||
ret p.src.ptr[p.pos] as i32;
|
||||
}
|
||||
|
||||
fn parse_string_raw(p: *Parser): ViewResult {
|
||||
if peek(p) != 34 { ret .Err(at(.InvalidFormat, p.pos)); }
|
||||
p.pos = p.pos + 1;
|
||||
var start = p.pos;
|
||||
while p.pos < p.src.len {
|
||||
var c = p.src.ptr[p.pos];
|
||||
if c == 34 { break; }
|
||||
if c == 92 { // '\\'
|
||||
p.pos = p.pos + 1;
|
||||
if p.pos >= p.src.len { ret .Err(at(.UnexpectedEnd, p.pos)); }
|
||||
if p.src.ptr[p.pos] == 117 { // 'u'
|
||||
var k: usize = 0;
|
||||
while k < 4 {
|
||||
p.pos = p.pos + 1;
|
||||
if p.pos >= p.src.len { ret .Err(at(.UnexpectedEnd, p.pos)); }
|
||||
if !is_hex_digit(p.src.ptr[p.pos]) { ret .Err(at(.InvalidDigit, p.pos)); }
|
||||
k = k + 1;
|
||||
}
|
||||
}
|
||||
} else if c < 32 {
|
||||
ret .Err(at(.InvalidFormat, p.pos));
|
||||
}
|
||||
p.pos = p.pos + 1;
|
||||
}
|
||||
if p.pos >= p.src.len { ret .Err(at(.UnexpectedEnd, p.pos)); }
|
||||
var end = p.pos;
|
||||
p.pos = p.pos + 1; // 收尾引号
|
||||
ret ByteView.slice(p.src, start, end);
|
||||
}
|
||||
|
||||
fn parse_literal(p: *Parser, word: []u8, kind: JsonKind): NodeResult {
|
||||
var i: usize = 0;
|
||||
while i < word.len {
|
||||
if p.pos + i >= p.src.len { ret .Err(at(.UnexpectedEnd, p.src.len)); }
|
||||
if p.src.ptr[p.pos + i] != word.ptr[i] {
|
||||
ret .Err(at(.InvalidFormat, p.pos + i));
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
p.pos = p.pos + word.len;
|
||||
var node = blank_node(kind);
|
||||
ret intern_node(p, node);
|
||||
}
|
||||
|
||||
fn parse_number(p: *Parser): NodeResult {
|
||||
var start = p.pos;
|
||||
if peek(p) == 45 { p.pos = p.pos + 1; } // '-'
|
||||
if p.pos >= p.src.len || !is_digit(p.src.ptr[p.pos]) {
|
||||
ret .Err(at(.InvalidDigit, p.pos));
|
||||
}
|
||||
if p.src.ptr[p.pos] == 48 { // 前导 0 后面不能接数字
|
||||
p.pos = p.pos + 1;
|
||||
} else {
|
||||
while p.pos < p.src.len && is_digit(p.src.ptr[p.pos]) {
|
||||
p.pos = p.pos + 1;
|
||||
}
|
||||
}
|
||||
if p.pos < p.src.len && p.src.ptr[p.pos] == 46 { // '.'
|
||||
p.pos = p.pos + 1;
|
||||
if p.pos >= p.src.len || !is_digit(p.src.ptr[p.pos]) {
|
||||
ret .Err(at(.InvalidDigit, p.pos));
|
||||
}
|
||||
while p.pos < p.src.len && is_digit(p.src.ptr[p.pos]) {
|
||||
p.pos = p.pos + 1;
|
||||
}
|
||||
}
|
||||
if p.pos < p.src.len && (p.src.ptr[p.pos] == 101 || p.src.ptr[p.pos] == 69) { // e/E
|
||||
p.pos = p.pos + 1;
|
||||
if p.pos < p.src.len && (p.src.ptr[p.pos] == 43 || p.src.ptr[p.pos] == 45) {
|
||||
p.pos = p.pos + 1;
|
||||
}
|
||||
if p.pos >= p.src.len || !is_digit(p.src.ptr[p.pos]) {
|
||||
ret .Err(at(.InvalidDigit, p.pos));
|
||||
}
|
||||
while p.pos < p.src.len && is_digit(p.src.ptr[p.pos]) {
|
||||
p.pos = p.pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
var text = ByteView.slice(p.src, start, p.pos);
|
||||
match text {
|
||||
.Ok(v) => {
|
||||
var node = blank_node(.Number);
|
||||
node.text = v;
|
||||
ret intern_node(p, node);
|
||||
},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_array(p: *Parser): NodeResult {
|
||||
p.pos = p.pos + 1; // '['
|
||||
var kids = Vec.blank(@sizeof(JsonChild), 8);
|
||||
|
||||
skip_ws(p);
|
||||
if peek(p) == 93 { // ']'
|
||||
p.pos = p.pos + 1;
|
||||
var empty = blank_node(.Array);
|
||||
empty.children = kids;
|
||||
ret intern_node(p, empty);
|
||||
}
|
||||
|
||||
while true {
|
||||
var element = parse_value(p);
|
||||
var index: usize = 0;
|
||||
match element {
|
||||
.Ok(v) => { index = v; },
|
||||
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
|
||||
}
|
||||
var child = JsonChild { .key = ByteView.empty(), .val = index };
|
||||
match Vec.push(&kids, &child as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
|
||||
}
|
||||
|
||||
skip_ws(p);
|
||||
var c = peek(p);
|
||||
if c == 44 { // ','
|
||||
p.pos = p.pos + 1;
|
||||
continue;
|
||||
}
|
||||
if c == 93 { // ']'
|
||||
p.pos = p.pos + 1;
|
||||
break;
|
||||
}
|
||||
Vec.drop(&kids);
|
||||
ret .Err(at(.InvalidFormat, p.pos));
|
||||
}
|
||||
|
||||
var node = blank_node(.Array);
|
||||
node.children = kids;
|
||||
ret intern_node(p, node);
|
||||
}
|
||||
|
||||
fn parse_object(p: *Parser): NodeResult {
|
||||
p.pos = p.pos + 1; // '{'
|
||||
var kids = Vec.blank(@sizeof(JsonChild), 8);
|
||||
|
||||
skip_ws(p);
|
||||
if peek(p) == 125 { // '}'
|
||||
p.pos = p.pos + 1;
|
||||
var empty = blank_node(.Object);
|
||||
empty.children = kids;
|
||||
ret intern_node(p, empty);
|
||||
}
|
||||
|
||||
while true {
|
||||
skip_ws(p);
|
||||
var key_view = ByteView.empty();
|
||||
match parse_string_raw(p) {
|
||||
.Ok(v) => { key_view = v; },
|
||||
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
|
||||
}
|
||||
|
||||
skip_ws(p);
|
||||
if peek(p) != 58 { // ':'
|
||||
Vec.drop(&kids);
|
||||
ret .Err(at(.InvalidFormat, p.pos));
|
||||
}
|
||||
p.pos = p.pos + 1;
|
||||
|
||||
var value = parse_value(p);
|
||||
var index: usize = 0;
|
||||
match value {
|
||||
.Ok(v) => { index = v; },
|
||||
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
|
||||
}
|
||||
|
||||
var child = JsonChild { .key = key_view, .val = index };
|
||||
match Vec.push(&kids, &child as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { Vec.drop(&kids); ret .Err(error); },
|
||||
}
|
||||
|
||||
skip_ws(p);
|
||||
var c = peek(p);
|
||||
if c == 44 { // ','
|
||||
p.pos = p.pos + 1;
|
||||
continue;
|
||||
}
|
||||
if c == 125 { // '}'
|
||||
p.pos = p.pos + 1;
|
||||
break;
|
||||
}
|
||||
Vec.drop(&kids);
|
||||
ret .Err(at(.InvalidFormat, p.pos));
|
||||
}
|
||||
|
||||
var node = blank_node(.Object);
|
||||
node.children = kids;
|
||||
ret intern_node(p, node);
|
||||
}
|
||||
|
||||
fn parse_value(p: *Parser): NodeResult {
|
||||
skip_ws(p);
|
||||
var c = peek(p);
|
||||
if c < 0 { ret .Err(at(.UnexpectedEnd, p.pos)); }
|
||||
var ch = c as u8;
|
||||
|
||||
if ch == 34 { // '"'
|
||||
var view = ByteView.empty();
|
||||
match parse_string_raw(p) {
|
||||
.Ok(v) => { view = v; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var node = blank_node(.String);
|
||||
node.text = view;
|
||||
ret intern_node(p, node);
|
||||
}
|
||||
if ch == 123 { ret parse_object(p); } // '{'
|
||||
if ch == 91 { ret parse_array(p); } // '['
|
||||
if ch == 116 { ret parse_literal(p, "true", .True); }
|
||||
if ch == 102 { ret parse_literal(p, "false", .False); }
|
||||
if ch == 110 { ret parse_literal(p, "null", .Null); }
|
||||
ret parse_number(p);
|
||||
}
|
||||
|
||||
fn drop_node_pool(pool: *Vec) {
|
||||
var i: usize = 0;
|
||||
while i < pool.*.len {
|
||||
match Vec.get(pool, i) {
|
||||
.Ok(ptr) => {
|
||||
var n = ptr as *JsonNode;
|
||||
Vec.drop(&n.children);
|
||||
},
|
||||
.Err(error) => {},
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(pool);
|
||||
}
|
||||
|
||||
fn parse(src: ByteView): JsonResult {
|
||||
var p = Parser {
|
||||
.src = src,
|
||||
.pos = 0,
|
||||
.nodes = Vec.blank(@sizeof(JsonNode), 8),
|
||||
};
|
||||
|
||||
skip_ws(&p);
|
||||
var root: usize = 0;
|
||||
var result = parse_value(&p);
|
||||
match result {
|
||||
.Ok(index) => { root = index; },
|
||||
.Err(error) => {
|
||||
drop_node_pool(&p.nodes);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
|
||||
skip_ws(&p);
|
||||
if p.pos != src.len {
|
||||
drop_node_pool(&p.nodes);
|
||||
ret .Err(at(.InvalidFormat, p.pos));
|
||||
}
|
||||
|
||||
ret .Ok(Json { .nodes = p.nodes, .root = root });
|
||||
}
|
||||
|
||||
fn drop(doc: *Json) {
|
||||
drop_node_pool(&doc.nodes);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
fn text_of(doc: *Json, index: usize): ByteView {
|
||||
var n = node_at(doc, index);
|
||||
if n == null { ret ByteView.empty(); }
|
||||
ret n.text;
|
||||
}
|
||||
|
||||
fn array_count(doc: *Json, index: usize): usize {
|
||||
var n = node_at(doc, index);
|
||||
if n == null { ret 0; }
|
||||
match n.kind {
|
||||
.Array => { ret n.children.len; },
|
||||
_ => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn array_at(doc: *Json, index: usize, i: usize): usize {
|
||||
var n = node_at(doc, index);
|
||||
if n == null { ret ~(0 as usize); }
|
||||
match n.kind {
|
||||
.Array => {
|
||||
if i >= n.children.len { ret ~(0 as usize); }
|
||||
match Vec.get(&n.children, i) {
|
||||
.Ok(ptr) => { ret (ptr as *JsonChild).val; },
|
||||
.Err(error) => { ret ~(0 as usize); },
|
||||
}
|
||||
},
|
||||
_ => { ret ~(0 as usize); },
|
||||
}
|
||||
ret ~(0 as usize);
|
||||
}
|
||||
|
||||
// 对象按键查找(线性,保持插入顺序)。找到返回节点下标。
|
||||
fn object_find(doc: *Json, index: usize, key: ByteView): FindResult {
|
||||
var n = node_at(doc, index);
|
||||
if n == null { ret .NotFound; }
|
||||
match n.kind {
|
||||
.Object => {
|
||||
var i: usize = 0;
|
||||
var count = n.children.len;
|
||||
while i < count {
|
||||
match Vec.get(&n.children, i) {
|
||||
.Ok(ptr) => {
|
||||
var child = ptr as *JsonChild;
|
||||
if ByteView.eq(child.key, key) { ret .Found(child.val); }
|
||||
},
|
||||
.Err(error) => {},
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret .NotFound;
|
||||
},
|
||||
_ => { ret .NotFound; },
|
||||
}
|
||||
ret .NotFound;
|
||||
}
|
||||
|
||||
fn emit_quoted(out: *ByteBuffer, text: ByteView): Status {
|
||||
match ByteBuffer.append_byte(out, 34) { // '"'
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match ByteBuffer.append(out, ByteView.as_slice(text)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
ret ByteBuffer.append_byte(out, 34);
|
||||
}
|
||||
|
||||
fn emit_indent(out: *ByteBuffer, indent: usize, depth: usize): Status {
|
||||
match ByteBuffer.append_byte(out, 10) { // '\n'
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
ret ByteBuffer.append_repeat(out, 32, depth * indent);
|
||||
}
|
||||
|
||||
fn emit(out: *ByteBuffer, doc: *Json, index: usize, indent: usize, depth: usize): Status {
|
||||
var n = node_at(doc, index);
|
||||
if n == null { ret .Err(make(.OutOfRange)); }
|
||||
|
||||
match n.kind {
|
||||
.Null => { ret ByteBuffer.append(out, "null"); },
|
||||
.True => { ret ByteBuffer.append(out, "true"); },
|
||||
.False => { ret ByteBuffer.append(out, "false"); },
|
||||
.Number => { ret ByteBuffer.append(out, ByteView.as_slice(n.text)); },
|
||||
.String => { ret emit_quoted(out, n.text); },
|
||||
.Array => { ret emit_children(out, doc, n, false, indent, depth); },
|
||||
.Object => { ret emit_children(out, doc, n, true, indent, depth); },
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn emit_children(out: *ByteBuffer, doc: *Json, n: *JsonNode, is_object: bool, indent: usize,
|
||||
depth: usize): Status {
|
||||
var open_byte: u8 = 91; // '['
|
||||
var close_byte: u8 = 93; // ']'
|
||||
if is_object {
|
||||
open_byte = 123; // '{'
|
||||
close_byte = 125; // '}'
|
||||
}
|
||||
|
||||
match ByteBuffer.append_byte(out, open_byte) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var count = n.children.len;
|
||||
if count == 0 {
|
||||
ret ByteBuffer.append_byte(out, close_byte);
|
||||
}
|
||||
|
||||
var i: usize = 0;
|
||||
while i < count {
|
||||
if i != 0 {
|
||||
match ByteBuffer.append_byte(out, 44) { // ','
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
}
|
||||
if indent != 0 {
|
||||
match emit_indent(out, indent, depth + 1) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
}
|
||||
|
||||
match Vec.get(&n.children, i) {
|
||||
.Ok(ptr) => {
|
||||
var child = ptr as *JsonChild;
|
||||
if is_object {
|
||||
match emit_quoted(out, child.key) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match ByteBuffer.append_byte(out, 58) { // ':'
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if indent != 0 {
|
||||
match ByteBuffer.append_byte(out, 32) { // ' '
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
}
|
||||
}
|
||||
match emit(out, doc, child.val, indent, depth + 1) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
if indent != 0 {
|
||||
match emit_indent(out, indent, depth) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
}
|
||||
ret ByteBuffer.append_byte(out, close_byte);
|
||||
}
|
||||
|
||||
fn emit_compact(out: *ByteBuffer, doc: *Json): Status {
|
||||
ret emit(out, doc, doc.root, 0, 0);
|
||||
}
|
||||
|
||||
fn emit_pretty(out: *ByteBuffer, doc: *Json, indent: usize): Status {
|
||||
ret emit(out, doc, doc.root, indent, 0);
|
||||
}
|
||||
|
||||
// ---- 构造辅助(写 JSON 片段;字符串需由调用方保证已转义)----
|
||||
|
||||
fn write_int(out: *ByteBuffer, value: i64): Status {
|
||||
ret append_i64(out, value, 10, false);
|
||||
}
|
||||
|
||||
fn write_u64(out: *ByteBuffer, value: u64): Status {
|
||||
ret append_u64(out, value, 10, false);
|
||||
}
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "unexpected json error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_bytes(out: *ByteBuffer, expected: []u8) {
|
||||
var actual = ByteBuffer.as_slice(out.*);
|
||||
@assert(actual.len == expected.len, "emitted length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(actual.ptr[i] == expected.ptr[i], "emitted byte");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_parse_error(result: JsonResult, pos: usize) {
|
||||
match result {
|
||||
.Ok(doc) => { @assert(false, "expected parse error"); },
|
||||
.Err(error) => {
|
||||
@assert(error.has_position, "positioned error");
|
||||
@assert(error.position == pos, "error position");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_kind(doc: *Json, index: usize, kind: JsonKind) {
|
||||
var n = node_at(doc, index);
|
||||
@assert(n != null, "node exists");
|
||||
@assert(kind_code(n.kind) == kind_code(kind), "node kind");
|
||||
}
|
||||
|
||||
fn expect_text(doc: *Json, index: usize, expected: []u8) {
|
||||
var got = text_of(doc, index);
|
||||
var want = ByteView.from_slice(expected);
|
||||
@assert(ByteView.eq(got, want), "node text");
|
||||
}
|
||||
|
||||
fn test_scalars() {
|
||||
var r = parse(ByteView.from_slice("null"));
|
||||
match r {
|
||||
.Ok(doc) => { expect_kind(&doc, root_index(&doc), .Null); drop(&doc); },
|
||||
.Err(error) => { @assert(false, "parse null"); },
|
||||
}
|
||||
|
||||
r = parse(ByteView.from_slice("true"));
|
||||
match r {
|
||||
.Ok(doc) => { expect_kind(&doc, root_index(&doc), .True); drop(&doc); },
|
||||
.Err(error) => { @assert(false, "parse true"); },
|
||||
}
|
||||
|
||||
r = parse(ByteView.from_slice("false"));
|
||||
match r {
|
||||
.Ok(doc) => { expect_kind(&doc, root_index(&doc), .False); drop(&doc); },
|
||||
.Err(error) => { @assert(false, "parse false"); },
|
||||
}
|
||||
|
||||
r = parse(ByteView.from_slice("-12.5e2"));
|
||||
match r {
|
||||
.Ok(doc) => {
|
||||
var idx = root_index(&doc);
|
||||
expect_kind(&doc, idx, .Number);
|
||||
expect_text(&doc, idx, "-12.5e2");
|
||||
drop(&doc);
|
||||
},
|
||||
.Err(error) => { @assert(false, "parse number"); },
|
||||
}
|
||||
|
||||
r = parse(ByteView.from_slice("\"hi\\n\""));
|
||||
match r {
|
||||
.Ok(doc) => {
|
||||
var idx = root_index(&doc);
|
||||
expect_kind(&doc, idx, .String);
|
||||
expect_text(&doc, idx, "hi\\n"); // 原始切片,未解码
|
||||
drop(&doc);
|
||||
},
|
||||
.Err(error) => { @assert(false, "parse string"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn test_containers_and_lookup() {
|
||||
var text = "{\"a\":1,\"b\":[true,null,\"x\"],\"c\":{\"d\":2}}";
|
||||
var r = parse(ByteView.from_slice(text));
|
||||
match r {
|
||||
.Ok(doc) => {
|
||||
var root = root_index(&doc);
|
||||
expect_kind(&doc, root, .Object);
|
||||
|
||||
var a = ByteView.from_slice("a");
|
||||
match object_find(&doc, root, a) {
|
||||
.Found(idx) => {
|
||||
expect_kind(&doc, idx, .Number);
|
||||
expect_text(&doc, idx, "1");
|
||||
},
|
||||
.NotFound => { @assert(false, "find a"); },
|
||||
}
|
||||
|
||||
var b = ByteView.from_slice("b");
|
||||
var bidx: usize = 0;
|
||||
match object_find(&doc, root, b) {
|
||||
.Found(idx) => { bidx = idx; },
|
||||
.NotFound => { @assert(false, "find b"); },
|
||||
}
|
||||
expect_kind(&doc, bidx, .Array);
|
||||
@assert(array_count(&doc, bidx) == 3, "array count");
|
||||
expect_kind(&doc, array_at(&doc, bidx, 0), .True);
|
||||
expect_kind(&doc, array_at(&doc, bidx, 1), .Null);
|
||||
expect_text(&doc, array_at(&doc, bidx, 2), "x");
|
||||
|
||||
var c = ByteView.from_slice("c");
|
||||
var cidx: usize = 0;
|
||||
match object_find(&doc, root, c) {
|
||||
.Found(idx) => { cidx = idx; },
|
||||
.NotFound => { @assert(false, "find c"); },
|
||||
}
|
||||
var d = ByteView.from_slice("d");
|
||||
match object_find(&doc, cidx, d) {
|
||||
.Found(idx) => { expect_text(&doc, idx, "2"); },
|
||||
.NotFound => { @assert(false, "find c.d"); },
|
||||
}
|
||||
|
||||
var missing = ByteView.from_slice("zzz");
|
||||
match object_find(&doc, root, missing) {
|
||||
.Found(idx) => { @assert(false, "unexpected find"); },
|
||||
.NotFound => {},
|
||||
}
|
||||
|
||||
drop(&doc);
|
||||
},
|
||||
.Err(error) => { @assert(false, "parse object"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn test_roundtrip_compact() {
|
||||
var text = "{\"a\":1,\"b\":[true,null,\"x\\n\"],\"c\":{},\"d\":[],\"e\":-3.5e-2}";
|
||||
var r = parse(ByteView.from_slice(text));
|
||||
match r {
|
||||
.Ok(doc) => {
|
||||
var out = ByteBuffer.new();
|
||||
expect_ok(emit_compact(&out, &doc));
|
||||
expect_bytes(&out, text);
|
||||
ByteBuffer.drop(&out);
|
||||
drop(&doc);
|
||||
},
|
||||
.Err(error) => { @assert(false, "parse for roundtrip"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn test_pretty() {
|
||||
var text = "{\"a\":1,\"b\":[1,2]}";
|
||||
var r = parse(ByteView.from_slice(text));
|
||||
match r {
|
||||
.Ok(doc) => {
|
||||
var out = ByteBuffer.new();
|
||||
expect_ok(emit_pretty(&out, &doc, 2));
|
||||
var expected = "{\n \"a\": 1,\n \"b\": [\n 1,\n 2\n ]\n}";
|
||||
expect_bytes(&out, expected);
|
||||
ByteBuffer.drop(&out);
|
||||
drop(&doc);
|
||||
},
|
||||
.Err(error) => { @assert(false, "parse pretty"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn test_errors() {
|
||||
expect_parse_error(parse(ByteView.from_slice("")), 0);
|
||||
expect_parse_error(parse(ByteView.from_slice("\"abc")), 4);
|
||||
expect_parse_error(parse(ByteView.from_slice("tru")), 3);
|
||||
expect_parse_error(parse(ByteView.from_slice("[1,]")), 3);
|
||||
expect_parse_error(parse(ByteView.from_slice("{\"a\":}")), 5);
|
||||
expect_parse_error(parse(ByteView.from_slice("1 2")), 2);
|
||||
expect_parse_error(parse(ByteView.from_slice("01")), 1);
|
||||
}
|
||||
|
||||
fn test_construction_helpers() {
|
||||
var out = ByteBuffer.new();
|
||||
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();
|
||||
expect_ok(ByteBuffer.append_byte(&out, 91)); // '['
|
||||
var i: usize = 0;
|
||||
while i < 1000 {
|
||||
if i != 0 { expect_ok(ByteBuffer.append_byte(&out, 44)); }
|
||||
expect_ok(ByteBuffer.append_byte(&out, 49)); // '1'
|
||||
i = i + 1;
|
||||
}
|
||||
expect_ok(ByteBuffer.append_byte(&out, 93)); // ']'
|
||||
|
||||
var text = ByteBuffer.as_slice(out);
|
||||
var r = parse(ByteView.from_slice(text));
|
||||
match r {
|
||||
.Ok(doc) => {
|
||||
expect_kind(&doc, root_index(&doc), .Array);
|
||||
@assert(array_count(&doc, root_index(&doc)) == 1000, "large array count");
|
||||
expect_kind(&doc, array_at(&doc, root_index(&doc), 999), .Number);
|
||||
drop(&doc);
|
||||
},
|
||||
.Err(error) => { @assert(false, "parse large array"); },
|
||||
}
|
||||
ByteBuffer.drop(&out);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_scalars();
|
||||
test_containers_and_lookup();
|
||||
test_roundtrip_compact();
|
||||
test_pretty();
|
||||
test_errors();
|
||||
test_construction_helpers();
|
||||
test_large_array();
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
// Expect retcode = 0
|
||||
// 开放寻址 + tombstone 哈希表。键和值都按字节复制到表自己的存储。
|
||||
// 键类型由 hash_fn / eq_fn 解释:回调收到指向键字节的指针(如 *usize / *ByteView)。
|
||||
// 普通赋值只复制句柄(与 Vec 一样),所有权转移必须使用 take。
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
type .{ .ByteView } = @import("byte_view.spl");
|
||||
type .{ .copy } = @import("../sys/mem.spl");
|
||||
|
||||
// 槽位状态:0=EMPTY,1=OCCUPIED,2=DELETED(墓碑,仅在扩容重哈希时清除)。
|
||||
// 负载因子 70/128。
|
||||
|
||||
type Map = @self;
|
||||
|
||||
keys: Vec,
|
||||
vals: Vec,
|
||||
states: Vec,
|
||||
size: usize,
|
||||
key_size: usize,
|
||||
val_size: usize,
|
||||
hash_fn: fn(*u8): usize,
|
||||
eq_fn: fn(*u8, *u8): bool,
|
||||
|
||||
fn blank(key_size: usize, key_align: usize, val_size: usize, val_align: usize,
|
||||
hash_fn: fn(*u8): usize, eq_fn: fn(*u8, *u8): bool): Map {
|
||||
ret Map {
|
||||
.keys = Vec.blank(key_size, key_align),
|
||||
.vals = Vec.blank(val_size, val_align),
|
||||
.states = Vec.blank(1, 1),
|
||||
.size = 0,
|
||||
.key_size = key_size,
|
||||
.val_size = val_size,
|
||||
.hash_fn = hash_fn,
|
||||
.eq_fn = eq_fn,
|
||||
};
|
||||
}
|
||||
|
||||
fn new(key_size: usize, key_align: usize, val_size: usize, val_align: usize,
|
||||
hash_fn: fn(*u8): usize, eq_fn: fn(*u8, *u8): bool): Map {
|
||||
ret blank(key_size, key_align, val_size, val_align, hash_fn, eq_fn);
|
||||
}
|
||||
|
||||
fn valid(self: Map): bool {
|
||||
if self.key_size == 0 || self.val_size == 0 { ret false; }
|
||||
if self.states.len != self.keys.len || self.states.len != self.vals.len {
|
||||
ret false;
|
||||
}
|
||||
if self.size > self.states.len { ret false; }
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn size(self: Map): usize {
|
||||
ret self.size;
|
||||
}
|
||||
|
||||
fn capacity(self: Map): usize {
|
||||
ret self.states.cap;
|
||||
}
|
||||
|
||||
// 迭代支持:遍历 [0, slot_count),slot_state 为 1 时 key_at/val_at 有效。
|
||||
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_occupied(state: u8): bool {
|
||||
ret state == 1;
|
||||
}
|
||||
|
||||
fn key_at(self: *Map, index: usize): *u8 {
|
||||
ret self.keys.data + index * self.key_size;
|
||||
}
|
||||
|
||||
fn val_at(self: *Map, index: usize): *u8 {
|
||||
ret self.vals.data + index * self.val_size;
|
||||
}
|
||||
|
||||
// 返回占用键的槽位;未找到返回 ~0。容量为 0 时直接未找到。
|
||||
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) {
|
||||
ret i;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
ret ~(0 as 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 {
|
||||
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.size = self.size + 1;
|
||||
}
|
||||
|
||||
// 保证至少 need 个槽位。所有检查在旧表改动前完成,失败保持原表不变。
|
||||
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 fresh = self.*;
|
||||
fresh.keys = Vec.blank(self.key_size, self.keys.elem_align);
|
||||
fresh.vals = Vec.blank(self.val_size, self.vals.elem_align);
|
||||
fresh.states = Vec.blank(1, 1);
|
||||
fresh.size = 0;
|
||||
|
||||
match Vec.reserve(&fresh.keys, new_cap) {
|
||||
.Ok => {},
|
||||
.Err(error) => { drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
match Vec.reserve(&fresh.vals, new_cap) {
|
||||
.Ok => {},
|
||||
.Err(error) => { drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
match Vec.reserve(&fresh.states, new_cap) {
|
||||
.Ok => {},
|
||||
.Err(error) => { drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
fresh.keys.len = new_cap;
|
||||
fresh.vals.len = new_cap;
|
||||
fresh.states.len = new_cap;
|
||||
|
||||
var i: usize = 0;
|
||||
while i < new_cap {
|
||||
fresh.states.data[i] = 0;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while i < self.states.len {
|
||||
if self.states.data[i] == 1 {
|
||||
place(&fresh, self.keys.data + i * self.key_size,
|
||||
self.vals.data + i * self.val_size);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
var old_keys = self.keys;
|
||||
var old_vals = self.vals;
|
||||
var old_states = self.states;
|
||||
self.* = fresh;
|
||||
Vec.drop(&old_keys);
|
||||
Vec.drop(&old_vals);
|
||||
Vec.drop(&old_states);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 覆盖已存在的键;否则插入并可能扩容。
|
||||
fn put(self: *Map, key: *u8, val: *u8): Status {
|
||||
if !valid(self.*) || key == null || val == null {
|
||||
ret .Err(make(.InvalidArgument));
|
||||
}
|
||||
|
||||
var existing = find_slot(self, key);
|
||||
if existing != ~(0 as usize) {
|
||||
copy((self.vals.data + existing * self.val_size) as *any, val as *any, self.val_size);
|
||||
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) {
|
||||
.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; }
|
||||
var i = find_slot(self, key);
|
||||
if i == ~(0 as usize) { ret false; }
|
||||
copy(out as *any, (self.vals.data + i * self.val_size) as *any, self.val_size);
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn del(self: *Map, key: *u8): bool {
|
||||
if !valid(self.*) || key == null { ret false; }
|
||||
var i = find_slot(self, key);
|
||||
if i == ~(0 as usize) { ret false; }
|
||||
self.states.data[i] = 2;
|
||||
self.size = self.size - 1;
|
||||
ret true;
|
||||
}
|
||||
|
||||
// 清空内容但保留容量。墓碑与负载计数一并复位。
|
||||
fn clear(self: *Map) {
|
||||
var i: usize = 0;
|
||||
while i < self.states.len {
|
||||
self.states.data[i] = 0;
|
||||
i = i + 1;
|
||||
}
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
fn drop(self: *Map) {
|
||||
Vec.drop(&self.keys);
|
||||
Vec.drop(&self.vals);
|
||||
Vec.drop(&self.states);
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
fn take(source: *Map): Map {
|
||||
var result = source.*;
|
||||
source.keys = Vec.blank(source.key_size, source.keys.elem_align);
|
||||
source.vals = Vec.blank(source.val_size, source.vals.elem_align);
|
||||
source.states = Vec.blank(1, 1);
|
||||
source.size = 0;
|
||||
ret result;
|
||||
}
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
fn hash_usize(key: *u8): usize {
|
||||
var value = (key as *usize).*;
|
||||
ret value * 2654435761;
|
||||
}
|
||||
|
||||
fn eq_usize(a: *u8, b: *u8): bool {
|
||||
ret (a as *usize).* == (b as *usize).*;
|
||||
}
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "unexpected Map error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_invalid(result: Status) {
|
||||
match result {
|
||||
.Ok => { @assert(false, "expected Map error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.InvalidArgument => {},
|
||||
_ => { @assert(false, "unexpected Map error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn count_occupied(m: *Map): usize {
|
||||
var n: usize = 0;
|
||||
var i: usize = 0;
|
||||
while i < slot_count(m.*) {
|
||||
if slot_occupied(slot_state(m.*, i)) { n = n + 1; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret n;
|
||||
}
|
||||
|
||||
fn make_int_map(): Map {
|
||||
ret new(@sizeof(usize), @alignof(usize), @sizeof(i32), @alignof(i32), hash_usize, eq_usize);
|
||||
}
|
||||
|
||||
fn test_put_get_update() {
|
||||
var m = make_int_map();
|
||||
@assert(size(m) == 0 && capacity(m) == 0, "empty map");
|
||||
@assert(valid(m));
|
||||
|
||||
var key: usize = 0;
|
||||
var value: i32 = 0;
|
||||
var out: i32 = 0;
|
||||
|
||||
expect_invalid(put(&m, null, &value as *u8));
|
||||
expect_invalid(put(&m, &key as *u8, null));
|
||||
@assert(size(m) == 0, "failed put preserves map");
|
||||
|
||||
key = 7;
|
||||
value = 70;
|
||||
expect_ok(put(&m, &key as *u8, &value as *u8));
|
||||
@assert(size(m) == 1 && valid(m));
|
||||
|
||||
out = -1;
|
||||
@assert(get(&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");
|
||||
|
||||
key = 8;
|
||||
out = 123;
|
||||
@assert(!get(&m, &key as *u8, &out as *u8) && out == 123, "missing key preserves out");
|
||||
|
||||
drop(&m);
|
||||
@assert(size(m) == 0 && capacity(m) == 0, "drop resets map");
|
||||
}
|
||||
|
||||
fn test_growth_and_delete() {
|
||||
var m = make_int_map();
|
||||
var i: i32 = 0;
|
||||
while i < 1000 {
|
||||
var key: usize = i as usize;
|
||||
var value: i32 = i * 3 - 7;
|
||||
expect_ok(put(&m, &key as *u8, &value as *u8));
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(size(m) == 1000);
|
||||
@assert(count_occupied(&m) == 1000, "all slots accounted");
|
||||
|
||||
i = 0;
|
||||
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(out == i * 3 - 7, "value after growth");
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while i < 500 {
|
||||
var key: usize = i as usize;
|
||||
@assert(del(&m, &key as *u8));
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(size(m) == 500 && count_occupied(&m) == 500, "deleted slots not occupied");
|
||||
|
||||
i = 0;
|
||||
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");
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
// 墓碑槽位可被再次插入复用,且不会重复计数。
|
||||
var key: usize = 12345;
|
||||
var value: i32 = 99;
|
||||
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(del(&m, &key as *u8), "delete key");
|
||||
@assert(size(m) == 500);
|
||||
@assert(!get(&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();
|
||||
var key: usize = 5;
|
||||
var value: i32 = 55;
|
||||
expect_ok(put(&m, &key as *u8, &value as *u8));
|
||||
|
||||
var moved = take(&m);
|
||||
@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");
|
||||
|
||||
// 值指针可以指向表内已有的值(按字节移动,允许重叠)。
|
||||
var alias: i32 = 0;
|
||||
@assert(get(&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);
|
||||
}
|
||||
|
||||
fn hash_view(key: *u8): usize {
|
||||
var view = (key as *ByteView).*;
|
||||
var h: usize = 14695981039346656037;
|
||||
var i: usize = 0;
|
||||
while i < view.len {
|
||||
h = (h ^ (view.ptr[i] as usize)) * 1099511628211;
|
||||
i = i + 1;
|
||||
}
|
||||
ret h;
|
||||
}
|
||||
|
||||
fn eq_view(a: *u8, b: *u8): bool {
|
||||
ret ByteView.eq((a as *ByteView).*, (b as *ByteView).*);
|
||||
}
|
||||
|
||||
fn test_byte_view_keys() {
|
||||
var m = new(@sizeof(ByteView), @alignof(ByteView), @sizeof(i32), @alignof(i32),
|
||||
hash_view, eq_view);
|
||||
|
||||
var alpha = ByteView.from_slice("alpha");
|
||||
var beta = ByteView.from_slice("beta");
|
||||
var gamma = ByteView.from_slice("gamma");
|
||||
var one: i32 = 1;
|
||||
var two: i32 = 2;
|
||||
var three: i32 = 3;
|
||||
|
||||
expect_ok(put(&m, &alpha as *u8, &one as *u8));
|
||||
expect_ok(put(&m, &beta as *u8, &two as *u8));
|
||||
expect_ok(put(&m, &gamma as *u8, &three as *u8));
|
||||
@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");
|
||||
|
||||
// 内容相同的不同 ByteView 视为同一个键。
|
||||
var alpha2 = ByteView.from_slice("alpha");
|
||||
out = 0;
|
||||
@assert(get(&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(size(m) == 2);
|
||||
@assert(!get(&m, &beta as *u8, &out as *u8));
|
||||
|
||||
drop(&m);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_put_get_update();
|
||||
test_growth_and_delete();
|
||||
test_take_and_self_put();
|
||||
test_byte_view_keys();
|
||||
}
|
||||
Reference in new issue
Block a user