Expressions

An expression is the part of a tag that works out a value: 1 + 2, $price * $qty, $name | upper, $state in ('open', 'pending'). PAD's evaluator reads it - numbers, strings, fields, lists, operators, functions and pipes - and answers one value. This page goes through all of it; every example is live, its template beside what PAD made of it.

Where expressions stand

An expression is read wherever a tag takes a value: the parameter of {echo} and of {if} and {elseif}, the right side of {set $x = ...}, the value of {case} and its {when}s, the condition of {while} and {until}, a quoted option such as where='...', and an occurrence assignment %name = ... on a tag that loops:

PHP
HTML
fragments/expressions_1.pad
Result
{set $price = 12.5}
{set $qty = 3}
<p>Total: {echo $price * $qty}</p>
{set $total = $price * $qty}
<p>{if $total gt 30}Free shipping{else}Shipping: 4.95{/if}</p>

Total: 37.5

Free shipping

PHP
HTML
fragments/expressions_2.pad
Result
{data 'stock'}
[ {"item": "pen", "qty": 12}, {"item": "ink", "qty": 0}, {"item": "pad", "qty": 5} ]
{/data}
{stock where='$qty gt 0', %order = 20 - $qty}
<p>{$item}: {$qty} in stock, order {$order}</p>
{/stock}

pen: 12 in stock, order 8

pad: 5 in stock, order 15

PHP
HTML
fragments/expressions_3.pad
Result
{set $n = 7}
<p>{$n} is {case $n % 2}{when0}even{when 1}odd{/case}</p>
{set $i = 1}
<p>{while $i le 20}{$i} {set $i = $i * 2}{/while}</p>

7 is odd

1 2 4 8 16

A field tag takes a pipe as it stands - {$name | trim} - and so does the closing tag of a pair, over everything the pair rendered. The pipes page has the opening and closing tag pipes in full:

PHP
HTML
fragments/expressions_4.pad
Result
{set $name = '  ada lovelace  '}
<p>{$name | trim | ucwords}</p>
{data 'langs'}["php", "sql", "pad"]{/data}
<p>{langs}{$langs} {/langs | upper}</p>

Ada Lovelace

PHP SQL PAD

Values

Numbers

Whole numbers, decimals, a decimal that starts with its point (.5) and exponents (1e3, 2.5E-1). A minus before a number belongs to it. 0x followed by hexadecimal digits is not a number but a string of bytes: 0x41 is the letter A.

HTML RESULT
{echo 42}
42
{echo -7 + 2}
-5
{echo 1.25 * 4}
5
{echo .5 + .25}
0.75
{echo 1e3 + 1}
1001
{echo 2.5E-1 * 4}
1
{echo 0x41}
A
{echo 0x504144}
PAD

Strings

Text stands between single or double quotes, which mean the same: a $name inside double quotes stays text, it is not filled in as PHP would. A backslash starts an escape - \n, \r, \t, \\, \' and \"; any other letter after it is an error. The dot joins strings, and numbers with them:

HTML RESULT
{echo 'single quotes'}
single quotes
{echo "double quotes"}
double quotes
{echo 'it\'s'}
it's
{echo "say \"hi\""}
say "hi"
{echo 'back\\slash'}
back\slash
{echo 'one\ntwo' | php:nl2br}
one
two
{echo "a\tb" | php:strlen}
3
{echo 'Order ' . 42 . "!"}
Order 42!

Bare words

A word without quotes and without a sigil is looked up. An operator word - eq, and, not ... - is the operator; a PHP constant - TRUE, FALSE, NULL, PHP_EOL, M_PI - is its value; a word that names nothing stands for its own text.

HTML RESULT
{echo TRUE}
1
[{echo FALSE}] [{echo NULL}]
[] []
{echo PHP_INT_SIZE}
8
{echo M_PI | %.4f}
3.1416
{echo hello}
hello
{echo 'hello' eq hello}
1

Quote text. A bare word that names something PAD knows - a function, a tag, a property, a data set, a field - is that thing: count, name and first are properties, so {echo count} does not write the word count. Text in quotes is always text.

Fields, paths and indirection

$name reads a field: a variable the page's PHP set, one a {set} made, a field of the row a loop stands on. A dot walks into an array - $user.address.city, and $items.0 by position.

PHP
fragments/expressions_8.php
HTML
fragments/expressions_8.pad
Result
<?php

$user
= [ 'name' => 'Ada', 'address' => [ 'city' => 'London', 'zip' => 'W1' ] ];
$langs = [ 'PHP', 'SQL', 'PAD' ];

?>
<p>{echo $user.name . ' from ' . $user.address.city}</p>
<p>{echo $langs.0} and {echo $langs.2}</p>
<p>{$user.address.city | upper}, {$user.address.zip}</p>

Ada from London

PHP and PAD

LONDON, W1

$$name reads the field whose name another field holds - a path as well - the way {$$name} does as a tag. The name must be one of the application's fields; the engine's own pad... variables are never read this way.

PHP
fragments/expressions_9.php
HTML
fragments/expressions_9.pad
Result
<?php

$lang
= 'nl';
$hello_en = 'Hello';
$hello_nl = 'Hallo';
$hello_de = 'Guten Tag';

$user = [ 'name' => 'Ada', 'address' => [ 'city' => 'London' ] ];
$show = 'user.address.city';

?>
{set $key = 'hello_' . $lang}
<p>{echo $$key}, {echo $user.name}</p>
<p>{echo $$show}</p>

Hallo, Ada

London

A field that holds a list is not a value: $items + 1 is an error. Walk the list with a loop, or hand it to a function with the array: prefix, below.

Options, properties and the placeholder

PHP
HTML
fragments/expressions_10.pad
Result
{define 'gross'}{parms net, vat=21}{echo #net * (1 + #vat / 100) | %.2f}{/define}
<p>{gross net=100} and {gross net=100, vat=9}</p>
{data 'steps'}["plan", "build", "ship"]{/data}
{steps}
<p>{echo &current . ' of ' . &count}: {$steps}{echo last@steps ? ' - the last one' : ''}</p>
{/steps}

121.00 and 109.00

1 of 3: plan

2 of 3: build

3 of 3: ship - the last one

Lists

[ ] makes a list, its items separated by commas, with keys written key => value where wanted, mixed with plain items as PHP mixes them. A list is what {set} stores, what a loop walks and what a PHP function takes:

PHP
HTML
fragments/expressions_11.pad
Result
{set $book = [ 'title' => 'Minutes', 'pages' => 2 * 120 ]}
<p>{$book.title}: {$book.pages} pages</p>
{set $primes = [2, 3, 5, 7]}
<p>{primes}{$primes} {/primes}</p>
<p>{echo php:implode(' + ', [1, 2, 3]) . ' = ' . php:array_sum([1, 2, 3])}</p>

Minutes: 240 pages

2 3 5 7

1 + 2 + 3 = 6

Type prefixes

A prefix says what a name is, so nothing else of that name can be meant: field:, array: (a list as a value), data: (a {data} set), constant:, php: (a PHP function), pad: and app: (a built-in or an application pipe function), function:, tag: and the others the prefix page lists:

PHP
HTML
fragments/expressions_12.pad
Result
{data 'colors'}["red", "green", "blue"]{/data}
{set $nums = [4, 8, 15]}
{set $title = 'Prefixes'}
<p>{echo constant:PHP_INT_SIZE}</p>
<p>{echo php:count(data:colors)}</p>
<p>{echo php:count(array:nums)} numbers, together {echo array:nums + 0}</p>
<p>{echo php:str_repeat('=', 5)}</p>
<p>{echo field:title | upper}</p>

8

3

3 numbers, together 27

=====

PREFIXES

Operators

The operators bind in this order, strongest first. Those of one row bind equally and are applied left to right - 10 - 2 + 3 is 11 - except **, which binds right to left. Round brackets group as usual.

operators what they do
** power, right to left: 2 ** 3 ** 2 is 2 ** 9
!, a sign - + before a value not, and the signs - weaker than **: -3 ** 2 is -9
* / % multiply, divide, remainder
+ - add, subtract
. join as text
lt le gt ge - < <= > >= order
eq ne - == = != <> equal, not equal
not denies what follows, a comparison included
and - && both hold
xor exactly one holds
or - || either holds
?? the left side, or the right one when the left is empty or missing
? : and ?: the inline ternary, weakest of all

The tests in, like, matches, range, between and contains read infix as well; they have a section of their own.

Arithmetic

As in PHP: / answers a decimal when it does not divide evenly, % works on whole numbers. A text that is a number counts as one - '5' + '5' is 10 - a text that is not, 'a' + 1, is an error, and so is a division by zero.

HTML RESULT
{echo 7 + 3}
10
{echo 7 - 3}
4
{echo 7 * 3}
21
{echo 7 / 2}
3.5
{echo 7 % 3}
1
{echo 2 ** 10}
1024
{echo -(2 + 3)}
-5
{echo 10 / 3}
3.3333333333333

Precedence

HTML RESULT
{echo 1 + 2 * 3}
7
{echo (1 + 2) * 3}
9
{echo 10 - 2 + 3}
11
{echo 12 / 2 * 3}
18
{echo 2 ** 3 ** 2}
512
{echo -3 ** 2}
-9
{echo (-3) ** 2}
9
{echo 2 ** -1}
0.5

Joining text

The dot binds weaker than + and -: in 1 + 2 . 3 the sum comes first and is then joined with the 3, and in 1 . 5 + 5 the 1 is joined with 10. After a value a dot is the join, also right against it - 'a'.1 - and only where no value stands before it does a dot start a number.

HTML RESULT
{echo 'Order ' . 42}
Order 42
{echo 'Total: ' . 3 * 4}
Total: 12
{echo 1 + 2 . 3}
33
{echo 1 . 5 + 5}
110
{echo (1 . 5) + 5}
20
{echo 'a'.1}
a1

Comparing

Each comparison has a word and a symbol: eq or == (a single = too), ne, != or <>, and lt le gt ge or < <= > >=. The words read better in a template full of HTML. Numbers, and texts that are numbers, compare as numbers - '2' lt '10' holds; other texts compare character by character, case counting. PHP's === and !== are not there. A comparison answers 1 or nothing:

HTML RESULT
[{echo 3 eq 3}] [{echo 3 == '3'}] [{echo 3 = 4}]
[1] [1] []
[{echo 3 ne 4}] [{echo 3 != 4}] [{echo 3 <> 3}]
[1] [1] []
[{echo 2 lt 10}] [{echo 2 < 10}] [{echo 9 le 9}]
[1] [1] [1]
[{echo 5 gt 9}] [{echo 5 >= 5}]
[] [1]
[{echo '2' lt '10'}] [{echo 'apple' lt 'banana'}]
[1] [1]
[{echo 'abc' eq 'ABC'}] [{echo 'abc' eq 0}]
[] []

Logic

and, or, xor and not, with &&, || and ! as their symbols. The word not binds weaker than the comparisons, so not $a eq $b denies the comparison; ! binds as tightly as a sign, so !$a eq $b compares !$a with $b. and and or stop at a left side that decides: $n gt 0 and 10 / $n gt 1 never divides by zero.

HTML RESULT
[{echo 1 and 0}] [{echo 1 && 1}]
[] [1]
[{echo 1 or 0}] [{echo 0 || 0}]
[1] []
[{echo 1 xor 1}] [{echo 1 xor 0}]
[] [1]
[{echo not 1}] [{echo !0}]
[] [1]
[{echo not 1 eq 2}]
[1]
[{echo 1 eq 1 and not 2 eq 3}]
[1]
[{echo 2 gt 1 and 3 gt 2 or 0}]
[1]

A default: ??

a ?? b answers a unless it is empty - '', NULL, FALSE, an empty list - or a field that does not exist; then it answers b. A 0 is a value and stays. A field in front of ?? may be missing, also under the strict check: giving it a default is what the operator is for. The right side is worked out only when it is wanted. In a pipe it takes the piped value, and the default function does the same:

PHP
fragments/expressions_18.php
HTML
fragments/expressions_18.pad
Result
<?php

$nickname
= '';
$count = 0;
$tags = [];

?>
<p>{echo $nickname ?? 'no nickname'}</p>
<p>{echo $count ?? 'no count'}</p>
<p>{echo $surname ?? 'no surname'}</p>
<p>{echo $surname ?? $nickname ?? 'nothing at all'}</p>
<p>{$nickname | ?? 'anonymous'} - {$nickname | default('anonymous')}</p>

no nickname

0

no surname

nothing at all

anonymous - anonymous

The inline ternary

cond ? then : else binds weakest of all: everything before the ? is the condition, and each branch runs to its : or to the end - 0 ? 'yes' : 'no' . '!' joins the '!' to the no. Only the branch chosen is worked out. Ternaries in a row read from the right, a ? b : c ? d : e being a ? b : (c ? d : e). The short form a ?: b answers a itself when it holds, and with nothing before the ? the piped value is the condition:

HTML RESULT
{echo 3 gt 1 ? 'many' : 'one'}
many
{echo 15 gt 10 ? (15 gt 20 ? 'L' : 'M') : 'S'}
M
{echo 15 gt 20 ? 'L' : 15 gt 10 ? 'M' : 'S'}
M
{echo '' ?: 'Untitled'}
Untitled
{echo 'Draft' ?: 'Untitled'}
Draft
{echo 0 | ? 'some' : 'none'}
none
{echo 0 ? 'yes' : 'no' . '!'}
no!

PHP
HTML
fragments/expressions_20.pad
Result
{data 'cart'}
[ {"item": "pen", "qty": 1}, {"item": "book", "qty": 3}, {"item": "lamp", "qty": 0} ]
{/data}
{cart}
<p>{$qty} {echo $qty eq 1 ? $item : $item . 's'}{echo $qty ? '' : ' - sold out'}</p>
{/cart}

1 pen

3 books

0 lamps - sold out

Truth

Where a condition is wanted - {if}, where=, the ternary, and and or - a value is tested as PHP tests it: '', '0', 0, NULL, FALSE and an empty list are false, every other value is true - the texts '0.0', 'false' and a space among them.

HTML RESULT
{echo '' ? 'true' : 'false'}
false
{echo '0' ? 'true' : 'false'}
false
{echo 0 ? 'true' : 'false'}
false
{echo NULL ? 'true' : 'false'}
false
{echo [] ? 'true' : 'false'}
false
{echo '0.0' ? 'true' : 'false'}
true
{echo 'false' ? 'true' : 'false'}
true
{echo ' ' ? 'true' : 'false'}
true
{echo -1 ? 'true' : 'false'}
true
{echo [0] ? 'true' : 'false'}
true

The comparisons, the tests and the logic operators answer 1 for true and '' for false: written by {echo} a truth is a 1 or nothing at all, and truths add up as numbers.

HTML RESULT
[{echo 2 gt 1}] [{echo 1 gt 2}]
[1] []
{echo (2 gt 1) + (3 gt 1) + (0 gt 1)}
2
{echo ('a' eq 'a') . '/' . ('a' eq 'b')}
1/
{if 'x' eq 'x'}holds{/if}
holds

Tests: in, like, matches, range, between, contains

test holds when
$x in (a, b, ...) the value equals one of the list
$x like 'A%' the value matches the SQL pattern: % any run of characters, _ one character, \\% and \\_ themselves; case is ignored
$x matches '/re/' the regular expression matches
$x range (a, b) a <= value <= b, the ends included
$x between (a, b) a < value < b, the ends left out
$x contains y the text holds y - or, for a list, an item of it equals y

A not between the value and the test denies the test, as SQL writes it: $s not in (...), not like, not matches, not range. The value is everything left of the test - 5 + 1 in (6, 7) tests 6.

HTML RESULT
[{echo 'a' in ('a', 'b')}] [{echo 'x' in ('a', 'b')}]
[1] []
[{echo 3 in (1, 2, 3)}]
[1]
[{echo 'x' not in ('a', 'b')}]
[1]
[{echo 5 + 1 in (6, 7)}]
[1]
[{echo not 'a' in ('a', 'b')}]
[]

like ignores case; _ stands for exactly one character:

HTML RESULT
[{echo 'Alice' like 'A%'}] [{echo 'alice' like 'A%'}]
[1] [1]
[{echo 'Bob' like 'B_b'}] [{echo 'Barb' like 'B_b'}]
[1] []
[{echo 'Bob' not like 'A%'}]
[1]
[{echo '50%' like '50\\%'}] [{echo '500' like '50\\%'}]
[1] []

A backslash in a PAD string is an escape, so the backslash a regular expression needs is written twice: '/a\\.b/'. A {n} quantifier would open a tag - repeat the class, as below, or let the page's PHP hand the pattern over.

HTML RESULT
[{echo 'NL1234' matches '/^[A-Z][A-Z][0-9]+$/'}]
[1]
[{echo 'a.b' matches '/a\\.b/'}] [{echo 'axb' matches '/a\\.b/'}]
[1] []
[{echo 'Hello' matches '/^h/i'}] [{echo 'Hello' matches '/^h/'}]
[1] []
[{echo 'Hello' not matches '/^h/'}]
[1]

HTML RESULT
[{echo 20 range (20, 40)}] [{echo 40 range (20, 40)}] [{echo 41 range (20, 40)}]
[1] [1] []
[{echo 20 between (20, 40)}] [{echo 30 between (20, 40)}]
[] [1]
[{echo 'm' range ('a', 'z')}]
[1]
[{echo 50 not range (20, 40)}]
[1]

contains looks into a text, or into a list for an item equal to the one asked for:

HTML RESULT
[{echo 'banana' contains 'nan'}] [{echo 'banana' contains 'x'}]
[1] []
[{echo 'banana' not contains 'x'}]
[1]
[{echo [1, 2, 3] contains 3}] [{echo ['a', 'b'] contains 'c'}]
[1] []

Each test is a pipe function too, so it can stand after a pipe or be called, its value the first argument:

HTML RESULT
[{echo 'b' | in('a', 'b')}] [{echo in('b', 'a', 'b')}]
[1] [1]
[{echo 'Alice' | like('A%')}] [{echo like('Alice', 'A%')}]
[1] [1]
[{echo 7 | range(1, 9)}] [{echo range(7, 1, 9)}]
[1] [1]
[{echo contains('banana', 'nan')}]
[1]

Together with and and or they make the conditions of the where option, which the data handling page covers with the other options:

PHP
HTML
fragments/expressions_29.pad
Result
{data 'orders'}
[ {"nr": 1, "state": "paid", "customer": "Ada"},
{"nr": 2, "state": "cancelled", "customer": "Alan"},
{"nr": 3, "state": "sent", "customer": "Bob"},
{"nr": 4, "state": "sent", "customer": "Alice"} ]
{/data}
{orders where='$state not in ("cancelled", "refunded") and $customer like "A%"'}
<p>{$nr}: {$customer}, {$state}</p>
{/orders}

1: Ada, paid

4: Alice, sent

Functions

The built-in pipe functions - upper, left, replace, date and the rest of the {functions} list - are used two ways. After a pipe they take the value on its left: $s | left(2). Written as a call with nothing piped in they take it as their first argument: left($s, 2), as PHP's own functions do - so they nest, and stand wherever a value can. The number of parameters is checked.

HTML RESULT
{echo 'abcdef' | left(2)}
ab
{echo left('abcdef', 2)}
ab
{echo 'Hello ' . upper('world')}
Hello WORLD
{echo upper(left('abcdef', 3))}
ABC
{echo 'a-b' | replace('-', upper(' and '))}
a AND b
{if left('NL1234', 2) eq 'NL'}Dutch{/if}
Dutch

HTML RESULT
{echo 'abcdef' | mid(2, 3)}
bcd
{echo 'abcdef' | right(2)}
ef
{echo 'user@example.com' | after('@') | before('.')}
example
{echo 'Crème Brûlée & Co.' | slug}
creme-brulee-co
{echo 1536 | bytes}
1.5 KB
{echo 'a long sentence that goes on' | truncate(17)}
a long sentence…
{echo 1234567 | abbreviate(1)}
1.2M
{echo 22 | ordinal}
22nd

php:name calls a PHP function, and so does a plain name followed by ( that is no PAD function - ucfirst('word'); the setting $padPhpFunctions decides which PHP functions a template may call. A piped value goes in as the only argument - 'abc' | php:strrev - and into a call with arguments where @ stands: $n | php:number_format(@, 2).

HTML RESULT
{echo php:strlen('abc')}
3
{echo php:str_repeat('ab', 3)}
ababab
{echo 'abc' | php:strrev}
cba
{echo php:number_format(1234567.891, 2)}
1,234,567.89
{echo 1234.5 | php:number_format(@, 2, ',', '.')}
1.234,50
{echo php:str_pad('7', 3, '0', STR_PAD_LEFT)}
007
{echo php:max(3, 9, 4)} {echo php:min([3, 9, 4])}
9 3
{echo ucfirst('word')} {echo 'word' | ucfirst}
Word Word

A file in the application's _functions/ directory is a pipe function as well, the piped value its $value - this manual has _functions/stars.php. function: asks for whatever function kind the name is, app: for the application's own (see extending):

PHP
HTML
fragments/expressions_33.pad
Result
<p>{echo 'well done' | stars}</p>
<p>{echo 'well done' | function:stars}</p>
<p>{echo 'well done' | upper | stars | php:strlen}</p>

*** well done ***

*** well done ***

17

Pipes

A | hands the value on its left to what follows; the parts run left to right, each over what the one before answered:

HTML RESULT
{echo '  ada LOVELACE ' | trim | lower | ucwords}
Ada Lovelace
{echo 'user@example.com' | after('@') | upper}
EXAMPLE.COM
{echo 'pad' | upper | . '!' | php:str_repeat(@, 2)}
PAD!PAD!
{echo 12 | * 1.21 | %.2f | . ' EUR'}
14.52 EUR

A part after a pipe is an expression of its own, and the piped value fills the place of the operand it lacks. An operator that opens the part takes it as its left side - | + 1, | . ' EUR', | ?? 'none'. A space between such an operator and its operand is required: | + 1, never | +1.

HTML RESULT
{echo 5 | + 1}
6
{echo 5 | * 2 | - 1}
9
{echo 50 | * 4 | / 8}
25
{echo 7 | % 3}
1
{echo 2 | ** 8}
256
{echo 'Hello' | . ' world'}
Hello world

HTML RESULT
[{echo 10 | gt 5}]
[1]
[{echo 'abc' | eq 'abc'}]
[1]
[{echo 7 | in (1, 7)}]
[1]
[{echo 5 | * 2 gt 9}]
[1]
{echo '' | ?? 'empty'}
empty
{echo 3 | - 1 . ' left'}
2 left

@ stands for the piped value wherever the part needs it, as often as it needs it:

HTML RESULT
{echo 4 | @ * @}
16
{echo 4 | @ + @ * 2}
12
{echo 'pad' | '"' . @ . '"'}
"pad"
{echo 'pad' | @ . ' ' . upper(@)}
pad PAD
{echo 5 | @ gt 3 ? 'big' : 'small'}
big

A part that is a printf format - it starts with % and is one conversion - formats the value as PHP's sprintf does. A % that is no format is the remainder: | % 3.

HTML RESULT
{echo 3.14159 | %.2f}
3.14
{echo 5 | %05.2f}
05.00
{echo 42 | %05d}
00042
{echo 42 | %+d}
+42
{echo 255 | %x} {echo 255 | %X}
ff FF
{echo 1234.5 | %e}
1.234500e+3
{echo 0.256 | * 100 | %.1f | . '%'}
25.6%

A pipe inside round or square brackets belongs to that group: ($n | + 1) * 2 adds before it multiplies, upper($s | trim) trims the argument, and in a list or an argument list the pipe works on its own item. Outside brackets a pipe always ends the whole expression before it.

HTML RESULT
{echo (5 | + 1) * 2}
12
{echo upper('  pad  ' | trim)}
PAD
{echo php:max(1, 5 | + 1)}
6
{echo php:implode('-', [5 | + 1, 2 | * 10])}
6-20
{echo 'x' . ('pad' | upper) . 'x'}
xPADx

A field tag with a pipe, {$x | upper}, writes its answer through the sanitize chain, like every field tag. {echo} writes it raw - the right one for HTML the page made itself, the wrong one for what a visitor typed:

PHP
HTML
fragments/expressions_40.pad
Result
{set $html = '<i>pad</i>'}
<p>{$html | upper}</p>
<p>{echo $html | upper}</p>

<I>PAD</I>

PAD

Dates

date(format, modifier) reads a Unix timestamp, a date in text or a date object the page's PHP made; the modifier is what PHP's strtotime reads, such as '+1 week'. Written as a call it keeps PHP's order, the format first: date('Y', $created). Dates written YYYY-MM-DD compare correctly as plain text. now is the current time, and the locale page has localDate:

HTML RESULT
{echo '2026-10-18' | date('d M Y')}
18 Oct 2026
{echo '2026-10-18' | date('l')}
Sunday
{echo '2026-10-18' | date('Y-m-d', '+1 week')}
2026-10-25
{echo '2026-10-18 14:30' | date('H:i')}
14:30
{echo date('Y', 1000000000)}
2001
[{echo '2026-10-18' gt '2026-01-01'}]
[1]

Lists meeting operators

An arithmetic operator or a comparison that meets a list works with the sum of its numbers. A list holding more than numbers, or met by the dot, is an error. A field that holds a list is handed over with the array: prefix:

HTML RESULT
{echo [1, 2, 3] + 1}
7
{echo [1, 2] + [3, 4]}
10
{echo [1, 2] * 2}
6
[{echo [1, 2, 3] eq 6}]
[1]
{echo php:count([1, 2, 3])}
3
{echo [] ?? 'an empty list'}
an empty list

PHP
HTML
fragments/expressions_43.pad
Result
{set $scores = [7, 9, 4, 10]}
<p>Sum {echo array:scores + 0}, count {echo php:count(array:scores)}</p>
<p>Average {echo (array:scores + 0) / php:count(array:scores)}</p>
<p>Best {echo php:max(array:scores)}, worst {echo php:min(array:scores)}</p>
<p>{echo php:implode(', ', array:scores)}</p>

Sum 30, count 4

Average 7.5

Best 10, worst 4

7, 9, 4, 10

Numbers at the edges

What is not a number - a NAN such as php:sqrt(-1) - is empty. % works on whole numbers, as PHP's does; php:fmod keeps the fraction. Decimals are written with PHP's precision, so 0.1 + 0.2 comes out as 0.3:

HTML RESULT
[{echo php:sqrt(-1)}]
[]
{echo php:sqrt(-1) ?? 'not a number'}
not a number
{echo php:intdiv(7, 2)} {echo 7 % 2}
3 1
{echo 7.5 % 2} {echo php:fmod(7.5, 2)}
1 1.5
{echo 0.1 + 0.2}
0.3

Values are text

An expression's answer is a value, and a value never becomes template code: a field that holds braces - from a database, a form, a file - is written as the text it is. To run it as PAD on purpose, say so where it is used, with the code pipe (or sandbox, which runs it in an isolated pass):

PHP
fragments/expressions_45.php
HTML
fragments/expressions_45.pad
Result
<?php

// Text that came from somewhere else - a database, a form, a file.

$snippet = '{echo 6 * 7}';

?>
<p>{$snippet}</p>
<p>{echo $snippet . ' is text'}</p>
<p>{echo $snippet | code}</p>

{echo 6 * 7}

{echo 6 * 7} is text

42

When an expression is wrong

Under the strict syntax check - $padCheckSyntax, on by default - a mistake in an expression is an error that names the fault and its place, and the page answers with the error report instead of guessing. The report shows the template's file, line and column with the lines around it (see errors in the template). What some mistakes are told:

written the error says
{echo (1 + 2} the ( opened at position 1 is never closed
{echo 'abc} the string opened with ' at position 1 is never closed
{echo $name | uppr} there is no pipe function named 'uppr'
{echo nofunc(1)} there is no function named 'nofunc'
{if $typo eq 1} there is no field named '$typo'
{echo $items + 1} the field '$items' is a list, not a value
{if $n eq} the operator 'eq' has nothing on its right
{echo $n + * 3} the operators '+' and '*' stand side by side - a value is missing between them
{if $n === 5} '===' is no operator - eq (==) compares two values
{echo $n | +1} a pipe operator needs a space before its operand - write '| + 1', not '| +1'
{echo 5 & 3} '&' is no operator or value
{if $n not 5} a 'not' after a value denies a test - write $a not in (...), not like, not matches or not range, or put it in front: not $a eq $b
{echo 7 / 0} a division by zero in /
{echo 'a' * 2} 'a' is not a number for *
{echo $n ? 'a'} the ? of an inline ternary has no : - write cond ? then : else
{echo [ 'a' => ]} the key 'a' of a list has no value after its =>
{echo replace('abc')} the function 'replace' wants 2 parameters after the value it works on - written as a call its first argument is that value: replace($value, ...)
{echo php:str_repeat('x', -1)} the PHP function 'str_repeat' refused its arguments: str_repeat(): Argument #2 ($times) must be greater than or equal to 0
{echo 'a\qb'} Unsupported \ char

The lenient walk

With $padCheckSyntax = FALSE in _config/config.php nothing is reported and the evaluator answers what it can: a text that is no number counts as 0 ('a' + 1 is 1), a missing field is empty ($nope + 1 is 1), an unknown escape loses its backslash, and an expression that cannot be worked out at all - a bracket never closed, a division by zero, a PHP function refusing its arguments - answers nothing. That suits old templates; a new one is better off with the check, which finds the typo the lenient walk hides.

See also