UNPKG

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
/** * 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 = {