- use 'zig build' to build from source
- use
zig build testto run tests (it setsPARA_BINfor the test runner)
Para is a data representation language designed to produce clear, statically typed data structures. It allows you to write your config in a more programmatic style, then convert it to the serialization language of your choice. Current output formats are: JSON, YAML, TOML, ZON, RON.
NOTE: This project is still in early development and should not be relied on in production databases.
- Simplicity: Minimal syntax with clear distinctions between variables and groups.
- Explicitness: All variables must be initialized, and types are strictly enforced.
- Compile-Time Safety: Errors (e.g., undefined references, type mismatches) are caught at compile time.
- Mutability: Variables must be explicitly declared as
var(mutable) orconst(immutable). - Scoped Naming: Variables and groups have distinct namespaces within their scope.
Para supports mathematical expressions with standard arithmetic operators:
const result = 10 + 5 * 2 // 20
const power = 2 ^ 3 // 8 (exponentiation)
const modulo = 17 % 5 // 2
const division = 15 / 3 // 5
The peek operator allows you to inspect variable values during preprocessing. Place ? after any variable or expression to print its current value and type:
const x = 42
x? // Prints: [line:col] x :int = 42
const result = x + 8
result? // Prints: [line:col] result :int = 50
This is especially useful for debugging complex expressions or verifying preprocessing steps.
Variables can be assigned and reassigned within their scope:
var counter = 0
counter = counter + 1 // Reassignment allowed for 'var'
Use the temp keyword to declare variables that are only used during preprocessing and dropped from the final output:
temp const intermediate = 100
const final = intermediate * 2 // 'intermediate' won't appear in output
You can add assertions to help catch incorrect or corrupted data:
const port = 8080
# assert port > 1024 and port < 65535 // Valid port range
const timeout = 30
# assert timeout > 0 and timeout < 300 // Reasonable timeout range
const percentage = 85
# assert percentage >= 0 and percentage <= 100 // Valid percentage
- Variables are declared with
:followed by a type and=followed by a value or expression. - Groups are accessed using dotted paths (e.g.
person.age,person.job.salary). Scopes with braces can also be used for clarity (e.g.person { ... }). - Statements end with a newline or EOF; whitespace and empty lines are ignored.
- Identifiers must be unique within their scope (e.g., a variable and group cannot share a name).
- All variables must be initialized; uninitialized declarations are a compile-time error.
- References to variables or groups must be defined earlier in the file (top-to-bottom evaluation).
- Valid identifiers use
a-z,0-9,_, but must not start with a number (e.g.,my_var,user2,first_name). - Case-sensitive (e.g.,
myVarandMyVarare distinct). - Reserved keywords (TBD, e.g.,
int,float,string,bool,time) cannot be used as identifiers.
- Single-line:
// This is a comment - Multi-line:
/* This is a multi-line comment */
Para has five fixed scalar types. Variables must be explicitly declared as var (mutable) or const (immutable). Temporary variables can be declared with the temp prefix and are dropped during the compression stage. Homogeneous arrays are supported via T[] (and can be nested like T[][]).
// int is an i64
var x: int = 18
// float is a f64
var y: float = 3.14
// string is a []u8
var z: string = "hello world"
// bool is a boolean
var logics: bool = true
time is stored as an i64 containing Unix epoch milliseconds (UTC).
Accepted formats:
- Integer literal: treated as epoch milliseconds
- String literal: either a numeric epoch-millis value (optional leading
+/-), or a subset of ISO-8601 (examples:"2024-03-14","2024-03-14T16:45:00Z","2024-03-14T16:45:00.123+02:00"); if no timezone is provided, it’s treated as UTC
var created: time = 1710434700000
var updated: time = "2024-03-14T16:45:00Z"
Arrays are homogeneous and can be nested. Empty arrays require an explicit type annotation.
const xs: int[] = [1, 2, 3]
const xss: int[][] = [[1, 2], [3]]
const empty: string[] = []
Variables must be explicitly declared as mutable or immutable:
// Mutable variables (can be reassigned)
var x: int = 42
x = 50 // This works
// Immutable constants (cannot be reassigned)
const id: int = 567
id = 600 // This errors
// Temporary variables (dropped during preprocessing)
temp var intermediate: int = 100
temp const constant_temp: string = "temporary"
Variables can be assigned to the value of another variable. The value is always copied, not referenced.
const x: int = 5
const y: int = x
Para has one complex type called a "group", similar to a struct in other languages.
person.age: int = 25
person.name: string = "Bob"
// nested groupings are allowed as well
bigNest.littleNest.member1: int = 5
Groups can be written using scope syntax for better readability:
// this
bigNest {
littleNest {
member1: int = 10
}
member2: int = 2
member3: int = 3
}
// reduces to
bigNest.littleNest.member1: int = 10
bigNest.member2: int = 2
bigNest.member3: int = 3
Groups can enforce types for their members:
// this
nest: int {
const member1 = 10
const member2 = 20
}
// reduces to
const nest.member1: int = 10
const nest.member2: int = 20
Para files go through several preprocessing steps for flexibility and optimization:
Configs are flexible - as long as values are initialized before use, they can be referenced:
const default_age: int = 25
temp const new_age: int = defaults.age + 1
var person.age: int = new_age
const person.name: string = "Robert"
temp const nickname: string = "Bob"
const person.nickname = nickname
First stage is resolving expressions and assignments to literal values:
default_age: int = 25
new_age: int = 26
person.age: int = 26
person.name: string = "Robert"
nickname: string = "Bob"
person.nickname = "Bob"
Next we clean up the file and unify formatting. Temps are dropped, globals are raised, and groups are unified:
default_age: int = 25
person {
age: int = 26
name: string = "Robert"
nickname = "Bob"
}
The compressed format can be transpiled into other formats:
{
"default_age": 25,
"person": {
"age": 26,
"name": "Robert",
"nickname": "Bob"
}
}Or into Zig object notation (ZON):
.{
.default_age = 25,
.person = .{
.age = 26,
.name = "Robert",
.nickname = "Bob",
},
}Or into TOML tables:
default_age = 25
[person]
age = 26
name = "Robert"
nickname = "Bob"Or into RON:
(
default_age: 25,
person: (
age: 26,
name: "Robert",
nickname: "Bob",
),
)