Tyco (Typed Config) is a configuration file format designed for explicit typing, readability, and composability. Unlike formats that infer types from values, Tyco requires explicit type annotations, making configurations self-documenting and eliminating ambiguity.
Tyco is designed for:
Here is a complete example demonstrating many of Tyco's features:
# Global configuration
str environment: production
str region: us-east-1
# Database definitions
Database:
*str name:
str host:
int port: 3306
bool ssl: true
- primary, db1.example.com
- replica, db2.example.com, ssl: false
# Server definitions
Server:
*str hostname:
str ip:
int cores:
bool ssd: true
?str notes:
- web-01, 10.0.1.10, 8
- web-02, 10.0.1.11, 8
- db-01, 10.0.2.10, 16, notes: "High memory instance"
# Application definitions
Application:
*str name:
str version:
Server server:
Database database:
int[] ports:
str url:
- web-app, 2.1.0, Server(web-01), Database(primary), \
ports: [80, 443], url: "https://{name}.{global.region}.example.com"
- api, 1.5.3, Server(web-02), Database(replica), \
ports: [8080], url: "https://{name}.{server.hostname}:8080"
Tyco provides a rich set of features for building type-safe, maintainable configuration files:
str, int, float, decimal, bool, date, time, datetime[] suffix notation? prefix to allow null values[value1, value2, ...] syntax* to enable lookups and referencesTypeName(key) syntaxTypeName(value1, value2, ...)\n, \t, \u0000, etc.)""" and ''') for multi-line text{field} syntax{object.field}{..field}{global.field}42, -170x prefix for base-16: 0x1F, 0xFF0o prefix for base-8: 0o755, 0o6440b prefix for base-2: 0b1010, 0b111111113.14, -0.00199.95, 0.0825#include path# line commentsglobal., ..)global as field names to prevent conflicts with scope accessYYYY-MM-DD formatHH:MM:SS formatThis document defines Tyco version 0.2.0. A Tyco file must be a valid UTF-8 encoded Unicode document.
Whitespace means tab (0x09) or space (0x20). Newline means LF (0x0A) or CRLF (0x0D 0x0A).
A hash symbol (#) marks the rest of the line as a comment, which is ignored by parsers.
# This is a full-line comment
str timezone: UTC # This is an end-of-line comment
Comments cannot contain control characters other than tab (U+0009).
Tyco provides a comprehensive set of built-in data types for configuration values. Each type has specific syntax and validation rules:
str — Text values with support for quotes, escaping, and templatesint — Whole numbers (decimal, hex, octal, binary formats supported)float — Floating-point numbers with decimal notationdecimal — Arbitrary-precision numbers for exact arithmeticbool — Boolean values using true and false keywordsdate — Calendar dates in YYYY-MM-DD formattime — Time of day in HH:MM:SS formatdatetime — Combined date and time with optional timezonetype[] — Arrays of any type using [value1, value2, ...] syntaxAll values in Tyco must explicitly declare their type, enabling strong type checking and eliminating ambiguity. Arrays can be created for any type by adding [] after the type name.
Strings are sequences of Unicode characters. Tyco supports both basic strings (with escape sequences) and literal strings (without escape processing).
For the majority of configuration values, quotes are not required at all and can be safely omitted. Simple values like hostnames, names, and identifiers work perfectly without quotes:
str hostname: server1.example.com
str environment: production
str region: us-east-1
Use quoted strings when your content contains syntax characters that could be interpreted as delimiters or structural elements:
# URLs with colons, query parameters with commas
str api_url: 'https://api.example.com/search?q=python,java&limit=10'
# Descriptions with commas
str description: 'A fast, reliable, and secure service'
# Values with brackets, parentheses, or braces
str expression: 'func(arg1, arg2)'
str template: '{variable} with literal braces'
No quotes needed for: Simple hostnames, paths, version numbers, and times (since times start with digits and can't be confused with field names):
str hostname: api.example.com
str version: 1.2.3
str start_time: 14:30:00
Unquoted strings undergo template expansion (replacing {variable} references), while literal strings (using single quotes) disable this behavior.
Basic strings are surrounded by double quotes ("). Any Unicode character may be used except those that must be escaped: backslash (\) and the double quote ("). Basic strings support template expansion.
str message: "Hello, world!"
str path: "C:\\Users\\name"
Escape sequences:
| Escape | Unicode | Description |
|---|---|---|
\b | U+0008 | backspace |
\t | U+0009 | tab |
\n | U+000A | linefeed |
\f | U+000C | form feed |
\r | U+000D | carriage return |
\" | U+0022 | quote |
\\ | U+005C | backslash |
\uXXXX | — | Unicode (4 hex digits) |
\UXXXXXXXX | — | Unicode (8 hex digits) |
Multi-line basic strings are surrounded by three double quotes ("""). Newlines are allowed and will be preserved.
str poem: """
Roses are red
Violets are blue
Tyco is typed
And you're my type too"""
A backslash at the end of a line will trim all whitespace (including newlines) up to the next non-whitespace character or closing delimiter.
str long: """This is a \
very long \
sentence.""" # Renders as: "This is a very long sentence."
Literal strings are surrounded by single quotes ('). No escape sequences are processed, and template expansion is disabled. What you see is what you get. Use literal strings when you need the exact text without any processing.
str winpath: 'C:\Users\name' # Backslashes are literal
str regex: '\d{2}-\w+'
str template: '{variable}' # No template expansion - literal {variable}
Multi-line literal strings are surrounded by three single quotes (''').
str lines: '''
First line
Second line'''
Integers are whole numbers. Positive integers may be prefixed with a plus (+). Negative integers are prefixed with a minus (-).
int count: 42
int negative: -17
int positive: +99
Tyco supports hexadecimal, octal, and binary representations:
# Hexadecimal with prefix 0x
int hex: 0x1F # 31 in decimal
# Octal with prefix 0o
int octal: 0o755 # 493 in decimal
# Binary with prefix 0b
int binary: 0b11010110 # 214 in decimal
Floats are numbers with a fractional component. They must contain a decimal point.
float pi: 3.14159
float negative: -0.001
float version: 1.0
Decimals are arbitrary-precision numbers with a fixed number of decimal places. Unlike floats, decimals avoid floating-point precision errors, making them ideal for financial calculations, currencies, and other applications requiring exact decimal arithmetic.
decimal price: 99.95
decimal tax_rate: 0.0825
decimal balance: -1234.56
Decimal values use the same numeric syntax as floats but are interpreted with arbitrary precision. Both positive and negative values are supported.
decimal amount: 1234567.89
decimal precise: 0.123456789012345
Parser implementations map decimal values to appropriate types in the target language:
decimal.Decimal from the standard libraryjava.math.BigDecimalSystem.DecimalDecimal class holding integer value and scaleshopspring/decimal)rust_decimal)For languages without built-in decimal support, parsers implement a lightweight decimal representation:
// Example JavaScript implementation
class Decimal {
constructor(value, scale) {
this.value = value; // Integer representation
this.scale = scale; // Number of decimal places
}
toString() {
const str = this.value.toString();
if (this.scale === 0) return str;
const negative = str.startsWith('-');
const digits = negative ? str.slice(1) : str;
const padded = digits.padStart(this.scale + 1, '0');
const whole = padded.slice(0, -this.scale) || '0';
const fraction = padded.slice(-this.scale);
return (negative ? '-' : '') + whole + '.' + fraction;
}
}
Note: Custom decimal implementations typically store only the representation (value and scale) and delegate arithmetic operations to external libraries or user code. This ensures accurate decimal representation without complex arithmetic implementation.
Booleans are represented by the lowercase keywords true and false.
bool enabled: true
bool debug: false
Tyco supports date, time, and datetime values using ISO 8601 format.
date birthday: 1990-05-15 # YYYY-MM-DD
time alarm: 07:30:00 # HH:MM:SS
Datetime values combine date and time components. The standard ISO 8601 format uses a "T" separator, but this can be omitted and replaced with a space for improved readability:
# Standard ISO 8601 format with T separator
datetime created: 2024-01-15T14:30:00
datetime updated: 2024-01-15T14:30:00Z # With timezone
# Alternative format with space separator (more readable)
datetime started: 2024-01-15 14:30:00
datetime finished: 2024-01-15 14:30:00Z # With timezone
Both formats are equivalent and supported by Tyco parsers. The space-separated format is often preferred in configuration files for better human readability.
Arrays are collections of values enclosed in square brackets. Values are separated by commas. Trailing commas are allowed and can improve readability when arrays span multiple lines.
int[] ports: [80, 443, 8080]
str[] tags: ["production", "web", "critical"]
Arrays can span multiple lines:
str[] servers: [
"server1.example.com",
"server2.example.com",
"server3.example.com", # Trailing comma allowed
]
Empty arrays are allowed:
int[] empty: []
Global attributes are top-level key-value pairs that are accessible throughout the configuration. Struct instances can access global variables in templates using the explicit {global.field} syntax.
str timezone: UTC
str environment: production
int max_connections: 1000
Note: The word global is semi-reserved for template access to global scope. As a best practice, avoid using global as a field name in struct definitions to prevent conflicts with the global access mechanism. See String Templates for more information on global variable access in templates.
Global attributes can be marked as nullable by prefixing the type with ?. Nullable attributes can have the value null.
?str optional_setting: null
?int timeout: 30
Structs are the core building blocks of Tyco, allowing you to define custom data types with strongly-typed fields. The struct system provides four main capabilities:
Structs support advanced features including primary keys for references, nullable fields, array types, and inheritance through default value cascading.
Structs define structured data types with named fields. A struct definition begins with a capitalized identifier followed by a colon.
Server:
str hostname:
int port:
bool ssl:
The schema section defines the structure and types of a struct's fields. Each field declaration consists of a type, field name, and optional default value or modifiers.
[modifier]type[[]] field_name: [default_value]
Fields marked with an asterisk (*) are primary keys, used for object lookups and references.
User:
*str username: # Primary key
str email:
int age:
Fields marked with a question mark (?) are nullable and can have the value null.
Person:
str name:
?str nickname: # Optional field
?int age:
Fields with the [] suffix are arrays of the specified type.
Application:
str name:
str[] tags: # Array of strings
int[] ports:
Fields can reference other struct types.
Database:
*str name:
str host:
int port:
Application:
str name:
Database database: # References a Database instance
Scalar fields can restrict their allowed values by supplying a parenthesized list of choices in place of a default. Each choice is parsed using the field's declared type, and every instance must provide a value from that list.
Service:
str name:
str tier: (primary, backup, canary)
int port:
Region:
str name:
str tier:
- ewr1, primary
- sjc1, backup
Enum constraints are validated before templating or object creation, so misspelled or unsupported values fail fast. Choices can span multiple lines, include quoted literals, and use any base scalar type (strings, ints, decimals, booleans, etc.). Because enums represent a constraint—not a default—Tyco requires each instance to explicitly set the field.
Default values can be specified in the schema declaration or as separate attribute assignments.
Server:
str hostname:
int port: 80 # Default port
bool ssl: false # Default SSL setting
Defaults can also be set after the schema using indented attribute assignments.
Server:
str hostname:
int port:
bool ssl:
port: 80 # Default port value
ssl: false
You can create multiple struct blocks for the same type with different default values. Each block can define its own defaults and instances. This is useful for organizing instances by environment, region, or other categories.
Defaults are applied procedurally in the order they appear in the file. Each new defaults declaration updates the current default values for subsequent instances, allowing you to change defaults as you progress through the configuration.
# First Server block with development defaults
Server:
*str hostname:
int port:
bool ssl:
port: 8080 # Dev default
ssl: false
- dev1.internal
- dev2.internal
# Second Server block with production defaults
Server:
port: 443 # Production default
ssl: true
- prod1.example.com
- prod2.example.com
# Third Server block with custom defaults
Server:
port: 3000 # API default
ssl: true
- api1.example.com
- api2.example.com
All instances from all blocks are collected together into a single array for the Server type.
Struct instances are created using a dash (-) followed by field values. Values can be provided in order or as named assignments. Trailing commas are allowed in instance declarations.
When using positional values, they must match the order of struct's fields.
User:
*str username:
str email:
int age:
- alice, alice@example.com, 30
- bob, bob@example.com, 25, # Trailing comma allowed
Values can be explicitly named using field_name: value syntax. These can be ommitted if using defaults, and for clarity should follow the order of field names in the schema, but technically can be in any order.
User:
*str username:
str email:
int age:
- username: alice, email: alice@example.com, age: 30
- username: bob, email: bob@example.com, age: 25
Positional and named values can be mixed, with positional values coming first.
User:
*str username:
str email:
int age:
- alice, email: alice@example.com, age: 30
Long instance declarations can be split across multiple lines using a backslash (\) at the end of a line.
Server:
*str hostname:
int port:
bool ssl:
str description:
- hostname: server1.example.com, port: 443, ssl: true, \
description: "Production web server"
Objects with primary keys can be referenced by other objects. References use the syntax TypeName(primary_key_value).
Database:
*str name:
str host:
int port:
- main_db, db.example.com, 3306
- cache_db, redis.example.com, 6379
Application:
str name:
Database database:
- web_app, Database(main_db) # References Database with name "main_db"
- cache_service, Database(cache_db)
Objects can be created inline by providing all required field values in the reference syntax. Trailing commas are allowed in inline object creation.
Person:
str name:
int age:
Server:
str hostname:
Person owner:
- server1, Person(Alice, 30,) # Creates Person inline, trailing comma allowed
String values support template interpolation using curly braces {field}. Templates are resolved after all instances are created.
Server:
*str name:
str domain:
str url:
- web1, example.com, "https://{name}.{domain}" # Resolves to "https://web1.example.com"
Templates can access fields of referenced objects using dot notation.
Host:
*str name:
str ip:
- server1, 192.168.1.10
Application:
str name:
Host host:
str connection:
- web_app, Host(server1), "{name} @ {host.ip}" # Resolves to "web_app @ 192.168.1.10"
Schema field names can legally contain dots, but it is best to avoid them to prevent confusion. Template resolution first treats each dot as a scope separator; if that lookup fails, Tyco falls back to matching the longest remaining segment as a literal field name.
Service:
*str name:
Host host:
str hostname.value:
str description:
- edge-api, Host(edge), "literal", "host={host.name} literal={hostname.value}"
In the example above {host.name} behaves like standard dot access, while {hostname.value} resolves to the field whose literal name contains a dot.
Templates can reference parent object fields using explicit .. prefix. Each additional dot traverses one level up in the parent hierarchy. This provides explicit control over scope traversal.
Project:
*str name:
str status:
Task task:
- WebApp, active, Task("Setup for {..name}")
Task:
str description:
The {..name} reference explicitly accesses the parent Project's name field, making scope relationships clear and predictable.
Struct instances must use explicit global. syntax to access global variables, ensuring clarity and preventing naming conflicts. Global variables can reference other globals directly.
str company: "TechCorp"
str region: "us-west"
Person:
*str name:
str company:
str email:
- Alice, "{global.company}", "{name}@{global.company}.com"
# Global variables access other globals directly
str server_name: "{company}-api-{region}" # Resolves to "TechCorp-api-us-west"
The explicit global. prefix makes it clear when struct instances are accessing global scope, preventing confusion with local field names. If a struct has a field named global, that field will take precedence over the global scope access mechanism. Global variables themselves can reference other globals directly without the prefix.
str company: "TechCorp"
Department:
*str name:
str global:
str summary:
- LocalScope, local-value, "{global}" # Resolves to "local-value"
In this case {global} binds to the local field, not the global scope - however avoid naming field names global to reduce ambiguity.
The #include directive allows splitting configuration across multiple files.
#include shared_config.tyco
#include databases.tyco
Application:
str name:
Database db:
- main_app, Database(primary)
Note: Included files must not redefine struct types already defined in the including file. Implementations may process includes recursively.
Modern Tyco parsers support loading entire directory structures, automatically discovering and processing all .tyco files recursively.
When a directory path is provided to the loader, all .tyco files within that directory and subdirectories are processed as a single configuration context:
# Python example
from tyco import load
# Load all .tyco files in config/ directory recursively
context = load('config/')
data = context.as_json()
Directory structure example:
config/
├── globals.tyco # Global settings
├── databases.tyco # Database definitions
├── environments/
│ ├── production.tyco # Production config
│ └── staging.tyco # Staging config
└── services/
├── web.tyco # Web service config
└── api.tyco # API service config
Note: All files are processed in the same context, so struct definitions and references work across file boundaries. However, duplicate struct type definitions will cause errors.
Some Tyco parser implementations support language-specific validation modules that provide custom validation logic and constructors for configuration objects.
The Python parser automatically loads corresponding .py files when processing .tyco files. If config.tyco exists alongside config.py, the Python module is imported and can provide custom validation:
Import errors (including syntax errors) in a sibling .py module now halt loading so broken validators surface immediately.
# config.tyco
User:
*str username:
str email:
int age:
- alice, alice@example.com, 30
- bob, invalid-email, 25
# config.py
import re
from tyco.parser import Struct
class User(Struct):
def validate(self):
# Custom email validation
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, self.email):
raise ValueError(f"Invalid email format: {self.email}")
# Age validation
if self.age < 0:
raise ValueError(f"Age must be positive: {self.age}")
Validation errors are reported with clear messages indicating which configuration objects failed validation:
ValueError: Invalid email format: invalid-email
in User(bob) at config.tyco:5
Note: Validation modules are optional. If a .py file cannot be loaded or contains errors, configuration parsing continues without validation. This ensures that malformed validation code doesn't break configuration loading.
This section provides a formal grammar for Tyco in Augmented Backus-Naur Form (ABNF) per RFC 5234.
; Document Structure
tyco-document = *( global-attr / struct-def / include / comment / newline )
; Global Attributes
global-attr = [ nullable ] type-name [ array-marker ] ws+ identifier ws* ":" ws* value newline
; Struct Definition
struct-def = struct-header schema-section [ defaults-section ] [ instances-section ]
struct-header = identifier ":" newline
schema-section = 1*( ws+ [ modifier ] type-name [ array-marker ] ws+ identifier ws* ":" [ ws* value ] newline )
; Modifiers
modifier = primary-key / nullable
primary-key = "*"
nullable = "?"
array-marker = "[]"
; Default Values
defaults-section = *( ws+ identifier ws* ":" ws* value newline )
; Instances
instances-section = *( ws+ "-" ws* instance-values [ "\" newline ] newline )
instance-values = value *( ws* "," ws* [ identifier ws* ":" ws* ] value )
; Values
value = string / integer / float / boolean / null / array / reference
string = basic-string / multiline-basic / literal-string / multiline-literal
basic-string = %x22 *( string-char / escape-seq ) %x22
multiline-basic = %x22.22.22 *( string-char / newline / escape-seq / line-ending-backslash ) %x22.22.22
literal-string = %x27 *literal-char %x27
multiline-literal = %x27.27.27 *( literal-char / newline ) %x27.27.27
; Escape Sequences
escape-seq = "\\" ( %x62 / %x74 / %x6E / %x66 / %x72 / %x22 / %x5C / unicode-escape )
unicode-escape = "u" 4HEXDIG / "U" 8HEXDIG
line-ending-backslash = "\\" ws* newline ws*
; Numbers
integer = [ "+" / "-" ] ( decimal / hexadecimal / octal / binary )
decimal = 1*DIGIT
hexadecimal = "0x" 1*HEXDIG
octal = "0o" 1*( %x30-37 )
binary = "0b" 1*BIT
float = [ "+" / "-" ] 1*DIGIT "." 1*DIGIT
; Boolean and Null
boolean = "true" / "false"
null = "null"
; Array
array = "[" [ ws* value *( ws* "," ws* value ) [ ws* "," ] ] ws* "]"
; Reference
reference = identifier "(" ( identifier / ( value *( "," ws* value ) ) ) ")"
; Types
type-name = "str" / "int" / "float" / "bool" / "date" / "time" / "datetime" / identifier
; Identifiers
identifier = ( ALPHA / "_" ) *( ALPHA / DIGIT / "_" / "-" )
; Comments and Includes
comment = "#" *( %x09 / %x20-10FFFF ) newline
include = "#include" ws+ path newline
path = 1*( ALPHA / DIGIT / "." / "/" / "_" / "-" )
; Whitespace
ws = %x20 / %x09 ; Space or Tab
newline = %x0A / %x0D.0A ; LF or CRLF
; Character Classes
string-char = %x20-21 / %x23-5B / %x5D-10FFFF ; Any Unicode except " and \
literal-char = %x20-26 / %x28-10FFFF ; Any Unicode except '