mathjslab
Version:
MathJSLab - An interpreter with language syntax like MATLAB®/Octave, ISBN 978-65-00-82338-7.
609 lines (608 loc) • 21.4 kB
TypeScript
/**
* 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;