Expressions (PEL)
PEL (Phony Expression Language) is the small language used wherever PDL composes or transforms values:
| Used in | As |
|---|---|
template.pattern / variants[].pattern | a template string with {{ … }} placeholders |
field computed | a bare expression over sibling fields |
modifiers.transform | an expression over the produced value |
template.operations | a list of function names applied in sequence |
Linked rules | an expression over prior columns |
Template syntax
Anything outside {{ … }} is literal text; inside is an expression. Escape braces with a backslash.
text
Hi {{first_name}}! Ref #{{number:1000-9999}}
\{\{ literal braces \}\}References
text
{{name}} run the generator "name" (or reuse a same-named row field)
{{country.code}} a nested property of an object value
{{self.first_name}} another field in the same entity rowReferences are memoized per row (same reference → one value).
Inline generators
Generate a value inline, without a separate generator definition. Each occurrence draws a fresh sub-seed.
text
{{number:1-100}} {{number:0.0-1.0:2}} {{number:1-100:step:5}}
{{pattern:###-##}} # digit · ? A–Z · ^ hex · * alphanumeric
{{date:2020-01-01..2024-12-31}} {{datetime:-1year..now}} {{time:09:00..18:00}}
{{uuid}} {{uuid:v7}} {{ulid}} {{nanoid}} {{nanoid:10}}Operators
| Class | Operators |
|---|---|
| arithmetic | + - * / % |
| comparison | == != < > <= >= |
| logical | && || ! |
| grouping | ( ) |
| literals | numbers, 'strings'/"strings", inline lists ["a","b"] |
json
"grade": { "computed": "if(score >= 90, 'A', if(score >= 80, 'B', 'C'))" },
"total": { "computed": "round((subtotal + tax) * (1 - discount), 2)" }Function reference
Case & string
| Function | Result |
|---|---|
lowercase uppercase capitalize titlecase | case transforms |
camelcase snakecase kebabcase pascalcase | identifier casings |
trim reverse length repeat(s, n) | basic ops |
truncate(s, n[, suffix]) | cut to n chars |
substring(s, start[, len]) | slice |
pad(s, n[, ch]) padRight(s, n[, ch]) | pad to width |
replace(s, from, to) concat(…) slugify(s) | rewrite |
Numeric
| Function | Result |
|---|---|
add subtract multiply divide modulo | arithmetic (also + - * / %) |
round(n[, places]) floor ceil abs | rounding |
min(a, b) max(a, b) clamp(v, lo, hi) | bounds |
uniform(lo, hi) | random float in range |
Conditional
| Function | Result |
|---|---|
if(cond, then, else) | branch |
switch(x, k: v, …, default: v) | multi-way |
coalesce(…) | first non-null |
default(v, fallback) | fallback if v is empty/null |
optional(v) | "" if null |
Lists
| Function | Result |
|---|---|
pick(list) | random element |
cycle(list) | element at row % len (sequential by row) |
sample(list, n|"a-b") | n unique elements (array) |
pick_weighted(list) | weighted pick from [{value, weight}] |
Masking & crypto
| Function | Result |
|---|---|
mask(v, n[, "start"]) | show last (or first) n, mask the rest |
redact(v) | [REDACTED] |
hash(v[, "sha256"|"sha1"|"md5"]) | hex digest |
hmac(data, key) | HMAC-SHA256 hex |
base64(v) urlEncode(v) | encodings |
anonymize(v[, "domain"]) | consistent pseudonym (referential integrity) |
Format
| Call | Output |
|---|---|
format(x, "decimal:2") | 1234.56 |
format(x, "percent") | 12.34% |
format(x, "number") | 1,234 |
format(x, "currency[:CODE]") | $1,234.56 / ₺… |
format(x, "filesize") | 1.5 MB |
Date & time
| Function | Result |
|---|---|
now() today() | the reference instant |
age(date) | whole years to the reference instant |
addDays subtractDays addMonths addYears | shift a date |
startOfMonth endOfMonth startOfYear | snap a date |
dateDiff(a, b, "days"|"hours"|"months"|"years") | difference |
TIP
now/today/age and relative ranges never read the system clock — they resolve against an injected reference instant, so output stays reproducible.
For the complete language design (including parts still on the roadmap), see Spec → Expression Language.