A polyglot programming language IR where every program is a Protocol Buffer message.
Website · Playground · Documentation · Examples
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. |
| Provably complete security auditing | ball audit statically reports every side effect. No eval, no FFI, no hidden capabilities — every I/O operation flows through a named base function. |
| 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 (3000 LOC) 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 — 37/37 Dart fixtures round-trip to TS and execute byte-identical on Node, and the full engine.dart parses cleanly. The C++ compiler runs 41/41 fixture programs end-to-end. |
| 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);cd dart && 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):
{
"name": "hello_world",
"entryModule": "main",
"entryFunction": "main",
"modules": [
{
"name": "std",
"types": [{ "name": "PrintInput", "field": [{ "name": "message", "number": 1, "type": "TYPE_STRING" }] }],
"functions": [{ "name": "print", "inputType": "PrintInput", "isBase": true }]
},
{
"name": "main",
"imports": ["std"],
"functions": [{
"name": "main",
"body": {
"call": {
"module": "std", "function": "print",
"input": { "messageCreation": { "typeName": "PrintInput", "fields": [
{ "name": "message", "value": { "literal": { "stringValue": "Hello, World!" } } }
]}}
}
}
}]
}
]
}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— ~73 functions: arithmetic, comparison, logic, bitwise, strings, math, control flow, type opsstd_collections— ~43 functions: list/map operationsstd_io— ~10 functions: console, process, time, randomstd_memory— ~30 functions: linear memory for C/C++ interopdart_std— ~18 functions: Dart-specific (cascade, null-aware access, spread)
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) |
| C++ | Yes | Prototype | Prototype | Prototype |
| TypeScript | Yes | -- | -- | Full (browser + Node) |
| Go | Yes | -- | -- | -- |
| Python | Yes | -- | -- | -- |
| Java | Yes | -- | -- | -- |
| C# | Yes | -- | -- | -- |
flowchart LR
BP["Ball Program\n(protobuf)"] -- compiler --> SRC["Source Code"]
SRC -- encoder --> BP
BP -- engine --> RT["Runtime\nExecution"]
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
cd dart && 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 docs/ROADMAP.md for planned work and docs/STD_COMPLETENESS.md 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 (prototype)
├── ts/engine/ # TypeScript engine (browser + Node)
├── go/, python/, java/, csharp/ # Proto bindings
├── 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
