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