A plaintext, interpreted language whose addressing space and memory space are both kvspace — a small core with extensions on top (the front-end language of deepx, formerly dxlang). Code and data live in one KV tree; the PC is a KV path (crash-resumable), the source is the IR, and every KV value is plaintext. The core runtime does only the execute loop and control flow; all other capabilities are carried by rwirext extensions (term / json are example base extensions).
中文文档: 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.
Design docs (CN): deepx-design/doc/kvlang-design-and-implementation · (EN): deepx-design/doc-en/kvlang-design-and-implementation — 19 chapters covering architecture, parser, runtime, kvspace, and language design reference.
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]/[0,0]/[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.
lib main {
rwfunc add(A:int64, B:int64) -> (C:int64) { 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"
Two address-space domains exist: /lib (function library — signatures, instruction trees, .src) and /vthread (runtime frames). Everything else under / is user-defined. There is no /dev device domain and no terminal — the KV world holds only keys and values; I/O such as print is an extension rwir, not an address-space domain.
kvspace is the addressing and memory space at the core; the language is a small runtime with extensions on top.
- kvspace — one C ABI (
kvspace_*, 24 symbols), two implementations selected by DSN:kvspace-c(C,shm://, linksblockmalloc+slotsboxmalloc) andkvspace-durable(Rust,redis:///fs://, s3/tikv planned). - kvlang —
layout(Rust, compile) andruntime(C, execute), both depending only on thekvspace_*C ABI. - rwirext — extensions on top of the runtime. Embedded (Rust
term, linkslibkvlang_runtimeviakvlang_rwirext.h) or process-separated by handoff (Gojson, Pythonnumpy).term/jsonare example base extensions, not headline features.
# Requirements: Go 1.24+, Redis
make build
./kvlang tutorial/01-basics/hello.kv # run a file
./kvlang -c 'print("hello, world")'# inline modeecho'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 { }, rwfunc, and single instructions. Always wrap code in rwfunc main() -> () { … }; main(). Bare if / while / for at top level may auto-wrap into implicit init() but this is unreliable — explicitly wrapping in main() is the only guaranteed pattern. Never name your function init to avoid conflicts with implicit wrapping.
rwfunc main() -> () {
total = 0# = is equivalent to <-1-> i
while (i <=5) {
total <- total + i
i +1-> i
}
println(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: -> _A write slot must be a location: a bare name (frame-local), /abs/path (global key), or base.name (member). Literals are not locations.
rwfunc func(ra,rb) -> (wa,wb) { … } = composite rwir, the named form. Single-line rwir like A + B -> C is atomic (one opcode + reads + writes); rwfunc 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.
-> (C:int64) in a rwfunc 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: array as read param, a[i] <- v writes through read-param slot → parser rejects
rwfunc bad(a:int64) -> () { 99-> a[0] }
# ✅ correct: array as write param, readable and writable inside the body
rwfunc 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): rwfunc sum(arr:int64) -> (acc:int64) { acc + arr[i] -> acc }.
A pure working variable is copied to a local first (A -> a, then use a):
lib mylib {
rwfunc add(A:int64, B:int64) -> (C:int64) {
A + B -> C
}
}
rwfunc main() -> () {
mylib.add(3, 4) -> s
println(s) # 7
}
main()d = { name="kv"; ver=1 } # dict literal: members are the flat key-family d.name, d.ver
println(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):
rwfunc build() -> () {
/n1 = { val=1; next="/n2" } # = is equivalent to <-/n2 <- { val=2; next="/n3" }
{ val=3; next="" } ->/n3
}
rwfunc main() -> () {
build()
"/n1"-> p # p holds a path string (a pointer)while (p !="") {
p.val -> v # pointer deref: reads /n1.val
println(v)
p.next -> p
}
}
main()f = float32(3) # ten constructors: 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/float64
x:int64 =42# type-annotated variable declarationint and float are rejected by the parser — use exact-width types only. The ten precision operators are both constructors and converters.
i =1; sum=0while (i <=10) { sum+ i ->sum; i +1-> i }
if (sum>50) { println("big") } else { println("small") } # sum=55 → bigfor (x in [7, 2, 9, 4]) { println(x) }Conditions may be compound expressions: if (7 % 2 != 0) and while (i < string.len(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)./is reserved for paths and path separators.*is reserved for future pointer dereference.
Builtins are the rwir the runtime evaluates in-process (the bi_is_native set). They are pure KV→KV computations — no I/O:
Scalar:absnegsignpowsqrtexplogminmax (variadic, e.g. max(a,b,c)) debugger
Types:boolint8int16int32int64uint8uint16uint32uint64float32float64char/utf8char/utf32char/ascii
Collections:arrayatsethasarray.sortarray.slicearray.appenddict
Shape:xv.numelxv.dimxv.shapexv.atxv.set
KV tree:kv.getkv.setkv.delkv.deltreekv.listkv.mkindexkv.extindexkv.rmindexextkv.watchkv.haskv.at
Strings:string.charstring.ordstring.lenstring.cmpstring.findstring.slicestring.concatstring.set
Time:time.nowtime.subtime.addtime.beforetime.aftertime/duration.nanostime/duration.as_nanos (and millis/seconds/minutes/hours variants)
Random:random.uint64random.int63random.intn
print / println / cerr are NOT builtins. In the KV world there is no terminal — only keys and values — so I/O is not a core-language primitive. They are extension rwir: the term extension runtime registers them at /lib/<opcode> (kind rwir) and writes to the host process's stdout/stderr. The core runtime recognizes any /lib/<opcode> that carries an rwir signature and is not a builtin as an extension rwir, and hands it off to its extension runtime. Same mechanism as json.to / json.from (the json extension) and tensor ops (the numpy / GPU extensions).
a:int64 = [7, 2, 9, 4] # typed 1D array, = ≡ <-
xv.numel(a) -> n # 4
at(a, 2) -> e # 9 (0-indexed)set(a, 1, 99) -> a # modify element: a becomes [7, 99, 9, 4]
sort(a) ->sorted# sorted copy: [2, 4, 7, 9]s ="hello"
string.char(s, 1) ="a"# replace char at index 1 → "hallo"
s +" world"-> t # concatenation → "hallo world"
string.len(s) -> n # 5
string.find(s, "ll") -> i # 2 (first index of substring, -1 if absent)
string.slice(s, 0, 2) -> p # "he"Strings support indexing and concatenation with +; at(s, i) reads the i-th char, string.char(s, i) reads it, string.char(s, i) = "X" replaces one char.
140 self-contained examples (129 with expected output, fully CI-verified), organized by topic:
01-basics/ hello, arith, precision, numtypes, strings, … (15 files)
02-func/ rwfunc, call, accumulator (2 files)
03-control/ if, while, for, guess (5 files)
03-debugger/ chain_array, debugger builtin (4 files)
04-algo/ fibonacci, gcd, collatz, … (13 files)
06-lib/ lib block, nested, cross-lib, anon (11 files)
07-leetcode/ LeetCode solutions (90 files)
error_cases/ type_error, index_error, zero_division, … (36 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 positive examples — CI verification
python3 tutorial/error_test.py # all negative testsIn-depth design and implementation docs covering the full architecture:
| Chapter | EN | CN |
|---|---|---|
| Architecture — storage/compute/control separation | en | cn |
| Architecture — everything is plaintext | en | cn |
| Architecture — program as data + functions | en | cn |
| Architecture — four-level code hierarchy | en | cn |
| Parser — instruction architecture | en | cn |
| Parser — functions | en | cn |
| Parser — compiler pipeline | en | cn |
| Parser — diagnostics | en | cn |
| Parser — layoutrwir | en | cn |
| Parser & Runtime — control flow | en | cn |
| Runtime — type system | en | cn |
| Runtime — member access & data structures | en | cn |
| Runtime — debugging & observability | en | cn |
| Runtime — function calls & builtins | en | cn |
| KVSpace — address space | en | cn |
| KVSpace — addressing & naming | en | cn |
| KVSpace — code instruction layout | en | cn |
| KVSpace — system variables | en | cn |
| Reference — how to design a programming language | en | cn |
Each English translation includes Implementation Consistency Notes cross-checked against the Go source.
MIT — see LICENSE
