diff --git a/layout/src/builtin.rs b/layout/src/builtin.rs index 5220f802..d76fca15 100644 --- a/layout/src/builtin.rs +++ b/layout/src/builtin.rs @@ -1,5 +1,5 @@ //! builtin 辅助(对齐 rwir/builtin 中 lower/layout 依赖的部分: -//! NumOp/IsNumKind/WiderNumKind/OpKind/TryParseNumber/IsNativeRwir/IsGlobalRwir)。 +//! NumOp/IsNumKind/WiderNumKind/OpKind/TryParseNumber)。 use super::ffi; @@ -149,39 +149,3 @@ pub fn try_parse_number(s: &str) -> Option> { } None } - -// ── native / global rwir 判定(layout 命名空间用) ─────────────────── - -/// VM 原生 rwir 中不带 `.` 的 opcode(带 `.` 的由 MemberSep 检查兜底)。 -fn native_rwir_set() -> &'static [&'static str] { - &[ - // 单字 builtin - "array", "obj", "map", "debugger", - // cast kind - "bool", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", - "float32", "float64", "char/utf8", "char/ascii", "char/utf32", - // 数值 op word + glyph - "add", "+", "sub", "-", "mul", "×", "div", "÷", "mod", "%", - "eq", "==", "neq", "!=", "≠", "lt", "<", "gt", ">", "le", "<=", "≤", "ge", ">=", "≥", - "and", "&&", "or", "||", "not", "!", - "bitand", "&", "bitor", "|", "bitxor", "^", "shl", "<<", "shr", ">>", - "sqrt", "√", "neg", "abs", "sign", "max", "min", "pow", "exp", "log", - // kv.* 树操作 - "kv.get", "kv.set", "kv.del", "kv.deltree", "kv.list", "kv.mkindex", - "kv.extindex", "kv.rmindexext", "kv.watch", - // xv.* 形状内省与多维元素访问 - "xv.numel", "xv.dim", "xv.shape", "xv.at", "xv.set", "xv.reshape", - ] -} - -pub fn is_native_rwir(opcode: &str) -> bool { - native_rwir_set().contains(&opcode) -} - -fn global_rwir_set() -> &'static [&'static str] { - &["print", "println", "cerr", "input", "json.to", "json.from"] -} - -pub fn is_global_rwir(opcode: &str) -> bool { - global_rwir_set().contains(&opcode) -} diff --git a/layout/src/code.rs b/layout/src/code.rs index 34aa132a..510feb14 100644 --- a/layout/src/code.rs +++ b/layout/src/code.rs @@ -30,11 +30,23 @@ pub fn compile(kv: &mut Kv, src: &str) -> Result<(), String> { return Err("parse: error-level diagnostics — refusing to load".to_string()); } + // 用户函数名 → 有效包名。layout 只对这些名字加包前缀(正向识别用户函数), + // 其余 opcode(native / 扩展 rwir)原样落盘,由 runtime 查 /lib/ 的 XValue kind 判定。 + let mut user_pkg: HashMap = HashMap::new(); + for func in &file.funcs { + let p = if func.pkg.is_empty() { file.package.clone() } else { func.pkg.clone() }; + user_pkg.insert(func.sig.name.clone(), p); + } + for decl in &file.rwir_decls { + let p = if decl.pkg.is_empty() { file.package.clone() } else { decl.pkg.clone() }; + user_pkg.insert(decl.sig.name.clone(), p); + } + let mut any_code = false; for func in &file.funcs { let pkg = if func.pkg.is_empty() { file.package.clone() } else { func.pkg.clone() }; let mut lowered = lower::lower_func(func); - write_func(kv, &pkg, &mut lowered); + write_func(kv, &pkg, &mut lowered, &user_pkg); any_code = true; } for decl in &file.rwir_decls { @@ -53,7 +65,7 @@ pub fn compile(kv: &mut Kv, src: &str) -> Result<(), String> { pkg: String::new(), }; let mut lowered = lower::lower_func(&init_fn); - write_func(kv, "", &mut lowered); + write_func(kv, "", &mut lowered, &user_pkg); any_code = true; } @@ -100,7 +112,7 @@ pub fn vet(src: &str) -> Result<(), String> { } /// 写函数到 /lib/:签名(rwfunc)、源码、参数 Ptr、指令体。 -pub fn write_func(kv: &mut Kv, pkg: &str, fn_: &mut Func) { +pub fn write_func(kv: &mut Kv, pkg: &str, fn_: &mut Func, user_pkg: &HashMap) { let mut type_map = lower::infer_types(fn_); lower::specialize(fn_, &type_map); let func_dir = keytree::lib_func(pkg, &fn_.sig.name); @@ -133,7 +145,7 @@ pub fn write_func(kv: &mut Kv, pkg: &str, fn_: &mut Func) { } let _ = kv.set(&pairs); - write_body(kv, pkg, &fn_.sig.name, &fn_.body, &mut type_map, 1); + write_body(kv, pkg, &fn_.sig.name, &fn_.body, &mut type_map, 1, user_pkg); } /// 写用户声明的 rwir(无体)到 /lib/。 @@ -147,11 +159,11 @@ pub fn write_rwir_decl(kv: &mut Kv, decl: &RwirDecl) { } /// 将 body 写入 /lib/// 下。offset 起始 idx(顶层函数=1)。 -fn write_body(kv: &mut Kv, pkg: &str, name: &str, body: &[Stmt], type_map: &mut HashMap, offset: i32) { +fn write_body(kv: &mut Kv, pkg: &str, name: &str, body: &[Stmt], type_map: &mut HashMap, offset: i32, user_pkg: &HashMap) { let prefix = keytree::lib_func(pkg, name); let mut idx = offset; for st in body { - write_stmt(kv, st, &prefix, &mut idx, type_map, pkg); + write_stmt(kv, st, &prefix, &mut idx, type_map, pkg, user_pkg); } } @@ -162,6 +174,7 @@ fn write_stmt( idx: &mut i32, type_map: &mut HashMap, pkg: &str, + user_pkg: &HashMap, ) { match st { Stmt::Instruction(s) => { @@ -173,9 +186,7 @@ fn write_stmt( } let (mut opcode, reads) = s.flat(); if !pkg.is_empty() - && !builtin::is_native_rwir(&opcode) - && !builtin::is_global_rwir(&opcode) - && !is_control_op(&opcode) + && user_pkg.get(&opcode).map_or(false, |p| p == pkg) && !opcode.contains(keytree::MEMBER_SEP) && !opcode.starts_with("/lib/") && symbol::lookup(&opcode).word != "assign" @@ -207,7 +218,7 @@ fn write_stmt( let scope_prefix = format!("{prefix}/{}", s.label); let mut scope_idx = 0; for child in &s.body { - write_stmt_scope(kv, child, &scope_prefix, &mut scope_idx, type_map, pkg, prefix); + write_stmt_scope(kv, child, &scope_prefix, &mut scope_idx, type_map, pkg, prefix, user_pkg); } } _ => {} @@ -222,6 +233,7 @@ fn write_stmt_scope( type_map: &mut HashMap, pkg: &str, func_prefix: &str, + user_pkg: &HashMap, ) { match st { Stmt::Instruction(s) => { @@ -233,9 +245,7 @@ fn write_stmt_scope( } let (mut opcode, reads) = s.flat(); if !pkg.is_empty() - && !builtin::is_native_rwir(&opcode) - && !builtin::is_global_rwir(&opcode) - && !is_control_op(&opcode) + && user_pkg.get(&opcode).map_or(false, |p| p == pkg) && !opcode.contains(keytree::MEMBER_SEP) && !opcode.starts_with("/lib/") && symbol::lookup(&opcode).word != "assign" @@ -267,7 +277,7 @@ fn write_stmt_scope( let child_prefix = format!("{func_prefix}/{}", s.label); let mut child_idx = 0; for child in &s.body { - write_stmt_scope(kv, child, &child_prefix, &mut child_idx, type_map, pkg, func_prefix); + write_stmt_scope(kv, child, &child_prefix, &mut child_idx, type_map, pkg, func_prefix, user_pkg); } } _ => {} @@ -304,10 +314,6 @@ fn count_direct_insts(body: &[Stmt]) -> i32 { body.iter().filter(|st| matches!(st, Stmt::Instruction(_))).count() as i32 } -fn is_control_op(op: &str) -> bool { - matches!(op, "call" | "return" | "br" | "goto") -} - fn is_literal(s: &str) -> bool { if s.is_empty() { return false; diff --git a/layout/src/type_expr.rs b/layout/src/type_expr.rs index 0e2f4b40..98c96754 100644 --- a/layout/src/type_expr.rs +++ b/layout/src/type_expr.rs @@ -17,7 +17,7 @@ fn known_kind(k: &str) -> bool { "bool" | "int8" | "int16" | "int32" | "int64" | "uint8" | "uint16" | "uint32" | "uint64" | "float32" | "float64" | "char/utf32" | "char/utf8" | "char/ascii" | "objindex" | "strkeymapindex" | "index" | "extindex" | "rwir" | "rwfunc" | "scope" | "time" - | "duration" | "json" + | "duration" ) } @@ -132,11 +132,11 @@ mod tests { fn valid() { for e in [ "int64", "uint8", "float32", "bool", "any", - "char/utf8", "char/utf32", "char/ascii", "objindex", "strkeymapindex", "json", "index", + "char/utf8", "char/utf32", "char/ascii", "objindex", "strkeymapindex", "index", "[]float32", "[2]float32", "[2,3]float32", "[2,3,4]float64", "[?,768]float32", "[?,?]int8", "int64|float64", "[2,3]float32|float32", "[]float32|[]float64", - "bool|char/utf8", "index|objindex", "json|strkeymapindex", + "bool|char/utf8", "index|objindex", "any...", "int64|float64...", "[]float32...", ] { assert!(valid_type_expr(e), "{e} should be valid"); diff --git a/runtime-rwirext_example/go/json/edge_test.go b/runtime-rwirext_example/go/json/edge_test.go new file mode 100644 index 00000000..6dac9423 --- /dev/null +++ b/runtime-rwirext_example/go/json/edge_test.go @@ -0,0 +1,31 @@ +package json + +import ( + "encoding/json" + "fmt" + "testing" +) + +func TestEdgeKeys(t *testing.T) { + c := rtConn(t) + defer disconnect(c) + cases := []string{ + `{"a.b":1}`, // 点号 key(应拒绝) + `{"a/b":1}`, // 斜杠 key(应拒绝) + `{"a[b]":1}`, // 方括号 key(应拒绝) + `{"a\nb":1}`, // 换行 key(应拒绝) + `{"0":"zero","1":"one"}`, // 数字字符串 key(合法) + `{"":1}`, // 空 key(应拒绝) + `{"a":"v.b/c\nx"}`, // 值含特殊字符(应允许) + } + for i, in := range cases { + root := "/rt/edge" + fmt.Sprint(i) + err := writeMap(c, root, fromJSON([]byte(in))) + status := "OK " + if err != nil { + status = "REJ " + } + out, _ := json.Marshal(buildMap(c, root)) + fmt.Printf("[%s] in: %s\n err: %v\n out: %s\n", status, in, err, out) + } +} diff --git a/runtime-rwirext_example/go/json/json.go b/runtime-rwirext_example/go/json/json.go index 03d658d3..933e1d40 100644 --- a/runtime-rwirext_example/go/json/json.go +++ b/runtime-rwirext_example/go/json/json.go @@ -44,6 +44,7 @@ import ( "bytes" "encoding/binary" "encoding/json" + "fmt" "math" "sort" "strconv" @@ -347,46 +348,6 @@ func jsonValueToTLV(v interface{}) []byte { } } -func jsonArrayToTLV(arr []interface{}) []byte { - if len(arr) == 0 { - return nil - } - switch arr[0].(type) { - case json.Number: - allInt := true - for _, e := range arr { - if _, err := e.(json.Number).Int64(); err != nil { - allInt = false - break - } - } - if allInt { - raw := make([]byte, 0, len(arr)*8) - for _, e := range arr { - i, _ := e.(json.Number).Int64() - raw = append(raw, u64(uint64(i))...) - } - return constructTLV("int64", raw, len(arr)) - } - raw := make([]byte, 0, len(arr)*8) - for _, e := range arr { - f, _ := e.(json.Number).Float64() - raw = append(raw, u64bits(f)...) - } - return constructTLV("float64", raw, len(arr)) - case bool: - raw := make([]byte, len(arr)) - for i, e := range arr { - if e.(bool) { - raw[i] = 1 - } - } - return constructTLV("bool", raw, len(arr)) - default: - return nil - } -} - func u64(v uint64) []byte { b := make([]byte, 8) binary.LittleEndian.PutUint64(b, v) @@ -399,63 +360,124 @@ func u64bits(f float64) []byte { } // ── KV 子树 ↔ map[string]any ─────────────────────────────────────── - -func splitArrayName(name string) (base string, idx int, ok bool) { - lt := strings.LastIndex(name, "[") - if lt <= 0 || !strings.HasSuffix(name, "]") { - return "", 0, false +// objindex(对象)/ strkeymapindex(数组)承载复杂结构:marker 在 path., +// 成员在 path.(key 恒字符串,数组下标为数字字符串)。后端按 . 成员自动维护 index。 + +func mkIndexMarker(kind string, names []string) []byte { + body := make([]byte, 4) + binary.LittleEndian.PutUint32(body, uint32(len(names))) + body = append(body, []byte(strings.Join(names, "\n"))...) + return constructTLV(kind, body, 1) +} + +// validateKey:JSON 对象 key 不能含影响 kvspace 存储分隔的字符(§5.4)。 +// 空串、/ . [ ] \n \r \0 U+2025 及 ASCII 控制字符一律拒绝(不静默丢键、不转义)。 +func validateKey(k string) error { + if k == "" { + return fmt.Errorf("json: empty key rejected") + } + for _, r := range k { + if r == '/' || r == '.' || r == '[' || r == ']' || r == '\n' || r == '\r' || + r == 0 || r < 0x20 || r == '‥' { + return fmt.Errorf("json: forbidden char %q in key %q", r, k) + } } - i, err := strconv.Atoi(name[lt+1 : len(name)-1]) - if err != nil { - return "", 0, false + return nil +} + +func writeValue(c unsafe.Pointer, path string, v interface{}) error { + switch t := v.(type) { + case nil: + setTLV(c, path, nil) // None:key 存在、值为空字节(JSON null) + case map[string]any: + return writeObj(c, path, t) + case []interface{}: + return writeArr(c, path, t) + default: + setTLV(c, path, jsonValueToTLV(v)) } - return name[:lt], i, true + return nil } -func buildMap(c unsafe.Pointer, root string) map[string]any { - m := map[string]any{} - scat := map[string][]int{} - for _, child := range list(c, root+"/") { - if child == "" { - continue - } - if strings.HasSuffix(child, "/") { // index 目录 → 递归 - name := strings.TrimSuffix(child, "/") - m[name] = buildMap(c, root+"/"+name) - continue +func writeObj(c unsafe.Pointer, path string, m map[string]any) error { + keys := make([]string, 0, len(m)) + for k := range m { + if err := validateKey(k); err != nil { + return err } - if base, idx, ok := splitArrayName(child); ok { - scat[base] = append(scat[base], idx) - continue + keys = append(keys, k) + } + sort.Strings(keys) + setTLV(c, path+".", mkIndexMarker("objindex", keys)) + for _, k := range keys { + if err := writeValue(c, path+"."+k, m[k]); err != nil { + return err } - kind, raw, arrLen := parseTLV(getTLV(c, root+"/"+child)) - m[child] = tlvToJSONValue(kind, raw, arrLen) - } - for base, idxs := range scat { - sort.Ints(idxs) - arr := make([]interface{}, len(idxs)) - for i, idx := range idxs { - kind, raw, arrLen := parseTLV(getTLV(c, root+"/"+base+"["+strconv.Itoa(idx)+"]")) - arr[i] = tlvToJSONValue(kind, raw, arrLen) + } + return nil +} + +func writeArr(c unsafe.Pointer, path string, arr []interface{}) error { + keys := make([]string, len(arr)) + for i := range arr { + keys[i] = strconv.Itoa(i) + } + setTLV(c, path+".", mkIndexMarker("strkeymapindex", keys)) + for i, v := range arr { + if err := writeValue(c, path+"."+strconv.Itoa(i), v); err != nil { + return err } - m[base] = arr + } + return nil +} + +func readValue(c unsafe.Pointer, path string) interface{} { + kind, _, _ := parseTLV(getTLV(c, path+".")) + switch kind { + case "objindex": + return readObj(c, path) + case "strkeymapindex": + return readArr(c, path) + } + kind, raw, arrLen := parseTLV(getTLV(c, path)) + if kind == "" { + return nil // None → JSON null + } + return tlvToJSONValue(kind, raw, arrLen) +} + +func readObj(c unsafe.Pointer, path string) map[string]any { + m := map[string]any{} + for _, name := range list(c, path+".") { + m[name] = readValue(c, path+"."+name) } return m } -func writeMap(c unsafe.Pointer, root string, m map[string]any) { - for k, v := range m { - childPath := root + "/" + k - switch t := v.(type) { - case map[string]any: - mkindex(c, childPath+"/") - writeMap(c, childPath, t) - case []interface{}: - setTLV(c, childPath, jsonArrayToTLV(t)) - default: - setTLV(c, childPath, jsonValueToTLV(v)) +func readArr(c unsafe.Pointer, path string) []interface{} { + idxs := make([]int, 0, 8) + for _, n := range list(c, path+".") { + if i, err := strconv.Atoi(n); err == nil { + idxs = append(idxs, i) } } + sort.Ints(idxs) + arr := make([]interface{}, len(idxs)) + for i, idx := range idxs { + arr[i] = readValue(c, path+"."+strconv.Itoa(idx)) + } + return arr +} + +func writeMap(c unsafe.Pointer, root string, m map[string]any) error { + return writeObj(c, root, m) +} + +func buildMap(c unsafe.Pointer, root string) map[string]any { + if m, ok := readValue(c, root).(map[string]any); ok { + return m + } + return map[string]any{} } func fromJSON(data []byte) map[string]any { @@ -502,13 +524,17 @@ func doTo(c unsafe.Pointer, pc string, readNames, writeNames []string) { setChar(c, dest, string(data)) } -func doFrom(c unsafe.Pointer, pc string, readNames, writeNames []string) { +func doFrom(c unsafe.Pointer, pc string, readNames, writeNames []string, vid string) { src := resolveRead(c, pc, 0) root := writeNames[0] if !strings.HasPrefix(root, "/") { root = resolveWrite(c, pc, 0) } - writeMap(c, root, fromJSON([]byte(src))) + if err := writeMap(c, root, fromJSON([]byte(src))); err != nil { + setChar(c, "/vthread/"+vid+"/‥status", "error") + setChar(c, "/vthread/"+vid+"/‥error/msg", err.Error()) + return + } } func serveOp(c unsafe.Pointer, o op) { @@ -532,7 +558,7 @@ func serveOp(c unsafe.Pointer, o op) { if opcode == "json.to" { doTo(c, pc, readNames, writeNames) } else { - doFrom(c, pc, readNames, writeNames) + doFrom(c, pc, readNames, writeNames, vid) } nxt := nextPC(pc) @@ -542,11 +568,19 @@ func serveOp(c unsafe.Pointer, o op) { } } -// Serve 常驻循环:注册 + 监控 .todo + 批量执行 + 交还 PC。 -func Serve(dsn string) { +func connect(dsn string) unsafe.Pointer { cd := cstr(dsn) defer C.free(unsafe.Pointer(cd)) - c := C.kvspaceConnect(cd) + return C.kvspaceConnect(cd) +} + +func disconnect(c unsafe.Pointer) { + C.kvspaceFree(c) +} + +// Serve 常驻循环:注册 + 监控 .todo + 批量执行 + 交还 PC。 +func Serve(dsn string) { + c := connect(dsn) if c == nil { return } diff --git a/runtime-rwirext_example/go/json/random_test.go b/runtime-rwirext_example/go/json/random_test.go new file mode 100644 index 00000000..891fbb45 --- /dev/null +++ b/runtime-rwirext_example/go/json/random_test.go @@ -0,0 +1,130 @@ +package json + +import ( + "encoding/json" + "fmt" + "math/rand" + "reflect" + "strconv" + "strings" + "testing" +) + +// genValue 随机生成 JSON 值(用 json.Number 保留数值精度)。 +func genValue(r *rand.Rand, depth int) interface{} { + if depth > 4 { + return genScalar(r) + } + switch r.Intn(8) { + case 0: + return nil + case 1: + return r.Intn(2) == 0 + case 2: + return json.Number(strconv.Itoa(r.Intn(2000) - 1000)) + case 3: + return json.Number(fmt.Sprintf("%.4f", r.Float64()*2000-1000)) + case 4: + return genString(r) + case 5: // array + n := r.Intn(6) + arr := make([]interface{}, n) + for i := range arr { + arr[i] = genValue(r, depth+1) + } + return arr + case 6: // object + n := r.Intn(6) + m := map[string]interface{}{} + for i := 0; i < n; i++ { + m[genKey(r)] = genValue(r, depth+1) + } + return m + default: + return genScalar(r) + } +} + +func genScalar(r *rand.Rand) interface{} { + switch r.Intn(4) { + case 0: + return nil + case 1: + return r.Intn(2) == 0 + case 2: + return json.Number(strconv.Itoa(r.Intn(2000) - 1000)) + default: + return genString(r) + } +} + +func genString(r *rand.Rand) string { + pool := []string{"", "a", "hello", "中文", "emoji🚀", "line\nbreak", "quote\"x", "back\\slash", "tab\tx", "0"} + return pool[r.Intn(len(pool))] +} + +func genKey(r *rand.Rand) string { + pool := []string{"a", "b", "c", "name", "x1", "_under", "k-2", "中文键"} + return pool[r.Intn(len(pool))] +} + +// normalize 把 json.Number 转成 int64/float64,供 reflect.DeepEqual 语义比较。 +func normalize(v interface{}) interface{} { + switch t := v.(type) { + case json.Number: + if i, err := t.Int64(); err == nil { + return i + } + f, _ := t.Float64() + return f + case map[string]interface{}: + m := map[string]interface{}{} + for k, vv := range t { + m[k] = normalize(vv) + } + return m + case []interface{}: + arr := make([]interface{}, len(t)) + for i, vv := range t { + arr[i] = normalize(vv) + } + return arr + default: + return v + } +} + +func canonical(s string) interface{} { + var v interface{} + dec := json.NewDecoder(strings.NewReader(s)) + dec.UseNumber() + if err := dec.Decode(&v); err != nil { + return nil + } + return normalize(v) +} + +func TestRandomRoundtrip(t *testing.T) { + c := rtConn(t) + defer disconnect(c) + r := rand.New(rand.NewSource(42)) + fail := 0 + for i := 0; i < 300; i++ { + v := genValue(r, 0) + in, _ := json.Marshal(v) + root := "/rt/rand" + strconv.Itoa(i) + m := fromJSON(in) + if m == nil { + continue + } + _ = writeMap(c, root, m) + out, _ := json.Marshal(buildMap(c, root)) + if !reflect.DeepEqual(canonical(string(in)), canonical(string(out))) { + fail++ + if fail <= 8 { + fmt.Printf("[FAIL #%d]\n in : %s\n out: %s\n", i, in, out) + } + } + } + fmt.Printf("random roundtrip: 300 cases, %d mismatches\n", fail) +} diff --git a/runtime-rwirext_example/go/json/roundtrip_test.go b/runtime-rwirext_example/go/json/roundtrip_test.go new file mode 100644 index 00000000..a196a8a7 --- /dev/null +++ b/runtime-rwirext_example/go/json/roundtrip_test.go @@ -0,0 +1,56 @@ +package json + +import ( + "encoding/json" + "fmt" + "os" + "testing" + "unsafe" +) + +func rtConn(t *testing.T) unsafe.Pointer { + dsn := os.Getenv("KVSPACE") + if dsn == "" { + dsn = "redis://127.0.0.1:6379" + } + c := connect(dsn) + if c == nil { + t.Fatalf("connect failed: %s", dsn) + } + return c +} + +func roundTrip(c unsafe.Pointer, root, input string) string { + m := fromJSON([]byte(input)) + if m == nil { + return "" + } + _ = writeMap(c, root, m) + out, _ := json.Marshal(buildMap(c, root)) + return string(out) +} + +func TestRepresentative(t *testing.T) { + c := rtConn(t) + defer disconnect(c) + cases := []struct{ name, in string }{ + {"scalar-obj", `{"a":1,"b":true,"c":"x","d":3.14,"e":null}`}, + {"nested-obj", `{"a":{"b":{"c":1}},"d":2}`}, + {"obj-array", `{"list":[{"x":1},{"y":2}]}`}, + {"str-array", `{"list":["a","b","c"]}`}, + {"mixed-array", `{"list":[1,"a",true,null,3.14]}`}, + {"nested-array", `{"m":[[1,2],[3,4]]}`}, + {"num-array", `{"list":[1,2,3]}`}, + {"empty-obj", `{}`}, + {"empty-array", `{"list":[]}`}, + {"deep", `{"a":{"b":[1,{"c":[true,false]},"z"]}}`}, + } + for _, cc := range cases { + got := roundTrip(c, "/rt/"+cc.name, cc.in) + eq := "OK " + if got != cc.in { + eq = "FAIL" + } + fmt.Printf("[%s] %s\n in : %s\n out: %s\n", eq, cc.name, cc.in, got) + } +} diff --git a/tutorial/10-rwirext/json.kv b/runtime-rwirext_example/go/json/tutorial/json.kv similarity index 100% rename from tutorial/10-rwirext/json.kv rename to runtime-rwirext_example/go/json/tutorial/json.kv diff --git a/runtime-rwirext_example/go/json/tutorial/json_roundtrip.kv b/runtime-rwirext_example/go/json/tutorial/json_roundtrip.kv new file mode 100644 index 00000000..240da85c --- /dev/null +++ b/runtime-rwirext_example/go/json/tutorial/json_roundtrip.kv @@ -0,0 +1,16 @@ +# json: json.to / json.from 往返(objindex / strkeymapindex 承载复杂结构) +# extern: 需先启动 redis 与 json rwirext 两个外部进程 +# 1) redis-server --port 6379 +# 2) ./bin/json-rwirext & +# 3) ./bin/kvlang <本文件> +# 语义: json.from(json) 把 JSON 反序列化进 KV 树(对象→objindex、数组→strkeymapindex、 +# null→None 空字节);json.to(root) 把 KV 子树序列化回 JSON,二者无损等价。 +# 期望输出: +# roundtrip = {"a":[1,{"b":"x"}],"list":[{"x":1},{"y":2}],"mixed":[1,"a",true,null,3.14],"n":null,"s":["a","b"]} +rwfunc main() -> () { + json.from("{\"a\":[1,{\"b\":\"x\"}],\"list\":[{\"x\":1},{\"y\":2}],\"mixed\":[1,\"a\",true,null,3.14],\"n\":null,\"s\":[\"a\",\"b\"]}") -> /t + json.to(/t) -> out + println("roundtrip =", out) +} + +main() diff --git a/tutorial/14-numpy/01-creation.kv b/runtime-rwirext_example/py/numpy/tutorial/01-creation.kv similarity index 100% rename from tutorial/14-numpy/01-creation.kv rename to runtime-rwirext_example/py/numpy/tutorial/01-creation.kv diff --git a/tutorial/14-numpy/02-elementwise.kv b/runtime-rwirext_example/py/numpy/tutorial/02-elementwise.kv similarity index 100% rename from tutorial/14-numpy/02-elementwise.kv rename to runtime-rwirext_example/py/numpy/tutorial/02-elementwise.kv diff --git a/tutorial/14-numpy/03-linalg.kv b/runtime-rwirext_example/py/numpy/tutorial/03-linalg.kv similarity index 100% rename from tutorial/14-numpy/03-linalg.kv rename to runtime-rwirext_example/py/numpy/tutorial/03-linalg.kv diff --git a/tutorial/14-numpy/04-reduce.kv b/runtime-rwirext_example/py/numpy/tutorial/04-reduce.kv similarity index 100% rename from tutorial/14-numpy/04-reduce.kv rename to runtime-rwirext_example/py/numpy/tutorial/04-reduce.kv diff --git a/tutorial/14-numpy/05-manipulation.kv b/runtime-rwirext_example/py/numpy/tutorial/05-manipulation.kv similarity index 100% rename from tutorial/14-numpy/05-manipulation.kv rename to runtime-rwirext_example/py/numpy/tutorial/05-manipulation.kv diff --git a/tutorial/14-numpy/06-pipeline.kv b/runtime-rwirext_example/py/numpy/tutorial/06-pipeline.kv similarity index 100% rename from tutorial/14-numpy/06-pipeline.kv rename to runtime-rwirext_example/py/numpy/tutorial/06-pipeline.kv diff --git a/runtime-rwirext_example/rust/term/src/main.rs b/runtime-rwirext_example/rust/term/src/main.rs index 158c33fa..5fc06457 100644 --- a/runtime-rwirext_example/rust/term/src/main.rs +++ b/runtime-rwirext_example/rust/term/src/main.rs @@ -71,11 +71,17 @@ unsafe extern "C" { nw: c_int, sig: *const c_char, ) -> c_int; - fn kvlang_rwirextPrintLine( + fn kvlang_rwirextHandoff( kvspace: *mut c_void, + vtid: *const c_char, pc: *const c_char, - rawnl: *mut c_int, - cerr: *mut c_int, + ) -> c_int; + fn kvlang_rwirextIsExt(kvspace: *mut c_void, opcode: *const c_char) -> c_int; + fn kvlang_rwirextParams(kvspace: *mut c_void, pc: *const c_char) -> *mut c_char; + fn kvlang_rwirextResolveRead( + kvspace: *mut c_void, + pc: *const c_char, + idx: c_int, ) -> *mut c_char; fn kvlang_rwirextNextPc(pc: *const c_char) -> *mut c_char; } @@ -247,22 +253,42 @@ fn main() { std::process::exit(1); } - // RunSeq:连续处理己方 print,遇非己方停下(c 停在非己方 pc) + // RunSeq:连续处理己方 print/println/cerr,遇非己方指令停下(c 停在非己方 pc) let mut c = take(pc); - loop { - let mut rawnl = 0i32; - let mut is_cerr = 0i32; - let p = unsafe { kvlang_rwirextPrintLine(kv, cs(&c).as_ptr(), &mut rawnl, &mut is_cerr) }; - if p.is_null() { - break; + let non_print = loop { + let params = take(unsafe { kvlang_rwirextParams(kv, cs(&c).as_ptr()) }); + let mut it = params.split('\n'); + let opcode = it.next().unwrap_or(""); + let reads: Vec<&str> = it.collect(); + let (sep, rawnl, is_cerr) = match opcode { + "print" => ("", 1, 0), + "println" => (" ", 0, 0), + "cerr" => (" ", 0, 1), + _ => break opcode.to_string(), + }; + let nr = reads.len(); + let mut line = String::new(); + for i in 0..nr { + if i > 0 { + line.push_str(sep); + } + let d = take(unsafe { kvlang_rwirextResolveRead(kv, cs(&c).as_ptr(), i as c_int) }); + line.push_str(&d); } - let line = take(p); print_line(&line, rawnl, is_cerr); c = take(unsafe { kvlang_rwirextNextPc(cs(&c).as_ptr()) }); - } + }; - // 写回非己方 pc,让 runtime 从它继续 - kv_set(kv, &vpc, &c); + // 外部扩展 rwir(json.to/numpy…):handoff 给对应扩展进程,扩展写回下一 PC 并 signal .done + if !non_print.is_empty() && unsafe { kvlang_rwirextIsExt(kv, cs(&non_print).as_ptr()) } != 0 { + if unsafe { kvlang_rwirextHandoff(kv, cs(&vid).as_ptr(), cs(&c).as_ptr()) } != 0 { + eprintln!("kvlang: handoff {non_print} failed at {c}"); + std::process::exit(1); + } + } else { + // native/control/帧结束:写回 pc 让 runtime 继续/判 done + kv_set(kv, &vpc, &c); + } } unsafe { kvspaceFree(kv) }; diff --git a/runtime/include/kvlang_rwirext.h b/runtime/include/kvlang_rwirext.h index 8246c534..c74a0a49 100644 --- a/runtime/include/kvlang_rwirext.h +++ b/runtime/include/kvlang_rwirext.h @@ -14,13 +14,14 @@ int kvlang_rwirextRegister(void *kvspace, const char *opcode, int32_t nr, int32_t nw, const char *sig); -/* 从 pc 解码指令;若 opcode ∈ {print,println,cerr},resolve 全部 reads 并 - * display, 以自身 sep(print 无分隔、println/cerr - * 空格分隔)连接返回(malloc); 非己方指令返回 NULL(调用方应停止 RunSeq)。 - * rawnl/cerr 输出该指令的换行/流属性:print→rawnl=1(不换行,stdout); - * println→rawnl=0(换行,stdout);cerr→rawnl=0,cerr=1(换行,stderr)。 */ -char *kvlang_rwirextPrintLine(void *kvspace, const char *pc, int *rawnl, - int *cerr); +/* 外部扩展 handoff:写 /lib//.todo 并阻塞 watch .done(30s + * 超时)。RETURN 模式下 term 遇非己方 ext rwir(如 json.to/numpy)时调用,把 + * 该指令交给对应扩展进程,扩展处理后写回下一 PC 并 signal .done。返回 0 成功, + * -1 失败/超时。 */ +int kvlang_rwirextHandoff(void *kvspace, const char *vtid, const char *pc); + +/* opcode 是否外部扩展 rwir(/lib/ kind=rwir)。非 ext(native/control/用户函数)返回 0。 */ +int kvlang_rwirextIsExt(void *kvspace, const char *opcode); /* 当前指令的下一条 PC(malloc) */ char *kvlang_rwirextNextPc(const char *pc); diff --git a/runtime/src/kvcpu.c b/runtime/src/kvcpu.c index 1ec1568b..d868d756 100644 --- a/runtime/src/kvcpu.c +++ b/runtime/src/kvcpu.c @@ -420,7 +420,7 @@ static bool is_copy_op(const char *opcode) { return strcmp(opcode, "=") == 0; } -static bool is_ext_rwir(kvlangKv_t *kv, const char *opcode) { +bool is_ext_rwir(kvlangKv_t *kv, const char *opcode) { if (opcode[0] == '/') return false; char *rk = kvlangKeytreeRwir(opcode); kvlangXvalue_t v; kvlangXvalueZero(&v); @@ -432,7 +432,7 @@ static bool is_ext_rwir(kvlangKv_t *kv, const char *opcode) { static int64_t handoff_seq = 0; -static int handoff_external_rwir(kvlangKv_t *kv, const char *vtid, const char *pc, kvlangRwirInst_t *inst) { +int handoff_external_rwir(kvlangKv_t *kv, const char *vtid, const char *pc, kvlangRwirInst_t *inst) { char *base = kvlangKeytreeRwir(inst->opcode); kvlangStrbuf_t todo, done; kvlangStrbufInit(&todo); kvlangStrbufInit(&done); kvlangStrbufPrintf(&todo, "%s/.todo<%s>", base, vtid); diff --git a/runtime/src/runtime_internal.h b/runtime/src/runtime_internal.h index 1aa36e77..405a29f9 100644 --- a/runtime/src/runtime_internal.h +++ b/runtime/src/runtime_internal.h @@ -194,6 +194,10 @@ int kvlangRwirNextPc(const char *pc, kvlangStrbuf_t *out); int kvlangRwirExtractAddr0(const char *coord); int kvlangRwirDecode(kvlangKv_t *kv, const char *link_base, const char *pc, kvlangRwirInst_t *out, char *err, uint32_t err_cap); void kvlangRwirInstFree(kvlangRwirInst_t *inst); +/* 外部扩展 handoff:写 /lib//.todo 并阻塞 watch .done(30s 超时)。 */ +int handoff_external_rwir(kvlangKv_t *kv, const char *vtid, const char *pc, kvlangRwirInst_t *inst); +/* opcode 是否外部扩展 rwir(/lib/ kind=rwir)。 */ +bool is_ext_rwir(kvlangKv_t *kv, const char *opcode); /* ── vthread ───────────────────────────────────────────────────────── */ diff --git a/runtime/src/rwirext.c b/runtime/src/rwirext.c index 9a623fa7..ce86651b 100644 --- a/runtime/src/rwirext.c +++ b/runtime/src/rwirext.c @@ -15,56 +15,29 @@ int kvlang_rwirextRegister(void *kvspace, const char *opcode, int32_t nr, return rc; } -char *kvlang_rwirextPrintLine(void *kvspace, const char *pc, int *rawnl, - int *cerr) { +int kvlang_rwirextHandoff(void *kvspace, const char *vtid, const char *pc) { kvlangKv_t k = {kvspace}; - if (rawnl) - *rawnl = 0; - if (cerr) - *cerr = 0; char *fr = kvlangKeytreeFrameRoot(pc); if (!fr) - return NULL; + return -1; char *lb = kvlangKeytreeStack(fr); kvlangRwirInst_t inst; char err[256]; if (kvlangRwirDecode(&k, lb, pc, &inst, err, sizeof err) != 0) { free(fr); free(lb); - return NULL; + return -1; } free(lb); - if (!inst.opcode || (strcmp(inst.opcode, "print") != 0 && - strcmp(inst.opcode, "println") != 0 && - strcmp(inst.opcode, "cerr") != 0)) { - free(fr); - kvlangRwirInstFree(&inst); - return NULL; - } - const char *sep = strcmp(inst.opcode, "print") == 0 ? "" : " "; - if (rawnl) - *rawnl = (strcmp(inst.opcode, "print") == 0); - if (cerr) - *cerr = (strcmp(inst.opcode, "cerr") == 0); - - kvlangStrbuf_t line; - kvlangStrbufInit(&line); - for (int i = 0; i < inst.nr; i++) { - if (i > 0) - kvlangStrbufPuts(&line, sep); - kvlangXvalue_t v; - kvlangXvalueZero(&v); - kvlangBuiltinResolveReadValue(&k, fr, inst.reads[i].name, - &inst.reads[i].val, &v); - char *s = NULL; - kvlangDisplay(&v, &s); - kvlangStrbufPuts(&line, s); - free(s); - kvlangXvalueFree(&v); - } + int rc = handoff_external_rwir(&k, vtid, pc, &inst); free(fr); kvlangRwirInstFree(&inst); - return kvlangStrbufDetach(&line); + return rc; +} + +int kvlang_rwirextIsExt(void *kvspace, const char *opcode) { + kvlangKv_t k = {kvspace}; + return is_ext_rwir(&k, opcode) ? 1 : 0; } char *kvlang_rwirextNextPc(const char *pc) { diff --git a/runtime/src/type_expr.c b/runtime/src/type_expr.c index 58b99f04..51aa06c5 100644 --- a/runtime/src/type_expr.c +++ b/runtime/src/type_expr.c @@ -32,8 +32,7 @@ static bool known_kind(const char *s, size_t len) { kind_eq(s, len, KVSPACE_KIND_INDEX) || kind_eq(s, len, KVSPACE_KIND_EXT_INDEX) || kind_eq(s, len, KVSPACE_KIND_RWIR) || kind_eq(s, len, KVSPACE_KIND_RWFUNC) || kind_eq(s, len, KVSPACE_KIND_SCOPE) || kind_eq(s, len, KVSPACE_KIND_TIME) || - kind_eq(s, len, KVSPACE_KIND_DURATION) || - kind_eq(s, len, "json"); /* json:递归 union,type-only,非落盘 kind */ + kind_eq(s, len, KVSPACE_KIND_DURATION); } /* base = any | kind(kind 为精确合法 kind 串) */ diff --git a/tutorial/test.py b/tutorial/test.py index 63573ef8..e06dabf3 100755 --- a/tutorial/test.py +++ b/tutorial/test.py @@ -277,7 +277,7 @@ def _c_test_file(f: Path, expects: list[str], env: dict) -> tuple[bool, str]: def main(): ap = argparse.ArgumentParser(description="tutorial test") - ap.add_argument("--filter", default="", help="按路径子串过滤(如 14-numpy / 11-string/01)") + ap.add_argument("--filter", default="", help="按路径子串过滤(如 11-string/01 / 08-leetcode/01)") ap.add_argument("--no-build", action="store_true", help="skip make build") ap.add_argument("--errorexit", action="store_true", help="exit on first error") ap.add_argument("--bench", action="store_true", help="benchmark matching .kv/.py/.c files")