pad

PAD Evaluation Subsystem

This document explains how the PAD expression evaluator works.

Overview

The evaluator turns the text of an expression - {echo $price * 1.1 | round}, the condition of an {if}, a where= option - into a value. It works in four steps:

text ── validate ── scan ── parse ── evaluate ── value
        (shape)     tokens   tree     with the value piped in

The tokens of a clean expression and its tree are kept for the request, so a loop that evaluates the same text once per row scans and parses it once; each evaluation then reads the expression’s fields and walks the tree.

File Does
lib/eval/eval.php padEval, padEvalBool, padEvalNumber, padEvalTrace - the public face
eval/eval.php The entry: fast paths, then validate, scan, check, parse, evaluate
lib/eval/validate.php The checks before parsing: brackets, strings, words, operands
lib/eval/scan.php The scanner: the text as tokens
lib/eval/tree.php The parser: the tokens as a tree
lib/eval/run.php The evaluator: the tree as a value, the operators, calls and lookups
lib/eval/const.php The operator tables and binding powers
eval/parms/<kind>.php A call with arguments - a built-in, an application’s function, php:, a tag …
eval/single/<kind>.php A lookup without arguments - field:, data:, array: …
eval/fast.php An expression that is only the name of a built-in pipe function

Entry Point

padEval ( $eval, $value, $pipe ) reaches eval/eval.php through the try guard (try/eval/eval.php). $value is the value piped in; $pipe says the text is a pipe body - what {$x | ...} or a tag pipe applies - whose head is a function as well.

// A plain name of a built-in pipe function: eval/fast.php calls it directly
// A number, a quoted string without quote or backslash, a plain $field: answered at once

$tokens = padEvalTokens ( $eval );                  // validate and scan, kept per request
if ( $tokens === FALSE ) return '';                 // the validation named the fault
if ( ! padEvalCheckPipes ( $tokens, $eval, $pipe ) ) return '';
$tree = padEvalTreeGet ( $tokens, $eval, $pipe );   // parse, kept per request
return padEvalSegments ( $tree, $value );           // the segments, each leaf read where reached

Step 1: Validation (lib/eval/validate.php)

Before anything is scanned, padEvalValidate checks that every string is closed and that ( ) and [ ] are balanced and nested, naming the position in characters. After scanning, padEvalCheckPipes judges the tokens: a word after a | must name a function, an operator needs its operands where no piped value stands in, ===, a lone :, a => outside a list, punctuation words, a not that denies no test. A text that passed is not judged again in the request when its words alone decide it.

Step 2: Scanning (lib/eval/scan.php)

padEvalScan splits off the pipe segments that are a printf format (%05.2f) and padEvalScanText reads the rest, each kind of token with a function of its own. A token is [0] its text or value and [1] its kind:

Kind Meaning Example
VAL A number, or a quoted string ('lit' => TRUE) 'hello', 123, 1.5E-10, 4..10
OPR An operator, [2] as written when it is a symbol +, **, LT (from <), AND (from &&), NEG
$ A field $name, $user.name, $items@2
$$n The field another field names $$pick
prop A property and its target first@items, current@
& A property of the tag &current
# An option of the tag #title
$$ The @ placeholder - the value piped in @, $$
hex The digits of a hex value 0x41
other A bare word - an operator word, a function, a reference, a constant and, upper, php:strlen, TRUE
pipe open close a-open a-close arrow \| ( ) [ ] =>  
% A whole pipe segment that is a printf format %05.2f

Whitespace and commas end a token and are dropped; the token after a comma inside brackets, and after a =>, carries 'arg' => TRUE.

Step 3: Parsing (lib/eval/tree.php)

The grammar, weakest first:

expression  segment ( | segment )*        each segment's answer is the input of the next
segment     a printf format, or a range
range       values ? range? : range       the inline ternary; its condition is one value
            values                        values side by side are joined as text
value       operators by binding power, prefix operators, calls
primary     a literal, $field, @, ( arguments ), [ list ], a call, a lookup

What a bare word is depends on where the expression stands: padEvalWords asks padTypeFunction (or reads the type: prefix written) whether it names a function, a tag, a field, a data store … A word that names nothing is a constant (padConstant). A built-in function is its file’s name in its own case - upper, afterLast - also where the file system ignores case (padFunctionBuiltIn): Upper is no pipe function, and DATE is PHP’s. A tree is kept per text, per pipe body, per fallback after it and per what its words name.

Binding power

The operators with a value on both sides, strongest first - those of one power are taken left to right, except **, which binds right to left (2 ** 3 ** 2 is 2 ** 9):

Power Operators
130 a call after a value - $s upper - takes that value as its input
120 **
110 the prefix !, -, + - -$b ** 2 is -($b ** 2), 2 ** -$b is 2 ** (-$b)
100 * / %
90 + -
80 .
75 the tests in, like, matches, range, between, contains written after a value
70 lt le gt ge (< <= > >=)
60 eq ne (= == != <>)
50 the prefix not - not $a eq $b denies the comparison
40 and (&&)
30 xor
20 or (\|\|)
10 ??

Weaker than all of them is the inline ternary cond ? then : else: the first ? of a range splits it at its own : (a nested ? takes the next one), so a ? b : c ? d : e is a ? b : (c ? d : e). Nothing before the ? means the piped value is the condition: {$n | ? 'some' : 'none'}. With no then branch - $title ?: 'Untitled' - a true condition is the answer itself.

What an operator lacks, the piped value stands in for

Calls and tests

Brackets and lists

An argument of ( ) and an item of [ ] is a range of its own: a comma ends it, a => ends the key of a list item. With a | of its own it is a pipeline whose head takes the segment’s value: ($n | + 1) * 2, upper($s | trim), [$n | + 1, 2]; an @ after that pipe is the value piped into its part. Values left side by side in ( ) stand side by side where the brackets stood; after a function they are its arguments.

A [ ] list takes keys written with => - [ 'title' => 'Minutes', 'n' => $n + 1 ] - mixed with plain items as PHP mixes them (padEvalArrayItems).

Nodes

A node is a list, its kind first:

Kind Holds
lit a value written in the template, and whether it was a quoted string
leaf a field, an option, a property, a hex value, a constant - read where the walk reaches it
me the value piped in - @
fmt a printf format
un / bin an operator and its operand(s), and how it was written
tern condition, then (NULL for ?:), else (NULL when empty)
join values side by side
list the entries of a [ ] list
call kind, name, input, arguments, brackets, whether a built-in reads its first argument
look kind, name, and whether it may be missing - its value going into a fallback
pipe the parts of a pipeline inside brackets
seq nodes read and left out, then a node
err / none / more a fault reported where it is evaluated

Step 4: Evaluation (lib/eval/run.php)

padEvalSegments evaluates each segment with the previous one’s answer as its input. padEvalNode walks the tree and reads a leaf where it gets to it (padEvalLeaf): the branch a ternary does not take is left alone, fields in it included, and so is the right side of and, or and ?? when the left one decides - 0 and tick() calls nothing, 'ok' ?? 7 / 0 is ok. Both sides of the other operators are evaluated, the left one first.

Operators (padEvalApply)

Left Right Function
- any padEvalUnary - ! and not answer '1' or '', the signs a number
scalar scalar padEvalScalar
array array padEvalArrays
array / scalar scalar / array padEvalMixed

Calls and lookups

A call goes to padEvalCall ( $kind, $name, $value, $parm ), which includes eval/parms/$kind.php with $value, $parm and $count in scope; a lookup goes to padEvalLook, which includes eval/single/$kind.php with $name.

Single kinds (single/)

Kind File Returns
field single/field.php padFieldValue($name)
data single/data.php $GLOBALS['padDataStore'][$name]
content single/content.php $GLOBALS['padContentStore'][$name]
property single/property.php padTagValue($name, 1)
parm single/parm.php padOptValue($name, 1)
array single/array.php padArrayValue($name, TRUE)
constant single/constant.php constant($name)
level single/level.php padGetLevelArray($name)
bool single/bool.php $padBoolStore[$name], FALSE when not set
flag single/flag.php $padBoolStore[$name] - an error when not set
pull single/pull.php The stored sequence $pqStore[$name]
object single/object.php An application global as an array (padToArray)
local single/local.php A _data/ file’s data
include single/include.php An _include/ snippet, rendered

Kinds with parameters (parms/)

Kind File Description
tag parms/tag.php Tag as function: padTagAsFunction($name, $value, $parm)
pad parms/pad.php A built-in pipe function, functions/$name.php, after its parameter count is checked
php parms/php.php A PHP function $padPhpFunctions allows
app parms/app.php An application’s _functions/ file
function parms/function.php Whichever function kind the name is
action parms/action.php A sequence action
script parms/script.php A _scripts/ script
sequence parms/sequence.php Sequence generator

Fast Paths

An expression that is only the name of a built-in pipe function - one the application has no function of that name in place of - goes to eval/fast.php, which calls the function through eval/parms/pad.php. A number, a quoted string with no quote or backslash in it, and a plain $field are answered in eval/eval.php before any scanning - outside a pipe body. None of them is taken while the trace records.

Tracing

With $padEvalTrace on, padEvalTrace records the expression, the tokens and the tree of the last evaluation in $_eval - begun anew by each one, so an expression the validation refused leaves its text alone there.

Example

Expression: $price * 1.1 | round

  1. Scan: [price, $] [*, OPR] [1.1, VAL] [|, pipe] [round, other]
  2. Words: round names the built-in function - kind pad
  3. Parse: two segments - bin(*, leaf $price, lit 1.1) and call(pad, round)
  4. Evaluate: the leaf $price is read (123.45); the first segment is 135.795, which is the input of the second: round answers 136

Boolean Handling

The comparison and logical operators answer 1 for true and '' for false. A value is tested for truth as PHP tests it:

Error Handling

Under $padCheckSyntax (the default) errors are reported via padError(). Before anything runs, lib/eval/validate.php reads the expression and names the fault and its position - Expression error: ...:

While it runs:

With the check off nothing is reported, and the evaluator answers what it can: a value 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 ('a\qb' is aqb), a ternary without its : keeps what it has (1 ? 'a' is 1a), and an expression that cannot be reduced - an unclosed bracket, a division by zero, a PHP function refusing its arguments, a mistake the check before parsing names - answers ''.