Tantu — a statically-typed, Python-readable language with fibers + channels, compiled to bytecode and run on a hand-built stack VM.
Project description
Tantu
A statically-typed, Python-readable language with fibers and channels — compiled to bytecode and run on a hand-built stack VM, implemented in Python 3.12 with zero third-party runtime dependencies.
Try it without installing anything: tantu-play.netlify.app has the full guide and an in-browser playground running this interpreter.
- Design contract:
DESIGN.md(v1.2) is the single source of truth. - Decisions log:
DECISIONS.mdrecords anything the design leaves silent. - Status: M1–M9 complete — a full pipeline (lexer → parser → checker → compiler → linker → stack VM),
static types with no null (
Option/Result), pattern matching, closures, fibers + channels withselect, multi-file modules, and user-defined generics. The full test suite and all examples run in CI.
See CHANGELOG.md for release notes and CONTRIBUTING.md
if you want to hack on it.
Why Tantu
Every language I like makes me give something up. Python reads like pseudocode but catches nothing before it runs and has no concurrency primitives of its own. Go gives you cheap goroutines, then shares memory by default and only asks you not to race — the detector is opt-in and finds races at runtime, if you're lucky. Rust proves your concurrency is safe but charges you the borrow checker for the proof.
Tantu is a small answer to a narrow question: can a language read like Python, catch your mistakes before it runs, and make data races impossible without a borrow checker?
It gets there with three constraints:
- No null. Absence and failure are ordinary values (
Option,Result), and the checker won't let you ignore them.matchand the?operator are the only ways through. - Immutable by default.
letbindings and every collection are values;pushreturns a new list.varbuys you a reassignable binding and nothing more. - No shared mutable state across fibers. Capturing a
varin a closure or a spawned fiber is a compile error. Fibers talk over typed channels, never shared memory.
That third rule is why Tantu exists. A Tantu program can't contain a data race — not
because you were careful, but because the language took away the tools to write one.
You get Go's concurrency ergonomics (spawn, chan, select) with a guarantee Go
doesn't give you, for the price of one checker rule instead of an ownership system.
When to use it, and when not to
Use Tantu if you want a tiny statically-typed language with real concurrency that you can read end to end — for scripts, for exercises, or to see how these pieces fit together. The whole thing is pure Python with no runtime dependencies, and each stage (lexer, parser, checker, compiler, stack VM) is a small module you can read on its own. It's as much something to study as something to run.
Don't use it if you need speed or a library ecosystem. It runs on a bytecode VM written in Python, with a cooperative, deterministic scheduler; it's built for correctness and clarity, not throughput. v1 is deliberately small: erased generics, one concurrency model, one way to handle errors. Need performance? Use Go or Rust. Need libraries? Use Python. Tantu is for when you'd rather have a language you can hold in your head, with the safety properties above spelled out.
A taste
enum Shape:
Circle(Float)
Rect(Float, Float)
fn area(s: Shape) -> Float:
match s:
Circle(r) => 3.14159 * r * r
Rect(w, h) => w * h
fn main() -> Unit:
print(area(Circle(2.0)))
print(area(Rect(3.0, 4.0)))
No null: absence and failure are values (Option[T], Result[T, E]) unwrapped by match
or the ? operator. Bindings are immutable by default (let; var opts in). Concurrency is
CSP — lightweight spawned fibers passing messages over typed chan[T] channels, never shared
mutable state. See examples/ for runnable programs (hello, arithmetic, closures,
collections, options, fib, fizzbuzz, word-frequency, channels, shapes, select, generics).
select waits on several channel operations at once and takes the first ready arm (a recv
arm binds Option[T]; a closed channel makes its recv arm fire with None). With no ready
arm it blocks until one is; an else arm makes it non-blocking. The canonical use is a
timeout — race real work against a fiber that sleeps then signals:
select:
v = recv(result) =>
match v:
Some(n) => print(n)
None => print("closed")
deadline = recv(timeout) =>
print("timed out")
Ready arms are polled in source order, so scheduling stays deterministic and testable.
Install
Tantu needs Python 3.12 or newer and installs a tantu command.
pip install tantu # from PyPI (or: pipx install tantu)
pip install . # from a clone
Usage
tantu run file.tn # run a program (use `run -` to read stdin)
tantu check file.tn # static check only
tantu dis file.tn # disassemble to bytecode
tantu # REPL
tantu --version
If you're working from a checkout without installing, python -m tantu ... takes the same
arguments.
Modules
A program is a set of sibling *.tn files in one directory. import name makes the
members of name.tn available as name.member:
# mathlib.tn
fn square(n: Int) -> Int:
n * n
let answer = 42
# main.tn
import mathlib
fn main() -> Unit:
print(mathlib.square(5)) # 25
print(mathlib.answer) # 42
Running tantu run main.tn discovers the import graph from the root, checks each module in
topological order, links every module's globals into one flat table, runs each module's
top-level initializers (dependencies first), then calls the root's main(). Import cycles and
missing modules are compile-time errors. (import needs a source file on disk — it isn't
available for stdin or the REPL.)
Generics
Functions, enums, and structs can take type parameters in [ ]. Generics are fully
parametric (no bounds) and erased — a generic definition compiles to a single function or
descriptor, and type arguments are always inferred from the call, never written explicitly:
fn map[T, U](xs: List[T], f: (T) -> U) -> List[U]:
var out: List[U] = []
for x in xs:
out = push(out, f(x))
out
enum Tree[T]:
Leaf
Node(Tree[T], T, Tree[T])
struct Pair[A, B]:
first: A
second: B
Inside a generic body a type parameter is opaque: it can be passed, stored, matched, and sent over a channel, but not added, compared, or called — there is nothing the checker knows it supports. A generic function is call-only (not a first-class value); pass a lambda with concrete types if you need one as a value.
Development
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
pytest
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tantu-0.1.0.tar.gz.
File metadata
- Download URL: tantu-0.1.0.tar.gz
- Upload date:
- Size: 278.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d8a26d720dbb51a7acf1ef50631a35aa669f6dfffeda5f4883d73fdb00c5f545
|
|
| MD5 |
682812ce3d8bc3b24d115534bf8accbc
|
|
| BLAKE2b-256 |
a709b9bcb14c4cc425b36a82de42c2c41633d295434875a6d6644379c1d9c673
|
Provenance
The following attestation bundles were made for tantu-0.1.0.tar.gz:
Publisher:
publish.yml on DRACULA1729/tantu-lang
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tantu-0.1.0.tar.gz -
Subject digest:
d8a26d720dbb51a7acf1ef50631a35aa669f6dfffeda5f4883d73fdb00c5f545 - Sigstore transparency entry: 2156120298
- Sigstore integration time:
-
Permalink:
DRACULA1729/tantu-lang@bc1c6f50cda4a0f3e210086dbdea0020d4e7cbf7 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/DRACULA1729
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bc1c6f50cda4a0f3e210086dbdea0020d4e7cbf7 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file tantu-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tantu-0.1.0-py3-none-any.whl
- Upload date:
- Size: 74.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ebb570afdeccd11adafb2bc1448d8a0ff9d0867e99e3c0dcc5c159f3a7c7de63
|
|
| MD5 |
48ce24013229b2ac25f858bc0cbd14bc
|
|
| BLAKE2b-256 |
06aab95e108d5dff53623a7d70a7e521258b9c313739aff6bdbfd321878d2484
|
Provenance
The following attestation bundles were made for tantu-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on DRACULA1729/tantu-lang
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tantu-0.1.0-py3-none-any.whl -
Subject digest:
ebb570afdeccd11adafb2bc1448d8a0ff9d0867e99e3c0dcc5c159f3a7c7de63 - Sigstore transparency entry: 2156120389
- Sigstore integration time:
-
Permalink:
DRACULA1729/tantu-lang@bc1c6f50cda4a0f3e210086dbdea0020d4e7cbf7 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/DRACULA1729
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bc1c6f50cda4a0f3e210086dbdea0020d4e7cbf7 -
Trigger Event:
workflow_dispatch
-
Statement type: