A compiled, statically-typed programming language that treats physical units and dimensions as first-class citizens of the type system. Dimension and unit errors are caught before execution — and unit conversions happen automatically.
Warning
The language and its documentation are unfinished. While usable, Numerobis is not recommended for production code yet. Only Linux is supported at the moment.
In most programming languages, physical quantities are plain numbers. Units exist only as informal convention, and there is no way to automatically detect unit inconsistencies — the kind of mistake that caused the loss of the Mars Climate Orbiter in 1999.
Numerobis integrates units and dimensions directly into its type system:
- Dimension errors are caught at compile time — adding a length to a mass is a type error.
- Unit conversions are automatic — you never write conversion factors by hand.
- Non-multiplicative units are supported natively — including affine units like °C and °F, and logarithmic units like dBm and pH.
Numerobis compiles to C99, giving it a significant performance advantage over interpreted languages.
Install the Python package locally (editable):
make installBuild the runtime library and compiler:
make buildAfter installation, the nbis CLI entry point is available.
sudo apt install -y \
pkg-config \
libglib2.0-dev \
libgc-dev \
build-essential \
ccache \
moldpkg-config— discoversglib-2.0andbdw-gclibglib2.0-dev— dynamic lists and stringslibgc-dev— Boehm–Demers–Weiser garbage collectorbuild-essential— C toolchain (gcc/clang),ar,makeccache+mold— required for running the test suite
nbis build SOURCE [-o OUTPUT] [--run] [--quiet] [--debug/--no-debug] [-O {0,1,2,3,s}] [--cc COMPILER]
nbis view SOURCE [-o OUTPUT] [--theme THEME] [--line-numbers/--no-line-numbers]
| Flag | Default | Description |
|---|---|---|
-o, --output |
source filename | Output binary path |
--run / --no-run |
off | Execute the binary immediately after building |
--quiet |
off | Suppress the build summary line |
--debug / --no-debug |
on | Emit debug info (-g) |
-O {0,1,2,3,s} |
0 |
Optimization level passed to the C compiler |
--cc |
gcc |
C compiler to use (e.g. --cc clang) |
| Flag | Default | Description |
|---|---|---|
-o, --output |
(print to terminal) | Write generated C code to a file instead of printing |
--theme |
monokai |
Syntax highlighting theme |
--line-numbers / --no-line-numbers |
on | Show line numbers |
| Target | Description |
|---|---|
make install |
Install the Python package (editable) |
make build |
Build the compiler and runtime library |
make test |
Run the full test suite |
make benchmark |
Run performance benchmarks against Python and C |
make runtimelib |
Rebuild the static runtime library (libruntime.a) only |
make graph |
Generate class/package diagrams into assets/ |
make help |
List all available targets |
You can pass flags to the test runner directly:
make test -- --verbose
make test CC=clangFor concise examples, browse the
tests/directory (canonical examples) andexamples/.
# single-line comment
#[ multi-line
comment ]#Type and dimension annotations are optional — the compiler infers them bidirectionally.
x: Type = expr
x: Mass = 1000 # dimension annotation
i: Int = 10
f: Float = 3.14
m: Int[Length] = 10 m
s: Str = "hello"
b: Bool = true
l0: List = [1, 2, 3]
l1: List[Int] = [1, 2, 3]
l2: List[Mass] = [1 kg, 2 kg, 3 kg]
fn: ![[s: Str, n: Int], Str] = !(s: Str, n: Int): Str = s * nFunctions support default arguments and optional type annotations. The body can be a single expression or a block.
greet!(name: Str, times: Int = 1): Str = name * times
fibonacci!(n: Int): Int = {
if n <= 1 then
return n
return fibonacci(n - 1) + fibonacci(n - 2)
}
echo(fibonacci(20))# if / else
if a < b then echo("small")
else {
if a > 15 then echo("large") else echo("medium")
}
# for over a range
for i in 0..10 do echo(i)
# for over a list
for item in [1, 2, 3] do echo(item)
# while
i = 0
while i < 10 do {
echo(i)
i = i + 1
}Lists are ordered and homogeneous. Indexing is zero-based and follows Python conventions (negative indices, slices).
lst = [10, 20, 30]
lst[0] # 10
lst[-1] # 30
lst[1:] # [20, 30]dimension Length # base dimension
dimension Volume = Length^3 # derived dimension
unit m: Length # base unit for Length
unit km = 1000 m # derived unit (dimension inferred)
unit L = (0.1 m)^3 # volume in litres
unit taco; # shorthand: creates dimension "Taco" and base unit "taco"Units are suffixes on number literals, not standalone values. This avoids naming conflicts (e.g. m can still be used as a variable name).
m = 410 kg # 'm' is a variable, 'kg' is a unit suffix — no conflictA unit suffix extends up to one space from the number, or arbitrarily far inside parentheses:
# "5 metres divided by variable s"
5 m / s
5m / s
# "5 metres per second"
5m/s
5 m/s
5(m / s)Allowed operators inside suffixes: *, /, ^
unit °C: Temperature = _ K + 273.15
unit °F = (5/9) * (_ K + 459.67)
echo(0°C -> K) # 273.15 K
echo(0°C -> °F) # 32 °FThe underscore _ is the placeholder for the input value. Every unit definition is a function that maps a value to its base unit.
unit mW = 0.001 W
unit dBm = 10^(_ mW / 10mW)
echo(2 * 60dBm) # 63.0103 dBm
echo(60dBm |+| 60dBm) # 120 dBm (raw number addition, ignores unit)For affine and logarithmic units, plain + and - are semantically restricted. The delta operators |+| and |-| operate on raw values and reattach the unit afterwards:
0°C |-| 32°F # 0 °C
10dB |+| 5dB # 15 dBUse -> to convert between compatible units or between types:
500 m -> km # 0.5 km
1 gallon -> L # 3.78541 L
"1234" -> Int # 1234 (type conversion)
("1234" -> Int) + 1 # 1235Conversions between incompatible dimensions are caught at compile time.
Import with the @ prefix to distinguish units/dimensions from regular names:
from mymodule import name, @myunit, @MyDimension
from imperial import @gallon
# grouped import shorthand
from si import @(kg, m, km, s, K, J, kJ, kW, h)x: Length = 42 m
y: Float[Mass] = 3.14 kg
l: List[Length] = [x, 6 m, 7 km]'m' declared as [Mass] but has dimension [Length]
m: Mass = 2m
Incompatible dimensions in addition: [Mass·Length²·Time⁻³] vs [Mass·Length²·Time⁻²]
42 watt + 3.14 joule
External C functions can be declared and called from Numerobis using the extern keyword:
extern echo!(value, end: Str = "\n"): None;from si import @(kg, L, g, K, kJ, kW, h, J)
from imperial import @gallon
unit °F = (5/9) * (_ K + 459.67)
unit cal = 4.184 J
unit kWh = kW * h
density_water = 1 (kg / L)
mass_water = 1 gallon * density_water
c_water = 1 (cal / (g * K))
ΔT = (212°F -> K) - (70°F -> K)
heat = mass_water * c_water * ΔT
echo(heat -> kJ) # 1249.45 kJ
echo(heat -> kWh) # 0.347071 kWhunit mW = 0.001 W
unit dBm = 10^(_ mW / 10mW)
echo(2 * 60dBm) # 63.0103 dBm
echo(60dBm |+| 60dBm) # 120 dBmunit °C: Temperature = _ K + 273.15
unit °F = (5/9) * (_ K + 459.67)
echo(5°C) # 5 °C
echo(0°C -> K) # 273.15 K
echo(0°C -> °F) # 32 °F
echo(0°C |-| 32°F) # 0 °Cmake test
# or
python3 run.py --verboseTests are .nbis files under tests/. The runner executes them and checks outputs and expected error codes. The test suite currently contains 1,128 unit tests.
src/numerobis/
analysis/ — dimension checker, unit simplifier, inversion
cli/ — nbis command-line entry point
compiler/ — codegen, linker, scoping, C emission
lexer/ — tokeniser
nodes/ — AST node definitions
parser/ — recursive-descent parser + unit expression parser
typechecker/ — type inference, dimension algebra, operator rules
stdlib/ — built-in modules
runtime/ — C runtime sources, built into libruntime.a
tests/ — language tests, benchmarking
examples/ — small example programs
scripts/ — build helpers
assets/ — generated diagrams
