The VM of deepx (formerly dxlang) — an agent-native, train-inference-unified, self-iterating AI compute architecture. kvspace tree paths form a single unified address space; one syntax simultaneously serves as VM instructions, high-level language, compiler IR, and human-readable source.
中文文档: README_CN.md | Design: deep-dive — root design doc; README is the teaching derivative. All behavior norms (p0–p7), instruction model (§2), Link call mechanism (§6), type system (§9), diagnostics (§12) live there.
No IR layers — source IS the IR. The program counter is a kvspace path string; call-stack depth equals path depth:
PC = "/vthread/tid/[0,0]/.fn/[1,0]" the program counter is a KV path
fetch = kv.Get(PC) instruction fetch is one KV read
call = create subtree; return = clean it crash? restart and resume from PC
Every instruction occupies a 2-D coordinate [s0, s1]: [s0,0] is always the opcode, [s0,-j] read params, [s0,+j] write params.
def add(A: int, B: int) -> (C: int) { A + B -> C }/lib/main/add/[0,0] = "+" /lib/main/add/[0,-1] = "A"
/lib/main/add/[0,-2] = "B" /lib/main/add/[0,1] = "C"
Four address-space domains: /lib (function library) /vthread (runtime frames) /sys (infrastructure) /dev (I/O).
# Requirements: Go 1.24+, Redis
make build
./kvlang tutorial/01-basics/hello.kv # run a file
./kvlang -c 'print("hello, world")' # inline mode
echo '40 + 2 -> x; print(x)' | ./kvlang # pipe mode (; separates statements on one line)
./kvlang vet my.kv # syntax check
./kvlang format my.kv # formatTop level: lib name { } def, and single instructions. Bare if / while / for at the top level are auto-wrapped into an implicit def init() { … }. The convention is to define main and call it:
def main() -> () {
total = 0 # = is equivalent to <-
1 -> i
while (i <= 5) {
total <- total + i
i + 1 -> i
}
print(total)
}
main()x = 40 + 2 # = : write slot on the left (≡ <-); = is NOT an expression, cannot nest in conditions
y <- x # left arrow: write slot on the left
x * y -> z # right arrow: write slot on the right
f(a, b) -> r # write-param mapping for calls; multiple: -> x, y; discard: -> _
divmod(17, 5) -> _, r # multi-write-param: discard quotient with _, keep remainderA write slot must be a location: a bare name (frame-local), /abs/path (global key), or base.name (member). Literals are not locations.
def func(ra,rb) -> (wa,wb) { … } = composite rwir, the named form. Single-line rwir like A + B -> C is atomic (one opcode + reads + writes); def packs multiple rwir into a named unit with the same arrow interface — (ra,rb) declare read params, -> (wa,wb) declare write params. Calling add(3,4) -> s binds arguments to read slots, maps write slots back to the caller frame. No return values, only write-param mapping. Must match all write params — discard unwanted with ._ (Go-style _, engine-level: never written to kvspace).
-> (C: int) in a def signature is a write-param declaration. The function writes results into its write-param slots; the caller maps them with -> r.
Read params are read-only: the body may not place a read param in a write slot (e.g. A = A + 1). This includes array element writes — a[i] <- v writes through a, so a must be a write param if you need to modify it. Array/dict to mutate → write param; array/dict to read only → read param.
# ❌ wrong:数组作读参,a[i] <- v 写读参槽 → parser 拒绝
def bad(a) -> () { 99 -> a[0] }
# ✅ correct:数组作写参,函数内读写自由
def good() -> (a:int64) { a:int64 = [10, 20]; 99 -> a[0]; a }Decide the role first —
an accumulator is an output, so declare it as a write param (write params start at zero, are readable and writable in the body — like Go named return values): def sum(arr:int64) -> (acc:int64) { acc + arr[i] -> acc }.
A pure working variable is copied to a local first (A -> a, then use a):
def add(A: int, B: int) -> (C: int) {
A + B -> C
}
def main() -> () {
add(3, 4) -> s
print(s) # 7
}
main()d = { name="kv"; ver=1 } # dict literal: members are the flat key-family d.name, d.ver
print(d.name) # member read
d.ver = 2 # member write
k = "name"; d.*k -> v # dynamic key: reads d.name (k's value becomes the key)Pointer via path string: store an absolute path in a variable, then use .member to read/write at that path — the variable's string value becomes the path prefix.
/node = { val=42 } # dict at absolute path
"/node" -> p # p holds the path string
p.val -> v # reads /node.val → 42Data structures shared across functions (e.g. linked lists) create nodes at absolute paths (frame-locals die when the frame returns):
def build() -> () {
/n1 = { val=1; next="/n2" } # = is equivalent to <-
/n2 <- { val=2; next="/n3" }
{ val=3; next="" } -> /n3
}
def main() -> () {
build()
"/n1" -> p # p holds a path string (a pointer)
while (p != "") {
p.val -> v # pointer deref: reads /n1.val
print(v)
p.next -> p
}
}
main()f = float32(3) # ten operators int8/16/32/64 uint8/16/32/64 float32/64 — they construct AND convert
w = int8(300) # 44: narrowing wraps (two's complement); float→int truncates toward zero; arithmetic domain is int64/float64i = 1; sum = 0
while (i <= 10) { sum + i -> sum; i + 1 -> i }
if (sum > 50) { print("big") } else { print("small") } # sum=55 → big
for (x in [7, 2, 9, 4]) { print(x) }Conditions may be compound expressions: if (7 % 2 != 0) and while (i < strlen(s)) both work (auto-flattened to temp slots at compile time).
| Category | Symbols |
|---|---|
| Arithmetic | + - * / % |
| Comparison | == != < > <= >= |
| Logic | && || ! |
| Bitwise | & | ^ << >> |
/: both ints → integer division (C-style,7/2=3,-9/2=-4); either side float → float division (7.0/2=3.5).
abs neg sign pow sqrt exp log min max (variadic, e.g. max(a,b,c)) print cerr input
int float bool plus the ten precision operators · char ord strlen strcmp strstr slice concat · array len at set has sort dict kvat kvhas
a:int64 = [7, 2, 9, 4] # typed 1D array, = ≡ <-
len(a) -> n # 4
at(a, 2) -> e # 9 (0-indexed)s = "hello"
s[1] = "a" # replaces char at index 1 → "hallo"
s + " world" -> t # concatenation → "hallo world"Strings support indexing and concatenation: s[i] reads the i-th char, s[i] = "X" replaces one char, a + b concatenates.
C-style API: strlen; strcmp returns -1/0/1; strstr(hay, needle) returns the first index (-1 if absent); ord(c) returns the byte code.
100 self-contained examples (99 with expected output, fully CI-verified), organized by topic:
01-basics/ hello, arith, precision, numtypes, strings (6 files)
02-func/ def, call, accumulator (2 files)
03-control/ if, while, for, guess game (5 files)
04-algo/ fibonacci, gcd, collatz, ... (13 files)
07-leetcode/ LeetCode solutions (69 files)
06-lib/ lib block, nested, cross-lib, anon (8 files)
./kvlang tutorial/01-basics/hello.kv # hello kvlang
./kvlang tutorial/04-algo/fibonacci.kv # fib = 55
./kvlang tutorial/07-leetcode/001_two_sum.kv # LeetCode
python3 tutorial/test.py # all 99 examples — CI verificationNegative tests verifying diagnostic accuracy: each .kv file is annotated with expected error/warning messages,
and error_test.py checks that the compiler and runtime produce those diagnostics exactly.
error_cases/
type_error/ e.g. char(1) → TypeError
index_error/ e.g. at([1,2], 5) → IndexError
zero_division/ e.g. 1/0 → ZeroDivisionError
key_error/ e.g. kvat("/x","y") → KeyError
value_error/ e.g. log(-5) → ValueError
name_error/ e.g. nosuch() → NameError
read_only/ e.g. reading a read param → compiler rejection
recursion_error/ e.g. unbounded recurse → RecursionError
runtime_error/ e.g. Bootstrap failure → RuntimeError
python3 tutorial/error_test.py # all 10 negative tests
python3 tutorial/test.py # all 94 positive tests — CI verificationDiagnostic categories follow the diagnostic-style specification aligned to Python naming conventions (fix-028).
MIT — see LICENSE