UNPKG

decimal-eval

Version:

A tiny, safe, fast JavaScript library for decimal arithmetic expressions.

1,082 lines (884 loc) 27.9 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.DecimalEval = {})); }(this, (function (exports) { 'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var TokenType = /** * Token label */ /** * 是否为二元运算符 */ /** * 是否可以作为前缀(一元运算符,仅支持运算符在左侧) */ /** * 运算符优先级 * @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence */ /** * 读取 Token 时更新上下文 */ /** * constructor * @param label * @param options */ function TokenType(label) { var _options$precedence; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; _classCallCheck(this, TokenType); _defineProperty(this, "label", void 0); _defineProperty(this, "isBinary", void 0); _defineProperty(this, "isPrefix", void 0); _defineProperty(this, "precedence", void 0); _defineProperty(this, "updateContext", void 0); this.label = label; this.isBinary = !!options.isBinary; this.isPrefix = !!options.isPrefix; this.precedence = (_options$precedence = options.precedence) !== null && _options$precedence !== void 0 ? _options$precedence : -1; }; var tokenTypes = { start: new TokenType('start'), end: new TokenType('end'), parenL: new TokenType('('), parenR: new TokenType(')'), numeric: new TokenType('numeric'), identifier: new TokenType('identifier'), plus: new TokenType('+', { isBinary: true, precedence: 13 }), minus: new TokenType('-', { isBinary: true, precedence: 13 }), times: new TokenType('*', { isBinary: true, precedence: 14 }), div: new TokenType('/', { isBinary: true, precedence: 14 }), prefixPlus: new TokenType('+', { isPrefix: true, precedence: 16 }), prefixMinus: new TokenType('-', { isPrefix: true, precedence: 16 }) }; tokenTypes.parenL.updateContext = function () { this.allowPrefix = true; }; tokenTypes.parenR.updateContext = function () { this.allowPrefix = false; }; tokenTypes.numeric.updateContext = function () { this.allowPrefix = false; }; tokenTypes.identifier.updateContext = function () { this.allowPrefix = false; }; /** * AST 节点 */ var Node = function Node(start) { _classCallCheck(this, Node); _defineProperty(this, "type", void 0); _defineProperty(this, "start", void 0); _defineProperty(this, "end", void 0); this.type = ''; this.start = start; this.end = start; }; /** * 判断数字可能的字符 * @param code */ function isNumericChar(code) { return code >= 48 && code <= 57; } /** * 判断数字开始字符 * @param code */ function isNumericStart(code) { if (code === 46) return true; // `.` return isNumericChar(code); // 0-9 } /** * 判断标识符开始字符 * @param code */ function isIdentifierStart(code) { if (code >= 65 && code <= 90) return true; // A-Z if (code >= 97 && code <= 122) return true; // a-z return false; } /** * 判断是标识符开始字符 * @param code */ function isIdentifierChar(code) { if (isIdentifierStart(code)) return true; if (isNumericChar(code)) return true; // 0-9 if (code === 95) return true; // `_` return false; } /** * 返回一个数字的原始数据 * @param rawValue */ function getRealNumeric(rawValue) { var value = rawValue; // 去掉数字分割线线 var arr = []; for (var i = 0; i < value.length; i++) { var code = value.charCodeAt(i); if (code !== 95) { // `_` arr.push(value[i]); } } if (arr.length !== value.length) { value = arr.join(''); } return value; } // 保留字符 var reserved = ['+', '-', '*', '/', '(', ')']; /** * 注册的所有自定义运算符 */ var installedOperators = []; /** * 注册自定义运算符 * @param operator */ function useOperator(operator) { if (!installedOperators.includes(operator)) { installedOperators.unshift(operator); // 注册相同运算符,保证后面注册的覆盖前面的 } } /** * 检查参数 * @param value * @param precedence */ function checkCreateArgs(value, precedence) { if (typeof value !== 'string' || !/^\S+$/.test(value)) { throw new Error('The custom operator should be a non-empty string'); } if (reserved.includes(value)) { throw new Error("The custom operator cannot use reserved character, including: ".concat(reserved.join(', '))); } if (isNumericStart(value.charCodeAt(0))) { // 0-9, `.` throw new Error('The custom operator cannot start with a possible number, including: `.`, 0-9'); } if (value.charCodeAt(0) === 63) { // `?` throw new Error('The custom operator cannot start with `?`'); } if (precedence != null && (typeof precedence !== 'number' || precedence < 0)) { throw new Error('The precedence should be a number greater than 0'); } } /** * 运算符 */ var Operator = function Operator(value, precedence, calc) { var isUnary = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; _classCallCheck(this, Operator); _defineProperty(this, "value", void 0); _defineProperty(this, "codes", void 0); _defineProperty(this, "type", void 0); _defineProperty(this, "calc", void 0); this.value = value; this.codes = value.split('').map(function (_, i) { return value.charCodeAt(i); }); this.type = new TokenType(value, { isBinary: !isUnary, isPrefix: isUnary, precedence: precedence }); this.calc = calc; }; function createBinaryOperator(value, precedence, calc) { checkCreateArgs(value, precedence); if (typeof calc !== 'function') { throw new Error('Expected to receive a calculation method, like: `(left, right) => String(left - right)`'); } return new Operator(value, precedence, calc, false); } /** * 创建一个一元运算符 * @param value * @param precedence * @param calc * * @see 运算符优先级参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence */ function createUnaryOperator(value, precedence, calc) { checkCreateArgs(value, precedence); if (typeof calc !== 'function') { throw new Error('Expected to receive a calculation method, like: `(value) => String(Math.abs(value))`'); } return new Operator(value, precedence, calc, true); } /** * 二元表达式计算方法适配器 */ var _adapter = { '+': function _(left, right) { return String(Number(left) + Number(right)); }, '-': function _(left, right) { return String(Number(left) - Number(right)); }, '*': function _(left, right) { return String(Number(left) * Number(right)); }, '/': function _(left, right) { return String(Number(left) / Number(right)); } }; /** * 使用指定的计算方法适配器用于计算值 * @param adapter */ function useAdapter(adapter) { var baseOperators = ['+', '-', '*', '/']; adapter = adapter || {}; baseOperators.forEach(function (op) { if (typeof adapter[op] !== 'function') { throw new Error("Missing method for calculation operator `".concat(op, "`")); } }); _adapter = adapter; } /** * 二元表达式计算 * @param left * @param right * @param operator */ function binaryCalculation(left, right, operator) { switch (operator) { case '+': case '-': case '*': case '/': return _adapter[operator](left, right); default: for (var i = 0; i < installedOperators.length; i++) { var op = installedOperators[i]; if (op.type.isBinary && op.value === operator) { return op.calc(left, right); } } } /* istanbul ignore next */ throw new Error("Unexpected binary operator: ".concat(operator)); } /** * 一元表达式计算 * @param value * @param operator */ function unaryCalculation(value, operator) { switch (operator) { case '+': return value; case '-': return String(-value); default: for (var i = 0; i < installedOperators.length; i++) { var op = installedOperators[i]; if (op.type.isPrefix && op.value === operator) { return op.calc(value); } } } /* istanbul ignore next */ throw new Error("Unexpected unary operator: ".concat(operator)); } /** * 转换 AST -> 计算结果 * @param node * @param scope */ function transform(node) { var scope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (node instanceof Node) { var scopeValue; switch (node.type) { case 'Expression': return transform(node.expression, scope); case 'BinaryExpression': return binaryCalculation(transform(node.left, scope), transform(node.right, scope), node.operator); case 'UnaryExpression': return unaryCalculation(transform(node.argument, scope), node.operator); case 'NumericLiteral': return node.value; case 'Identifier': scopeValue = scope[node.name]; if (scopeValue === undefined) { throw new Error("The scope name `".concat(node.name, "` is not initialized")); } return String(scopeValue); default: /* istanbul ignore next */ throw new Error("Unexpected type: ".concat(node.type)); } } return node; } /** * AST Parser */ var Parser = /*#__PURE__*/function () { /** * 解析的字符串 * @param input */ function Parser(input) { _classCallCheck(this, Parser); _defineProperty(this, "input", void 0); _defineProperty(this, "node", void 0); _defineProperty(this, "pos", void 0); _defineProperty(this, "tokenType", void 0); _defineProperty(this, "value", void 0); _defineProperty(this, "start", void 0); _defineProperty(this, "end", void 0); _defineProperty(this, "lastTokenStart", void 0); _defineProperty(this, "lastTokenEnd", void 0); _defineProperty(this, "allowPrefix", void 0); this.input = String(input); this.tokenType = tokenTypes.start; this.value = ''; this.pos = 0; this.start = 0; this.end = 0; this.lastTokenStart = 0; this.lastTokenEnd = 0; this.allowPrefix = true; } /** * 编译表达式 */ _createClass(Parser, [{ key: "compile", value: function compile() { var node = this.node; if (node === undefined) { node = this.parse(); } return function (scope) { if (!node) return '0'; return transform(node, scope !== null && scope !== void 0 ? scope : {}); }; } /** * 开始解析,如果是空字符串返回 `null` */ }, { key: "parse", value: function parse() { this.next(); var node = this.startNode(this.start); if (this.tokenType === tokenTypes.end) { return this.node = null; // 空字符 } node.expression = this.parseExpression(); if (this.tokenType !== tokenTypes.end) { // 其后遇到其他非法字符 this.unexpected(this.value); } return this.node = this.finishNode(node, 'Expression'); } /** * 解析表达式 */ }, { key: "parseExpression", value: function parseExpression() { return this.parseExprAtom(this.start, -1); } /** * 解析一个表达式原子, 如: `1 + 2`、`(1 + 2 + 3)`、`1` 都作为一个表达式原子解析 * @param leftStartPos 左侧开始位置 * @param minPrecedence 当前上下文的优先级 */ }, { key: "parseExprAtom", value: function parseExprAtom(leftStartPos, minPrecedence) { if (this.tokenType === tokenTypes.parenL) { // 遇到 `(` 则递归解析表达式原子 this.next(); var left = this.parseExprAtom(this.start, -1); this.expect(tokenTypes.parenR); return this.parseExprOp(left, leftStartPos, minPrecedence); // 将 `(expr)` 整体作为左值,进入优先级解析流程 } else { var _left = this.parseMaybeUnary(minPrecedence); return this.parseExprOp(_left, leftStartPos, minPrecedence); // 读取一个数字作为左值,进入优先级解析流程 } } /** * 解析二元表达式优先级 * @param left 左值 * @param leftStartPos 左值节点起始位置 * @param minPrecedence 当前上下文的优先级 */ }, { key: "parseExprOp", value: function parseExprOp(left, leftStartPos, minPrecedence) { var precedence = this.tokenType.precedence; if (this.tokenType.isBinary && precedence > minPrecedence) { // 比较当前运算符与上下文优先级 var node = this.startNode(leftStartPos); var operator = this.value; this.next(); // 解析可能更高优先级的右侧表达式,如: `1 + 2 * 3` 将解析 `2 * 3` 作为右值 var start = this.start; var maybeHighPrecedenceExpr = this.parseExprAtom(start, precedence); var right = this.parseExprOp(maybeHighPrecedenceExpr, start, precedence); node.left = left; node.operator = operator; node.right = right; this.finishNode(node, 'BinaryExpression'); // 将已经解析的二元表达式作为左值,然后递归解析后面可能的同等优先级或低优先级的表达式作为右值 // 如: `1 + 2 + 3`, 当前已经解析 `1 + 2`, 然后将该节点作为左值递归解析表达式优先级 return this.parseExprOp(node, leftStartPos, minPrecedence); } return left; } /** * 解析可能带前缀的表达式,如: `+1`, `-(2)`, `3`, `-(3 + 4)`, `+-+5` * @param minPrecedence 当前上下文的优先级 */ }, { key: "parseMaybeUnary", value: function parseMaybeUnary(minPrecedence) { var precedence = this.tokenType.precedence; var node = this.startNode(); var start = this.start; var value = this.value; // Note: `1 ++ 1` 会当作 `1 + (+1)` 对待,与 JS 会作为 `1++` 对待不同 if (this.tokenType.isPrefix) { if (precedence >= minPrecedence) { // 相同优先级的一元运算符可以连续 node.operator = value; node.prefix = true; this.next(); node.argument = this.parseExprAtom(this.start, precedence); return this.finishNode(node, 'UnaryExpression'); } } if (this.tokenType === tokenTypes.numeric) { var realValue = getRealNumeric(value); node.rawValue = value; node.value = realValue; this.next(); return this.finishNode(node, 'NumericLiteral'); } if (this.tokenType === tokenTypes.identifier) { node.name = value; this.next(); return this.finishNode(node, 'Identifier'); } return this.unexpected(value, start); } /** * 读取并移到下一个 Token */ }, { key: "next", value: function next() { this.lastTokenStart = this.start; this.lastTokenEnd = this.end; this.skipSpace(); this.start = this.pos; if (this.pos >= this.input.length) { if (this.tokenType === tokenTypes.end) return; this.finishToken(tokenTypes.end); } else { this.readToken(); } } /** * 读取一个 token */ }, { key: "readToken", value: function readToken() { var code = this.codeAt(this.pos); if (isNumericStart(code)) { return this.readNumeric(); } return this.readTokenFromCode(); } /** * 读取一个数字 */ }, { key: "readNumeric", value: function readNumeric() { var chunkStart = this.pos; var countE = -1; // 统计字符 `e` 出现次数,-1 表示当前位置不允许出现 `e` var allowDot = true; var allowUnderline = false; var expectANumber = false; // 是否期望字符是数字 var unexpectedPos = -1; while (this.isValidPosition()) { var code = this.codeAt(this.pos); if (isNumericChar(code)) { // 0-9 if (countE === -1) { countE = 0; } this.pos++; expectANumber = false; allowUnderline = true; } else if (expectANumber) { unexpectedPos = this.pos; expectANumber = false; break; } else if (code === 69 || code === 101) { // `E` / `e` if (countE !== 0) { unexpectedPos = this.pos; break; } countE++; this.pos++; if (this.isValidPosition() && (this.codeAt(this.pos) === 43 || this.codeAt(this.pos) === 45) // `+` / `-` ) { this.pos++; } allowDot = false; expectANumber = true; } else if (code === 46) { // `.` if (!allowDot) { unexpectedPos = this.pos; break; } allowDot = false; this.pos++; if (this.pos - chunkStart === 1) { expectANumber = true; } } else if (code === 95) { // `_` if (!allowUnderline) { unexpectedPos = this.pos; break; } allowUnderline = false; expectANumber = true; this.pos++; } else { break; } } if (expectANumber) { unexpectedPos = this.pos - 1; } if (unexpectedPos >= 0) { this.unexpected(this.input[unexpectedPos], unexpectedPos); } var value = this.input.slice(chunkStart, this.pos); this.finishToken(tokenTypes.numeric, value); } /** * 根据字符 code 读取一个 Token,包括自定义注册的运算符 */ }, { key: "readTokenFromCode", value: function readTokenFromCode() { var code = this.codeAt(this.pos); // 优先解析自定义运算符 var operator, i, j; for (i = 0; i < installedOperators.length; i++) { var op = installedOperators[i]; for (j = 0; j < op.codes.length; j++) { if (op.codes[j] !== this.codeAt(this.pos + j)) break; } if (j === op.codes.length) { operator = op; break; } } if (operator) { this.pos += operator.codes.length; return this.finishToken(operator.type, operator.value); } switch (code) { case 40: // `(` this.pos++; return this.finishToken(tokenTypes.parenL, '('); case 41: // `)` this.pos++; return this.finishToken(tokenTypes.parenR, ')'); case 42: // `*` this.pos++; return this.finishToken(tokenTypes.times, '*'); case 43: // `+` this.pos++; if (this.allowPrefix) { return this.finishToken(tokenTypes.prefixPlus, '+'); } return this.finishToken(tokenTypes.plus, '+'); case 45: // `-` this.pos++; if (this.allowPrefix) { return this.finishToken(tokenTypes.prefixMinus, '-'); } return this.finishToken(tokenTypes.minus, '-'); case 47: // `/` this.pos++; return this.finishToken(tokenTypes.div, '/'); default: if (isIdentifierStart(code)) { return this.readIdentifier(); } } this.unexpected(this.input[this.pos]); } /** * 读取一个 scope 变量 Token */ }, { key: "readIdentifier", value: function readIdentifier() { var chunkStart = this.pos; while (this.isValidPosition()) { var code = this.codeAt(this.pos); if (isIdentifierChar(code)) { this.pos++; } else { break; } } var value = this.input.slice(chunkStart, this.pos); this.finishToken(tokenTypes.identifier, value); } /** * 完善一个 Token * @param type * @param value */ }, { key: "finishToken", value: function finishToken(type) { var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; var prevType = this.tokenType; this.end = this.pos; this.tokenType = type; this.value = value; this.updateContext(prevType); } /** * 读取 Token 完成后更新上下文 * @param prevType */ }, { key: "updateContext", value: function updateContext(prevType) { var type = this.tokenType; if (type.isBinary || type.isPrefix) { this.allowPrefix = true; } else if (type.updateContext) { type.updateContext.call(this, prevType); } } }, { key: "codeAt", value: function codeAt(index) { return this.input.charCodeAt(index); } }, { key: "isValidPosition", value: function isValidPosition() { return this.pos < this.input.length; } /** * 在当前位置创建一个新节点 */ }, { key: "startNode", value: function startNode(pos) { return new Node(pos !== null && pos !== void 0 ? pos : this.start); } /** * 完善一个节点 * @param node * @param type */ }, { key: "finishNode", value: function finishNode(node, type) { node.type = type; node.end = this.lastTokenEnd; return node; } /** * 跳过空白字符 */ }, { key: "skipSpace", value: function skipSpace() { while (this.isValidPosition()) { var code = this.codeAt(this.pos); if (code === 32 || code === 160) { // ` ` this.pos++; } else if (code === 13 || code === 10 || code === 8232 || code === 8233) { // new line if (code === 13 && this.codeAt(this.pos + 1) === 10) { // CRLF this.pos++; } this.pos++; } else if (code > 8 && code < 14) { // 制表符等 this.pos++; } else { break; } } } /** * 消费当前指定类型的 token,否则抛出异常 * @param type */ }, { key: "expect", value: function expect(type) { if (!this.eat(type)) { this.unexpected(this.value); } } /** * 消费一个 token,如果是指定的 token 类型,则移动到下一个 token ,返回 true,否则返回 false * @param type */ }, { key: "eat", value: function eat(type) { if (this.tokenType === type) { this.next(); return true; } return false; } /** * 抛出 Unexpected token 异常 * @param token * @param pos */ }, { key: "unexpected", value: function unexpected() { var token = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; var pos = arguments.length > 1 ? arguments[1] : undefined; this.raise(pos !== null && pos !== void 0 ? pos : this.start, "Unexpected token ".concat(token)); } /** * 抛出异常 * @param pos * @param message */ }, { key: "raise", value: function raise(pos, message) { if (pos > this.input.length - 1) { message = 'Unexpected end of input'; } else { message += " at position ".concat(pos); } throw new SyntaxError(message); } }], [{ key: "evaluate", value: // 输入的解析字符串 // 解析后的 AST 节点 // 当前位置 // 当前 Token 的类型 // 当前 Token 的值 // 当前 Token 的开始位置 // 当前 Token 的结束位置 // 上一个 Token 的开始位置 // 上一个 Token 的结束位置 // 当前上下文是否允许前缀 // pubic static method function evaluate(expression, scope) { return new Parser(expression).compile()(scope); } // internal static }]); return Parser; }(); _defineProperty(Parser, "createBinaryOperator", void 0); _defineProperty(Parser, "createUnaryOperator", void 0); _defineProperty(Parser, "useOperator", void 0); _defineProperty(Parser, "useAdapter", void 0); _defineProperty(Parser, "Node", Node); _defineProperty(Parser, "TokenType", TokenType); _defineProperty(Parser, "tokenTypes", tokenTypes); _defineProperty(Parser, "installedOperators", installedOperators); var evaluate = Parser.evaluate; Parser.createBinaryOperator = createBinaryOperator; Parser.createUnaryOperator = createUnaryOperator; Parser.useOperator = useOperator; Parser.useAdapter = useAdapter; var DecimalEval = { evaluate: evaluate, Parser: Parser, Operator: Operator, version: "0.1.1" }; exports.Operator = Operator; exports.Parser = Parser; exports.default = DecimalEval; exports.evaluate = evaluate; Object.defineProperty(exports, '__esModule', { value: true }); }))); //# sourceMappingURL=pure.js.map