Systems code and scripts, joined by the compiler.

Dado is a systems language that transpiles to C11. DadoScript is its scripting dialect, compiled to JavaScript. One compiler reads both, so every call across the boundary is type-checked and the glue is written for you, natively or on wasm.

Dado on GitHub Read the docs → MIT · pre-release
main.dadoDado
package main

import "./game"

i32 main():
    i64 left = game.on_hit(10, 3)
    return i32(left)
game/Rules.dadosDadoScript
class Rules

export int on_hit(int hp, int damage):
    if damage > hp:
        return 0
    return hp - damage
int ⇄ i64, checked by dadoc no VM, no handle, no binding code on wasm, a direct import of the module
Across the seam

Every direction, checked when it compiles.

The boundary runs both ways and reaches JavaScript itself. In every direction the compiler checks the call against the declaration on the other side and generates the conversion.

Dado→DadoScript

Run a script, hold its instances

A program makes a VM, creates an instance of a class and calls its methods through a typed handle. The class is compiled into the program, so nothing is loaded by path or looked up by name.

import "./npc"

try VM vm = #script_vm()
try npc.Goblin g = #script_new(vm, npc.Goblin, 3)
g.hit(3)
#delete(vm)
DadoScript→Dado

Call Dado by name

A script calls its package's Dado functions as if they were its own. Integers wrap into narrower widths, strings cross as UTF-8 copies, and a Dado failure arrives as an error the script can catch.

// shapes.dado
public f32 len2(f32 x, f32 y):
    return x * x + y * y
// Probe.dados, same package
class Probe

float measure():
    return len2(3.0, 4.0)
DadoScript→JavaScript

Use JavaScript libraries, typed

Declare a module's functions with script types. Arguments are checked going out and answers coming back, and a thrown exception becomes an Error. The JavaScript is embedded in the binary.

class Terrain

foreign "lib/noise.js":
    float noise2(float x, float y)

float height(float x, float z):
    return noise2(x, z) * 40.0
JavaScript→Dado

Reach Dado's exports from JavaScript

On the web every export is a wasm export. JavaScript that a script imports reaches them through dados:exports, which is filled before main runs.

// game.dado
export i32 damage(i32 n):
    return n * 2
// hit.js, imported by a script
import { exports, memory } from "dados:exports";

export function hit(n) {
    return exports === null ? 0 : exports.damage(n);
}
Dado signal→DadoScript

Deliver events from any thread

connect hands a Dado signal to a script function. Messages emitted from any thread arrive in order on the script's own thread, at a safe point, never in the middle of script code.

// events.dado
#signal(16) hits(i32 n, f64 x)
// Hud.dados
class Hud

void ready():
    connect(hits, void(int n, float x): println(n))
DadoScript→npm

Ship scripts as a JavaScript library

A package of scripts builds as an ES module with index.d.ts written from the checked program. A wrong argument from JavaScript throws a DadosError with a code and a trace.

export class Dice

int sides = 6

void init(int n):
    sides = n

int roll(int seed):
    return seed % sides + 1
// JavaScript, typed by index.d.ts
new Dice(20).roll(7);
What crosses, and as what

One table, written into the compiler.

Dado DadoScript JavaScript
i8 … u64Narrower widths wrap; u64 goes by its bits.intnumber or bigint
f32, f64A float never becomes an integer by itself.floatnumber
boolboolboolean
string8, ref([]char8)Copied, read as UTF-8.stringstring
slice, ref([]T), [N]TCopied for the call, locked against writes while lent.[]TT[]
type Vec2: (f32 x, f32 y)Slots that share a type are an array.[]float, read as p.xnumber[]
(i32 id, f64 w)tuplereadonly [A, B]
handle, npc.Goblinthe instanceclass Goblin
failure, an i32 codeError with that codethrown DadosError
pointer, map, distinctRefused when it compiles, naming what to write instead.refusedrefused

Checked where it is written

The count and type of every argument are checked against the other side's declaration, in both directions, before anything runs.

One manifest

dadoc --emit=manifest writes the public surface of both dialects as JSON. The C header and the TypeScript declarations are generated from it.

Failures keep their meaning

A Dado failure code becomes a script Error, a script Error fails a Dado try, and JavaScript sees the same code.

Targets

One program, native or in the browser.

Native

Dado becomes C11 and the scripts run in QuickJS-ng, embedded in the binary with any JavaScript they import. A VM belongs to its thread and can be stopped or paused from any other.

$ dado build game

Web

The C is built to wasm32 with zig cc. Exports become wasm exports, a script's pure functions become the module's imports, and the build writes the page and loader from your HTML template.

$ dado build game --target=web

JavaScript library

A package of scripts alone builds as an ES module for Node, a page or a bundler, with its TypeScript declarations beside it.

$ dado build game --target=js
DadoGL
Designed, not built yet

Shaders from the same compiler.

DadoGL is the third dialect, for the GPU. A shader is written once in a .dadogl file, compiled to SPIR-V and translated for every graphics API, and loaded by the Dado program beside it.

  • One source, every backend

    A build names the formats it needs and can bundle several, so one shader serves Vulkan, Metal, Direct3D, OpenGL and WebGPU.

  • The same boundary, automated

    The plan is for shaders to share Dado's types, so the compiler lays out buffers and uniforms from the declarations the program already has, and writes the binding glue, as it does between Dado and DadoScript.

  • Reflection without a runtime parser

    Entry points, bindings and vertex layout come from the compiler, so a pipeline is laid out from facts it already checked.

  • Backends can be written today

    The Shader interface already exists in core:shader. A renderer written against it now won't need to change when shaders arrive.

Until then the compiler recognises .dadogl files and refuses them with a message saying DadoGL is not built yet. Its spellings are reserved, so nothing written today will collide with it.

render.dadoDado
// Lit.dadogl sits in this package
Shader lit = #shader_load(Lit)
  • to_string()WGSL, GLSL, GLSL ES, HLSL, MSL
  • to_bytes()SPIR-V, DXIL, metallib
  • format()which one this is
  • entry_points()and their stages
  • bindings()by group and slot
  • vertex_attributes()the vertex layout
  • vertex_buffers()and its buffers
wgslWebGPU
glslOpenGL
esslWebGL, GLES
hlslDirect3D
mslMetal
spirvVulkan
dxilDirect3D 12
metallibMetal
$ dado build game --shader=spirv,msl,wgsl
Underneath

Everything is a tuple.

  • f64 number
  • (f64) number
  • [1]f64 number

Function arguments, structs, arrays, unions, enums and multiple returns are one construct, and a slot's name is a view over the shape rather than part of it.

The seam stays small for the same reason. A shape whose slots share a type is an array, so it crosses as a script array, and its names come along as views.

color.dadoDado
type Rgb: (f32 r, f32 g, f32 b)

// Rgb is [3]f32, so a written name and a
// computed subscript reach the same slots.
public f32 channel(Rgb c, i32 i):
    return c.r + c[i]
One housing, three stems

The family

Dado .dado

The systems language. Transpiles to C11, with no runtime and no garbage collector.

DadoScript .dados

Scripted Dado. Compiles to plain JavaScript, run by QuickJS-ng natively and by the browser on the web.

DadoGL .dadogl

The shader language. Its spellings are fixed now so that code written today keeps working.

Not built yet
Status

One person's language, released in stages.

Dado is written to suit one developer's preferences. There are more mature languages in this space, with far bigger ecosystems. If you happen to share the same ideals, you might enjoy it too.

GitHub carries the releases, the standard library collections and the documentation. The compiler's source will follow once the language is stable.

  • Linux x86_64Built and tested
  • macOS, Apple siliconBuilt and tested
  • WindowsSupported
  • Web, wasm32 in the browserSupported
  • Linux aarch64Checked
  • macOS, IntelChecked

Checked means the standard library type-checks and emits C for that target on every test run.