1
0
Fork
You've already forked kinship
0
prototype of alternative forth
  • C 97.2%
  • Makefile 2.8%
2026年07月11日 00:44:21 +05:00
src first commit 2026年07月11日 00:44:21 +05:00
Makefile first commit 2026年07月11日 00:44:21 +05:00
QUICKREF first commit 2026年07月11日 00:44:21 +05:00
README first commit 2026年07月11日 00:44:21 +05:00

Kinship Interpreter — Reference Implementation
===============================================
This is a tree-walking interpreter for the Kinship S4X64M ISA.
It provides a shared library (libkinship) for embedding and a
standalone binary (kinship) for running scripts or interactive use.
Building
--------
 make # builds kinship, libkinship.a, libkinship.so
 make install # installs to /usr/local (set DESTDIR= to override)
Requires: a C11-capable compiler (gcc or clang), make, ar.
Usage
-----
 kinship file Run a source file
 kinship < file Run source from stdin
 kinship Interactive REPL (when stdin is a terminal)
 kinship -h Print usage
Language notes
--------------
Numbers
 Any token that parses as a decimal or 0x-prefixed hex integer is
 pushed onto the active data stack.
 ASCII character literals use a leading tick:
 'A → 65
 '\n → 10 (the character after the tick is taken literally)
Comments
 A backslash and everything after it on the same line is ignored.
 \ this is a comment
Words
 DEF name ... RETURN defines a user word.
 Words can be called directly by name or with CALL name.
 CALL is required when a word is defined after the call site.
Control flow
 IF/FI, DO/JZ, DO/JNZ all *peek* TOS — the condition value is not
 consumed. Use DROP when you no longer need it.
 Conditional chains without DUP:
 EQ IF ... FI NOT IF ... FI DROP
WORDS (Forth compatibility)
 Typing WORDS prints all currently defined words (builtins + any
 user-defined words registered via ks_register_native).
Implementation details
----------------------
- The interpreter is a token-walker (no bytecode compilation step).
 DEF bodies are stored as token-array slices and re-walked on each
 call. This keeps the implementation small and straightforward.
- Loops (DO/JNZ, DO/JZ) are implemented as recursive re-execution
 of the body token slice.
- Memory (FETCH/STORE) is a flat 1 MiB byte array inside ks_vm_t.
 Addresses are byte offsets; values are 64-bit little-endian.
- The two data stacks (DS1/DS2) and the SWITCH/THROW/CATCH primitives
 are fully implemented. CD0 tracks the active stack.
- LEAVE inside a loop exits immediately; LEAVE outside a loop acts
 as RETURN (matches the spec).
- BYE halts the VM and returns KS_ERR_HALTED to the caller.
Known limitations
-----------------
- No bytecode compiler yet (see the open question below).
- RECV reads one line from stdin and parses it as a decimal integer.
- MAX_TOKENS is 65 536 — source files larger than ~500 KB may hit
 this limit; increase MAX_TOKENS in vm.c if needed.
- User-defined words are local to a single ks_exec_string call.
 Call ks_exec_string once with the full source, not line by line,
 if you need words defined in one call to be visible in the next.
Open question: compiler in the same binary?
-------------------------------------------
Short answer: no, not hard at all — and yes, it fits naturally in one
binary.
The current interpreter walks the token stream twice at most (once
to collect DEF bodies, once to execute). A compiler would replace the
execution pass with a code-generation pass that emits a compact
bytecode (e.g. a 1-byte opcode + optional 8-byte immediate).
What that looks like in practice:
 1. A second pass function ks_compile(interp_t*, uint8_t *out, size_t *len)
 walks the same token array and emits opcodes.
 2. A bytecode executor ks_run_bytecode(ks_vm_t*, uint8_t*, size_t)
 replaces the recursive token-walker with a tight dispatch loop.
 3. `kinship file` → compile + run (already the implied semantics).
 `kinship -c file` → compile only, write .ksb file.
 `kinship < file` → interpret directly (no compile step needed).
The token-walker and the compiler can share the same word table and
the same ks_vm_t struct — they only differ in what they do with each
token. The binary stays one file, the library gains one extra symbol
(ks_compile), and the total added code is roughly the size of vm.c
itself. The main challenge is resolving forward CALL addresses during
compilation, which requires a two-pass approach or a fixup table —
both are standard techniques.