UNPKG

mathjslab

Version:

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

1,316 lines (1,246 loc) 74.1 kB
/** * MATLAB®/Octave like syntax parser/interpreter/compiler. */ import { constantsTable } from './constants-table'; import { substSymbol } from './subst-symbol'; import { CharString } from './char-string'; import { ComplexDecimal } from './complex-decimal'; import { MultiArray } from './multi-array'; import { CoreFunctions } from './core-functions'; import { LinearAlgebra } from './linear-algebra'; import { MathObject } from './math-object'; import Parser from './parser.js'; /** * Operator type. */ export type TOperator = | '+' | '-' | '.*' | '*' | './' | '/' | '.\\' | '\\' | '.^' | '^' | '.**' | '**' | '<' | '<=' | '==' | '>=' | '>' | '!=' | '~=' | '&' | '|' | '&&' | '||' | '=' | '+=' | '-=' | '*=' | '/=' | '\\=' | '^=' | '**=' | '.*=' | './=' | '.\\=' | '.^=' | '.**=' | '&=' | '|=' | '()' | '!' | '~' | '+_' | '-_' | '++_' | '--_' | ".'" | "'" | '_++' | '_--'; /** * aliasNameTable type. */ export type TAliasNameTable = Record<string, RegExp>; /** * baseFunctionTable type. */ export type TBaseFunctionTableEntry = { mapper?: boolean; ev: boolean[]; func: Function; unparserMathML?: (tree: any) => string; }; export type TBaseFunctionTable = Record<string, TBaseFunctionTableEntry>; /** * nameTable type. */ export type TNameTableEntry = { args: Array<any>; expr: any; }; export type TNameTable = Record<string, TNameTableEntry>; /** * commandWordListTable type. */ export type TCommandWordListFunction = (...args: string[]) => void; export type TCommandWordListTableEntry = { func: TCommandWordListFunction; }; export type TCommandWordListTable = Record<string, TCommandWordListTableEntry>; /** * TEvaluatorConfig type. */ export type TEvaluatorConfig = { aliasTable?: TAliasNameTable; externalFunctionTable?: TBaseFunctionTable; externalCmdWListTable?: TCommandWordListTable; }; /** * AST (Abstract Syntax Tree) nodes. */ /** * Common primary node. */ interface PrimaryNode { type: string | number; parent?: any; index?: number; } /** * Expression node. */ export type NodeExpr = NodeName | NodeArgExpr | NodeOperation | NodeList | NodeRange | NodeReturnList | MultiArray | ComplexDecimal; /** * Reserved node. */ interface NodeReserved extends PrimaryNode {} /** * Name node. */ export interface NodeName extends PrimaryNode { type: 'NAME'; id: string; } /** * Command word list node. */ interface NodeCmdWList extends PrimaryNode { type: 'CmdWList'; id: string; args: Array<CharString>; } /** * Expression and arguments node. */ export interface NodeArgExpr extends PrimaryNode { type: 'ARG'; expr: NodeExpr; args: Array<NodeExpr>; } /** * Range node. */ interface NodeRange extends PrimaryNode { type: 'RANGE'; start: NodeExpr | null; stop: NodeExpr | null; stride: NodeExpr | null; } /** * Operation node. */ export type NodeOperation = UnaryOperation | BinaryOperation; /** * Unary operation node. */ type UnaryOperation = UnaryOperationL | UnaryOperationR; /** * Right unary operation node. */ interface UnaryOperationR extends PrimaryNode { right: NodeExpr; } /** * Left unary operation node. */ interface UnaryOperationL extends PrimaryNode { left: NodeExpr; } /** * Binary operation. */ interface BinaryOperation extends PrimaryNode { left: NodeExpr; right: NodeExpr; } /** * List node */ export interface NodeList extends PrimaryNode { type: 'LIST'; list: Array<NodeExpr>; } export type ReturnSelector = (length: number, index: number) => any; /** * Return list node */ export interface NodeReturnList extends PrimaryNode { type: 'RETLIST'; selector: ReturnSelector; } /** * External parser declarations (defined in parser body) */ declare global { /* eslint-disable-next-line no-var */ var EvaluatorPointer: Evaluator; /* eslint-disable-next-line no-var */ var commandsTable: string[]; } /** * Evaluator object. * It is implemented as a class but cannot be instantiated more than one time * simultaneously. Instance is given by `Evaluator.initialize` static method * or global variable global.EvaluatorPointer. */ export class Evaluator { /** * After run Parser or Evaluate method, the exitStatus property will contains * exit state of method. */ public static response = { EXTERNAL: -2, WARNING: -1, OK: 0, LEX_ERROR: 1, PARSER_ERROR: 2, EVAL_ERROR: 3, }; /** * Debug flag, setter and getter. */ private _debug: boolean = false; public get debug(): boolean { return this._debug; } public set debug(value: boolean) { this._debug = value; } /** * Native name table. It's inserted in nameTable when * `Evaluator.initialize` 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(), }; /** * Name table. */ public nameTable: TNameTable = {}; public readonlyNameTable: string[] = []; /** * Alias table. */ private aliasTable: TAliasNameTable; /** * Base function table. */ public baseFunctionTable: TBaseFunctionTable = {}; /** * Get a list of names of defined functions in baseFunctionTable. */ public get baseFunctionList(): string[] { return Object.keys(this.baseFunctionTable); } /** * Local table. */ public localTable: Record<string, any> = {}; /** * Command word list table. */ public commandWordListTable: TCommandWordListTable = {}; /** * Parser (generated by Jison). */ private readonly parser: { parse: (input: string) => any } = Parser; /** * 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: any) => any`. */ private incDecOp(pre: boolean, operation: 'plus' | 'minus'): (tree: any) => any { if (pre) { return (tree: any): any => { if (tree.type === 'NAME') { if (this.nameTable[tree.id].expr) { this.nameTable[tree.id].expr = MathObject[operation](this.nameTable[tree.id].expr, ComplexDecimal.one()); return this.nameTable[tree.id].expr; } 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: any): any => { if (tree.type === 'NAME') { if (this.nameTable[tree.id].expr) { const value = MathObject.copy(this.nameTable[tree.id].expr); this.nameTable[tree.id].expr = MathObject[operation](this.nameTable[tree.id].expr, 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, Function> = { '+': MathObject.plus, '-': MathObject.minus, '.*': MathObject.times, '*': MathObject.mtimes, './': MathObject.rdivide, '/': MathObject.mrdivide, '.\\': MathObject.ldivide, '\\': MathObject.mldivide, '.^': MathObject.power, '^': MathObject.mpower, '+_': MathObject.uplus, '-_': MathObject.uminus, ".'": MathObject.transpose, "'": MathObject.ctranspose, '<': MathObject.lt, '<=': MathObject.le, '==': MathObject.eq, '>=': MathObject.ge, '>': MathObject.gt, '!=': MathObject.ne, '&': MathObject.and, '|': MathObject.or, '!': MathObject.not, '&&': MathObject.mand, '||': MathObject.mor, '++_': this.incDecOp(true, 'plus'), '--_': this.incDecOp(true, 'minus'), '_++': this.incDecOp(false, 'plus'), '_--': this.incDecOp(false, 'minus'), }; /** * User functions. */ private readonly functions: Record<string, Function> = { unparse: (tree: any): CharString => new CharString(EvaluatorPointer.Unparse(tree)), }; /** * Parser AST (Abstract Syntax Tree) constructor methods. */ public readonly nodeString = CharString.parse; public readonly isString = CharString.isThis; public readonly unparseString = CharString.unparse; public readonly unparseStringMathML = CharString.unparseMathML; public readonly removeQuotes = CharString.removeQuotes; public readonly nodeNumber = ComplexDecimal.parse; public readonly newNumber = ComplexDecimal.newThis; public readonly isNumber = ComplexDecimal.isThis; public readonly unparseNumber = ComplexDecimal.unparse; public readonly unparseNumberMathML = ComplexDecimal.unparseMathML; public readonly isTensor = MultiArray.isThis; public readonly isRowVector = MultiArray.isRowVector; public readonly unparseTensor = MultiArray.unparse; public readonly unparseTensorMathML = MultiArray.unparseMathML; public readonly evaluateTensor = MultiArray.evaluate; public readonly mapTensor = MultiArray.map; 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 firstRow = MultiArray.firstRow; public readonly appendRow = MultiArray.appendRow; public readonly tensor0x0 = MultiArray.array_0x0; public readonly linearize = MultiArray.linearize; public readonly toTensor = MultiArray.scalarToMultiArray; public readonly linearLength = MultiArray.linearLength; public readonly getDimension = MultiArray.getDimension; /** * Special functions MathML unparser. */ private readonly unparseMathMLFunctions: Record<string, (tree: any) => string> = { abs: (tree: any) => '<mrow><mo>|</mo>' + this.unparserMathML(tree.args[0]) + '<mo>|</mo></mrow>', conj: (tree: any) => '<mover><mrow>' + this.unparserMathML(tree.args[0]) + '</mrow><mo>&OverBar;</mo></mover>', sqrt: (tree: any) => '<msqrt><mrow>' + this.unparserMathML(tree.args[0]) + '</mrow></msqrt>', root: (tree: any) => '<mroot><mrow>' + this.unparserMathML(tree.args[0]) + '</mrow><mrow>' + this.unparserMathML(tree.args[1]) + '</mrow></mroot>', exp: (tree: any) => '<msup><mi>e</mi><mrow>' + this.unparserMathML(tree.args[0]) + '</mrow></msup>', logb: (tree: any) => '<msub><mi>log</mi><mrow>' + this.unparserMathML(tree.args[0]) + '</mrow></msub><mrow>' + this.unparserMathML(tree.args[1]) + '</mrow>', log2: (tree: any) => '<msub><mi>log</mi><mrow>' + '<mn>2</mn>' + '</mrow></msub><mrow>' + this.unparserMathML(tree.args[0]) + '</mrow>', log10: (tree: any) => '<msub><mi>log</mi><mrow>' + '<mn>10</mn>' + '</mrow></msub><mrow>' + this.unparserMathML(tree.args[0]) + '</mrow>', gamma: (tree: any) => '<mi>&Gamma;</mi><mrow><mo>(</mo>' + this.unparserMathML(tree.args[0]) + '<mo>)</mo></mrow>', factorial: (tree: any) => '<mrow><mo>(</mo>' + this.unparserMathML(tree.args[0]) + '<mo>)</mo></mrow><mo>!</mo>', }; /** * Evaluator object constructor */ private constructor() { global.EvaluatorPointer = this; this.exitStatus = Evaluator.response.OK; /* Set opTable aliases */ this.opTable['**'] = this.opTable['^']; this.opTable['.**'] = this.opTable['.^']; this.opTable['~='] = this.opTable['!=']; this.opTable['~'] = this.opTable['!']; /* Load nativeNameTable and constantsTable in nameTable */ this.loadNativeTable(); /* Define Evaluator functions */ for (const func in this.functions) { this.defineFunction(func, this.functions[func]); } /* Define function operators */ for (const func in MathObject.twoMoreOpFunction) { this.defineTwoOrMoreOperandFunction(func, MathObject.twoMoreOpFunction[func]); } for (const func in MathObject.binaryOpFunction) { this.defineBinaryOperatorFunction(func, MathObject.binaryOpFunction[func]); } for (const func in MathObject.unaryOpFunction) { this.defineUnaryOperatorFunction(func, MathObject.unaryOpFunction[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 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.baseFunctionTable[func].unparserMathML = this.unparseMathMLFunctions[func]; } } /** * Evaluator initialization. * @param config Evaluator configuration. * @returns Evaluator instance. */ public static initialize(config?: TEvaluatorConfig): Evaluator { const evaluator = new Evaluator(); if (config) { if (config.aliasTable) { evaluator.aliasTable = config.aliasTable; evaluator.aliasName = (name: string): string => { let result = false; let aliasname = ''; for (const i in evaluator.aliasTable) { if (evaluator.aliasTable[i].test(name)) { result = true; aliasname = i; break; } } if (result) { return aliasname; } else { return name; } }; } else { evaluator.aliasName = (name: string): string => name; } if (config.externalFunctionTable) { Object.assign(evaluator.baseFunctionTable, config.externalFunctionTable); } if (config.externalCmdWListTable) { Object.assign(evaluator.commandWordListTable, config.externalCmdWListTable); } for (const cmd in evaluator.commandWordListTable) { global.commandsTable.push(cmd); } } else { evaluator.aliasName = (name: string): string => name; } return evaluator; } /** * Alias name function. This property is set when running Evaluator.initialize. * @param name Alias name. * @returns Canonical name. */ public aliasName: (name: string) => string = (name: string): string => name; /** * Load native name table in name table. */ private loadNativeTable(): void { /* Insert nativeNameTable in nameTable */ for (const name in this.nativeNameTable) { this.nameTable[name] = { args: [], expr: this.nativeNameTable[name] }; this.readonlyNameTable.push(name); } /* Insert constantsTable in nameTable */ for (const name in constantsTable) { this.nameTable[constantsTable[name][0]] = { args: [], expr: constantsTable[name][1] }; this.readonlyNameTable.push(constantsTable[name][0]); } } /** * Restart evaluator. */ public Restart(): void { this.nameTable = {}; this.localTable = {}; this.readonlyNameTable = []; this.loadNativeTable(); } /** * Clear variables. If names is 0 lenght restart evaluator. * @param names Variable names to clear in nameTable and basFunctionTable. */ public Clear(...names: string[]): void { if (names.length === 0) { this.Restart(); } else { names.forEach((name) => { if (!this.readonlyNameTable.includes(name)) { delete this.nameTable[name]; delete this.baseFunctionTable[name]; } }); } } /** * Parse input. * @param input Input string * @returns Parsed input. */ public Parse(input: string): any { return this.parser.parse(input); } /** * Create reserved node. * @param nodeid * @returns */ public nodeReserved(nodeid: string): NodeReserved { return { type: nodeid }; } /** * Create name node. * @param nodeid * @returns */ public nodeName(nodeid: string): NodeName { return { type: 'NAME', id: nodeid.replace(/(\r\n|[\n\r])|[\ ]/gm, ''), }; } /** * Create command word list node. * @param nodename * @param nodelist * @returns */ public nodeCmdWList(nodename: NodeName, nodelist: NodeList): NodeCmdWList { return { type: 'CmdWList', id: nodename.id, args: nodelist ? (nodelist.list as any) : [], }; } /** * Create expression and arguments node. * @param nodeexpr * @param nodelist * @returns */ public nodeArgExpr(nodeexpr: any, nodelist?: any): NodeArgExpr { return { type: 'ARG', expr: nodeexpr, args: nodelist ? nodelist.list : [], }; } /** * Create range node. If two arguments are passed then it is 'start' and * 'stop' of range. If three arguments are passed then it is 'start', * 'stride' and 'stop'. * @param args 'start' and 'stop' or 'start', 'stride' and 'stop'. * @returns NodeRange. */ public nodeRange(...args: any): NodeRange { if (args.length === 2) { return { type: 'RANGE', start: args[0], stop: args[1], stride: null, }; } else if (args.length === 3) { return { type: 'RANGE', start: args[0], stop: args[2], stride: args[1], }; } else { throw new SyntaxError('invalid range.'); } } /** * Create operator node. * @param op * @param data1 * @param data2 * @returns */ public nodeOp(op: TOperator, data1: any, data2?: any): NodeOperation { switch (op) { case '+': case '-': case '.*': case '*': case './': case '/': case '.\\': case '\\': case '.^': case '^': case '.**': case '**': case '<': case '<=': case '==': case '>=': case '>': case '!=': case '~=': case '&': case '|': case '&&': case '||': case '=': case '+=': case '-=': case '*=': case '/=': case '\\=': case '^=': case '**=': case '.*=': case './=': case '.\\=': case '.^=': case '.**=': case '&=': case '|=': return { type: op, left: data1, right: data2 }; case '()': case '!': case '~': case '+_': case '-_': case '++_': case '--_': return { type: op, right: data1 }; case ".'": case "'": case '_++': case '_--': return { type: op, left: data1 }; default: return { type: 'INVALID' } as NodeOperation; } } /** * Create first element of list node. * @param node First element of list node. * @returns A NodeList. */ public nodeListFirst(node?: any): NodeList { if (node) { const result = { type: 'LIST', list: [node], }; node.parent = result; return result as NodeList; } else { return { type: 'LIST', list: [], }; } } /** * Append node to list node. * @param lnode NodeList. * @param node Element to append to list. * @returns NodeList with element appended. */ public nodeList(lnode: NodeList, node: any): NodeList { node.parent = lnode; lnode.list.push(node); return lnode; } /** * Create first row of a MultiArray. * @param row * @returns */ public nodeFirstRow(row: NodeList): MultiArray { if (row) { return this.firstRow(row.list); } else { return this.tensor0x0(); } } /** * Append row to MultiArray. * @param M * @param row * @returns */ public nodeAppendRow(M: MultiArray, row: NodeList): MultiArray { if (row) { return this.appendRow(M, row.list); } else { return M; } } /** * Creates NodeReturnList (multiple assignment) * @param selector Left side selector function. * @returns Return list node. */ public nodeReturnList(selector: ReturnSelector): NodeReturnList { return { type: 'RETLIST', selector, }; } /** * 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 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: any): any { 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 three properties: `left`, `id` and `args`. */ public validateAssignment(tree: any, shallow: boolean = true): { left: any; id: string; args: any[] }[] { const invalidMessageBase = 'invalid left hand side of assignment'; const invalidMessage = `${invalidMessageBase}: cannot assign to a read only value:`; if (tree.type === 'NAME') { if (this.readonlyNameTable.includes(tree.id)) { throw new EvalError(`${invalidMessage} ${tree.id}.`); } return [ { left: tree, id: tree.id, args: [], }, ]; } else if (tree.type === 'ARG' && tree.expr.type === 'NAME') { if (this.readonlyNameTable.includes(tree.expr.id)) { throw new EvalError(`${invalidMessage} ${tree.expr.id}.`); } return [ { left: tree.expr, id: tree.expr.id, args: tree.args, }, ]; } else if (tree.type === '<~>') { return [ { left: null, id: '~', args: [], }, ]; } else if (shallow && this.isRowVector(tree)) { return tree.array[0].map((left: any) => this.validateAssignment(left, false)[0]); } else { throw new EvalError(`${invalidMessageBase}.`); } } /** * Define function in baseFunctionTable. * @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. */ private defineFunction(name: string, func: Function, map?: boolean, ev?: boolean[]): void { this.baseFunctionTable[name] = { mapper: map ?? false, ev: ev ?? [], func, }; } /** * Define unary operator function in baseFunctionTable. * @param name Name of function. * @param func Function body. */ private defineUnaryOperatorFunction(name: string, func: Function): void { this.baseFunctionTable[name] = { mapper: false, ev: [], func: (...operand: any) => { if (operand.length === 1) { return func(operand[0]); } else { throw new EvalError(`Invalid call to ${name}.`); } }, }; } /** * Define binary operator function in baseFunctionTable. * @param name Name of function. * @param func Function body. */ private defineBinaryOperatorFunction(name: string, func: Function): void { this.baseFunctionTable[name] = { mapper: false, ev: [], func: (left: any, ...right: any) => { if (right.length === 1) { return func(left, right[0]); } else { throw new EvalError(`Invalid call to ${name}.`); } }, }; } /** * Define define two-or-more operand function in baseFunctionTable. * @param name * @param func */ private defineTwoOrMoreOperandFunction(name: string, func: Function): void { this.baseFunctionTable[name] = { mapper: false, ev: [], func: (left: any, ...right: any) => { 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}.`); } }, }; } /** * 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: any, local: boolean = false, fname: string = ''): any { if (this._debug) { console.log( `Evaluator(\ntree:${JSON.stringify( tree, (key: string, value: any) => (key !== 'parent' ? value : value === null ? 'root' : true), 2, )},\nlocal:${local},\nfname:${fname});`, ); } if (this.isNumber(tree) || this.isString(tree)) { /* NUMBER or STRING */ return tree; } else if (this.isTensor(tree)) { /* MATRIX */ return this.evaluateTensor(tree, 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]( 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 '-_': tree.right.parent = tree; return this.opTable[tree.type](this.reduceIfReturnList(this.Evaluator(tree.right, local, fname))); case '++_': case '--_': tree.right.parent = tree; return this.opTable[tree.type](tree.right); case ".'": case "'": tree.left.parent = tree; return this.opTable[tree.type](this.reduceIfReturnList(this.Evaluator(tree.left, local, fname))); case '_++': case '_--': tree.left.parent = tree; return this.opTable[tree.type](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); const op: 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: any; try { right = this.Evaluator(tree.right, false, fname); } catch { right = tree.right; } if (right.type !== 'RETLIST') { right = this.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 = this.nodeListFirst(); for (let n = 0; n < assignment.length; n++) { const { left, id, args } = assignment[n]; if (left) { if (args.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 ? this.nodeOp(op, left, rightN) : rightN; try { this.nameTable[id] = { args: [], expr: this.reduceIfReturnList(this.Evaluator(expr)) }; this.nodeList(resultList, this.nodeOp('=', left, this.nameTable[id].expr)); continue; } catch (error) { this.nameTable[id] = { args: [], expr: expr }; throw error; } } else { /* Function definition or indexed matrix reference. */ if (op) { if (typeof this.nameTable[id] !== 'undefined') { if (this.nameTable[id].args.length === 0) { /* Indexed matrix reference on left hand side with operator. */ if (args.length === 1) { /* Test logical indexing. */ const arg0 = this.reduceIfReturnList(this.Evaluator(args[0], local, fname)); if (this.isTensor(arg0) && arg0.type === ComplexDecimal.numberClass.logical) { /* Logical indexing. */ this.setElementsLogical( this.nameTable, id, this.linearize(arg0), this.toTensor( this.reduceIfReturnList( this.Evaluator( this.nodeOp( op, this.getElementsLogical(this.nameTable[id].expr, id, arg0), this.toTensor(this.reduceIfReturnList(this.Evaluator(right.selector(assignment.length, n)))), ), false, fname, ), ), ), ); } else { /* Not logical indexing. */ this.setElements( this.nameTable, id, [arg0], this.toTensor( this.reduceIfReturnList( this.Evaluator( this.nodeOp( op, this.getElements(this.nameTable[id].expr, id, [arg0]), this.toTensor(this.reduceIfReturnList(this.Evaluator(right.selector(assignment.length, n)))), ), false, fname, ), ), ), ); } } else { this.setElements( this.nameTable, id, args.map((arg: any) => this.reduceIfReturnList(this.Evaluator(arg))), this.toTensor( this.reduceIfReturnList( this.Evaluator( this.nodeOp( op, this.getElements(this.nameTable[id].expr, id, args), this.toTensor(this.reduceIfReturnList(this.Evaluator(right.selector(assignment.length, n)))), ), false, fname, ), ), ), ); } this.nodeList(resultList, this.nodeOp('=', this.nodeName(id), this.nameTable[id].expr)); continue; } else { throw new EvalError(`in computed assignment ${id}(index) OP= X, ${id} cannot be a function.`); } } else { throw new EvalError(`in computed assignment ${id}(index) OP= X, ${id} must be defined first.`); } } else { /* Test if is a function definition (test if args is a list of undefined NAME). */ let isFunction: boolean = true; for (let i = 0; i < args.length; i++) { isFunction &&= args[i].type === 'NAME'; if (isFunction) { isFunction &&= typeof this.nameTable[args[i].id] === 'undefined'; } if (!isFunction) { break; } } if (isFunction) { this.nameTable[id] = { args: args, expr: right.selector(assignment.length, n) }; this.nodeList(resultList, tree); continue; } else { /* Indexed matrix reference on left hand side. */ if (args.length === 1) { /* Test logical indexing. */ args[0].parent = tree.left; args[0].index = 0; const arg0 = this.reduceIfReturnList(this.Evaluator(args[0], local, fname)); if (this.isTensor(arg0) && arg0.type === ComplexDecimal.numberClass.logical) { /* Logical indexing. */ this.setElementsLogical( this.nameTable, id, this.linearize(arg0), this.toTensor(this.reduceIfReturnList(this.Evaluator(right.selector(assignment.length, n)))), ); } else { /* Not logical indexing. */ this.setElements( this.nameTable, id, [arg0], this.toTensor(this.reduceIfReturnList(this.Evaluator(right.selector(assignment.length, n)))), ); } } else { this.setElements( this.nameTable, id, args.map((arg: any, i: number) => { arg.parent = tree.left; arg.index = i; return this.reduceIfReturnList(this.Evaluator(arg)); }), this.toTensor(this.reduceIfReturnList(this.Evaluator(right.selector(assignment.length, n)))), ); } this.nodeList(resultList, this.nodeOp('=', this.nodeName(id), this.nameTable[id].expr)); continue; } } } } } if (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 any).right; } case 'NAME': 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. */ if (this.nameTable[tree.id].args.length === 0) { /* Defined as name. */ this.nameTable[tree.id].expr.parent = tree; return this.reduceIfReturnList(this.Evaluator(this.nameTable[tree.id].expr)); } else { /* Defined as function name. */ throw new EvalError(`calling ${tree.id} function without arguments list.`); } } else { throw new ReferenceError(`'${tree.id}' undefined.`); } case 'LIST': const result = { type: 'LIST', list: new Array(tree.list.length), parent: tree.parent === null ? null : tree, }; for (let i = 0; i < tree.list.length; i++) { /* Convert undefined name, defined in word-list command, to word-list command. * (Null length word-list command) */ if ( tree.list[i].type === 'NAME' && !(local && this.localTable[fname] && this.localTable[fname][tree.list[i].id]) && !(tree.list[i].id in this.nameTable) && commandsTable.indexOf(tree.list[i].id) >= 0 ) { tree.list[i].type = 'CmdWList'; tree.list[i]['args'] = []; } tree.list[i].parent = result; tree.list[i].index = i; result.list[i] = this.reduceIfReturnList(this.Evaluator(tree.list[i], local, fname)); if (typeof result.list[i].type === 'number') { this.nameTable['ans'] = { args: [], expr: result.list[i] }; } } return result; case 'RANGE': tree.start.parent = tree; tree.stop.parent = tree; if (tree.stride) { tree.stride.parent = tree; } return this.expandRange( this.reduceIfReturnList(this.Evaluator(tree.start, local, fname)), this.reduceIfReturnList(this.Evaluator(tree.stop, local, fname)), tree.stride ? this.reduceIfReturnList(this.Evaluator(tree.stride, local, fname)) : null, ); case 'ENDRANGE': { let parent = tree.parent; /* Search for 'ARG' node until reach 'ARG' or root node */ while (parent !== null && parent.type !== 'ARG') { parent = parent.parent; } if ( parent &&