From 36d8f8b49dac04451d6dfa89474407e98ca22e41 Mon Sep 17 00:00:00 2001 From: "peng.li24" <734991033@qq.com> Date: Wed, 19 Aug 2026 11:54:50 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20layout=20=E6=A0=BC=E5=BC=8F=E5=8C=96?= =?UTF-8?q?=EF=BC=88round-trip=20=E4=BF=9D=E5=8C=85=E5=90=8D=EF=BC=89+=20C?= =?UTF-8?q?=20ABI=20=E4=B8=89=E5=85=A5=E5=8F=A3=20Code/Format/Vet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ast.rs:PkgNode 按 lib 包名分组,format 重建嵌套 lib 块(保包名不丢)。 - capi.rs:kvlangLayoutCode / kvlangLayoutFormat / kvlangLayoutVet 三入口,File 是 Code 薄封装。 - code.rs/lib.rs/verify_src_abi.rs 同步。 Co-Authored-By: Claude --- layout/examples/verify_src_abi.rs | 8 +- layout/src/ast.rs | 169 ++++++++++++++++++++++++++++++ layout/src/capi.rs | 44 ++++++-- layout/src/code.rs | 45 ++++++++ layout/src/lib.rs | 2 +- 5 files changed, 255 insertions(+), 13 deletions(-) diff --git a/layout/examples/verify_src_abi.rs b/layout/examples/verify_src_abi.rs index 0bcda405..1dac3775 100644 --- a/layout/examples/verify_src_abi.rs +++ b/layout/examples/verify_src_abi.rs @@ -1,9 +1,9 @@ -//! 验证 corebrain 自造代码三入口:kvlangLayoutVet / kvlangLayoutSrc + `.src` 读回。 +//! 验证 corebrain 自造代码三入口:kvlangLayoutVet / kvlangLayoutCode + `.src` 读回。 //! 用法:verify_src_abi [dsn] use std::os::raw::c_char; -use kvlang_layout::capi::{kvlangLayoutSrc, kvlangLayoutVet}; +use kvlang_layout::capi::{kvlangLayoutCode, kvlangLayoutVet}; use kvlang_layout::Kv; fn cs(s: &str) -> std::ffi::CString { @@ -34,7 +34,7 @@ fn main() { // 3) layout 内存源码进 kvspace → 0,返回入口名 let mut entry = [0u8; 512]; let mut err3 = [0u8; 512]; - let rc = kvlangLayoutSrc( + let rc = kvlangLayoutCode( cs(good).as_ptr(), cs(&dsn).as_ptr(), entry.as_mut_ptr() as *mut c_char, @@ -57,7 +57,7 @@ fn main() { // 5) layout 非法源码 → -1,不污染 /lib、不打崩 let mut err4 = [0u8; 512]; - let rc = kvlangLayoutSrc( + let rc = kvlangLayoutCode( cs(bad).as_ptr(), cs(&dsn).as_ptr(), std::ptr::null_mut(), diff --git a/layout/src/ast.rs b/layout/src/ast.rs index 6905b57c..cd767ed2 100644 --- a/layout/src/ast.rs +++ b/layout/src/ast.rs @@ -551,6 +551,175 @@ pub struct File { pub init_body: Vec, } +/// 包树节点:按 lib 包名分组,format 时重建嵌套 lib 块(保包名,round-trip 不丢)。 +#[derive(Default)] +struct PkgNode { + rwirs: Vec, + funcs: Vec, + body: Vec, + children: std::collections::BTreeMap, +} + +fn pkg_node<'a>(root: &'a mut PkgNode, pkg: &str) -> &'a mut PkgNode { + if pkg.is_empty() { + return root; + } + let mut cur = root; + for seg in pkg.split('/') { + cur = cur.children.entry(seg.to_string()).or_default(); + } + cur +} + +fn emit_node(sb: &mut String, node: &PkgNode, indent: &str) { + let mut items: Vec = Vec::new(); + for d in &node.rwirs { + let mut s = String::new(); + for c in &d.comments { + s.push_str(indent); + s.push_str(c); + s.push('\n'); + } + s.push_str(indent); + s.push_str(&d.sig_string()); + items.push(s); + } + for f in &node.funcs { + let mut s = String::new(); + for c in &f.comments { + s.push_str(indent); + s.push_str(c); + s.push('\n'); + } + s.push_str(indent); + s.push_str(&f.sig.to_string()); + s.push_str(" {\n"); + format_body(&mut s, &f.body, &format!("{indent}\t")); + s.push_str(indent); + s.push('}'); + items.push(s); + } + if !node.body.is_empty() { + let mut s = String::new(); + format_body(&mut s, &node.body, indent); + items.push(s.trim_end_matches('\n').to_string()); + } + for (name, child) in &node.children { + let mut s = String::new(); + s.push_str(indent); + s.push_str("lib "); + s.push_str(name); + s.push_str(" {\n"); + emit_node(&mut s, child, &format!("{indent}\t")); + s.push_str(indent); + s.push('}'); + items.push(s); + } + sb.push_str(&items.join("\n\n")); +} + +impl File { + /// 格式化为规范 kvlang 源码(重建 lib 分组,保留包名,round-trip 语义等价)。 + pub fn format(&self) -> String { + let mut root = PkgNode::default(); + for d in &self.rwir_decls { + pkg_node(&mut root, &d.pkg).rwirs.push(d.clone()); + } + for f in &self.funcs { + let n = pkg_node(&mut root, &f.pkg); + if f.sig.name == "init" { + n.body.extend(f.body.clone()); + } else { + n.funcs.push(f.clone()); + } + } + let mut sb = String::new(); + emit_node(&mut sb, &root, ""); + for inst in &self.top_level_calls { + if !sb.is_empty() { + sb.push('\n'); + } + for c in &inst.comments { + sb.push_str(c); + sb.push('\n'); + } + sb.push_str(&inst.to_string()); + } + sb + } +} + +/// 缩进格式化语句体(对齐 Go ast.formatBody)。 +fn format_body(sb: &mut String, stmts: &[Stmt], indent: &str) { + for (i, st) in stmts.iter().enumerate() { + if i > 0 { + let prev = &stmts[i - 1]; + let prev_block = matches!(prev, Stmt::Scope(_) | Stmt::If(_)); + let cur_block = matches!(st, Stmt::Scope(_) | Stmt::If(_)); + if prev_block || cur_block { + sb.push('\n'); + } + } + for c in stmt_comments(st) { + sb.push_str(indent); + sb.push_str(c); + sb.push('\n'); + } + let child = format!("{indent}\t"); + match st { + Stmt::Instruction(s) => { + sb.push_str(indent); + sb.push_str(&s.to_string()); + sb.push('\n'); + } + Stmt::Scope(s) => { + sb.push_str(indent); + sb.push_str(&s.label); + sb.push_str(": {\n"); + format_body(sb, &s.body, &child); + sb.push_str(indent); + sb.push_str("}\n"); + } + Stmt::If(s) => { + let cond = s.cond.as_ref().map(|c| c.to_string()).unwrap_or_default(); + sb.push_str(indent); + sb.push_str(&format!("if ({cond}) {{\n")); + format_body(sb, &s.then_, &child); + if !s.else_.is_empty() { + sb.push_str(indent); + sb.push_str("} else {\n"); + format_body(sb, &s.else_, &child); + } + sb.push_str(indent); + sb.push_str("}\n"); + } + Stmt::For(s) => { + sb.push_str(indent); + sb.push_str(&format!("for ({} in {}) {{\n", s.var, s.iter)); + format_body(sb, &s.body, &child); + sb.push_str(indent); + sb.push_str("}\n"); + } + Stmt::While(s) => { + let cond = s.cond.as_ref().map(|c| c.to_string()).unwrap_or_default(); + sb.push_str(indent); + sb.push_str(&format!("while ({cond}) {{\n")); + format_body(sb, &s.body, &child); + sb.push_str(indent); + sb.push_str("}\n"); + } + Stmt::Break(_) => { + sb.push_str(indent); + sb.push_str("break\n"); + } + Stmt::Continue(_) => { + sb.push_str(indent); + sb.push_str("continue\n"); + } + } + } +} + // ── 工具 ───────────────────────────────────────────────────────────── fn escape_string(s: &str) -> String { diff --git a/layout/src/capi.rs b/layout/src/capi.rs index 8ec33ba7..d3e02b71 100644 --- a/layout/src/capi.rs +++ b/layout/src/capi.rs @@ -1,13 +1,14 @@ //! layout 的 C ABI:供第三方(Rust/Python/C 等)把 .kv 代码 layout 进 kvspace, //! 无需 fork 子进程。符号在 cdylib(libkvlang_layout.so)中导出。 //! -//! 三个入口,对应 corebrain 自造 kv 代码的三步: -//! kvlangLayoutFile(path,…) 从文件 layout(原有) -//! kvlangLayoutSrc(src,…) 从内存源码串 layout —— LLM 生成即插入,不落盘 -//! kvlangLayoutVet(src,…) 只校验(parse+lower),不写 kvspace —— 自造代码的闸门 -//! 源码读回(`.src`)是纯 KV 读(/lib/.src),不在此 ABI。 +//! 三个入口: +//! kvlangLayoutVet(src,…) 只校验(parse+lower),不写 kvspace —— 自造代码闸门 +//! kvlangLayoutFormat(src,…) 格式化(parse → 规范化源码),不写 kvspace +//! kvlangLayoutCode(src,dsn,…) 从源码串 layout 进 kvspace(LLM 生成即插入,不落盘) +//! kvlangLayoutFile(path,…) 是 Code 的薄封装(读文件后走同一 core)。源码读回(`.src`) +//! 是纯 KV 读(/lib/.src),不在此 ABI。 //! -//! 三者都在 C 边界用 catch_unwind 兜住 kvlang 内部 panic(设计上对非法输入 panic), +//! 各入口都在 C 边界用 catch_unwind 兜住 kvlang 内部 panic(设计上对非法输入 panic), //! 坏代码只会返回 -1,绝不打崩宿主进程。 use std::ffi::CStr; @@ -15,7 +16,7 @@ use std::fs; use std::os::raw::c_char; use std::panic::catch_unwind; -use crate::{compile, init_dirs, vet, Kv}; +use crate::{compile, format, init_dirs, vet, Kv}; /// 复刻 Go runtime / layout_file 的 findEntry:DFS /lib/ 找首个 `.init`,否则 "init"。 fn find_entry(kv: &mut Kv, prefix: &str) -> String { @@ -106,7 +107,7 @@ pub extern "C" fn kvlangLayoutFile( /// 把内存源码串 `src` 直接 layout 进 `dsn`(LLM 生成即插入,不落盘)。 /// 成功返回 0(entry_out=入口名),失败返回 -1(err_out=错误)。 #[no_mangle] -pub extern "C" fn kvlangLayoutSrc( +pub extern "C" fn kvlangLayoutCode( src: *const c_char, dsn: *const c_char, entry_out: *mut c_char, @@ -135,3 +136,30 @@ pub extern "C" fn kvlangLayoutVet(src: *const c_char, err_out: *mut c_char, err_ } } } + +/// 格式化 `src`(parse → 规范化源码),不写 kvspace。合法返回 0(out=格式化结果), +/// 非法返回 -1(err_out=错误)。 +#[no_mangle] +pub extern "C" fn kvlangLayoutFormat( + src: *const c_char, + out: *mut c_char, + out_cap: u32, + err_out: *mut c_char, + err_cap: u32, +) -> i32 { + let src = cstr(src).to_string(); + match catch_unwind(|| format(&src)) { + Ok(Ok(s)) => { + write_out(out, out_cap, &s); + 0 + } + Ok(Err(e)) => { + write_out(err_out, err_cap, &e); + -1 + } + Err(_) => { + write_out(err_out, err_cap, "format panicked (invalid program)"); + -1 + } + } +} diff --git a/layout/src/code.rs b/layout/src/code.rs index 0e786b16..34aa132a 100644 --- a/layout/src/code.rs +++ b/layout/src/code.rs @@ -63,6 +63,18 @@ pub fn compile(kv: &mut Kv, src: &str) -> Result<(), String> { Ok(()) } +/// 格式化源码(parse → 规范化源码),不写入 kvspace。失败返回错误。 +pub fn format(src: &str) -> Result { + let (file, diags) = parser::parse_code(src)?; + for d in &diags { + eprintln!("{}", d.string()); + } + if parser::has_errors(&diags) { + return Err("parse: error-level diagnostics — refusing to format".to_string()); + } + Ok(file.format()) +} + /// 校验源码是否可 layout(parse + lower),但不写入 kvspace。 /// 供运行时 vet 闸门:LLM 生成的 kv 代码先过此关,失败不污染 /lib。 pub fn vet(src: &str) -> Result<(), String> { @@ -93,6 +105,8 @@ pub fn write_func(kv: &mut Kv, pkg: &str, fn_: &mut Func) { lower::specialize(fn_, &type_map); let func_dir = keytree::lib_func(pkg, &fn_.sig.name); + // 按函数覆盖(文件夹复制式合并):只 del_tree 本函数子树,不动 /lib 下其它函数。 + // 禁止整库删除——layoutcode 必须可增量:多次 layout 各自覆盖其函数,不误删先前的函数。 let _ = kv.del_tree(&func_dir); let _ = kv.mkindex(&format!("{func_dir}/")); @@ -307,3 +321,34 @@ fn is_literal(s: &str) -> bool { || b[0].is_ascii_digit() || (b[0] == b'-' && s.len() > 1) } + +#[cfg(test)] +mod tests { + use super::*; + + fn roundtrip(src: &str) { + let once = format(src).unwrap_or_else(|e| panic!("format: {e}\nsrc:\n{src}")); + assert!(vet(&once).is_ok(), "formatted must vet:\n{once}"); + assert_eq!(format(&once).unwrap(), once, "idempotent fail:\n{once}"); + } + + #[test] + fn format_preserves_lib() { + roundtrip("lib http {\n\trwfunc get(url:[]char/utf32) -> (resp:[]char/utf32) {\n\t\thttp.call(\"GET\", \"\", url, \"\") -> resp\n\t}\n}\n"); + roundtrip("lib byteseek {\nlib session {\nlib s1 {\nrwfunc main() -> () {\n3 + 4 -> s\nprintln(s)\n}\n}\n}\n}\nmain()\n"); + } + + #[test] + fn nested_lib_layout_and_merge() { + let mut kv = Kv::conn(&format!("fs:///tmp/kvlang_layout_nested_{}", std::process::id())); + init_dirs(&mut kv).unwrap(); + + compile(&mut kv, "lib a {\nlib b {\nrwfunc f() -> (r:int64) {\n1 -> r\n}\n}\n}\n").unwrap(); + assert_eq!(kvkind::kind(&kv.get_one("/lib/a/b.f/[0,0]")), "rwfunc"); + + // 同 lib a 下再 layout 另一嵌套 lib c,验证 b.f 未被整库删除(增量合并) + compile(&mut kv, "lib a {\nlib c {\nrwfunc g() -> (r:int64) {\n2 -> r\n}\n}\n}\n").unwrap(); + assert_eq!(kvkind::kind(&kv.get_one("/lib/a/b.f/[0,0]")), "rwfunc", "b.f 应保留"); + assert_eq!(kvkind::kind(&kv.get_one("/lib/a/c.g/[0,0]")), "rwfunc"); + } +} diff --git a/layout/src/lib.rs b/layout/src/lib.rs index b06ad4ac..e3f1ff95 100644 --- a/layout/src/lib.rs +++ b/layout/src/lib.rs @@ -16,6 +16,6 @@ pub mod lower; pub mod code; pub mod capi; -pub use code::{compile, init_dirs, vet, write_func, write_rwir_decl}; +pub use code::{compile, format, init_dirs, vet, write_func, write_rwir_decl}; pub use ffi::Kv; pub use scanner::Diagnostic;