UNPKG

mathjslab

Version:

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

609 lines (608 loc) 21.4 kB
/** * MATLAB®/Octave like syntax parser/interpreter/compiler. */ import type { NodeInput, NodeExpr, NodeIdentifier, NodeFunctionDefinition, NodeBuiltInFunction, BuiltInFunctionInputSignature, BuiltInFunctionSignature, NameEntry, AliasNameTable, BuiltInFunctionTable, CommandWordListTable } from './AST'; import { CharString, ComplexType } from './AST'; import type { MathObject, UnaryMathOperation, BinaryMathOperation, KeyOfTypeOfMathOperation } from './MathOperation'; import { Scope } from './Scope'; import { CallFrame } from './CallFrame'; import { type Callable } from './Callable'; /** * `response` type */ type ExitStatus = number; type ExitStatusValues = Record<string, ExitStatus>; /** * # InterpreterError * * 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 InterpreterError extends Error { /** * Optional call stack snapshot. * * Top frame should be the first element. */ readonly stackFrames?: CallFrame[]; /** * Creates a new InterpreterError. * * @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; } /** * # EvalError * * Represents a general runtime evaluation error. * * Examples: * - invalid operations * - domain errors */ declare class EvalError extends InterpreterError { constructor(message: string, stackFrames?: CallFrame[]); } /** * # ReferenceError * * Represents errors related to undefined identifiers. * * Examples: * - undefined variable * - undefined function */ declare class ReferenceError extends InterpreterError { constructor(message: string, stackFrames?: CallFrame[]); } /** * # UndefinedReferenceError * * Represents an unresolved identifier that may be registered as a * forward reference when the current evaluation mode allows it. */ declare class UndefinedReferenceError extends ReferenceError { readonly identifier: string; constructor(identifier: string, stackFrames?: CallFrame[]); } /** * # CircularReferenceError * * Represents a circular dependency between unresolved forward references. */ declare class CircularReferenceError extends InterpreterError { readonly chain: string[]; constructor(chain: string[], stackFrames?: CallFrame[]); } /** * # SyntaxError * * Represents syntax-related errors detected during parsing or preprocessing. */ declare class SyntaxError extends InterpreterError { constructor(message: string, stackFrames?: CallFrame[]); } declare class Context { /** * Interpreter instance associated to this context. */ interpreter?: Interpreter | undefined; /** * Global scope. */ globalScope?: Scope | undefined; /** * Function call stack. */ callStack: CallFrame[]; /** * Built-in function table. */ builtInFunctionTable: Record<string, NodeBuiltInFunction>; /** * Whether assignments may keep unresolved identifiers for later resolution. */ allowForwardReference: boolean; /** * Names declared as global in the current context. */ globalNameSet: Set<string>; /** * Assignment targets whose right-hand side is currently being evaluated. * * This stack lets undefined-reference handling distinguish a local * forward reference from a dependency cycle such as `A -> B -> A`. */ private forwardReferenceTargetStack; /** * Requested output counts for expressions currently being evaluated. */ private requestedOutputCountStack; /** * Reset the execution context to a provided scope/stack or to a fresh global state. * * @param globalScope Optional global scope to install. * @param callStack Optional call stack to install. */ loadContext(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 Context} object. * @param interpreter Optional interpreter instance associated with the context. * @param globalScope Optional global scope reference. * @param callStack Optional function call stack reference. * @returns New interpreter context. */ static readonly create: (interpreter?: Interpreter, globalScope?: Scope, callStack?: CallFrame[]) => Context; /** * Native constants inserted into the global name table during interpreter loading. */ nativeNameTable: Record<string, ComplexType>; /** * Names provided by {@link nativeNameTable}. */ nativeNameTableList: string[]; /** * Alias name table. */ private aliasNameTable; /** * Alias name function. This property is set at Interpreter instantiation. * @param name Alias name. * @returns Canonical name. */ aliasNameFunction: (name: string) => string; /** * Configure aliases that map alternative spellings to canonical function names. * @param aliasNameTable Alias patterns keyed by canonical names. */ 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; /** * Resolve a variable name from the current scope chain. * @param name Identifier to resolve. * @returns Matching name entry, or `undefined` when not found. */ 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; /** * Track assignment targets while their right-hand side is evaluated. * @param targets Assignment target identifiers. */ pushForwardReferenceTargets(targets: string[]): void; /** * Stop tracking the current right-hand-side assignment targets. * @returns Removed target list, if any. */ popForwardReferenceTargets(): string[] | undefined; /** * Track how many outputs the current expression context asks from a call. * @param count Requested output count. */ pushRequestedOutputCount(count: number): void; /** * Stop tracking the current requested output count. * @returns Removed requested output count, if any. */ popRequestedOutputCount(): number | undefined; /** * Output count requested by the nearest evaluation context. */ get requestedOutputCount(): number; private get currentForwardReferenceTargets(); /** * Follow unresolved-reference metadata to build a dependency chain. * * Pending expressions can be partially re-linked while forward references * are resolved, so this also inspects the stored expression tree to retain * useful chains such as `A -> B -> C -> A`. */ private getUndefinedReferenceChain; private getNextUndefinedReference; /** * Find the next pending identifier inside a stored expression tree. */ private findPendingIdentifier; /** * Throw when resolving `name` would close an unresolved-reference cycle. */ private throwIfCircularReference; resolveCallable(expr: NodeExpr): Callable | undefined; resolveIdentifier(tree: NodeInput, scope: Scope): NodeExpr; private evaluateArgs; throwEvalError(message: string): never; throwReferenceError(message: string): never; throwUndefinedReferenceError(identifier: string): never; throwCircularReferenceError(chain: string[]): never; throwSyntaxError(message: string): never; private getCurrentFunctionDefinition; private getCurrentFunctionCountFrame; private getCallerWorkspace; resolveWorkspace(name: string, forAssignment?: boolean): Scope; currentFunctionArgumentCount(name: 'nargin' | 'nargout'): ComplexType; currentFunctionArgumentCountOrZero(): ComplexType; currentFunctionOutputCount(name: 'nargin' | 'nargout'): ComplexType; currentFunctionOutputCountOrZero(): ComplexType; currentFunctionInputName(indexNode: NodeInput): CharString; currentFunctionName(): string; declarePersistent(name: string, value: NodeInput | undefined, scope: Scope): void; loadPersistentVariables(func: NodeFunctionDefinition, scope: Scope): void; storePersistentVariables(func: NodeFunctionDefinition, scope: Scope): void; declareGlobal(name: string, value: NodeInput | undefined, scope: Scope): void; clearGlobalVariables(): void; private resolveCallSite; builtInInputSignatures(node: NodeBuiltInFunction): BuiltInFunctionInputSignature[]; builtInOutputSignatures(node: NodeBuiltInFunction): BuiltInFunctionInputSignature[]; builtInDeclaredArity(signatures: BuiltInFunctionInputSignature[]): number | undefined; private validateBuiltInInputArity; private validateBuiltInInputParameters; private valueDimensions; private sizeReturnList; private callFunctionDefinition; 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[], signature?: BuiltInFunctionSignature): void; /** * Merge external built-in functions into the current built-in table. * @param table Built-in functions to add or override. */ 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 a left-associative operator function that accepts two or more operands. * @param id Operator name. * @param func Binary operation used to fold the operands. */ 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; } /** * InterpreterConfig type. */ type InterpreterConfig = { aliasNameTable?: AliasNameTable; externalFunctionTable?: BuiltInFunctionTable; externalCmdWListTable?: CommandWordListTable; }; /** * Increment and decrement operator handler type. */ type IncDecOperator = (tree: NodeIdentifier) => MathObject; /** * Interpreter instance interface. */ interface InterpreterInterface { debug: boolean; context: Context; 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; Execute(input: string): 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; } /** * `Interpreter` object. */ declare class Interpreter implements InterpreterInterface { /** * 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); /** * Interpreter context. */ context: Context; /** * Command word list table. */ private commandWordListTable; /** * Interpreter exit status. */ private _exitStatus; /** * Interpreter 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 functionArityCallable; private functionArgumentArity; private functionOutputArity; private localFunctionHandles; private dbstackResult; private evalStringInScope; private checkFunctionCount; private existCode; private whichResult; private valueIsRuntimeClass; private readonly interpreterFunctionSignatures; private readonly functions; /** * Special functions MathML unparser. */ private readonly unparseMathMLFunctions; /** * Load the `Interpreter`. * @param config */ private loadInterpreter; /** * `Interpreter` object private constructor */ private constructor(); /** * Creates an instance of the `Interpreter` object. * @param config Optional interpreter configuration. * @param context Optional pre-built interpreter context. * @returns New interpreter instance. */ static readonly Create: (config?: InterpreterConfig, context?: Context) => Interpreter; /** * 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 interpreter. */ Restart(): void; /** * Clear variables/functions. When no names are provided, restart the interpreter. * @param names Variable/function names to clear in the current scope. */ 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; /** * Convert an evaluated expression to a boolean condition. * @param tree Evaluated expression. * @returns Boolean truth value. */ private toBoolean; /** * Re-evaluate expressions waiting for a newly defined forward reference. * @param id Identifier that may unblock pending references. * @param scope Scope that stores the pending references. * @param resolving Resolution chain used to detect recursive cycles. */ private solveUndefined; /** * A forward reference is recoverable only when it was raised in the * currently executing callable. Errors propagated from deeper calls must * keep their original stack trace and should not be registered as local * pending assignments. */ private isLocalUndefinedReference; private getArgumentValidationName; private getNameValueArgumentTarget; private getArgumentValidationDisplayName; private validateFunctionArgumentsBlocks; private getValueClassName; private getArgumentValidationEntry; private validateArgumentValidation; private validateFunctionArguments; validateFunctionInputArguments(func: NodeFunctionDefinition, scope: Scope): void; validateFunctionRepeatingArguments(func: NodeFunctionDefinition, scope: Scope, values: NodeInput[]): void; getFunctionNameValueParameters(func: NodeFunctionDefinition): Set<string>; private getFunctionNameValueDeclarations; splitFunctionCallNameValueArguments(func: NodeFunctionDefinition, args: NodeExpr[]): { positional: NodeExpr[]; named: Map<string, NodeExpr>; }; bindFunctionNameValueArguments(func: NodeFunctionDefinition, scope: Scope, values: Map<string, NodeInput>): void; registerNestedFunctions(func: NodeFunctionDefinition, scope: Scope): void; validateFunctionOutputArguments(func: NodeFunctionDefinition, scope: Scope, requestedOutputCount: number): void; getFunctionInputArgumentDefaults(func: NodeFunctionDefinition): Map<string, NodeExpr>; /** * Expression tree recursive interpreter. * @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 { InterpreterConfig, IncDecOperator }; export type { BuiltinCallable, Callable, FunctionDefinitionCallable, LambdaCallable } from './Callable'; export { Scope, CallFrame, InterpreterError, EvalError, ReferenceError, UndefinedReferenceError, CircularReferenceError, SyntaxError, Context, Interpreter }; declare const _default: { Scope: typeof Scope; CallFrame: typeof CallFrame; InterpreterError: typeof InterpreterError; EvalError: typeof EvalError; ReferenceError: typeof ReferenceError; UndefinedReferenceError: typeof UndefinedReferenceError; CircularReferenceError: typeof CircularReferenceError; SyntaxError: typeof SyntaxError; Context: typeof Context; Interpreter: typeof Interpreter; }; export default _default;