UNPKG

@quantlab/handsontable

Version:

Spreadsheet-like data grid editor that provides copy/paste functionality compatible with Excel/Google Docs

249 lines (212 loc) 5.94 kB
import Emitter from 'tiny-emitter'; import evaluateByOperator from './evaluate-by-operator/evaluate-by-operator'; import {Parser as GrammarParser} from './grammar-parser/grammar-parser'; import {trimEdges} from './helper/string'; import {toNumber, invertNumber} from './helper/number'; import errorParser, {isValidStrict as isErrorValid, ERROR, ERROR_NAME} from './error'; import {extractLabel, toLabel} from './helper/cell'; /** * @class Parser */ class Parser extends Emitter { constructor() { super(); this.parser = new GrammarParser(); this.parser.yy = { toNumber, trimEdges, invertNumber, throwError: (errorName) => this._throwError(errorName), callVariable: (variable) => this._callVariable(variable), evaluateByOperator, callFunction: (name, params) => this._callFunction(name, params), cellValue: (value) => this._callCellValue(value), rangeValue: (start, end) => this._callRangeValue(start, end), }; this.variables = Object.create(null); this.functions = Object.create(null); this .setVariable('TRUE', true) .setVariable('FALSE', false) .setVariable('NULL', null); } /** * Parse formula expression. * * @param {String} expression to parse. * @return {*} Returns an object with tow properties `error` and `result`. */ parse(expression) { let result = null; let error = null; try { if (expression === '') { result = ''; } else { result = this.parser.parse(expression); } } catch (ex) { const message = errorParser(ex.message); if (message) { error = message; } else { error = errorParser(ERROR); } } if (result instanceof Error) { error = errorParser(result.message) || errorParser(ERROR); result = null; } return { error, result, }; } /** * Set predefined variable name which can be visible while parsing formula expression. * * @param {String} name Variable name. * @param {*} value Variable value. * @returns {Parser} */ setVariable(name, value) { this.variables[name] = value; return this; } /** * Get variable name. * * @param {String} name Variable name. * @returns {*} */ getVariable(name) { return this.variables[name]; } /** * Retrieve variable value by its name. * * @param name Variable name. * @returns {*} * @private */ _callVariable(name) { let value = this.getVariable(name); this.emit('callVariable', name, (newValue) => { if (newValue !== void 0) { value = newValue; } }); if (value === void 0) { throw Error(ERROR_NAME); } return value; } /** * Set custom function which can be visible while parsing formula expression. * * @param {String} name Custom function name. * @param {Function} fn Custom function. * @returns {Parser} */ setFunction(name, fn) { this.functions[name] = fn; return this; } /** * Get custom function. * * @param {String} name Custom function name. * @returns {*} */ getFunction(name) { return this.functions[name]; } /** * Call function with provided params. * * @param name Function name. * @param params Function params. * @returns {*} * @private */ _callFunction(name, params = []) { const fn = this.getFunction(name); let value; if (fn) { value = fn(params); } this.emit('callFunction', name, params, (newValue) => { if (newValue !== void 0) { value = newValue; } }); return value === void 0 ? evaluateByOperator(name, params) : value; } /** * Retrieve value by its label (`B3`, `B$3`, `B$3`, `$B$3`). * * @param {String} label Coordinates. * @returns {*} * @private */ _callCellValue(label) { label = label.toUpperCase(); const [row, column] = extractLabel(label); let value = void 0; this.emit('callCellValue', {label, row, column}, (_value) => { value = _value; }); return value; } /** * Retrieve value by its label (`B3:A1`, `B$3:A1`, `B$3:$A1`, `$B$3:A$1`). * * @param {String} startLabel Coordinates of the first cell. * @param {String} endLabel Coordinates of the last cell. * @returns {Array} Returns an array of mixed values. * @private */ _callRangeValue(startLabel, endLabel) { startLabel = startLabel.toUpperCase(); endLabel = endLabel.toUpperCase(); const [startRow, startColumn] = extractLabel(startLabel); const [endRow, endColumn] = extractLabel(endLabel); let startCell = {}; let endCell = {}; if (startRow.index <= endRow.index) { startCell.row = startRow; endCell.row = endRow; } else { startCell.row = endRow; endCell.row = startRow; } if (startColumn.index <= endColumn.index) { startCell.column = startColumn; endCell.column = endColumn; } else { startCell.column = endColumn; endCell.column = startColumn; } startCell.label = toLabel(startCell.row, startCell.column); endCell.label = toLabel(endCell.row, endCell.column); let value = []; this.emit('callRangeValue', startCell, endCell, (_value = []) => { value = _value; }); return value; } /** * Try to throw error by its name. * * @param {String} errorName Error name. * @returns {String} * @private */ _throwError(errorName) { if (isErrorValid(errorName)) { throw Error(errorName); } throw Error(ERROR); } } export default Parser;