Coming soon · v0.9 · rev31zzdj

A scripting language for serious work.

DominScript is strongly typed, plugin-based, and built for deterministic performance work — graphics, media, networking, system tools. Three validation passes catch your bugs before the program even starts.

Spec version
0.9
Test suite
377 + 41
Plugins
32 + 4 wip
Platforms
Linux · Win
01 — Demonstration
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 600 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/blob/BlobPlugin";
plugin "../plugins/script_state/ScriptStatePlugin";
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 ... */
}

/* =========== 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)
{
    ref blob &$S = blob.RefFromPointer(script_state.GetPointer(),
                                       script_state.GetSize());
    $S assign RenderState;
    gl.UpdateTexture($S.VideoSlot, $FramePacket);
    return 1;
}

/* 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)
{
    ref blob &$S = blob.RefFromPointer(script_state.GetPointer(),
                                       script_state.GetSize());
    $S assign RenderState;
    $EventPacket assign GlEvent;

    if ($EventPacket.type == GL_EVENT_MOUSE_WHEEL)
    {
        $S.CameraDistance = $S.CameraDistance
            - (0.25 * (double)$EventPacket.wheel);
        $S.CameraDistance = ClampCameraDistance($S.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;
    }

    /* Allocate the shared render state and initialize. Every callback
       and the main loop will bind a ref-blob view over these bytes --
       no globals, no duplication; every caller sees the same data. */
    script_state.Allocate(sizeof(RenderState));
    {
        ref blob &$S = blob.RefFromPointer(script_state.GetPointer(),
                                           script_state.GetSize());
        $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 ... */
    }

    /* Load the logo and bind it to its texture slot. */
    $LogoPacket = image.Load("Assets/Images/domin_logo.png");
    /* ... bind $S, opengl.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);
        }
        RenderScriptFrame();  /* draws the cube -- see full source */
    }

    ffmpeg.Close($Video);
    gl.Close();
    script_state.Free();
    return 0;
}
02 — Philosophy

A condensate of practiced engineering.

DominScript is not an academic language experiment. It's the deliberate distillation of decades of professional work — distributed vote-counting systems, on-site collectors, performance-critical clients — into a reusable tool.

Every design choice traces back to a real problem: byte-precise schemas, packed structs, ref-style callbacks, zero-copy boundaries, plugin architecture. Comfort in the language means the engineer thinks about the task, not the tool.

In most languages, parsing is something you do because the data isn't yet in the right shape — bytes come in, objects come out, and a tax is paid in between. In DominScript, packed structs and zero-copy blobs mean the data is often already in the right shape; the parse step disappears. (The fastest parse is the parse you don't run.)

And when you genuinely need runtime code generation — REPLs, formula engines, config DSLs — validate-then-eval lets you do it without giving up the static guarantees the rest of the language gives you.

That is the entire goal.

03 — Design pillars

Three rules, kept seriously.

DominScript is built on three commitments. They aren't slogans — every feature is checked against them.

01

Minimal interpreter

The core knows almost nothing on its own. No built-in network, file, graphics, or OS calls. Every capability arrives through a plugin — a platform-specific shared library (.so on Linux, .dll on Windows). Scripts get exactly the powers their loaded plugins grant. Nothing more.

02

Early error detection

Three validation passes run before a single instruction executes. The parser checks syntax. The binder resolves types, variable scopes, definite assignment, and plugin ABI compatibility. The runtime handles dynamic range and type checks at execution. Bugs caught before they run.

03

C-translatability

A hot script function should be mechanically and conceptually easy to move into a C plugin. The #define directive, brace blocks, typed signatures, and explicit casts all serve this goal. When performance demands it, the path from script to native is short and predictable.

04 — Plugin ecosystem

Capabilities, not bloat.

Each capability is an independent shared library — load what you need, ignore the rest. The runtime stays small while the surface area grows.

Built-in
os
Command-line flags, env vars, exit codes, script paths.
Built-in
blob · string · struct
Byte-precise data types with packed-struct support.
I/O
file_io · path · stdio
Filesystem access, path normalization, stream I/O.
Networking
callback_tcp_listener
Real socket I/O dispatched through the callback runtime.
Networking
callback_udp_client / server
Async UDP with retain/release blob ownership.
Networking
callback_http_listener
HTTP server with route metadata and hardening.
Graphics
sdl2
Windowing, input, and rendering primitives via SDL2.
Graphics
gl
Self-sufficient OpenGL adapter (gl.*) — window/context lifecycle (Open/Close/IsOpen), the SDL event pump and texture upload, plus the classic fixed-pipeline primitives (Begin, Vertex3f, Frustum, ...). The whole spinning-cube render is built from these in the script; it drives the FFmpeg packet pipeline at 60 FPS.
Media
ffmpeg · image
Video decoding, BMP loading, frame blobs across plugin boundaries.
Async
callback_worker
Background jobs with phase/status/error inspection.
Async
timer · async_meta
Sync and async timers, shared capability profiles.
Process
process
Spawn child processes, capture output, manage lifetimes.
Storage
sqlite
Embedded persistence via libsqlite3 — prepared statements, transactions, pre-update hooks. Self-diagnosing on missing or mismatched runtime DLL/SO.
Data
json
cJSON-based parse and serialize with dot-notation path reads (json.GetString(h, "user.profile.name")), object/array builders, and pretty-print output.
Concurrency
queue
Thread-safe named FIFO queues with blocking wait variants. Event-driven on condition variables — 0% CPU when idle, microsecond wake-up on push. Multi-producer / multi-consumer ready.
Observability
log
Structured logging with severity levels (INFO/WARN/ERROR), timestamps, plain or JSON output, and file rotation. Pure C — no external library dependency.
Data
map
Thread-safe named hash maps with mixed value types (string, i64, f64, blob). Per-map RW locks for concurrent access; race-free SetIfAbsent for default-value init.
Data
csv
RFC-style CSV parse and serialize with quoting and multi-row headers. File I/O stays in the script (file_io.ReadFile → csv.Parse) for clean separation and no platform-specific file risk.
Security
crypto
SHA-256 (FIPS 180-4), HMAC-SHA256 (RFC 2104), CSPRNG, hex codec — string and binary-blob variants. Pure C, no OpenSSL. Correctness proven against known-answer test vectors.
Networking
tls
OpenSSL-backed TLS: client connect/handshake/read/write, plus callbacktcp.EnableTls to wrap the TCP listener for HTTPS. Backend detected at runtime; graceful skip when libssl is absent.
Text
str
Fast text toolkit — search, slice, transform, compare, split, and number conversion. memchr/memcmp core (typically SIMD-accelerated) with 256-entry char-set tables. ASCII-oriented; binary data goes through blob.
Web
web
HTTP plumbing in pure script: URL and HTML escaping, Base64 (text and blob), request-line / header / cookie / form parsing, content-type and MIME helpers, and response builders.
Async
callback_listener
Generic async listener: multi-subscription dispatch plus the unified job / route / subscription inspection contract that the TCP, UDP, and HTTP listeners share.
Data
random
Seedable pseudo-random numbers (xoshiro256++): integers, ranges, doubles, fair bool, deterministic seeding, plus save/restore of the full state for reproducible runs.
Storage · P1
mysql
Client to MySQL / MariaDB servers via the shared wire protocol — one plugin covers both.
Storage · P2
postgres
PostgreSQL client with named parameters and LISTEN/NOTIFY support via libpq.
Tools
log_analyzer
Structured log processing: format detection (syslog, access logs, JSON-lines), aggregation, anomaly detection, live-tail.
Text · P3
regex
POSIX regular expressions for log parsing, input validation, compact text processing.

* In development — planned for upcoming releases. Further plugins under consideration: compression (gzip/zstd) · email (SMTP).

05 — 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.

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.
06 — Why DominScript

Seven things you'll notice immediately.

/ 01

Strong static types

i8 i16 i32 i64, unsigned variants, f32 f64, bool, string, blob, plus user schemas. Explicit casts with exact-numeric runtime checks.

/ 02

Definite assignment

The binder traces every code path. Reading an uninitialized variable is a bind-time error, even across branchy while-if-break flow. The runtime never sees a half-set value.

/ 03

Zero-copy at the boundary

A ref blob parameter aliases memory across the plugin / script line — in both directions. A plugin hands bytes to a callback without allocating; a script hands a blob to a plugin without the host copying first. No per-frame allocation, no host-object boxing — it's the same data on both sides. The plugin chooses when to retain.

/ 04

Packed schemas

Byte-precise struct layouts you can lay over a blob, like a C struct over a buffer. Predictable offsets, no padding surprises, native interop without bindings.

/ 05

Three validation passes

Parser, binder, and runtime check the script in order: syntax, types & scopes & plugin ABI compatibility, then dynamic range and type guards at execution. Most error classes are caught before the first instruction runs.

/ 06

Race-free by construction

A single dispatcher thread runs every callback, one at a time — like a JavaScript event loop or Python's GIL. Two callbacks never execute concurrently, so script-level state shared between them is safe without you writing a single mutex. No hidden allocations, no surprise GC pauses, no fire-and-forget concurrency.

/ 07

Validated runtime code

Build a script fragment at runtime, gate it with os.ValidateScript() to catch lexer / parser / binder errors before a single line runs, then os.Eval() it in the caller's own scope. REPLs, formula engines, config DSLs — without the "fingers crossed" pattern.

07 — 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.

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.
08 — 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; the test suite stands at 377 passing unit tests plus 41 example smoke tests, with identical baseline results on Linux and Windows.

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
Windows ↔ Linux parity
Byte-for-byte
Documentation
In progress
Editor support (VS Code / VSCodium)
Preview
Public v1.0 release
Preparing
ARM / Raspberry Pi port
Feasibility done
macOS port
Planned
09 — Questions

Frequently asked.

The language is at spec version 0.9, with the parser, binder, runtime, and plugin system stable. A public v1.0 release is in preparation. Sign up below — we'll let you know the moment it's ready.

It's closer to embeddable, performance-oriented languages — think Lua, Tcl, or the role C extensions play inside Python — than to general-purpose languages like Python or Node.

If you need byte-precise control over data, want to drive native libraries like SDL2 / OpenGL / FFmpeg directly from script, and care about deterministic behavior, 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 377+ test suite as Linux — identical pass/fail counts, byte-for-byte. macOS and ARM / Raspberry Pi support are planned but not yet started — there are no native macOS or ARM binaries at v1.0.

Yes — a Visual Studio Code / VSCodium extension provides syntax highlighting, snippets, and plugin-aware completion and hover for all 32 official plugins (648 functions), generated straight from the source so it stays in lockstep with the reference.

It also gives live diagnostics: the three static phases — 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 or Python's GIL. 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.

Licensing for the public release hasn't been finalized yet. Once decisions are made — including any source availability — they'll be announced ahead of v1.0.

Be there
at launch.

v1.0 is in preparation. Leave your email and we'll let you know when it's ready — one message, no spam.

Linux Windows macOS — planned ARM — planned