In development

Typed scripting for native systems.

Packed binary data. Native plugins. Static validation before execution. No garbage collector. Two-way C integration. A scripting language for C applications where native data can stay native — without turning every buffer into a runtime object.

First downloadable release expected late October 2026.

Test suite · in DominScript
900+ tests · 50+ examples
Fault-injection cases · per pass
~18k
Native plugins under test
35
Platforms
Linux · Win
01 — Demonstration: OpenGL + FFmpeg driven from a script
demo.mp4
▸ Demo source — read the script behind the demo

opengl_ffmpeg_script_driven_demo.dom — the script that produces the demo above — opens an OpenGL window, decodes a video stream with FFmpeg into a spinning textured cube, handles mouse input, and runs at 60 FPS. Every drawing decision lives in the source; there is no rendering engine hidden behind a high-level façade.

The excerpt below shows the spine: plugin declarations, the packed render state struct, the two callbacks the host dispatches into, and the main lifecycle. Geometry helpers, draw functions, and the per-frame renderer are omitted — see the full source for those (about 770 lines total).

opengl_ffmpeg_script_driven_demo.dom · excerpt dominscript
/* The runtime knows almost nothing on its own. Each `plugin` line loads
   a shared library that contributes a namespace (gl, ffmpeg, ...). */
plugin "builtin:os";
plugin "builtin:struct";
plugin "../plugins/worker/WorkerPlugin";
plugin "../plugins/opengl/GlPlugin";
plugin "../plugins/ffmpeg/FfmpegPlugin";
plugin "../plugins/image/ImagePlugin";

#define WINDOW_WIDTH  1280
#define WINDOW_HEIGHT  720
#define VIDEO_WIDTH    640
#define VIDEO_HEIGHT   360

#define AUTO_SPEED_X  0.23
#define AUTO_SPEED_Y  0.37
#define AUTO_SPEED_Z  0.11

/* Packed struct: byte-precise layout, no padding surprises. The runtime
   can overlay this onto any blob with `$X assign RenderState;` and read
   typed fields without copying. */
struct RenderState packet
{
    i32     WindowWidth;
    i32     WindowHeight;
    i32     VideoSlot;
    i32     LogoSlot;
    double  CameraDistance;
    double  ViewScale;
    double  AngleX;
    double  AngleY;
    double  AngleZ;
    double  SpeedX;
    double  SpeedY;
    double  SpeedZ;
    /* ... edge color, cube gap, line width ... */
};

/* The interactive state lives in the callback world -- the thread the
   mouse/resize and frame callbacks run on -- as a callback global. The
   main thread never names it: once per frame it calls Tick, which
   advances the rotation and writes a snapshot into the blob main lends. */
callback global blob $State[sizeof(RenderState)];

callback i32 Tick(ref blob $Snapshot)
{
    $Snapshot assign RenderState;
    $State    assign RenderState;
    /* ... advance AngleX/Y/Z by Speed * dt (clamped) ... */
    $Snapshot.AngleX = $State.AngleX;
    /* ... copy the remaining fields into the snapshot ... */
    return 1;
}

/* =========== Callbacks the host dispatches into =================== */

/* Called by the ffmpeg plugin for each decoded video frame. The frame
   bytes arrive in $FramePacket as a borrowed, mutable blob -- no
   allocation, no memcpy. We hand them straight to the GL texture slot
   and return; the plugin reclaims ownership the moment we exit. */
callback i32 OnVideoFrame(ref blob $FramePacket)
{
    $State assign RenderState;
    gl.UpdateTexture($State.VideoSlot, $FramePacket);
    return 1;
}

/* The event packet the gl plugin hands to the callback: a packed
   struct over the same bytes, so `$EventPacket assign GlEvent;` gives
   typed fields with no copy. */
struct GlEvent packet
{
    u8  magic0;
    u8  magic1;
    u8  magic2;
    u8  magic3;
    u8  version;
    u8  type;
    u8  button;
    u8  pad0;
    i16 x;
    i16 y;
    i16 dx;
    i16 dy;
    i16 wheel;
    u16 width;
    u16 height;
};

/* Called by the gl plugin for each pumped input event. We pull the
   typed event view over the same packet bytes and update shared state. */
callback i32 OnOpenGlEvent(ref blob $EventPacket)
{
    $State assign RenderState;
    $EventPacket assign GlEvent;

    if ($EventPacket.type == GL_EVENT_MOUSE_WHEEL)
    {
        $State.CameraDistance = $State.CameraDistance
            - (0.25 * (double)$EventPacket.wheel);
        $State.CameraDistance = ClampCameraDistance($State.CameraDistance);
    }
    /* ... mouse down / up / move handlers for left-drag rotation ... */

    return 1;
}

/* =========== Entry point ========================================== */

i32 main()
{
    i32  $Video = 0;
    blob $LogoPacket[0];

    /* Open the GL window. gl.Open returns 0 if no display is
       available -- on a headless box the demo just exits cleanly. */
    if (gl.Open(WINDOW_WIDTH, WINDOW_HEIGHT,
                    "Domin OpenGL FFmpeg demo") == 0)
    {
        return 0;
    }

    /* main's own copy of the state: seeded here, then refreshed by Tick
       every frame. Configure carries it into the callback world once. */
    blob $S[sizeof(RenderState)];
    $S assign RenderState;
    $S.WindowWidth    = WINDOW_WIDTH;
    $S.WindowHeight   = WINDOW_HEIGHT;
    $S.CameraDistance = 4.2;
    $S.SpeedX         = AUTO_SPEED_X;
    $S.SpeedY         = AUTO_SPEED_Y;
    $S.SpeedZ         = AUTO_SPEED_Z;
    $S.VideoSlot      = gl.CreateTextureSlot();
    $S.LogoSlot       = gl.CreateTextureSlot();
    /* ... starting angles, edge colors, cube gap ... */
    worker.Call("Configure", ref $S, 1000);   /* seed the callback world */

    /* Load the logo and bind it to its texture slot. */
    $LogoPacket = image.Load("Assets/Images/domin_logo.png");
    gl.UpdateTexture($S.LogoSlot, $LogoPacket);

    /* Wire FFmpeg to deliver frames into OnVideoFrame at real-time pace. */
    ffmpeg.SetOutputSize(VIDEO_WIDTH, VIDEO_HEIGHT);
    ffmpeg.SetRealtime(1);
    $Video = ffmpeg.Open("Assets/Videos/demo.mp4");
    ffmpeg.StartAudio($Video);

    /* Main loop: pump input -> decode frame -> draw. */
    while (gl.IsOpen() != 0)
    {
        gl.PumpEvents("OnOpenGlEvent");
        if (gl.IsOpen() == 0) { break; }

        if (ffmpeg.DecodeNext($Video, "OnVideoFrame") == 0)
        {
            ffmpeg.Rewind($Video);
        }
        worker.Call("Tick", ref $S, 1000);  /* advance + snapshot */
        RenderScriptFrame(ref $S);          /* draws the cube -- see full source */
    }

    ffmpeg.Close($Video);
    gl.Close();
    return 0;
}
02 — The problem, and the shape of the answer

A native buffer arrives. Then what?

In most scripting languages the answer is a pipeline: parse → allocate → convert to runtime objects → script → marshal back → native. In DominScript the answer is: lay a typed view over the bytes you already have. blob → typed view → script.

packet_view_demo.dom · the handlerdominscript
/* 9 bytes on the wire, byte-precise: u8 u8 u16 i32 u8 */
struct SensorFrame packet { u8 version; u8 kind; u16 sensorId; i32 value; u8 flags; }

/* The frame handler. $Request aliases the bytes the listener received. */
callback i32 OnFrame(ref blob $Request)
{
    $Request assign SensorFrame;             /* typed view over the bytes that arrived */
    if ($Request.version != 1) { return 0; } /* validate */
    printf("sensor=%d value=%d\n", $Request.sensorId, $Request.value);
    $Request.value = $Request.value * 2;        /* modify in place */
    $Request.flags = 0x80;                      /* ack bit */
    $Request = $Request;                        /* reply = these bytes */
    return 1;
}
socket → buffer → script

Copies between the receive buffer and the script: 0

The runtime allocates nothing and copies nothing for a callback's ref blob. The plugin lends a pointer and a length — any memory it can point at: its own receive buffer (the TCP server reads the socket straight into it), a decoded frame (the ffmpeg plugin's frame slot), or the wrapped library's own buffer — and the script sees exactly those bytes, in place. A release hook hands ownership back the moment the callback returns. The principle is general: if the library guarantees that the area behind the pointer it handed over stays valid until the callback returns, the adapter plugin can wrap that very area into the blob and copy nothing. Where a library reuses its buffers on its own schedule or hands out non-contiguous data (a planar video frame), the plugin keeps one slot of its own and copies once into it — the runtime side stays copy-free either way. That is what makes an adapter plugin cheap: it wraps memory a .so/.dll already holds instead of re-creating it on the script side.

no parse step

Runtime object conversions: 0

assign builds nothing. It gives the bytes a typed shape: $Request.value is a 4-byte read at a fixed offset.

typed, checked

Fields available immediately — and bounds-checked

Field access on a view that is too short is a runtime error with file and line, not a silent read past the end.

The whole script — handler and a client in one file — is Examples/packet_view_demo.dom; it is also a regression test and an example smoke recipe. The numbers above describe the shipped code path. The reply reassignment is the one copy of the round trip: it is the response contract.

03 — Why it is different

Not one feature. The combination.

None of these is unique on its own. Together they make one coherent model for scripting next to native code: the script and the native program do not have to live in two separate worlds.

01

Native data stays native

blob is raw bytes. A packet struct is a byte-precise layout you lay over them with assign. Data that is already in the right shape is not parsed, not boxed, not re-encoded. The language makes zero-copy the natural path; a plugin that copies does so by its own choice.

02

No garbage collector

No GC pauses, no hidden object conversion, no GC-managed object allocation hidden behind ordinary data access. Sized buffers, explicit casts, definite assignment. Data movement stays explicit — in a systems program you can tell what happens to the bytes.

03

Two-way native integration

Scripts call native plugins; native plugin threads can initiate callbacks, handing over a buffer by reference — script execution itself is serialized on the runtime's dispatcher thread. The plugin API is a small, versioned C ABI. A small existing C component can become a DominScript plugin in an afternoon.

04

Static safety without heavy runtime machinery

Preprocessor, lexer, parser and binder run before execution: types, scopes, definite assignment, plugin signatures. The runtime keeps only the guards that cannot be static. Callbacks execute one at a time on a dispatcher thread, so script state needs no locks.

04 — Where it helps

A big native application that needs a scriptable layer.

Without moving the data handling into a dynamic object model. That is the use case; these are its shapes.

Network
Packets & binary protocols
Frame in, typed view, decision, frame out. TCP, UDP, HTTP and TLS listeners with script callbacks.
Test & simulation
Hardware and protocol test rigs
Drive a device, check a byte-exact response, log it — in a script you change without a rebuild.
Tooling
Native tooling & diagnostics
Log and data collectors, instrumentation, custom servers, industrial glue — where the data is already binary.
Media
Media & data pipelines
FFmpeg frames, OpenGL, SDL2 — the buffer goes from the decoder to the script to the GPU without a detour through objects.
05 — Design

Every choice traces back to a real problem.

Byte-precise schemas, packed structs, ref-style callbacks, zero-copy boundaries, a plugin architecture: each exists because a data collector, a broadcast statistics pipeline or a performance-critical client needed it. The philosophy, the design pillars and the seven things you notice in the first hour are on their own page.

Read the design page →  ·  The story behind it →

06 — Plugins

Capabilities, not bloat.

The interpreter knows almost nothing on its own. Each capability is an independent shared library — load what you need, ignore the rest. 35 official plugins, 755 functions, every one documented — and the few things every script needs (output, blobs, structs, the OS) are built in.

Native I/O
tcpserver · httpserver · udpserver · udpclient · tls · tcpclient
Servers that call your script back per request, with peer metadata; TLS and mutual TLS; plain TCP and UDP clients.
Data
json · csv · sqlite · postgres · mysql · map · queue
Parse and query where you must; bind blobs straight into SQL where you can.
Real-time / media
ffmpeg · opengl · sdl2 · image · timer
Decoded frames as blobs, GL from the script, audio and windows via SDL2, monotonic clocks and schedulers.
Systems
process · fileio · path · stdio · crypto · log · regex · math · str · random · web
Child processes with captured output, files and paths, SHA/HMAC/AES, logging, regex, elementary math, string and URL helpers.
Built-in
os · blob · string · struct · plugin
Arguments and environment, byte-precise data types with packed structs, and introspection of the loaded plugins.
Yours
C, in an afternoon
A versioned C ABI, a callback-capable skeleton to copy, and a generated reference for every plugin you write. See 08 below.

→ See all 35 plugins and 755 functions · further plugins under consideration: log analysis · compression (gzip/zstd) · email (SMTP).

07 — Extend it yourself

Your own plugin, in an afternoon.

Need a capability that isn't shipping? Write your own plugin. DominScript ships with two complete skeletons you can copy as a starting point — one for event-driven plugins that call back into the script, and one for retain-style plugins that accept script-supplied data and keep their own copy. Fill in the parts specific to your task; the host integration, ABI, and lifecycle are already wired up.

A plugin is just a shared library (.so on Linux, .dll on Windows) exporting a fixed set of C entry points. Once compiled, a script picks it up with a single plugin "..." line at the top of the file.

No FFI bindings, no JNI-style glue, no marshaling layer between your code and the runtime. Your C function gets called directly when the script invokes it.

Start here: Writing an adapter plugin — the smallest complete plugin (plugins/hello, two verbs, ninety lines) walked through part by part: why capabilities live in adapters, what you must fill in, what is optional, and what the binder, the runtime and the loop machine do with each part. Then: How plugins start and stop — the init and deinit hooks every plugin has, with diagrams: why a missing library stops a script before it starts, and how plugins hand data to each other — and Optional libraries, the runtime folder a missing library is copied into.

Two ready-made skeletons
plugins/callback_skeleton/ · plugins/retain_skeleton/
  • callback_skeleton — event-driven For plugins that need to invoke the script on their own schedule: timers, listeners, async jobs. Full source + example script + step-by-step guide.
  • retain_skeleton — script-supplied data For plugins that accept blobs from the script and keep their own copy: caches, queues, persistence layers. Demonstrates the zero-copy host API in the script → plugin direction.
  • PluginApiReference.txt The full host-plugin ABI: types, callback dispatch contract, blob ownership rules, thread-safety model.
  • PluginDesignPrinciples.txt When to pick each pattern, how to retain data correctly, common pitfalls, and the lifecycle guarantees the host provides.
08 — Tooling

Mistakes caught while you type.

The VS Code / VSCodium extension runs the validator on every keystroke. The four static phases — preprocess, lexer, parser, binder — finish in milliseconds and surface each problem inline with exact line and column. Errors are red; non-fatal observations (an unused function, an orphan callback) come up in yellow. Both shapes carry the source label that tells you which phase produced them. A step debugger ships with the same extension: breakpoints, stepping, Watch, threads, remote attach — installing and using the debugger →

VS Codium showing a DominScript file with a yellow squiggle under an unused function definition on line 169 and a red error in the Problems panel for an unresolved call on line 334
At line 169 the yellow squiggle marks GetCubeFace_Geometry — defined but never called. A red error elsewhere in the file flags an unresolved call to the same name (visible in the Problems panel at line 334). Each entry is tagged with its source — dominscript (binder warning) vs. dominscript (binder error) — so you know which static phase noticed.
09 — Performance, honestly

DominScript is not a faster Python. It isn't trying to be.

The interpreter is a tree-walker with one specialization: the binder compiles provably-numeric statements and call-free loops into a small register machine (no JIT, no machine code). Its design advantage is elsewhere: when data already exists in native memory, the script works on it in place instead of converting it into scripting-language objects first. Two platforms, measured on the same machine, shown as two separate tables.

These benchmarks are not a claim that DominScript is generally faster than Python, and the table is not a race. Python is there as a reference point: a language everyone can place, measured on the same machine with the same programs, so that a number like “44 ms” means something. The two languages are of different kinds — Python is a general-purpose language with a vast ecosystem; DominScript is a typed scripting layer for native data and C plugins — and a row where DominScript is ahead does not make it a Python replacement, any more than a row where it is behind makes it unfit for its own job. The Python side of every row is an idiomatic CPython program (the timed loop inside a function, range-based loops, hashlib/json/re/struct where a Python programmer would use them), not a line-by-line transcription of the DominScript source; both sides of every row are in the repository, and every row name below links to the two programs side by side. What the rows show is where the runtime spends its time and where the native-data model changes the cost: Fibonacci is a measurement of function calls; the arithmetic loop shows what the binder’s specialization buys; JSON and SHA-256 show the plugin model; the packet path shows what the language was built for. The rows stay on the list whichever way they point, so that a regression cannot hide.

LinuxAMD Ryzen 9 8945HX · Linux x86-64 · GCC Release build · CPython 3.12.3 · package 512, 2026-10-06
Six of the twenty-one rows, Linux DominScript, ms Python 3, ms Elapsed time, shorter is better DS / Py
Recursive Fibonacci(28)pure script · recursion — a small integer function calling itself 9 24
DS
Py
0.38×
Arithmetic loop, 10M iterationspure script · tight integer loop 102 451
DS
Py
0.23×
JSON parse + read, 30kplugin-heavy · json plugin 22 38
DS
Py
0.58×
Plugin call, 1M × str.Lenplugin-heavy · the price of one native call from script · a verb the plugin declares pure runs as a loop-machine op 12 24
DS
Py
0.50×
Packet data path, 200k framesworkload · receive buffer → slice → typed view → fields → decision · Python: bytearray + struct.unpack_from 8 28
DS
Py
0.29×
Header parsing with a schema, 200k headersworkload · the same headers read through a packet big schema with bit fields · Python: the same program as the row above 59 114
DS
Py
0.52×
DominScriptCPython 3.12.3median in-script elapsed time of 7 runs, same machine (AMD Ryzen 9 8945HX, Linux x86-64), measured 2026-10-01 · ratio < 1 means DominScript is faster
See all 21 reproducible benchmarks →   both full tables, the reading of every row, and the two programs behind each one
Windowsthe same machine · Windows 11 x86-64 · mingw-w64 GCC 13 build · package 512, 2026-10-06, CPython 3.14.7
Six of the twenty-one rows, Windows DominScript, ms Python 3, ms Elapsed time, shorter is better DS / Py
Recursive Fibonacci(28)pure script · recursion — a small integer function calling itself 8 34
DS
Py
0.24×
Arithmetic loop, 10M iterationspure script · tight integer loop 102 520
DS
Py
0.20×
JSON parse + read, 30kplugin-heavy · json plugin 30 41
DS
Py
0.73×
Plugin call, 1M × str.Lenplugin-heavy · the price of one native call from script · a verb the plugin declares pure runs as a loop-machine op 13 28
DS
Py
0.46×
Packet data path, 200k framesworkload · receive buffer → slice → typed view → fields → decision · Python: bytearray + struct.unpack_from 8 32
DS
Py
0.25×
Header parsing with a schema, 200k headersworkload · the same headers read through a packet big schema with bit fields · Python: the same program as the row above 63 124
DS
Py
0.51×
DominScriptCPython 3.14.7the same machine booted into Windows 11, mingw-w64 GCC 13 build, measured 2026-10-01 · ratio < 1 means DominScript is faster
See all 21 reproducible benchmarks →   both full tables, the reading of every row, and the two programs behind each one
interpreter microbenchmarks

Where the time goes, row by row

Numeric loops, typed arrays and struct members run at a quarter to a half of CPython’s time, because the binder compiles them into a small register machine; plugin-bound rows follow the plugin’s C code at 0.3–1.0×; the packet data path, the shape this language exists for, at 0.25–0.30×; headers read through a packet big schema with bit fields at half of Python’s time — the readable form is the fast one. Python holds one row on each platform — SHA-256 at parity on Linux, regex on Windows (1.09×) — and neither is the interpreter’s doing. Twenty of the twenty-one rows are ahead on both platforms.

data-oriented workloads

What actually matters here

How many parses, copies, allocations and object conversions a socket → buffer → struct → decision → plugin path needs. On the shipped TCP listener path: 0 copies between the receive buffer and the script, 0 runtime object conversions, typed fields immediately (section 02). Per-call script→plugin and plugin→script overhead, blob hand-over at 1 kB / 64 kB, startup+validation time and steady event-loop latency are the next numbers to publish, measured the same way.

Performance note. The numbers above measure the current implementation — a deliberately simple interpreter that does not execute bytecode — not the ceiling set by the design. A JIT compiler and a garbage collector are intentionally not part of the design: DominScript favors predictable execution over optimizations that may introduce runtime compilation pauses or garbage-collection stalls. Raw interpreter speed is therefore not the primary design goal. The focus is on predictable execution, direct native data access, and efficient integration with C plugins — if a hotspot is too slow in script, it belongs in a C plugin.

No cherry-picking: every benchmark script for both languages is in the repository, the four workload rows included, and the runner writes results.json. Rows are added, never dropped. The numbers above will move; the method won't.

10 — Project status

Where it stands today.

A snapshot of what's working, what's in flight, and what's planned. The project is in active development, currently at version 0.0.3.

Tested on itself, across real workloads

900+ automated tests. ~18,000 fault-injection cases. 35 native plugins under test.

The test suite covers the language, the runtime and all 35 native plugins, with 900+ functional tests and roughly 18,000 injected-failure cases per pass. The test system itself is written in DominScript. It reaches from language semantics to networking and native plugin interactions: the functional tests and 50+ example smoke runs are driven by one .dom runner that is make test on Linux and on Windows alike — plus the harnesses around it: TLS round-trips against a live OpenSSL server, mutual-TLS listeners, TCP/UDP/HTTP callback servers, database smoke tests, the debugger protocol, out-of-memory injection sweeps (~18,000 injected runs per pass), and AddressSanitizer runs of the whole suite. A language that can drive processes, sockets, files and its own interpreter well enough to be its own test framework has been exercised the way users will exercise it.

Parser & AST
Stable
Binder & type checker
Stable
Runtime & interpreter
Stable
Plugin system & ABI
Stable
Callback / event ABI
Stable
Runtime eval & validation
Stable
Windows native build
Validated
Test suite in DominScript (900+ tests, 50+ example smokes)
Done (0.0.3)
Windows ↔ Linux parity
Byte-for-byte
Documentation
Audited; dependency matrix pending
Editor support (VS Code / VSCodium)
Preview
First public release (0.1.0)
Engineering gates closed; licence & packaging pending
ARM / Raspberry Pi port
Feasibility done
macOS port
Planned
11 — Questions

Frequently asked.

The interpreter is on the 0.0.x development line — currently 0.0.3. The first public release is milestone 0.1.0, gated on a release-readiness audit; the number is deliberately conservative, because under semantic versioning 0.x means the surface may still change. A 1.0 would be a promise of stability, and that promise is made once, when it can be kept. The parser, binder, runtime, and plugin system are in daily use and covered by a regression suite of several hundred tests; the first downloadable release will be announced on this page.

It sits next to embeddable scripting runtimes — Lua/LuaJIT, AngelScript, Wren, QuickJS, or Python used as an extension host. None of them is "worse"; the trade-off is different: DominScript keeps the data native and typed instead of converting it into a runtime object model, and lets the native side call the script back. If your problem is glue over a dynamic object graph, one of those is a better fit. If your problem is a binary buffer that has to stay a binary buffer, this is the one built for it.

If you need byte-precise control over data, want to drive native libraries like SDL2 / OpenGL / FFmpeg directly from script, and want explicit, predictable data handling (no GC, no hidden object conversion), DominScript fits. If you want a one-liner to rename some files, use whatever shell you already have.

Linux and Windows today. The native Windows build runs the same regression suite as Linux, through the same DominScript runner; the only differences in the verdicts are platform-specific skips (a POSIX-only shell recipe here, a missing runtime library there), never a failure on one side. macOS and ARM / Raspberry Pi support are planned but not yet started — there will be no native macOS or ARM binaries in the first release.

By itself. Since version 0.0.2 the entire test suite is written in DominScript: the manifest runner that drives the 900+ regression tests, and every harness around it — TLS round-trips with a live OpenSSL server, mutual-TLS listeners, out-of-memory injection sweeps, the debugger protocol, database smoke tests, the benchmark self-test. The same .dom runner is make test on Linux and on Windows; the retired shell and PowerShell twins are gone.

That is not a stunt. A language that can drive processes, sockets, files, and its own interpreter well enough to be its own test framework has been exercised in exactly the ways users will exercise it — and every bug the migration surfaced (there were several, on both platforms) was found by DominScript code reading DominScript output.

Yes — a Visual Studio Code / VSCodium extension provides syntax highlighting, plugin-aware completion and hover for all 35 official plugins (755 functions) and the built-in namespaces — generated straight from the source so it stays in lockstep with the reference — plus a snippet pack of 131 templates: generic language scaffolds and plugin-specific patterns (2–5 per plugin, each naming the exact plugin load path it needs). A step debugger ships with it: breakpoints (including conditional ones), stepping, a Watch panel, and hover / Debug Console evaluation in the selected frame's real scope — see the debugger page.

It also gives live diagnostics: the four static phases — preprocessor, lexer, parser, binder — run on save (or as you type) and surface errors inline with exact line and column, without executing your script. The extension ships alongside the preview; diagnostics use the DominScript binary, so they light up once you have a build.

A plugin is a shared library (.so on Linux, .dll on Windows) that exports a fixed C ABI. Scripts declare what they need with a plugin "..." directive at the top of the file. The runtime loads each plugin once at startup, validates the ABI, and unloads everything cleanly at exit.

Built-in plugins (blob, string, struct, os) are linked into the interpreter directly. Everything else lives as an external library that any DominScript build can pick up.

Functions marked with the callback keyword are reserved entry points the runtime can dispatch into — they aren't directly callable from script. Plugins (TCP listener, UDP server, HTTP listener, worker, timer) register callbacks and the host calls them when their event fires.

Borrowed buffers come in as ref blob parameters: zero-copy, mutable, and freed by the plugin once the callback returns. The host provides timeout, error, and shutdown policies plus introspection helpers like Phase(), StatusText(), and JobInfo().

Crucially, a single dispatcher thread runs every callback, one event at a time — the same model as a JavaScript event loop. Plugin threads enqueue an event and wait; they never run script code themselves. The practical consequence: two callbacks never execute concurrently, so script-level state shared between them (a ref blob, a counter, a buffer) is race-free by construction. You don't write a mutex to protect ordinary script variables.

The core interpreter stays closed source; everything you build against is open. The release ships the domin binary for Linux and Windows, and with it the full source of the official plugins, the plugin ABI headers, the examples and the documentation. The source of the core itself is not published.

The binary is free — for individuals and companies alike, commercial use included. Free means free to download and to use in your own projects and products; the exact licence text ships with the release.

You never need the core source to extend it. A plugin is a C shared library that talks to the runtime through a small, versioned ABI (the plugin API headers, shipped with the release). With the plugin skeletons and the 35 official plugins as worked examples, a new plugin is a copy-and-edit job — small enough that an AI coding assistant can draft one from the examples. Nothing forces you to use the built-in plugins either: a plugin of your own with the same job can take their place, and your own namespaces sit next to the official ones. A plugin you write is yours: you choose its licence, open or closed — nothing in DominScript obliges you to publish its source.

Be there
at launch.

The first release (milestone 0.1.0) is in preparation, and it will be free to download and use: individuals and companies alike, commercial use included. The core stays closed source; the plugins, the plugin ABI, the examples and the docs ship as source. The exact licence text ships with the release.

Linux Windows macOS — planned ARM — planned