tdl

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 30, 2026 License: GPL-3.0

README

tdl

CI Built with Nix Go Reference Last commit

TDL is a language for describing domain models. It says what things are, what identifies them, how they relate, and what values they may hold, and compiles them into equivalent definitions in other structured formats. It describes no behavior and has no expressions, control flow, or runtime.

This repository owns the canonical language specification and its reference implementation, written in Go.

Status

Early and incomplete, and moving.

The front end is done. The lexer and parser read the whole grammar, and tdl check, tdl fmt, tdl ast, and tdl tokens work across it. union is reserved and unimplemented; nothing else in grammar.ebnf is missing.

The middle is most of the way there. tdl ir prints a resolved model:

  • Names resolve, with scopes, shadowing, and the spec's recursion rules.
  • Sugar lowers to prelude types, and the prelude is TDL source rather than something built in, so [T] means whatever the loaded prelude says List is.
  • Imports resolve across packages without inlining the dependency.
  • Mixins expand, and the class satisfaction index answers both for declarations and for types made to satisfy a class by a conditional instance.
  • Constraints accumulate down newtype chains, and defaults resolve against their field's type.
  • Target directives resolve against the model and attach to the nodes they apply to.

The back end does not exist. There are no code generators yet, and nothing speaks the plugin protocol. Units, and merging a dependency's target blocks, are the two pieces of resolution still outstanding.

The design is settled and written down:

Document What it covers
spec.md The language. Canonical.
grammar.ebnf The formal grammar.
design/parser-plan.md Rewriting the lexer and parser to match. Done.
design/ir.md The resolved model backends consume.
design/ir-plan.md Implementing it. Phases 1 to 8 of 10 done.
design/plugins.md The backend plugin protocol.
design/plugins-plan.md Implementing it. Not started.
design/workflow.md What a model author does with all of it.
backlog.md Wanted, unscheduled: tree-sitter, an LSP, editor support, an MCP server.

Example

package shop

entity Order {
  key id: OrderId
  customer: Customer
  shipping: Address?
  items: [LineItem] owned where { length(1..) }
  status: Status = Draft
  total: Money
}

entity Customer {
  key email: Email
  name: string?
}

value Address {
  line1: string
  line2: string?
  city: string
  postcode: string
  country: string
}

value Money {
  amount: decimal
  currency: Currency
}

type OrderId: uuid

type Email: string where {
  matches(/^[^@]+@[^@]+$/)
  length(3..254)
}

enum Currency { USD EUR GBP }

enum Status { Draft Placed Shipped Cancelled }

entity and value is the modelling decision: an Order has identity that survives its contents changing, an Address does not. Everything a code generator needs lives in a separate target block, never in the model.

Usage

tdl check ./types.tdl    # parse and report syntax errors
tdl fmt ./types.tdl      # print canonical formatting; -w to write in place
tdl ast ./types.tdl      # print the parse tree
tdl gen ./types.tdl      # run every target block; --target narrows, -o overrides
                         # --verify checks, --clean empties first, --watch reruns
tdl ir ./types.tdl       # print the resolved model; --format json for the plugin view
tdl tokens ./types.tdl   # print the token stream
tdl version              # tool and spec versions
Playground

tdl play watches a file and re-renders it on every save.

tdl play                              # scratch.tdl, created from a template if missing
tdl play ./types.tdl --views all      # source, fmt, ast, tokens, stats
tdl play ./types.tdl --views fmt      # one pane
tdl play ./types.tdl --once           # render and exit

Views are source, fmt, ast, tokens, stats, or all; the default is fmt,ast. Parse errors render below the panes with a caret at the reported column.

examples/ holds files to start from: the same domain modelled flat and nested, plus collections and target blocks.

Releases

Versions come from release-please: a release pull request accumulates conventional commits and, when merged, tags a release and writes CHANGELOG.md.

Nothing about a version is edited by hand. toolVersion and the Nix package version carry annotations that the release PR rewrites.

Development

command make build   # nix build .#
command make test    # go test ./...
command make lint    # nix flake check + buf lint
command make fmt     # nix fmt

go build ./... and go test ./... work directly for anyone not using Nix.

Design philosophy

  • The language core is small. Almost everything that looks like a type system is library code written in TDL and shipped in a replaceable prelude.
  • Identity is first class, and the model is pure: a .tdl file describes the domain, and everything a backend needs lives in a target block.
  • Constraints are syntax, not semantics. The compiler parses and resolves them; backends decide what they mean.
  • A small, strict grammar with a hand-written lexer and parser. No parser generator, no YAML or JSON stand-in syntax.
  • One canonical Go implementation. The spec and the testdata/conformance and testdata/invalid corpora are the contract another implementation would satisfy, which is why they are plain text rather than Go tests.

Directories

Path Synopsis
Package ast defines the TDL abstract syntax tree: a parse tree that mirrors source text 1:1, with names left unresolved.
Package ast defines the TDL abstract syntax tree: a parse tree that mirrors source text 1:1, with names left unresolved.
backend
debug
Package debug is a backend that describes the model it was given.
Package debug is a backend that describes the model it was given.
cli module
cmd
tdl command
Command tdl parses and formats Type Description Language source files.
Command tdl parses and formats Type Description Language source files.
tdl-gen-debug command
Command tdl-gen-debug is the debug backend as a plugin.
Command tdl-gen-debug is the debug backend as a plugin.
e2e module
gen module
internal
cli
Package cli wires up the tdl command-line interface.
Package cli wires up the tdl command-line interface.
gen
Package gen is the compiler side of the plugin protocol: which backends exist, how a request is built, and what happens to the files that come back.
Package gen is the compiler side of the plugin protocol: which backends exist, how a request is built, and what happens to the files that come back.
sema
Package sema lowers a parse tree to the resolved semantic model in the ir package: it resolves names, lowers sugar to prelude types, and interns type references.
Package sema lowers a parse tree to the resolved semantic model in the ir package: it resolves names, lowers sugar to prelude types, and interns type references.
Package ir is the resolved semantic model backends consume: what the parse tree becomes once names are resolved and sugar is lowered.
Package ir is the resolved semantic model backends consume: what the parse tree becomes once names are resolved and sugar is lowered.
Package lex implements the TDL lexer, turning source text into a stream of tokens for the parser.
Package lex implements the TDL lexer, turning source text into a stream of tokens for the parser.
Package parser implements a hand-written recursive-descent parser that turns TDL source text into an ast.File.
Package parser implements a hand-written recursive-descent parser that turns TDL source text into an ast.File.
pkg module
Package plugin is the wire protocol a TDL backend speaks, and the SDK for writing one.
Package plugin is the wire protocol a TDL backend speaks, and the SDK for writing one.
Package prelude embeds the standard prelude, the TDL source declaring the roots every other file depends on.
Package prelude embeds the standard prelude, the TDL source declaring the roots every other file depends on.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL