Texomy syntax at a glance.
A Texomy specification describes what your text means — types and their fields — and how those types appear on the surface. The compiler turns it into a deterministic parser.
The mental model
A specification describes a semantic model and the rules that recognize it in text. A type describes what a value is; a field describes the value's role in another concept;$patterns describes how that concept appears on the surface. The result is typed JSON, not just regex captures.
Object-oriented data modeling
Texomy applies familiar object-oriented modeling concepts to semantic parsing. It uses the data-modeling part of OO: types, inheritance, composition, and named properties. It is a parsing language rather than an object runtime, so these concepts describe semantic values and their recognition rules rather than methods or mutable object behaviour.
- Type identity. Every matched node records its concrete type in the result.
- Inheritance.
Currency::USDis a kind ofCurrencyand may refine the parent’s recognition contract. - Polymorphic substitution. A field typed
Currencyaccepts compatible child types while retaining the concrete match, such asCurrency::USD. - Inherited properties. Fields declared by a parent are available to its children, so a common frame can define shared roles once.
- Inherited configuration. Type-level
$flagsflow to children, which may override them where needed. - Composition. A
PaymentcontainsMoney; it does not extend it. - Roles separate structure from value kinds. A
yearis anIntegerin the role of a year. - Patterns execute the model. They combine literal grammar, fields, and other types into a deterministic parser.
Types and inheritance
Every ordinary declaration defines a type. Nesting an ordinary declaration creates a child type. The two declarations below are equivalent: both define Currency::USD andCurrency::EUR. A reference to Currency accepts either child, while the result retains the concrete type that matched.
Currency:
USD: [ 'USD', '\$' ]
EUR: [ 'EUR', '€' ]
# Equivalent fully qualified declarations:
# Currency::USD: [ 'USD', '\$' ]
# Currency::EUR: [ 'EUR', '€' ]
Number:
Integer: '\d+'
Decimal: '${Number::Integer}\.${Number::Integer}'Use a parent when it represents a real shared category or when consumers need to accept its variants polymorphically. Use a direct type when there is only one meaningful concept.
A child type may add its own pattern while inheriting fields from its parent. HereDate::Iso and Date::Slash are ordinary child types ofDate.
Date:
$fields:
year: { $type: Number::Integer, $match: '\d{4}' }
month: { $type: Number::Integer, $match: '\d{2}' }
day: { $type: Number::Integer, $match: '\d{2}' }
Iso: '#{year}-#{month}-#{day}'
Slash: '#{month}/#{day}/#{year}'Nested types
$types defines types whose recognition belongs to one containing type family. They are visible in that type and its descendants, and are compiled into the containing pattern rather than run as independent top-level matchers. They still keep their type identity when captured through a field.
Comparison:
$types:
Operator: [ '>=', '<=', '=', '>', '<' ]
$fields:
left: Token
operator: Operator
right: Token
$patterns: '#{left} #{operator} #{right}'Use a normal declaration for a concept whose own text or format has a stable meaning outside this grammar. Use $types when the containing type supplies the boundary or the meaning that makes a recognizer safe.
Fields
$fields declares semantic roles inside a structured type. The type says what a value is; the field name says what role it plays here. Use a direct type name when the complete type is accepted, or $type + $match when this role accepts a narrower surface form.
Integer: '\d+'
Payment:
$fields:
money: Money
fiscalYear:
$type: Integer
$match: '\d{4}'
$patterns: '#{money} for #{fiscalYear}'Do not encode a role into a new type when the value category is unchanged. A year or quarter number is still an Integer; year orquarter is its field role. A name, identifier, amount, or status can still deserve its own type when that category has stable meaning beyond its immediate context.
Use [] on the type side for collection fields.
Line:
$fields:
words: Word[]
$patterns: '#{words}( #{words})*'Patterns
$patterns is where a type's surface form meets structure. It accepts one pattern or a list of alternatives. A field-level $match narrows the form accepted for that one role.
#{field}references a field declared in$fields.#{field:Type}creates an inline typed field without declaring it in$fields.${Type}recognizes a type without assigning it a field role.${!Type}asserts that the next text is not recognized as that type.${-Type}recognizes a type without carrying its node into the enclosing result.#{=field}is a backreference to an earlier field match.
TransactionId: 'TX-${Number::Integer}' # compose a type without a field
Payment: '#{money:Money} due on #{date:Date}' # inline typed fields
HtmlTag:
$types:
Content: '.*?'
$fields:
name: Identifier
content: Content
$patterns: '<#{name}>#{content}</#{=name}>'A type may declare multiple patterns as alternatives. Any of them will match.
Country:
$fields:
code: CountryCode
name: CountryName
$patterns:
- '#{code}'
- '#{name}'Recognition order
Declaration and pattern order controls recognition order. Put a composite or more specific recognizer before a broad type that could consume part of the same text. Once Texomy recognizes a span, later patterns compose that semantic node rather than matching its raw text again.
Cross-references and paths
Use :: to reference a child type. A short child name resolves relative to its type hierarchy; use a fully qualified path when the short name is ambiguous.
Number:
Decimal: '${Integer}\.${Integer}'
Integer: '\d+'
Latency:
$fields:
ms: Number::Integer # specifically the Integer child
$patterns: '#{ms}ms'Imports
A file can import other specifications by relative path. Split reusable concepts into files when they are shared by several domain specifications.
$imports:
- types/money
- types/date
Payment:
$fields:
money: Money
date: Date
$patterns: '#{money} due on #{date}'Flags
$flags controls how patterns match at scope. They are inherited from the file scope down into each type; a type may opt out of an inherited flag by prefixing it with -.
$flags: ['FLEXIBLE_WHITESPACE', 'WORD_BOUNDARY']
Number:
$flags: '-WORD_BOUNDARY' # opt out for this type; digits are composed elsewhere
Decimal: '\d+\.\d+'
Integer: '\d+'
Call:
$flags: OPTIONAL_WHITESPACE
$fields: { name: Identifier, arguments: 'Identifier[]' }
$patterns: '#{name}\((#{arguments}(, #{arguments})*)?\)'Common flags:
WORD_BOUNDARY— anchor matches to word boundaries. Ideal for keyword-like enums.FLEXIBLE_WHITESPACE— treat any whitespace in a pattern as "one or more".OPTIONAL_WHITESPACE— treat whitespace in a pattern as optional. Useful for compact syntax such as calls, lists, and operators.CASE_INSENSITIVE— self-explanatory.MULTILINEandDOTALL— Java regex flags for line-oriented or multi-line documents.
Put a flag at the widest scope it correctly applies to. Do not repeat it on every child.
Composition rules of thumb
- Model the domain, not the sample. A closed vocabulary gets the whole typical set even if input contains only one value.
- Use literals for syntax, not hidden semantics. Words such as
due on,from, ortoare fine in patterns. Domain values, identifiers, states, and vocabularies should usually be typed. - Prefer named alternatives for meaningful vocabularies. If the exact variant matters, model children such as
Status::OpenandStatus::Closed. - Use fields for roles. If the same value kind appears in several roles, keep the type reusable and express the role through the field name.
- Compose declared types. Write
'K-${Number::Integer}', not'K-\d+', whenNumber::Integeralready exists and the composition is meaningful.
Try it
The fastest way to learn the syntax is to open an example in Studio and change it.