Skip to content

tono

The Tono language compiler: reads .tono files and generates idiomatic SDKs in multiple languages. Polyglot monorepo - OCaml frontend, Rust backend.

Layout

  • frontend/ - OCaml: lexer, parser, typecheck, IR
  • lsp/ - OCaml: language server (reuses the frontend)
  • backend/ - Rust: codegen engine
  • cli/ - Rust: tono binary
  • ir-schema/- serialized IR contract

Build

  • OCaml (frontend/, lsp/): dune build
  • Rust (backend/, cli/): cargo build

Install

curl -fsSL https://tono-lang.github.io/tono/install.sh | sh

Or via Homebrew:

brew install tono-lang/tono/tono

Either way you get three binaries: tono (the CLI), tono-frontend (the parser/typechecker it shells out to), and tono-lsp (the language server editors launch).

Prebuilt archives for macOS (arm64/x86_64) and Linux (x86_64/arm64) are also attached directly to each release.

Update

  • Homebrew: brew upgrade tono
  • Install script: re-run the curl command above
  • Manual: download the latest release archive for your platform

Using it

tono init                # write tono.toml and a build manifest per target
tono check src/api.tono  # parse and typecheck
tono gen                 # generate every enabled target's SDK
tono gen --clean         # the same, clearing output the spec no longer produces

tono gen compiles the project's .tono sources (everything under project.root) and writes each enabled target under its configured out. The IR is an internal artifact, so no separate compile step is needed; pass an IR file, or pipe one in, only when you already have one.

Renaming or deleting a module leaves its old output behind, since generation only writes. --clean sweeps each output directory of generated files the run did not produce, and removes the directories that leaves empty. Only files carrying the Code generated by tono banner are eligible, so a build manifest, a hand-written source file, or anything else you keep alongside the SDK is never touched.

Progress and results go to stderr, so stdout stays clean for the one command that produces data on it (tono fmt, which prints the formatted source).

Organizing generated SDKs

tono init scaffolds a tono.toml with one [target.<lang>] block per language you enable, each with its own out directory. out accepts any path, so how you version and publish what lands there is up to you. Three patterns cover most cases:

  • Single repo, multi-target (the init default). Each target's out (typescript/, rust/, go/, as init writes them) lives in the same repo as the .tono spec. No extra infrastructure: each target publishes straight to its own registry (npm publish, cargo publish) from a tag-triggered CI job. Go supports this natively: a module in a subdirectory is published with a tag prefixed by that subdirectory (go/v1.2.3 for the default layout), per the Go modules reference, no separate repo required.
  • One repo per language (opt-in, more mature). Most SDK generators default here instead (a monorepo is treated as the advanced setup): each language gets its own idiomatic repo (org/api-sdk-python, org/api-sdk-go), while the .tono spec and tono.toml stay the single source of truth in one place. Getting there from the single-repo default is a read-only split per package (a git subtree/splitsh-style mirror), which is separate, opt-in tooling init does not set up on its own.
  • Fully separate repo, manual sync. Always available for free, since a target's out can point anywhere, including a sibling clone or a submodule. Maximum flexibility, zero opinion from tono, entirely up to you to keep in sync.

About

The Tono language compiler: generates idiomatic SDKs (OCaml frontend, Rust backend)

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages