Skip to main content
Version: 0.2.0-alpha

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 FormTargetDescriptionExample
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 \Closure instances created via fn[...], preventing arbitrary callable string evaluation.

Special Form Keyword Registry​

Expression Lab reserves 11 core Special Form keywords:

KeywordCategorySignatureDescription
progSequencingprog[<expr1>, ..., <exprN>] : mixedEvaluates expressions sequentially from left to right and returns the result of <exprN>.
setState Bindingset[<name:string>, <value:mixed>] : mixedStores <value> in the expression variable store under <name> and returns the assigned <value>.
varState Accessvar[<name:string>] : mixedRetrieves the stored value of an expression variable.
issetState Inspectionisset[<name:string>] : boolReturns true if <name> exists in the expression variable store and is not null.
unsetState Mutationunset[<name:string>] : boolRemoves <name> from the expression variable store and returns true.
fnClosuresfn[[<param1>, ...], <body:expr>] : \ClosureDefines a first-class anonymous closure with lazy evaluation.
argsLexical Scopeargs[<name:string>] : mixedAccesses a parameter value inside the active fn closure execution frame.
mapStream Transformmap[<iterable>, <fn>] : arrayTransforms each element of <iterable> through <fn>.
filterStream Filterfilter[<iterable>, <fn>, <mode:int>?] : arrayFilters elements of <iterable> matching predicate <fn>.
reduceStream Foldreduce[<iterable>, <fn>, <initial:mixed>?] : mixedFolds <iterable> into an accumulated result using <fn>.
showVisualizationshow[<expr>] : mixedForces 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 returns null.
  • Trailing commas are fully supported: prog[ set['a', 1], set['b', 2], ].
  • Arbitrary nesting is supported: prog blocks 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.

In-Memory Scope vs. PHP Sessions

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
Dot Property Notation

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.

Variable Scope: Global Expression State vs Lexical Scope
  • set[...] and var[...] operate at Global Expression Scope: Any variable assigned with set[...]—even from inside a nested prog[...] block or an fn[...] closure body—modifies the shared state in VariableStore throughout the evaluation of the expression.
  • args[...] has Lexical Scope: Parameters in args[...] exist only for the duration of that specific fn[...] 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 ValueBehaviorClosure SignatureIndex Handling
0 (Default)Filter by valuefn[['item'], ...]Sequential lists are re-indexed ([0, 1, ...]). Associative keys are preserved.
1 (USE_BOTH)Filter by value and keyfn[['item', 'key'], ...]Preserves original keys.
2 (USE_KEY)Filter by keyfn[['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​