Parser¶
Audience: Developers integrating ExpresZo into their projects.
The Parser class is the main entry point for parsing and evaluating expressions.
Quick Start¶
import { Parser } from '@pro-fa/expreszo';
// Create a parser instance
const parser = new Parser();
// Or parse once and evaluate multiple times
const expr = parser.parse('2 * x + 1');
expr.evaluate({ x: 3 }); // 7
expr.evaluate({ x: 5 }); // 11
Constructor¶
Options¶
| Option | Type | Description |
|---|---|---|
operators |
object |
Enable/disable specific operators (see table below) |
Operator Configuration¶
All operators default to true (enabled) except where noted:
| Option Key | Default | Operators Affected |
|---|---|---|
add |
true |
+ (addition) |
subtract |
true |
- (subtraction) |
multiply |
true |
* (multiplication) |
divide |
true |
/ (division) |
remainder |
true |
% (modulo) |
power |
true |
^ (exponentiation) |
factorial |
true |
! (factorial) |
concatenate |
true |
\| (array/string concatenation) |
conditional |
true |
? : (ternary) and ?? (coalesce) |
logical |
true |
and, or, not |
comparison |
true |
==, !=, <, >, <=, >= |
in |
false |
in, not in |
assignment |
false |
= (variable assignment) |
fndef |
true |
Function definitions and arrow functions |
conversion |
false |
as (type conversion) |
Example: Restricted Parser
const parser = new Parser({
operators: {
// Disable logical and comparison
logical: false,
comparison: false,
// Enable 'in' operator
in: true,
// Enable assignment
assignment: true
}
});
Instance Methods¶
parse(expression: string)¶
Convert an expression string into an Expression object.
Returns an Expression object with methods like evaluate(), simplify(), variables(), etc.
evaluate(expression: string, variables?: object, resolver?: VariableResolver)¶
Parse and immediately evaluate an expression.
The optional resolver callback is a per-call custom variable resolver. It is tried before parser.resolve when a variable is not found in variables.
parser.evaluate('$a + $b', {}, (name) =>
name.startsWith('$') ? { value: lookup(name.substring(1)) } : undefined
); // per-call resolver; parser.resolve is not mutated
evaluateObject(object, variables?, resolver?)¶
Recursively resolve every property of a plain object against a set of variables. Useful when a payload mixes literal values with expression strings.
const parser = new Parser();
parser.evaluateObject(
{
name: 'user.name',
ageNextYear: 'user.age + 1',
label: 'plain string',
scores: ['user.score', 'user.score * 2']
},
{ user: { name: 'Jane', age: 29, score: 10 } }
);
// {
// name: 'Jane',
// ageNextYear: 30,
// label: 'plain string',
// scores: [10, 20]
// }
String handling rules
Each string leaf is treated as a possible expression:
- The string is parsed. If parsing fails (
ParseError), the original string is returned unchanged. - If parsing succeeds but the expression references no variables (e.g.
"42","2 + 3"), the original string is returned unchanged. This preserves literal text and keeps types stable. - The expression is evaluated with
variablesand the optional per-callresolver. If evaluation raises aVariableError(i.e. the string looked like a bareword such as"literal"but no matching variable exists), the original string is returned unchanged. All other evaluation errors propagate.
Nested arrays and objects recurse. All other primitives (numbers, booleans, null, undefined) pass through untouched.
evaluateArray(array, variables?, resolver?)¶
Array counterpart to evaluateObject. Each element is resolved with the same rules: strings that reference variables are evaluated, literal strings stay as-is, nested structures recurse, primitives pass through.
parser.evaluateArray(
['x', 'x * 2', 'literal', { nested: 'x + 1' }],
{ x: 21 }
);
// [21, 42, 'literal', { nested: 22 }]
Instance Properties¶
parser.functions¶
An object containing all available functions. You can add, modify, or remove functions:
const parser = new Parser();
// Add a custom function
parser.functions.double = (x) => x * 2;
// Add a function that returns a Promise (makes evaluate async)
parser.functions.fetchValue = async (id) => {
const response = await fetch(`/api/values/${id}`);
return response.json();
};
// Remove a built-in function
delete parser.functions.random;
// Use custom function
parser.evaluate('double(5)'); // 10
// Async evaluation
await parser.evaluate('fetchValue(123) * 2');
parser.numericConstants¶
An object containing numeric constants. You can add, modify, or remove constants:
const parser = new Parser();
// Add custom constants
parser.numericConstants.TAU = Math.PI * 2;
parser.numericConstants.GOLDEN_RATIO = 1.618033988749;
// Use in expressions
parser.evaluate('TAU'); // 6.283185307179586
parser.evaluate('2 * PI'); // 6.283185307179586 (PI is built-in)
// Remove all numeric constants
parser.numericConstants = {};
Built-in Numeric Constants:
| Constant | Value |
|---|---|
E |
Math.E (~2.718) |
PI |
Math.PI (~3.14159) |
Infinity |
Infinity |
NaN |
NaN |
parser.buildInLiterals¶
An object containing built-in literal values:
| Literal | Value |
|---|---|
true |
true |
false |
false |
null |
null |
parser.resolve¶
A callback for custom variable name resolution. Called when a variable name is not found in the provided variables object.
const parser = new Parser();
const data = { variables: { a: 5, b: 10 } };
// Alias resolution: $v becomes 'variables'
parser.resolve = (name) => {
if (name === '$v') {
return { alias: 'variables' };
}
return undefined;
};
parser.evaluate('$v.a + $v.b', data); // 15
// Value resolution: return the value directly
parser.resolve = (name) => {
if (name.startsWith('$')) {
const key = name.substring(1);
return { value: data.variables[key] };
}
return undefined;
};
parser.evaluate('$a + $b', {}); // 15
The resolve callback should return:
- { alias: string } - to redirect to another variable name
- { value: any } - to return a value directly
- undefined - to use default behavior (throws error for unknown variables)
For cases where different evaluations of the same parsed expression need different resolution logic, prefer passing a resolver directly to Expression.evaluate(values, resolver) or parser.evaluate(expr, values, resolver) instead of mutating parser.resolve. See Per-Expression Variable Resolver.
Using plugins¶
parser.use(plugin) is the friendly way to register a bundle of operators, functions, and constants on an existing parser. Companion packages export their entries as a single Plugin object, so wiring one up takes a single call:
import { defineParser, fullParser } from '@pro-fa/expreszo';
import { dateTimePlugin } from '@pro-fa/expreszo-datetime';
const parser = defineParser({ ...fullParser })
.use(dateTimePlugin);
parser.parse("format(addDuration(now(), 7, 'days'), 'yyyy-MM-dd')").evaluate();
use() returns the parser, so chained calls work too:
Plugin interface¶
interface Plugin {
readonly name: string; // shown in collision errors
readonly version?: string; // informational
readonly operators?: readonly OperatorDescriptor[];
readonly functions?: readonly FunctionDescriptor[];
readonly constants?: Readonly<Record<string, Value>>;
}
Authoring a plugin is the same as authoring a preset — assemble FunctionDescriptor / OperatorDescriptor arrays — plus a name. See @pro-fa/expreszo-datetime's plugin.ts for a worked example.
Collision handling¶
By default, use() throws if a plugin tries to register an operator, function, or constant that's already on the parser:
parser.use(somePluginWithMax);
// Error: Plugin 'somePluginWithMax': function 'max' already registered.
// Pass { override: true } to replace.
Pass { override: true } to silently replace existing entries:
The default-throw behaviour is intentional — it surfaces unintentional shadowing of built-ins (often a sign you want a different function name).
Spread-into-defineParser form¶
If you prefer the spread-composition style, every companion package also exports a ParserPreset:
import { defineParser, fullParser } from '@pro-fa/expreszo';
import { withDateTime } from '@pro-fa/expreszo-datetime';
const parser = defineParser({
operators: [...fullParser.operators],
functions: [...fullParser.functions, ...withDateTime.functions]
});
Both produce identical parsers; pick whichever matches the rest of your codebase.
Advanced Configuration¶
Type Conversion (as operator)¶
The as operator is disabled by default. When enabled, it provides basic type conversion:
const parser = new Parser({ operators: { conversion: true } });
parser.evaluate('"1.6" as "number"'); // 1.6
parser.evaluate('"1.6" as "int"'); // 2 (rounded)
parser.evaluate('"1.6" as "integer"'); // 2 (rounded)
parser.evaluate('"1" as "boolean"'); // true
You can override the default as implementation:
parser.binaryOps.as = (value, type) => {
// Custom conversion logic
if (type === 'date') {
return new Date(value);
}
return value;
};
Operator Customization¶
You can customize binary and unary operators via parser.binaryOps and parser.unaryOps:
const parser = new Parser();
// Add a custom binary operator
parser.binaryOps['%%'] = (a, b) => ((a % b) + b) % b; // Positive modulo
// Note: Custom operators must be registered before parsing
See Also¶
- Expression - Expression object methods
- Expression Syntax - Complete syntax reference
- Advanced Features - Promises, SQL CASE, object construction