stage2 修复全局变量未完成的问题 提供错误处理解析
This commit is contained in:
1 parent
c0a86ea552
commit
0b8f649818
11 files changed
+317
-18
No files matched your search
+6
-6
@@ -96,15 +96,15 @@ static const char* splc1_deps[] = {
|
|||||||
|
|
||||||
static const char* splc2_deps[] = {
|
static const char* splc2_deps[] = {
|
||||||
/* stage3 */
|
/* stage3 */
|
||||||
"stage3/ast2ir.spl",
|
"stage3/builtin.spl",
|
||||||
"stage3/ast_clone.spl",
|
"stage3/hir.spl",
|
||||||
"stage3/ctvm.spl",
|
"stage3/hir2ir.spl",
|
||||||
"stage3/eval.spl",
|
|
||||||
"stage3/intrin.spl",
|
|
||||||
"stage3/lang_type.spl",
|
"stage3/lang_type.spl",
|
||||||
|
"stage3/lower.spl",
|
||||||
|
"stage3/scope.spl",
|
||||||
"stage3/sema.spl",
|
"stage3/sema.spl",
|
||||||
"stage3/splc2.spl",
|
"stage3/splc2.spl",
|
||||||
"stage3/splrt.spl",
|
"stage3/tag.spl",
|
||||||
/* stage2 */
|
/* stage2 */
|
||||||
"stage2/sys/exit.spl",
|
"stage2/sys/exit.spl",
|
||||||
"stage2/sys/file.spl",
|
"stage2/sys/file.spl",
|
||||||
|
|||||||
+24
-4
@@ -87,12 +87,32 @@ SPL_FN u64 vm_timestamp(u64 kind) {
|
|||||||
#define SPL_VM_EMBED_IMPLIMENTATION
|
#define SPL_VM_EMBED_IMPLIMENTATION
|
||||||
#include "spl_vm_embed.h"
|
#include "spl_vm_embed.h"
|
||||||
|
|
||||||
/* 配置层外部函数处理器(native 端)。VM 模式的实现见 spl_syscall.c。
|
/* 配置层外部函数处理器(native 端):把 nested VM 未解析的 native 按名分发到
|
||||||
* native 的 foreign 分发尚未接入(后续),这里先提供可链接入口。 */
|
* 编译器生成的 `ct_*` 函数(表由 ir2c 生成,见 stage2/ir2c.spl)。 */
|
||||||
SPL_FN void vm_set_foreign(void *vm, unsigned long long handler, void *ctx) {
|
extern void *spl_ct_lookup(const char *name);
|
||||||
|
|
||||||
|
typedef spl_vm_val_t (*spl_native_ct_fn_t)(void *, spl_vm_val_t, spl_vm_val_t, spl_vm_val_t);
|
||||||
|
|
||||||
|
SPL_FN spl_vm_val_t spl_native_foreign(spl_vm_t *vm, const spl_vm_call_t *call, void *user) {
|
||||||
|
void *fn;
|
||||||
|
spl_vm_val_t a0 = 0, a1 = 0;
|
||||||
(void)vm;
|
(void)vm;
|
||||||
|
if (!call)
|
||||||
|
return 0;
|
||||||
|
fn = spl_ct_lookup(call->name);
|
||||||
|
if (!fn)
|
||||||
|
return 0;
|
||||||
|
if (call->nargs > 0)
|
||||||
|
a0 = call->args[0];
|
||||||
|
if (call->nargs > 1)
|
||||||
|
a1 = call->args[1];
|
||||||
|
/* ct_* 统一形状:返回 u64,参数 (ctx, u64, u64);多余参数被忽略。 */
|
||||||
|
return ((spl_native_ct_fn_t)fn)(user, a0, a1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SPL_FN void vm_set_foreign(void *p, unsigned long long handler, void *ctx) {
|
||||||
(void)handler;
|
(void)handler;
|
||||||
(void)ctx;
|
spl_vm_set_foreign((spl_vm_t *)p, spl_native_foreign, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* __SPL_RT_H__ */
|
#endif /* __SPL_RT_H__ */
|
||||||
+1
-4
@@ -1674,11 +1674,8 @@ L_SPL_STORE:
|
|||||||
spl_vm_val_t _v = POP();
|
spl_vm_val_t _v = POP();
|
||||||
spl_vm_val_t _off = POP();
|
spl_vm_val_t _off = POP();
|
||||||
void *_addr = spl_vm_xlate(vm, _off, spl_type_size(ins->type));
|
void *_addr = spl_vm_xlate(vm, _off, spl_type_size(ins->type));
|
||||||
if (!_addr) {
|
if (!_addr)
|
||||||
fprintf(stderr, "vm: STORE bad off=%zu sz=%d\n", (size_t)_off, (int)spl_type_size(ins->type));
|
|
||||||
spl_vm_backtrace(vm, vm->fp);
|
|
||||||
VM_ERROR("STORE: invalid address");
|
VM_ERROR("STORE: invalid address");
|
||||||
}
|
|
||||||
memcpy(_addr, &_v, spl_type_size(ins->type));
|
memcpy(_addr, &_v, spl_type_size(ins->type));
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ type Body = enum {
|
|||||||
StatementBreak: struct { label: usize, expr: usize },
|
StatementBreak: struct { label: usize, expr: usize },
|
||||||
StatementContinue: struct { label: usize },
|
StatementContinue: struct { label: usize },
|
||||||
StatementDefer: struct { body: usize },
|
StatementDefer: struct { body: usize },
|
||||||
|
StatementErrdefer: struct { body: usize }, // errdefer:仅错误退出时执行
|
||||||
StatementComptime: struct { body: usize },
|
StatementComptime: struct { body: usize },
|
||||||
StatementDecl: struct { decl: usize },
|
StatementDecl: struct { decl: usize },
|
||||||
StatementExpr: struct { expr: usize },
|
StatementExpr: struct { expr: usize },
|
||||||
@@ -87,6 +88,8 @@ type Body = enum {
|
|||||||
},
|
},
|
||||||
ExprAggregateLit: struct { name: ByteView, fields: Vec },
|
ExprAggregateLit: struct { name: ByteView, fields: Vec },
|
||||||
ExprComptimeCall: struct { name: ByteView, args: Vec },
|
ExprComptimeCall: struct { name: ByteView, args: Vec },
|
||||||
|
ExprTry: struct { value: usize }, // `try expr`
|
||||||
|
ExprCatch: struct { value: usize, pattern: usize, handler: usize }, // `expr catch <pat> { }`
|
||||||
ExprBlock: struct { stmts: Vec, tail_expr: usize },
|
ExprBlock: struct { stmts: Vec, tail_expr: usize },
|
||||||
ExprIf: struct { cond: usize, then_block: usize, else_branch: usize },
|
ExprIf: struct { cond: usize, then_block: usize, else_branch: usize },
|
||||||
ExprIfvar: struct { var_decl: usize, then_block: usize, else_branch: usize },
|
ExprIfvar: struct { var_decl: usize, then_block: usize, else_branch: usize },
|
||||||
@@ -144,6 +147,7 @@ fn kind_name(body: Body): []u8 {
|
|||||||
.StatementBreak(_) => { ret "statement_break"; },
|
.StatementBreak(_) => { ret "statement_break"; },
|
||||||
.StatementContinue(_) => { ret "statement_continue"; },
|
.StatementContinue(_) => { ret "statement_continue"; },
|
||||||
.StatementDefer(_) => { ret "statement_defer"; },
|
.StatementDefer(_) => { ret "statement_defer"; },
|
||||||
|
.StatementErrdefer(_) => { ret "statement_errdefer"; },
|
||||||
.StatementComptime(_) => { ret "statement_comptime"; },
|
.StatementComptime(_) => { ret "statement_comptime"; },
|
||||||
.ExprComptime(_) => { ret "expr_comptime"; },
|
.ExprComptime(_) => { ret "expr_comptime"; },
|
||||||
.StatementDecl(_) => { ret "statement_decl"; },
|
.StatementDecl(_) => { ret "statement_decl"; },
|
||||||
@@ -171,6 +175,8 @@ fn kind_name(body: Body): []u8 {
|
|||||||
.ExprArrayLit(_) => { ret "array_literal"; },
|
.ExprArrayLit(_) => { ret "array_literal"; },
|
||||||
.ExprAggregateLit(_) => { ret "aggregate_literal"; },
|
.ExprAggregateLit(_) => { ret "aggregate_literal"; },
|
||||||
.ExprComptimeCall(_) => { ret "comptime_call"; },
|
.ExprComptimeCall(_) => { ret "comptime_call"; },
|
||||||
|
.ExprTry(_) => { ret "try_expr"; },
|
||||||
|
.ExprCatch(_) => { ret "catch_expr"; },
|
||||||
.ExprComptimeInit(_) => { ret "comptime_init"; },
|
.ExprComptimeInit(_) => { ret "comptime_init"; },
|
||||||
.ExprBlock(_) => { ret "block"; },
|
.ExprBlock(_) => { ret "block"; },
|
||||||
.ExprIf(_) => { ret "if_expr"; },
|
.ExprIf(_) => { ret "if_expr"; },
|
||||||
@@ -466,6 +472,7 @@ fn dump_node(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool):
|
|||||||
},
|
},
|
||||||
.StatementContinue(x) => { ok = dump_opt(ast, buf, stack, x.label, true); },
|
.StatementContinue(x) => { ok = dump_opt(ast, buf, stack, x.label, true); },
|
||||||
.StatementDefer(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
.StatementDefer(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
||||||
|
.StatementErrdefer(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
||||||
.StatementComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
.StatementComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
||||||
.ExprComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
.ExprComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
||||||
.StatementDecl(d) => { ok = dump_opt(ast, buf, stack, d.decl, true); },
|
.StatementDecl(d) => { ok = dump_opt(ast, buf, stack, d.decl, true); },
|
||||||
@@ -519,6 +526,12 @@ fn dump_node(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool):
|
|||||||
},
|
},
|
||||||
.ExprAggregateLit(a) => { ok = dump_list(ast, buf, stack, a.fields); },
|
.ExprAggregateLit(a) => { ok = dump_list(ast, buf, stack, a.fields); },
|
||||||
.ExprComptimeCall(c) => { ok = dump_list(ast, buf, stack, c.args); },
|
.ExprComptimeCall(c) => { ok = dump_list(ast, buf, stack, c.args); },
|
||||||
|
.ExprTry(t2) => { ok = dump_opt(ast, buf, stack, t2.value, true); },
|
||||||
|
.ExprCatch(c2) => {
|
||||||
|
ok = dump_opt(ast, buf, stack, c2.value, false) &&
|
||||||
|
dump_opt(ast, buf, stack, c2.pattern, false) &&
|
||||||
|
dump_opt(ast, buf, stack, c2.handler, true);
|
||||||
|
},
|
||||||
.ExprComptimeInit(x) => { ok = dump_list(ast, buf, stack, x.fields); },
|
.ExprComptimeInit(x) => { ok = dump_list(ast, buf, stack, x.fields); },
|
||||||
.ExprBlock(b) => {
|
.ExprBlock(b) => {
|
||||||
ok = dump_list(ast, buf, stack, b.stmts) &&
|
ok = dump_list(ast, buf, stack, b.stmts) &&
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ sema: *Sema,
|
|||||||
ir: *IrBuilder,
|
ir: *IrBuilder,
|
||||||
ast: *Ast,
|
ast: *Ast,
|
||||||
def2ir_map: Map,
|
def2ir_map: Map,
|
||||||
|
glob_gidx: Map, // def_id -> gidx+1(全局变量)
|
||||||
loop_stack: Vec, // LoopCtx
|
loop_stack: Vec, // LoopCtx
|
||||||
defer_stack: Vec, // ast node ref
|
defer_stack: Vec, // ast node ref
|
||||||
cur_ret_lang: usize,
|
cur_ret_lang: usize,
|
||||||
@@ -105,6 +106,62 @@ fn ir_map_put(self: *Ast2Ir, def_id: usize, val: usize) {
|
|||||||
match Map.put(&self.def2ir_map, &def_id as *u8, &val as *u8) { .Ok => {}, .Err(e) => {} }
|
match Map.put(&self.def2ir_map, &def_id as *u8, &val as *u8) { .Ok => {}, .Err(e) => {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn glob_get(self: *Ast2Ir, def_id: usize, out: *usize): bool {
|
||||||
|
match Map.get(&self.glob_gidx, &def_id as *u8, out as *u8) {
|
||||||
|
.Found => { ret true; },
|
||||||
|
.NotFound => { ret false; },
|
||||||
|
}
|
||||||
|
ret false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn glob_put(self: *Ast2Ir, def_id: usize, val: usize) {
|
||||||
|
match Map.put(&self.glob_gidx, &def_id as *u8, &val as *u8) { .Ok => {}, .Err(e) => {} }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顶层 var/const → 模块全局(alloc 于静态存储;引用是 *T 指针常量)。
|
||||||
|
fn emit_global_var(self: *Ast2Ir, d: *Node) {
|
||||||
|
if d == null || d.resolved_def_id == 0 { ret; }
|
||||||
|
var def_id = d.resolved_def_id;
|
||||||
|
var existing: usize = 0;
|
||||||
|
if glob_get(self, def_id, &existing) { ret; }
|
||||||
|
var lang = LangType.def_lang_id(&self.sema.types, def_id);
|
||||||
|
var ir = to_ir_type(self, lang);
|
||||||
|
if ir == 0 { ret; }
|
||||||
|
var init_ref: usize = 0;
|
||||||
|
match d.body {
|
||||||
|
.VarDecl(x) => { init_ref = x.init_expr; },
|
||||||
|
.ConstDecl(x) => { init_ref = x.init_expr; },
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
var zero = true;
|
||||||
|
var bytes = Vec.blank(1, 1);
|
||||||
|
if init_ref != 0 {
|
||||||
|
var inode = Ast.node(self.ast, init_ref);
|
||||||
|
if inode != null {
|
||||||
|
match inode.body {
|
||||||
|
.ExprIntLit(bi) => {
|
||||||
|
var v: u64 = 0;
|
||||||
|
match BigInt.to_u64(&v, bi.value) { .Ok => {}, .Err(e) => {} }
|
||||||
|
var bits = LangType.int_bits(&self.sema.types, ir);
|
||||||
|
var size = bits / 8;
|
||||||
|
if size != 0 {
|
||||||
|
zero = false;
|
||||||
|
var i: usize = 0;
|
||||||
|
while i < size {
|
||||||
|
var b = ((v >> (8 * i)) & 255) as u8;
|
||||||
|
match Vec.push(&bytes, &b as *u8) { .Ok => {}, .Err(e) => {} }
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var gidx = IrBuilder.add_global(self.ir, ir, zero, bytes);
|
||||||
|
glob_put(self, def_id, gidx + 1);
|
||||||
|
}
|
||||||
|
|
||||||
fn kind_is(self: *Ast2Ir, lang_id: usize, want: LangKind): bool {
|
fn kind_is(self: *Ast2Ir, lang_id: usize, want: LangKind): bool {
|
||||||
var e = ltn(self, lang_id);
|
var e = ltn(self, lang_id);
|
||||||
if e == null { ret false; }
|
if e == null { ret false; }
|
||||||
@@ -608,6 +665,13 @@ fn emit_lvalue(self: *Ast2Ir, ref: usize): usize {
|
|||||||
.ExprIdent(x) => {
|
.ExprIdent(x) => {
|
||||||
var d = defn(self, n.resolved_def_id);
|
var d = defn(self, n.resolved_def_id);
|
||||||
if d != null && @enum2int(d.kind) == @enum2int(DefKind.Fn) { ret 0; }
|
if d != null && @enum2int(d.kind) == @enum2int(DefKind.Fn) { ret 0; }
|
||||||
|
var g: usize = 0;
|
||||||
|
if glob_get(self, n.resolved_def_id, &g) {
|
||||||
|
var lang = node_lang(n);
|
||||||
|
var ir = to_ir_type(self, lang);
|
||||||
|
var ptr_ir = TypeTable.make_ptr(&self.sema.types.ir, ir);
|
||||||
|
ret IrBuilder.type_const_global(self.ir, ptr_ir, g - 1);
|
||||||
|
}
|
||||||
var slot: usize = 0;
|
var slot: usize = 0;
|
||||||
if !ir_map_get(self, n.resolved_def_id, &slot) { ret 0; }
|
if !ir_map_get(self, n.resolved_def_id, &slot) { ret 0; }
|
||||||
ret slot;
|
ret slot;
|
||||||
@@ -935,6 +999,13 @@ fn emit_ident(self: *Ast2Ir, n: *Node): usize {
|
|||||||
if !ir_map_get(self, n.resolved_def_id, &fn_ref) { ret 0; }
|
if !ir_map_get(self, n.resolved_def_id, &fn_ref) { ret 0; }
|
||||||
ret IrBuilder.type_const_fn(self.ir, node_ir(self, n), fn_ref);
|
ret IrBuilder.type_const_fn(self.ir, node_ir(self, n), fn_ref);
|
||||||
}
|
}
|
||||||
|
var g: usize = 0;
|
||||||
|
if glob_get(self, n.resolved_def_id, &g) {
|
||||||
|
var ir = node_ir(self, n);
|
||||||
|
var ptr_ir = TypeTable.make_ptr(&self.sema.types.ir, ir);
|
||||||
|
var ptr = IrBuilder.type_const_global(self.ir, ptr_ir, g - 1);
|
||||||
|
ret IrBuilder.mem_load(self.ir, ir, ptr);
|
||||||
|
}
|
||||||
var slot: usize = 0;
|
var slot: usize = 0;
|
||||||
if !ir_map_get(self, n.resolved_def_id, &slot) { ret 0; }
|
if !ir_map_get(self, n.resolved_def_id, &slot) { ret 0; }
|
||||||
ret IrBuilder.mem_load(self.ir, node_ir(self, n), slot);
|
ret IrBuilder.mem_load(self.ir, node_ir(self, n), slot);
|
||||||
@@ -1961,6 +2032,8 @@ fn emit_container_reserve(self: *Ast2Ir, ref: usize) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.TopLevelBlock(x) => { emit_container_reserve(self, dref); },
|
.TopLevelBlock(x) => { emit_container_reserve(self, dref); },
|
||||||
|
.VarDecl(_) => { emit_global_var(self, d); },
|
||||||
|
.ConstDecl(_) => { emit_global_var(self, d); },
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1997,6 +2070,8 @@ fn emit_container_body(self: *Ast2Ir, ref: usize) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.TopLevelBlock(x) => { emit_container_body(self, dref); },
|
.TopLevelBlock(x) => { emit_container_body(self, dref); },
|
||||||
|
.VarDecl(_) => {},
|
||||||
|
.ConstDecl(_) => {},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2070,6 +2145,8 @@ fn new(builder: *IrBuilder, sema: *Sema, diag: *Diag): Ast2Ir {
|
|||||||
.ast = sema.ast,
|
.ast = sema.ast,
|
||||||
.def2ir_map = Map.new(@sizeof(usize), @alignof(usize), @sizeof(usize), @alignof(usize),
|
.def2ir_map = Map.new(@sizeof(usize), @alignof(usize), @sizeof(usize), @alignof(usize),
|
||||||
hash_kind_usize()),
|
hash_kind_usize()),
|
||||||
|
.glob_gidx = Map.new(@sizeof(usize), @alignof(usize), @sizeof(usize), @alignof(usize),
|
||||||
|
hash_kind_usize()),
|
||||||
.loop_stack = Vec.blank(@sizeof(LoopCtx), @alignof(LoopCtx)),
|
.loop_stack = Vec.blank(@sizeof(LoopCtx), @alignof(LoopCtx)),
|
||||||
.defer_stack = Vec.blank(@sizeof(usize), @alignof(usize)),
|
.defer_stack = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||||
.cur_ret_lang = 0,
|
.cur_ret_lang = 0,
|
||||||
@@ -2089,6 +2166,7 @@ fn drop(self: *Ast2Ir) {
|
|||||||
Vec.drop(&self.loop_stack);
|
Vec.drop(&self.loop_stack);
|
||||||
Vec.drop(&self.defer_stack);
|
Vec.drop(&self.defer_stack);
|
||||||
Map.drop(&self.def2ir_map);
|
Map.drop(&self.def2ir_map);
|
||||||
|
Map.drop(&self.glob_gidx);
|
||||||
var k: usize = 0;
|
var k: usize = 0;
|
||||||
while k < self.owned.len {
|
while k < self.owned.len {
|
||||||
match Vec.get(&self.owned, k) {
|
match Vec.get(&self.owned, k) {
|
||||||
|
|||||||
+37
-1
@@ -82,6 +82,14 @@ type ConstLit = enum {
|
|||||||
Float: f64,
|
Float: f64,
|
||||||
Char: u8,
|
Char: u8,
|
||||||
Fn: usize,
|
Fn: usize,
|
||||||
|
Global: usize, // 全局变量下标(Ir.globals);值 = 指向该全局的 *T
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模块级全局变量:编译期分配的静态存储,引用时是一个 *T 指针常量(同 LLVM GlobalVariable)。
|
||||||
|
type Global = struct {
|
||||||
|
tid: usize,
|
||||||
|
zero: bool, // true = 零初始化(BSS,不携带字节)
|
||||||
|
data: Vec, // u8 初始字节(zero=false 时使用)
|
||||||
}
|
}
|
||||||
|
|
||||||
type IrBody = enum {
|
type IrBody = enum {
|
||||||
@@ -128,6 +136,7 @@ type IrFunc = struct {
|
|||||||
type Ir = @self;
|
type Ir = @self;
|
||||||
|
|
||||||
funcs: Vec, // IrFunc
|
funcs: Vec, // IrFunc
|
||||||
|
globals: Vec, // Global
|
||||||
|
|
||||||
// ---- 生命周期 ----
|
// ---- 生命周期 ----
|
||||||
|
|
||||||
@@ -141,7 +150,8 @@ fn new_entry(): IrEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new(): Ir {
|
fn new(): Ir {
|
||||||
var ir = Ir { .funcs = Vec.blank(@sizeof(IrFunc), @alignof(IrFunc)) };
|
var ir = Ir { .funcs = Vec.blank(@sizeof(IrFunc), @alignof(IrFunc)),
|
||||||
|
.globals = Vec.blank(@sizeof(Global), @alignof(Global)) };
|
||||||
var sentinel = IrFunc {
|
var sentinel = IrFunc {
|
||||||
.name = ByteView.empty(),
|
.name = ByteView.empty(),
|
||||||
.attrs = Vec.blank(@sizeof(usize), @alignof(usize)),
|
.attrs = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||||
@@ -180,6 +190,25 @@ fn drop(self: *Ir) {
|
|||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
Vec.drop(&self.funcs);
|
Vec.drop(&self.funcs);
|
||||||
|
var g: usize = 0;
|
||||||
|
while g < self.globals.len {
|
||||||
|
var ge = (self.globals.data as *Global) + g;
|
||||||
|
Vec.drop(&ge.data);
|
||||||
|
g = g + 1;
|
||||||
|
}
|
||||||
|
Vec.drop(&self.globals);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 追加一个全局变量;返回其下标(gidx)。bytes 的所有权转移进表。
|
||||||
|
fn add_global(self: *Ir, tid: usize, zero: bool, bytes: Vec): usize {
|
||||||
|
var g = Global { .tid = tid, .zero = zero, .data = bytes };
|
||||||
|
match Vec.push(&self.globals, &g as *u8) { .Ok => {}, .Err(error) => { ret 0; } }
|
||||||
|
ret self.globals.len - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn global_at(self: *Ir, gidx: usize): *Global {
|
||||||
|
if gidx >= self.globals.len { ret null; }
|
||||||
|
ret (self.globals.data as *Global) + gidx;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_fn(self: *Ir): usize {
|
fn alloc_fn(self: *Ir): usize {
|
||||||
@@ -394,6 +423,13 @@ fn dump_body(out: *ByteBuffer, ty: *TypeTable, e: *IrEntry): bool {
|
|||||||
},
|
},
|
||||||
.Const(c) => {
|
.Const(c) => {
|
||||||
if !dump_type(out, ty, c.tid) { ret false; }
|
if !dump_type(out, ty, c.tid) { ret false; }
|
||||||
|
match c.lit {
|
||||||
|
.Global(g) => {
|
||||||
|
if !out_str(out, " global#") { ret false; }
|
||||||
|
ret out_u(out, g);
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
var te = TypeTable.node(ty, c.tid);
|
var te = TypeTable.node(ty, c.tid);
|
||||||
var is_float = false;
|
var is_float = false;
|
||||||
var is_fn = false;
|
var is_fn = false;
|
||||||
|
|||||||
+57
-1
@@ -18,7 +18,7 @@ type .{ .Layout } = @import("./layout.spl");
|
|||||||
type AttrM = @import("./attr.spl");
|
type AttrM = @import("./attr.spl");
|
||||||
type .{ .Attr } = @import("./attr.spl");
|
type .{ .Attr } = @import("./attr.spl");
|
||||||
type IrM = @import("./ir.spl");
|
type IrM = @import("./ir.spl");
|
||||||
type .{ .Ir, .IrFunc, .IrEntry } = @import("./ir.spl");
|
type .{ .Ir, .IrFunc, .IrEntry, .Global } = @import("./ir.spl");
|
||||||
|
|
||||||
type Ir2C = @self;
|
type Ir2C = @self;
|
||||||
|
|
||||||
@@ -570,6 +570,7 @@ fn emit_const(self: *Ir2C, ref: usize, n: *IrEntry) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.Ptr(v) => { put(self, "(void*)"); put_u(self, v); },
|
.Ptr(v) => { put(self, "(void*)"); put_u(self, v); },
|
||||||
|
.Global(g) => { put(self, "(void*)spl_g_"); put_u(self, g); },
|
||||||
.Float(v) => { put(self, "0.0"); },
|
.Float(v) => { put(self, "0.0"); },
|
||||||
.Fn(fid) => { put(self, "(void*)&"); c_fn_name(self, fid); },
|
.Fn(fid) => { put(self, "(void*)&"); c_fn_name(self, fid); },
|
||||||
}
|
}
|
||||||
@@ -1240,6 +1241,32 @@ fn find_main(self: *Ir2C): usize {
|
|||||||
fn run(self: *Ir2C) {
|
fn run(self: *Ir2C) {
|
||||||
put(self, "#include \"spl_rt.h\"\n");
|
put(self, "#include \"spl_rt.h\"\n");
|
||||||
|
|
||||||
|
// 模块级全局变量(Zero → BSS:无初始化式)。
|
||||||
|
var gi: usize = 0;
|
||||||
|
while gi < self.ir.globals.len {
|
||||||
|
var g = (self.ir.globals.data as *Global) + gi;
|
||||||
|
var sz = type_size(self, g.tid);
|
||||||
|
if sz == 0 { sz = 1; }
|
||||||
|
put(self, "static unsigned char spl_g_");
|
||||||
|
put_u(self, gi);
|
||||||
|
put(self, "[");
|
||||||
|
put_u(self, sz);
|
||||||
|
put(self, "]");
|
||||||
|
if !g.zero {
|
||||||
|
put(self, " = {");
|
||||||
|
var k: usize = 0;
|
||||||
|
while k < g.data.len {
|
||||||
|
if k > 0 { put(self, ", "); }
|
||||||
|
put_u(self, ((g.data.data as *u8) + k).* as usize);
|
||||||
|
k = k + 1;
|
||||||
|
}
|
||||||
|
if g.data.len == 0 { put(self, "0"); }
|
||||||
|
put(self, "}");
|
||||||
|
}
|
||||||
|
put(self, ";\n");
|
||||||
|
gi = gi + 1;
|
||||||
|
}
|
||||||
|
|
||||||
var i: usize = 1;
|
var i: usize = 1;
|
||||||
while i < self.ir.funcs.len {
|
while i < self.ir.funcs.len {
|
||||||
var f = IrM.func(self.ir, i);
|
var f = IrM.func(self.ir, i);
|
||||||
@@ -1256,6 +1283,7 @@ fn run(self: *Ir2C) {
|
|||||||
}
|
}
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
|
emit_ct_table(self);
|
||||||
var m = find_main(self);
|
var m = find_main(self);
|
||||||
if m != 0 {
|
if m != 0 {
|
||||||
var f = IrM.func(self.ir, m);
|
var f = IrM.func(self.ir, m);
|
||||||
@@ -1283,6 +1311,34 @@ fn run(self: *Ir2C) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compiler function(`ct_` 前缀)回调表:供 native 端 vm_set_foreign 按名分发。
|
||||||
|
fn emit_ct_table(self: *Ir2C) {
|
||||||
|
put(self, "static const struct { const char *name; void *fn; } spl_ct_tab[] = {\n");
|
||||||
|
var n: usize = 0;
|
||||||
|
var i: usize = 1;
|
||||||
|
while i < self.ir.funcs.len {
|
||||||
|
var f = IrM.func(self.ir, i);
|
||||||
|
if f != null && !fn_is_extern(self, f) &&
|
||||||
|
ByteView.starts_with(f.name, ByteView.from_slice("ct_")) {
|
||||||
|
put(self, " {\"");
|
||||||
|
put_bv(self, f.name);
|
||||||
|
put(self, "\", (void*)f");
|
||||||
|
put_u(self, i);
|
||||||
|
put(self, "},\n");
|
||||||
|
n = n + 1;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
if n == 0 { put(self, " {0, 0},\n"); }
|
||||||
|
put(self, "};\n");
|
||||||
|
put(self, "void *spl_ct_lookup(const char *name) {\n");
|
||||||
|
put(self, " unsigned long i;\n");
|
||||||
|
put(self, " for (i = 0; i < sizeof(spl_ct_tab)/sizeof(spl_ct_tab[0]); i++)\n");
|
||||||
|
put(self, " if (spl_ct_tab[i].name && strcmp(spl_ct_tab[i].name, name) == 0) return spl_ct_tab[i].fn;\n");
|
||||||
|
put(self, " return 0;\n");
|
||||||
|
put(self, "}\n");
|
||||||
|
}
|
||||||
|
|
||||||
fn new(ir: *Ir, ty: *TypeTable, layout: *Layout, attr: *Attr, out: *ByteBuffer): Ir2C {
|
fn new(ir: *Ir, ty: *TypeTable, layout: *Layout, attr: *Attr, out: *ByteBuffer): Ir2C {
|
||||||
ret Ir2C {
|
ret Ir2C {
|
||||||
.ir = ir,
|
.ir = ir,
|
||||||
|
|||||||
+37
-1
@@ -16,7 +16,7 @@ type .{ .Layout } = @import("./layout.spl");
|
|||||||
type AttrM = @import("./attr.spl");
|
type AttrM = @import("./attr.spl");
|
||||||
type .{ .Attr, .AttrArg } = @import("./attr.spl");
|
type .{ .Attr, .AttrArg } = @import("./attr.spl");
|
||||||
type IrM = @import("./ir.spl");
|
type IrM = @import("./ir.spl");
|
||||||
type .{ .Ir, .IrFunc, .IrEntry } = @import("./ir.spl");
|
type .{ .Ir, .IrFunc, .IrEntry, .Global } = @import("./ir.spl");
|
||||||
type Mcode = @import("./mcode.spl");
|
type Mcode = @import("./mcode.spl");
|
||||||
type .{ .Prog, .Op, .VmKind, .VmIns, .VmFunc } = @import("./mcode.spl");
|
type .{ .Prog, .Op, .VmKind, .VmIns, .VmFunc } = @import("./mcode.spl");
|
||||||
type Mem = @import("sys/mem.spl");
|
type Mem = @import("sys/mem.spl");
|
||||||
@@ -28,6 +28,7 @@ ty: *TypeTable,
|
|||||||
attr: *Attr,
|
attr: *Attr,
|
||||||
layout: *Layout,
|
layout: *Layout,
|
||||||
extern_native: Vec, // isize
|
extern_native: Vec, // isize
|
||||||
|
global_gaddr: Vec, // usize:每个全局变量在 prog.gdata 的下标
|
||||||
prog: Prog,
|
prog: Prog,
|
||||||
|
|
||||||
// ---- 类型/布局查询 ----
|
// ---- 类型/布局查询 ----
|
||||||
@@ -589,6 +590,23 @@ fn cg_const(cg: *Cgen, ref: usize, n: *IrEntry) {
|
|||||||
var te = TypeTable.node(ctx.ty, tid);
|
var te = TypeTable.node(ctx.ty, tid);
|
||||||
if te == null { ret; }
|
if te == null { ret; }
|
||||||
|
|
||||||
|
// 全局变量引用:Const{ lit = Global(gidx) } → GADDR(该全局的运行时地址)。
|
||||||
|
match n.body {
|
||||||
|
.Const(c) => {
|
||||||
|
match c.lit {
|
||||||
|
.Global(g) => {
|
||||||
|
var di: usize = 0;
|
||||||
|
if g < ctx.global_gaddr.len { di = (ctx.global_gaddr.data as *usize)[g]; }
|
||||||
|
emit(cg, .Gaddr, .Void, di);
|
||||||
|
cg_store_slot(cg, ref, .Ptr);
|
||||||
|
ret;
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
|
||||||
match te.node {
|
match te.node {
|
||||||
.Fn(f) => {
|
.Fn(f) => {
|
||||||
var cref = const_fn(n);
|
var cref = const_fn(n);
|
||||||
@@ -1213,6 +1231,7 @@ fn init(ctx: *Ir2Vm, ir: *Ir, ty: *TypeTable, layout: *Layout, attr: *Attr) {
|
|||||||
ctx.attr = attr;
|
ctx.attr = attr;
|
||||||
ctx.layout = layout;
|
ctx.layout = layout;
|
||||||
ctx.extern_native = Vec.blank(@sizeof(isize), @alignof(isize));
|
ctx.extern_native = Vec.blank(@sizeof(isize), @alignof(isize));
|
||||||
|
ctx.global_gaddr = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||||
|
|
||||||
match Mcode.new() {
|
match Mcode.new() {
|
||||||
.Ok(v) => { ctx.prog = v; },
|
.Ok(v) => { ctx.prog = v; },
|
||||||
@@ -1223,6 +1242,7 @@ fn init(ctx: *Ir2Vm, ir: *Ir, ty: *TypeTable, layout: *Layout, attr: *Attr) {
|
|||||||
fn drop(ctx: *Ir2Vm) {
|
fn drop(ctx: *Ir2Vm) {
|
||||||
Mcode.drop(&ctx.prog);
|
Mcode.drop(&ctx.prog);
|
||||||
Vec.drop(&ctx.extern_native);
|
Vec.drop(&ctx.extern_native);
|
||||||
|
Vec.drop(&ctx.global_gaddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_func(ctx: *Ir2Vm, ref: usize): i32 {
|
fn run_func(ctx: *Ir2Vm, ref: usize): i32 {
|
||||||
@@ -1253,6 +1273,22 @@ fn run(ctx: *Ir2Vm): i32 {
|
|||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 登记全局变量到 prog.gdata(Zero→零填/BSS,Bytes→拷贝),记下运行时下标。
|
||||||
|
var gi: usize = 0;
|
||||||
|
while gi < ctx.ir.globals.len {
|
||||||
|
var g = (ctx.ir.globals.data as *Global) + gi;
|
||||||
|
var gsize = ir_type_size(ctx, g.tid);
|
||||||
|
var didx: usize = 0;
|
||||||
|
if g.zero {
|
||||||
|
didx = Mcode.add_zero_data(&ctx.prog, gsize);
|
||||||
|
} else {
|
||||||
|
didx = Mcode.add_data(&ctx.prog, g.data.data as usize, gsize);
|
||||||
|
}
|
||||||
|
var dv = didx;
|
||||||
|
match Vec.push(&ctx.global_gaddr, &dv as *u8) { .Ok => {}, .Err(error) => {} }
|
||||||
|
gi = gi + 1;
|
||||||
|
}
|
||||||
|
|
||||||
// 哨兵:保证真实函数入口地址非零。
|
// 哨兵:保证真实函数入口地址非零。
|
||||||
ignore_usize(Mcode.add_instr(&ctx.prog, .Halt, .Void, 0));
|
ignore_usize(Mcode.add_instr(&ctx.prog, .Halt, .Void, 0));
|
||||||
|
|
||||||
|
|||||||
@@ -285,6 +285,12 @@ fn type_const_float(self: *IrBuilder, tid: usize, val: f64): usize { ret const_n
|
|||||||
fn type_const_char(self: *IrBuilder, tid: usize, val: u8): usize { ret const_node(self, tid, .Char(val)); }
|
fn type_const_char(self: *IrBuilder, tid: usize, val: u8): usize { ret const_node(self, tid, .Char(val)); }
|
||||||
fn type_const_fn(self: *IrBuilder, tid: usize, val: usize): usize { ret const_node(self, tid, .Fn(val)); }
|
fn type_const_fn(self: *IrBuilder, tid: usize, val: usize): usize { ret const_node(self, tid, .Fn(val)); }
|
||||||
fn type_const_bytes(self: *IrBuilder, tid: usize, data: usize): usize { ret const_node(self, tid, .Ptr(data)); }
|
fn type_const_bytes(self: *IrBuilder, tid: usize, data: usize): usize { ret const_node(self, tid, .Ptr(data)); }
|
||||||
|
fn type_const_global(self: *IrBuilder, tid: usize, gidx: usize): usize { ret const_node(self, tid, .Global(gidx)); }
|
||||||
|
|
||||||
|
// 追加模块级全局变量;返回 gidx。bytes 所有权转移进 Ir。
|
||||||
|
fn add_global(self: *IrBuilder, tid: usize, zero: bool, bytes: Vec): usize {
|
||||||
|
ret Ir.add_global(&self.ir, tid, zero, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- type ops ----
|
// ---- type ops ----
|
||||||
|
|
||||||
|
|||||||
@@ -314,6 +314,26 @@ fn add_data(self: *Prog, ptr: usize, size: usize): usize {
|
|||||||
ret self.gdata.len - 1;
|
ret self.gdata.len - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 追加一条 size 字节的零初始化全局数据(BSS 语义);返回其索引。
|
||||||
|
fn add_zero_data(self: *Prog, size: usize): usize {
|
||||||
|
var entry = ByteBuffer.new();
|
||||||
|
match ByteBuffer.reserve(&entry, size) {
|
||||||
|
.Ok => {},
|
||||||
|
.Err(error) => { ByteBuffer.drop(&entry); ret 0; },
|
||||||
|
}
|
||||||
|
if size != 0 {
|
||||||
|
entry.storage.len = size;
|
||||||
|
var p = entry.storage.data as *u8;
|
||||||
|
var i: usize = 0;
|
||||||
|
while i < size { p[i] = 0; i = i + 1; }
|
||||||
|
}
|
||||||
|
match Vec.push(&self.gdata, &entry as *u8) {
|
||||||
|
.Ok => {},
|
||||||
|
.Err(error) => { ByteBuffer.drop(&entry); ret 0; },
|
||||||
|
}
|
||||||
|
ret self.gdata.len - 1;
|
||||||
|
}
|
||||||
|
|
||||||
// 返回 name 对应的 native 索引;不存在则登记。name 为空指针时返回 -1。
|
// 返回 name 对应的 native 索引;不存在则登记。name 为空指针时返回 -1。
|
||||||
fn get_or_add_native(self: *Prog, name: ByteView): isize {
|
fn get_or_add_native(self: *Prog, name: ByteView): isize {
|
||||||
if name.ptr == null { ret -1; }
|
if name.ptr == null { ret -1; }
|
||||||
|
|||||||
+38
-1
@@ -282,6 +282,7 @@ fn can_start_expr(kind: TokenKind): bool {
|
|||||||
.For => { ret true; },
|
.For => { ret true; },
|
||||||
.Match => { ret true; },
|
.Match => { ret true; },
|
||||||
.Comptime => { ret true; },
|
.Comptime => { ret true; },
|
||||||
|
.Try => { ret true; },
|
||||||
_ => { ret false; },
|
_ => { ret false; },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -321,6 +322,7 @@ fn can_start_statement(kind: TokenKind): bool {
|
|||||||
.Break => { ret true; },
|
.Break => { ret true; },
|
||||||
.Continue => { ret true; },
|
.Continue => { ret true; },
|
||||||
.Defer => { ret true; },
|
.Defer => { ret true; },
|
||||||
|
.Errdefer => { ret true; },
|
||||||
.Var => { ret true; },
|
.Var => { ret true; },
|
||||||
.Const => { ret true; },
|
.Const => { ret true; },
|
||||||
.Type => { ret true; },
|
.Type => { ret true; },
|
||||||
@@ -1237,7 +1239,19 @@ fn parse_expr_prec(self: *Parser, min_prec: i32): usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_expr(self: *Parser): usize {
|
fn parse_expr(self: *Parser): usize {
|
||||||
ret parse_expr_prec(self, 1);
|
var e = parse_expr_prec(self, 1);
|
||||||
|
// `expr catch <pattern> <block>`:catch 用解包语法绑定错误。
|
||||||
|
while !self.failed {
|
||||||
|
var t = peek(self);
|
||||||
|
if t == null { break; }
|
||||||
|
if !kind_is_keyword(t.kind, .Catch) { break; }
|
||||||
|
advance(self);
|
||||||
|
var pat = parse_pattern(self);
|
||||||
|
if pat == 0 { ret e; }
|
||||||
|
var handler = parse_block(self);
|
||||||
|
e = new_node(self, .ExprCatch(.{ .value = e, .pattern = pat, .handler = handler }), t);
|
||||||
|
}
|
||||||
|
ret e;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_cast(self: *Parser): usize {
|
fn parse_cast(self: *Parser): usize {
|
||||||
@@ -1444,6 +1458,12 @@ fn parse_primary(self: *Parser): usize {
|
|||||||
if @enum2int(k) == @enum2int(Keyword.Loop) { ret parse_loop_expr(self); }
|
if @enum2int(k) == @enum2int(Keyword.Loop) { ret parse_loop_expr(self); }
|
||||||
if @enum2int(k) == @enum2int(Keyword.For) { ret parse_for_expr(self); }
|
if @enum2int(k) == @enum2int(Keyword.For) { ret parse_for_expr(self); }
|
||||||
if @enum2int(k) == @enum2int(Keyword.Match) { ret parse_match_expr(self); }
|
if @enum2int(k) == @enum2int(Keyword.Match) { ret parse_match_expr(self); }
|
||||||
|
// `try <expr>`:错误上抛前缀。
|
||||||
|
if @enum2int(k) == @enum2int(Keyword.Try) {
|
||||||
|
advance(self);
|
||||||
|
var tv = parse_expr(self);
|
||||||
|
ret new_node(self, .ExprTry(.{ .value = tv }), t);
|
||||||
|
}
|
||||||
// `comptime <expr>`:编译期表达式(可调用普通函数、返回类型等)。
|
// `comptime <expr>`:编译期表达式(可调用普通函数、返回类型等)。
|
||||||
if @enum2int(k) == @enum2int(Keyword.Comptime) {
|
if @enum2int(k) == @enum2int(Keyword.Comptime) {
|
||||||
advance(self);
|
advance(self);
|
||||||
@@ -1958,6 +1978,22 @@ fn parse_defer_stmt(self: *Parser): usize {
|
|||||||
ret new_node(self, .StatementDefer(.{ .body = body }), kw);
|
ret new_node(self, .StatementDefer(.{ .body = body }), kw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_errdefer_stmt(self: *Parser): usize {
|
||||||
|
var kw = peek(self);
|
||||||
|
if kw == null || !kind_is_keyword(kw.kind, .Errdefer) { ret 0; }
|
||||||
|
advance(self);
|
||||||
|
var body: usize = 0;
|
||||||
|
var t = peek(self);
|
||||||
|
if t != null && kind_is_punct(t.kind, .LBrace) { body = parse_block(self); }
|
||||||
|
else if t != null && can_start_statement(t.kind) { body = parse_statement(self); }
|
||||||
|
else {
|
||||||
|
body = parse_expr(self);
|
||||||
|
t = peek(self);
|
||||||
|
if t != null && kind_is_punct(t.kind, .Semicolon) { advance(self); }
|
||||||
|
}
|
||||||
|
ret new_node(self, .StatementErrdefer(.{ .body = body }), kw);
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_comptime_stmt(self: *Parser): usize {
|
fn parse_comptime_stmt(self: *Parser): usize {
|
||||||
var kw = peek(self);
|
var kw = peek(self);
|
||||||
if kw == null || !kind_is_keyword(kw.kind, .Comptime) { ret 0; }
|
if kw == null || !kind_is_keyword(kw.kind, .Comptime) { ret 0; }
|
||||||
@@ -1979,6 +2015,7 @@ fn parse_statement(self: *Parser): usize {
|
|||||||
if @enum2int(k) == @enum2int(Keyword.Break) { ret parse_break_stmt(self); }
|
if @enum2int(k) == @enum2int(Keyword.Break) { ret parse_break_stmt(self); }
|
||||||
if @enum2int(k) == @enum2int(Keyword.Continue) { ret parse_continue_stmt(self); }
|
if @enum2int(k) == @enum2int(Keyword.Continue) { ret parse_continue_stmt(self); }
|
||||||
if @enum2int(k) == @enum2int(Keyword.Defer) { ret parse_defer_stmt(self); }
|
if @enum2int(k) == @enum2int(Keyword.Defer) { ret parse_defer_stmt(self); }
|
||||||
|
if @enum2int(k) == @enum2int(Keyword.Errdefer) { ret parse_errdefer_stmt(self); }
|
||||||
if @enum2int(k) == @enum2int(Keyword.Comptime) { ret parse_comptime_stmt(self); }
|
if @enum2int(k) == @enum2int(Keyword.Comptime) { ret parse_comptime_stmt(self); }
|
||||||
if @enum2int(k) == @enum2int(Keyword.Var) ||
|
if @enum2int(k) == @enum2int(Keyword.Var) ||
|
||||||
@enum2int(k) == @enum2int(Keyword.Const) ||
|
@enum2int(k) == @enum2int(Keyword.Const) ||
|
||||||
|
|||||||
Reference in new issue
Block a user