From 97f63d05d60d12078592a8f0e6685c400b8e3e40 Mon Sep 17 00:00:00 2001 From: "peng.li24" <734991033@qq.com> Date: Sat, 12 Sep 2026 22:06:36 +0800 Subject: [PATCH 1/3] =?UTF-8?q?layout+runtime:=20`*`=20=E6=98=AF=E5=94=AF?= =?UTF-8?q?=E4=B8=80=E8=A7=A3=E5=BC=95=E7=94=A8=E8=B7=AF=E5=BE=84=EF=BC=88?= =?UTF-8?q?#305=20=E8=A3=81=E5=86=B3=E8=90=BD=E5=9C=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `[]` 与 `·[…]` 都不对指针隐式解引用,先用显式 `*p` 取回目标: - layout:`p[i]`(p:*[]T)报 TypeError(ptr_index);`·[…]` 的 base 须为容器或路径串,compact 数组/标量报 TypeError(成员访问判定, ast/lower/parser/symbol 配套) - runtime:rwir_kv 成员访问要求容器(struct/map/index/extindex 或 []char/* 路径串),compact 数组报 TypeError 而非静默脏值 - spec:01-词法/09 优先级、03/15 成员访问、03/16 ptr(`*` 唯一解引用 路径,读写同规则)、04/03 函数(局部指针与形参槽同一约定)、 05/03 成员访问(base 形态判定)、附录/05 表达式 - 新增 error_cases:array_member / ptr_index / ptr_member - 新增 tutorial 10-types/09-deref Closes #305 Co-Authored-By: Claude Code --- error_cases/type_error/array_member.kv | 8 ++ error_cases/type_error/ptr_index.kv | 9 ++ error_cases/type_error/ptr_member.kv | 9 ++ layout/src/ast.rs | 13 +++ layout/src/lower.rs | 82 ++++++++++++----- layout/src/parser.rs | 87 ++++++++++++++++++- layout/src/symbol.rs | 2 +- runtime/src/rwir_kv.c | 59 ++++++++++--- runtime/src/rwir_xvalue.c | 34 ++++++-- ...16\344\274\230\345\205\210\347\272\247.kv" | 4 +- ...20\345\221\230\350\256\277\351\227\256.kv" | 5 ++ .../16-ptr.kv" | 8 ++ .../03-\345\207\275\346\225\260.kv" | 5 ++ ...20\345\221\230\350\256\277\351\227\256.kv" | 9 ++ ...5-\350\241\250\350\276\276\345\274\217.kv" | 2 +- tutorial/10-types/09-deref.kv | 20 +++++ 16 files changed, 306 insertions(+), 50 deletions(-) create mode 100644 error_cases/type_error/array_member.kv create mode 100644 error_cases/type_error/ptr_index.kv create mode 100644 error_cases/type_error/ptr_member.kv create mode 100644 tutorial/10-types/09-deref.kv diff --git a/error_cases/type_error/array_member.kv b/error_cases/type_error/array_member.kv new file mode 100644 index 00000000..2cd4b73f --- /dev/null +++ b/error_cases/type_error/array_member.kv @@ -0,0 +1,8 @@ +// expected: +// error: TypeError: kvspace·get: member access requires a container (struct/map), got [3]int64; compact arrays use [] indexing + +rwfunc test() -> () { + list:[]int64 = [11, 22, 33] + list·[0] -> q + println("q=", q) +} diff --git a/error_cases/type_error/ptr_index.kv b/error_cases/type_error/ptr_index.kv new file mode 100644 index 00000000..39578077 --- /dev/null +++ b/error_cases/type_error/ptr_index.kv @@ -0,0 +1,9 @@ +// expected: +// error: 0:0: `[]` 下标不能用于指针 `p`(类型 *[]int64);先解引用 `*p` 再下标 + +rwfunc test() -> () { + list:[]int64 = [11, 22, 33] + &list -> p + p[0] -> q + println("q=", q) +} diff --git a/error_cases/type_error/ptr_member.kv b/error_cases/type_error/ptr_member.kv new file mode 100644 index 00000000..874f9171 --- /dev/null +++ b/error_cases/type_error/ptr_member.kv @@ -0,0 +1,9 @@ +// expected: +// error: TypeError: kvspace·get: member access requires a container (struct/map), got [3]int64; compact arrays use [] indexing + +rwfunc test() -> () { + list:[]int64 = [11, 22, 33] + &list -> p + p·[0] -> q + println("q=", q) +} diff --git a/layout/src/ast.rs b/layout/src/ast.rs index 93799c60..7c44e4af 100644 --- a/layout/src/ast.rs +++ b/layout/src/ast.rs @@ -256,6 +256,10 @@ pub struct Expr { pub lit: LitKind, // 字面量类型(仅叶节点有意义) } +/// 一元前缀 `*` 解引用在 AST 里的算子名。落成读参/写槽名时前缀到名字上(`*p`), +/// runtime 按该槽里的 Ptr 解引用到目标——与形参槽 `*[0,±k]` 同一约定(见 spec [[ptr]])。 +pub const DEREF_OP: &str = "*"; + impl Expr { pub fn is_leaf(&self) -> bool { self.op.is_empty() @@ -394,6 +398,15 @@ impl Instruction { } return ("=".to_string(), vec![v.clone()]); } + // 解引用:`*<叶>` → 读参/写槽名 `*name`(与形参槽 `*[0,±k]` 同一约定,runtime 按该槽里的 + // Ptr 解引用到目标)。非叶操作数由 lower 先展开成临时槽,故此处必为叶。 + if e.op == DEREF_OP { + let a = match e.args.first() { + Some(a) if a.is_leaf() => a, + _ => panic!("flat: `*` 解引用要求叶操作数(lower 未展开?)"), + }; + return ("=".to_string(), vec![format!("*{}", a.val)]); + } let opcode = e.op.clone(); let reads: Vec = e .args diff --git a/layout/src/lower.rs b/layout/src/lower.rs index 92d8c0bd..87ddcc30 100644 --- a/layout/src/lower.rs +++ b/layout/src/lower.rs @@ -4,7 +4,7 @@ use std::collections::{HashMap, HashSet}; use super::ast::{self, Expr, Func, Instruction, LitKind, Stmt}; use super::scanner::{Diagnostic, Pos}; -use super::{builtin, keytree, symbol}; +use super::{builtin, keytree, langtype, symbol}; /// 容器类型(stringkeymap / mapexpr)不可用 `[]` 下标访问成员—— /// `[]` 仅限 compact array(shaped langtype,含字符串 `[]char/*`)。 @@ -195,30 +195,45 @@ fn check_map_inst(s: &Instruction, defined: &HashSet, diags: &mut Vec Vec { let tm = infer_types(fn_); + // 地址传参/返回的 `*` 是**传递方式**而非指针值:layout 体内已把它解引用(名字指称实参本体), + // 故 `p[i]`/`p·x` 是对实操本体的下标/成员,不是「指针当数组」。 + let addr: Vec<&str> = fn_ + .sig + .params + .iter() + .chain(fn_.sig.returns.iter()) + .filter(|p| langtype::is_addr_param(&p.ty)) + .map(|p| p.name.as_str()) + .collect(); let mut diags = Vec::new(); - check_subscript_body(&fn_.body, &tm, &mut diags); + check_subscript_body(&fn_.body, &tm, &addr, &mut diags); diags } -fn check_subscript_body(body: &[Stmt], tm: &HashMap, diags: &mut Vec) { +fn check_subscript_body( + body: &[Stmt], + tm: &HashMap, + addr: &[&str], + diags: &mut Vec, +) { for st in body { match st { - Stmt::Instruction(s) => check_subscript_inst(s, tm, diags), - Stmt::Scope(s) => check_subscript_body(&s.body, tm, diags), + Stmt::Instruction(s) => check_subscript_inst(s, tm, addr, diags), + Stmt::Scope(s) => check_subscript_body(&s.body, tm, addr, diags), Stmt::If(s) => { if let Some(c) = &s.cond { - check_subscript_inst(c, tm, diags); + check_subscript_inst(c, tm, addr, diags); } - check_subscript_body(&s.then_, tm, diags); - check_subscript_body(&s.else_, tm, diags); + check_subscript_body(&s.then_, tm, addr, diags); + check_subscript_body(&s.else_, tm, addr, diags); } Stmt::While(s) => { if let Some(c) = &s.cond { - check_subscript_inst(c, tm, diags); + check_subscript_inst(c, tm, addr, diags); } - check_subscript_body(&s.body, tm, diags); + check_subscript_body(&s.body, tm, addr, diags); } - Stmt::For(s) => check_subscript_body(&s.body, tm, diags), + Stmt::For(s) => check_subscript_body(&s.body, tm, addr, diags), _ => {} } } @@ -227,25 +242,42 @@ fn check_subscript_body(body: &[Stmt], tm: &HashMap, diags: &mut fn check_subscript_inst( inst: &Instruction, tm: &HashMap, + addr: &[&str], diags: &mut Vec, ) { if let Some(e) = &inst.expr { - check_subscript_expr(e, tm, diags); + check_subscript_expr(e, tm, addr, diags); } } -fn check_subscript_expr(e: &Expr, tm: &HashMap, diags: &mut Vec) { +fn check_subscript_expr( + e: &Expr, + tm: &HashMap, + addr: &[&str], + diags: &mut Vec, +) { if (e.op == "xv·at" || e.op == "xv·set") && !e.args.is_empty() { let base = &e.args[0]; - if base.is_leaf() { + if base.is_leaf() && !addr.contains(&base.val.as_str()) { if let Some(t) = tm.get(&base.val) { - if is_container_type(t) { + let msg = if is_container_type(t) { + Some(format!( + "`[]` 下标不能用于容器 `{}`(类型 {});容器成员访问用 kvspace·get/kvspace·set 或 `{}·key`,`[]` 仅限 compact array", + base.val, t, base.val + )) + } else if t.starts_with('*') { + // 指针不是数组:`[]` 不对指针隐式解引用(见 spec [[ptr]])。 + Some(format!( + "`[]` 下标不能用于指针 `{}`(类型 {});先解引用 `*{}` 再下标", + base.val, t, base.val + )) + } else { + None + }; + if let Some(message) = msg { diags.push(Diagnostic { pos: Pos { line: 0, col: 0 }, - message: format!( - "`[]` 下标不能用于容器 `{}`(类型 {});容器成员访问用 kvspace·get/kvspace·set 或 `{}·key`,`[]` 仅限 compact array", - base.val, t, base.val - ), + message, warn: false, info: false, source: String::new(), @@ -257,7 +289,7 @@ fn check_subscript_expr(e: &Expr, tm: &HashMap, diags: &mut Vec< } } for a in &e.args { - check_subscript_expr(a, tm, diags); + check_subscript_expr(a, tm, addr, diags); } } @@ -1034,6 +1066,12 @@ fn infer_op_type(opcode: &str, reads: &[String], tm: &mut HashMap`(目标类型未知则记裸 `*`)——`*` 前缀即 ref 轴标记, + // 供 `[]` 下标校验拦下「指针当数组」(见 [[ptr]])。 + let t = reads.first().map(|r| slot_type(r, tm)).unwrap_or_default(); + return format!("*{t}"); + } if is_cast_op(opcode) { return opcode.to_string(); } @@ -1049,8 +1087,8 @@ fn infer_op_type(opcode: &str, reads: &[String], tm: &mut HashMap { + "kvlen" | "ndarray·numel" | "ndarray·dim" | "kvspace·listlen" | "string·len" + | "string·ord" | "string·cmp" | "string·find" | "string·parseint" | "xv·bodylen" => { return "int64".to_string(); } "xv·langtype" => return "[]char/utf8".to_string(), diff --git a/layout/src/parser.rs b/layout/src/parser.rs index 0cb5ee05..7b34bb19 100644 --- a/layout/src/parser.rs +++ b/layout/src/parser.rs @@ -6,6 +6,7 @@ use super::ast::{ self, Expr, Field, Func, FuncSig, Instruction, Param, RwirDecl, Stmt, StructDecl, }; use super::keytree; +use super::langtype; use super::scanner::{scan, Diagnostic, Kind, Pos, Token}; use super::symbol; @@ -23,6 +24,7 @@ pub fn parse_code(src: &str) -> Result<(ast::File, Vec), String> { tokens: scan(src), pos: 0, errors: Vec::new(), + addr_params: Vec::new(), }; let f = p.parse_file(); for d in &mut p.errors { @@ -40,6 +42,9 @@ struct Parser { tokens: Vec, pos: usize, errors: Vec, + // 当前函数体的地址形参名(声明带 `*`/`@`):体内名字已被 layout 解引用一次(即实参值), + // 故体里再写 `*p` 就是两层间接——当前参数模型不支持(见 issue #286),报错不静默降级。 + addr_params: Vec, } impl Parser { @@ -329,10 +334,18 @@ impl Parser { self.check_param_types(&sig); self.check_variadic(&sig); self.check_param_dup(&sig); + self.addr_params = sig + .params + .iter() + .chain(sig.returns.iter()) + .filter(|p| langtype::is_addr_param(&p.ty)) + .map(|p| p.name.clone()) + .collect(); self.skip_newlines_and_comments(); self.expect(Kind::LBrace); let body = self.parse_body(); self.expect(Kind::RBrace); + self.addr_params.clear(); let func = Func { comments: Vec::new(), sig, @@ -1407,6 +1420,14 @@ impl Parser { if symbol::lookup(&t.value).word == "add" { return self.parse_pratt(UNARY_PREC); } + // 一元前缀 * = 解引用:*p 读/写该 Ptr 槽的目标(与形参槽 *[0,±k] 同一读参命名约定, + // 见 spec [[ptr]])。操作数先按 UNARY_PREC 结合(*p·x ≡ *(p·x),同 C),非叶操作数由 + // lower 展开成临时槽后再取 *名——runtime 一律按「读该槽 → 槽里是 Ptr → 落其目标」。 + if symbol::lookup(&t.value).word == "pointer" { + let arg = self.parse_pratt(UNARY_PREC)?; + self.check_deref_operand(&arg, t.pos); + return Some(ast::call(ast::DEREF_OP, vec![arg])); + } // 一元前缀 & = 取址:&x ≡ kvlang·abs(x)(中缀 & 仍为按位与,走 pratt 中缀路径)。 // & 对成员链 kvspace·get(base, segs...) → kvlang·abs(base, segs...):取成员路径地址,非读值取址。 if symbol::lookup(&t.value).word == "bitand" { @@ -1924,8 +1945,72 @@ impl Parser { (writes, wtypes) } + /// 解引用操作数检查:须是名字(Ptr 槽);地址形参名不可再 `*`(体内已解引用一次 = 两层间接)。 + fn check_deref_operand(&mut self, arg: &Expr, pos: Pos) { + if !arg.is_leaf() { + return; + } + let msg = if arg.quote != 0 { + "`*` 解引用要求 Ptr 槽(名字),不能作用于字符串字面量".to_string() + } else if self.addr_params.contains(&arg.val) { + format!( + "`*{}` 是两层间接:地址形参在体内已被解引用一次(名字即实参值);先 `{} -> local` 再 `*local`(见 issue #286)", + arg.val, arg.val + ) + } else { + return; + }; + self.errors.push(Diagnostic { + pos, + message: msg, + warn: false, + info: false, + source: String::new(), + src_file: String::new(), + src_name: String::new(), + }); + } + fn parse_write_slot(&mut self) -> (String, String) { - let name = self.advance().value; + // 解引用写槽 `*p`:写穿该 Ptr 槽的目标(与读参 `*p` 同一约定,见 spec [[ptr]])。 + // 只接**名字**——成员写 `p·x` 自带按指针优先、下标写先 `*p` 解引用到变量,故 `*` 后 + // 跟 `·`/`[` 一律报错,不静默错拼成两个写槽。 + let mut name = String::new(); + if self.peek().kind == Kind::Ident + && self.peek().value == ast::DEREF_OP + && self.peek_at(1).kind == Kind::Ident + { + self.advance(); + name.push_str(ast::DEREF_OP); + } + name.push_str(&self.advance().value); + if name.starts_with(ast::DEREF_OP) && self.addr_params.contains(&name[1..].to_string()) { + self.errors.push(Diagnostic { + pos: self.peek().pos, + message: format!( + "`{}` 是两层间接:地址形参在体内已被解引用一次(名字即实参值);先 `{} -> local` 再写 `*local`(见 issue #286)", + name, &name[1..] + ), + warn: false, + info: false, + source: String::new(), + src_file: String::new(), + src_name: String::new(), + }); + } + if name.starts_with(ast::DEREF_OP) && matches!(self.peek().kind, Kind::Dot | Kind::LBrack) { + self.errors.push(Diagnostic { + pos: self.peek().pos, + message: + "`*` 解引用写槽只接名字(如 `*p`):成员写用 `p·x`,下标写先 `*p` 解引用到变量" + .to_string(), + warn: false, + info: false, + source: String::new(), + src_file: String::new(), + src_name: String::new(), + }); + } let mut typ = String::new(); if self.peek().kind == Kind::Colon { self.advance(); diff --git a/layout/src/symbol.rs b/layout/src/symbol.rs index af842463..2a0b377d 100644 --- a/layout/src/symbol.rs +++ b/layout/src/symbol.rs @@ -144,7 +144,7 @@ static ENTRIES: &[Entry] = &[ precedence: 0, arith: false, cmp: false, - unary: false, + unary: true, // 表达式前缀位置 = 解引用(类型位置仍是 ref 标注,由 parse_type 剥离) }, Entry { word: "mul", diff --git a/runtime/src/rwir_kv.c b/runtime/src/rwir_kv.c index 9057c5e2..6a370e2b 100644 --- a/runtime/src/rwir_kv.c +++ b/runtime/src/rwir_kv.c @@ -42,18 +42,41 @@ static bool base_is_container(const kvlangXvalue_t *base) { strcmp(k, KVSPACE_KIND_EXT_INDEX) == 0; } -static char *member_path(kvlangFrame_t *f, const kvlangXvalue_t *in, int n) { +/* 非容器 base 的统一报错(返回 NULL 供调用方判定失败):带上实际 langtype 便于定位。 */ +static char *member_not_container(const kvlangXvalue_t *v, char *err, + size_t errsz) { + kvspaceHead_t h; + const char *lt = + kvlangXvalueHead(v, &h) == 0 ? (const char *)h.langtype : ""; + snprintf(err, errsz, + "member access requires a container (struct/map), got %s; " + "compact arrays use [] indexing", + lt); + return NULL; +} + +/* 成员访问的 base 解析(spec [[成员访问]] 的「按指针优先、按名回退」+ 形态判定): + * Ptr → 取其目标路径(目标须是容器);容器/None → 取 base 的写槽路径;路径串(char/)→ 其值即父路径; + * 其余(compact 数组、标量)不是容器 → 报错,绝不把 body 字节冒充路径串拼键。 */ +static char *member_path(kvlangFrame_t *f, const kvlangXvalue_t *in, int n, + char *err, size_t errsz) { const kvlangXvalue_t *base = &in[0]; char *bp = NULL; + err[0] = 0; if (kvlangXvalueIsPtr(base)) { - /* 数据 Ptr(&x)作成员 base:直接取其目标路径,逐段下钻。 */ + /* 数据 Ptr(&x)作成员 base:直接取其目标路径,逐段下钻。Ptr 的 head 即目标 kindexpr, + * 故容器判定即对目标判定——`p·val`(目标 struct)通过,`p·[0]`(目标 compact 数组)报错。 */ + if (!base_is_container(base)) + return member_not_container(base, err, errsz); bp = kvlangXvaluePtrTarget(base); } else if (base_is_container(base)) { char *fr = kvlangKeytreeFrameRoot(f->pc); bp = kvlangBuiltinResolveWriteSlot(f->kv, fr, f->inst->reads[0].name); free(fr); - } else { + } else if (kvlangXvalueIsCharKind(kvlangXvalueKind(base))) { bp = kvlangXvalueValueString(base); + } else { + return member_not_container(base, err, errsz); } /* 成员链:base 之后逐段拼 key(变参),每段可为静态字面量或动态键(运行时值)。 */ for (int i = 1; i < n; i++) { @@ -69,11 +92,13 @@ static char *member_path(kvlangFrame_t *f, const kvlangXvalue_t *in, int n) { int kvlangCGet(kvlangFrame_t *f) { kvlangXvalue_t in[MAX_PARAMS]; int n = kvlangBuiltinReadInputs(f, in, MAX_PARAMS); - char *key = f->inst->nr >= 2 ? member_path(f, in, n) + char merr[512]; + char *key = f->inst->nr >= 2 ? member_path(f, in, n, merr, sizeof merr) : (n >= 1 ? path_arg(f, 0, in) : NULL); if (!key) { kvlangBuiltinFreeInputs(in, n); - return kvlangBuiltinSetErr(f, "TypeError: kvspace·get requires a path"); + return kvlangBuiltinSetErr(f, "TypeError: kvspace·get: %s", + merr[0] ? merr : "requires a path"); } kvlangXvalue_t v; kvlangXvalueZero(&v); @@ -109,6 +134,7 @@ int kvlangCSet(kvlangFrame_t *f) { int n = kvlangBuiltinReadInputs(f, in, MAX_PARAMS); char *key; kvlangXvalue_t *val; + char merr[512]; if (f->inst->nr >= 3) { const char *base = f->inst->reads[0].name; char *fr = kvlangKeytreeFrameRoot(f->pc); @@ -122,17 +148,18 @@ int kvlangCSet(kvlangFrame_t *f) { "first (e.g. `%s:T = {}`)", base, base); } - key = member_path(f, in, n - 1); + key = member_path(f, in, n - 1, merr, sizeof merr); val = &in[n - 1]; } else { key = n >= 1 ? path_arg(f, 0, in) : NULL; + merr[0] = 0; val = &in[1]; } if (!key || (f->inst->nr < 3 && n < 2)) { free(key); kvlangBuiltinFreeInputs(in, n); - return kvlangBuiltinSetErr(f, - "TypeError: kvspace·set requires path and value"); + return kvlangBuiltinSetErr(f, "TypeError: kvspace·set: %s", + merr[0] ? merr : "requires path and value"); } kvlangKvPair_t p = {key, *val}; char err[256]; @@ -228,13 +255,15 @@ int kvlangCAbs(kvlangFrame_t *f) { kvlangXvalue_t in[MAX_PARAMS]; int n = kvlangBuiltinReadInputs(f, in, MAX_PARAMS); char *p = NULL; + char merr[512]; if (n >= 2) - p = member_path(f, in, n); + p = member_path(f, in, n, merr, sizeof merr); else if (n >= 1) p = resolve_path_arg(f, 0, in); if (!p) { kvlangBuiltinFreeInputs(in, n); - return kvlangBuiltinSetErr(f, "TypeError: kvlang·abs requires a key"); + return kvlangBuiltinSetErr(f, "TypeError: kvlang·abs: %s", + n >= 2 && merr[0] ? merr : "requires a key"); } /* 产出 Ptr(ref=1):langtype = 目标 kindexpr、body = 目标绝对路径。 * &x ≡ kvlang·abs(x):取址返回指向 x 所在节点的软链接(单跳同型),不再产 char 路径串。 */ @@ -263,8 +292,8 @@ int kvlangCAbs(kvlangFrame_t *f) { int kvlangCList(kvlangFrame_t *f) { if (f->inst->nw == 0) - return kvlangBuiltinSetErr(f, - "TypeError: kvspace·list requires a write param"); + return kvlangBuiltinSetErr( + f, "TypeError: kvspace·list requires a write param"); kvlangXvalue_t in[1]; int n = kvlangBuiltinReadInputs(f, in, 1); char *key = n >= 1 ? path_arg(f, 0, in) : NULL; @@ -281,7 +310,8 @@ int kvlangCList(kvlangFrame_t *f) { } if (!key) { kvlangBuiltinFreeInputs(in, n); - return kvlangBuiltinSetErr(f, "TypeError: kvspace·list requires 1 path arg"); + return kvlangBuiltinSetErr( + f, "TypeError: kvspace·list requires 1 path arg"); } char **names = NULL; int count = 0; @@ -420,7 +450,8 @@ int kvlangCMkindex(kvlangFrame_t *f) { char *key = n >= 1 ? path_arg(f, 0, in) : NULL; if (!key) { kvlangBuiltinFreeInputs(in, n); - return kvlangBuiltinSetErr(f, "TypeError: kvspace·mkindex requires a path"); + return kvlangBuiltinSetErr( + f, "TypeError: kvspace·mkindex requires a path"); } uint32_t capacity = n >= 2 ? (uint32_t)kvlangScalarI64(kvlangXvalueScalar(&in[1])) : 0; diff --git a/runtime/src/rwir_xvalue.c b/runtime/src/rwir_xvalue.c index 7bda0ff0..17deb257 100644 --- a/runtime/src/rwir_xvalue.c +++ b/runtime/src/rwir_xvalue.c @@ -24,7 +24,9 @@ static int xv_elem_size(const kvlangLangtype *kx) { } /* 读参 ri 的 head:变量走 GetHead 只读前缀(不借 body,*key=malloc'd 键),字面量数组借整块 - * 解 head(*key=NULL,*borrow 持整块,调用方 kvlangXvalueFree)。返回 0 成功、非 0 空/不存在。 */ + * 解 head(*key=NULL,*borrow 持整块,调用方 kvlangXvalueFree)。 + * 下标操作数须是 compact 数组**值**本身:Ptr(ref=1)是寻址间接层,不是数组(见 spec [[ptr]])。 + * 返回 0 成功、-1 空/不存在、-2 是 Ptr(下标须先 `*p` 解引用)。 */ static int xv_read_head(kvlangFrame_t *f, const char *fr, int ri, kvspaceHead_t *h, char **key, kvlangXvalue_t *borrow) { kvlangXvalueZero(borrow); @@ -36,6 +38,10 @@ static int xv_read_head(kvlangFrame_t *f, const char *fr, int ri, free(k); return -1; } + if (h->ref == KVSPACE_REF_PTR) { + free(k); + return -2; + } *key = k; return 0; } @@ -43,6 +49,8 @@ static int xv_read_head(kvlangFrame_t *f, const char *fr, int ri, &f->inst->reads[ri].val, borrow); if (kvlangXvalueNone(borrow)) return -1; + if (kvlangXvalueIsPtr(borrow)) + return -2; return kvspaceDecodeHead(borrow->data, borrow->len, h) == 0 ? 0 : -1; } @@ -81,10 +89,13 @@ int kvlangBuiltinXvAt(kvlangFrame_t *f) { kvspaceHead_t h; char *key; kvlangXvalue_t arr; - if (xv_read_head(f, fr, 0, &h, &key, &arr) != 0) { + int hrc = xv_read_head(f, fr, 0, &h, &key, &arr); + if (hrc != 0) { free(fr); - return kvlangBuiltinSetErr(f, - "TypeError: xv.at requires a compact array"); + return kvlangBuiltinSetErr( + f, hrc == -2 ? "TypeError: xv.at: pointer cannot be indexed — " + "dereference it first (*p)" + : "TypeError: xv.at requires a compact array"); } kvlangLangtype kx; kvlangLangtypeParse(h.langtype, &kx); @@ -169,7 +180,10 @@ int kvlangBuiltinXvSet(kvlangFrame_t *f) { ? flat_index(&kx, idx, nidx) : -1; const char *emsg = - sz <= 0 || kx.ndim == 0 + h.ref == KVSPACE_REF_PTR + ? "TypeError: xv.set: pointer cannot be indexed — " + "dereference it first (*p)" + : sz <= 0 || kx.ndim == 0 ? "TypeError: xv.set requires a compact array" : nidx != kx.ndim ? "IndexError: xv.set: dim/index count mismatch" : flat < 0 ? "IndexError: xv.set: index out of bounds" @@ -204,9 +218,12 @@ int kvlangBuiltinXvSet(kvlangFrame_t *f) { kvlangLangtype kx; kvlangLangtypeParse(ah.langtype, &kx); const char *emsg = - sz <= 0 || kx.ndim == 0 ? "TypeError: xv.set requires a compact array" - : nidx != kx.ndim ? "IndexError: xv.set: dim/index count mismatch" - : NULL; + kvlangXvalueIsPtr(&arr) + ? "TypeError: xv.set: pointer cannot be indexed — " + "dereference it first (*p)" + : sz <= 0 || kx.ndim == 0 ? "TypeError: xv.set requires a compact array" + : nidx != kx.ndim ? "IndexError: xv.set: dim/index count mismatch" + : NULL; int64_t flat = emsg ? -1 : flat_index(&kx, idx, nidx); if (!emsg && flat < 0) emsg = "IndexError: xv.set: index out of bounds"; @@ -351,4 +368,3 @@ int kvlangBuiltinXvBodylen(kvlangFrame_t *f) { kvlangXvalueFree(&r); return rc; } - diff --git "a/stdlib/kvlang/spec/01-\350\257\215\346\263\225/09-\351\200\273\350\276\221\344\275\215\350\277\220\347\256\227\344\270\216\344\274\230\345\205\210\347\272\247.kv" "b/stdlib/kvlang/spec/01-\350\257\215\346\263\225/09-\351\200\273\350\276\221\344\275\215\350\277\220\347\256\227\344\270\216\344\274\230\345\205\210\347\272\247.kv" index 0f82c24b..6cb19d5c 100644 --- "a/stdlib/kvlang/spec/01-\350\257\215\346\263\225/09-\351\200\273\350\276\221\344\275\215\350\277\220\347\256\227\344\270\216\344\274\230\345\205\210\347\272\247.kv" +++ "b/stdlib/kvlang/spec/01-\350\257\215\346\263\225/09-\351\200\273\350\276\221\344\275\215\350\277\220\347\256\227\344\270\216\344\274\230\345\205\210\347\272\247.kv" @@ -40,8 +40,8 @@ p·field -> v // 经指针访问成员(自动解引用,见 [[ptr]]) ## 优先级 -中缀运算符的优先级如下(数值越大结合越紧);一元前缀运算符(`+ - ! √ &`)优先级高于全部中缀。 -故 `&a·b ≡ &(a·b)`(成员链先结合,同 C 的 `&a.b`)。 +中缀运算符的优先级如下(数值越大结合越紧);一元前缀运算符(`+ - ! √ & *`)优先级高于全部中缀。 +故 `&a·b ≡ &(a·b)`、`*p·x ≡ *(p·x)`(成员链先结合,同 C 的 `&a.b`)。 | 运算符 | 优先级 | |--------|--------| diff --git "a/stdlib/kvlang/spec/03-\347\261\273\345\236\213\347\263\273\347\273\237/15-\346\210\220\345\221\230\350\256\277\351\227\256.kv" "b/stdlib/kvlang/spec/03-\347\261\273\345\236\213\347\263\273\347\273\237/15-\346\210\220\345\221\230\350\256\277\351\227\256.kv" index 69276855..80a9d66c 100644 --- "a/stdlib/kvlang/spec/03-\347\261\273\345\236\213\347\263\273\347\273\237/15-\346\210\220\345\221\230\350\256\277\351\227\256.kv" +++ "b/stdlib/kvlang/spec/03-\347\261\273\345\236\213\347\263\273\347\273\237/15-\346\210\220\345\221\230\350\256\277\351\227\256.kv" @@ -34,5 +34,10 @@ lib kvlang/spec/类型系统/成员访问 { 该规则让「局部结构体」(base 非 Ptr,按名)与「指针成员访问」(base 是 Ptr,按其 target)共用一套语 法。 + +**base 必须是容器**(struct 实例 / `map` / index)或**路径串**(`[]char/*` 的值即父路径本身,如 +`kvspace·get("/a/b", k)` 的基座):compact 数组与标量两者都不是,用 `·` 访问报 TypeError(compact 数组用 +`[]` 下标,见 [[数组形态]])。这条在解引用之后判定——`p·[0]` 里 base 是 `p` 指向的 compact 数组,虽 `p` +是 Ptr 仍报错。 "#### -> /lib/kvlang/spec/类型系统/成员访问 } diff --git "a/stdlib/kvlang/spec/03-\347\261\273\345\236\213\347\263\273\347\273\237/16-ptr.kv" "b/stdlib/kvlang/spec/03-\347\261\273\345\236\213\347\263\273\347\273\237/16-ptr.kv" index 3e9601ae..d47fa499 100644 --- "a/stdlib/kvlang/spec/03-\347\261\273\345\236\213\347\263\273\347\273\237/16-ptr.kv" +++ "b/stdlib/kvlang/spec/03-\347\261\273\345\236\213\347\263\273\347\273\237/16-ptr.kv" @@ -18,6 +18,14 @@ lib kvlang/spec/类型系统/ptr { | `*p` | 解引用 → 读 body 目标地址的值 | | `None` | **空指针**(唯一的空指针字面量;即键不存在=无值) | +`*` 是**唯一**的解引用路径,对任何 ref=1 的 Ptr 槽成立:既含函数体内的形参槽 `*[0,±k]`(见 +[[函数]]、[[调用extindex]]),也含局部指针变量(`&x -> p` 后的 `*p`)——二者是同一读参命名约定, +layout 一律把 `*` 落成读参名/写槽名的前缀。读 `*p` 取目标整值;写 `42 -> *p` 写穿到目标(读写同规 +则:槽里存的是指针,只有 `*` 才落到目标)。 + +`[]` 与 `·[…]` 都**不**对指针隐式下标:`p[i]`(`p:*[]T`)报 TypeError——先 `*p` 再下标;`p·[0]` 先按 +指针解引用到目标,目标若是 compact 数组则不是容器,同样报 TypeError(见 [[成员访问]])。 + ## 空指针即 None `*T` 的槽只有两种取值:指向某地址的 Ptr,或 **None**。None 在 kvspace 即「无值」——键不存在、读回 diff --git "a/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/03-\345\207\275\346\225\260.kv" "b/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/03-\345\207\275\346\225\260.kv" index b1778b4c..c3670837 100644 --- "a/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/03-\345\207\275\346\225\260.kv" +++ "b/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/03-\345\207\275\346\225\260.kv" @@ -56,6 +56,11 @@ runtime-c 不解引用 `@`,也不设通用 extresolver。 参数声明的类型串是 **def langtype**(定义处的匹配类型,见 [[langtype签名类型表达式]]):它可含并集与 `any`,故**同一参数可接受多种 `langtype` 的实参**(如 `a:int8|int16` 同时接受 int8 与 int16 值)。 +局部指针变量与形参槽走**同一约定**:`*p`(`p` 为局部 `*T`)也落成读参名/写槽名的 `*` 前缀。layout 只 +在名下加 `*`,解引用由 runtime 按该槽里的 Ptr 完成——与 `*[0,±k]` 是同一机制,故 `*` 是唯一的解引用路 +径(见 [[ptr]])。地址形参名在体内**已被解引用一次**(名字即实参值),故体内再写 `*形参` 是两层间接, +layout 报错(待 [[调用extindex]] 的名称绑定模型收拢,见 issue #286)。 + 由此:读参(`param<0`,不论值传/址传)**只读**——`p:*Point` 的 `p·field = v`、值参 `x:int64` 的 `x = x + 1` 都是写槽命中读参,layout 报错。要改值就先拷到新局部变量(`x -> local`,此后写 `local`);要 改调用方对象就把该参数放到写参侧(`-> (p:*/lib/Point)`)。写参(`param>0`)可读可写:`-> (p:*/lib/Point)` diff --git "a/stdlib/kvlang/spec/05-runtime\350\257\255\344\271\211/03-\346\210\220\345\221\230\350\256\277\351\227\256.kv" "b/stdlib/kvlang/spec/05-runtime\350\257\255\344\271\211/03-\346\210\220\345\221\230\350\256\277\351\227\256.kv" index e45624db..b1686faa 100644 --- "a/stdlib/kvlang/spec/05-runtime\350\257\255\344\271\211/03-\346\210\220\345\221\230\350\256\277\351\227\256.kv" +++ "b/stdlib/kvlang/spec/05-runtime\350\257\255\344\271\211/03-\346\210\220\345\221\230\350\256\277\351\227\256.kv" @@ -39,6 +39,12 @@ kvlang 用两个符号访问复合数据,语义严格分离,runtime **不得 该规则让"局部结构体"与"指针成员访问"共用一套语法:结构体的 `base` 非 Ptr(走按名),指针变量持 Ptr(按 其 target)。跨函数共享的数据**应**放绝对路径(`/n1`),使不同帧的指针都解引用到同一 key。 +3. **base 形态判定**:解析出的 `base`(Ptr 则取其**目标**)**必须**是容器——struct 实例(kindexpr 以 + `/` 开头)、`stringkeymap`/`index`/`extindex`;或**路径串**(`[]char/*`,显式形式 `kvspace·get(path, + k)` 的 base 就是它,其值即成员键的父路径)。compact 数组与标量两者都不是,报 TypeError(`·` 要求容 + 器;compact 数组用 `[]` 索引);None 是动态读,返回 None。**不得**把非容器、非路径串的 body 字节当路 + 径串拼成员键——那会静默给出脏值或 None。 + ## 动态成员名 `·*`:解引用操作数取 memitemkey `base·*k` 用 `k` 的**值**作为成员键:`·*` 令 runtime-c **解引用/求值**操作数 `k`,以其值定位 @@ -77,6 +83,9 @@ ref=2(@ext)表示真实值在扩展世界,head 的 langtype 仍为目标 **stringkeymap**(`{...}` 字面量,散 key,每元素落 `base·[i]` 坐标段独立子 key)。下标 `a[i]` / `xv·at` / `xv·set` 用于 **compact** 形态;stringkeymap 形态用 `for-in` / `kvspace·get` 遍历访问。 +下标的操作数**必须**是 compact 数组**值**本身。Ptr(ref=1)是寻址用的间接层,不是数组:`p[i]`(`p:*[]T`) +报 TypeError——先显式解引用 `*p` 再下标(`*` 是唯一解引用路径,`[]` 不做隐式解引用,见 [[ptr]])。 + - **下标读**:`a[i] -> e`(layout 脱糖为 `xv·at`/`kvspace·get`),按 `kvlangXvalueElemSize(kind)` 定的元 素字节宽零拷贝定位第 `i` 个元素。 - **下标写**:`a[i] = v` / `v -> a[i]`(两种书写等价,脱糖为 `xv·set`,见 [[指令架构]]),就地改第 `i` diff --git "a/stdlib/kvlang/spec/\351\231\204\345\275\225/05-\350\241\250\350\276\276\345\274\217.kv" "b/stdlib/kvlang/spec/\351\231\204\345\275\225/05-\350\241\250\350\276\276\345\274\217.kv" index 098fa884..eef4cee7 100644 --- "a/stdlib/kvlang/spec/\351\231\204\345\275\225/05-\350\241\250\350\276\276\345\274\217.kv" +++ "b/stdlib/kvlang/spec/\351\231\204\345\275\225/05-\350\241\250\350\276\276\345\274\217.kv" @@ -18,7 +18,7 @@ primary = literal | ident | path | struct_literal | map_literal -prefix_unary = "-" | "!" | "√" | "&" | "*" (* √ = sqrt(U+221A);&x ≡ kvlang·abs(x) 绝对地址(产 ref=1 Ptr);*p 解引用 *) +prefix_unary = "-" | "!" | "√" | "&" | "*" (* √ = sqrt(U+221A);&x ≡ kvlang·abs(x) 绝对地址(产 ref=1 Ptr);*p 解引用该 Ptr 槽(唯一解引用路径,[] 不隐式解引用,见 [[ptr]]) *) infix_primary = binary_op expr diff --git a/tutorial/10-types/09-deref.kv b/tutorial/10-types/09-deref.kv new file mode 100644 index 00000000..d87c123e --- /dev/null +++ b/tutorial/10-types/09-deref.kv @@ -0,0 +1,20 @@ +// 语义: `*` 是唯一解引用路径([[ptr]])——`&x` 造 Ptr,读 `*T` 变量得地址值本身,`*p` 落到目标: +// 读侧取目标整值、写侧写穿目标(读写同规则)。`[]`/`·[…]` 不对指针隐式解引用: +// 经指针下标要先 `*p` 取回数组值(`p[i]`、`p·[0]` 均报 TypeError)。 +// 期望输出: +// q= 42 +// x= 100 +// e= 11 +rwfunc test() -> () { + 42 -> x + &x -> p + *p -> q + println("q=", q) + 100 -> *p + println("x=", x) + list:[]int64 = [11, 22, 33] + &list -> lp + *lp -> lv + lv[0] -> e + println("e=", e) +} From 894d82a45a568c6f66bac63805a2139e61e0e31b Mon Sep 17 00:00:00 2001 From: "peng.li24" <734991033@qq.com> Date: Sun, 13 Sep 2026 21:37:02 +0800 Subject: [PATCH 2/3] =?UTF-8?q?build:=20make=20clear/status=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E5=AE=89=E8=A3=85=E6=80=81=E7=9A=84=E6=B8=85?= =?UTF-8?q?=E7=90=86=E4=B8=8E=E6=9F=A5=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install 的逆操作(配合「测试恒按 /usr 最终态」的约定,便于反复重装): - `make status` 列出系统目录里已安装的库 / pkgconfig / 可执行 / 头 - `make clear` 删 .so(/usr/lib、/usr/lib/kvspace、/lib、/usr/local/lib…) 与可执行(kvlang / kvlanglayout / kvspace)+ kvspace.pc - `make clear-headers` 删头(/usr/include/kvspace|kvlang、/usr/local/include/…) - `make clear-all` clear + clear-headers /lib、/bin 多为指向 /usr 的符号链接,重复删同一文件无副作用;SUDO 可覆盖。 Co-Authored-By: Claude Code --- Makefile | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4300df35..e08a1f7b 100644 --- a/Makefile +++ b/Makefile @@ -8,11 +8,14 @@ # make install 一次性安装产物到最终态目录(.so→/usr/lib,可执行→/usr/bin,头→/usr/include/kvlang/) # make all 全部(runtime + runtime-rs + layout + json) # make clean 清理 bin/ 与各构建目录 +# make clear install 的逆操作:删系统目录里 kvspace/kvlang 的 .so 与可执行 +# make status 列出系统目录里已安装的库 / 可执行 / 头 # kvspace 后端由 libkvspace dispatch 前端按 DSN 运行时选择,不再编译期切换。 BIN := bin -.PHONY: all runtime runtime-rs layout json oldhero test install clean +.PHONY: all runtime runtime-rs layout json oldhero test install clean \ + status clear clear-headers clear-all all: runtime runtime-rs layout json @@ -49,3 +52,37 @@ clean: rm -rf $(BIN) build cargo clean --manifest-path layout/Cargo.toml cargo clean --manifest-path runtime-rs/Cargo.toml + +# ── 安装态清理(install 的逆操作,kvspace / kvlang 生态) ───────────────── +# make clear 删 .so(/usr/lib、/usr/lib/kvspace、/lib、/usr/local/lib …)+ 可执行 +# make clear-headers 再删头(/usr/include/kvspace、/usr/include/kvlang …) +# make clear-all clear + clear-headers +# 注:/lib、/bin 多为指向 /usr 的符号链接,重复删同一文件无副作用。 +SUDO ?= sudo +LIBDIRS := /usr/lib/kvspace /usr/lib/x86_64-linux-gnu /usr/lib /lib/x86_64-linux-gnu /lib /usr/local/lib +BINDIRS := /usr/bin /bin /usr/local/bin +BINS := kvlang kvlanglayout kvspace +HDRDIRS := /usr/include/kvspace /usr/include/kvlang /usr/local/include/kvspace /usr/local/include/kvlang +PCDIRS := /usr/lib/x86_64-linux-gnu/pkgconfig /usr/lib/pkgconfig /usr/local/lib/pkgconfig + +status: + @echo "── 库 ──" + @for d in $(LIBDIRS); do ls -1 $$d/libkvspace*.so* $$d/libkvlang*.so* 2>/dev/null | sed 's|^| |'; done; true + @for d in $(PCDIRS); do [ -e $$d/kvspace.pc ] && echo " $$d/kvspace.pc"; done; true + @echo "── 可执行 ──" + @for d in $(BINDIRS); do for b in $(BINS); do [ -e $$d/$$b ] && echo " $$d/$$b"; done; done; true + @echo "── 头 ──" + @for d in $(HDRDIRS); do [ -e $$d ] && echo " $$d"; done; true + +clear: + @for d in $(LIBDIRS); do $(SUDO) rm -f $$d/libkvspace*.so* $$d/libkvlang*.so*; done + @for d in $(PCDIRS); do $(SUDO) rm -f $$d/kvspace.pc; done + @for d in $(BINDIRS); do for b in $(BINS); do $(SUDO) rm -f $$d/$$b; done; done + @$(SUDO) ldconfig 2>/dev/null || true + @echo "✅ clear:已删 kvspace/kvlang 的 .so 与可执行" + +clear-headers: + @$(SUDO) rm -rf $(HDRDIRS) + @echo "✅ clear-headers:已删头文件" + +clear-all: clear clear-headers From ee4b27ffd2e897736d0de6bd8aaf25dedc9eeb94 Mon Sep 17 00:00:00 2001 From: "peng.li24" <734991033@qq.com> Date: Sun, 13 Sep 2026 23:14:15 +0800 Subject: [PATCH 3/3] =?UTF-8?q?release(macos):=20=E4=BF=AE=E6=AD=A3=20CLI?= =?UTF-8?q?=20=E6=9E=84=E5=BB=BA=E9=A1=BA=E5=BA=8F=EF=BC=88libkvlanglayout?= =?UTF-8?q?=20=E5=85=88=E5=85=A5=20bin=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit runtime-rs 的 build.rs 在 ../bin 搜索 libkvlang_runtime / libkvlanglayout, 原先三个 cargo build 全跑完才 cp → 构建 kvlang 时报 `ld: library 'kvlanglayout' not found`。改为 layout 构建后立即 cp。 Co-Authored-By: Claude Code --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4950b615..0f4f88e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,11 +74,11 @@ jobs: cmake -S runtime -B build/runtime -DCMAKE_BUILD_TYPE=Release -DKVSPACE_LIB_DIR="$KVSPACE_LIB_DIR" cmake --build build/runtime --target kvlang_runtime -j cargo build --release --manifest-path layout/Cargo.toml - cargo build --release --manifest-path runtime-rs/Cargo.toml - # kvspace CLI 是本步 clone 到 /tmp/kvspace 的 cli/(独立 Cargo 项目) - cargo build --release --manifest-path /tmp/kvspace/cli/Cargo.toml + # runtime-rs 的 build.rs 在 ../bin 找 libkvlang_runtime/libkvlanglayout,必须先就位 mkdir -p bin cp layout/target/release/libkvlanglayout.dylib bin/ + cargo build --release --manifest-path runtime-rs/Cargo.toml + cargo build --release --manifest-path /tmp/kvspace/cli/Cargo.toml cp layout/target/release/kvlanglayout bin/ cp runtime-rs/target/release/kvlang bin/ - name: 后端装载冒烟(frontend 的 dlopen 真能加载后端)