From 4fcc44b4de6b105ae6bbaab00c4a4f9d3016d757 Mon Sep 17 00:00:00 2001 From: "peng.li24" <734991033@qq.com> Date: Sat, 29 Aug 2026 14:52:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20#118=20=E6=94=B6=E5=B0=BE=E2=80=94?= =?UTF-8?q?=E2=80=94json=C2=B7to=20=E5=B1=80=E9=83=A8=E5=AE=B9=E5=99=A8=20?= =?UTF-8?q?+=20display=5Fmap=20+=20entry=20=E6=9F=A5=E6=89=BE=20+=20tutori?= =?UTF-8?q?al=20=E5=88=87=20=C2=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude --- layout/examples/layout_file.rs | 23 +++++---- layout/src/capi.rs | 21 +++++---- layout/src/parser.rs | 4 ++ layout/src/scanner.rs | 2 +- .../go/json/native_test.go | 34 ++++++++++++++ .../go/json/tutorial/json.kv | 30 ++++++++---- .../go/json/tutorial/json_local.kv | 33 +++++++++++++ runtime/src/builtin.c | 11 ----- runtime/src/rwirext.c | 47 ++++++++++++++++++- 9 files changed, 166 insertions(+), 39 deletions(-) create mode 100644 runtime-rwirext_example/go/json/tutorial/json_local.kv diff --git a/layout/examples/layout_file.rs b/layout/examples/layout_file.rs index fb158e53..98d2adf7 100644 --- a/layout/examples/layout_file.rs +++ b/layout/examples/layout_file.rs @@ -7,18 +7,23 @@ use std::fs; use kvlang_layout::{compile, init_dirs, Kv}; -/// 复刻 Go runtime 的 findEntry:DFS /lib/ 找首个 `·init`,否则 "init"。 -fn find_entry(kv: &mut Kv, prefix: &str) -> String { +/// 复刻 Go runtime 的 findEntry:DFS /lib/ 找首个 init(顶层 `init` 或 lib 块内 `pkg·init`)。 +fn find_entry(kv: &mut Kv, prefix: &str, pkg: &str) -> String { let children = kv.list(prefix, false, true); for c in &children { - let c = c.trim_end_matches('/'); - if c.ends_with("·init") { - return c.to_string(); + let base = c.trim_end_matches('/'); + if base.ends_with(".src") { + continue; } - let sub = format!("{prefix}{c}/"); - let entry = find_entry(kv, &sub); + let full = if pkg.is_empty() { base.to_string() } else { format!("{pkg}·{base}") }; + if base == "init" { + return full; + } + let next_pkg = if pkg.is_empty() { base.to_string() } else { format!("{pkg}/{base}") }; + let sub = if c.ends_with('/') { format!("{prefix}{base}/") } else { format!("{prefix}{base}·") }; + let entry = find_entry(kv, &sub, &next_pkg); if !entry.is_empty() { - return format!("{c}/{entry}"); + return entry; } } String::new() @@ -37,7 +42,7 @@ fn main() { init_dirs(&mut kv).expect("init_dirs"); compile(&mut kv, &src).expect("compile"); - let entry = find_entry(&mut kv, "/lib/"); + let entry = find_entry(&mut kv, "/lib/", ""); let entry = if entry.is_empty() { "init" } else { &entry }; println!("ENTRY={entry}"); eprintln!("[rust-layout] {} -> {} (entry={entry})", args[1], dsn); diff --git a/layout/src/capi.rs b/layout/src/capi.rs index 68ae3064..44ab29c7 100644 --- a/layout/src/capi.rs +++ b/layout/src/capi.rs @@ -19,16 +19,21 @@ use std::panic::catch_unwind; use crate::{compile, format, init_dirs, kvkind, vet, Kv}; /// 复刻 Go runtime / layout_file 的 findEntry:DFS /lib/ 找首个 `·init`,否则 "init"。 -fn find_entry(kv: &mut Kv, prefix: &str) -> String { +fn find_entry(kv: &mut Kv, prefix: &str, pkg: &str) -> String { for c in kv.list(prefix, false, true) { - let c = c.trim_end_matches('/'); - if c.ends_with("·init") { - return c.to_string(); + let base = c.trim_end_matches('/'); + if base.ends_with(".src") { + continue; } - let sub = format!("{prefix}{c}/"); - let entry = find_entry(kv, &sub); + let full = if pkg.is_empty() { base.to_string() } else { format!("{pkg}·{base}") }; + if base == "init" { + return full; + } + let next_pkg = if pkg.is_empty() { base.to_string() } else { format!("{pkg}/{base}") }; + let sub = if c.ends_with('/') { format!("{prefix}{base}/") } else { format!("{prefix}{base}·") }; + let entry = find_entry(kv, &sub, &next_pkg); if !entry.is_empty() { - return format!("{c}/{entry}"); + return entry; } } String::new() @@ -58,7 +63,7 @@ fn layout_core(src: &str, dsn: &str) -> Result { let mut kv = Kv::conn(dsn); init_dirs(&mut kv)?; compile(&mut kv, src)?; - let entry = find_entry(&mut kv, "/lib/"); + let entry = find_entry(&mut kv, "/lib/", ""); Ok(if entry.is_empty() { "init".to_string() } else { entry }) } diff --git a/layout/src/parser.rs b/layout/src/parser.rs index c9ef780d..ea94d398 100644 --- a/layout/src/parser.rs +++ b/layout/src/parser.rs @@ -1403,6 +1403,10 @@ impl Parser { return; } let s = inst.writes[0].clone(); + // 路径字面量(/ 开头)是完整 key,不是成员写,勿脱糖。 + if s.starts_with('/') { + return; + } let dt = s.find(keytree::MEMBER_SEP).unwrap_or(s.len()); let base = s[..dt].to_string(); let field = s[dt + keytree::MEMBER_SEP.len()..].to_string(); diff --git a/layout/src/scanner.rs b/layout/src/scanner.rs index d58ef161..c2c6ba7e 100644 --- a/layout/src/scanner.rs +++ b/layout/src/scanner.rs @@ -2,7 +2,7 @@ use std::fmt; -use super::{keytree, symbol}; +use super::symbol; #[derive(Clone, Copy)] pub struct Pos { diff --git a/runtime-rwirext_example/go/json/native_test.go b/runtime-rwirext_example/go/json/native_test.go index df779ce1..9d2804f2 100644 --- a/runtime-rwirext_example/go/json/native_test.go +++ b/runtime-rwirext_example/go/json/native_test.go @@ -34,3 +34,37 @@ func TestNativeDataTo(t *testing.T) { t.Errorf("dir with scatter got %s", got) } } + +// TestLocalContainer:object/stringkeymap 值(模拟 kvlang 局部变量,无尾 / 的帧槽路径)→ json。 +func TestLocalContainer(t *testing.T) { + c := rtConn(t) + defer disconnect(c) + + // object 局部变量:混合类型 + compact 数组成员 + if err := write(c, "/rt/profile", map[string]any{ + "name": "alice", "age": json.Number("42"), + "active": true, "score": json.Number("3.14"), + "nums": []interface{}{json.Number("1"), json.Number("2"), json.Number("3")}, + }); err != nil { + t.Fatal(err) + } + if got, _ := json.Marshal(build(c, "/rt/profile")); string(got) != `{"active":true,"age":42,"name":"alice","nums":[1,2,3],"score":3.14}` { + t.Errorf("obj got %s", got) + } + + // stringkeymap 局部变量:整型散 key 数组 + if err := write(c, "/rt/map", []interface{}{json.Number("10"), json.Number("20"), json.Number("30")}); err != nil { + t.Fatal(err) + } + if got, _ := json.Marshal(build(c, "/rt/map")); string(got) != `[10,20,30]` { + t.Errorf("map got %s", got) + } + + // stringkeymap 局部变量:字符串散 key 数组 + if err := write(c, "/rt/tags", []interface{}{"a", "b", "c"}); err != nil { + t.Fatal(err) + } + if got, _ := json.Marshal(build(c, "/rt/tags")); string(got) != `["a","b","c"]` { + t.Errorf("tags got %s", got) + } +} diff --git a/runtime-rwirext_example/go/json/tutorial/json.kv b/runtime-rwirext_example/go/json/tutorial/json.kv index c294ba64..437f51bd 100644 --- a/runtime-rwirext_example/go/json/tutorial/json.kv +++ b/runtime-rwirext_example/go/json/tutorial/json.kv @@ -7,22 +7,30 @@ # json·from(json) 反序列化 JSON 写回子树。目录→嵌套对象;compact 数组([]T)与 # 散 key 数组(name[0]..name[N-1])统一序列化为 JSON 数组。 # 期望输出: -# j = {"active":true,"age":42,"cont":[10,20,30],"grp":{"c":1,"d":2},"name":"alice","scat":[10,20,30],"score":3.14} +# j = {"active":true,"age":42,"cont":[10,20,30],"grp":{"c":1,"d":2},"name":"alice","nested":{"level1":{"count":3,"flags":[true,false,true],"level2":"deep"},"list":[1,2,3]},"scat":[10,20,30],"score":3.14} # name = alice # c = 1 # cont = [10, 20, 30] # scat = [10, 20, 30] # len = 3 3 +# level2 = deep +# count = 3 +# flags = [true, false, true] +# list = [1, 2, 3] rwfunc main() -> () { - 42 -> /data/age - "alice" -> /data/name - 3.14 -> /data/score - true -> /data/active - 1 -> /data/grp/c - 2 -> /data/grp/d + 42 -> /data·age + "alice" -> /data·name + 3.14 -> /data·score + true -> /data·active + 1 -> /data·grp·c + 2 -> /data·grp·d + "deep" -> /data·nested·level1·level2 + 3 -> /data·nested·level1·count + [true, false, true] -> /data·nested·level1·flags + [1, 2, 3] -> /data·nested·list a:[]int64 = [10, 20, 30] - [10, 20, 30] -> /data/cont - array·scatter(a) -> /data/scat + [10, 20, 30] -> /data·cont + array·scatter(a) -> /data·scat json·to(/data) -> j println("j =", j) json·from(j) -> /data2 @@ -33,6 +41,10 @@ rwfunc main() -> () { ndarray·numel(/data2·cont) -> n1 ndarray·numel(/data2·scat) -> n2 println("len =", n1, n2) + println("level2 =", /data2·nested·level1·level2) + println("count =", /data2·nested·level1·count) + println("flags =", /data2·nested·level1·flags) + println("list =", /data2·nested·list) } main() diff --git a/runtime-rwirext_example/go/json/tutorial/json_local.kv b/runtime-rwirext_example/go/json/tutorial/json_local.kv new file mode 100644 index 00000000..4349cd42 --- /dev/null +++ b/runtime-rwirext_example/go/json/tutorial/json_local.kv @@ -0,0 +1,33 @@ +# json_local: 局部变量 object/stringkeymap → json 转换 +# extern: 需先启动 redis 与 json rwirext 两个外部进程 +# 1) make kvspace # 起 redis 并清空 kvspace +# 2) go build -o /tmp/json-rwirext ./rwirext/json/cmd/ && /tmp/json-rwirext & +# 3) ./kvlang tutorial/10-rwirext/json_local.kv +# 语义: json·to 接受局部 object/stringkeymap 变量(resolve 到帧槽路径后序列化)。 +# object 局部变量({} + kv·set)→ JSON 对象;stringkeymap 局部变量({..} 散 key +# 数组)→ JSON 数组。散 key 数组成员名是坐标段 "[i]"。 +# 期望输出: +# profile = {"active":true,"age":42,"name":"alice","nums":[1,2,3],"score":3.14} +# map = [10,20,30] +# tags = ["a","b","c"] +rwfunc main() -> () { + profile = {} + kv·set(profile, "name", "alice") + kv·set(profile, "age", 42) + kv·set(profile, "active", true) + kv·set(profile, "score", 3.14) + nums = [1, 2, 3] + kv·set(profile, "nums", nums) + json·to(profile) -> j1 + println("profile =", j1) + + a = {10, 20, 30} + json·to(a) -> j2 + println("map =", j2) + + tags = {"a", "b", "c"} + json·to(tags) -> j3 + println("tags =", j3) +} + +main() diff --git a/runtime/src/builtin.c b/runtime/src/builtin.c index af023de7..89503a3a 100644 --- a/runtime/src/builtin.c +++ b/runtime/src/builtin.c @@ -987,22 +987,11 @@ int kvlangBuiltinScatter(kvlangFrame_t *f) { char *dst = kvlangBuiltinResolveWriteSlot(f->kv, fr, f->inst->writes[0].name); int al = kvlangXvalueArrayLen(&in[0]); if (al > 0) { - char **names = malloc(sizeof(char *) * (size_t)al); - for (int i = 0; i < al; i++) { - kvlangStrbuf_t s; kvlangStrbufInit(&s); kvlangStrbufPrintf(&s, "[%d]", i); - names[i] = kvlangStrbufDetach(&s); - } char err[256]; int32_t dims[1] = { al }; kvlangXvalue_t mark; map_marker(&mark, dims, 1); kvlangKvPair_t p0 = { dst, mark }; kvlangKvSet(f->kv, &p0, 1, err, sizeof err); kvlangXvalueFree(&mark); - char *dir = kvlangKeytreeMember(dst, ""); - kvlangXvalue_t mi; memindex(&mi, (const char *const *)names, al); - kvlangKvPair_t p1 = { dir, mi }; kvlangKvSet(f->kv, &p1, 1, err, sizeof err); - kvlangXvalueFree(&mi); free(dir); - for (int i = 0; i < al; i++) free(names[i]); - free(names); } for (int i = 0; i < al; i++) { int64_t c[1] = { i }; diff --git a/runtime/src/rwirext.c b/runtime/src/rwirext.c index ce86651b..70870a4e 100644 --- a/runtime/src/rwirext.c +++ b/runtime/src/rwirext.c @@ -77,6 +77,42 @@ char *kvlang_rwirextParams(void *kvspace, const char *pc) { return kvlangStrbufDetach(&b); } +/* stringkeymap 容器值:遍历 p· 成员 → "[e0, e1, ...]"(成员为 map 时递归)。 */ +static char *display_map(kvlangKv_t *k, const char *path) { + char *dir = kvlangKeytreeMember(path, ""); + char **names; + int cnt; + if (kvlangKvList(k, dir, false, false, &names, &cnt) != 0 || cnt <= 0) { + free(dir); + free(names); + return strdup("[]"); + } + kvlangStrbuf_t b; + kvlangStrbufInit(&b); + kvlangStrbufPutc(&b, '['); + for (int i = 0; i < cnt; i++) { + if (i) kvlangStrbufPuts(&b, ", "); + char *mk = kvlangKeytreeMember(path, names[i]); + kvlangXvalue_t mv; + kvlangXvalueZero(&mv); + kvlangKvGetOne(k, mk, &mv); + char *ms = kvlangXvalueNone(&mv) + ? strdup("") + : strcmp(kvlangXvalueKind(&mv), KVSPACE_KIND_MAP) == 0 + ? display_map(k, mk) + : kvlangXvalueValueString(&mv); + kvlangStrbufPuts(&b, ms); + free(ms); + kvlangXvalueFree(&mv); + free(mk); + free(names[i]); + } + kvlangStrbufPutc(&b, ']'); + free(names); + free(dir); + return kvlangStrbufDetach(&b); +} + /* 解析读参 idx 为字符串(变量 → 帧槽值;路径 → 该路径下的值)。 */ char *kvlang_rwirextResolveRead(void *kvspace, const char *pc, int idx) { kvlangKv_t k = {kvspace}; @@ -97,7 +133,16 @@ char *kvlang_rwirextResolveRead(void *kvspace, const char *pc, int idx) { kvlangXvalueZero(&v); kvlangBuiltinResolveReadValue(&k, fr, inst.reads[idx].name, &inst.reads[idx].val, &v); - char *s = kvlangXvalueNone(&v) ? strdup("") : kvlangXvalueValueString(&v); + char *s; + if (kvlangXvalueNone(&v)) { + s = strdup(""); + } else if (strcmp(kvlangXvalueKind(&v), KVSPACE_KIND_MAP) == 0) { + char *path = kvlangBuiltinResolveWriteSlot(&k, fr, inst.reads[idx].name); + s = display_map(&k, path); + free(path); + } else { + s = kvlangXvalueValueString(&v); + } kvlangXvalueFree(&v); free(fr); kvlangRwirInstFree(&inst);