Language reference¶
Reference. TTR-M in full — the definition kinds, properties and constructs the parser accepts.
Skeleton
This is the language-reference skeleton for TTR-M grammar v0.9 (the grammar version
tracks the product major.minor). The tables below enumerate the surface; the per-construct
property detail marked (from grammar) is generated from TTR.g4 so it cannot drift from what
the parser accepts, and the prose marked (hand-written) is authored. Fuller worked examples
are migrating in from the user manual.
File shape¶
A TTR-M document is a package member with a model directive:
package <name> // the package this file belongs to
import <name>[.*] // optional cross-package imports
model <code> [schema <id>] // the layer this file defines
def <kind> <name> { … } // one or more definitions
<code> is the layer: db, er, binding (er↔db and other mappings), md, cn (cnc is deprecated). Identifiers
allow Latin-1/Extended letters, so real Czech names are valid verbatim.
Definition kinds (from grammar)¶
| Layer | Kinds |
|---|---|
db |
table, column (inline), view, index, constraint, fk, procedure |
er |
entity, attribute (inline), relation |
binding |
er2db_entity, er2db_attribute, er2db_relation |
cn |
kind, variant (or subkind), role, event, quality, phase, relation_type; grounded by cn2md_* / cn2er_* in binding |
cnc (deprecated) |
role, er2cnc_role |
md |
domain, dimension, hierarchy, measure, cubelet, map, md2db_*, md2er_* |
| structure | package, import, area (packages: / entities:), graph, drill_map, query, world |
Packages and areas (hand-written)¶
- A package is a directory of
.ttrmfiles sharing apackage <name>header, with amodeler.tomlmanifest at its root. Packages are the unit of import and versioning. - An
areais a subject area that spans packages —def area <name> { packages: […], entities: […] }. Use it to name a business domain that cuts across package boundaries.
Bindings (hand-written)¶
Bindings keep the er meaning anchored to the db truth without repeating it. er2db_entity,
er2db_attribute and er2db_relation map logical constructs to their physical targets; an inline
binding: shorthand on an entity/attribute/relation covers the common case. This is what lets the
db mirror be regenerated while the er model stays hand-owned.
Naming, search and the lexicon (from grammar)¶
Entities and attributes carry the vocabulary the understanding layer resolves against:
aliases: [ … ]— alternate names for the concept.search { searchable, fuzzy, keywords { <locale>: [ … ] }, patterns [ … ] }— how the fuzzy and search doors find this field, including localized keywords.lexicon { … }— inline sugar for terms, patterns and examples the resolution layer uses.valueLabels { "<code>": { <locale>: "…" } }— human labels for coded values.semantics { … }— the closed semantic vocabulary, in two orthogonal facets. On an attribute or column,role:declares the grounding facet (which date to filter on, which column is the amount, where the coordinates are). On an entity or table,kind:is the grounding facet andname:/code:/measures:are the mention facet — which attribute carries this entity when a human refers to it by name, by code, or as a value to aggregate.measures:is ordered and its first item is the entity's default measure.
nameAttribute: / codeAttribute: are deprecated
The entity properties nameAttribute: and codeAttribute: still parse, but
semantics { name: … , code: … } is the source of truth from vocabulary v3 on. Declaring only
the legacy property, or declaring both in agreement, raises a deprecation warning;
declaring both so they disagree is an error — a disagreement is always a bug, not a
preference. For quoting a literal, the column a quoted value filters is
read from the semantics block; where the block names no name: (or no code:), the legacy
property is used as a fallback, so an existing model keeps working. Only the semantics block
can say more than that — a code_pattern: needs code: beside it. Prefer the semantics block
in new models.
Quoting a literal (hand-written)¶
TTR-M has one escape hatch on the asking side, and it belongs here because what it needs from a model is declared in the section above. Putting a span of a question in quotes means take this exactly as typed: it is a value, not language. Nothing rewrites it, nothing corrects its spelling, nothing proposes alternatives for it, and no language model is asked what it might have meant. The one thing that can happen to it besides becoming a filter is a lookup among the values the model already knows for that column — and only when the question gives no predicate word and the model has such a vocabulary (below).
Both lines filter the store name with starts with Abl: starting with and začínající na are
predicate words from the standard library (below), and "Abl" is the value they apply to.
The delimiter family¶
Any member of this closed set opens a literal, and any member closes it — so a question pasted out of Word, typed on a phone keyboard, or written with Czech typography all work without the user knowing which quote character they produced.
| Character | Name | |
|---|---|---|
| ✅ | " |
U+0022 quotation mark |
| ✅ | „ “ ” ‟ |
U+201E · U+201C · U+201D · U+201F — the curly family, German/Czech low-high included |
| ✅ | « » |
U+00AB · U+00BB guillemets |
| ❌ | ' ‚ ‘ ’ |
apostrophes and single quotes — they appear inside real names (O'Brien) |
| ❌ | ‹ › |
single guillemets |
| ❌ | ` |
backtick |
A delimiter only counts as one at a word boundary: an opener must follow the start of the text,
whitespace, or one of ([{-–—:;,, and a closer must precede the end, whitespace, or one of
)]}.,;:!?-–—. That is what keeps the inch mark in a 5" screen from opening a literal. Pairing
is left to right and greedy to the nearest closer; an unpaired delimiter is just text, and so is an
empty one. Whatever sits between the delimiters is the value, with the outer whitespace trimmed and
everything else — case, diacritics, inner spaces, punctuation — kept exactly as typed. There is no
escaping syntax, because a literal is never read as anything but itself.
What the literal filters, and how¶
The literal has to land on a column, and the model is what says which one. The head it attaches to is the nearest model object that governs it, and that head's mention facet decides:
| The head declares | A literal there filters on |
|---|---|
semantics { name: … } |
the name attribute |
semantics { code: … }, and the literal is code-shaped |
the code attribute |
| neither | nothing — the literal stays unattached and the question is refused, visibly |
That last row is the one to design for. A head with no mention facet cannot carry a quoted literal
at all, and refusing is deliberate: guessing a column for a value the user was explicit about is
the one failure mode that produces a confident wrong answer. If quoting a name does not work on
some entity, the fix is semantics { name: … } on that entity, not a rephrasing. (An entity that
still uses the legacy nameAttribute: / codeAttribute: gets them as a fallback.)
Code-shaped means the literal matches the head's code_pattern: — a Java regular expression
every code of that entity matches — or, failing that, the generic code shape: capital letters,
digits and - / ., starting with a letter or digit, and containing at least one digit. The
digit is not required when the head declares a code: and no name: (there is nothing else the
literal could be). A period table's code with a code_format: mask (yyyyMM) is recognised by
that mask. Declare code_pattern: whenever your codes are not caught by the generic shape — codes
made only of letters, for instance:
def entity promotion {
semantics { name: promo_name, code: promo_id, code_pattern: "^[A-P]{16}$" }
attributes: [
def attribute promo_name { type: text },
def attribute promo_id { type: text } // AAAAAAAABAAAAAAA …
]
}
code_pattern: needs code: in the same block, and a pattern that does not compile is an error
(TTR-SEM-219). Prefer character classes to backslashes ([0-9], not \d): inside a TTR string
a backslash escapes the next character, so \d has to be written \\d.
How it filters comes from the predicate words beside the literal — ordinary lexicon vocabulary shipped in the standard library, matched in Czech and English:
| Predicate | Some of the words | Filter |
|---|---|---|
starts_with |
začínající na · začíná na · s prefixem · starts with · starting with · begins with | col LIKE ? \|\| '%' |
not_starts_with |
nezačínající na · nezačíná na · not starting with · not beginning with | NOT (col LIKE ? \|\| '%') |
ends_with |
končící na · končí na · s příponou · ends with · ending with · ending in | col LIKE '%' \|\| ? |
not_ends_with |
nekončící na · nekončí na · not ending with · not ending in | NOT (col LIKE '%' \|\| ?) |
contains |
obsahující · obsahuje · s textem · v názvu · contains · containing · including | col LIKE '%' \|\| ? \|\| '%' |
not_contains |
neobsahující · neobsahuje · not containing · without · excluding | NOT (col LIKE '%' \|\| ? \|\| '%') |
equals |
přesně · s názvem přesně · rovná se · exactly · named exactly · equal to | col = ? |
not_equals |
nerovná se · se nerovná · not equal to · not equals | col <> ? |
Every predicate word matches whole and exactly as listed — starting with fires, a bare with
never does — so the forms are spelled out one by one rather than left to a lemmatiser: Czech
participles in every case (firmy obsahující, firem obsahujících, s firmami obsahujícími),
both word orders where Czech uses both (rovná se · se rovná). A form may be at most three
words. Negation is part of the vocabulary: not starting with and nezačínající na are the
not_starts_with predicate, never starts_with, and a predicate word right after a negator
(does not start with, never, ne) is negated too. The bare named / s názvem are
deliberately not predicate words: quoting a name after them keeps the name default below; say
named exactly / s názvem přesně to mean equals.
With no predicate word, what happens depends on where the literal landed:
- On a name attribute whose values the model indexes (a member vocabulary), the literal is first looked up among those values. One match: the question is about that value. Several: you are asked which one you meant. None — or no vocabulary to look in, or the lookup service unavailable — falls through to the default below. The literal itself is never altered; a lookup can only pick one of the column's existing values for it.
- Otherwise, the default follows the facet it was attributed to: a name (or an attribute
that is itself the head, as in customers with the name "Valmy") is
contains, a code isequals. Someone asking about a name usually means "has this in it"; someone quoting a code means that code. The resolver decides this from the attribution and sends the predicate to the query stage explicitly — the filter never depends on a later stage re-guessing it from the literal's shape.
An estate can add its own wording — a pred: entry in the estate's lexicon/ area extends the
shipped list rather than replacing it — but the eight predicates themselves are a closed set.
The literal is a bound parameter, always
The quoted text never becomes SQL text. It is bound as a parameter, and the query stage
escapes the wildcard characters %, _ and [ inside it, with an explicit ESCAPE clause,
so a value containing one filters for that character rather than acting as a wildcard — [
included, which SQL Server would otherwise read as the start of a character set. A user who
asks for names containing "50%" gets rows with 50% in them, and "[DE] Pelex" finds
exactly that text.
Queries (from grammar)¶
- Named queries —
def query <name> { … }with a SQL/DSL template and a parameter list; surfaced throughlist_queriesand runnable through the query door. - Pattern queries — parameterized query shapes an agent can bind and run.
- Logical queries — a
def querydeclared in amodel erfile is written over entities (er.query.<name>, bareJOINs conditioned from the relations, no parameters), and an entity can be stacked on it (binding: { target: { query: er.query.<name> } }) — agents then ask over that entity.
Governance — policy, not a security block¶
There is no security {} construct
Access policy is not part of the model text: row-level filters, column masks and the roles a
question requires live in the validator's store, versioned in git beside the model
(Policy in git). The validator enforces them downstream and reports
what it applied in pipelineWarnings on every governed answer.
Worlds and composition (from grammar)¶
def world <name> { … } (with engine/executor/storage nouns) describes a composition of
packages into a deployable whole — the model as the deployment artifact, named.
Types (from grammar)¶
The canonical type tokens (int, bigint, text, decimal, float, bool, date, time,
datetime, …) are what db columns and er attributes carry; ttr import-schema normalizes SQL
types onto them. The full type table is generated from the grammar.
For a guided path into the language rather than a lookup, start with model your first three tables; for how the layers relate, see the layers.