Kette is a message-passing language with a prototype object model and a custom bytecode VM. The syntax is inspired by Self: everything is a message send, objects are dictionaries of slots, and delegation is explicit via parent slots.
- Messages, not functions:
receiver selectoris a message send. - Unary / binary / keyword messages: precedence is Self‑style.
- Objects are slot dictionaries: slots can be constants, assignable, or parent links for delegation.
- Blocks are closures:
[ ... ]with optional| args |. - None/true/false are objects:
Noneis the canonical empty value. - Globals at top level: module-aware compile-time resolution is strict; an unresolved global is a compile error.
123
3.14
"hello"
true
false
None
receiver unarySelector
receiver + argument
receiver key1: arg1 Key2: arg2
Objects are defined with { ... }. Slots are separated by ..
If the body contains any expressions other than assignments, it is parsed as
an executable object (method body) rather than a pure slot dictionary.
Example (executable object body):
Log = {
prefix = "[log] ".
message = "ready".
(prefix + message) println
}.
Point = {
x := 0.
y := 0.
moveByX: dx Y: dy = { self x: (self x + dx). self y: (self y + dy) }.
toString = { "(" + ((self x) toString) + ", " + ((self y) toString) + ")" }.
}.
Point moveByX: 10 Y: 20.
Point moveByX: 40 Y: 20.
Point x
=defines a constant slot or method.:=defines an assignable slot.
Object extend: true With: {
parent* = Boolean.
toString = { "true" }.
}.
The parent* slot marks a delegation link. Any slot name ending with * is
treated as a parent slot by the runtime. The core library uses this to define
the inheritance structure (see core/init.ktt).
Objects are prototypes. You can use a prototype directly or clone it to create
an independent object with the same slots. Shared behavior is typically modeled
by adding a parent slot (e.g., parent* = SomeTrait.) so lookup delegates to a
common prototype.
- Getter:
self x - Setter:
self x: value
Accessors are messages, so the receiver must be explicit.
[ 1 + ] call
[ | x y | x + y ] call: 2 With: 3
obj foo; bar; baz: 1
^ expr
Kette does not have standalone functions. Behavior lives in object slots (methods) and is invoked by sending messages.
Math = {
double: x = { x + x }.
fortyTwo = { 40 + 2 }.
}.
Math double: 21.
Math fortyTwo
- A unary method call is
receiver selector. - A keyword method call is
receiver name: arg .... - Method execution keeps
selfas the receiver object.
The module system is designed to be CL-like in spirit: globals are resolved in the module environment known at compile time, and imported names behave like aliases to exported bindings.
Module open: 'Lib.
Module export: 'Hello.
Module use: 'Other.
Module use: 'Other As: { answer = 'otherAnswer }.
Equivalent low-level primitives are available on VM:
VM _ModuleOpen: 'Lib.
VM _ModuleExport: 'Hello.
VM _ModuleUse: 'Other.
VM _ModuleUse: 'Other As: { answer = 'otherAnswer }.
Module export:may appear before the definition; defining later in the same compilation unit works.Module use:imports exported names directly;As:is only needed for renaming or collision avoidance.- Top-level unresolved globals in non-
usermodules are compile errors. - Imported names are write-through aliases (assigning via an import updates the source module binding).
- Method global lookup uses the defining module context (not caller module).
- There is no legacy global fallback path: globals must resolve through module bindings/imports.
Module open: 'Lib.
Module export: 'Hello.
Hello := 41.
Greeter := {
greet = { Hello _FixnumAdd: 1 }.
}.
Module export: 'Greeter.
Module open: 'App.
Module use: 'Lib.
Greeter greet
The full commented walkthrough is in examples/module_resolution_demo.ktt.
For strict module mode, load the core modules first, then test scripts:
cargo run -p vm -- core/init.ktt core/math.ktt core/collections.ktt core/alien.ktt core/system.ktt core/os.ktt tests/disposable_using.ktt
cargo run -p vm -- core/init.ktt core/math.ktt core/collections.ktt core/alien.ktt core/system.ktt core/os.ktt tests/file_disposable.ktt
cargo run -p vm -- core/init.ktt core/math.ktt core/collections.ktt core/alien.ktt core/system.ktt core/os.ktt tests/mitternacht.kttExtra output/file-loading demo previously in init lives in:
tests/init_runtime_demo.ktt
A userspace raylib wrapper and examples are available under:
examples/raylib/
Run it with:
cargo run -p vm -- vendor/raylib.ktt examples/raylib/min.ktt
# 3D scene example
cargo run -p vm -- vendor/raylib.ktt examples/raylib/scene3d.kttHot-reload example (render loop on a platform thread + interactive REPL edits):
cargo run -p vm -- --repl vendor/raylib.ktt examples/raylib/hot_reload.kttThen, in the REPL, you can live-update values and render code, e.g.:
RayHotState liveText: "changed from REPL".
RayHotState renderHook: [ | rl | rl drawText: "hot swapped from REPL" X: 44 Y: 126 Size: 24 Color: (Raylib R: 255 G: 190 B: 80 A: 255) ].
On Linux/WSL, install raylib first so libraylib.so is available to dlopen.
For Ubuntu-based distros:
sudo apt update
sudo apt install libraylib-devOn Windows 11 with WSLg, the window should display automatically. If rendering is slow or broken, try software GL:
LIBGL_ALWAYS_SOFTWARE=1 cargo run -p vm -- vendor/raylib.ktt examples/raylib/min.ktti := 1.
[ i <= 15 ] whileTrue: [
(i % 15) == 0 ifTrue: [ "fizzbuzz" println ] IfFalse: [
(i % 3) == 0 ifTrue: [ "fizz" println ] IfFalse: [
(i % 5) == 0 ifTrue: [ "buzz" println ] IfFalse: [
i toString println
]
]
].
i := i + 1
].
- VM design doc:
DESIGN.md - Core modules:
core/init.ktt,core/math.ktt,core/collections.ktt,core/alien.ktt,core/system.ktt,core/os.ktt - Parser and AST:
parser/ - Bytecode + interpreter:
bytecode/,vm/ - Object model + lookup:
object/ - GC/heap:
heap/
- Neovim integration:
editor/README.md - VSCode integration and extension packaging:
editor/vscode/README.md
The VM and language are under active development. APIs and syntax may change.