Expressions Overview
Use a custom expression language for advanced calculations and application-specific logic.
What Are Expressions
An expression is a text string that evaluates to a value. Expressions range from simple arithmetic,
such as 1 + 1, which evaluates to 2, to complex calculations, such as
sum(Sales Revenue) / sum(Sales Quantity), which computes the average revenue per unit.
LyteNyte Grid lets developers define a custom expression language that application users can use to describe computations. Two common use cases are:
- Expression Filters: Define complex filtering rules.
- Expression Fields: Compute cell values dynamically.
Expressions In Action
The demo below uses expressions to filter rows in LyteNyte Grid. Type an expression to update the filter, or select one of the provided examples.
Note
For a detailed explanation of expression filters, see the Expression Filters guide.
Expression Engine
As raw text, sum(Sales Revenue) / sum(Sales Quantity) has no inherent meaning. LyteNyte Grid provides
an expression engine that evaluates these strings to return actual values. The expression
engine processes the input in three steps:
- Tokenization: Breaks the expression into its fundamental building blocks, or tokens.
- Parsing: Converts the generated tokens into an Abstract Syntax Tree (AST), which maps relationships between expression components for accurate evaluation.
- Evaluation: Resolves the AST to compute a final value. The grid then uses this result to filter rows, dynamically compute cell values, or drive other application logic.
Evaluator
LyteNyte Grid’s expression engine is exposed as an exported class
called Evaluator. Fundamentally, this class serves as a calculator
that evaluates mathematical expressions.
The code below shows the simplest use case:
import { Evaluator } from "@1771technologies/lytenyte-pro/expressions";
const result = new Evaluator().run("1 + 1"); // 2Evaluator supports a plugin system for adding more advanced expression features.
LyteNyte Grid includes a standard set of plugins that provide a ready-to-use,
JavaScript-like expression language.
The demo below shows this advanced expression language, including identifiers
such as age, functions such as fullName(), and interpolated strings.
Standard Expression Language
Expression Plugins
Expression plugins extend the Evaluator class with additional capabilities. Pass an array of plugins
when creating an instance of the Evaluator to enable specific features. For example:
import { Evaluator, standardPlugins, stringsPlugin } from "@1771technologies/lytenyte-pro/expressions";
// Evaluator with standard expression language supportconst standardEvaluator = new Evaluator(standardPlugins);
// Evaluator with string support (+ basic math operations)const stringEvaluator = new Evaluator([stringsPlugin]);Expression Context
For an expression such as age + 21, the Evaluator must resolve the identifier
age to a value. The Evaluator.run method accepts a context object as
its second argument. The context object provides values at runtime for
identifiers used in the expression.
const context = { age: 23, user: { firstName: "John", lastName: "Smith" }, fullName: () => context.user.firstName + " " + context.user.lastName,};
evaluator.run("age * 2", context); // 46evaluator.run("user.firstName", context); // "John"evaluator.run("fullName()", context); // "John Smith"The context can include any value type, such as primitives, objects, arrays, and functions. Expressions can call functions defined in the context directly. This mechanism allows you to expose domain-specific data and operations to expression authors.
Standard Expression Language
The base Evaluator, without plugins installed, supports the arithmetic
operators +, -, *, /, %, and **, where ** performs exponentiation.
The standardPlugins export from LyteNyte Grid turns the base Evaluator into a
JavaScript-like expression language. The table below lists each plugin,
its syntax, and an example.
| Plugin | Syntax | Example |
|---|---|---|
stringsPlugin | String literals and template literals | "hello", 'world', `Hi ${name}` |
booleansPlugin | true, false, null, undefined literals | status == true |
comparisonPlugin | == != < > <= >= | age >= 18 |
logicalPlugin | && || ?? and unary ! | a && b || c |
membershipPlugin | in and not in | "key" in obj |
ternaryPlugin | condition ? then : else | x > 0 ? "pos" : "neg" |
collectionsPlugin | Array and object literals, spread (...) | [1, 2, ...rest] |
arrowsPlugin | Arrow functions (single and multi-parameter) | x => x * 2, (a, b) => a + b |
pipePlugin | Pipe operator |> | items |> filter(x => x > 0) |
accessPlugin | .prop, [computed], ?., function calls | user.name, obj?.city |
quotedIdentifierPlugin | @"name" quoted identifiers for names with spaces | @"Age Group", @"Sub-Category" |
dateLiteralPlugin | d"<date string>" any formattable date string | d"2023-02-02", d"2023/01/01" |
Literals
Numbers support decimal, hexadecimal (0xFF), octal (0o77), binary (0b1010), scientific
notation (1e10), and underscore separators (1_000_000).
Strings support single quotes, double quotes, and standard escape sequences (\n, \t, \uXXXX, \xHH).
Template literals use backticks and support embedded expressions:
`${user.firstName} scored ${points * multiplier} points`Identifiers that contain spaces or special characters can be written using the @"..." quoted
identifier syntax. Quoted identifiers resolve the same way as plain identifiers, they look up
the name in the expression context:
@"Age Group" // resolves context["Age Group"]@"Sub-Category" == "Road Bikes"@"Age Group".toUpperCase() // member access works as normalBoth single and double quotes are accepted: @'Age Group'.
Operators
Operators follow standard precedence, from lowest to highest:
| Operators | Description |
|---|---|
?: | Ternary conditional |
|> | Pipe |
|| | Logical OR |
&& | Logical AND |
?? | Nullish coalescing |
== != | Equality |
< > <= >= in not in | Relational and membership |
+ - | Addition and subtraction |
* / % | Multiplication, division, modulo |
** | Exponentiation (right-associative) |
! - + | Unary NOT, negation, identity |
. […] ?. () | Member access, computed, optional, call |
Member Access
Use . dot notation or [computed] bracket notation to access nested values.
The ?. operator returns undefined when the value on the
left is null or undefined, instead of throwing an error.
user.address.city; // throws if address is nulluser.address?.city; // returns undefined if address is nulldata["dynamic-key"]; // computed bracket accessCall a function from the context or a method on a value:
fullName(); // calls fullName from the context"hello".toUpperCase(); // method call (this binding preserved)items.filter((x) => x > 0); // method with arrow function argumentArrow Functions
Arrow functions are first-class values. Use them as callbacks for array methods or any function in the context that accepts a function argument:
items.map((x) => x * 2);items.filter((x, i) => x > 0 && i < 10);Single-parameter arrows do not require parentheses. Multi-parameter arrows do.
Note
Arrow functions in the expression language are not closures. Identifiers in the surrounding scope are not captured. The expression language does not support scoping rules or variable declarations.
Pipe Operator
The |> operator passes the left-hand value as the last argument to the right-hand
function or call expression. Chains read left-to-right without deep nesting:
// Without piperound(multiply(price, 1.1), 2)
// With pipeprice |> multiply(1.1) |> round(2)If the right side is a bare identifier, the input is passed as the sole argument:
value |> format.
Logical Fallbacks
The && and || operators short-circuit, so the right-hand expression evaluates only
when needed. The ?? operator returns the right-hand expression only when the left-hand
value is null or undefined, unlike ||, which also returns the right-hand expression
for falsy values such as 0 or "".
user && user.name; // safe: user.name only evaluated if user is truthycount || "none"; // returns "none" if count is 0 (falsy)label ?? "default"; // returns "default" only if label is null/undefinedStandalone Expression Language
The expression engine is independent of LyteNyte Grid. The Evaluator, standardPlugins, and plugin
system are general-purpose APIs that you can import and use in any JavaScript or
TypeScript project. LyteNyte Grid features, including filters, computed fields,
and the ExpressionEditor component, build on the same public API.
Expressions supports use cases outside LyteNyte Grid, including:
- Rule Engines: Evaluate user-defined conditions against application state, such as access control rules, notification triggers, or business logic configured by non-developers.
- Formula Inputs: Let users enter calculations in a form field, such
as in a pricing configurator where a sales rep enters
basePrice * quantity * (1 - discount). - Template Rendering: Use template literals and the pipe operator to build lightweight text templates that generate messages or documents from a context object, without requiring a full templating library.
- Configuration DSLs: Store expressions as strings in a database or config file and evaluate them at runtime. This keeps computed logic out of the codebase and allows updates without redeployment.
- Workflow automation: Evaluate step conditions in a visual workflow builder, where each node’s transition condition is an expression authored by the user.
Next Steps
- Expression Filters: Define complex logical conditions to filter grid rows.
- Expression Fields: Compute cell values from user-defined expressions.
- Expression Plugins: Extend expression capabilities with custom plugins.
