Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions layout/examples/verify_src_abi.rs
Original file line numberDiff line numberDiff line change
@@ -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 {
Expand DownExpand Up@@ -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,
Expand All@@ -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(),
Expand Down
169 changes: 169 additions & 0 deletions layout/src/ast.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -551,6 +551,175 @@ pub struct File {
pub init_body: Vec<Stmt>,
}

/// 包树节点:按 lib 包名分组,format 时重建嵌套 lib 块(保包名,round-trip 不丢)。
#[derive(Default)]
struct PkgNode {
rwirs: Vec<RwirDecl>,
funcs: Vec<Func>,
body: Vec<Stmt>,
children: std::collections::BTreeMap<String, PkgNode>,
}

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<String> = 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 {
Expand Down
44 changes: 36 additions & 8 deletions layout/src/capi.rs
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
//! 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/<fn>.src),不在此 ABI。
//! 三个入口:
//! kvlangLayoutVet(src,…) 只校验(parse+lower),不写 kvspace —— 自造代码闸门
//! kvlangLayoutFormat(src,…) 格式化(parse → 规范化源码),不写 kvspace
//! kvlangLayoutCode(src,dsn,…) 从源码串 layout 进 kvspace(LLM 生成即插入,不落盘)
//! kvlangLayoutFile(path,…) 是 Code 的薄封装(读文件后走同一 core)。源码读回(`.src`)
//! 是纯 KV 读(/lib/<fn>.src),不在此 ABI。
//!
//! 三者都在 C 边界用 catch_unwind 兜住 kvlang 内部 panic(设计上对非法输入 panic),
//! 各入口都在 C 边界用 catch_unwind 兜住 kvlang 内部 panic(设计上对非法输入 panic),
//! 坏代码只会返回 -1,绝不打崩宿主进程。

use std::ffi::CStr;
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 {
Expand DownExpand Up@@ -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,
Expand DownExpand Up@@ -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
}
}
}
45 changes: 45 additions & 0 deletions layout/src/code.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,6 +63,18 @@ pub fn compile(kv: &mut Kv, src: &str) -> Result<(), String> {
Ok(())
}

/// 格式化源码(parse → 规范化源码),不写入 kvspace。失败返回错误。
pub fn format(src: &str) -> Result<String, String> {
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> {
Expand DownExpand Up@@ -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}/"));

Expand DownExpand Up@@ -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");
}
}
2 changes: 1 addition & 1 deletion layout/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Loading