mathjslab
Version:
MathJSLab - An interpreter with language syntax like MATLAB®/Octave, ISBN 978-65-00-82338-7.
1,080 lines (1,032 loc) • 80.5 kB
text/typescript
/**
* MATLAB®/Octave like syntax parser/interpreter/compiler.
*/
import { CharStreams, CommonTokenStream, DiagnosticErrorListener, PredictionMode } from 'antlr4';
import MathJSLabLexer from './MathJSLabLexer';
import MathJSLabParser from './MathJSLabParser';
import { LexerErrorListener } from './LexerErrorListener';
import { ParserErrorListener } from './ParserErrorListener';
import * as AST from './AST';
import { substSymbol } from './substSymbol';
import { CharString } from './CharString';
import { ComplexDecimal } from './ComplexDecimal';
import { MultiArray } from './MultiArray';
import { CoreFunctions } from './CoreFunctions';
import { LinearAlgebra } from './LinearAlgebra';
import type { MathObject, MathOperationType, UnaryMathOperation, BinaryMathOperation } from './MathOperation';
import { MathOperation } from './MathOperation';
import { Configuration } from './Configuration';
import { Structure } from './Structure';
import { SymbolTable } from './SymbolTable';
import { FunctionHandle } from './FunctionHandle';
import { MathML } from './MathML';
/**
* aliasNameTable and AliasFunction type.
*/
type AliasNameTable = Record<string, RegExp>;
type AliasFunction = (name: string) => string;
/**
* builtInFunctionTable type.
*/
type BuiltInFunctionTableEntry = {
type: 'BUILTIN';
mapper: boolean;
ev: boolean[];
func: Function;
unparserMathML?: (tree: AST.NodeInput) => string;
};
type BuiltInFunctionTable = Record<string, BuiltInFunctionTableEntry>;
/**
* nameTable type.
*/
type NameTable = Record<string, AST.NodeExpr>;
/**
* commandWordListTable type.
*/
type CommandWordListFunction = (...args: string[]) => any;
type CommandWordListTableEntry = {
func: CommandWordListFunction;
};
type CommandWordListTable = Record<string, CommandWordListTableEntry>;
/**
* EvaluatorConfig type.
*/
type EvaluatorConfig = {
aliasNameTable?: AliasNameTable;
externalFunctionTable?: BuiltInFunctionTable;
externalCmdWListTable?: CommandWordListTable;
};
type IncDecOperator = (tree: AST.NodeIdentifier) => MathObject;
/**
* Evaluator object.
*/
class Evaluator {
/**
* After run Evaluate method, the exitStatus property will contains
* exit state of method.
*/
public response = {
EXTERNAL: -2,
WARNING: -1,
OK: 0,
LEX_ERROR: 1,
PARSER_ERROR: 2,
EVAL_ERROR: 3,
};
/**
* Debug flag.
*/
public debug: boolean = false;
/**
* Native name table. It's inserted in nameTable when Evaluator constructor executed.
*/
private readonly nativeNameTable: Record<string, ComplexDecimal> = {
false: ComplexDecimal.false(),
true: ComplexDecimal.true(),
i: ComplexDecimal.onei(),
I: ComplexDecimal.onei(),
j: ComplexDecimal.onei(),
J: ComplexDecimal.onei(),
e: ComplexDecimal.e(),
pi: ComplexDecimal.pi(),
inf: ComplexDecimal.inf_0(),
Inf: ComplexDecimal.inf_0(),
nan: ComplexDecimal.NaN_0(),
NaN: ComplexDecimal.NaN_0(),
};
public readonly nativeNameTableList = Object.keys(this.nativeNameTable);
/**
* Alias table.
*/
private aliasNameTable: AliasNameTable;
// public symbolTable: SymbolTable;
/**
* Name table.
*/
public nameTable: NameTable = {};
/**
* Built-in function table.
*/
public builtInFunctionTable: BuiltInFunctionTable = {};
/**
* Get a list of names of defined functions in builtInFunctionTable.
*/
public get builtInFunctionList(): string[] {
return Object.keys(this.builtInFunctionTable);
}
/**
* Local table.
*/
public localTable: Record<string, AST.NodeInput> = {};
/**
* Command word list table.
*/
public commandWordListTable: CommandWordListTable = {
clear: {
func: (...args: string[]): void => this.Clear(...args),
},
/* Debug purpose commands */
__operators__: {
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
func: (...args: string[]): MultiArray => {
const operators = Object.keys(this.opTable).sort();
const result = new MultiArray([operators.length, 1], null, true);
result.array = operators.map((operator) => [new CharString(operator)]);
return result;
},
},
__keywords__: {
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
func: (...args: string[]): MultiArray => {
const keywords = MathJSLabLexer.keywordNames.slice(1).sort() as string[];
const result = new MultiArray([keywords.length, 1], null, true);
result.array = keywords.map((keyword) => [new CharString(keyword)]);
return result;
},
},
__builtins__: {
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
func: (...args: string[]): MultiArray => {
const result = new MultiArray([this.builtInFunctionList.length, 1], null, true);
result.array = this.builtInFunctionList.sort().map((name) => [new CharString(name)]);
return result;
},
},
__list_functions__: {
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
func: (...args: string[]): MultiArray => {
return MultiArray.emptyArray(true);
},
},
localfunctions: {
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
func: (...args: string[]): MultiArray => {
return MultiArray.emptyArray(true);
},
},
__dump_symtab_info__: {
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
func: (...args: string[]): MultiArray => {
return MultiArray.emptyArray(true);
},
},
};
/**
* Evaluator exit status.
*/
public exitStatus: number;
/**
* Increment and decrement operator
* @param pre `true` if prefixed. `false` if postfixed.
* @param operation Operation (`'plus'` or `'minus'`).
* @returns Operator function with signature `(tree: AST.NodeIdentifier) => MathObject`.
*/
private incDecOp(pre: boolean, operation: 'plus' | 'minus'): IncDecOperator {
if (pre) {
return (tree: AST.NodeIdentifier): MathObject => {
if (tree.type === 'IDENT') {
if (this.nameTable[tree.id]) {
this.nameTable[tree.id] = MathOperation[operation](this.nameTable[tree.id], ComplexDecimal.one());
return this.nameTable[tree.id];
} else {
throw new EvalError('in x++ or ++x, x must be defined first.');
}
} else {
throw new SyntaxError(`invalid ${operation === 'plus' ? 'increment' : 'decrement'} variable.`);
}
};
} else {
return (tree: AST.NodeIdentifier): MathObject => {
if (tree.type === 'IDENT') {
if (this.nameTable[tree.id]) {
const value = MathOperation.copy(this.nameTable[tree.id]);
this.nameTable[tree.id] = MathOperation[operation](this.nameTable[tree.id], ComplexDecimal.one());
return value;
} else {
throw new EvalError('in x++ or ++x, x must be defined first.');
}
} else {
throw new SyntaxError(`invalid ${operation === 'plus' ? 'increment' : 'decrement'} variable.`);
}
};
}
}
/**
* Operator table.
*/
private readonly opTable: Record<string, MathOperationType | IncDecOperator> = {
'+': MathOperation.plus,
'-': MathOperation.minus,
'.*': MathOperation.times,
'*': MathOperation.mtimes,
'./': MathOperation.rdivide,
'/': MathOperation.mrdivide,
'.\\': MathOperation.ldivide,
'\\': MathOperation.mldivide,
'.^': MathOperation.power,
'^': MathOperation.mpower,
'+_': MathOperation.uplus,
'-_': MathOperation.uminus,
".'": MathOperation.transpose,
"'": MathOperation.ctranspose,
'<': MathOperation.lt,
'<=': MathOperation.le,
'==': MathOperation.eq,
'>=': MathOperation.ge,
'>': MathOperation.gt,
'!=': MathOperation.ne,
'&': MathOperation.and,
'|': MathOperation.or,
'!': MathOperation.not,
'&&': MathOperation.mand,
'||': MathOperation.mor,
'++_': this.incDecOp(true, 'plus'),
'--_': this.incDecOp(true, 'minus'),
'_++': this.incDecOp(false, 'plus'),
'_--': this.incDecOp(false, 'minus'),
};
private static readonly precedence: string[][] = [
['min', '=', '+=', '-=', '*=', '/=', '\\='],
['||'],
['&&'],
['|'],
['&'],
['!=', '<', '>', '<=', '>='],
['+', '-'],
['.*', '*', './', '/', '.\\', '\\'],
['^', '.**', '**', ".'", "'"],
['!', '~', '-_', '+_'],
['preMax', '_.^', '_.**', '_^', '_**', "__.'", "__'", '__++'],
['max', '()', 'IDENT', 'ENDRANGE', ':', '<~>', '.'],
];
/**
* Operator precedence table.
*/
public readonly precedenceTable: { [key: string]: number };
/**
* Get tree node precedence.
* @param tree Tree node.
* @returns Node precedence.
*/
private nodePrecedence(tree: AST.NodeInput): number {
if (typeof tree.type === 'number') {
/* If `typeof tree.type === 'number'` then tree is a literal number or singleton element. */
if (tree instanceof ComplexDecimal) {
return ComplexDecimal.precedence(tree, this);
} else {
return this.precedenceTable.max;
}
} else if (tree.type === 'IDX') {
const aliasTreeName = this.aliasName(tree.expr.id);
return tree.expr.type === 'IDENT' && aliasTreeName in this.builtInFunctionTable && (!!this.builtInFunctionTable[aliasTreeName].unparserMathML || aliasTreeName in MathML.format)
? this.precedenceTable.max
: this.precedenceTable.preMax;
} else if (tree.type === 'RANGE') {
return tree.start_ && tree.stop_ ? this.precedenceTable.min : this.precedenceTable.max;
} else {
return this.precedenceTable[tree.type] || 0;
}
}
/**
* User functions.
*/
private readonly functions: Record<string, Function> = {
unparse: (tree: AST.NodeInput): CharString => new CharString(this.Unparse(tree)),
};
/**
* Local methods.
*/
public readonly unparseString = CharString.unparse;
public readonly unparseStringMathML = CharString.unparseMathML;
public readonly newNumber = ComplexDecimal.newThis;
public readonly unparseNumber = ComplexDecimal.unparse;
public readonly unparseNumberMathML = ComplexDecimal.unparseMathML;
public readonly isRowVector = MultiArray.isRowVector;
public readonly unparseArray = MultiArray.unparse;
public readonly unparseStructure = Structure.unparse;
public readonly unparseStructureMathML = Structure.unparseMathML;
public readonly newFunctionHandle = FunctionHandle.newThis;
public readonly unparseFunctionHandle = FunctionHandle.unparse;
public readonly unparseFunctionHandleMathML = FunctionHandle.unparseMathML;
public readonly unparseArrayMathML = MultiArray.unparseMathML;
public readonly evaluateArray = MultiArray.evaluate;
public readonly mapArray = MultiArray.rawMap;
public readonly getElements = MultiArray.getElements;
public readonly getElementsLogical = MultiArray.getElementsLogical;
public readonly setElements = MultiArray.setElements;
public readonly setElementsLogical = MultiArray.setElementsLogical;
public readonly expandRange = MultiArray.expandRange;
public readonly expandColon = MultiArray.expandColon;
public readonly array0x0 = MultiArray.emptyArray;
public readonly linearize = MultiArray.linearize;
public readonly scalarToArray = MultiArray.scalarToMultiArray;
public readonly scalarOrCellToArray = MultiArray.scalarOrCellToMultiArray;
public readonly arrayToScalar = MultiArray.MultiArrayToScalar;
public readonly linearLength = MultiArray.linearLength;
public readonly getDimension = MultiArray.getDimension;
public readonly toLogical = MultiArray.toLogical;
public readonly getFields = Structure.getFields;
public readonly setNewField = Structure.setNewField;
/**
* Special functions MathML unparser.
*/
private readonly unparseMathMLFunctions: Record<string, (tree: AST.NodeIndexExpr) => string> = {
logb: (tree: AST.NodeIndexExpr): string => {
let unparseArgument = this.unparserMathML(tree.args[1]);
if (this.nodePrecedence(tree.args[1]) < this.precedenceTable.max) {
unparseArgument = MathML.format['()']('(', unparseArgument, ')');
}
return MathML.format['logb'](this.unparserMathML(tree.args[0]), unparseArgument);
},
log2: (tree: AST.NodeIndexExpr): string => {
let unparseArgument = this.unparserMathML(tree.args[0]);
if (this.nodePrecedence(tree.args[0]) < this.precedenceTable.max) {
unparseArgument = MathML.format['()']('(', unparseArgument, ')');
}
return MathML.format['log2'](unparseArgument);
},
log10: (tree: AST.NodeIndexExpr): string => {
let unparseArgument = this.unparserMathML(tree.args[0]);
if (this.nodePrecedence(tree.args[0]) < this.precedenceTable.max) {
unparseArgument = MathML.format['()']('(', unparseArgument, ')');
}
return MathML.format['log10'](unparseArgument);
},
factorial: (tree: AST.NodeIndexExpr): string => {
let unparseArgument = this.unparserMathML(tree.args[0]);
if (this.nodePrecedence(tree.args[0]) < this.precedenceTable.max) {
unparseArgument = MathML.format['()']('(', unparseArgument, ')');
}
return MathML.format['factorial'](unparseArgument);
},
};
/**
* Alias name function. This property is set at Evaluator instantiation.
* @param name Alias name.
* @returns Canonical name.
*/
public aliasName: AliasFunction = (name: string): string => name;
/**
* Evaluator object constructor
*/
constructor(config?: EvaluatorConfig) {
this.exitStatus = this.response.OK;
this.precedenceTable = {};
Evaluator.precedence.forEach((list, precedence) => {
list.forEach((field) => {
this.precedenceTable[field] = precedence + 1;
});
});
/* Set opTable aliases */
this.opTable['**'] = this.opTable['^'];
this.precedenceTable['**'] = this.precedenceTable['^'];
this.opTable['.**'] = this.opTable['.^'];
this.precedenceTable['.**'] = this.precedenceTable['.^'];
this.precedenceTable['_**'] = this.precedenceTable['_^'];
this.precedenceTable['_.**'] = this.precedenceTable['_.^'];
this.opTable['~='] = this.opTable['!='];
this.precedenceTable['~='] = this.precedenceTable['!='];
this.opTable['~'] = this.opTable['!'];
this.precedenceTable['~'] = this.precedenceTable['!'];
/* Load nativeNameTable */
this.loadNativeTable();
/* Define Evaluator functions */
for (const func in this.functions) {
this.defineFunction(func, this.functions[func]);
}
/* Define function operators */
for (const func in MathOperation.leftAssociativeMultipleOperations) {
this.defineLeftAssociativeMultipleOperationFunction(func, MathOperation.leftAssociativeMultipleOperations[func]);
}
for (const func in MathOperation.binaryOperations) {
this.defineBinaryOperatorFunction(func, MathOperation.binaryOperations[func]);
}
for (const func in MathOperation.unaryOperations) {
this.defineUnaryOperatorFunction(func, MathOperation.unaryOperations[func]);
}
/* Define function mappers */
for (const func in ComplexDecimal.mapFunction) {
this.defineFunction(func, ComplexDecimal.mapFunction[func], true);
}
/* Define other functions */
for (const func in ComplexDecimal.twoArgFunction) {
this.defineFunction(func, ComplexDecimal.twoArgFunction[func]);
}
/* Define Configuration functions */
for (const func in Configuration.functions) {
this.defineFunction(func, Configuration.functions[func]);
}
/* Define CoreFunctions functions */
for (const func in CoreFunctions.functions) {
this.defineFunction(func, CoreFunctions.functions[func]);
}
/* Define LinearAlgebra functions */
for (const func in LinearAlgebra.functions) {
this.defineFunction(func, LinearAlgebra.functions[func]);
}
/* Load unparserMathML for special functions */
for (const func in this.unparseMathMLFunctions) {
this.builtInFunctionTable[func].unparserMathML = this.unparseMathMLFunctions[func];
}
if (config) {
if (config.aliasNameTable) {
this.aliasNameTable = config.aliasNameTable;
this.aliasName = (name: string): string => {
let result = false;
let aliasname = '';
for (const i in this.aliasNameTable) {
if (this.aliasNameTable[i].test(name)) {
result = true;
aliasname = i;
break;
}
}
if (result) {
return aliasname;
} else {
return name;
}
};
} else {
this.aliasName = (name: string): string => name;
}
if (config.externalFunctionTable) {
Object.assign(this.builtInFunctionTable, config.externalFunctionTable);
}
if (config.externalCmdWListTable) {
Object.assign(this.commandWordListTable, config.externalCmdWListTable);
}
} else {
this.aliasName = (name: string): string => name;
}
// this.symbolTable = new SymbolTable(this.builtInFunctionTable, this.aliasName);
}
/**
* Parse input string.
* @param input String to parse.
* @returns Abstract syntax tree of input.
*/
public Parse(input: string): AST.NodeInput {
// Give the lexer the input as a stream of characters.
const inputStream = CharStreams.fromString(input);
const lexer = new MathJSLabLexer(inputStream);
// Set word-list commands in lexer.
lexer.commandNames = Object.keys(this.commandWordListTable);
// Create a stream of tokens and give it to the parser. Set parser to construct a parse tree.
const tokenStream = new CommonTokenStream(lexer);
const parser = new MathJSLabParser(tokenStream);
parser.buildParseTrees = true;
// Remove error listeners and add LexerErrorListener and ParserErrorListener.
lexer.removeErrorListeners();
lexer.addErrorListener(new LexerErrorListener());
parser.removeErrorListeners();
parser.addErrorListener(new ParserErrorListener());
if (this.debug) {
// Add DiagnosticErrorListener to parser to notify when the parser
// detects an ambiguity. Set prediction mode to report all ambiguities.
// parser.addErrorListener(new DiagnosticErrorListener());
// parser._interp.predictionMode = PredictionMode.LL_EXACT_AMBIG_DETECTION;
}
// Parse input and return AST.
return parser.input().node;
}
/**
* Load native name table in name table.
*/
private loadNativeTable(): void {
/* Insert nativeNameTable in nameTable */
for (const name in this.nativeNameTable) {
this.nameTable[name] = this.nativeNameTable[name];
}
}
/**
* Restart evaluator.
*/
public Restart(): void {
this.nameTable = {};
this.localTable = {};
this.loadNativeTable();
}
/**
* Clear variables. If names is 0 lenght restart evaluator.
* @param names Variable names to clear in nameTable and builtInFunctionTable.
*/
public Clear(...names: string[]): void {
if (names.length === 0) {
this.Restart();
} else {
names.forEach((name) => {
delete this.nameTable[name];
delete this.builtInFunctionTable[name];
if (this.nativeNameTableList.includes(name)) {
this.nameTable[name] = this.nativeNameTable[name];
}
});
}
}
/**
* Throws error if left hand side length of multiple assignment greater
* than maximum length (to be used in ReturnSelector functions).
* @param maxLength Maximum length of return list.
* @param currentLength Requested length of return list.
*/
public static throwErrorIfGreaterThanReturnList(maxLength: number, currentLength: number): void {
if (currentLength > maxLength) {
throw new EvalError(`element number ${maxLength + 1} undefined in return list`);
}
}
/**
* Tests if it is a NodeReturnList and if so reduces it to its first
* element.
* @param value A node.
* @returns Reduced node if `tree` is a NodeReturnList.
*/
public reduceIfReturnList(tree: AST.NodeInput): AST.NodeInput {
if (tree.type === 'RETLIST') {
const result = tree.selector(1, 0);
result.parent = tree.parent;
return result;
} else {
return tree;
}
}
/**
* 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`.
*/
public validateAssignment(tree: AST.NodeExpr, shallow: boolean, local: boolean, fname: string): { id: string; index: AST.NodeExpr[]; field: string[] }[] {
const invalidLeftAssignmentMessage = 'invalid left hand side of assignment';
if (tree.type === 'IDENT') {
return [
{
id: tree.id,
index: [],
field: [],
},
];
} else if (tree.type === 'IDX' && tree.expr.type === 'IDENT') {
return [
{
id: tree.expr.id,
index: tree.args,
field: [],
},
];
} else if (tree.type === '.') {
const field = tree.field.map((field: AST.NodeExpr) => {
if (typeof field === 'string') {
return field;
} else {
const result = this.reduceIfReturnList(this.Evaluator(field, local, fname));
if (result instanceof CharString) {
return result.str;
} else {
throw new EvalError(`${invalidLeftAssignmentMessage}: dynamic structure field names must be strings.`);
}
}
});
if (tree.obj.type === 'IDENT') {
return [
{
id: tree.obj.id,
index: [],
field,
},
];
} else if (tree.obj.type === 'IDX' && tree.obj.expr.type === 'IDENT') {
return [
{
id: tree.obj.expr.id,
index: tree.obj.args,
field,
},
];
} else {
throw new EvalError(`${invalidLeftAssignmentMessage}.`);
}
} else if (tree.type === '<~>') {
return [
{
id: '~',
index: [],
field: [],
},
];
} else if (shallow && this.isRowVector(tree)) {
return tree.array[0].map((left: AST.NodeExpr) => this.validateAssignment(left, false, local, fname)[0]);
} else {
throw new EvalError(`${invalidLeftAssignmentMessage}.`);
}
}
/**
* Define function in builtInFunctionTable.
* @param name 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.
*/
private defineFunction(name: string, func: Function, mapper: boolean = false, ev: boolean[] = []): void {
this.builtInFunctionTable[name] = { type: 'BUILTIN', mapper, ev, func };
}
/**
* Define unary operator function in builtInFunctionTable.
* @param name Name of function.
* @param func Function body.
*/
private defineUnaryOperatorFunction(name: string, func: UnaryMathOperation): void {
this.builtInFunctionTable[name] = {
type: 'BUILTIN',
mapper: false,
ev: [],
func: (...operand: AST.NodeExpr) => {
if (operand.length === 1) {
return func(operand[0]);
} else {
throw new EvalError(`Invalid call to ${name}. Type 'help ${name}' to see correct usage.`);
}
},
};
}
/**
* Define binary operator function in builtInFunctionTable.
* @param name Name of function.
* @param func Function body.
*/
private defineBinaryOperatorFunction(name: string, func: BinaryMathOperation): void {
this.builtInFunctionTable[name] = {
type: 'BUILTIN',
mapper: false,
ev: [],
func: (left: AST.NodeExpr, ...right: AST.NodeExpr) => {
if (right.length === 1) {
return func(left, right[0]);
} else {
throw new EvalError(`Invalid call to ${name}. Type 'help ${name}' to see correct usage.`);
}
},
};
}
/**
* Define define two-or-more operand function in builtInFunctionTable.
* @param name
* @param func
*/
private defineLeftAssociativeMultipleOperationFunction(name: string, func: BinaryMathOperation): void {
this.builtInFunctionTable[name] = {
type: 'BUILTIN',
mapper: false,
ev: [],
func: (left: AST.NodeExpr, ...right: AST.NodeExpr) => {
if (right.length === 1) {
return func(left, right[0]);
} else if (right.length > 1) {
let result = func(left, right[0]);
for (let i = 1; i < right.length; i++) {
result = func(result, right[i]);
}
return result;
} else {
throw new EvalError(`Invalid call to ${name}. Type 'help ${name}' to see correct usage.`);
}
},
};
}
/**
*
* @param tree
* @returns
*/
public toBoolean(tree: AST.NodeExpr): boolean {
const value = tree instanceof MultiArray ? this.toLogical(tree) : tree;
if (value instanceof ComplexDecimal) {
return Boolean(value.re.toNumber() || value.im.toNumber());
} else {
return !!value.str;
}
}
/**
* Expression tree recursive evaluator.
* @param tree Expression to evaluate.
* @param local Set `true` if evaluating function.
* @param fname Function name if evaluating function.
* @returns Expression `tree` evaluated.
*/
public Evaluator(tree: AST.NodeInput, local: boolean = false, fname: string = ''): AST.NodeInput {
if (this.debug) {
// eslint-disable-next-line no-console
console.log(
`Evaluator(\ntree:${JSON.stringify(
tree,
(key: string, value: AST.NodeInput) => (key !== 'parent' ? value : value === null ? 'root' : true),
2,
)},\nlocal:${local},\nfname:${fname});`,
);
}
if (tree) {
if (tree instanceof ComplexDecimal || tree instanceof FunctionHandle || tree instanceof CharString || tree instanceof Structure) {
return tree;
} else if (tree instanceof MultiArray) {
return this.evaluateArray(tree, this, local, fname);
} else {
switch (tree.type) {
case '+':
case '-':
case '.*':
case '*':
case './':
case '/':
case '.\\':
case '\\':
case '.^':
case '^':
case '.**':
case '**':
case '<':
case '<=':
case '==':
case '>=':
case '>':
case '!=':
case '~=':
case '&':
case '|':
case '&&':
case '||':
tree.left.parent = tree;
tree.right.parent = tree;
return (this.opTable[tree.type] as BinaryMathOperation)(
this.reduceIfReturnList(this.Evaluator(tree.left, local, fname)),
this.reduceIfReturnList(this.Evaluator(tree.right, local, fname)),
);
case '()':
tree.right.parent = tree;
return this.reduceIfReturnList(this.Evaluator(tree.right, local, fname));
case '!':
case '~':
case '+_':
case '-_':
tree.right.parent = tree;
return (this.opTable[tree.type] as UnaryMathOperation)(this.reduceIfReturnList(this.Evaluator(tree.right, local, fname)));
case '++_':
case '--_':
tree.right.parent = tree;
return (this.opTable[tree.type] as IncDecOperator)(tree.right);
case ".'":
case "'":
tree.left.parent = tree;
return (this.opTable[tree.type] as UnaryMathOperation)(this.reduceIfReturnList(this.Evaluator(tree.left, local, fname)));
case '_++':
case '_--':
tree.left.parent = tree;
return (this.opTable[tree.type] as IncDecOperator)(tree.left);
case '=':
case '+=':
case '-=':
case '*=':
case '/=':
case '\\=':
case '^=':
case '**=':
case '.*=':
case './=':
case '.\\=':
case '.^=':
case '.**=':
case '&=':
case '|=': {
tree.left.parent = tree;
tree.right.parent = tree;
const assignment = this.validateAssignment(tree.left, true, local, fname);
const op: AST.TOperator | '' = tree.type.substring(0, tree.type.length - 1);
if (assignment.length > 1 && op.length > 0) {
throw new EvalError('computed multiple assignment not allowed.');
}
let right: AST.NodeExpr;
try {
right = this.Evaluator(tree.right, false, fname);
} catch {
right = tree.right;
}
if (right.type !== 'RETLIST') {
right = AST.nodeReturnList((length: number, index: number) => {
if (index === 0) {
return tree.right;
} else {
throw new EvalError(`element number ${index + 1} undefined in return list`);
}
});
}
const resultList = AST.nodeListFirst();
for (let n = 0; n < assignment.length; n++) {
const { id, index, field } = assignment[n];
if (id !== '~') {
if (index.length === 0) {
/* Name definition. */
if (right.type !== 'RETLIST') {
right = this.Evaluator(right, false, fname);
}
const rightN = right.selector(assignment.length, n);
rightN.parent = tree.right;
const expr = op.length ? AST.nodeOp(op as AST.TOperator, AST.nodeIdentifier(id), rightN) : rightN;
try {
if (field.length > 0) {
if (typeof this.nameTable[id] === 'undefined') {
this.nameTable[id] = new Structure({});
}
if (this.nameTable[id] instanceof Structure) {
this.setNewField(this.nameTable[id], field, this.reduceIfReturnList(this.Evaluator(expr)));
} else {
throw new EvalError('in indexed assignment.');
}
AST.appendNodeList(resultList, AST.nodeOp('=', AST.nodeIdentifier(id), this.nameTable[id]));
} else {
this.nameTable[id] = this.reduceIfReturnList(this.Evaluator(expr));
AST.appendNodeList(resultList, AST.nodeOp('=', AST.nodeIdentifier(id), this.nameTable[id]));
}
continue;
} catch (error) {
this.nameTable[id] = expr;
throw error;
}
} else {
/* Function definition or indexed matrix reference. */
if (op) {
if (typeof this.nameTable[id] !== 'undefined') {
if (!(this.nameTable[id] instanceof FunctionHandle)) {
/* Indexed matrix reference on left hand side with operator. */
if (index.length === 1) {
/* Test logical indexing. */
const arg0 = this.reduceIfReturnList(this.Evaluator(index[0], local, fname));
if (arg0 instanceof MultiArray && arg0.type === ComplexDecimal.LOGICAL) {
/* Logical indexing. */
this.setElementsLogical(
this.nameTable,
id,
field,
this.linearize(arg0) as ComplexDecimal[],
this.scalarToArray(
this.reduceIfReturnList(
this.Evaluator(
AST.nodeOp(
op,
this.getElementsLogical(this.nameTable[id], id, field, arg0),
this.scalarToArray(this.reduceIfReturnList(this.Evaluator(right.selector(assignment.length, n)))),
),
false,
fname,
),
),
),
);
} else {
/* Not logical indexing. */
this.setElements(
this.nameTable,
id,
field,
[arg0],
this.scalarToArray(
this.reduceIfReturnList(
this.Evaluator(
AST.nodeOp(
op,
this.getElements(this.nameTable[id], id, field, [arg0]),
this.scalarToArray(this.reduceIfReturnList(this.Evaluator(right.selector(assignment.length, n)))),
),
false,
fname,
),
),
),
);
}
} else {
this.setElements(
this.nameTable,
id,
field,
index.map((arg: AST.NodeExpr) => this.reduceIfReturnList(this.Evaluator(arg))),
this.scalarToArray(
this.reduceIfReturnList(
this.Evaluator(
AST.nodeOp(
op,
this.getElements(this.nameTable[id], id, field, index),
this.scalarToArray(this.reduceIfReturnList(this.Evaluator(right.selector(assignment.length, n)))),
),
false,
fname,
),
),
),
);
}
AST.appendNodeList(resultList, AST.nodeOp('=', AST.nodeIdentifier(id), this.nameTable[id]));
continue;
} else {
throw new EvalError(`can't perform indexed assignment for function handle type.`);
}
} else {
throw new EvalError(`in computed assignment ${id}(index) OP= X, ${id} must be defined first.`);
}
} else {
/* Indexed matrix reference on left hand side. */
if (index.length === 1) {
/* Test logical indexing. */
index[0].parent = tree.left;
index[0].index = 0;
const arg0 = this.reduceIfReturnList(this.Evaluator(index[0], local, fname));
if (arg0 instanceof MultiArray && arg0.type === ComplexDecimal.LOGICAL) {
/* Logical indexing. */
this.setElementsLogical(
this.nameTable,
id,
field,
this.linearize(arg0) as ComplexDecimal[],
this.scalarToArray(this.reduceIfReturnList(this.Evaluator(right.selector(assignment.length, n)))),
);
} else {
/* Not logical indexing. */
this.setElements(
this.nameTable,
id,
field,
[arg0],
this.scalarToArray(this.reduceIfReturnList(this.Evaluator(right.selector(assignment.length, n)))),
);
}
} else {
this.setElements(
this.nameTable,
id,
field,
index.map((arg: AST.NodeExpr, i: number) => {
arg.parent = tree.left;
arg.index = i;
return this.reduceIfReturnList(this.Evaluator(arg));
}),
this.scalarToArray(this.reduceIfReturnList(this.Evaluator(right.selector(assignment.length, n)))),
);
}
AST.appendNodeList(resultList, AST.nodeOp('=', AST.nodeIdentifier(id), this.nameTable[id]));
}
}
}
}
if (tree.parent === null || tree.parent.parent === null) {
/* assignment at root expression */
if (resultList.list.length === 1) {
/* single assignment */
return resultList.list[0];
} else {
/* multiple assignment */
return resultList;
}
} else {
/* assignment at right side */
return (resultList.list[0] as AST.NodeExpr).right;
}
}
case 'IDENT':
if (local && this.localTable[fname] && this.localTable[fname][tree.id]) {
/* Defined in localTable. */
this.localTable[fname][tree.id].parent = tree;
return this.localTable[fname][tree.id];
} else if (tree.id in this.nameTable) {
/* Defined in nameTable. */
this.nameTable[tree.id].parent = tree;
return this.reduceIfReturnList(this.Evaluator(this.nameTable[tree.id]));
} else if (this.aliasName(tree.id) in this.builtInFunctionTable) {
/* Defined as built-in function */
return this.newFunctionHandle(tree.id);
} else {
throw new ReferenceError(`'${tree.id}' undefined.`);
}
case '.': {
const result = this.getFields(
this.reduceIfReturnList(this.Evaluator(tree.obj, local, fname)),
tree.field.map((field: AST.NodeExpr) => {
if (typeof field === 'string') {
return field;
} else {
const result = this.reduceIfReturnList(this.Evaluator(field, local, fname));
if (result instanceof CharString) {
return result.str;
} else {
throw new EvalError(`Dynamic structure field names must be strings.`);
}
}
}),
);
if (result.length === 1) {
return result[0];
} else {
return AST.nodeList(result);
}
}
case 'LIST': {
const result = {