Scripting Syntax
Expression Lab introduces a declarative, functional scripting dialect built into its formal Abstract Syntax Tree (AST) parser. While remaining expression-oriented, this scripting layer allows you to compose multi-step execution workflows, declare reusable lambda closures, manage expression-scoped state, and perform higher-order transformations on datasets within the boundaries of registered language forms.
Language Paradigm & Design Tenets
The Expression Lab scripting dialect is designed around three foundational principles:
1. Expression-Oriented Nature
Everything in Expression Lab is an expression that evaluates to a value. There are no imperative statements or control keywords (such as while, for, goto, or $x = 10). Multi-step logic is expressed via sequential and functional composition.
2. Syntactic Dichotomy: [...] vs (...)
To ensure predictable parsing and unambiguous grammatical structure, Expression Lab maintains a clean separation between engine-level constructs and host PHP functions:
| Syntax Form | Target | Description | Example |
|---|---|---|---|
| Special Forms | <Keyword>[ ... ] | Engine-level syntax for sequencing, state binding, closures, and collection processing. | prog[...], set[...], fn[...], map[...] |
| Host Functions | <Identifier>( ... ) | Standard PHP and WordPress functions registered in the execution context with eager parameter evaluation. | count(...), strtoupper(...), Database.mirror(...) |
3. Execution Controls & Operational Limits
The scripting engine operates under defined evaluation constraints:
- Lazy Evaluation: Lambda closures created with
fn[...]compile their body into an AST subtree that is evaluated only when the closure is invoked. - Operation Counter (Gas Limit): The engine increments an operation counter during iterations and AST traversals to enforce the configured execution time limit (
EXPRESSION_LAB_MAX_EXECUTION_LIMIT). - Closure Type Enforcement: Higher-order stream forms (
map,filter,reduce) require PHP\Closureinstances created viafn[...], preventing arbitrary callable string evaluation.
Special Form Keyword Registry
Expression Lab reserves 11 core Special Form keywords:
| Keyword | Category | Signature | Description |
|---|---|---|---|
prog | Sequencing | prog[<expr1>, ..., <exprN>] : mixed | Evaluates expressions sequentially from left to right and returns the result of <exprN>. |
set | State Binding | set[<name:string>, <value:mixed>] : mixed | Stores <value> in the expression variable store under <name> and returns the assigned <value>. |
var | State Access | var[<name:string>] : mixed | Retrieves the stored value of an expression variable. |
isset | State Inspection | isset[<name:string>] : bool | Returns true if <name> exists in the expression variable store and is not null. |
unset | State Mutation | unset[<name:string>] : bool | Removes <name> from the expression variable store and returns true. |
fn | Closures | fn[[<param1>, ...], <body:expr>] : \Closure | Defines a first-class anonymous closure with lazy evaluation. |
args | Lexical Scope | args[<name:string>] : mixed | Accesses a parameter value inside the active fn closure execution frame. |
map | Stream Transform | map[<iterable>, <fn>] : array | Transforms each element of <iterable> through <fn>. |
filter | Stream Filter | filter[<iterable>, <fn>, <mode:int>?] : array | Filters elements of <iterable> matching predicate <fn>. |
reduce | Stream Fold | reduce[<iterable>, <fn>, <initial:mixed>?] : mixed | Folds <iterable> into an accumulated result using <fn>. |
show | Visualization | show[<expr>] : mixed | Forces rendering of any silenced data visualizations generated by <expr>. |
Sequential Execution: prog[]
The prog[...] special form (inspired by Lisp's progn function) executes multiple expressions in sequence, returning the result of the final expression:
prog[
set['post_type', 'page'],
set['pages', Posts.where('post_type', var['post_type']).get()],
count(var['pages'])
]
Behavior & Rules
- Expressions are evaluated from left to right.
- If
prog[]is empty, it returnsnull. - Trailing commas are fully supported:
prog[ set['a', 1], set['b', 2], ]. - Arbitrary nesting is supported:
progblocks can appear inside expressions, conditionals, or closure bodies.
State & Expression Variables
Expression variables allow you to store intermediate calculations, models, and query sets across multiple steps within a script or prog[...] block.
Variables declared with set[...] reside exclusively in the evaluation engine's in-memory storage (VariableStore) for the lifecycle of the expression. They do not touch PHP's superglobal $_SESSION, do not set cookies, and do not persist across HTTP requests or in the browser once evaluation finishes.
Declaring variables: set[]
Binds a value to the expression variable store. Returns the assigned value:
set['threshold', 100]
Reading variables: var[]
Retrieves a stored variable from the expression context:
var['threshold'] * 1.25
You can also read expression variables using dot syntax on the var object (e.g., var.threshold). Both var['threshold'] and var.threshold access the same expression variable store.
Nested Dot-Separated Paths
set and var support nested dot-separated paths to organize complex state in structured maps:
prog[
set['app.settings.theme', 'dark'],
set['app.settings.per_page', 25],
var['app.settings.per_page']
]
Checking variable existence: isset[]
Checks if an expression variable or nested path exists and is not null:
isset['app.settings.theme'] ? var['app.settings.theme'] : 'light'
Removing variables: unset[]
Removes a variable from the expression variable store, freeing allocated resources. Returns true:
unset['pages']
First-Class Closures & Lexical Scope
Anonymous functions (lambdas) are defined using the fn special form.
Creating closures: fn[]
Creates a native PHP \Closure with lazy evaluation. The body expression is compiled into an AST subtree and evaluated only upon invocation:
/* Lambda accepting one parameter */
fn[['x'], args['x'] * 2]
/* Lambda accepting multiple parameters */
fn[['first', 'last'], args['first'] ~ ' ' ~ args['last']]
Accessing parameters: args[]
Within the body of an fn closure, parameters are accessed using args['<param_name>']. Outside of a closure execution frame, args[...] safely returns null.
set[...]andvar[...]operate at Global Expression Scope: Any variable assigned withset[...]—even from inside a nestedprog[...]block or anfn[...]closure body—modifies the shared state inVariableStorethroughout the evaluation of the expression.args[...]has Lexical Scope: Parameters inargs[...]exist only for the duration of that specificfn[...]closure call and do not leak into other closures or the global expression store.
/* Returns: ["Hello, ALICE!", "Hello, BOB!"] */
prog[
set['greet', fn[['name'], 'Hello, ' ~ strtoupper(args['name']) ~ '!']],
/* Closures stored in variables can be passed to higher-order functions */
map[
['alice', 'bob'],
var['greet']
]
]
Functional Stream Processing
Expression Lab provides built-in higher-order Special Forms for processing arrays, query collections, and iterables.
Mapping: map[]
Applies an fn closure to every element in an iterable and returns the resulting transformed list:
/* Returns: ["post_id_10", "post_id_25", "post_id_42"] */
prog[
set['raw_ids', [10, 25, 42]],
map[
var['raw_ids'],
fn[['id'], 'post_id_' ~ args['id']]
]
]
Filtering: filter[]
Filters elements of an iterable, keeping only items where the predicate closure evaluates to a truthy value:
/* Returns: [25, 42] */
prog[
set['raw_ids', [10, 25, 42]],
filter[
var['raw_ids'],
fn[['id'], args['id'] > 20]
]
]
Filtering modes
filter supports an optional third argument for filtering by key or both item and key:
| Mode Value | Behavior | Closure Signature | Index Handling |
|---|---|---|---|
0 (Default) | Filter by value | fn[['item'], ...] | Sequential lists are re-indexed ([0, 1, ...]). Associative keys are preserved. |
1 (USE_BOTH) | Filter by value and key | fn[['item', 'key'], ...] | Preserves original keys. |
2 (USE_KEY) | Filter by key | fn[['key'], ...] | Preserves original keys. |
/* Returns: { "wp_core": 1, "plugin_a": 2 } */
filter[
{ 'wp_core': 1, 'plugin_a': 2, 'temp_cache': 3 },
fn[['k'], not (args['k'] matches '/^temp_/')],
2
]
Reducing: reduce[]
Folds an iterable into a single scalar or composite value. The reducer closure receives (accumulator, current_item):
/* Returns: 100 */
prog[
set['values', [10, 20, 30, 40]],
reduce[
var['values'],
fn[['acc', 'n'], args['acc'] + args['n']],
0
]
]
Console Visualizations: show[]
When evaluating composite prog[...] blocks, intermediate visualizations (such as database query tables or diagnostic charts) are silenced by default so only the final output is displayed.
Use show[<expr>] to force the immediate rendering of any visualization produced by an expression while returning its value:
prog[
/* Force the database query visualization table to render in the console */
show[Database.mirror('posts', { post_status: 'publish' }).fetch()],
/* Continue with further calculations using the optimized count API */
set['total', Database.count('posts', { post_status: 'publish' })],
'Audit complete. Total posts: ' ~ var['total']
]
Practical Scripting Recipes
Example 1: Post Status Distribution Audit
Calculate total posts per status and format the summary map:
prog[
set['posts', Posts.limit(50).get()],
set['published', filter[var['posts'], fn[['p'], args['p'].post_status == 'publish']]],
set['drafts', filter[var['posts'], fn[['p'], args['p'].post_status == 'draft']]],
{
total_audited: count(var['posts']),
published_count: count(var['published']),
draft_count: count(var['drafts'])
}
]
Example 2: Multi-Post Content Extraction Pipeline
Fetch recent published posts, transform each into a structured summary map, and filter for posts with written content:
prog[
set['posts', Posts.where('post_status', 'publish').limit(10).get()],
set['summaries', map[
var['posts'],
fn[['p'], {
id: args['p'].ID,
title: args['p'].post_title,
slug: args['p'].post_name,
word_count: count(explode(' ', args['p'].post_content)),
has_comments: intval(args['p'].comment_count) > 0
}]
]],
filter[var['summaries'], fn[['item'], args['item']['word_count'] > 0]]
]
Next Steps
- Discover available database and query tools in the Database API.
- Explore built-in helpers and constants in Functions and Constants.
- Review execution limits and environment controls in Configuration.