npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@claudiu-ceia/exp

v0.1.2

Published

Small expression language toolkit

Readme

exp

JSR npm CI

A small, deterministic expression language: parse into a typed AST with spans, then evaluate safely against an explicit environment.

  • Docs: https://jsr.io/@claudiu-ceia/exp/doc
  • Package: https://jsr.io/@claudiu-ceia/exp
  • Repo: https://github.com/ClaudiuCeia/exp
import { evaluateExpression } from "jsr:@claudiu-ceia/exp";

const res = evaluateExpression('status == "open" && priority >= 3', {
  env: { status: "open", priority: 4 },
  throwOnError: false,
});

if (res.success) {
  console.log(res.value); // true
}

Overview

exp is a tiny, deterministic expression parser + evaluator intended for "mini-language" use cases:

  • API-rich filters (status == "open" && priority >= 3)
  • data-wrangling pipelines (input |> map(...) |> filter(...))
  • backtesting strategies (signals, conditions, thresholds) in a constrained DSL

Design goals:

  • No eval / new Function — all evaluation is interpreter-based.
  • Typed AST + spans — nodes carry { start, end } indices for diagnostics.
  • Safe-by-default access — expressions only touch data/functions you place in env.
  • Resource budgets — bounded parsing, evaluation, and runtime value graphs.

Quickstart

Install:

# Deno
deno add jsr:@claudiu-ceia/exp

# Node/Bun
npm install @claudiu-ceia/exp

Evaluate (non-throwing) and render a pretty diagnostic on failure:

import {
  evaluateExpression,
  ExpEvalError,
  formatDiagnosticReport,
} from "jsr:@claudiu-ceia/exp";

const input = "missing + 1";

try {
  const res = evaluateExpression(input); // throws by default
  console.log(res.value);
} catch (e) {
  if (e instanceof ExpEvalError) {
    console.error(
      formatDiagnosticReport(input, {
        message: e.message,
        span: e.span,
        index: e.index,
      }),
    );
  } else {
    throw e;
  }
}

Notes:

  • Missing identifiers throw by default (unknownIdentifier: "error").
  • Opt into legacy behavior with unknownIdentifier: "undefined".

Table of contents

Why

The expression language is intentionally small, but ergonomic enough for real application DSLs.

Non-goals

  • Full JavaScript parsing.
  • Executing untrusted code via eval / new Function.

Supported syntax (today)

Expressions:

  • literals: numbers, strings, true, false, null, undefined
  • identifiers: [A-Za-z_] followed by [A-Za-z0-9_]* (with true/false/null reserved)
  • arrays: [expr, expr, ...]
  • grouping: (expr)
  • postfix chaining: expr.ident and expr(arg1, arg2, ...) (chainable)
  • unary: !, +, -
  • binary (with precedence): * / %, + -, < <= > >=, == !=, && || ??
  • conditional: test ? consequent : alternate
  • pipeline: lhs |> fn and lhs |> fn(arg1, arg2, ...) (desugars to fn(lhs) / fn(lhs, ...))

Standard library (std.*)

std is always available during evaluation (you don’t need to pass it in env). It exposes a small set of deterministic helpers:

  • std.len(x) — length of a string or array
  • math: std.abs, std.min, std.max, std.clamp, std.floor, std.ceil, std.round, std.trunc, std.sqrt, std.pow
  • strings: std.lower, std.upper, std.trim, std.startsWith, std.endsWith, std.includes, std.slice

Note: std.includes(haystack, needle) works for both strings (substring check) and arrays (membership check).

env.std is reserved and cannot be overridden.

Equality semantics (== / !=)

Equality is intentionally JS-like for primitives, but never coerces objects/arrays/functions via implicit ToPrimitive (so no surprise toString() / valueOf() calls).

  • Primitives: loosely coerced similar to JavaScript
    • null == undefined is true
    • booleans coerce to numbers (true1, false0)
    • strings and numbers may coerce via Number(...)
  • Non-primitives (plain objects, arrays, functions): reference equality only
    • user == user can be true
    • user == "[object Object]" is false (no coercion)

String literals:

  • single or double quotes
  • ECMAScript-oriented escape semantics (see src/string_literal.ts for tc39 links)
  • strict-mode-style failures for digit/octal escapes

Examples

Filters:

  • status == "open" && priority >= 3
  • user.plan != "free" && (user.age >= 18 || user.admin == true)

Chaining:

  • user.profile.name
  • fn(1, 2).next(3).done

Safe evaluation model

Use evaluateExpression to parse + evaluate in one step, with an explicit environment and resource budgets.

At a high level:

  • Identifiers read from env only.
  • Member access is restricted.
  • Calls are only possible through functions present in env.
  • Evaluation has configurable budgets.

env and runtime values

env is the only way expressions can access data and functions. Identifiers resolve to properties on env.

  • Missing identifiers throw by default (unknownIdentifier: "error").
  • Set unknownIdentifier: "undefined" to treat missing identifiers as undefined.
  • Values must be made of supported runtime values:
    • primitives: undefined | null | boolean | number | string
    • arrays of supported values
    • plain objects ({...}) whose values are supported values
    • functions that accept/return supported values

Member access (obj.prop) is intentionally conservative:

  • Works on plain objects (and arrays only expose .length).
  • Blocks __proto__, prototype, and constructor.

env is validated at runtime: it must be a plain object (or proto-null object), and all nested values must be supported runtime values.

Member access restrictions

  • Only plain objects expose own-properties.
  • Arrays expose .length only.
  • Everything else returns undefined.

This is designed to avoid prototype leakage and surprise access to inherited properties.

Resource budgets

Parsing supports these defensive limits (all optional):

  • maxInputLength (default 100_000): max UTF-16 code units
  • maxNestingDepth (default 64): max recursive syntax nesting
  • maxNodes (default 10_000): max AST nodes

Evaluation supports these defensive limits (all optional):

  • maxSteps (default 10_000): max AST nodes visited
  • maxDepth (default 256): max recursion depth while evaluating
  • maxArrayElements (default 1_000): max elements in an array literal
  • maxRuntimeDepth (default 64): max nesting in environment and function return values
  • maxRuntimeEntries (default 10_000): max entries in environment and function return values

Example: filter over an input object

import { evaluateExpression } from "jsr:@claudiu-ceia/exp";

const env = {
  status: "open",
  priority: 4,
};

const res = evaluateExpression('status == "open" && priority >= 3', {
  env,
  throwOnError: false,
});

if (res.success) {
  console.log(res.value);
}

Example: allow-listed helper functions

import { evaluateExpression } from "jsr:@claudiu-ceia/exp";

const env = {
  user: { plan: "Free" },
};

const res = evaluateExpression('std.includes(std.lower(user.plan), "free")', {
  env,
  maxSteps: 5_000,
  throwOnError: false,
});

Installation

Deno / JSR

import { evaluateExpression } from "jsr:@claudiu-ceia/exp";

Or add it to your project:

deno add jsr:@claudiu-ceia/exp

npm

This package is published for npm via a generated build.

npm install @claudiu-ceia/exp

Then:

import { evaluateExpression } from "@claudiu-ceia/exp";

Getting started

Parse only

import { parseExpression } from "jsr:@claudiu-ceia/exp";

const parsed = parseExpression("1 + 2 * 3", { throwOnError: false });
if (parsed.success) {
  console.log(parsed.value.kind); // "binary"
}

Evaluate a pre-parsed AST

import { evaluateAst, parseExpression } from "jsr:@claudiu-ceia/exp";

const ast = parseExpression("x + 1").value;
const out = evaluateAst(ast, { env: { x: 41 }, throwOnError: false });

API reference

parseExpression(input, opts?)

Parse a single expression into a typed AST.

  • Import: import { parseExpression } from "jsr:@claudiu-ceia/exp"
  • Returns: ParseResult
  • Throws: ExpParseError (default behavior)

ParseOptions

  • throwOnError?: boolean — default true
  • maxInputLength?: number — default 100_000
  • maxNestingDepth?: number — default 64
  • maxNodes?: number — default 10_000

ParseResult

  • Success: { success: true, value: Expr }
  • Failure: { success: false, error: ParseError }

ParseError

  • message: string — compact parser error message
  • index: number — UTF-16 code-unit index into the input string

evaluateExpression(input, opts?)

Parse + evaluate in one step.

  • Import: import { evaluateExpression } from "jsr:@claudiu-ceia/exp"
  • Returns: EvalResult
  • Throws: ExpEvalError (default behavior)

EvaluateExpressionOptions

Includes all EvalOptions plus:

  • throwOnParseError?: boolean — default true
  • maxInputLength?: number — default 100_000
  • maxNestingDepth?: number — default 64
  • maxNodes?: number — default 10_000

Parse errors:

  • If throwOnParseError is true (default), parse errors throw ExpParseError.
  • If throwOnParseError is false, parse errors return { success: false, error: { message, index, steps: 0 } }.

evaluateAst(expr, opts?)

Evaluate a pre-parsed AST.

  • Import: import { evaluateAst } from "jsr:@claudiu-ceia/exp"
  • Returns: EvalResult
  • Throws: ExpEvalError (default behavior)

env is validated at runtime before evaluation begins.

Options and result types

EvalOptions

  • env?: Record<string, RuntimeValue> — default {}
  • unknownIdentifier?: "error" | "undefined" — default "error"
  • maxSteps?: number — default 10_000
  • maxDepth?: number — default 256
  • maxArrayElements?: number — default 1_000
  • maxRuntimeDepth?: number — default 64
  • maxRuntimeEntries?: number — default 10_000
  • throwOnError?: boolean — default true

EvalResult

  • Success: { success: true, value: RuntimeValue }
  • Failure: { success: false, error: EvalError }

EvalError

  • message: string — user-facing error message
  • span?: Span — present for evaluation errors tied to an AST node
  • steps?: number — step counter at time of failure
  • index?: number — present when failure is due to parse error (only returned when throwOnParseError: false)

AST types

All AST nodes include span: { start: number; end: number }.

Expr is a tagged union with these kinds:

  • number, string, boolean, null, undefined
  • identifier
  • array
  • unary
  • binary
  • member
  • call
  • conditional

Runtime values

RuntimeValue is the allowed runtime data model:

  • primitives: undefined | null | boolean | number | string
  • arrays of RuntimeValue
  • plain objects ({...} or Object.create(null)) with RuntimeValue values
  • functions: (...args: RuntimeValue[]) => RuntimeValue

Notes:

  • env must be a plain/proto-null object at runtime; class instances (e.g. Date) are rejected.
  • Function return values are validated; returning an unsupported value fails evaluation.

Errors and diagnostics

When you enable throwing (the default), you’ll get typed errors.

ExpParseError

  • Extends Error
  • Fields:
    • index: number

ExpEvalError

  • Extends Error
  • Fields:
    • span?: Span
    • steps?: number
    • index?: number

This makes it easy to render caret diagnostics from either a UTF-16 code-unit index or an AST span.

Example caret formatter:

import { formatCaret } from "jsr:@claudiu-ceia/exp";

console.log(formatCaret("1 + ", 4));

This package also exports a richer report-style formatter (used by the CLI):

import { formatDiagnosticReport } from "jsr:@claudiu-ceia/exp";

console.log(
  formatDiagnosticReport("1 + ", {
    message: "expected expression at 1:4",
    index: 4,
  }),
);

Diagnostics helpers exported from mod.ts:

  • formatCaret / formatSpanCaret

  • formatDiagnosticCaret (prefers index, falls back to span.start)

  • formatDiagnosticReport (Elm/OCaml-inspired report output)

  • ExpParseError: includes index (UTF-16 code-unit index into the input string)

  • ExpEvalError: includes span (AST span) and steps (budget counter)

If you prefer non-throwing control flow, use throwOnError: false and inspect the returned { success: false, error: { message, span?, steps?, index? } }.

Development

  • deno task check
  • deno test

CLI

This repo includes a small Deno-only CLI (not part of the npm build), built with @stricli/core.

  • deno task repl
  • deno task exp -- run [file]

Providing env

For real usage, you typically want helper functions in env (so JSON alone is often not enough). The CLI supports both:

  • --env path/to/env.ts (JS/TS module; supports functions)
  • --env-json path/to/env.json (JSON object; values only)

Example env.ts:

export const env = {
  lower: (s: unknown) => (typeof s === "string" ? s.toLowerCase() : ""),
  user: { plan: "Free" },
};

Run:

deno task exp -- run --env ./env.ts program.expr
echo '1 + 2*3' | deno task exp -- run
deno task repl -- --env ./env.ts

# value-only env (no functions)
echo 'x + 1' | deno task exp -- run --env-inline '{"x": 41}'

# see full flag docs
deno task exp -- --help

License

MIT