Skip to content

PEL function reference ​

Every function callable inside a PEL expression, verified against crates/pgdl/src/pel/functions.rs and crypto.rs. Functions receive already-evaluated arguments; stochastic ones draw from a fresh per-occurrence seed, so two calls in one template differ yet stay reproducible.

For the readable guide, see Expressions (PEL); for operators and coercion, see PEL operators.

Argument coercion ​

Two coercions govern how arguments are read:

  • String coercion (most string functions): null becomes the empty string, booleans and numbers render naturally (integral numbers without a decimal point), arrays and objects render as JSON.
  • Numeric coercion (arithmetic functions add/subtract/…): a number stays itself; a boolean is 0/1; a string is parsed, defaulting to 0 when it is not a number; anything else is 0. (This is the function-argument rule. The + - * / operators are stricter — see PEL operators.)

A result that is a whole number is narrowed to an integer. Every function checks its arity; a wrong count is a BadParams error, never a panic.

Conditional ​

FunctionArityReturnsDescription
if(cond, a, b)3anya if cond is truthy, else b.
coalesce(…)1+anyThe first non-null argument, else null.
default(v, fallback)2anyv unless it is empty (null or ""), then fallback.
optional(v)1any"" when v is null, else v.

switch is a special expression form (not a plain function): switch(subject, key: value, …, default: value). The subject is stringified and matched against each arm key; an unmatched subject with no default arm is an error.

if(age >= 18, 'adult', 'minor')      → 'adult'   (age = 40)
coalesce(null, '', 'x')              → ''
default('', 'n/a')                   → 'n/a'
switch(tier, gold: 3, silver: 2, default: 0)   → 3   (tier = 'gold')

Case transforms ​

All take one string argument and return a string.

FunctionDescriptionExample input → output
lowercase(s)Lowercase.'AbC' → 'abc'
uppercase(s)Uppercase.'AbC' → 'ABC'
capitalize(s)First letter upper, rest lower.'hELLO' → 'Hello'
titlecase(s)Capitalize each word, space-joined.'hello world' → 'Hello World'
pascalcase(s)Capitalize each word, no separator.'hello world' → 'HelloWorld'
camelcase(s)First word lower, rest capitalized.'hello world' → 'helloWorld'
snakecase(s)Lowercase words joined with _.'Hello World' → 'hello_world'
kebabcase(s)Lowercase words joined with -.'Hello World' → 'hello-world'

Words are split on non-alphanumeric separators and camelCase humps.

String ​

FunctionArityReturnsDescription
trim(s)1stringStrip leading/trailing whitespace.
reverse(s)1stringReverse by Unicode scalar.
length(v)1numberArray length, or character count of a string.
slugify(s)1stringLowercase; runs of non-alphanumerics become single -; trimmed.
repeat(s, n)2strings repeated n times (n < 0 → 0).
truncate(s, n, suffix?)2–3stringFirst n chars; append suffix only if truncated.
substring(s, start, len?)2–3stringlen chars from start (to the end when len omitted); clamped.
pad(s, width, ch?)2–3stringLeft-pad to width with ch (default space). No-op if already ≥ width.
padRight(s, width, ch?)2–3stringRight-pad to width with ch.
replace(s, from, to)3stringReplace all occurrences of from.
concat(…)0+stringConcatenate all arguments (string-coerced).
split(s, sep)2arraySplit on sep; an empty sep splits into characters.
join(arr, sep)2stringJoin array elements with sep.
contains(s, needle)2booleanWhether s contains needle.
startsWith(s, p) / starts_with2booleanPrefix test.
endsWith(s, p) / ends_with2booleanSuffix test.
indexOf(s, needle) / index_of2numberCharacter index of needle, or -1.
slugify('Merhaba Dünya!')    → 'merhaba-dünya'
truncate('hello world', 5, '…')  → 'hello…'
substring('abcdef', 1, 3)    → 'bcd'
pad('7', 3, '0')             → '007'
split('a,b,c', ',')          → ['a','b','c']
concat('id-', 42)            → 'id-42'

Pattern fills ​

Each replaces the placeholders in its own symbol class with random characters (other characters pass through; a backslash escapes the next character). See the inline pattern grammar for the full symbol table.

FunctionArityFillsDescription
numerify(s)1# %Digits (% = non-zero digit).
letterify(s)1! @ ?Letters (! upper, @ lower, ? any).
hexify(s)1^ _Hex (^ upper, _ lower).
alphanumerify(s)1all classesEvery placeholder in any class.
numerify('+90 5## ### ## ##')   → '+90 542 187 63 09'
letterify('??-###')             → 'Kp-###'

Numeric ​

Arithmetic functions coerce arguments to numbers (see Argument coercion).

FunctionArityReturnsDescription
add(a, b)2numbera + b.
subtract(a, b)2numbera - b.
multiply(a, b)2numbera * b.
divide(a, b)2numbera / b; error on divide by zero.
modulo(a, b)2numbera % b; error on modulo by zero.
round(x, places?)1–2numberRound to places decimals (default 0).
floor(x)1numberRound toward −∞.
ceil(x)1numberRound toward +∞.
abs(x)1numberAbsolute value.
min(a, b)2numberSmaller of two.
max(a, b)2numberLarger of two.
clamp(x, lo, hi)3numberConstrain x to [lo, hi].
uniform(a, b)2numberRandom float in [a, b] (stochastic).
pow(x, y)2numberx raised to y.
sqrt(x)1numberSquare root (of max(x, 0)).
log(x, base?)1–2numberLogarithm; natural log when base omitted.
sign(x)1number-1, 0, or 1.
round(3.14159, 2)     → 3.14
clamp(120, 0, 100)    → 100
divide(10, 4)         → 2.5

List ​

FunctionArityReturnsDescription
pick(arr)1anyA uniform-random element (a non-array returns itself; empty → null).
first(arr)1anyFirst element, or the value itself; null if empty.
last(arr)1anyLast element, or the value itself; null if empty.
nth(arr, i)2anyElement at index i (null if out of range).
sum(arr)1numberSum of the elements (numeric-coerced).
cycle(arr)1anyElement at row_index mod len — deterministic round-robin by output row.
sample(arr, count?)1–2arraycount distinct random elements without replacement. count is a number or an "a-b" range; default 1.
pick_weighted(arr)1anyWeighted pick; elements are { value, weight } (missing weight = 1, 0 = never). All-zero weights error.
pick(['a','b','c'])                         → 'b'
cycle(['Mon','Tue','Wed'])                  → 'Wed'   (row index 2)
sample([1,2,3,4,5], 2)                       → [4, 1]
pick_weighted([{value:'x',weight:9},{value:'y',weight:1}])  → 'x'

Masking / privacy ​

FunctionArityReturnsDescription
redact(x?)0–1stringAlways "[REDACTED]".
mask(s, show?, side?)1–3stringReplace all but show characters with * (default show = 4). side "start" keeps the first show; otherwise the last show.
mask('4111111111111234')          → '************1234'
mask('secrettoken', 3, 'start')   → 'sec********'

Formatting ​

FunctionArityReturnsDescription
format(v, spec?)1–2stringFormat v per spec (see below).

spec is kind or kind:arg:

specResult
decimal:PFixed to P decimals (default 2).
percentv × 100 with two decimals and a %.
numberRounded integer with thousands separators.
currency:CODEThousands-grouped with two decimals and a symbol: TRY → ₺, EUR → €, GBP → £, anything else → $.
filesizeHuman byte size (B/KB/MB/GB/TB).
(other / omitted)The value, string-coerced.
format(1234567, 'number')       → '1,234,567'
format(0.185, 'percent')        → '18.50%'
format(4200, 'currency:TRY')    → '₺4,200.00'
format(1536, 'filesize')        → '1.5 KB'

Dates ​

Dates are ISO strings in, ISO strings out (UTC). Parsing rejects out-of-range values (2024-02-31 is not silently normalized).

FunctionArityReturnsDescription
addDays(date, n)2dateShift by n days.
subtractDays(date, n)2dateShift back n days.
addMonths(date, n)2dateShift by n months (day clamped to month length).
addYears(date, n)2dateShift by n years.
startOfMonth(date)1dateFirst day of the month.
endOfMonth(date)1dateLast day of the month.
startOfYear(date)1dateJanuary 1 of the year.
dateDiff(a, b, unit?)2–3numberb − a in unit.

dateDiff units: hours, days (default), months, years. months and years are approximations (30-day months, 365-day years), not calendar-aware. Any other unit (including seconds) yields the raw difference in seconds.

addDays('2024-01-30', 5)          → '2024-02-04'
addMonths('2024-01-31', 1)        → '2024-02-29'
endOfMonth('2024-02-10')          → '2024-02-29'
dateDiff('2024-01-01', '2024-03-01', 'days')   → 60

Reference clock ​

Deterministic when a reference instant is frozen (via --reference-time or the registry). See Determinism.

FunctionArityReturnsDescription
now()0datetimeThe reference instant as an ISO datetime.
today()0dateThe reference instant's date.
age(birthdate)1numberWhole years from birthdate to the reference date.
age('1990-07-01')   → 36   (reference date 2026-07-06, before birthday)

Crypto / privacy ​

Standard algorithms, so output is identical across runtimes.

FunctionArityReturnsDescription
hash(s, algo?)1–2stringHex digest. algo is sha256 (default), sha1, or md5; an unknown algorithm errors.
hmac(data, key)2stringHMAC-SHA256, hex-encoded.
base64(s)1stringStandard base64 of the UTF-8 bytes.
urlEncode(s)1stringPercent-encode per RFC 3986 (unreserved set kept).
anonymize(s, mode?)1–2stringA consistent pseudonym — the same input always maps to the same token (referential integrity survives). mode "domain" keeps an email's domain and fakes only the local part.
hash('secret')                → '2bb80d537b1da3e3…'   (sha256, hex)
hash('secret', 'md5')         → '5ebe2294ecd0e0f0…'
anonymize('user@acme.com', 'domain')  → 'a1b2c3d4e5@acme.com'

Inline generators ​

Inside a template, a placeholder whose body starts with a reserved keyword is an inline generator rather than an expression. The reserved heads are number, pattern, date, datetime, time, uuid, ulid, nanoid. Each occurrence draws a fresh sub-seed, so repeated inline generators in one template differ. The forms below are shown inside a template's double braces.

number ​

{{ number:MIN-MAX }}              integer if both bounds are integers
{{ number:MIN-MAX:PRECISION }}    float rounded to PRECISION decimals
{{ number:MIN-MAX:step:STEP }}    draw from MIN, MIN+STEP, … ≤ MAX

If either bound has a decimal point the result is a float; otherwise it is an integer. PRECISION applies only to the float form. STEP is at least 1.

{{ number:1-100 }}        → 42
{{ number:0-1:2 }}        → 0.37
{{ number:0-100:step:5 }} → 65

pattern ​

A pattern:SPEC inline generator replaces placeholder characters with random ones; a backslash escapes the next character (kept literal). Other characters pass through.

SymbolDraws from
#digit 0–9
%non-zero digit 1–9
!uppercase letter
@lowercase letter
?any letter
^hex digit, uppercase
_hex digit, lowercase
*alphanumeric
{{ pattern:###-##-#### }}   → 271-04-8853
{{ pattern:!!##@@ }}        → QX47kd

date / datetime / time ​

{{ date:START..END }}          random ISO date (YYYY-MM-DD)
{{ datetime:START..END }}      random ISO datetime (…T…Z)
{{ time:START..END }}          random HH:MM:SS

date and datetime bounds accept ISO-8601 instants or relative specs resolved against the reference clock: now, today, and offsets like -1year, +7days, now-24h, today+1month. time bounds are HH:MM:SS or HH:MM. A trailing :FORMAT tag on the range is ignored (output is always ISO); because times legitimately contain :, a fully parseable end bound always wins over format-stripping.

Relative-offset units: s/sec/secs/second/seconds, m/min/mins/minute/minutes, h/hour/hours, d/day/days, w/week/weeks, month/months, y/year/years.

{{ date:2020-01-01..2024-12-31 }}   → 2022-08-14
{{ datetime:-30days..now }}         → 2026-06-19T11:42:07Z
{{ time:09:00..17:00 }}             → 13:27:41

id generators ​

{{ uuid }}         UUID v4 (seed-derived)
{{ uuid:v7 }}      UUID v7 (synthetic, index-sortable timestamp)
{{ ulid }}         26-char Crockford base32 ULID
{{ nanoid }}       21-char Nano ID
{{ nanoid:LEN }}   Nano ID of length LEN

uuid (bare or uuid: with anything other than v7) is v4; only uuid:v7 selects v7. All are pure functions of the seed (v7's timestamp is the frozen reference instant plus the invocation index — sortable yet reproducible, never wall-clock).

{{ uuid }}       → 3f2504e0-4f89-41d3-9a0c-0305e82c3301
{{ nanoid:10 }}  → V1StGXR8_Z

Phony Cloud — Documentation & Specification