🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@marcbachmann/cel-js

Package Overview
Dependencies
Maintainers
1
Versions
87
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@marcbachmann/cel-js - npm Package Compare versions

Comparing version
7.5.3
to
7.6.0
+80
-42
lib/errors.js

@@ -1,10 +0,19 @@

export class ParseError extends Error {
class CelError extends Error {
#node
constructor(message, node, cause) {
super(message, {cause})
this.name = 'ParseError'
#code
#range
#summary
constructor({name, code, message, node, cause, range}) {
super(message, cause ? {cause} : undefined)
this.name = name
this.#code = code
this.#summary = message
this.#node = node
this.#range = (range && normalizeRange(range)) || normalizeRange(node)
if (!node?.input) return
this.message = formatErrorWithHighlight(this.message, node)
this.message = formatErrorWithHighlight(this.#summary, node, this.#range)
}

@@ -16,23 +25,12 @@

withAst(node) {
if (this.#node || !node?.input) return this
this.#node = node
this.message = formatErrorWithHighlight(this.message, node)
return this
get code() {
return this.#code
}
}
export class EvaluationError extends Error {
#node
constructor(message, node, cause) {
super(message, {cause})
this.name = 'EvaluationError'
this.#node = node
if (!node?.input) return
this.message = formatErrorWithHighlight(this.message, node)
get range() {
return this.#range
}
get node() {
return this.#node
get summary() {
return this.#summary
}

@@ -43,3 +41,4 @@

this.#node = node
this.message = formatErrorWithHighlight(this.message, node)
this.#range ??= normalizeRange(node)
this.message = formatErrorWithHighlight(this.#summary, node, this.#range)
return this

@@ -49,28 +48,62 @@ }

export class TypeError extends Error {
#node
constructor(message, node, cause) {
super(message, {cause})
this.name = 'TypeError'
this.#node = node
function normalizeArgs(name, defaultCode, message, node, cause) {
if (typeof message === 'string') return {name, code: defaultCode, message, node, cause}
if (!node?.input) return
this.message = formatErrorWithHighlight(this.message, node)
const opts = message
if (typeof opts !== 'object') throw new Error('First param to error must be a string or object')
return {
name,
code: opts.code || defaultCode,
message: opts.message,
node: opts.node,
cause: opts.cause,
range: opts.range
}
}
get node() {
return this.#node
export class ParseError extends CelError {
constructor(message, node, cause) {
super(normalizeArgs('ParseError', 'parse_error', message, node, cause))
}
}
withAst(node) {
if (this.#node || !node?.input) return this
this.#node = node
this.message = formatErrorWithHighlight(this.message, node)
return this
export class EvaluationError extends CelError {
constructor(message, node, cause) {
super(normalizeArgs('EvaluationError', 'evaluation_error', message, node, cause))
}
}
function formatErrorWithHighlight(message, node) {
if (node?.pos === undefined) return message
const pos = node.pos
export class TypeError extends CelError {
constructor(message, node, cause) {
super(normalizeArgs('TypeError', 'type_error', message, node, cause))
}
}
export function parseError(code, message, node) {
if (typeof code === 'object') return new ParseError(code)
return new ParseError({code, message, node})
}
export function evaluationError(code, message, node) {
if (typeof code === 'object') return new EvaluationError(code)
return new EvaluationError({code, message, node})
}
export function typeError(code, message, node) {
if (typeof code === 'object') return new TypeError(code)
return new TypeError({code, message, node})
}
function normalizeRange(node) {
const start = node?.pos ?? node?.start
if (typeof start !== 'number') return
const end = typeof node.end === 'number' ? node.end : start
return {start, end}
}
function formatErrorWithHighlight(message, node, range) {
const pos = node.pos ?? range?.start
if (typeof pos !== 'number') return message
const input = node.input

@@ -91,3 +124,2 @@

// Show a few lines of context
let contextStart = pos

@@ -100,3 +132,9 @@ let contextEnd = pos

const highlight = `> ${`${lineNum}`.padStart(4, ' ')} | ${line}\n${' '.repeat(9 + columnNum)}^`
return `${message}\n\n${highlight}`
}
export function attachErrorAst(error, node) {
if (error instanceof CelError) return error.withAst(node)
return error
}

@@ -1,4 +0,16 @@

export * from './index.js'
import {Environment, check, evaluate, parse} from './index.js'
import {Duration, UnsignedInt} from './functions.js'
// Re-export Duration and UnsignedInt from functions.js
export {Environment, check, evaluate, parse}
export {Duration, UnsignedInt} from './functions.js'
declare const evaluator: {
parse: typeof parse
evaluate: typeof evaluate
check: typeof check
Environment: typeof Environment
Duration: typeof Duration
UnsignedInt: typeof UnsignedInt
}
export default evaluator
import {createRegistry, RootContext} from './registry.js'
import {EvaluationError} from './errors.js'
import {evaluationError} from './errors.js'
import {registerFunctions, Duration, UnsignedInt} from './functions.js'

@@ -77,4 +77,4 @@ import {registerMacros} from './macros.js'

return this.#checkAST(this.#parser.parse(expression))
} catch (e) {
return {valid: false, error: e}
} catch (error) {
return {valid: false, error}
}

@@ -87,4 +87,4 @@ }

return {valid: true, type: this.#formatTypeForCheck(typeDecl)}
} catch (e) {
return {valid: false, error: e}
} catch (error) {
return {valid: false, error}
}

@@ -124,3 +124,3 @@ }

super(opts)
this.Error = EvaluationError
this.createError = evaluationError
}

@@ -127,0 +127,0 @@

@@ -23,2 +23,5 @@ import type {Registry} from './registry.js'

toString(): string
/** Validate and store an unsigned integer value. */
verify(v: bigint): void
}

@@ -44,7 +47,34 @@

/** Convert to primitive bigint (seconds only) for operations. */
valueOf(): bigint
/** Construct a duration from a millisecond value. */
static fromMilliseconds(ms: number): Duration
/** Convert to primitive milliseconds for operations. */
valueOf(): number
/** Add another duration. */
addDuration(other: Duration): Duration
/** Subtract another duration. */
subtractDuration(other: Duration): Duration
/** Add this duration to a timestamp. */
extendTimestamp(timestamp: Date): Date
/** Subtract this duration from a timestamp. */
subtractTimestamp(timestamp: Date): Date
/** Convert to string representation in format like "5s", "1h30m", etc. */
toString(): string
/** Whole hours represented by this duration. */
getHours(): bigint
/** Whole minutes represented by this duration. */
getMinutes(): bigint
/** Whole seconds represented by this duration. */
getSeconds(): bigint
/** Total milliseconds represented by this duration. */
getMilliseconds(): bigint
}

@@ -51,0 +81,0 @@

@@ -1,9 +0,6 @@

import {EvaluationError} from './errors.js'
import {evaluationError} from './errors.js'
import {TYPES, Type} from './registry.js'
import {register as registerOptional} from './optional.js'
import {objKeys, arrayFrom} from './globals.js'
import {objKeys, arrayFrom, MIN_UINT, MAX_UINT, MIN_INT, MAX_INT} from './globals.js'
const MIN_UINT = 0n
const MAX_UINT = 18446744073709551615n
export class UnsignedInt {

@@ -28,3 +25,5 @@ #value

verify(v) {
if (v < MIN_UINT || v > MAX_UINT) throw new EvaluationError('Unsigned integer overflow')
if (v < MIN_UINT || v > MAX_UINT) {
throw evaluationError('numeric_overflow', 'Unsigned integer overflow')
}
this.#value = v

@@ -42,9 +41,12 @@ }

const billion = 1_000_000_000
const billionBigInt = 1_000_000_000n
const UNIT_NANOSECONDS = {
h: 3600000000000n,
m: 60000000000n,
s: 1000000000n,
ms: 1000000n,
us: 1000n,
µs: 1000n,
h: 3_600_000_000_000n,
m: 60_000_000_000n,
s: billionBigInt,
ms: 1_000_000n,
us: 1_000n,
µs: 1_000n,
ns: 1n

@@ -71,3 +73,3 @@ }

valueOf() {
return Number(this.#seconds) * 1000 + this.#nanos / 1000000
return Number(this.#seconds) * 1000 + this.#nanos / 1_000_000
}

@@ -77,4 +79,4 @@

const totalNanos = BigInt(Math.trunc(ms * 1_000_000))
const seconds = totalNanos / 1_000_000_000n
const nanos = Number(totalNanos % 1_000_000_000n)
const seconds = totalNanos / billionBigInt
const nanos = Number(totalNanos % billionBigInt)
return new Duration(seconds, nanos)

@@ -86,4 +88,4 @@ }

return new Duration(
this.#seconds + other.seconds + BigInt(Math.floor(nanos / 1_000_000_000)),
nanos % 1_000_000_000
this.#seconds + other.seconds + BigInt(Math.floor(nanos / billion)),
nanos % billion
)

@@ -95,4 +97,4 @@ }

return new Duration(
this.#seconds - other.seconds + BigInt(Math.floor(nanos / 1_000_000_000)),
(nanos + 1_000_000_000) % 1_000_000_000
this.#seconds - other.seconds + BigInt(Math.floor(nanos / billion)),
(nanos + billion) % billion
)

@@ -115,3 +117,3 @@ }

const nanos = this.#nanos
? (this.#nanos / 1000000000)
? (this.#nanos / billion)
.toLocaleString('en-US', {useGrouping: false, maximumFractionDigits: 9})

@@ -177,3 +179,6 @@ .slice(1)

default:
throw new EvaluationError(`bool() conversion error: invalid string value "${v}"`)
throw evaluationError(
'bool_conversion_error',
`bool() conversion error: invalid string value "${v}"`
)
}

@@ -199,3 +204,6 @@ })

if (!v || v !== v.trim())
throw new EvaluationError('double() type error: cannot convert to double')
throw evaluationError(
'double_conversion_error',
'double() type error: cannot convert to double'
)

@@ -217,3 +225,6 @@ const s = v.toLowerCase()

if (!Number.isNaN(parsed)) return parsed
throw new EvaluationError('double() type error: cannot convert to double')
throw evaluationError(
'double_conversion_error',
'double() type error: cannot convert to double'
)
}

@@ -226,3 +237,3 @@ }

if (Number.isFinite(v)) return BigInt(Math.trunc(v))
throw new EvaluationError('int() type error: integer overflow')
throw evaluationError('numeric_overflow', 'int() type error: integer overflow')
})

@@ -232,3 +243,3 @@

if (v !== v.trim() || v.length > 20 || v.includes('0x')) {
throw new EvaluationError('int() type error: cannot convert to int')
throw evaluationError('int_conversion_error', 'int() type error: cannot convert to int')
}

@@ -238,6 +249,6 @@

const num = BigInt(v)
if (num <= 9223372036854775807n && num >= -9223372036854775808n) return num
if (num <= MAX_INT && num >= MIN_INT) return num
} catch (_e) {}
throw new EvaluationError('int() type error: cannot convert to int')
throw evaluationError('int_conversion_error', 'int() type error: cannot convert to int')
})

@@ -250,3 +261,3 @@

} catch (e) {
throw new EvaluationError('uint() type error: cannot convert to uint')
throw evaluationError('uint_conversion_error', 'uint() type error: cannot convert to uint')
}

@@ -258,3 +269,3 @@ })

} catch (e) {
throw new EvaluationError('uint() type error: unsigned integer overflow')
throw evaluationError('numeric_overflow', 'uint() type error: unsigned integer overflow')
}

@@ -265,3 +276,3 @@ })

if (v !== v.trim() || v.length > 20 || v.includes('0x')) {
throw new EvaluationError('uint() type error: cannot convert to uint')
throw evaluationError('uint_conversion_error', 'uint() type error: cannot convert to uint')
}

@@ -272,3 +283,3 @@

} catch (e) {
throw new EvaluationError('uint() type error: cannot convert to uint')
throw evaluationError('uint_conversion_error', 'uint() type error: cannot convert to uint')
}

@@ -303,3 +314,6 @@ })

if (fromIndex < 0 || fromIndex >= string.length) {
throw new EvaluationError('string.indexOf(search, fromIndex): fromIndex out of range')
throw evaluationError(
'index_out_of_range',
'string.indexOf(search, fromIndex): fromIndex out of range'
)
}

@@ -319,3 +333,6 @@

if (fromIndex < 0 || fromIndex >= string.length) {
throw new EvaluationError('string.lastIndexOf(search, fromIndex): fromIndex out of range')
throw evaluationError(
'index_out_of_range',
'string.lastIndexOf(search, fromIndex): fromIndex out of range'
)
}

@@ -329,3 +346,6 @@

if (start < 0 || start > string.length) {
throw new EvaluationError('string.substring(start, end): start index out of range')
throw evaluationError(
'index_out_of_range',
'string.substring(start, end): start index out of range'
)
}

@@ -339,3 +359,6 @@

if (start < 0 || start > string.length) {
throw new EvaluationError('string.substring(start, end): start index out of range')
throw evaluationError(
'index_out_of_range',
'string.substring(start, end): start index out of range'
)
}

@@ -345,3 +368,6 @@

if (end < start || end > string.length) {
throw new EvaluationError('string.substring(start, end): end index out of range')
throw evaluationError(
'index_out_of_range',
'string.substring(start, end): end index out of range'
)
}

@@ -356,3 +382,3 @@

} catch (_err) {
throw new EvaluationError(`Invalid regular expression: ${b}`)
throw evaluationError('invalid_regular_expression', `Invalid regular expression: ${b}`)
}

@@ -375,3 +401,6 @@ })

if (typeof v[i] !== 'string') {
throw new EvaluationError('string.join(): list must contain only strings')
throw evaluationError(
'invalid_list_element_type',
'string.join(): list must contain only strings'
)
}

@@ -385,3 +414,6 @@ }

if (typeof v[i] !== 'string') {
throw new EvaluationError('string.join(separator): list must contain only strings')
throw evaluationError(
'invalid_list_element_type',
'string.join(separator): list must contain only strings'
)
}

@@ -423,3 +455,5 @@ }

functionOverload('bytes.at(int): int', (b, index) => {
if (index < 0 || index >= b.length) throw new EvaluationError('Bytes index out of range')
if (index < 0 || index >= b.length) {
throw evaluationError('index_out_of_range', 'Bytes index out of range')
}
return BigInt(b[index])

@@ -451,3 +485,3 @@ })

if (v.length < 20 || v.length > 30) {
throw new EvaluationError('timestamp() requires a string in ISO 8601 format')
throw evaluationError('invalid_timestamp', 'timestamp() requires a string in ISO 8601 format')
}

@@ -457,3 +491,3 @@

if (d <= 253402300799999 && d >= -62135596800000) return d
throw new EvaluationError('timestamp() requires a string in ISO 8601 format')
throw evaluationError('invalid_timestamp', 'timestamp() requires a string in ISO 8601 format')
})

@@ -464,3 +498,6 @@

if (i <= 253402300799999 && i >= -62135596800000) return new Date(i)
throw new EvaluationError('timestamp() requires a valid integer unix timestamp')
throw evaluationError(
'invalid_timestamp',
'timestamp() requires a valid integer unix timestamp'
)
})

@@ -499,3 +536,3 @@

function parseDuration(/** @type {string} */ string) {
if (!string) throw new EvaluationError(`Invalid duration string: ''`)
if (!string) throw evaluationError('invalid_duration', `Invalid duration string: ''`)

@@ -508,5 +545,6 @@ const isNegative = string[0] === '-'

const match = parseDurationPattern.exec(string)
if (!match) throw new EvaluationError(`Invalid duration string: ${string}`)
if (!match) throw evaluationError('invalid_duration', `Invalid duration string: ${string}`)
if (match.index !== 0) throw new EvaluationError(`Invalid duration string: ${string}`)
if (match.index !== 0)
throw evaluationError('invalid_duration', `Invalid duration string: ${string}`)
string = string.slice(match[0].length)

@@ -518,3 +556,3 @@

const fracNanos = fracPart
? (BigInt(fracPart.slice(0, 13).padEnd(13, '0')) * unitNanos) / 10000000000000n
? (BigInt(fracPart.slice(0, 13).padEnd(13, '0')) * unitNanos) / 10_000_000_000_000n
: 0n

@@ -526,4 +564,4 @@

const seconds = nanoseconds >= 1000000000n ? nanoseconds / 1000000000n : 0n
const nanos = Number(nanoseconds % 1000000000n)
const seconds = nanoseconds >= billionBigInt ? nanoseconds / billionBigInt : 0n
const nanos = Number(nanoseconds % billionBigInt)

@@ -530,0 +568,0 @@ if (isNegative) return new Duration(-seconds, -nanos)

@@ -8,2 +8,8 @@ export const hasOwn = Object.hasOwn

export const MIN_UINT = 0n
export const MAX_UINT = 18446744073709551615n
export const MAX_INT = 9223372036854775807n
export const MIN_INT = -9223372036854775808n
export function isAsync(fn, fallback) {

@@ -10,0 +16,0 @@ if (fn?.[Symbol.toStringTag] === 'AsyncFunction') return true

import type {UnsignedInt} from './functions.js'
import type {
DefinitionsResult,
RegisterConstantDeclaration,
RegisterFunctionDeclaration,
RegisterFunctionMetadata,
RegisterFunctionOptions,
RegisterTypeDeclaration,
RegisterTypeDefinition,
RegisterVariableDeclaration,
RegisterVariableMetadata,
RegisterVariableOptions,
RegisteredFunctionHandler,
RegisteredVariableType
} from './registry.js'

@@ -63,7 +77,42 @@ /**

interface ASTNodeBase<T extends ASTOperator> {
/** The position in the source string where this node starts */
export interface SourceRange {
readonly start: number
readonly end: number
}
export interface ErrorOptions {
readonly code?: string
readonly message?: string
readonly node?: ErrorLocation | ASTNode
readonly cause?: unknown
readonly range?: SourceRange
}
export interface SourceLocation {
/** Compatibility position used by existing consumers and error reporting. */
readonly pos: number
/** The full source range start for this node or error anchor. */
readonly start: number
/** The full source range end for this node or error anchor. */
readonly end: number
/** The original CEL input string */
readonly input?: string
/** The full source range for this node or error anchor. */
readonly range?: SourceRange
}
export interface ErrorLocation extends SourceLocation {
/** Present when the error is attached to a parsed AST node. */
readonly op?: ASTOperator
/** Present when the error is attached to a parsed AST node. */
readonly args?: unknown
/** Present when the error is attached to a parsed AST node. */
toOldStructure?(): LegacyAstTuple
}
interface ASTNodeBase<T extends ASTOperator> extends SourceLocation {
/** The original CEL input string for this parsed AST node. */
readonly input: string
/** The full source range for this AST node. */
readonly range: SourceRange
/** Operator for this node */

@@ -85,7 +134,33 @@ readonly op: T

*/
export interface Context {
[key: string]: any
}
export type Context = Record<string, any>
export type {RootContext, OverlayContext} from './registry.d'
export type {
DefinitionFunction,
DefinitionFunctionParam,
DefinitionsResult,
DefinitionVariable,
ObjectSchema,
OverlayContext,
RegisterConstantDeclaration,
RegisterFunctionDeclaration,
RegisterFunctionMetadata,
RegisterFunctionOptions,
RegisterFunctionWithName,
RegisterFunctionWithSignature,
RegisterTypeDeclaration,
RegisterTypeDefinition,
RegisteredFunctionHandler,
RegisteredFunctionParam,
RegisteredFunctionTypedParam,
RegisteredType,
RegisteredTypeFieldDeclaration,
RegisteredVariableType,
RegisterVariableDeclaration,
RegisterVariableMetadata,
RegisterVariableOptions,
RegisterVariableSchemaOptions,
RegisterVariableTypeOptions,
RootContext,
TypeDeclaration
} from './registry.js'

@@ -100,4 +175,4 @@ /**

type?: string
/** The type error that occurred (only present if valid is false) */
error?: TypeError
/** The parse or type error that occurred (only present if valid is false) */
error?: ParseError | TypeError
}

@@ -117,4 +192,10 @@

export class ParseError extends Error {
constructor(message: string)
constructor(message: string, node?: ErrorLocation, cause?: unknown)
constructor(options: ErrorOptions)
readonly name: 'ParseError'
readonly code: string
readonly summary: string
readonly node?: ErrorLocation
readonly range?: SourceRange
withAst(node: ASTNode | ErrorLocation): this
}

@@ -126,4 +207,10 @@

export class EvaluationError extends Error {
constructor(message: string)
constructor(message: string, node?: ASTNode, cause?: unknown)
constructor(options: ErrorOptions)
readonly name: 'EvaluationError'
readonly code: string
readonly summary: string
readonly node?: ASTNode
readonly range?: SourceRange
withAst(node: ASTNode | SourceLocation): this
}

@@ -136,4 +223,10 @@

export class TypeError extends Error {
constructor(message: string)
constructor(message: string, node?: ASTNode, cause?: unknown)
constructor(options: ErrorOptions)
readonly name: 'TypeError'
readonly code: string
readonly summary: string
readonly node?: ASTNode
readonly range?: SourceRange
withAst(node: ASTNode | SourceLocation): this
}

@@ -145,3 +238,3 @@

*/
export class Optional {
export class Optional<T = unknown> {
/**

@@ -152,3 +245,3 @@ * Create a new Optional with a value.

*/
static of(value: any): Optional
static of<T>(value: T): Optional<T>

@@ -159,3 +252,3 @@ /**

*/
static none(): Optional
static none(): Optional<never>

@@ -170,3 +263,3 @@ /** Check if a value is present. */

*/
value(): any
value(): T

@@ -178,3 +271,3 @@ /**

*/
or(optional: Optional): Optional
or<U>(optional: Optional<U>): Optional<T | U>

@@ -186,3 +279,3 @@ /**

*/
orValue(defaultValue: any): any
orValue<U>(defaultValue: U): T | U
}

@@ -228,2 +321,10 @@

/**
* Type check a CEL expression string directly.
*
* @param expression - The CEL expression string to check
* @returns Validation result with inferred type or error details
*/
export function check(expression: string): TypeCheckResult
/**
* Serialize an AST back to a CEL expression string.

@@ -282,2 +383,6 @@ *

export type ResolvedEnvironmentOptions = Omit<Required<EnvironmentOptions>, 'limits'> & {
limits: Limits
}
/**

@@ -297,2 +402,5 @@ * Environment for CEL expression evaluation with type checking and custom functions.

export class Environment {
/** The fully resolved options for this environment instance. */
readonly opts: ResolvedEnvironmentOptions
/**

@@ -317,3 +425,3 @@ * Create a new Environment with optional configuration.

* @param typename - The name of the type (e.g., 'Vector', 'Point')
* @param constructor - The constructor function or class for the type
* @param definition - The type constructor or registration object
* @returns This environment for chaining

@@ -325,5 +433,8 @@ *

* env.registerType('Vector', Vector)
* env.registerType('Vector', {ctor: Vector, fields: {x: 'double', y: 'double'}})
* env.registerType({name: 'Vector', schema: {x: 'double', y: 'double'}})
* ```
*/
registerType(typename: string, constructor: any): this
registerType(typename: string, definition: RegisterTypeDefinition): this
registerType(definition: RegisterTypeDeclaration): this

@@ -333,4 +444,3 @@ /**

*
* @param name - The variable name
* @param type - The CEL type name ('string', 'int', 'double', 'bool', 'list', 'map', etc.)
* Supports `name + type`, `name + {type|schema}`, and a single declaration object.
* @returns This environment for chaining

@@ -342,6 +452,13 @@ * @throws Error if variable is already registered

* env.registerVariable('username', 'string')
* .registerVariable('count', 'int')
* env.registerVariable('user', {type: 'map', description: 'The current user'})
* env.registerVariable({name: 'profile', schema: {email: 'string'}})
* ```
*/
registerVariable(name: string, type: string): this
registerVariable(
name: string,
type: RegisteredVariableType,
opts?: RegisterVariableMetadata
): this
registerVariable(name: string, options: RegisterVariableOptions): this
registerVariable(definition: RegisterVariableDeclaration): this

@@ -351,5 +468,3 @@ /**

*
* @param name - The constant identifier exposed to CEL expressions
* @param type - The CEL type name of the constant (e.g., 'int', 'string')
* @param value - The concrete value supplied during registration
* Supports `name + type + value` and a single declaration object.
* @returns This environment for chaining further registrations

@@ -360,6 +475,8 @@ *

* const env = new Environment().registerConstant('timezone', 'string', 'UTC')
* env.registerConstant({name: 'minAge', type: 'int', value: 18n, description: 'Minimum age'})
* env.evaluate('timezone == "UTC"') // true
* ```
*/
registerConstant(name: string, type: string, value: any): this
registerConstant(name: string, type: RegisteredVariableType, value: any): this
registerConstant(definition: RegisterConstantDeclaration): this

@@ -369,4 +486,6 @@ /**

*
* Supports signature-based registration as well as a single declaration object.
* @param signature - Function signature in format 'name(type1, type2): returnType' or 'Type.method(args): returnType'
* @param handlerOrOptions - Either the function implementation or an options object with handler and optional typeCheck
* @param handler - The function implementation
* @param opts - Optional metadata such as descriptions, param docs, and async hints
* @returns This environment for chaining

@@ -379,10 +498,29 @@ *

*
* // Signature string with descriptions
* env.registerFunction('greet(string): string', handler, {description: 'Greets someone'})
*
* // Instance method
* env.registerFunction('string.reverse(): string', (str) => str.split('').reverse().join(''))
*
* // Macro function with type checker
* env.registerFunction('list.custom(ast, ast): bool', {
* handler: (receiver, ast) => { ... },
* typeCheck: (checker, receiverType, args) => 'bool'
* // Single object with signature and named params
* env.registerFunction({
* signature: 'formatDate(int, string): string',
* handler,
* description: 'Formats a timestamp',
* params: [
* {name: 'timestamp', description: 'Unix timestamp in seconds'},
* {name: 'format', description: 'Date format string'}
* ]
* })
*
* // Macro function
* env.registerFunction('macro(ast): dyn', ({args}) => ({
* firstArgument: args[0],
* typeCheck(checker, macro, ctx) {
* return checker.check(macro.firstArgument, ctx)
* },
* evaluate(evaluator, macro, ctx) {
* return evaluator.eval(macro.firstArgument, ctx)
* }
* }))
* ```

@@ -392,9 +530,7 @@ */

signature: string,
handlerOrOptions:
| ((...args: any[]) => any)
| {
handler: (...args: any[]) => any
typeCheck?: (checker: any, receiverType: string, args: any[]) => string
}
handler: RegisteredFunctionHandler,
opts?: RegisterFunctionMetadata
): this
registerFunction(signature: string, options: RegisterFunctionOptions): this
registerFunction(definition: RegisterFunctionDeclaration): this

@@ -416,2 +552,8 @@ /**

/**
* Return user-facing definitions for all registered variables and functions,
* including the built-ins inherited from the global environment.
*/
getDefinitions(): DefinitionsResult
/**
* Check if a variable is registered in this environment.

@@ -486,2 +628,3 @@ *

evaluate: typeof evaluate
check: typeof check
serialize: typeof serialize

@@ -488,0 +631,0 @@ Environment: typeof Environment

@@ -1,2 +0,2 @@

import {EvaluationError, ParseError} from './errors.js'
import {evaluationError, parseError} from './errors.js'
import {OPERATORS as OPS} from './operators.js'

@@ -7,3 +7,3 @@ const identity = (x) => x

if (node.op === 'id') return node.args
throw new ParseError(message, node)
throw parseError('invalid_macro_argument', message, node)
}

@@ -99,3 +99,3 @@

if (!(!inOptionalContext && i && node.op === '.')) break
throw new EvaluationError(`No such key: ${node.args[1]}`, node)
throw evaluationError('no_such_key', `No such key: ${node.args[1]}`, node)
}

@@ -107,7 +107,7 @@ return obj !== undefined

let node = macro.args[0]
if (node.op !== '.') throw new checker.Error(invalidHasArgument, node)
if (node.op !== '.') throw checker.createError('invalid_macro_argument', invalidHasArgument, node)
if (!macro.macroHasProps) {
const props = []
while (node.op === '.' || node.op === '.?') node = props.push(node) && node.args[0]
if (node.op !== 'id') throw new checker.Error(invalidHasArgument, node)
if (node.op !== 'id') throw checker.createError('invalid_macro_argument', invalidHasArgument, node)
checker.check(node, ctx)

@@ -114,0 +114,0 @@ props.push(node)

import {celTypes} from './registry.js'
import {objKeys, isArray, hasOwn} from './globals.js'
import {attachErrorAst} from './errors.js'
const dynType = celTypes.dyn

@@ -65,3 +66,3 @@

function unsupportedType(self, type) {
throw new self.Error(`Unsupported type: ${type}`)
throw self.createError('unsupported_type', `Unsupported type: ${type}`)
}

@@ -119,13 +120,15 @@

function checkElementHomogenous(chk, ctx, expected, el, m) {
const HOMOGENEOUS_PREFIX = {
heterogeneous_list_element: 'List elements must have the same type,',
heterogeneous_map_key: 'Map key uses wrong type,',
heterogeneous_map_value: 'Map value uses wrong type,'
}
function checkElementHomogenous(chk, ctx, expected, el, code) {
const type = chk.check(el, ctx)
if (type === expected || expected.isEmpty()) return type
if (type.isEmpty()) return expected
let prefix
if (m === 0) prefix = 'List elements must have the same type,'
else if (m === 1) prefix = 'Map key uses wrong type,'
else if (m === 2) prefix = 'Map value uses wrong type,'
throw new chk.Error(
`${prefix} expected type '${chk.formatType(expected)}' but found '${chk.formatType(type)}'`,
throw chk.createError(
code,
`${HOMOGENEOUS_PREFIX[code]} expected type '${chk.formatType(expected)}' but found '${chk.formatType(type)}'`,
el

@@ -141,3 +144,7 @@ )

const type = ev.debugRuntimeType(value)
return new ev.Error(`${node.meta.label || 'Ternary condition must be bool'}, got '${type}'`, node)
return ev.createError(
'invalid_condition_type',
`${node.meta.label || 'Ternary condition must be bool'}, got '${type}'`,
node
)
}

@@ -153,3 +160,7 @@

const type = ev.debugRuntimeType(value)
return new ev.Error(`Logical operator requires bool operands, got '${type}'`, node)
return ev.createError(
'invalid_logical_operand',
`Logical operator requires bool operands, got '${type}'`,
node
)
}

@@ -184,3 +195,4 @@

if (!leftType.isDynOrBool()) {
throw new chk.Error(
throw chk.createError(
'invalid_logical_operand',
`Logical operator requires bool operands, got '${chk.formatType(leftType)}'`,

@@ -191,3 +203,4 @@ ast

if (!rightType.isDynOrBool()) {
throw new chk.Error(
throw chk.createError(
'invalid_logical_operand',
`Logical operator requires bool operands, got '${chk.formatType(rightType)}'`,

@@ -212,3 +225,9 @@ ast

const overload = ast.candidates.findUnaryOverload(right)
if (!overload) throw new chk.Error(`no such overload: ${op[0]}${chk.formatType(right)}`, ast)
if (!overload) {
throw chk.createError(
'no_such_overload',
`no such overload: ${op[0]}${chk.formatType(right)}`,
ast
)
}

@@ -223,3 +242,3 @@ ast.handle = maybeAsync(ast.args, false, overload.handler)

if (overload) return overload.handler(left)
throw new ev.Error(`no such overload: ${ast.op[0]}${leftType}`, ast)
throw ev.createError('no_such_overload', `no such overload: ${ast.op[0]}${leftType}`, ast)
}

@@ -248,3 +267,4 @@

throw new chk.Error(
throw chk.createError(
'no_such_overload',
`no such overload: ${chk.formatType(left)} ${op} ${chk.formatType(right)}`,

@@ -268,5 +288,23 @@ ast

if (overload) return overload.handler(left, right, ast, ev)
throw new ev.Error(`no such overload: ${leftType} ${ast.op} ${rightType}`, ast)
throw ev.createError(
'no_such_overload',
`no such overload: ${leftType} ${ast.op} ${rightType}`,
ast
)
}
function callFunctionHandler(handler, ev, args, ast) {
try {
const result = handler.apply(ev, args)
if (result instanceof Promise) {
return result.catch((error) => {
throw attachErrorAst(error, ast)
})
}
return result
} catch (error) {
throw attachErrorAst(error, ast)
}
}
function callFn(args, ast, ev) {

@@ -279,4 +317,5 @@ const argAst = ast.args[1]

const decl = ast.candidates.findFunction(types)
if (decl) return decl.handler.apply(ev, args)
throw new ev.Error(
if (decl) return callFunctionHandler(decl.handler, ev, args, ast)
throw ev.createError(
'no_matching_overload',
`found no matching overload for '${ast.args[0]}(${types

@@ -297,5 +336,6 @@ .map((t) => t.unwrappedType)

const decl = ast.candidates.findFunction(types, receiverType)
if (decl) return decl.handler.apply(ev, args)
if (decl) return callFunctionHandler(decl.handler, ev, args, ast)
throw new ev.Error(
throw ev.createError(
'no_matching_overload',
`found no matching overload for '${receiverType.type}.${ast.args[0]}(${types

@@ -332,3 +372,4 @@ .map((t) => t.unwrappedType)

if (iterType.kind === 'map') return iterType.keyType
throw new chk.Error(
throw chk.createError(
'invalid_comprehension_range',
`Expression of type '${chk.formatType(

@@ -345,3 +386,4 @@ iterType

if (coll && typeof coll === 'object') return objKeys(coll)
throw new ev.Error(
throw ev.createError(
'invalid_comprehension_range',
`Expression of type '${ev.debugType(

@@ -463,3 +505,3 @@ coll

}
throw new ev.Error(`No such key: ${right}`, ast)
throw ev.createError('no_such_key', `No such key: ${right}`, ast)
}

@@ -482,3 +524,3 @@

const variable = ctx.getVariable(ast.args)
if (!variable) throw new chk.Error(`Unknown variable: ${ast.args}`, ast)
if (!variable) throw chk.createError('unknown_variable', `Unknown variable: ${ast.args}`, ast)
if (variable.constant) {

@@ -528,3 +570,4 @@ const alternate = ast.clone(OPERATORS.value, variable.value)

if (!decl) {
throw new chk.Error(
throw chk.createError(
'no_matching_overload',
`found no matching overload for '${functionName}(${chk.formatTypeList(argTypes)})'`,

@@ -537,3 +580,3 @@ ast

? callFn
: (decl.handler.__handle ??= (l, _ast, e) => decl.handler.apply(e, l))
: (decl.handler.__handle ??= (l, _ast, e) => callFunctionHandler(decl.handler, e, l, _ast))

@@ -565,3 +608,4 @@ ast.handle = maybeAsync(args, false, handle)

if (!decl) {
throw new chk.Error(
throw chk.createError(
'no_matching_overload',
`found no matching overload for '${receiverType.type}.${methodName}(${chk.formatTypeList(

@@ -575,4 +619,4 @@ argTypes

if (!receiverType.hasPlaceholderType && !argTypes.some((t) => t.hasDynType)) {
const handler = decl.handler
const handle = (handler.__handle ??= (a, ev) => handler.apply(ev, a))
const fn = decl.handler
const handle = (fn.__handle ??= (a, ev, _ast) => callFunctionHandler(fn, ev, a, _ast))
ast.handle = maybeAsync(ast.receiverWithArgs, false, handle)

@@ -596,3 +640,4 @@ }

for (let i = 1; i < arrLen; i++) valueType = check(chk, ctx, valueType, arr[i], 0)
for (let i = 1; i < arrLen; i++)
valueType = check(chk, ctx, valueType, arr[i], 'heterogeneous_list_element')
return chk.registry.getListType(valueType)

@@ -615,4 +660,4 @@ },

const e = arr[i]
keyType = check(chk, ctx, keyType, e[0], 1)
valueType = check(chk, ctx, valueType, e[1], 2)
keyType = check(chk, ctx, keyType, e[0], 'heterogeneous_map_key')
valueType = check(chk, ctx, valueType, e[1], 'heterogeneous_map_value')
}

@@ -701,3 +746,4 @@ return chk.registry.getMapType(keyType, valueType)

if (!condType.isDynOrBool()) {
throw new chk.Error(
throw chk.createError(
'invalid_condition_type',
`${condast.meta.label || 'Ternary condition must be bool'}, got '${chk.formatType(condType)}'`,

@@ -715,3 +761,4 @@ condast

throw new chk.Error(
throw chk.createError(
'incompatible_ternary_branches',
`Ternary branches must have the same type, got '${chk.formatType(

@@ -718,0 +765,0 @@ leftType

@@ -1,2 +0,2 @@

import {EvaluationError} from './errors.js'
import {evaluationError} from './errors.js'

@@ -24,3 +24,5 @@ export class Optional {

value() {
if (this.#value === undefined) throw new EvaluationError('Optional value is not present')
if (this.#value === undefined) {
throw evaluationError('optional_value_missing', 'Optional value is not present')
}
return this.#value

@@ -32,3 +34,6 @@ }

if (optional instanceof Optional) return optional
throw new EvaluationError('Optional.or must be called with an Optional argument')
throw evaluationError(
'invalid_optional_argument',
'Optional.or must be called with an Optional argument'
)
}

@@ -75,3 +80,3 @@

if (value instanceof Optional) return value
throw new EvaluationError(`${description} must be optional`, ast)
throw evaluationError('optional_expected', `${description} must be optional`, ast)
}

@@ -95,3 +100,3 @@

if (type.kind === 'dyn') return checker.getType('optional')
throw new checker.Error(`${description} must be optional, got '${type}'`, node)
throw checker.createError('optional_expected', `${description} must be optional, got '${type}'`, node)
}

@@ -127,3 +132,4 @@

if (unified) return unified
throw new check.Error(
throw check.createError(
'incompatible_argument_type',
`${macro.functionDesc} argument must be compatible type, got '${l}' and '${r}'`,

@@ -159,3 +165,4 @@ macro.arg

if (unified) return unified
throw new check.Error(
throw check.createError(
'incompatible_argument_type',
`${macro.functionDesc} argument must be compatible type, got '${l}' and '${r}'`,

@@ -162,0 +169,0 @@ macro.arg

import {UnsignedInt, Duration} from './functions.js'
import {EvaluationError} from './errors.js'
import {isArray, hasOwn, objKeys} from './globals.js'
import {evaluationError} from './errors.js'
import {isArray, hasOwn, objKeys, MIN_UINT, MIN_INT, MAX_INT} from './globals.js'

@@ -9,9 +9,15 @@ export function registerOverloads(registry) {

const MAX_INT = 9223372036854775807n
const MIN_INT = -9223372036854775808n
function verifyInteger(v, ast) {
if (v <= MAX_INT && v >= MIN_INT) return v
throw new EvaluationError(`integer overflow: ${v}`, ast)
throw evaluationError('numeric_overflow', `integer overflow: ${v}`, ast)
}
function throwDivisionByZero(ast) {
throw evaluationError('division_by_zero', 'division by zero', ast)
}
function throwModuloByZero(ast) {
throw evaluationError('modulo_by_zero', 'modulo by zero', ast)
}
unaryOverload('!', 'bool', (a) => !a)

@@ -25,7 +31,7 @@ unaryOverload('-', 'int', (a) => -a)

binaryOverload('int', '/', 'int', (a, b, ast) => {
if (b === 0n) throw new EvaluationError('division by zero', ast)
if (b === MIN_UINT) return throwDivisionByZero(ast)
return a / b
})
binaryOverload('int', '%', 'int', (a, b, ast) => {
if (b === 0n) throw new EvaluationError('modulo by zero', ast)
if (b === MIN_UINT) return throwModuloByZero(ast)
return a % b

@@ -160,7 +166,7 @@ })

binaryOverload('uint', '/', 'uint', (a, b, ast) => {
if (b.valueOf() === 0n) throw new EvaluationError('division by zero', ast)
if (b.valueOf() === MIN_UINT) return throwDivisionByZero(ast)
return new UnsignedInt(a.valueOf() / b.valueOf())
})
binaryOverload('uint', '%', 'uint', (a, b, ast) => {
if (b.valueOf() === 0n) throw new EvaluationError('modulo by zero', ast)
if (b.valueOf() === MIN_UINT) return throwModuloByZero(ast)
return new UnsignedInt(a.valueOf() % b.valueOf())

@@ -216,3 +222,3 @@ })

}
throw new EvaluationError(`Cannot compare values of type ${typeof a}`, ast)
throw evaluationError('invalid_comparison_type', `Cannot compare values of type ${typeof a}`, ast)
}
import {UnsignedInt} from './functions.js'
import {ParseError} from './errors.js'
import {parseError} from './errors.js'
import {OPERATORS as OPS} from './operators.js'

@@ -62,2 +62,12 @@ import {RESERVED, isAsync} from './globals.js'

const ESCAPE_ERRORS = {
bytes_unicode_escape: (e) => `\\${e} not allowed in bytes literals`,
invalid_unicode_escape: (e) => `Invalid Unicode escape: \\${e}`,
invalid_unicode_surrogate: (e) => `Invalid Unicode surrogate: \\${e}`,
invalid_hex_escape: (e) => `Invalid hex escape: \\${e}`,
invalid_octal_escape: () => 'Octal escape must be 3 digits',
octal_escape_out_of_range: (e) => `Octal escape out of range: \\${e}`,
invalid_escape_sequence: (e) => `Invalid escape sequence: \\${e}`
}
const STRING_ESCAPES = {

@@ -80,10 +90,15 @@ '\\': '\\',

#meta
constructor(input, pos, op, args) {
this.#meta = {input, pos, evaluate: op.evaluate, check: op.check}
#input
constructor(input, pos, start, end, op, args) {
this.#meta = {check: op.check, evaluate: op.evaluate}
this.#input = input
this.op = op.name
this.args = args
this.pos = pos
this.start = start
this.end = end
}
clone(op, args) {
return new ASTNode(this.#meta.input, this.#meta.pos, op, args)
return new ASTNode(this.#input, this.pos, this.start, this.end, op, args)
}

@@ -95,2 +110,6 @@

get input() {
return this.#input
}
#computeIsAsync() {

@@ -168,10 +187,6 @@ const ast = this.#meta.alternate ?? this

get input() {
return this.#meta.input
get range() {
return {start: this.start, end: this.end}
}
get pos() {
return this.#meta.pos
}
toOldStructure() {

@@ -295,3 +310,8 @@ const args = Array.isArray(this.args) ? this.args : [this.args]

throw new ParseError(`Unexpected character: ${ch}`, {pos, input})
throw parseError('unexpected_character', `Unexpected character: ${ch}`, {
pos,
start: pos,
end: pos + 1,
input
})
}

@@ -309,3 +329,8 @@ }

if (Number.isFinite(value)) return this.token(start, TOKEN.NUMBER, value)
throw new ParseError(`Invalid number: ${value}`, {pos: start, input: this.input})
throw parseError('invalid_number', `Invalid number: ${value}`, {
pos: start,
start,
end,
input: this.input
})
}

@@ -326,6 +351,7 @@

throw new ParseError(isHex ? `Invalid hex integer: ${string}` : `Invalid integer: ${string}`, {
pos: start,
input: this.input
})
throw parseError(
isHex ? 'invalid_hex_integer' : 'invalid_integer',
isHex ? `Invalid hex integer: ${string}` : `Invalid integer: ${string}`,
{pos: start, start, end: this.pos, input: this.input}
)
}

@@ -345,3 +371,9 @@

pos = this._readDigits(input, length, pos)
if (start === pos) throw new ParseError('Invalid exponent', {pos, input})
if (start === pos)
throw parseError('invalid_exponent', 'Invalid exponent', {
pos,
start: pos,
end: Math.min(pos + 1, input.length),
input
})
}

@@ -379,7 +411,7 @@ return pos

_closeQuotedString(rawValue, prefix, pos) {
_closeQuotedString(rawStart, rawValue, prefix, pos) {
switch (prefix) {
case 'b':
case 'B': {
const processed = this.processEscapes(rawValue, true)
const processed = this.processEscapes(rawStart, rawValue, true)
const bytes = new Uint8Array(processed.length)

@@ -394,3 +426,3 @@ for (let i = 0; i < processed.length; i++) bytes[i] = processed.charCodeAt(i) & 0xff

default: {
const value = this.processEscapes(rawValue, false)
const value = this.processEscapes(rawStart, rawValue, false)
return this.token(pos, TOKEN.STRING, value)

@@ -409,8 +441,14 @@ }

case delimiter:
const rawValue = input.slice(start + 1, pos)
const rawStart = start + 1
const rawValue = input.slice(rawStart, pos)
this.pos = ++pos
return this._closeQuotedString(rawValue, prefix, start)
return this._closeQuotedString(rawStart, rawValue, prefix, start)
case '\n':
case '\r':
throw new ParseError('Newlines not allowed in single-quoted strings', {pos: start, input})
throw parseError('newline_in_string', 'Newlines not allowed in single-quoted strings', {
pos,
start: pos,
end: pos + 1,
input
})
case '\\':

@@ -421,3 +459,8 @@ pos++

}
throw new ParseError('Unterminated string', {pos: start, input})
throw parseError('unterminated_string', 'Unterminated string', {
pos: start,
start,
end: input.length,
input
})
}

@@ -434,5 +477,6 @@

if (input[pos + 1] === delimiter && input[pos + 2] === delimiter) {
const rawValue = input.slice(start + 3, pos)
const rawStart = start + 3
const rawValue = input.slice(rawStart, pos)
this.pos = pos + 3
return this._closeQuotedString(rawValue, prefix, start)
return this._closeQuotedString(rawStart, rawValue, prefix, start)
}

@@ -445,12 +489,28 @@ break

}
throw new ParseError('Unterminated triple-quoted string', {pos: start, input})
throw parseError('unterminated_triple_quoted_string', 'Unterminated triple-quoted string', {
pos: start,
start,
end: input.length,
input
})
}
processEscapes(str, isBytes) {
#escapeErr(code, offset, len, i, chars, extra) {
const start = offset + i
return parseError(code, ESCAPE_ERRORS[code](extra), {
input: this.input,
pos: start,
start,
end: Math.min(start + chars, offset + len)
})
}
processEscapes(offset, str, isBytes) {
if (!str.includes('\\')) return str
const len = str.length
let result = ''
let i = 0
while (i < str.length) {
if (str[i] !== '\\' || i + 1 >= str.length) {
while (i < len) {
if (str[i] !== '\\' || i + 1 >= len) {
result += str[i++]

@@ -464,29 +524,29 @@ continue

i += 2
} else if (next === 'u') {
if (isBytes) throw new ParseError('\\u not allowed in bytes literals')
const hex = str.substring(i + 2, (i += 6))
if (!/^[0-9a-fA-F]{4}$/.test(hex)) throw new ParseError(`Invalid Unicode escape: \\u${hex}`)
} else if (next === 'u' || next === 'U') {
if (isBytes) throw this.#escapeErr('bytes_unicode_escape', offset, len, i, 2, next)
const hexLen = next === 'u' ? 4 : 8
const hex = str.substring(i + 2, i + 2 + hexLen)
const c = Number.parseInt(hex, 16)
if (c >= 0xd800 && c <= 0xdfff) throw new ParseError(`Invalid Unicode surrogate: \\u${hex}`)
result += String.fromCharCode(c)
} else if (next === 'U') {
if (isBytes) throw new ParseError('\\U not allowed in bytes literals')
const hex = str.substring(i + 2, (i += 10))
if (!/^[0-9a-fA-F]{8}$/.test(hex)) throw new ParseError(`Invalid Unicode escape: \\U${hex}`)
const c = Number.parseInt(hex, 16)
if (c > 0x10ffff) throw new ParseError(`Invalid Unicode escape: \\U${hex}`)
if (c >= 0xd800 && c <= 0xdfff) throw new ParseError(`Invalid Unicode surrogate: \\U${hex}`)
if (hex.length !== hexLen || !/^[0-9a-fA-F]+$/.test(hex) || c > 0x10ffff)
throw this.#escapeErr('invalid_unicode_escape', offset, len, i, 2 + hexLen, next + hex)
if (c >= 0xd800 && c <= 0xdfff)
throw this.#escapeErr('invalid_unicode_surrogate', offset, len, i, 2 + hexLen, next + hex)
result += String.fromCodePoint(c)
i += 2 + hexLen
} else if (next === 'x' || next === 'X') {
const h = str.substring(i + 2, (i += 4))
if (!/^[0-9a-fA-F]{2}$/.test(h)) throw new ParseError(`Invalid hex escape: \\${next}${h}`)
const h = str.substring(i + 2, i + 4)
if (!/^[0-9a-fA-F]{2}$/.test(h))
throw this.#escapeErr('invalid_hex_escape', offset, len, i, 4, next + h)
result += String.fromCharCode(Number.parseInt(h, 16))
i += 4
} else if (next >= '0' && next <= '7') {
const o = str.substring(i + 1, (i += 4))
if (!/^[0-7]{3}$/.test(o)) throw new ParseError('Octal escape must be 3 digits')
const o = str.substring(i + 1, i + 4)
if (!/^[0-7]{3}$/.test(o)) throw this.#escapeErr('invalid_octal_escape', offset, len, i, 4)
const value = Number.parseInt(o, 8)
if (value > 0xff) throw new ParseError(`Octal escape out of range: \\${o}`)
if (value > 0xff) throw this.#escapeErr('octal_escape_out_of_range', offset, len, i, 4, o)
result += String.fromCharCode(value)
i += 4
} else {
throw new ParseError(`Invalid escape sequence: \\${next}`)
throw this.#escapeErr('invalid_escape_sequence', offset, len, i, 2, next)
}

@@ -534,4 +594,6 @@ }

#limitExceeded(limitKey, pos = this.pos) {
throw new ParseError(`Exceeded ${limitKey} (${this.limits[limitKey]})`, {
throw parseError('limit_exceeded', `Exceeded ${limitKey} (${this.limits[limitKey]})`, {
pos,
start: pos,
end: pos,
input: this.input

@@ -541,4 +603,4 @@ })

#node(pos, op, args) {
const node = new ASTNode(this.input, pos, op, args)
#node(start, end, op, args, pos = start) {
const node = new ASTNode(this.input, pos, start, end, op, args)
if (!this.astNodesRemaining--) this.#limitExceeded('maxAstNodes', pos)

@@ -548,2 +610,22 @@ return node

#infixNode(op, left, right) {
return this.#node(left.start, right.end, op, [left, right])
}
#ternaryNode(expression, consequent, alternate) {
return this.#node(expression.start, alternate.end, OPS.ternary, [
expression,
consequent,
alternate
])
}
#unaryNode(pos, op, arg) {
return this.#node(pos, arg.end, op, arg)
}
#accessNode(op, left, right, end, pos = left.start) {
return this.#node(left.start, end, op, [left, right], pos)
}
#advanceToken(returnValue = this.pos) {

@@ -564,5 +646,6 @@ const l = this.lexer.nextToken()

if (this.type === expectedType) return this.#advanceToken()
throw new ParseError(
throw parseError(
'expected_token',
`Expected ${TOKEN_BY_NUMBER[expectedType]}, got ${TOKEN_BY_NUMBER[this.type]}`,
{pos: this.pos, input: this.input}
{pos: this.pos, start: this.pos, end: this.lexer.pos, input: this.input}
)

@@ -577,3 +660,5 @@ }

parse(input) {
if (typeof input !== 'string') throw new ParseError('Expression must be a string')
if (typeof input !== 'string') {
throw parseError('expression_must_be_string', 'Expression must be a string')
}
this.input = this.lexer.reset(input)

@@ -587,12 +672,20 @@ this.#advanceToken()

throw new ParseError(`Unexpected character: '${this.input[this.lexer.pos - 1]}'`, {
pos: this.pos,
input: this.input
})
throw parseError(
'unexpected_character',
`Unexpected character: '${this.input[this.lexer.pos - 1]}'`,
{
pos: this.pos,
start: this.pos,
end: this.lexer.pos,
input: this.input
}
)
}
#expandMacro(pos, op, args) {
const [methodName, receiver, fnArgs] = op === OPS.rcall ? args : [args[0], null, args[1]]
#expandMacro(start, end, op, args) {
const methodName = args[0]
const receiver = op === OPS.rcall ? args[1] : null
const fnArgs = op === OPS.rcall ? args[2] : args[1]
const decl = this.registry.findMacro(methodName, !!receiver, fnArgs.length)
const ast = this.#node(pos, op, args)
const ast = this.#node(start, end, op, args)
if (!decl) return ast

@@ -610,3 +703,3 @@ const macro = decl.handler({ast, args: fnArgs, receiver, methodName, parser: this})

const questionPos = this.#advanceToken()
this.#advanceToken()
const consequent = this.parseExpression()

@@ -616,3 +709,3 @@ this.consume(TOKEN.COLON)

this.maxDepthRemaining++
return this.#node(questionPos, OPS.ternary, [expr, consequent, alternate])
return this.#ternaryNode(expr, consequent, alternate)
}

@@ -623,4 +716,6 @@

let expr = this.parseLogicalAnd()
while (this.match(TOKEN.OR))
expr = this.#node(this.#advanceToken(), OPS['||'], [expr, this.parseLogicalAnd()])
while (this.match(TOKEN.OR)) {
this.#advanceToken()
expr = this.#infixNode(OPS['||'], expr, this.parseLogicalAnd())
}
return expr

@@ -632,4 +727,6 @@ }

let expr = this.parseEquality()
while (this.match(TOKEN.AND))
expr = this.#node(this.#advanceToken(), OPS['&&'], [expr, this.parseEquality()])
while (this.match(TOKEN.AND)) {
this.#advanceToken()
expr = this.#infixNode(OPS['&&'], expr, this.parseEquality())
}
return expr

@@ -643,3 +740,4 @@ }

const op = OP_FOR_TOKEN[this.type]
expr = this.#node(this.#advanceToken(), op, [expr, this.parseRelational()])
this.#advanceToken()
expr = this.#infixNode(op, expr, this.parseRelational())
}

@@ -660,3 +758,4 @@ return expr

const op = OP_FOR_TOKEN[this.type]
expr = this.#node(this.#advanceToken(), op, [expr, this.parseAdditive()])
this.#advanceToken()
expr = this.#infixNode(op, expr, this.parseAdditive())
}

@@ -671,3 +770,4 @@ return expr

const op = OP_FOR_TOKEN[this.type]
expr = this.#node(this.#advanceToken(), op, [expr, this.parseMultiplicative()])
this.#advanceToken()
expr = this.#infixNode(op, expr, this.parseMultiplicative())
}

@@ -682,3 +782,4 @@ return expr

const op = OP_FOR_TOKEN[this.type]
expr = this.#node(this.#advanceToken(), op, [expr, this.parseUnary()])
this.#advanceToken()
expr = this.#infixNode(op, expr, this.parseUnary())
}

@@ -690,6 +791,8 @@ return expr

parseUnary() {
if (this.type === TOKEN.NOT)
return this.#node(this.#advanceToken(), OPS.unaryNot, this.parseUnary())
if (this.type === TOKEN.MINUS)
return this.#node(this.#advanceToken(), OPS.unaryMinus, this.parseUnary())
if (this.type === TOKEN.NOT) {
return this.#unaryNode(this.#advanceToken(), OPS.unaryNot, this.parseUnary())
}
if (this.type === TOKEN.MINUS) {
return this.#unaryNode(this.#advanceToken(), OPS.unaryMinus, this.parseUnary())
}
return this.parsePostfix()

@@ -713,9 +816,12 @@ }

const propertyValue = this.value
const propertyPos = this.consume(TOKEN.IDENTIFIER)
const start = this.pos
const end = this.lexer.pos
this.consume(TOKEN.IDENTIFIER)
if (op === OPS.fieldAccess && this.match(TOKEN.LPAREN) && this.#advanceToken()) {
const args = this.parseArgumentList()
const closeEnd = this.lexer.pos
this.consume(TOKEN.RPAREN)
expr = this.#expandMacro(propertyPos, OPS.rcall, [propertyValue, expr, args])
expr = this.#expandMacro(expr.start, closeEnd, OPS.rcall, [propertyValue, expr, args])
} else {
expr = this.#node(propertyPos, op, [expr, propertyValue])
expr = this.#accessNode(op, expr, propertyValue, end, start)
}

@@ -735,4 +841,5 @@ continue

const index = this.parseExpression()
const closeEnd = this.lexer.pos
this.consume(TOKEN.RBRACKET)
expr = this.#node(bracket, op, [expr, index])
expr = this.#accessNode(op, expr, index, closeEnd)
continue

@@ -765,4 +872,6 @@ }

throw new ParseError(`Unexpected token: ${TOKEN_BY_NUMBER[this.type]}`, {
throw parseError('unexpected_token', `Unexpected token: ${TOKEN_BY_NUMBER[this.type]}`, {
pos: this.pos,
start: this.pos,
end: this.lexer.pos,
input: this.input

@@ -773,3 +882,3 @@ })

#consumeLiteral() {
return this.#advanceToken(this.#node(this.pos, OPS.value, this.value))
return this.#advanceToken(this.#node(this.pos, this.lexer.pos, OPS.value, this.value))
}

@@ -779,6 +888,9 @@

const value = this.value
const pos = this.consume(TOKEN.IDENTIFIER)
const end = this.lexer.pos
const start = this.consume(TOKEN.IDENTIFIER)
if (RESERVED.has(value)) {
throw new ParseError(`Reserved identifier: ${value}`, {
pos: pos,
throw parseError('reserved_identifier', `Reserved identifier: ${value}`, {
pos: start,
start,
end,
input: this.input

@@ -788,7 +900,8 @@ })

if (!this.match(TOKEN.LPAREN)) return this.#node(pos, OPS.id, value)
if (!this.match(TOKEN.LPAREN)) return this.#node(start, end, OPS.id, value)
this.#advanceToken()
const args = this.parseArgumentList()
const closeEnd = this.lexer.pos
this.consume(TOKEN.RPAREN)
return this.#expandMacro(pos, OPS.call, [value, args])
return this.#expandMacro(start, closeEnd, OPS.call, [value, args])
}

@@ -804,3 +917,3 @@

parseList() {
const token = this.consume(TOKEN.LBRACKET)
const start = this.consume(TOKEN.LBRACKET)
const elements = []

@@ -820,8 +933,9 @@ let remainingElements = this.limits.maxListElements

const closeEnd = this.lexer.pos
this.consume(TOKEN.RBRACKET)
return this.#node(token, OPS.list, elements)
return this.#node(start, closeEnd, OPS.list, elements)
}
parseMap() {
const token = this.consume(TOKEN.LBRACE)
const start = this.consume(TOKEN.LBRACE)
const props = []

@@ -841,4 +955,5 @@ let remainingEntries = this.limits.maxMapEntries

const closeEnd = this.lexer.pos
this.consume(TOKEN.RBRACE)
return this.#node(token, OPS.map, props)
return this.#node(start, closeEnd, OPS.map, props)
}

@@ -845,0 +960,0 @@

@@ -27,3 +27,3 @@ /**

constructor(options: {
kind: 'primitive' | 'list' | 'map' | 'message' | 'enum'
kind: 'primitive' | 'list' | 'map' | 'message' | 'enum' | 'dyn' | 'optional' | 'param'
type: string

@@ -36,4 +36,4 @@ name: string

/** The kind of type (primitive, list, map, message, enum). */
kind: 'primitive' | 'list' | 'map' | 'message' | 'enum'
/** The kind of type (primitive, list, map, message, enum, dyn, optional, param). */
kind: 'primitive' | 'list' | 'map' | 'message' | 'enum' | 'dyn' | 'optional' | 'param'

@@ -55,5 +55,26 @@ /** The type name. */

/** The underlying non-dyn type. */
unwrappedType: TypeDeclaration
/** A wrapped dyn variant of this type. */
wrappedType: TypeDeclaration
/** True when this declaration contains dyn anywhere in its structure. */
hasDynType: boolean
/** True when this declaration contains placeholder type parameters. */
hasPlaceholderType: boolean
/** Check if this type is 'dyn' or 'bool'. */
isDynOrBool(): boolean
/** Check if this declaration represents an empty aggregate placeholder. */
isEmpty(): boolean
/** Attempt to unify this declaration with another declaration. */
unify(registry: Registry, other: TypeDeclaration): TypeDeclaration | null
/** Replace placeholder types using the provided bindings. */
templated(registry: Registry, bind?: Map<string, TypeDeclaration>): TypeDeclaration
/** Convert to string representation. */

@@ -92,2 +113,3 @@ toString(): string

type: TypeDeclaration
optional: TypeDeclaration
list: TypeDeclaration

@@ -109,2 +131,142 @@ 'list<dyn>': TypeDeclaration

export type RegisteredVariableType = string | TypeDeclaration
export interface RegisterVariableMetadata {
description?: string
}
export interface RegisterVariableTypeOptions extends RegisterVariableMetadata {
type: RegisteredVariableType
}
export interface RegisterVariableSchemaOptions extends RegisterVariableMetadata {
schema: ObjectSchema
}
export type RegisterVariableOptions = RegisterVariableTypeOptions | RegisterVariableSchemaOptions
export type RegisterVariableDeclaration = {name: string} & RegisterVariableOptions
export type RegisterConstantDeclaration = {name: string; value: any} & RegisterVariableOptions
export type RegisteredFunctionHandler = (...args: any[]) => any
export interface RegisteredFunctionParam {
name?: string
type?: string
description?: string
}
export interface RegisteredFunctionTypedParam extends RegisteredFunctionParam {
type: string
}
export interface RegisterFunctionMetadata {
description?: string
params?: RegisteredFunctionParam[]
async?: boolean
}
export interface RegisterFunctionOptions extends RegisterFunctionMetadata {
handler: RegisteredFunctionHandler
}
export interface RegisterFunctionWithSignature extends RegisterFunctionOptions {
signature: string
}
export interface RegisterFunctionWithName extends Omit<RegisterFunctionOptions, 'params'> {
name: string
receiverType?: string
returnType: string
params: RegisteredFunctionTypedParam[]
}
export type RegisterFunctionDeclaration = RegisterFunctionWithSignature | RegisterFunctionWithName
export type RegisteredTypeFieldDeclaration =
| string
| {
id?: number
keyType?: string
map?: boolean
repeated?: boolean
type?: string
}
export interface RegisterTypeWithCtor {
ctor: Function
fields?: Record<string, RegisteredTypeFieldDeclaration>
convert?: (value: any) => any
}
export interface RegisterTypeWithFields {
fields: Record<string, RegisteredTypeFieldDeclaration>
convert?: (value: any) => any
}
export interface RegisterTypeWithSchema {
schema: ObjectSchema
ctor?: Function
convert?: (value: any) => any
}
export type NamedTypeIdentity = {name: string; fullName?: string} | {fullName: string; name?: string}
export type RegisterTypeDeclaration =
| ({name?: string; fullName?: string} & RegisterTypeWithCtor)
| (NamedTypeIdentity & RegisterTypeWithFields)
| (NamedTypeIdentity & RegisterTypeWithSchema)
export type RegisterTypeDefinition =
| Function
| RegisterTypeWithCtor
| RegisterTypeWithFields
| RegisterTypeWithSchema
export interface RegisteredType {
name: string
typeType: Type
type: TypeDeclaration
ctor: Function
convert?: (value: any) => any
fields?: Record<string, TypeDeclaration>
}
export class VariableDeclaration {
constructor(name: string, type: TypeDeclaration, description?: string | null, value?: any)
readonly name: string
readonly type: TypeDeclaration
readonly description: string | null
readonly constant: boolean
readonly value: any
}
export interface DefinitionVariable {
name: string
description: string | null
type: string
}
export interface DefinitionFunctionParam {
name: string
type: string
description: string | null
}
export interface DefinitionFunction {
signature: string
name: string
description: string | null
receiverType: string | null
returnType: string
params: DefinitionFunctionParam[]
}
export interface DefinitionsResult {
variables: DefinitionVariable[]
functions: DefinitionFunction[]
}
/**

@@ -123,13 +285,13 @@ * Registry for managing function overloads, operator overloads, and type mappings.

* @param signature - Function signature in format 'name(type1, type2): returnType' or 'Type.method(args): returnType'
* @param handlerOrOptions - Either the function implementation or an options object with handler and optional typeCheck
* @param handler - The function implementation
* @param opts - Optional metadata such as descriptions, param docs, and async hints
* Supports signature-based registration as well as a single declaration object.
*/
registerFunctionOverload(
signature: string,
handlerOrOptions:
| ((...args: any[]) => any)
| {
handler: (...args: any[]) => any
typeCheck?: (checker: any, receiverType: string, args: any[]) => string
}
handler: RegisteredFunctionHandler,
opts?: RegisterFunctionMetadata
): void
registerFunctionOverload(signature: string, options: RegisterFunctionOptions): void
registerFunctionOverload(definition: RegisterFunctionDeclaration): void

@@ -148,11 +310,9 @@ /**

* @param typename - The name of the type
* @param definition - Either a constructor function or an object with ctor/fields/convert
* @param definition - A constructor function or registration object with ctor, fields, schema, and/or convert
*/
registerType(
typename: string,
definition:
| Function
| {ctor: Function; fields?: Record<string, string>; convert?: (value: any) => any}
| {fields: Record<string, string>; convert?: (value: any) => any}
): void
definition: RegisterTypeDefinition
): RegisteredType
registerType(definition: RegisterTypeDeclaration): RegisteredType

@@ -170,15 +330,16 @@ /**

* via `registerType` with runtime conversion support.
* @param name - The variable name
* @param type - The variable type name, declaration, or options with schema
* Supports `name + type`, `name + {type|schema}`, and a single declaration object.
*/
registerVariable(
name: string,
type:
| string
| TypeDeclaration
| {type: string; description?: string}
| {schema: ObjectSchema; description?: string}
): this
registerVariable(name: string, type: RegisteredVariableType, opts?: RegisterVariableMetadata): this
registerVariable(name: string, options: RegisterVariableOptions): this
registerVariable(definition: RegisterVariableDeclaration): this
/**
* Register a constant value that is always available without requiring evaluation context.
* Supports `name + type + value` and a single declaration object.
*/
registerConstant(name: string, type: RegisteredVariableType, value: any): this
registerConstant(definition: RegisterConstantDeclaration): this
/**
* Register a unary operator overload.

@@ -214,10 +375,13 @@ * @param op - The operator symbol ('-' or '!')

/** Read back user-facing variable/function definitions. */
getDefinitions(): DefinitionsResult
/** Registered object types keyed by CEL typename. */
readonly objectTypes: Map<string, any>
readonly objectTypes: Map<string, RegisteredType>
/** Map of constructors to their registered type metadata. */
readonly objectTypesByConstructor: Map<Function | undefined, any>
readonly objectTypesByConstructor: Map<Function | undefined, RegisteredType>
/** Registered variables and their type declarations. */
readonly variables: Map<string, TypeDeclaration>
readonly variables: Map<string, VariableDeclaration>

@@ -232,8 +396,8 @@ /** Whether optional types/functions are enabled for this registry. */

export interface RegistryOptions {
objectTypes?: Map<string, any>
objectTypesByConstructor?: Map<Function | undefined, any>
objectTypes?: Map<string, RegisteredType>
objectTypesByConstructor?: Map<Function | undefined, RegisteredType>
functionDeclarations?: Map<string, any>
operatorDeclarations?: Map<string, any>
typeDeclarations?: Map<string, TypeDeclaration>
variables?: Map<string, TypeDeclaration>
variables?: Map<string, VariableDeclaration>
unlistedVariablesAreDyn?: boolean

@@ -240,0 +404,0 @@ enableOptionalTypes?: boolean

@@ -1,2 +0,2 @@

import {EvaluationError} from './errors.js'
import {EvaluationError, evaluationError} from './errors.js'
import {UnsignedInt} from './functions.js'

@@ -188,3 +188,4 @@ import {Optional, OPTIONAL_NONE, toggleOptionalTypes} from './optional.js'

if (type.matchesValueType(value, ev)) return value
throw new EvaluationError(
throw evaluationError(
'field_type_mismatch',
`Field '${key}' is not of type '${type}', got '${ev.debugType(value)}'`,

@@ -201,3 +202,4 @@ ast

throw new EvaluationError(
throw evaluationError(
'field_type_mismatch',
`Field '${key}' is not of type '${this.valueType}', got '${ev.debugType(value)}'`,

@@ -229,3 +231,4 @@ ast

if (!obj) return
throw new EvaluationError(
throw evaluationError(
'index_out_of_bounds',
`No such key: index out of bounds, index ${key} ${

@@ -240,3 +243,4 @@ key < 0 ? '< 0' : `>= size ${obj.length || obj.size}`

throw new EvaluationError(
throw evaluationError(
'list_item_type_mismatch',
`List item with index '${key}' is not of type '${this.valueType}', got '${ev.debugType(value)}'`,

@@ -252,3 +256,3 @@ ast

if (v !== undefined) return v
throw new EvaluationError(`No such key: ${key}`, ast)
throw evaluationError('no_such_key', `No such key: ${key}`, ast)
}

@@ -1360,3 +1364,5 @@

if (context === undefined || context === null) return
if (typeof context !== 'object') throw new EvaluationError('Context must be an object')
if (typeof context !== 'object') {
throw evaluationError('invalid_context', 'Context must be an object')
}
if (context instanceof Map) this.#contextMap = context

@@ -1382,3 +1388,3 @@ else this.#contextObj = context

const v = this.getValue(ast.args)
if (v === undefined) throw new ev.Error(`Unknown variable: ${ast.args}`, ast)
if (v === undefined) throw ev.createError('unknown_variable', `Unknown variable: ${ast.args}`, ast)
if (ast.checkedType.matchesValueType(v, ev)) return v

@@ -1394,3 +1400,7 @@

throw new ev.Error(`Variable '${ast.args}' is not of type '${type}', got '${valueType}'`, ast)
throw ev.createError(
'variable_type_mismatch',
`Variable '${ast.args}' is not of type '${type}', got '${valueType}'`,
ast
)
}

@@ -1436,3 +1446,6 @@

throw new ev.Error(`Variable '${this.iterVar}' is not of type '${type}', got '${valueType}'`)
throw ev.createError(
'variable_type_mismatch',
`Variable '${this.iterVar}' is not of type '${type}', got '${valueType}'`
)
}

@@ -1439,0 +1452,0 @@

@@ -1,1 +0,8 @@

export {serialize, ASTNode} from './index'
import type {ASTNode} from './index.js'
export function serialize(ast: ASTNode): string
declare const serializeDefault: typeof serialize
export type {ASTNode} from './index.js'
export default serializeDefault

@@ -1,2 +0,2 @@

import {TypeError, EvaluationError} from './errors.js'
import {attachErrorAst, evaluationError, typeError} from './errors.js'
import {Base} from './operators.js'

@@ -16,3 +16,3 @@ const toDynTypeBinding = new Map().set('A', 'dyn').set('T', 'dyn').set('K', 'dyn').set('V', 'dyn')

super(opts)
this.Error = isEvaluating ? EvaluationError : TypeError
this.createError = isEvaluating ? evaluationError : typeError
}

@@ -27,3 +27,7 @@

check(ast, ctx) {
return (ast.checkedType ??= ast.check(this, ast, ctx))
try {
return (ast.checkedType ??= ast.check(this, ast, ctx))
} catch (error) {
throw attachErrorAst(error, ast)
}
}

@@ -40,3 +44,3 @@

if (indexTypeName === 'int' || indexTypeName === 'dyn') return leftType.valueType
throw new this.Error(`List index must be int, got '${indexTypeName}'`, ast)
throw this.createError('invalid_index_type', `List index must be int, got '${indexTypeName}'`, ast)
}

@@ -49,3 +53,4 @@

if (!(indexTypeName === 'string' || indexTypeName === 'dyn')) {
throw new this.Error(
throw this.createError(
'invalid_index_type',
`Cannot index type '${leftType.name}' with type '${indexTypeName}'`,

@@ -66,3 +71,3 @@ ast

if (allowMissingField) return this.dynType
throw new this.Error(`No such key: ${keyName}`, ast)
throw this.createError('no_such_key', `No such key: ${keyName}`, ast)
}

@@ -74,3 +79,3 @@ }

// No other types support indexing/property access
throw new this.Error(`Cannot index type '${this.formatType(leftType)}'`, ast)
throw this.createError('cannot_index_type', `Cannot index type '${this.formatType(leftType)}'`, ast)
}

@@ -77,0 +82,0 @@

{
"name": "@marcbachmann/cel-js",
"version": "7.5.3",
"version": "7.6.0",
"description": "A lightweight Common Expression Language (CEL) implementation in JavaScript with zero dependencies",

@@ -72,12 +72,12 @@ "keywords": [

"service": "drone",
"commit": "7497170f8567f37a50d9428243fcc7140c02c2a2",
"build": "256",
"buildUrl": "https://drone.livingdocs.io/marcbachmann/cel-js/256",
"commit": "d600a93110f1bfc63833e39a84de3086b382879b",
"build": "261",
"buildUrl": "https://drone.livingdocs.io/marcbachmann/cel-js/261",
"branch": "main",
"jobUrl": "https://drone.livingdocs.io/marcbachmann/cel-js/256",
"jobUrl": "https://drone.livingdocs.io/marcbachmann/cel-js/261",
"isPr": false,
"slug": "marcbachmann/cel-js",
"root": "/drone/src",
"date": "2026-03-10T23:23:14.229Z"
"date": "2026-03-26T01:26:14.342Z"
}
}

@@ -607,3 +607,3 @@ # @marcbachmann/cel-js [![npm version](https://img.shields.io/npm/v/@marcbachmann/cel-js.svg)](https://www.npmjs.com/package/@marcbachmann/cel-js) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

```javascript
import {evaluate, ParseError, EvaluationError, TypeError} from '@marcbachmann/cel-js'
import {Environment, evaluate, ParseError, EvaluationError, TypeError} from '@marcbachmann/cel-js'

@@ -614,5 +614,6 @@ try {

if (error instanceof ParseError) {
console.error('Syntax error:', error.message)
console.error('Syntax error:', error.code, error.range, error.summary)
console.error(error.message) // Includes source highlighting for humans
} else if (error instanceof EvaluationError) {
console.error('Runtime error:', error.message)
console.error('Runtime error:', error.code, error.range, error.summary)
}

@@ -624,4 +625,5 @@ }

const result = env.check('x + "string"')
if (!result.valid && result.error instanceof TypeError) {
console.error('Type error:', result.error.message)
if (!result.valid) {
const error = result.error
console.error('Type error:', error.code, error.range)
}

@@ -628,0 +630,0 @@ ```