A polyglot programming language IR where every program is a Protocol Buffer message.
Website · Playground · Documentation · Examples · Introducing Ball
Programs are structured data, not text. A Ball program is a protobuf message that can be serialized, transmitted, stored in a database, and compiled to any target language — with zero parsing ambiguity.
| Capability | Details |
|---|---|
| Programs are data | Protobuf schema enforces structural validity. If it deserializes, it is syntactically valid. No parser, no syntax errors. |
| Static security auditing with no silent gaps | ball audit statically reports every side effect. No eval, no FFI, no hidden capabilities — every I/O operation flows through a named base function, and the audit resolves one by the identity the engine dispatches, not by the call site's (spoofable) module string. A program can still declare its own base module for a host to implement (the extension seam); the audit cannot know what such a function does, so it reports every call into one — qualified, unqualified or under a benign-looking module name alike — as an explicit custom capability at unknown risk naming the declaring module (deniable with --deny custom), never as purity. |
| Multi-language compilation | Compile Ball to Dart, C++, and more. Encode Dart source back to Ball. Round-trip real-world code. |
| Self-hosted toolchain | The Dart reference interpreter is itself encoded as Ball, then compiled back to Dart with byte-identical conformance output. A TS-native compiler (@ball-lang/compiler) uses ts-morph in-process — Dart fixtures round-trip to TS and execute byte-identical on Node, and the full engine.dart parses cleanly. The C++ compiler runs the conformance corpus end-to-end. Exact pass counts are CI-gated, never hand-maintained — see the conformance matrix. |
| Three runtime engines | Dart (true async), C++ (native), TypeScript (runs in the browser). |
| Package management | Import modules from pub, npm, and more registries with ball add pub:package@^1.0.0. |
| Web playground | Try Ball in your browser at ball-lang.dev/playground. |
npm install @ball-lang/engineimport { BallEngine } from '@ball-lang/engine';
const program = JSON.parse(fs.readFileSync('hello_world.ball.json', 'utf-8'));
const engine = new BallEngine();
await engine.run(program);# The Dart pub-workspace + Melos root is the repo root; resolve from there.
dart pub get
# Run a program
dart run ball_cli:ball run examples/hello_world/hello_world.ball.json
# Compile Ball to Dart source
dart run ball_cli:ball compile examples/hello_world/hello_world.ball.json
# Encode Dart source back to Ball
dart run ball_cli:ball encode my_app.dart
# Security audit
dart run ball_cli:ball audit examples/hello_world/hello_world.ball.jsonBall program (hello_world.ball.json):
{
"@type": "type.googleapis.com/ball.v1.Program",
"name": "hello_world",
"version": "1.0.0",
"modules": [
{
"name": "std",
"functions": [{ "name": "print", "isBase": true }],
"typeDefs": [{
"name": "PrintInput",
"descriptor": {
"name": "PrintInput",
"field": [{ "name": "message", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING" }]
}
}]
},
{
"name": "main",
"moduleImports": [{ "name": "std" }],
"functions": [{
"name": "main",
"outputType": "void",
"body": {
"call": {
"module": "std", "function": "print",
"input": { "messageCreation": { "typeName": "PrintInput", "fields": [
{ "name": "message", "value": { "literal": { "stringValue": "Hello, World!" } } }
]}}
}
}
}]
}
],
"entryModule": "main",
"entryFunction": "main"
}Compiled Dart output:
void main() {
print('Hello, World!');
}Compiled C++ output:
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}Every Ball computation is exactly one of these nodes:
| Node | Purpose | Example |
|---|---|---|
call |
Invoke a function | std.add(left, right) |
literal |
Constant value | 42, "hello", true |
reference |
Variable access | input, x |
fieldAccess |
Field of a message | input.name |
messageCreation |
Construct a message | Point{x: 1, y: 2} |
block |
Sequential statements | let x = 1; x + 1 |
lambda |
Anonymous function | (input) => input.x + 1 |
Every function takes one input message and returns one output message (gRPC-style). Base functions have no body — their implementation is provided per-platform:
std— arithmetic, comparison, logic, bitwise, strings, math, control flow, type ops, cascade, null-aware access, spread, invoke, record (seedart/shared/std.jsonfor the canonical base-function inventory)std_collections— list/map/set operationsstd_io— console, process, time, randomstd_memory— linear memory for C/C++ interopstd_convert— JSON / UTF-8 / base64 encode-decodestd_fs— file and directory operationsstd_time— clock, timestamp formatting/parsing, duration arithmeticstd_concurrency— threads, mutexes, and atomics
Control flow (if, for, while, for_each) is implemented as base function calls with lazy evaluation — keeping the language completely uniform.
The single source of truth is proto/ball/v1/ball.proto. All implementations deserialize from this schema. Metadata fields are cosmetic — stripping all metadata never changes what a program computes.
| Command | Description |
|---|---|
ball run <program> |
Execute a Ball program |
ball compile <program> |
Compile to target language source code |
ball encode <source> |
Encode source code into a Ball program |
ball round-trip <source> |
Encode then compile, show diff |
ball audit <program> |
Static capability analysis (security) |
ball info <program> |
Inspect program structure |
ball validate <program> |
Check program validity |
ball build <program> |
Resolve imports into self-contained program |
ball init |
Create ball.yaml in current directory |
ball add <spec> |
Add dependency (pub:pkg@^1.0.0) |
ball resolve |
Resolve dependencies into ball.lock.json |
ball tree |
Print dependency tree |
| Language | Proto Bindings | Compiler | Encoder | Engine |
|---|---|---|---|---|
| Dart | Yes | Full | Full | Full (true async) |
| TypeScript | Yes | Full | Full | Full (self-hosted, browser + Node) |
| C++ | Yes | Full | Full | Full (self-hosted) |
| Rust | Yes | Full | Full | Full (self-hosted) |
| C# | Yes | Full | Full | Full (self-hosted) |
| Go | Yes | -- | -- | -- |
| Python | Yes | -- | -- | -- |
| Java | Yes | -- | -- | -- |
Statuses drift — the authoritative source is CI (.github/workflows/ci.yml, conformance-matrix.yml), not this table. The TS pipeline is a full CI-gated compiler + self-hosted engine + encoder (the engine passes the conformance corpus; the encoder round-trips TS→Ball→target through universal std). C++ has a compiler, encoder (Clang AST → Ball), and self-hosted engine that passes every conformance fixture. Rust and C# are also complete, CI-gated pipelines — self-hosted engines at Dart parity (both at 364 passed, 0 failed, 364 total) — see rust/AGENTS.md and csharp/AGENTS.md.
flowchart LR
BP["Ball Program\n(protobuf)"] -- compiler --> SRC["Source Code"]
SRC -- encoder --> BP
BP -- engine --> RT["Runtime\nExecution"]
The table above is about this project's own corpus. This one is not: it is the standing measurement of what each pipeline does to code the project did not write — pinned third-party packages, round-tripped through that language's encoder and compiler (issue #493).
| Measure | scored | clean | 1 encoded | 2 compiled back | 3 re-encoded | 4 declarations kept | excluded (test-only) |
|---|---|---|---|---|---|---|---|
| Dart — Tier A | 106 | 65 (61%) | 106 | 106 | 106 | 106 | 0 |
| TypeScript — Tier A | 48 | 4 (8%) | 29 | 28 | 21 | 16 | 6 |
| C# — Tier A | 472 | 0 (0%) | 141 | 140 | 58 | 0 | 0 |
| Python — Tier A | 73 | 0 (0%) | 5 | 5 | 0 | 0 | 0 |
| Rust — Tier A | 77 | 0 (0%) | 7 | 7 | 1 | 0 | 34 |
| Go — Tier A | 21 | 0 (0%) | 0 | 0 | 0 | 0 | 13 |
| Dart — Tier B (per-file) | 106 | 103 (97%) | — | — | — | — | — |
| Dart — Tier B (whole-package) | 5 | 3 (60%) | — | — | — | — | — |
Measured over pinned third-party packages — code this project did not write — by .github/workflows/coverage-study.yml (weekly, plus workflow_dispatch). Tier A is structural (encode → compile back → re-encode → declaration inventory → fixpoint); Tier B substitutes the compiled-back file into the package's own test suite, which is the tier that sees a construct that round-trips cleanly but changes what the program computes.
Tier A scores library code only: a package's own test suite is a different population — written against that package's internals, compiled under different settings, and encoded by nobody — so it is excluded from the denominator by each language's own convention, and the count of what that removed is published above rather than applied silently.
Every row is floored at the number shown: the workflow fails on a drop in the clean ratio, in ANY funnel stage's ratio, or in the scored denominator, and raises the floor automatically on an improvement (tools/coverage-study/baseline.json). The exclusion count is recorded there too but is not floored — a pin whose own test suite grew moves it in either direction and neither is a regression. Four rows sit at 0% clean because those compilers emit runtime-call-shaped source their syntactic encoders cannot read back — for those, the funnel columns are the live signal. See tests/conformance/COVERAGE_STUDY.md for the methodology and the honest limits.
Define custom base modules for any platform (Flutter, Unity, embedded):
{
"name": "flutter",
"functions": [
{ "name": "text", "inputType": "TextInput", "outputType": "Widget", "isBase": true }
]
}Then implement the base function in your target compiler or engine. See docs/IMPLEMENTING_A_COMPILER.md for a complete guide.
Ball programs have provably complete capability analysis because:
- No
eval— programs are data, not text. There is no way to construct and execute arbitrary code at runtime. - No FFI — the only way to perform side effects is through base function calls with known names.
- Static analysis is exhaustive —
ball auditwalks the expression tree and reports every base function call, categorized by capability (I/O, network, filesystem, process, memory).
The ball-audit GitHub Action runs automatically on PRs that modify .ball.json files, blocking merges that introduce unauthorized capabilities.
# Dart (the pub-workspace + Melos root is the repo root)
dart pub get
cd dart/engine && dart test
cd dart/encoder && dart test
# C++
cd cpp && mkdir -p build && cd build && cmake .. && cmake --build .
# TypeScript
cd ts/engine && npm install && npm test
# Proto (regenerate all bindings)
buf lint && buf generate- Schema changes: edit
proto/ball/v1/ball.protothenbuf lint/buf generate - New std functions: edit
dart/shared/lib/std.dartthendart run bin/gen_std.dart - Implement in compiler, engine, or both
- Add tests alongside changes
- If C++ is in scope, mirror changes in
cpp/
See the issues board for planned work and issue #135 (std coverage inventory; dart/shared/std.json is the canonical source of truth) for standard library coverage.
ball/
├── proto/ball/v1/ball.proto # Language schema (single source of truth)
├── dart/ # Dart implementation (reference)
│ ├── shared/ # Protobuf types, std module, capability analyzer
│ ├── compiler/ # Ball -> Dart
│ ├── encoder/ # Dart -> Ball
│ ├── engine/ # Interpreter (true async)
│ ├── resolver/ # Package manager (pub/npm adapters)
│ └── cli/ # ball CLI
├── cpp/ # C++ implementation (compiler + encoder + self-hosted engine)
├── ts/ # TypeScript implementation (compiler + encoder + self-hosted engine, browser + Node)
├── rust/ # Rust implementation (compiler + encoder + self-hosted engine + CLI)
├── csharp/ # C# implementation (compiler + encoder + self-hosted engine + CLI)
├── go/, python/, java/ # Proto bindings only
├── examples/ # Example Ball programs
├── tests/conformance/ # Cross-implementation conformance tests
├── website/ # ball-lang.dev + playground
└── docs/ # Specs and guides
- ball-lang.dev — Website
- ball-lang.dev/playground — Web playground
- @ball-lang/engine on npm — TypeScript engine
- buf.build/ball-lang/ball — Proto schema on Buf registry
- docs/IMPLEMENTING_A_COMPILER.md — Guide for new target languages
