|
1 | | -# ExpresZo (monorepo) |
| 1 | +<picture> |
| 2 | + <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Pro-Fa/expreszo-typescript/main/packages/expreszo/docs/logo_dark.png"> |
| 3 | + <img src="https://raw.githubusercontent.com/Pro-Fa/expreszo-typescript/main/packages/expreszo/docs/logo.png" alt="ExpresZo" width="420"> |
| 4 | +</picture> |
2 | 5 |
|
3 | | -This repository hosts the ExpresZo expression-evaluator core and its companion packages. |
| 6 | +# ExpresZo Typescript |
| 7 | +[](https://www.npmjs.com/package/@pro-fa/expreszo) |
| 8 | +[](https://github.com/Pro-Fa/expreszo-typescript/actions/workflows/ci.yml) |
| 9 | +[](https://codecov.io/gh/Pro-Fa/expreszo-typescript) |
4 | 10 |
|
5 | | -## Packages |
| 11 | +**A fast, safe, and extensible expression evaluator for JavaScript and TypeScript.** |
6 | 12 |
|
7 | | -| Package | Description | |
8 | | -|---------|-------------| |
9 | | -| [`@pro-fa/expreszo`](packages/expreszo) | Core safe, extensible expression evaluator. Pratt parser + immutable AST + descriptor-based extensibility. | |
10 | | -| [`@pro-fa/expreszo-datetime`](packages/expreszo-datetime) | Optional date/time functions backed by [Luxon](https://moment.github.io/luxon/). Adds `now`, `parseISO`, `addDuration`, `format`, … via a single `dateTimePlugin`. | |
11 | | -| [`@pro-fa/expreszo-mcp-server`](packages/expreszo-mcp-server) | MCP server exposing the parser to AI assistants. Ships its own `expreszo-mcp` CLI. | |
| 13 | +ExpresZo parses and evaluates expressions at runtime — a configurable alternative to `eval()` that won't execute arbitrary code. Use it to power user-facing formula editors, rule engines, template systems, or any place you need to evaluate dynamic expressions safely. |
12 | 14 |
|
13 | | -## Quick start |
| 15 | +```js |
| 16 | +import { Parser } from '@pro-fa/expreszo'; |
14 | 17 |
|
15 | | -```bash |
16 | | -yarn install |
17 | | -yarn workspaces run build |
18 | | -yarn workspaces run test |
| 18 | +const parser = new Parser(); |
| 19 | +parser.evaluate('price * (1 - discount)', { price: 100, discount: 0.2 }); // 80 |
19 | 20 | ``` |
20 | 21 |
|
21 | | -## Adding a date/time plugin to a parser |
| 22 | +[Read full documentation](https://pro-fa.github.io/expreszo-typescript/) |
| 23 | + |
| 24 | +## Companion packages |
| 25 | + |
| 26 | +This package is the core. Two optional companions extend it without bloating the core install — pick them up only when you need them: |
| 27 | + |
| 28 | +- **[`@pro-fa/expreszo-datetime`](https://www.npmjs.com/package/@pro-fa/expreszo-datetime)** — ~70 [Luxon](https://moment.github.io/luxon/)-backed date/time functions (`now`, `parseISO`, `addDuration`, `format`, `isWeekend`, `daysUntil`, `toRelative`, `dateRange`, …). Polymorphic inputs accept Luxon `DateTime`, JS `Date`, ISO strings, and millisecond timestamps. The core never imports Luxon. |
| 29 | +- **[`@pro-fa/expreszo-mcp-server`](https://www.npmjs.com/package/@pro-fa/expreszo-mcp-server)** — MCP server that exposes the language service to AI assistants (Claude Desktop, Claude Code, Cursor, …). Ships an `expreszo-mcp` CLI. |
| 30 | + |
| 31 | +Companions register with the new `parser.use(plugin)` API: |
22 | 32 |
|
23 | 33 | ```ts |
24 | 34 | import { defineParser, fullParser } from '@pro-fa/expreszo'; |
25 | 35 | import { dateTimePlugin } from '@pro-fa/expreszo-datetime'; |
26 | 36 |
|
27 | | -const parser = defineParser({ ...fullParser }) |
28 | | - .use(dateTimePlugin); |
| 37 | +const parser = defineParser({ ...fullParser }).use(dateTimePlugin); |
29 | 38 |
|
30 | 39 | parser.parse("format(addDuration(now(), 7, 'days'), 'yyyy-MM-dd')").evaluate(); |
31 | 40 | ``` |
32 | 41 |
|
33 | | -`@pro-fa/expreszo` itself does not depend on Luxon — it is pulled in only when you install `@pro-fa/expreszo-datetime`. |
| 42 | +## Why ExpresZo? |
| 43 | + |
| 44 | +### Fast |
| 45 | + |
| 46 | +ExpresZo uses a **Pratt parser** — a top-down operator-precedence parsing algorithm that processes tokens in a single pass with no backtracking. Compared to the recursive-descent parser in the original expr-eval, this means: |
| 47 | + |
| 48 | +- **Significantly faster parsing** — simple expressions parse in microseconds, complex ones at 40,000+ ops/sec |
| 49 | +- **Predictable performance** — parsing time scales linearly with expression length, not exponentially with nesting depth |
| 50 | +- **Better error messages** — the parser knows exactly what it expected at each position, producing precise diagnostics instead of generic "parse error" messages |
| 51 | +- **Depth-limited** — a 256-level recursion cap prevents stack overflow from malicious or runaway input |
| 52 | + |
| 53 | +Parsed expressions compile to an **immutable AST** that can be evaluated repeatedly against different variable sets with near-zero overhead. |
| 54 | + |
| 55 | +### Safe |
| 56 | + |
| 57 | +ExpresZo is designed to be safe by default: |
| 58 | + |
| 59 | +- **No code execution** — expressions can only call explicitly registered functions, never arbitrary JavaScript |
| 60 | +- **Prototype pollution protection** — access to `__proto__`, `prototype`, and `constructor` is blocked |
| 61 | +- **Recursion depth limit** — deeply nested expressions are rejected at parse time |
| 62 | +- **No `eval()` or `new Function()`** — the entire evaluation runs on a stack-based AST walker |
| 63 | + |
| 64 | +### Extensible |
| 65 | + |
| 66 | +Build exactly the parser you need: |
| 67 | + |
| 68 | +```typescript |
| 69 | +import { defineParser, coreParser, withMath, withString } from '@pro-fa/expreszo'; |
| 70 | + |
| 71 | +// Tree-shakeable: only include what you use |
| 72 | +const parser = defineParser({ |
| 73 | + operators: [...coreParser.operators, ...withMath.operators, ...withString.operators], |
| 74 | + functions: [...coreParser.functions, ...withMath.functions, ...withString.functions], |
| 75 | +}); |
| 76 | +``` |
| 77 | + |
| 78 | +Or use the full kitchen-sink parser with zero configuration: |
| 79 | + |
| 80 | +```typescript |
| 81 | +const parser = new Parser(); // all built-in operators and functions included |
| 82 | +``` |
| 83 | + |
| 84 | +## Installation |
| 85 | + |
| 86 | +```bash |
| 87 | +npm install @pro-fa/expreszo |
| 88 | +``` |
| 89 | + |
| 90 | +## Quick Start |
| 91 | + |
| 92 | +```typescript |
| 93 | +import { Parser } from '@pro-fa/expreszo'; |
| 94 | + |
| 95 | +const parser = new Parser(); |
| 96 | + |
| 97 | +// Parse once, evaluate many times |
| 98 | +const expr = parser.parse('2 * x + 1'); |
| 99 | +expr.evaluate({ x: 3 }); // 7 |
| 100 | +expr.evaluate({ x: 10 }); // 21 |
| 101 | + |
| 102 | +// Or pass a resolver directly as the first argument |
| 103 | +expr.evaluate((name) => name === 'x' ? { value: 3 } : undefined); // 7 |
| 104 | + |
| 105 | +// Rich expression language |
| 106 | +parser.evaluate('user.name ?? "Anonymous"', { user: {} }); // "Anonymous" |
| 107 | +parser.evaluate('CASE WHEN score >= 90 THEN "A" WHEN score >= 80 THEN "B" ELSE "C" END', { score: 85 }); // "B" |
| 108 | +``` |
| 109 | + |
| 110 | +## Key Features |
| 111 | + |
| 112 | +| Category | Features | |
| 113 | +|----------|----------| |
| 114 | +| **Operators** | Arithmetic, comparison, logical, coalesce (`??`), ternary, assignment, member access | |
| 115 | +| **Data types** | Numbers, strings, booleans, arrays, objects, `null`, `undefined` | |
| 116 | +| **Functions** | 60+ built-in: math, string, array, object, type-checking, utility | |
| 117 | +| **Custom functions** | Register your own JavaScript functions callable from expressions | |
| 118 | +| **Arrow functions** | `x => x * 2`, `(a, b) => a + b` | |
| 119 | +| **SQL CASE** | `CASE WHEN ... THEN ... ELSE ... END` multi-way conditionals | |
| 120 | +| **Object construction** | `{ name: "Ada", score: x * 10 }` | |
| 121 | +| **Async support** | Custom functions can return Promises; `evaluate()` auto-awaits | |
| 122 | +| **Language service** | Completions, hover docs, diagnostics, syntax highlighting for IDE integration | |
| 123 | +| **TypeScript** | Full type definitions, strict mode, no `any` leaks | |
| 124 | +| **Tree-shakeable** | Subpath imports (`@pro-fa/expreszo/math`, `@pro-fa/expreszo/string`, ...) for minimal bundles | |
| 125 | + |
| 126 | +## Playground |
| 127 | + |
| 128 | +Try it live at the [Playground](https://pro-fa.github.io/expreszo-typescript/) — an interactive environment with code completions, syntax highlighting, and real-time evaluation. |
| 129 | + |
| 130 | +## Documentation |
| 131 | + |
| 132 | +### For Expression Writers |
| 133 | + |
| 134 | +| Document | Description | |
| 135 | +|:---------|:------------| |
| 136 | +| [Quick Reference](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/quick-reference.md) | Cheat sheet of operators, functions, and syntax | |
| 137 | +| [Expression Syntax](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/syntax.md) | Complete syntax reference with examples | |
| 138 | +| [Date / Time functions](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/datetime.md) | ~70 date/time functions (provided by the optional `@pro-fa/expreszo-datetime` package) | |
| 139 | + |
| 140 | +### For Developers |
| 141 | + |
| 142 | +| Document | Description | |
| 143 | +|:---------|:------------| |
| 144 | +| [Parser](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/parser.md) | Parser configuration, methods, customization, and the `parser.use(plugin)` API | |
| 145 | +| [Expression](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/expression.md) | Expression object methods: evaluate, simplify, variables | |
| 146 | +| [Advanced Features](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/advanced-features.md) | Promises, custom resolution, type conversion, operator customization | |
| 147 | +| [Date / Time integration](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/datetime-integration.md) | Wiring `@pro-fa/expreszo-datetime` into a parser | |
| 148 | +| [Language Service](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/language-service.md) | IDE integration: completions, hover info, diagnostics, Monaco Editor | |
| 149 | +| [MCP Server](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/mcp-server.md) | Model Context Protocol server exposing the language service to AI assistants | |
| 150 | +| [Migration Guide](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/migration.md) | Migrating from expr-eval, legacy mode, version history | |
| 151 | + |
| 152 | +### For Contributors |
| 153 | + |
| 154 | +| Document | Description | |
| 155 | +|:---------|:------------| |
| 156 | +| [Contributing](https://github.com/Pro-Fa/expreszo-typescript/blob/main/CONTRIBUTING.md) | Development setup, code style, and PR guidelines | |
| 157 | +| [Performance Testing](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/performance.md) | Benchmarks, profiling, and optimization guidance | |
| 158 | +| [Breaking Changes](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/BREAKING_CHANGES.md) | Version-by-version breaking change documentation | |
| 159 | + |
| 160 | +## Coming from expr-eval? |
| 161 | + |
| 162 | +ExpresZo is a direct successor to [expr-eval](https://github.com/silentmatt/expr-eval). Existing expressions work out of the box. A `{ legacy: true }` option preserves older operator semantics while you migrate incrementally. See the [Migration Guide](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/migration.md) for details. |
| 163 | + |
| 164 | +## Origins |
| 165 | + |
| 166 | +Originally based on [expr-eval 2.0.2](http://silentmatt.com/javascript-expression-evaluator/), completely rewritten with a Pratt parser, immutable AST, modular architecture, TypeScript, and comprehensive testing using Vitest. |
| 167 | + |
| 168 | +## Working in this repository |
| 169 | + |
| 170 | +The repo is a **yarn workspace monorepo** with three published packages under `packages/`: |
| 171 | + |
| 172 | +| Package | Path | |
| 173 | +|---------|------| |
| 174 | +| `@pro-fa/expreszo` | `packages/expreszo/` | |
| 175 | +| `@pro-fa/expreszo-datetime` | `packages/expreszo-datetime/` | |
| 176 | +| `@pro-fa/expreszo-mcp-server` | `packages/expreszo-mcp-server/` | |
| 177 | + |
| 178 | +```bash |
| 179 | +yarn install --frozen-lockfile # install all workspaces |
| 180 | +yarn workspaces run lint # eslint all packages |
| 181 | +yarn workspaces run type-check # tsc/tsgo on all packages |
| 182 | +yarn workspaces run build # produce dist/ for each package |
| 183 | +yarn workspaces run test # build + vitest in each package |
| 184 | +``` |
| 185 | + |
| 186 | +This file (`README.md`) is the canonical README. The publish workflow copies it into `packages/expreszo/` before `npm publish`, so the npm package always ships the same content. |
| 187 | + |
| 188 | +## License |
| 189 | + |
| 190 | +See [LICENSE.txt](https://github.com/Pro-Fa/expreszo-typescript/blob/main/LICENSE.txt) for license information. |
0 commit comments