Paradoc
SDK

Expr

Last updated on

Parse, evaluate, and type-check Paradoc expressions

@paradoc/expr is the engine behind Paradoc's expression language: the small language that powers visible, required, and computed values. This page documents the package API. For the language itself, its operators, functions, and fields.<id> addressing, see Logic.

The package gives you three things: a parse that turns source into a typed AST, an evaluate that runs an expression against data, and a check that type-checks an expression against an artifact's field types at authoring time. It also ships Decimal for exact decimal arithmetic, so money and percentage math never drift.

Installation

npm install @paradoc/expr

The same surface is re-exported through the SDK under an expr namespace, so you can reach it without a separate dependency:

// Direct import.
import { check, parse, evaluateExpression, Decimal } from '@paradoc/expr'

// Or via the SDK namespace.
import { expr } from '@paradoc/sdk'
expr.check(/* ... */)

Parsing

parse returns { ast, errors }. The AST is null when the source has syntax errors; the errors carry positioned diagnostics.

import { parse } from '@paradoc/expr'

const { ast, errors } = parse('fields.age >= 18')
// ast is the typed expression tree; errors is empty

Evaluating

evaluateExpression parses and runs a source string against a context built with createContext. It returns a result, never throws on a bad expression. Values are tagged ({ kind, value }); use valueToString or the Values helpers to read them.

import { evaluateExpression, createContext, valueToString } from '@paradoc/expr'

const ctx = createContext({ fields: { age: 25 } })

const result = evaluateExpression('fields.age >= 18', ctx)
// { success: true, value: { kind: 'boolean', value: true } }

const sum = evaluateExpression('fields.age + 5', ctx)
valueToString(sum.value) // "30"

If you already have an AST, evaluate(ast, ctx) runs it directly.

Checking

check is the authoring-time type checker. Its value is validate-as-you-type: as an author edits a visible or computed expression, check confirms every reference resolves and every operator and function is well-typed, surfacing positioned diagnostics before the form ever runs. This is what an editor lints with.

check(source, env) returns { type, diagnostics }. The env is a TypeEnv that maps reference paths to types. Build one from an artifact's field types so the checker knows what fields.age is, then catch references that don't exist.

import { check, createTypeEnv, T } from '@paradoc/expr'

// The host (e.g. @paradoc/core) builds this from real field definitions.
const env = createTypeEnv({
  'fields.age': T.number,
  'fields.country': T.string,
})

// A well-typed expression: no diagnostics, inferred as boolean.
check('fields.age >= 18', env)
// { type: { kind: 'boolean' }, diagnostics: [] }

// An unknown reference is caught at authoring time.
check('fields.age + fields.unknownField', env)
// diagnostics: [{
//   severity: 'error',
//   code: 'unknown-identifier',
//   message: 'Unknown reference: fields.unknownField',
//   span: { ... },
// }]

For expressions used as gates (visible, required, include, rules), checkBooleanGate adds a non-boolean-gate error when the result isn't boolean. T is the type constructor (T.number, T.string, T.array(T.string), etc.) and createTypeEnv builds a simple path-to-type environment; hosts build richer ones.

Exact decimals

Decimal is exact-decimal arithmetic backed by bigint. No floating point is used, so 19.95 * 3 is exactly 59.85 and money math doesn't accumulate rounding error. The constructor is private; build values with Decimal.fromString or Decimal.fromInt.

import { Decimal } from '@paradoc/expr'

Decimal.fromString('0.1').add(Decimal.fromString('0.2')).toString()
// "0.3"  (not 0.30000000000000004)

Decimal.fromString('19.95').mul(Decimal.fromInt(3)).toString()
// "59.85"

Decimal carries the usual operators (add, sub, mul, div, mod, cmp, round, and friends) and is the runtime representation of every number in the language.

Related

  • Logic - The expression language: operators, functions, addressing
  • Sessions - The session engine whose gates this powers
  • Artifacts - Where visible and required expressions live

On this page