Skip to content

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

Kirito

Kirito is a small, dynamically-typed yet strongly-typed scripting language built from scratch in modern C++20. It is designed first and foremost as an extension language for C++ applications: a high-level, ergonomic surface for writing logic, with a runtime that drops cleanly into a host program and lets that program reach back in.

Source files use the .ki extension. The whole interpreter is header-only — a single #include "kirito.hpp" — so Kirito runs both as a standalone interpreter (ki) and as a library embedded in any C++ project. One KiritoVM object is one fully isolated interpreter "process".

var io = import("io")

var greet = Function(name : String) -> String:
    return f"Hello, {name}!"

for who in ["world", "Kirito"]:
    io.print(greet(who))

Disclaimer

Kirito is 100% generated by Claude Code (Opus 4.8, 1M Context, Max effort). It was implemented with careful prompting and under human control, guidance, hand-holding to fulfill human requirements, but it is fully implemented and tested by AI agent. Entire development took about 3 days. It's provided in hope of being useful tool, but also as a test of AI competency — which, given the scope and time, seems unreal to me as the person guiding it.

Documentation (and this markdown file, except for disclaimer) is written by AI aswell.

And why did i call it after MC of SAO? For a long time, even I had forgotten.

Contents

Why Kirito

  • A high level of abstraction, with no boilerplate. Significant indentation, first-class functions and closures, classes with inheritance and operator overloading, exceptions, context managers, rich built-in collections (List / Set / Dict), f-strings, packing/unpacking, and a batteries-included standard library — so a few lines of .ki express a lot.

  • Memory safety by construction. Every value lives in a VM-owned arena and is referred to through lightweight handles, never raw pointers. A precise mark-and-sweep garbage collector reclaims values automatically (the test suite runs clean under AddressSanitizer and UBSan). Recursion depth, huge allocations, and deeply nested data are all guarded so hostile or runaway scripts throw a catchable error instead of crashing the host.

  • Strong typing without ceremony. Integer and Float are distinct, there are no silent coercions across incompatible types, and type annotations are enforced at runtime when you want them (Function(d : Dict) -> Float: actually checks the argument and the return value) — yet they are entirely optional.

  • Easy integration, both directions.

    • Embed Kirito in C++ — construct a KiritoDispatcher, run source on dispatcher.mainVM(), read results back. No build step beyond adding src/ to your include path and compiling as C++20; there is no library to link.
    • Extend Kirito from C++ — register your own functions, modules, and object types. Anything you add flows through the same object protocol as the built-ins, so to a Kirito program your C++ type is indistinguishable from a native one.

Installing

Prebuilt 64-bit ki binaries for Windows and Linux are attached to each GitHub Release: optimized builds with HTTPS (TLS) support, linked as statically as possible (OpenSSL and the C/C++ runtime are bundled in), so they run with no other dependencies.

The one-line installers download ki and the kpm package manager, drop launchers on your PATH, and create the package directory ~/.kirito/packages (which ki searches automatically):

Linux / macOS

curl -fsSL https://github.com/ghraw/kiritolang/kiritolang.github.io/main/tools/scripts/install.sh | sh

Installs to ~/.local/bin (no root). Pass options after -s --, e.g. | sh -s -- --bin-dir ~/bin, or --from-source to build locally instead of downloading. On a platform without a prebuilt binary the script builds from source automatically (needs git + cmake; install libssl-dev so kpm's HTTPS works).

Windows (PowerShell)

irm https://github.com/ghraw/kiritolang/kiritolang.github.io/main/tools/scripts/install.ps1 | iex

Installs ki.exe + kpm.cmd under %LOCALAPPDATA%\Programs\Kirito and adds it to your user PATH.

Manual — download ki-linux-x64 / ki-windows-x64.exe from the latest release, put it on your PATH (rename to ki/ki.exe), and chmod +x it on Unix.

Packages (kpm)

Kirito's package manager installs packages — collections of .ki modules — straight from a git repository (GitHub by default, GitLab too). There is no central index: you name an owner/repo.

kpm install owner/repo            # install the package (and its dependencies) from GitHub
kpm install owner/repo@^1.2.0     # pin a tag/branch/commit, or a semver constraint
kpm install gitlab.com/owner/repo # install from GitLab (also: gitlab:owner/repo, or a full URL)
kpm list                          # what's installed
kpm update --all                  # re-resolve + reinstall (or `kpm update <name>...`)
kpm remove name                   # uninstall
kpm update-kpm                    # update kpm itself; `kpm update-ki` updates the interpreter binary

Installed packages land in ~/.kirito/packages/<name>/ and are importable directly — import("name") — because ki puts that directory (and each package sub-directory, plus anything in the KIRITO_PATH environment variable) on its module import path.

A package repository carries a kirito.json manifest at its root:

{
  "name": "mypkg",
  "version": "1.0.0",
  "modules": ["mypkg.ki", "extra/util.ki"],
  "dependencies": ["someone/dep"]
}

modules are repo-relative .ki paths; dependencies are other owner/repo packages installed first. kpm is itself written in Kirito (kpm/kpm.ki) — it just uses the net, json, io, sys, and semver modules — so it doubles as a worked example.

Documentation

Full documentation is live at kiritolang.github.io — a small, dependency-free static site. It covers:

  • Getting started, the language guide, and a built-in types & operator-overloading reference
  • The built-in functions and a per-function standard-library reference
  • Embedding Kirito in a C++ program and extending it with C++ functions, modules, and types
  • Recipes and a multi-part course with worked sample projects

The site is generated from hand-authored Markdown in docs/pages/ by the dependency-free docs/build_docs.py into docs/site/; you can also open docs/site/index.html locally or read the Markdown sources directly.

Limitations

Kirito is young and makes deliberate trade-offs. The notable current limits:

  • Compute-bound code is still slower than a native VM. Kirito runs a bytecode VM (no longer a tree-walker), but interpreter-bound tight loops remain a few × slower than CPython and Lua 5.1, and far slower than C++; work that delegates to the C++ standard library (sorting, hashing, string ops) closes most of the gap and can match or beat Lua 5.1. See Benchmarks.
  • The default Integer is fixed-width int64 with well-defined two's-complement wraparound on overflow; for unbounded values the int module provides an arbitrary-precision BigInt value type.
  • Unicode case mapping (upper/lower) covers ASCII, Latin-1 and Latin Extended-A, not the full Unicode case-folding tables.
  • A single KiritoVM is single-threaded — one VM is one fully-encapsulated, serializable process touched by exactly one OS thread. True parallelism is therefore multiprocessing: the parallel module runs many fully-isolated VMs that share nothing and communicate only by passing serialized values through thread-safe queues and primitives.

Repository structure

src/kirito/        The interpreter — a header-only C++20 core (~50 headers). One umbrella header,
  kirito.hpp         pulls in everything: lexer, parser, AST, the bytecode compiler + stack VM, the
                     value/object model, the arena + mark-sweep GC, and the standard library
                     (the stdlib_*.hpp modules: io, math, complex, json, net, time, hash, …).
main.cpp           The standalone `ki` CLI (REPL + file runner) — the only `main()`.
CMakeLists.txt     Thin CMake: an INTERFACE target for the header-only core, the `ki` executable,
CMakePresets.json  and the test executables. Presets: debug / release / asan / tsan.

examples/          Sample `.ki` programs (RPN calculator, word count, todo, stats, …), plus:
  big_projects/      Large pure-Kirito programs that double as interpreter stress tests:
                     kgrad (tensor/autodiff/neural nets), sqldb (a concurrent networked SQL
                     database), webserver (a concurrent HTTP/1.1 server + routing framework), and
                     selfhost (a Kirito interpreter in Kirito).
  http_client/       A `net` HTTP-client app + server + Python test harness.
  data/              Sample datasets (e.g. iris.csv) used by the examples.

kpm/               kpm.ki — the package manager, written in Kirito (installs packages from GitHub).

tests/             The CTest suite (every feature gets a test):
  unit/              C++ unit tests (one executable per area).
  scripts/           Golden `.ki` programs — each `*.ki` checked against its `*.expected` stdout.
  errors/            `.ki` programs that must fail, with required diagnostics in `*.experr`.
  lang/              End-to-end language tests driven from C++.
  integration/       C++-embedding projects (each embeds a `KiritoVM`).
  fuzz/  bench/      Stability fuzzers; timing + cross-language C++/Python comparison.

tools/             Project tooling:
  scripts/           build_all.sh (release binaries), test_release.sh (run the `.ki` suites against
                     a built binary), post_work_check.sh (clean-build every variant + full CTest),
                     install.sh / install.ps1 (the Linux/macOS + Windows installers).
  versions.env       The pinned toolchain (g++/clang/cmake/ninja versions) the project builds against.

docs/              The documentation site: hand-authored Markdown in `docs/pages/`, rendered by
                   the dependency-free `docs/build_docs.py` into `docs/site/`.
  editors/           Syntax-highlighting definitions for `.ki` files — Notepad++ (UDL), VS Code
                     (TextMate grammar + extension), and Vim. See `docs/editors/README.md`.

license/           LICENSE (MIT) and THIRD_PARTY_LICENSES.md (licenses of incorporated software).
.audit/            Hidden but tracked: the paper trail of the codebase audit rounds (pre-1.12 through
                   v1.16.1) — per-subsystem findings, triaged roll-ups, and recorded false positives.
CLAUDE.md          The project charter: what Kirito is, how it's built, and the working rules.

Benchmarks

Kirito (bytecode VM) vs C++ (-O2, gcc 13.3) vs CPython 3.12 vs Lua 5.1 vs Bash on identical algorithms over identical (LCG-generated) data, release build. Microseconds per repetition, mean ± population stddev, one unit per row, lower is better — median of three runs (tests/bench/compare.py):

Workload N reps C++ (-O2) Python 3.12 Lua 5.1 Bash Kirito
pessimistic — interpreter-bound tight loops
sum_loop (arithmetic loop) 1000 2000 0.195 ± 0.005 34.2 ± 2.7 7.69 ± 1.0 1421 ± 19 112 ± 52
fib (recursive calls) 17 300 1.57 ± 0.02 120 ± 4.4 88.3 ± 5.3 30692 ± 74 782 ± 128
sieve (nested loops + indexed writes) 1500 500 1.01 ± 0.05 74.9 ± 4.4 57.2 ± 5.7 7400 ± 39 641 ± 186
optimistic — work delegated to C++ builtins
sort (builtin sort) 1500 2000 7.55 ± 1.3 38.8 ± 4.7 153 ± 15 64314 ± 355 143 ± 23
dict_ops (hash insert/lookup) 1000 1500 25.7 ± 2.3 64.4 ± 8.6 47.6 ± 11 3265 ± 77 146 ± 48
string_ops (split/join) 1500 2000 20.0 ± 1.8 25.3 ± 2.6 107 ± 18 4455 ± 17 70.0 ± 48

The shape is what a bytecode VM with a fast C++ standard library should show: on interpreter-bound tight loops (sum_loop/fib/sieve) it pays per-operation dispatch and trails Lua 5.1's register VM by roughly an order of magnitude and CPython by several ×, but once the work lands in native builtins (sort/dict_ops/string_ops delegate to std::sort / std::unordered_map / std::string) it closes the gap and matches or beats Lua 5.1 (faster on sort and string_ops; a few × behind on dict_ops). Slot-addressed locals (resolving each function's non-captured locals to a frame-slot index at compile time) plus a numeric binary fast path landed in 1.9 and roughly halved the tight-loop gap.

Measured on the project's WSL2 dev box (24-core), so absolutes are lower than the shared-cloud host these were previously taken on — but Kirito's per-run stddev stays large (rare GC pauses land on random reps), which is why the table reports the median of three runs. The C++ baseline runs in tens to hundreds of nanoseconds where timer jitter dominates, so its column is the least stable. Bash uses adaptive reps (~0.5 s per workload, min 5), so its stddev is over fewer samples. Lua 5.1 has no integer type, so its column uses doubles; the benchmark's 31-bit LCG is computed with an exact split-multiply so every language runs on byte-identical data.

Reproduce: cmake --build build-release --target ki && python3 tests/bench/compare.py --ki build-release/ki (the Lua column appears automatically when lua5.1 is on your PATH).

Embedding Kirito in C++

Construct a KiritoDispatcher — the recommended entry point — and run source on the VM it gives you. It owns a fully-configured KiritoVM (with the parallel module enabled) plus the worker machinery, exactly like the ki CLI, and costs nothing until you use it. (For the bare minimum without parallel, you can construct a KiritoVM directly — see the docs.)

#include "kirito.hpp"
using namespace kirito;

int main() {
    KiritoDispatcher dispatcher;                   // the embedding entry point
    KiritoVM& vm = dispatcher.mainVM();            // a fully-configured interpreter
    Handle result = vm.runSource("var x = 6 * 7\nx\n");
    std::printf("%s\n", vm.stringify(result).c_str());   // 42
}   // ~KiritoDispatcher cleanly joins any worker threads

Extending Kirito from C++

Expose a C++ function to scripts with a single registration. The Value / Args helpers read and build Kirito values, turning a bad argument into a clear error instead of a crash:

vm.registerGlobal("repeat", vm.alloc(std::make_unique<NativeFunction>(
    "repeat", [](KiritoVM& vm, std::span<const Handle> raw) -> Handle {
        Args a(vm, raw, "repeat");
        std::string s = a.at(0).asStringRef("s");
        int64_t n     = a.at(1).asInt("n");
        std::string out;
        for (int64_t i = 0; i < n; ++i) out += s;
        return Value(vm, out);
    })));
// Kirito:  repeat("ab", 3)   ->   "ababab"

To keep a Kirito value in a long-lived C++ object (a class member, a std::vector, a callback registry) hold a PinnedHandle rather than a bare Handle — it is an owning RAII GC root, so the value survives collection for as long as you keep it. Whole modules and brand-new object types (with their own methods and operators) are added the same way — see the documentation for complete, worked examples.

Building and running

The everyday build needs a C++20 compiler (GCC 13+ / Clang 18+ / MSVC), CMake ≥ 3.28, and Ninja. Install the toolchain for your platform:

sudo apt-get install -y build-essential g++ cmake ninja-build git   # Debian/Ubuntu/WSL
sudo dnf install -y gcc-c++ cmake ninja-build git                   # Fedora/RHEL
sudo pacman -S --needed base-devel cmake ninja git                  # Arch
xcode-select --install && brew install cmake ninja                  # macOS (Apple Clang + Homebrew)
# Windows: install Visual Studio 2022 with the "Desktop development with C++" workload (MSVC + CMake + Ninja).

CMake version: Ubuntu 24.04 ships 3.28 (fine); on 22.04 or older, apt's CMake is too old — use pip install --user cmake ninja, or the Kitware APT repo. If g++ is older than 13, apt-get install g++-13 and configure with CXX=g++-13 cmake --preset debug.

Then build and run (same commands on every platform):

cmake --preset debug               # configures into build-debug/  (presets: debug / release / asan / tsan)
cmake --build build-debug -j

./build-debug/ki path/to/program.ki   # run a script
./build-debug/ki                      # start the REPL
ctest --test-dir build-debug -j       # run the C++ unit + golden-script test suite

Sanitizers and the full test matrix

Every variant uses the same preset / build / test commands — just swap the name. The two safety gates are asan (AddressSanitizer + UBSan: memory and undefined-behaviour) and tsan (ThreadSanitizer: data races and lock-order inversions in the parallel dispatcher, the only concurrent code):

# AddressSanitizer + UBSan
cmake --preset asan && cmake --build build-asan -j
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
  ctest --test-dir build-asan -j

# ThreadSanitizer
cmake --preset tsan && cmake --build build-tsan -j
TSAN_OPTIONS=halt_on_error=1:second_deadlock_stack=1 ctest --test-dir build-tsan -j

ASan's recursion-guard frames are larger, so give it a roomier stack if a deep-nesting test trips the guard early: ulimit -s 262144 before the ctest line. Each sanitizer build is a separate build-asan/ / build-tsan/ directory (~8–13 GB of objects — make sure you have the disk).

Memory: the sanitizer-instrumented compiles are RAM-hungry (~1.5–2 GB per parallel job). On a memory-capped box — notably WSL2, whose default cap is a fraction of host RAM — a full--j build can be OOM-killed (a bare Terminated; dmesg shows oom-kill … cc1plus). Peak RAM is jobs × ~2.5 GB, so it scales with core count — a 24-core box launches 24 such compiles at once (~50 GB). Either build with fewer jobs (cmake --build build-asan -j8) or throw WSL2's cap in %UserProfile%\.wslconfig ([wsl2] then memory=48GB), wsl --shutdown, reopen. post_work_check.sh auto-caps the asan/tsan build jobs by available RAM (~3 GB/job); override with PW_SANITIZER_JOBS=N.

ThreadSanitizer on a recent kernel (Ubuntu 24.04 / WSL2): if every tsan test fails at once (even trivial ones like test_arena) with FATAL: ThreadSanitizer: unexpected memory mapping, the kernel's ASLR entropy is too high for TSan's shadow layout. Lower it once: sudo sysctl -w vm.mmap_rnd_bits=28 (persist via echo 'vm.mmap_rnd_bits=28' | sudo tee /etc/sysctl.d/99-tsan.conf), then re-run — no rebuild needed. This is a kernel/sanitizer knob, not a Kirito issue (debug/release/asan are unaffected).

To build and test all four variants in sequence (debug → release → asan → tsan, each a clean build of the whole auto-discovered CTest suite) with one command:

tools/scripts/post_work_check.sh        # debug+release first, then asan+tsan; prints a green/red summary

It does not touch git — it only builds and tests, then reports which variants are green so you decide whether to commit.

Building the release binaries (static, TLS)

tools/scripts/build_all.sh builds the shippable 64-bit binaries into dist/ — ki-linux-x64 and, when the mingw cross compiler is installed, ki-windows-x64.exe (it builds a static OpenSSL for the Windows target from source, cached under .deps/). On Debian/Ubuntu/WSL:

sudo apt-get update
sudo apt-get install -y build-essential cmake ninja-build git perl libssl-dev mingw-w64
tools/scripts/build_all.sh

Each shipped binary is a Release build with TLS on and linked as statically as possible (the Linux binary keeps only glibc dynamic; the Windows .exe is fully static). build_all.sh also produces two non-shippable sanitizer interpreters for third-party automated testing — dist/debug-asan (AddressSanitizer + UBSan) and dist/debug-tsan (ThreadSanitizer) — Debug, dynamically linked, and unstripped; they are built but not tested here. To cut a release, bump the version in src/kirito/version.hpp, build with build_all.sh, and upload dist/ki-linux-x64 and dist/ki-windows-x64.exe to a GitHub Release tagged with the bare version (e.g. 1.7.0). The project does not use CI.

Testing the built executables

tools/scripts/test_release.sh runs the end-to-end .ki test suites (golden-output + error-diagnostic scripts) against the binaries in dist/, so you can verify each interpreter directly — including the debug-asan / debug-tsan sanitizer builds, run with the sanitizer runtime active. Linux binaries run natively; Windows .exe binaries run under Wine:

sudo apt-get install -y wine64        # only needed to test the .exe
tools/scripts/test_release.sh               # tests every dist/ binary (release + sanitizer)
tools/scripts/test_release.sh ./build/ki    # or test one specific interpreter

License

Kirito is released under the MIT License — all of its own source (src/kirito/, tools/, kpm/, examples/, docs/) is original work.

Incorporated third-party software and its licenses are recorded in license/THIRD_PARTY_LICENSES.md: the bundled fum hash-map library (MIT), and OpenSSL (Apache License 2.0), which is linked only into TLS builds (-DKIRITO_ENABLE_TLS=ON). The compression, hashing, and regex modules are from-scratch implementations of public standards, not third-party code.

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages