UNPKG

mathjslab

Version:

MathJSLab - An interpreter with language syntax like MATLAB®/Octave, ISBN 978-65-00-82338-7.

809 lines (808 loc) 23 kB
/** * MATLAB®/Octave like syntax parser/interpreter/compiler. */ import type { NodeInput, NodeExpr, NodeIdentifier, NodeFunctionDefinition, NodeBuiltInFunction, NameEntry, NameTable, FunctionTable, AliasNameTable, BuiltInFunctionTable, CommandWordListTable, UndefinedReferenceTable } from './AST'; import { ComplexType, FunctionHandle } from './AST'; import type { MathObject, UnaryMathOperation, BinaryMathOperation, KeyOfTypeOfMathOperation } from './MathOperation'; /** * `response` type */ type ExitStatus = number; type ExitStatusValues = Record<string, ExitStatus>; /** * # Scope * * Represents a **lexical scope**. * * A `Scope` stores: * - variable bindings (`nameTable`) * - function bindings (`functionTable`) * - unresolved references for forward reference resolution (`undefinedReferenceTable`) * * Scopes are organized in a **parent chain**, forming a lexical environment. * * --- * * ## Resolution Model * * Name and function resolution follow this order: * * 1. Current scope * 2. Parent scope * 3. Recursively upward * * --- * * ## Mutation Semantics * * - `define*` → affects **current scope only** * - `remove*` → removes from **current scope only** * - `clear*` → removes from **entire scope chain** * * --- * * ## Notes * * - Tables use `Object.create(null)` to avoid prototype pollution. * - Resolution is read-only (no mutation). * - Supports shadowing (local overrides parent). */ declare class Scope { parent?: Scope | undefined; nameTable: NameTable; functionTable: FunctionTable; undefinedReferenceTable: UndefinedReferenceTable; /** * Private constructor. * * Use {@link Scope.create}. * * @param parent - Parent scope (optional) * @param nameTable - Variable table * @param functionTable - Function table * @param undefinedReferenceTable - Undefined references table */ private constructor(); /** * Factory method for creating a new scope. * * @param parent - Parent scope (optional) * @returns New Scope instance */ static readonly create: (parent?: Scope) => Scope; /** * Defines a variable in the current scope. * * Overwrites existing local definition if present. * * @param name - Identifier * @param node - Value node * @param undefinedReference - Optional unresolved reference tag * @returns Created/updated entry */ defineName(name: string, node: NodeInput, undefinedReference?: string): NameEntry; /** * Defines multiple variables in the current scope. * * @param table - Map of name → node */ defineNameTable(table: Record<string, NodeInput>): void; /** * Resolves a variable using lexical lookup. * * @param name - Identifier * @returns First matching entry or `undefined` */ resolveName(name: string): NameEntry | undefined; /** * Checks if a variable exists in the current scope. * * @param name - Identifier * @returns `true` if exists locally */ hasLocalName(name: string): boolean; /** * Removes a variable from the current scope only. * * @param name - Identifier */ removeName(name: string): void; /** * Removes a variable from the entire scope chain. * * @param name - Identifier */ clearName(name: string): void; /** * Defines a function in the current scope. * * @param name - Function name * @param func - Function definition * @returns Stored function */ defineFunction(name: string, func: NodeFunctionDefinition): NodeFunctionDefinition; /** * Defines multiple functions in the current scope. * * @param table - Function table */ defineFunctionTable(table: FunctionTable): void; /** * Resolves a function using lexical lookup. * * @param name - Function name * @returns Function or `undefined` */ resolveFunction(name: string): NodeFunctionDefinition | NodeBuiltInFunction | undefined; /** * Checks if a function exists in the current scope. * * @param name - Function name * @returns `true` if exists locally */ hasLocalFunction(name: string): boolean; /** * Removes a function from the current scope only. * * @param name - Function name */ removeFunction(name: string): void; /** * Removes a function from the entire scope chain. * * @param name - Function name */ clearFunction(name: string): void; /** * Binds parameters to arguments in the current scope. * * No validation is performed. * * @param names - Parameter names * @param args - Argument expressions */ bindParameters(names: string[], args: NodeExpr[]): void; /** * Binds parameters to arguments with arity checking. * * Throws an error if the number of arguments does not match * the number of parameters. * * @param names - Parameter names * @param args - Argument expressions * @throws Error if arity mismatch */ bindParametersChecked(names: string[], args: NodeExpr[]): void; /** * Registers an unresolved reference (forward reference). * * @param name - Identifier * @param undefinedReference - Reference id * @returns Updated reference list */ defineUndefinedReference(name: string, undefinedReference: string): string[]; /** * Resolves unresolved references using lexical lookup. * * @param name - Identifier * @returns Array of references or `undefined` */ resolveUndefinedReference(name: string): string[] | undefined; /** * Removes unresolved references from the current scope only. * * @param name - Identifier */ removeUndefinedReference(name: string): void; /** * Removes unresolved references from the entire scope chain. * * @param name - Identifier */ clearUndefinedReference(name: string): void; } /** * # Callable * * Discriminated union representing any **callable entity** in the engine. * * A callable is anything that can be invoked during evaluation. * * --- * * ## Variants * * - `BUILTIN` → Built-in function implemented in the runtime * - `LAMBDA` → Anonymous function (function handle with expression) * - `FCNDEF` → User-defined function (declared with `function`) * * --- * * ## Design Notes * * This abstraction allows the evaluator to treat all callable entities * uniformly, while preserving their specific execution semantics. * * Each variant wraps a different underlying AST/runtime representation. */ type Callable = BuiltinCallable | LambdaCallable | FunctionDefinitionCallable; /** * Represents a **built-in callable function**. * * Built-in functions are implemented directly in the runtime * (e.g., `sin`, `cos`, `exp`). */ interface BuiltinCallable { /** * Discriminator tag. */ type: 'BUILTIN'; /** * AST node representing the built-in function. */ node: NodeBuiltInFunction; } /** * Represents an **anonymous function (lambda)**. * * This wraps a {@link FunctionHandle} whose `id` is `undefined` * and contains: * - parameter list * - expression body * - optional closure */ interface LambdaCallable { /** * Discriminator tag. */ type: 'LAMBDA'; /** * Function handle representing the lambda. */ node: FunctionHandle & { id: undefined; }; } /** * Represents a **defined function**. * * Typically declared using a function definition construct. */ interface FunctionDefinitionCallable { /** * Discriminator tag. */ type: 'FCNDEF'; /** * AST node representing the function definition. */ node: NodeFunctionDefinition; } /** * # CallFrame * * Represents a **function call frame** in the evaluation stack ({@link EvaluatorWorkspace.callStack}). * * A call frame encapsulates: * - the **execution scope** for the call * - the **callable being executed** * - a link to the **caller frame** (stack chain) * * --- * * ## Role in Evaluation * * During function invocation, a new `CallFrame` is created: * * 1. A new `Scope` is created (child of the defining scope or global scope) * 2. Parameters are bound in that scope * 3. The callable is associated with the frame * 4. The frame is pushed onto the call stack * * This enables: * - proper lexical scoping * - recursion * - nested calls * - stack trace reconstruction * * --- * * ## Stack Structure * * Frames form a linked structure: * * ```text * currentFrame → parentFrame → parentFrame → ... * ``` * * --- * * ## Debugging Support * * Additional metadata is stored to support stack traces: * * - `callSite` → AST node where the call occurred * - `name` → human-readable function name * * --- * * ## Notes * * - `func` may be `undefined` for temporary frames * - `parentFrame` should be consistent with the call stack array */ declare class CallFrame { /** * Execution {@link Scope} for this frame. */ scope: Scope; /** * {@link Callable} being executed in this frame. */ func?: Callable | undefined; /** * AST node where the function call originated. * * Useful for error reporting (line/column in future). */ callSite?: NodeExpr | undefined; /** * Human-readable function name. * * Examples: * - "sin" * - "f" * - "@(x)x^2" */ name?: string | undefined; /** * Parent frame in the call stack. */ parentFrame?: CallFrame | undefined; /** * Creates a new call frame. * * @param scope - Execution {@link Scope} for this frame * @param func - Callable associated with this frame (optional) * @param parentFrame - Caller frame (optional) * @param callSite - {@link AST} node representing the call site (optional) * @param name - Human-readable function name (optional) */ constructor( /** * Execution {@link Scope} for this frame. */ scope: Scope, /** * {@link Callable} being executed in this frame. */ func?: Callable | undefined, /** * AST node where the function call originated. * * Useful for error reporting (line/column in future). */ callSite?: NodeExpr | undefined, /** * Human-readable function name. * * Examples: * - "sin" * - "f" * - "@(x)x^2" */ name?: string | undefined, /** * Parent frame in the call stack. */ parentFrame?: CallFrame | undefined); } /** * # EvaluatorError * * Base class for all evaluation-related errors. * * This class extends the native `Error` and adds optional * support for stack trace frames (`CallFrame[]`), enabling * MATLAB/Octave-like error reporting. * * --- * * ## Design Goals * * - Backward compatible with existing `throw new Error(...)` * - Allows incremental adoption of stack traces * - Provides a unified error hierarchy * * --- * * ## Notes * * - `stackFrames` is optional to support legacy code paths * - Formatting is deferred to `toString()` / `format()` */ declare class EvaluatorError extends Error { /** * Optional call stack snapshot. * * Top frame should be the first element. */ readonly stackFrames?: CallFrame[]; /** * Creates a new EvaluatorError. * * @param message - Error message * @param stackFrames - Optional stack trace snapshot */ constructor(message: string, stackFrames?: CallFrame[]); /** * Formats the error message with optional stack trace. * * @returns Formatted error string */ format(): string; /** * Derives a human-readable name for a frame. */ protected getFrameName(frame: CallFrame): string; /** * Default string representation. */ toString(): string; } declare class EvaluatorWorkspace { /** * Evaluator instance associated to this workspace. */ evaluator?: Evaluator | undefined; /** * Global scope. */ globalScope?: Scope | undefined; /** * Function call stack. */ callStack: CallFrame[]; /** * Built-in function table. */ builtInFunctionTable: Record<string, NodeBuiltInFunction>; /** * Allow forward reference flag. */ allowForwardReference: boolean; /** * * @param globalScope * @param callStack */ loadEvaluatorWorkspace(globalScope?: Scope, callStack?: CallFrame[]): void; /** * Private constructor (only used by `create` static method). * @param globalScope Optional global scope reference. * @param callStack Optional function call stack reference. */ private constructor(); /** * Create {@link EvaluatorWorkspace} object. * @param globalScope Optional global scope reference. * @param callStack Optional function call stack reference. * @returns */ static readonly create: (evaluator?: Evaluator, globalScope?: Scope, callStack?: CallFrame[]) => EvaluatorWorkspace; /** * Native name table. It's inserted in nameTable when Evaluator constructor executed. */ nativeNameTable: Record<string, ComplexType>; /** * Native name table list. */ nativeNameTableList: string[]; /** * Alias name table. */ private aliasNameTable; /** * Alias name function. This property is set at Evaluator instantiation. * @param name Alias name. * @returns Canonical name. */ aliasNameFunction: (name: string) => string; /** * Alias table setup * @param aliasNameTable */ setAliasNameTable(aliasNameTable?: AliasNameTable): void; /** * Get a list of names of defined functions in builtInFunctionTable. */ get builtInFunctionList(): string[]; /** * Current frame (top of call stack) getter. */ get currentFrame(): CallFrame | undefined; /** * Current scope (top of call stack) getter. */ get currentScope(): Scope; /** * Name resolver. * @param name * @returns */ resolveName(name: string): NameEntry | undefined; resolveFunction(name: string): NodeFunctionDefinition | NodeBuiltInFunction | undefined; assignName(name: string, value: NodeInput): void; assignFunction(name: string, func: NodeFunctionDefinition): void; createChildScope(parent?: Scope): Scope; resolveCallable(expr: NodeExpr): Callable | undefined; resolveIdentifier(tree: NodeInput, scope: Scope): NodeExpr; private evaluateArgs; private error; private resolveCallSite; callCallable(callable: Callable, args: NodeExpr[], parent: NodeInput): NodeExpr; apply(expr: NodeExpr, args: NodeExpr[], parent: NodeInput): NodeExpr; /** * Define function in builtInFunctionTable. * @param id Name of function. * @param func Function body. * @param map `true` if function is a mapper function. * @param ev A `boolean` array indicating which function argument should * be evaluated before executing the function. If array is zero-length all * arguments are evaluated. */ defineBuiltInFunction(id: string, func: Function, mapper?: boolean, ev?: boolean[]): void; /** * * @param table */ assignBuiltInFunctionTable(table?: Record<string, NodeBuiltInFunction>): void; /** * Define unary operator function in builtInFunctionTable. * @param id Name of function. * @param func Function body. */ defineUnaryOperatorFunction(id: KeyOfTypeOfMathOperation, func: UnaryMathOperation): void; /** * Define binary operator function in builtInFunctionTable. * @param id Name of function. * @param func Function body. */ defineBinaryOperatorFunction(id: KeyOfTypeOfMathOperation, func: BinaryMathOperation): void; /** * Define define two-or-more operand function in builtInFunctionTable. * @param name * @param func */ defineLeftAssociativeMultipleOperationFunction(id: KeyOfTypeOfMathOperation, func: BinaryMathOperation): void; /** * Push a new frame onto the call stack. * * @param frame - CallFrame to push */ pushCallStackFrame(frame: CallFrame): void; /** * Pop the current frame from the call stack. * * @returns Removed frame */ popCallStackFrame(): CallFrame | undefined; /** * Returns a snapshot of the current stack trace. * * Top frame is the first element. */ private getStackTrace; } /** * EvaluatorConfig type. */ type EvaluatorConfig = { aliasNameTable?: AliasNameTable; externalFunctionTable?: BuiltInFunctionTable; externalCmdWListTable?: CommandWordListTable; }; /** * Increment and decrement operator handler type. */ type IncDecOperator = (tree: NodeIdentifier) => MathObject; /** * Evaluator instance interface. */ interface EvaluatorInterface { debug: boolean; workspace: EvaluatorWorkspace; exitStatus: ExitStatus; precedenceTable: { [key: string]: number; }; Parse(input: string): NodeInput; Restart(): void; Clear(...names: string[]): void; Evaluator(tree: NodeInput, scope?: Scope): NodeInput; Evaluate(tree: NodeInput): NodeInput; Unparse(tree: NodeInput, parentPrecedence?: number): string; UnparserMathML(tree: NodeInput, parentPrecedence: number): string; UnparseMathML(tree: NodeInput, display: 'inline' | 'block'): string; ToMathML(input: string, display: 'inline' | 'block'): string; } /** * `Evaluator` object. */ declare class Evaluator implements EvaluatorInterface { /** * After run `Evaluate` method, the `exitStatus` property will contains * exit state of evaluation. */ static readonly response: ExitStatusValues; /** * Private debug flag. */ private _debug; /** * `debug` getter. */ get debug(): boolean; /** * `debug` setter. */ set debug(value: boolean); /** * Evaluator workspace. */ workspace: EvaluatorWorkspace; /** * Command word list table. */ private commandWordListTable; /** * Evaluator exit status. */ private _exitStatus; /** * Evaluator exit status getter. */ get exitStatus(): ExitStatus; /** * Increment and decrement operator * @param pre `true` if prefixed. `false` if postfixed. * @param operation Operation (`'plus'` or `'minus'`). * @returns Operator function with signature `(tree: NodeIdentifier) => MathObject`. */ private incDecOpFactory; /** * Operator table. */ private readonly opTable; /** * Precedence definitions. */ private static readonly precedence; /** * Operator precedence table. */ precedenceTable: { [key: string]: number; }; /** * Get tree node precedence. * @param tree Tree node. * @returns Node precedence. */ private nodePrecedence; /** * User functions. */ private readonly functions; /** * Special functions MathML unparser. */ private readonly unparseMathMLFunctions; /** * Load the `Evaluator`. * @param config */ private loadEvaluator; /** * `Evaluator` object private constructor */ private constructor(); /** * Creates an instance of the `Evaluator` object. * @param config * @returns */ static readonly Create: (config?: EvaluatorConfig, workspace?: EvaluatorWorkspace) => Evaluator; /** * Parse input string. * @param input String to parse. * @returns Abstract syntax tree of input. */ Parse(input: string): NodeInput; /** * Native name table factory. * @returns Native name table with actual `Complex` facade. */ private static readonly nativeNameTableFactory; /** * Restart evaluator. */ Restart(): void; /** * Clear variables. If names is 0 lenght restart evaluator. * @param names Variable names to clear in nameTable and builtInFunctionTable. */ Clear(...names: string[]): void; /** * Validate left side of assignment node. * @param tree Left side of assignment node. * @param shallow True if tree is a left root of assignment. * @returns An object with four properties: `left`, `id`, `args` and `field`. */ private validateAssignment; /** * * @param tree * @returns */ private toBoolean; /** * * @param id */ private solveUndefined; /** * Expression tree recursive evaluator. * @param tree Expression to evaluate. * @param scope Scope of execution. * @returns Expression `tree` evaluated. */ Evaluator(tree: NodeInput, scope?: Scope): NodeInput; /** * Evaluate expression `tree`. * @param tree Expression to evaluate. * @returns Expression `tree` evaluated. */ Evaluate(tree: NodeInput): NodeInput; /** * Executes the `Parse` and `Evaluate` methods on an input string, returning the computed result. * @param input String to parse and evaluate. * @returns Computed result of input. */ Execute(input: string): NodeInput; /** * Unparse expression `tree`. * @param tree Expression to unparse. * @returns Expression `tree` unparsed. */ Unparse(tree: NodeInput, parentPrecedence?: number): string; /** * Unparse recursively expression tree generating MathML representation. * @param tree Expression tree. * @returns String of expression `tree` unparsed as MathML language. */ UnparserMathML(tree: NodeInput, parentPrecedence?: number): string; /** * Unparse Expression tree in MathML. * @param tree Expression tree. * @returns String of expression unparsed as MathML language. */ UnparseMathML(tree: NodeInput, display?: 'inline' | 'block'): string; /** * Generates MathML representation of input without evaluation. * @param input Input to parse and generate MathML representation. * @param display `'inline'` or `'block'`. * @returns MathML representation of input. */ ToMathML(input: string, display?: 'inline' | 'block'): string; } export type { EvaluatorConfig, IncDecOperator }; export { Scope, CallFrame, EvaluatorError, EvaluatorWorkspace, Evaluator }; declare const _default: { Scope: typeof Scope; CallFrame: typeof CallFrame; EvaluatorError: typeof EvaluatorError; EvaluatorWorkspace: typeof EvaluatorWorkspace; Evaluator: typeof Evaluator; }; export default _default;