decimal-eval
Version:
A tiny, safe, fast JavaScript library for decimal arithmetic expressions.
1,082 lines (884 loc) • 27.9 kB
JavaScript
(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