├── .babelrc ├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── exp-parser.js └── exp-parser.min.js ├── lib ├── constants.js ├── handler.js ├── index.js ├── utils.js └── warn.js ├── package.json ├── src ├── constants.js ├── handler.js ├── index.js ├── utils.js └── warn.js ├── test └── parser.spec.js ├── webpack.config.development.js ├── webpack.config.js └── webpack.config.production.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015","stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | repo_token: V5L1Ro4D4htiGhOpKR6Y2kNLjXHZM87zI 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea 3 | .idea/ 4 | coverage -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: node_js 3 | node_js: 4 | - "v4" 5 | 6 | before_install: 7 | - | 8 | if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(^(docs|examples))/' 9 | then 10 | echo "Only docs were updated, stopping build process." 11 | exit 12 | fi 13 | npm install mocha babel-cli -g 14 | mocha --version & babel --version 15 | script: 16 | - | 17 | npm test 18 | npm run coveralls -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Janry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/exp-parser.svg)](https://badge.fury.io/js/exp-parser) 2 | [![Build Status](https://travis-ci.org/janryWang/exp-parser.svg)](https://travis-ci.org/janryWang/exp-parser) 3 | [![Coverage Status](https://coveralls.io/repos/janryWang/exp-parser/badge.svg?branch=master&service=github)](https://coveralls.io/github/janryWang/exp-parser?branch=master) 4 | 5 | Expression Parser(exp-parser) 6 | === 7 | 8 | ##安装 9 | 10 | ``` 11 | npm install --save exp-parser 12 | ``` 13 | 14 | ##问题 15 | 16 | 1. 为啥要山寨一个表达式解析器? 17 | 2. 表达式解析器能做神马事情? 18 | 3. 怎么自制表达式解析器? 19 | 20 | 21 | 22 | ##回答 23 | 24 | 1. 因为好玩,因为市面上好像还没有一款独立的表达式解析器,如果有的话,体积估计也不小,我打算做一个较为轻量级,同时扩展性较强的表达式解析器,功能与angular1.x的指令表达式解析器一致,最重要是的可扩展,提供内核级别的扩展性 25 | 2. 类似于angular的指令表达式,你可以集成在模板引擎中,主要用于解析组件的属性,组件于组件间传值计算,过滤,话说。。。。这其实就是将视图逻辑从后端中释放出来。 26 | 3. 本人其实就是个初学者,原来一直想搞个表达式解析器,苦于无从下手,后来找到了[jsep](http://jsep.from.so/)这个表达式AST生成器,它能自动解析表达式,生成一个json结构的抽象语法树,所以,后面我仅仅只需要深度遍历这棵树就行了,然后边遍历边计算,注意是后序遍历,就这样一层一层的剥开计算结果的心。其实真心没啥技术含量,我做这个仅仅只是为了好玩,又或者说后面自己需要的时候也不需要各种找东西,麻烦。。。。 27 | 28 | 29 | ##API文档 30 | 31 | ###解析 32 | 33 | ``` 34 | import ExpressionParser form 'expression-parser' 35 | 36 | let parser = new ExpressionParser({//注入一个全局上下文,比如将window注入进去,不过不建议这样做,如果方法被拦截,可能会发生危险,尽量是将闭包内受保护的对象注入其中 37 | 38 | }) 39 | 40 | parser.parse('2 * 2') // 4 41 | 42 | parser.parse('a * b',{ //变量求值 43 | a:4, 44 | b:5 45 | }) // 20 46 | 47 | parser.parse('fuck(a,b)',{//函数求值 48 | fuck(x,y){ 49 | return x * y; 50 | }, 51 | a:5, 52 | b:6 53 | }) // 30 54 | 55 | parser.parse('a * b|fuck:34:cc',{//作用域注入 56 | a:5, 57 | b:6, 58 | cc:7 59 | },{//过滤器注入 60 | fuck(x,y){ 61 | return (input)=>{ 62 | return (input - x) / y; 63 | } 64 | } 65 | }) 66 | 67 | ``` 68 | 69 | ###扩展 70 | 71 | ``` 72 | 73 | const {BINARY_EXP} = ExpressionParser.expressionTypes; 74 | 75 | 76 | ExpressionParser.injectExpHandler(BINARY_EXP,(tree)=>{ 77 | 78 | }) 79 | 80 | //添加一个优先级为10的二进制操作符^ 81 | ExpressionParser.addBinaryOp("^", 10); 82 | 83 | //添加一个一元操作符@ 84 | ExpressionParser.addUnaryOp('@'); 85 | 86 | //移除一个二进制操作符 87 | ExpressionParser.removeBinaryOp(">>>"); 88 | 89 | //移除一个一元操作符 90 | ExpressionParser.removeUnaryOp("~"); 91 | 92 | 93 | ``` 94 | -------------------------------------------------------------------------------- /dist/exp-parser.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define([], factory); 6 | else if(typeof exports === 'object') 7 | exports["ExpressionParser"] = factory(); 8 | else 9 | root["ExpressionParser"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | 39 | 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | 'use strict'; 58 | 59 | var _createClass = (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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 60 | 61 | Object.defineProperty(exports, "__esModule", { 62 | value: true 63 | }); 64 | 65 | var _utils = __webpack_require__(1); 66 | 67 | var _handler = __webpack_require__(3); 68 | 69 | var _jsep = __webpack_require__(4); 70 | 71 | var _jsep2 = _interopRequireDefault(_jsep); 72 | 73 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 74 | 75 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 76 | 77 | _jsep2.default.removeBinaryOp("|"); 78 | 79 | /** 80 | * 暂时还没注入处理器的功能,还有一个就是表达式内使用new操作符的功能, 81 | * 这个比较麻烦,要修改jsep的源码,后面打算给jsep提个pr吧 82 | **/ 83 | 84 | var parseTree = function parseTree(target, scope) { 85 | var cache = { 86 | iterationObj: scope 87 | }; 88 | function parseNode(node) { 89 | var result = null; 90 | 91 | (0, _utils.each)(_handler.ExpressionTypes, function (type) { 92 | if (node.type == type) { 93 | result = _handler.EXP_HANDLER[type](parseNode, cache)(node, scope); 94 | return false; 95 | } 96 | }); 97 | return result; 98 | } 99 | return parseNode((0, _jsep2.default)(target)); 100 | }; 101 | 102 | var ExpressionParser = (function () { 103 | function ExpressionParser(globalScope, globalFilter) { 104 | _classCallCheck(this, ExpressionParser); 105 | 106 | this.globalScope = globalScope; 107 | this.globalFilter = globalFilter; 108 | } 109 | 110 | _createClass(ExpressionParser, [{ 111 | key: 'filter', 112 | value: function filter(_filter) { 113 | (0, _utils.extend)(this.globalFilter, _filter); 114 | return this; 115 | } 116 | }, { 117 | key: 'scope', 118 | value: function scope(_scope) { 119 | (0, _utils.extend)(this.globalScope, _scope); 120 | return this; 121 | } 122 | }, { 123 | key: 'parse', 124 | value: function parse(str, scope, filter) { 125 | if ((0, _utils.isStr)(str)) { 126 | scope = (0, _utils.extend)(scope, this.globalScope); 127 | filter = (0, _utils.extend)(filter, this.globalFilter); 128 | 129 | var _parseStr2 = this._parseStr(str); 130 | 131 | var target = _parseStr2.target; 132 | var filterArr = _parseStr2.filterArr; 133 | 134 | return this._resolveFilter(filterArr, filter)(parseTree(target, scope), scope); 135 | } 136 | } 137 | }, { 138 | key: '_resolveFilter', 139 | value: function _resolveFilter(filterArr, filter) { 140 | return function (target, scope) { 141 | if (filterArr.length) { 142 | (0, _utils.each)(filterArr, function (filterStr) { 143 | filterStr = filterStr.split(":"); 144 | var name = filterStr[0], 145 | args = undefined, 146 | filterFunc = undefined; 147 | if (filter && filter[name]) { 148 | args = filterStr.slice(1).map(function (arg) { 149 | return parseTree(arg, scope); 150 | }); 151 | filterFunc = filter[name].apply(scope, args); 152 | if ((0, _utils.isFunc)(filterFunc)) { 153 | target = filterFunc(target); 154 | } else { 155 | //报错 156 | } 157 | } else { 158 | //报错 159 | } 160 | }); 161 | return target; 162 | } else { 163 | return target; 164 | } 165 | }; 166 | } 167 | }, { 168 | key: '_parseStr', 169 | value: function _parseStr(str) { 170 | str = str.replace(/\s*/g, "").split("|"); 171 | if (str.length > 1) { 172 | return { 173 | target: str[0], 174 | filterArr: str.slice(1) 175 | }; 176 | } else { 177 | return { 178 | target: str[0], 179 | filterArr: [] 180 | }; 181 | } 182 | } 183 | }]); 184 | 185 | return ExpressionParser; 186 | })(); 187 | 188 | ExpressionParser.injectExpHandler = function () {}; 189 | 190 | ExpressionParser.ExpressionTypes = _handler.ExpressionTypes; 191 | 192 | (0, _utils.extend)(ExpressionParser, _jsep2.default); 193 | 194 | exports.default = ExpressionParser; 195 | 196 | /***/ }, 197 | /* 1 */ 198 | /***/ function(module, exports) { 199 | 200 | 'use strict'; 201 | 202 | Object.defineProperty(exports, "__esModule", { 203 | value: true 204 | }); 205 | var is = function is(type) { 206 | return function (val) { 207 | return Object.prototype.toString.call(val) == '[object ' + type + ']'; 208 | }; 209 | }; 210 | 211 | var isFunc = exports.isFunc = is('Function'); 212 | 213 | var isStr = exports.isStr = is('String'); 214 | 215 | var isArr = exports.isArr = is('Array'); 216 | 217 | var isObj = exports.isObj = is('Object'); 218 | 219 | var each = exports.each = function each(obj, func) { 220 | if (!obj || !isFunc(func)) return; 221 | var keys = Object.keys(obj), 222 | i = undefined, 223 | val = undefined, 224 | key = undefined; 225 | for (i = 0; i < keys.length; i++) { 226 | key = keys[i]; 227 | val = obj[key]; 228 | if (obj.hasOwnProperty(key)) { 229 | if (func(val, key) === false) break; 230 | } 231 | } 232 | }; 233 | 234 | var extend = exports.extend = function extend(obj1, obj2) { 235 | if (isObj(obj1) || isArr(obj1)) { 236 | each(obj2, function (val, key) { 237 | obj1[key] = val; 238 | }); 239 | return obj1; 240 | } else { 241 | return obj1; 242 | } 243 | }; 244 | 245 | var calculator = exports.calculator = function calculator(operator) { 246 | return { 247 | unary: function unary(x) { 248 | return new Function('return ' + operator + x); 249 | }, 250 | binary: function binary(x, y) { 251 | return new Function('return ' + x + operator + y); 252 | } 253 | }; 254 | }; 255 | 256 | var toArray = exports.toArray = function toArray(obj) { 257 | return Array.prototype.slice.call(obj); 258 | }; 259 | 260 | /***/ }, 261 | /* 2 */ 262 | /***/ function(module, exports) { 263 | 264 | 'use strict'; 265 | 266 | Object.defineProperty(exports, "__esModule", { 267 | value: true 268 | }); 269 | var ExpressionTypes = exports.ExpressionTypes = { 270 | COMPOUND: 'Compound', 271 | IDENTIFIER: 'Identifier', 272 | MEMBER_EXP: 'MemberExpression', 273 | LITERAL: 'Literal', 274 | THIS_EXP: 'ThisExpression', 275 | CALL_EXP: 'CallExpression', 276 | UNARY_EXP: 'UnaryExpression', 277 | BINARY_EXP: 'BinaryExpression', 278 | LOGICAL_EXP: 'LogicalExpression', 279 | CONDITIONAL_EXP: 'ConditionalExpression', 280 | ARRAY_EXP: 'ArrayExpression' 281 | }; 282 | 283 | var ExpressionKeywords = exports.ExpressionKeywords = { 284 | NEW: 'new' 285 | }; 286 | 287 | /***/ }, 288 | /* 3 */ 289 | /***/ function(module, exports, __webpack_require__) { 290 | 291 | 'use strict'; 292 | 293 | var _EXP_HANDLER; 294 | 295 | Object.defineProperty(exports, "__esModule", { 296 | value: true 297 | }); 298 | exports.EXP_HANDLER = exports.ExpressionTypes = undefined; 299 | 300 | var _constants = __webpack_require__(2); 301 | 302 | var constants = _interopRequireWildcard(_constants); 303 | 304 | var _utils = __webpack_require__(1); 305 | 306 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 307 | 308 | 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; } 309 | 310 | var _constants$Expression = constants.ExpressionTypes; 311 | var COMPOUND = _constants$Expression.COMPOUND; 312 | var IDENTIFIER = _constants$Expression.IDENTIFIER; 313 | var MEMBER_EXP = _constants$Expression.MEMBER_EXP; 314 | var LITERAL = _constants$Expression.LITERAL; 315 | var THIS_EXP = _constants$Expression.THIS_EXP; 316 | var CALL_EXP = _constants$Expression.CALL_EXP; 317 | var UNARY_EXP = _constants$Expression.UNARY_EXP; 318 | var BINARY_EXP = _constants$Expression.BINARY_EXP; 319 | var LOGICAL_EXP = _constants$Expression.LOGICAL_EXP; 320 | var CONDITIONAL_EXP = _constants$Expression.CONDITIONAL_EXP; 321 | var ARRAY_EXP = _constants$Expression.ARRAY_EXP; 322 | var ExpressionTypes = exports.ExpressionTypes = constants.ExpressionTypes; 323 | 324 | var EXP_HANDLER = exports.EXP_HANDLER = (_EXP_HANDLER = {}, _defineProperty(_EXP_HANDLER, COMPOUND, function () { 325 | return function () {}; 326 | }), _defineProperty(_EXP_HANDLER, IDENTIFIER, function () { 327 | return function (node, scope) { 328 | return scope[node.name]; 329 | }; 330 | }), _defineProperty(_EXP_HANDLER, MEMBER_EXP, function (parseNode, cache) { 331 | return function (node) { 332 | var object = node.object; 333 | var property = node.property; 334 | var iterationObj = cache.iterationObj; 335 | 336 | switch (node.object.type) { 337 | case MEMBER_EXP: 338 | cache.iterationObj = parseNode(object)[property.name]; 339 | break; 340 | case THIS_EXP: 341 | cache.iterationObj = iterationObj[property.name]; 342 | break; 343 | case IDENTIFIER: 344 | cache.iterationObj = iterationObj[object.name][property.name]; 345 | } 346 | return cache.iterationObj; 347 | }; 348 | }), _defineProperty(_EXP_HANDLER, LITERAL, function () { 349 | return function (node) { 350 | return node.value; 351 | }; 352 | }), _defineProperty(_EXP_HANDLER, CALL_EXP, function (parseNode) { 353 | return function (node, scope) { 354 | var callee = node.callee; 355 | 356 | var args = (0, _utils.toArray)(node.arguments).map(function (arg) { 357 | return parseNode(arg, scope); 358 | }); 359 | if (callee.type == IDENTIFIER) { 360 | var method = scope[callee.name]; 361 | if (method) { 362 | if ((0, _utils.isFunc)(method)) { 363 | return method.apply(scope, args); 364 | } else if ((0, _utils.isObj)(method)) { 365 | if ((0, _utils.isFunc)(method.func)) { 366 | return method.func.apply(method.context, args); 367 | } else { 368 | //报错 369 | } 370 | } else { 371 | //报错 372 | } 373 | } else { 374 | //报错 375 | } 376 | } 377 | }; 378 | }), _defineProperty(_EXP_HANDLER, UNARY_EXP, function (parseNode) { 379 | return function (node) { 380 | if (node.argument.type !== UNARY_EXP) { 381 | return (0, _utils.calculator)(node.operator).unary(parseNode(node.argument))(); 382 | } else { 383 | //报错,不能在表达式内赋值 384 | } 385 | }; 386 | }), _defineProperty(_EXP_HANDLER, BINARY_EXP, function (parseNode) { 387 | return function (node) { 388 | return (0, _utils.calculator)(node.operator).binary(parseNode(node.left), parseNode(node.right))(); 389 | }; 390 | }), _defineProperty(_EXP_HANDLER, LOGICAL_EXP, function (parseNode) { 391 | return function (node) { 392 | return (0, _utils.calculator)(node.operator).binary(parseNode(node.left), parseNode(node.right))(); 393 | }; 394 | }), _defineProperty(_EXP_HANDLER, CONDITIONAL_EXP, function (parseNode) { 395 | return function (node) { 396 | if (parseNode(node.test)) { 397 | return parseNode(node.consequent); 398 | } else { 399 | return parseNode(node.alternate); 400 | } 401 | }; 402 | }), _defineProperty(_EXP_HANDLER, ARRAY_EXP, function (parseNode) { 403 | return function (node) { 404 | var res = []; 405 | (0, _utils.each)(node.elements, function (el) { 406 | res.push(parseNode(el)); 407 | }); 408 | return res; 409 | }; 410 | }), _EXP_HANDLER); 411 | 412 | /***/ }, 413 | /* 4 */ 414 | /***/ function(module, exports, __webpack_require__) { 415 | 416 | // JavaScript Expression Parser (JSEP) 0.3.0 417 | // JSEP may be freely distributed under the MIT License 418 | // http://jsep.from.so/ 419 | 420 | /*global module: true, exports: true, console: true */ 421 | (function (root) { 422 | 'use strict'; 423 | // Node Types 424 | // ---------- 425 | 426 | // This is the full set of types that any JSEP node can be. 427 | // Store them here to save space when minified 428 | var COMPOUND = 'Compound', 429 | IDENTIFIER = 'Identifier', 430 | MEMBER_EXP = 'MemberExpression', 431 | LITERAL = 'Literal', 432 | THIS_EXP = 'ThisExpression', 433 | CALL_EXP = 'CallExpression', 434 | UNARY_EXP = 'UnaryExpression', 435 | BINARY_EXP = 'BinaryExpression', 436 | LOGICAL_EXP = 'LogicalExpression', 437 | CONDITIONAL_EXP = 'ConditionalExpression', 438 | ARRAY_EXP = 'ArrayExpression', 439 | 440 | PERIOD_CODE = 46, // '.' 441 | COMMA_CODE = 44, // ',' 442 | SQUOTE_CODE = 39, // single quote 443 | DQUOTE_CODE = 34, // double quotes 444 | OPAREN_CODE = 40, // ( 445 | CPAREN_CODE = 41, // ) 446 | OBRACK_CODE = 91, // [ 447 | CBRACK_CODE = 93, // ] 448 | QUMARK_CODE = 63, // ? 449 | SEMCOL_CODE = 59, // ; 450 | COLON_CODE = 58, // : 451 | 452 | throwError = function(message, index) { 453 | var error = new Error(message + ' at character ' + index); 454 | error.index = index; 455 | error.description = message; 456 | throw error; 457 | }, 458 | 459 | // Operations 460 | // ---------- 461 | 462 | // Set `t` to `true` to save space (when minified, not gzipped) 463 | t = true, 464 | // Use a quickly-accessible map to store all of the unary operators 465 | // Values are set to `true` (it really doesn't matter) 466 | unary_ops = {'-': t, '!': t, '~': t, '+': t}, 467 | // Also use a map for the binary operations but set their values to their 468 | // binary precedence for quick reference: 469 | // see [Order of operations](http://en.wikipedia.org/wiki/Order_of_operations#Programming_language) 470 | binary_ops = { 471 | '||': 1, '&&': 2, '|': 3, '^': 4, '&': 5, 472 | '==': 6, '!=': 6, '===': 6, '!==': 6, 473 | '<': 7, '>': 7, '<=': 7, '>=': 7, 474 | '<<':8, '>>': 8, '>>>': 8, 475 | '+': 9, '-': 9, 476 | '*': 10, '/': 10, '%': 10 477 | }, 478 | // Get return the longest key length of any object 479 | getMaxKeyLen = function(obj) { 480 | var max_len = 0, len; 481 | for(var key in obj) { 482 | if((len = key.length) > max_len && obj.hasOwnProperty(key)) { 483 | max_len = len; 484 | } 485 | } 486 | return max_len; 487 | }, 488 | max_unop_len = getMaxKeyLen(unary_ops), 489 | max_binop_len = getMaxKeyLen(binary_ops), 490 | // Literals 491 | // ---------- 492 | // Store the values to return for the various literals we may encounter 493 | literals = { 494 | 'true': true, 495 | 'false': false, 496 | 'null': null 497 | }, 498 | // Except for `this`, which is special. This could be changed to something like `'self'` as well 499 | this_str = 'this', 500 | // Returns the precedence of a binary operator or `0` if it isn't a binary operator 501 | binaryPrecedence = function(op_val) { 502 | return binary_ops[op_val] || 0; 503 | }, 504 | // Utility function (gets called from multiple places) 505 | // Also note that `a && b` and `a || b` are *logical* expressions, not binary expressions 506 | createBinaryExpression = function (operator, left, right) { 507 | var type = (operator === '||' || operator === '&&') ? LOGICAL_EXP : BINARY_EXP; 508 | return { 509 | type: type, 510 | operator: operator, 511 | left: left, 512 | right: right 513 | }; 514 | }, 515 | // `ch` is a character code in the next three functions 516 | isDecimalDigit = function(ch) { 517 | return (ch >= 48 && ch <= 57); // 0...9 518 | }, 519 | isIdentifierStart = function(ch) { 520 | return (ch === 36) || (ch === 95) || // `$` and `_` 521 | (ch >= 65 && ch <= 90) || // A...Z 522 | (ch >= 97 && ch <= 122); // a...z 523 | }, 524 | isIdentifierPart = function(ch) { 525 | return (ch === 36) || (ch === 95) || // `$` and `_` 526 | (ch >= 65 && ch <= 90) || // A...Z 527 | (ch >= 97 && ch <= 122) || // a...z 528 | (ch >= 48 && ch <= 57); // 0...9 529 | }, 530 | 531 | // Parsing 532 | // ------- 533 | // `expr` is a string with the passed in expression 534 | jsep = function(expr) { 535 | // `index` stores the character number we are currently at while `length` is a constant 536 | // All of the gobbles below will modify `index` as we move along 537 | var index = 0, 538 | charAtFunc = expr.charAt, 539 | charCodeAtFunc = expr.charCodeAt, 540 | exprI = function(i) { return charAtFunc.call(expr, i); }, 541 | exprICode = function(i) { return charCodeAtFunc.call(expr, i); }, 542 | length = expr.length, 543 | 544 | // Push `index` up to the next non-space character 545 | gobbleSpaces = function() { 546 | var ch = exprICode(index); 547 | // space or tab 548 | while(ch === 32 || ch === 9) { 549 | ch = exprICode(++index); 550 | } 551 | }, 552 | 553 | // The main parsing function. Much of this code is dedicated to ternary expressions 554 | gobbleExpression = function() { 555 | var test = gobbleBinaryExpression(), 556 | consequent, alternate; 557 | gobbleSpaces(); 558 | if(exprICode(index) === QUMARK_CODE) { 559 | // Ternary expression: test ? consequent : alternate 560 | index++; 561 | consequent = gobbleExpression(); 562 | if(!consequent) { 563 | throwError('Expected expression', index); 564 | } 565 | gobbleSpaces(); 566 | if(exprICode(index) === COLON_CODE) { 567 | index++; 568 | alternate = gobbleExpression(); 569 | if(!alternate) { 570 | throwError('Expected expression', index); 571 | } 572 | return { 573 | type: CONDITIONAL_EXP, 574 | test: test, 575 | consequent: consequent, 576 | alternate: alternate 577 | }; 578 | } else { 579 | throwError('Expected :', index); 580 | } 581 | } else { 582 | return test; 583 | } 584 | }, 585 | 586 | // Search for the operation portion of the string (e.g. `+`, `===`) 587 | // Start by taking the longest possible binary operations (3 characters: `===`, `!==`, `>>>`) 588 | // and move down from 3 to 2 to 1 character until a matching binary operation is found 589 | // then, return that binary operation 590 | gobbleBinaryOp = function() { 591 | gobbleSpaces(); 592 | var biop, to_check = expr.substr(index, max_binop_len), tc_len = to_check.length; 593 | while(tc_len > 0) { 594 | if(binary_ops.hasOwnProperty(to_check)) { 595 | index += tc_len; 596 | return to_check; 597 | } 598 | to_check = to_check.substr(0, --tc_len); 599 | } 600 | return false; 601 | }, 602 | 603 | // This function is responsible for gobbling an individual expression, 604 | // e.g. `1`, `1+2`, `a+(b*2)-Math.sqrt(2)` 605 | gobbleBinaryExpression = function() { 606 | var ch_i, node, biop, prec, stack, biop_info, left, right, i; 607 | 608 | // First, try to get the leftmost thing 609 | // Then, check to see if there's a binary operator operating on that leftmost thing 610 | left = gobbleToken(); 611 | biop = gobbleBinaryOp(); 612 | 613 | // If there wasn't a binary operator, just return the leftmost node 614 | if(!biop) { 615 | return left; 616 | } 617 | 618 | // Otherwise, we need to start a stack to properly place the binary operations in their 619 | // precedence structure 620 | biop_info = { value: biop, prec: binaryPrecedence(biop)}; 621 | 622 | right = gobbleToken(); 623 | if(!right) { 624 | throwError("Expected expression after " + biop, index); 625 | } 626 | stack = [left, biop_info, right]; 627 | 628 | // Properly deal with precedence using [recursive descent](http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm) 629 | while((biop = gobbleBinaryOp())) { 630 | prec = binaryPrecedence(biop); 631 | 632 | if(prec === 0) { 633 | break; 634 | } 635 | biop_info = { value: biop, prec: prec }; 636 | 637 | // Reduce: make a binary expression from the three topmost entries. 638 | while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) { 639 | right = stack.pop(); 640 | biop = stack.pop().value; 641 | left = stack.pop(); 642 | node = createBinaryExpression(biop, left, right); 643 | stack.push(node); 644 | } 645 | 646 | node = gobbleToken(); 647 | if(!node) { 648 | throwError("Expected expression after " + biop, index); 649 | } 650 | stack.push(biop_info, node); 651 | } 652 | 653 | i = stack.length - 1; 654 | node = stack[i]; 655 | while(i > 1) { 656 | node = createBinaryExpression(stack[i - 1].value, stack[i - 2], node); 657 | i -= 2; 658 | } 659 | return node; 660 | }, 661 | 662 | // An individual part of a binary expression: 663 | // e.g. `foo.bar(baz)`, `1`, `"abc"`, `(a % 2)` (because it's in parenthesis) 664 | gobbleToken = function() { 665 | var ch, to_check, tc_len; 666 | 667 | gobbleSpaces(); 668 | ch = exprICode(index); 669 | 670 | if(isDecimalDigit(ch) || ch === PERIOD_CODE) { 671 | // Char code 46 is a dot `.` which can start off a numeric literal 672 | return gobbleNumericLiteral(); 673 | } else if(ch === SQUOTE_CODE || ch === DQUOTE_CODE) { 674 | // Single or double quotes 675 | return gobbleStringLiteral(); 676 | } else if(isIdentifierStart(ch) || ch === OPAREN_CODE) { // open parenthesis 677 | // `foo`, `bar.baz` 678 | return gobbleVariable(); 679 | } else if (ch === OBRACK_CODE) { 680 | return gobbleArray(); 681 | } else { 682 | to_check = expr.substr(index, max_unop_len); 683 | tc_len = to_check.length; 684 | while(tc_len > 0) { 685 | if(unary_ops.hasOwnProperty(to_check)) { 686 | index += tc_len; 687 | return { 688 | type: UNARY_EXP, 689 | operator: to_check, 690 | argument: gobbleToken(), 691 | prefix: true 692 | }; 693 | } 694 | to_check = to_check.substr(0, --tc_len); 695 | } 696 | 697 | return false; 698 | } 699 | }, 700 | // Parse simple numeric literals: `12`, `3.4`, `.5`. Do this by using a string to 701 | // keep track of everything in the numeric literal and then calling `parseFloat` on that string 702 | gobbleNumericLiteral = function() { 703 | var number = '', ch, chCode; 704 | while(isDecimalDigit(exprICode(index))) { 705 | number += exprI(index++); 706 | } 707 | 708 | if(exprICode(index) === PERIOD_CODE) { // can start with a decimal marker 709 | number += exprI(index++); 710 | 711 | while(isDecimalDigit(exprICode(index))) { 712 | number += exprI(index++); 713 | } 714 | } 715 | 716 | ch = exprI(index); 717 | if(ch === 'e' || ch === 'E') { // exponent marker 718 | number += exprI(index++); 719 | ch = exprI(index); 720 | if(ch === '+' || ch === '-') { // exponent sign 721 | number += exprI(index++); 722 | } 723 | while(isDecimalDigit(exprICode(index))) { //exponent itself 724 | number += exprI(index++); 725 | } 726 | if(!isDecimalDigit(exprICode(index-1)) ) { 727 | throwError('Expected exponent (' + number + exprI(index) + ')', index); 728 | } 729 | } 730 | 731 | 732 | chCode = exprICode(index); 733 | // Check to make sure this isn't a variable name that start with a number (123abc) 734 | if(isIdentifierStart(chCode)) { 735 | throwError('Variable names cannot start with a number (' + 736 | number + exprI(index) + ')', index); 737 | } else if(chCode === PERIOD_CODE) { 738 | throwError('Unexpected period', index); 739 | } 740 | 741 | return { 742 | type: LITERAL, 743 | value: parseFloat(number), 744 | raw: number 745 | }; 746 | }, 747 | 748 | // Parses a string literal, staring with single or double quotes with basic support for escape codes 749 | // e.g. `"hello world"`, `'this is\nJSEP'` 750 | gobbleStringLiteral = function() { 751 | var str = '', quote = exprI(index++), closed = false, ch; 752 | 753 | while(index < length) { 754 | ch = exprI(index++); 755 | if(ch === quote) { 756 | closed = true; 757 | break; 758 | } else if(ch === '\\') { 759 | // Check for all of the common escape codes 760 | ch = exprI(index++); 761 | switch(ch) { 762 | case 'n': str += '\n'; break; 763 | case 'r': str += '\r'; break; 764 | case 't': str += '\t'; break; 765 | case 'b': str += '\b'; break; 766 | case 'f': str += '\f'; break; 767 | case 'v': str += '\x0B'; break; 768 | } 769 | } else { 770 | str += ch; 771 | } 772 | } 773 | 774 | if(!closed) { 775 | throwError('Unclosed quote after "'+str+'"', index); 776 | } 777 | 778 | return { 779 | type: LITERAL, 780 | value: str, 781 | raw: quote + str + quote 782 | }; 783 | }, 784 | 785 | // Gobbles only identifiers 786 | // e.g.: `foo`, `_value`, `$x1` 787 | // Also, this function checks if that identifier is a literal: 788 | // (e.g. `true`, `false`, `null`) or `this` 789 | gobbleIdentifier = function() { 790 | var ch = exprICode(index), start = index, identifier; 791 | 792 | if(isIdentifierStart(ch)) { 793 | index++; 794 | } else { 795 | throwError('Unexpected ' + exprI(index), index); 796 | } 797 | 798 | while(index < length) { 799 | ch = exprICode(index); 800 | if(isIdentifierPart(ch)) { 801 | index++; 802 | } else { 803 | break; 804 | } 805 | } 806 | identifier = expr.slice(start, index); 807 | 808 | if(literals.hasOwnProperty(identifier)) { 809 | return { 810 | type: LITERAL, 811 | value: literals[identifier], 812 | raw: identifier 813 | }; 814 | } else if(identifier === this_str) { 815 | return { type: THIS_EXP }; 816 | } else { 817 | return { 818 | type: IDENTIFIER, 819 | name: identifier 820 | }; 821 | } 822 | }, 823 | 824 | // Gobbles a list of arguments within the context of a function call 825 | // or array literal. This function also assumes that the opening character 826 | // `(` or `[` has already been gobbled, and gobbles expressions and commas 827 | // until the terminator character `)` or `]` is encountered. 828 | // e.g. `foo(bar, baz)`, `my_func()`, or `[bar, baz]` 829 | gobbleArguments = function(termination) { 830 | var ch_i, args = [], node; 831 | while(index < length) { 832 | gobbleSpaces(); 833 | ch_i = exprICode(index); 834 | if(ch_i === termination) { // done parsing 835 | index++; 836 | break; 837 | } else if (ch_i === COMMA_CODE) { // between expressions 838 | index++; 839 | } else { 840 | node = gobbleExpression(); 841 | if(!node || node.type === COMPOUND) { 842 | throwError('Expected comma', index); 843 | } 844 | args.push(node); 845 | } 846 | } 847 | return args; 848 | }, 849 | 850 | // Gobble a non-literal variable name. This variable name may include properties 851 | // e.g. `foo`, `bar.baz`, `foo['bar'].baz` 852 | // It also gobbles function calls: 853 | // e.g. `Math.acos(obj.angle)` 854 | gobbleVariable = function() { 855 | var ch_i, node; 856 | ch_i = exprICode(index); 857 | 858 | if(ch_i === OPAREN_CODE) { 859 | node = gobbleGroup(); 860 | } else { 861 | node = gobbleIdentifier(); 862 | } 863 | gobbleSpaces(); 864 | ch_i = exprICode(index); 865 | while(ch_i === PERIOD_CODE || ch_i === OBRACK_CODE || ch_i === OPAREN_CODE) { 866 | index++; 867 | if(ch_i === PERIOD_CODE) { 868 | gobbleSpaces(); 869 | node = { 870 | type: MEMBER_EXP, 871 | computed: false, 872 | object: node, 873 | property: gobbleIdentifier() 874 | }; 875 | } else if(ch_i === OBRACK_CODE) { 876 | node = { 877 | type: MEMBER_EXP, 878 | computed: true, 879 | object: node, 880 | property: gobbleExpression() 881 | }; 882 | gobbleSpaces(); 883 | ch_i = exprICode(index); 884 | if(ch_i !== CBRACK_CODE) { 885 | throwError('Unclosed [', index); 886 | } 887 | index++; 888 | } else if(ch_i === OPAREN_CODE) { 889 | // A function call is being made; gobble all the arguments 890 | node = { 891 | type: CALL_EXP, 892 | 'arguments': gobbleArguments(CPAREN_CODE), 893 | callee: node 894 | }; 895 | } 896 | gobbleSpaces(); 897 | ch_i = exprICode(index); 898 | } 899 | return node; 900 | }, 901 | 902 | // Responsible for parsing a group of things within parentheses `()` 903 | // This function assumes that it needs to gobble the opening parenthesis 904 | // and then tries to gobble everything within that parenthesis, assuming 905 | // that the next thing it should see is the close parenthesis. If not, 906 | // then the expression probably doesn't have a `)` 907 | gobbleGroup = function() { 908 | index++; 909 | var node = gobbleExpression(); 910 | gobbleSpaces(); 911 | if(exprICode(index) === CPAREN_CODE) { 912 | index++; 913 | return node; 914 | } else { 915 | throwError('Unclosed (', index); 916 | } 917 | }, 918 | 919 | // Responsible for parsing Array literals `[1, 2, 3]` 920 | // This function assumes that it needs to gobble the opening bracket 921 | // and then tries to gobble the expressions as arguments. 922 | gobbleArray = function() { 923 | index++; 924 | return { 925 | type: ARRAY_EXP, 926 | elements: gobbleArguments(CBRACK_CODE) 927 | }; 928 | }, 929 | 930 | nodes = [], ch_i, node; 931 | 932 | while(index < length) { 933 | ch_i = exprICode(index); 934 | 935 | // Expressions can be separated by semicolons, commas, or just inferred without any 936 | // separators 937 | if(ch_i === SEMCOL_CODE || ch_i === COMMA_CODE) { 938 | index++; // ignore separators 939 | } else { 940 | // Try to gobble each expression individually 941 | if((node = gobbleExpression())) { 942 | nodes.push(node); 943 | // If we weren't able to find a binary expression and are out of room, then 944 | // the expression passed in probably has too much 945 | } else if(index < length) { 946 | throwError('Unexpected "' + exprI(index) + '"', index); 947 | } 948 | } 949 | } 950 | 951 | // If there's only one expression just try returning the expression 952 | if(nodes.length === 1) { 953 | return nodes[0]; 954 | } else { 955 | return { 956 | type: COMPOUND, 957 | body: nodes 958 | }; 959 | } 960 | }; 961 | 962 | // To be filled in by the template 963 | jsep.version = '0.3.0'; 964 | jsep.toString = function() { return 'JavaScript Expression Parser (JSEP) v' + jsep.version; }; 965 | 966 | /** 967 | * @method jsep.addUnaryOp 968 | * @param {string} op_name The name of the unary op to add 969 | * @return jsep 970 | */ 971 | jsep.addUnaryOp = function(op_name) { 972 | unary_ops[op_name] = t; return this; 973 | }; 974 | 975 | /** 976 | * @method jsep.addBinaryOp 977 | * @param {string} op_name The name of the binary op to add 978 | * @param {number} precedence The precedence of the binary op (can be a float) 979 | * @return jsep 980 | */ 981 | jsep.addBinaryOp = function(op_name, precedence) { 982 | max_binop_len = Math.max(op_name.length, max_binop_len); 983 | binary_ops[op_name] = precedence; 984 | return this; 985 | }; 986 | 987 | /** 988 | * @method jsep.removeUnaryOp 989 | * @param {string} op_name The name of the unary op to remove 990 | * @return jsep 991 | */ 992 | jsep.removeUnaryOp = function(op_name) { 993 | delete unary_ops[op_name]; 994 | if(op_name.length === max_unop_len) { 995 | max_unop_len = getMaxKeyLen(unary_ops); 996 | } 997 | return this; 998 | }; 999 | 1000 | /** 1001 | * @method jsep.removeBinaryOp 1002 | * @param {string} op_name The name of the binary op to remove 1003 | * @return jsep 1004 | */ 1005 | jsep.removeBinaryOp = function(op_name) { 1006 | delete binary_ops[op_name]; 1007 | if(op_name.length === max_binop_len) { 1008 | max_binop_len = getMaxKeyLen(binary_ops); 1009 | } 1010 | return this; 1011 | }; 1012 | 1013 | // In desktop environments, have a way to restore the old value for `jsep` 1014 | if (false) { 1015 | var old_jsep = root.jsep; 1016 | // The star of the show! It's a function! 1017 | root.jsep = jsep; 1018 | // And a courteous function willing to move out of the way for other similarly-named objects! 1019 | jsep.noConflict = function() { 1020 | if(root.jsep === jsep) { 1021 | root.jsep = old_jsep; 1022 | } 1023 | return jsep; 1024 | }; 1025 | } else { 1026 | // In Node.JS environments 1027 | if (typeof module !== 'undefined' && module.exports) { 1028 | exports = module.exports = jsep; 1029 | } else { 1030 | exports.parse = jsep; 1031 | } 1032 | } 1033 | }(this)); 1034 | 1035 | 1036 | /***/ } 1037 | /******/ ]) 1038 | }); 1039 | ; -------------------------------------------------------------------------------- /dist/exp-parser.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"object"==typeof exports&&"object"==typeof module?module.exports=r():"function"==typeof define&&define.amd?define([],r):"object"==typeof exports?exports.ExpressionParser=r():e.ExpressionParser=r()}(this,function(){return function(e){function r(t){if(n[t])return n[t].exports;var o=n[t]={exports:{},id:t,loaded:!1};return e[t].call(o.exports,o,o.exports,r),o.loaded=!0,o.exports}var n={};return r.m=e,r.c=n,r.p="",r(0)}([function(e,r,n){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}function o(e,r){if(!(e instanceof r))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,r){for(var n=0;n1?{target:e[0],filterArr:e.slice(1)}:{target:e[0],filterArr:[]}}}]),e}();p.injectExpHandler=function(){},p.ExpressionTypes=a.ExpressionTypes,(0,u.extend)(p,s.default),r.default=p},function(e,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0});var n=function(e){return function(r){return Object.prototype.toString.call(r)=="[object "+e+"]"}},t=r.isFunc=n("Function"),o=(r.isStr=n("String"),r.isArr=n("Array")),i=r.isObj=n("Object"),u=r.each=function(e,r){if(e&&t(r)){var n=Object.keys(e),o=void 0,i=void 0,u=void 0;for(o=0;o":7,"<=":7,">=":7,"<<":8,">>":8,">>>":8,"+":9,"-":9,"*":10,"/":10,"%":10},I=function(e){var r,n=0;for(var t in e)(r=t.length)>n&&e.hasOwnProperty(t)&&(n=r);return n},T=I(w),X=I(L),R={"true":!0,"false":!1,"null":null},k="this",C=function(e){return L[e]||0},N=function(e,r,n){var t="||"===e||"&&"===e?p:f;return{type:t,operator:e,left:r,right:n}},F=function(e){return e>=48&&57>=e},M=function(e){return 36===e||95===e||e>=65&&90>=e||e>=97&&122>=e},S=function(e){return 36===e||95===e||e>=65&&90>=e||e>=97&&122>=e||e>=48&&57>=e},U=function(e){for(var r,n,f=0,p=e.charAt,j=e.charCodeAt,I=function(r){return p.call(e,r)},U=function(r){return j.call(e,r)},B=e.length,D=function(){for(var e=U(f);32===e||9===e;)e=U(++f)},H=function(){var e,r,n=q();return D(),U(f)!==m?n:(f++,e=H(),e||A("Expected expression",f),D(),U(f)===_?(f++,r=H(),r||A("Expected expression",f),{type:l,test:n,consequent:e,alternate:r}):void A("Expected :",f))},Y=function(){D();for(var r=e.substr(f,X),n=r.length;n>0;){if(L.hasOwnProperty(r))return f+=n,r;r=r.substr(0,--n)}return!1},q=function(){var e,r,n,t,o,i,u,a;if(i=G(),r=Y(),!r)return i;for(o={value:r,prec:C(r)},u=G(),u||A("Expected expression after "+r,f),t=[i,o,u];(r=Y())&&(n=C(r),0!==n);){for(o={value:r,prec:n};t.length>2&&n<=t[t.length-2].prec;)u=t.pop(),r=t.pop().value,i=t.pop(),e=N(r,i,u),t.push(e);e=G(),e||A("Expected expression after "+r,f),t.push(o,e)}for(a=t.length-1,e=t[a];a>1;)e=N(t[a-1].value,t[a-2],e),a-=2;return e},G=function(){var r,n,t;if(D(),r=U(f),F(r)||r===d)return J();if(r===E||r===x)return K();if(M(r)||r===b)return z();if(r===g)return Z();for(n=e.substr(f,T),t=n.length;t>0;){if(w.hasOwnProperty(n))return f+=t,{type:s,operator:n,argument:G(),prefix:!0};n=n.substr(0,--t)}return!1},J=function(){for(var e,r,n="";F(U(f));)n+=I(f++);if(U(f)===d)for(n+=I(f++);F(U(f));)n+=I(f++);if(e=I(f),"e"===e||"E"===e){for(n+=I(f++),e=I(f),("+"===e||"-"===e)&&(n+=I(f++));F(U(f));)n+=I(f++);F(U(f-1))||A("Expected exponent ("+n+I(f)+")",f)}return r=U(f),M(r)?A("Variable names cannot start with a number ("+n+I(f)+")",f):r===d&&A("Unexpected period",f),{type:u,value:parseFloat(n),raw:n}},K=function(){for(var e,r="",n=I(f++),t=!1;B>f;){if(e=I(f++),e===n){t=!0;break}if("\\"===e)switch(e=I(f++)){case"n":r+="\n";break;case"r":r+="\r";break;case"t":r+=" ";break;case"b":r+="\b";break;case"f":r+="\f";break;case"v":r+="\x0B"}else r+=e}return t||A('Unclosed quote after "'+r+'"',f),{type:u,value:r,raw:n+r+n}},V=function(){var r,n=U(f),t=f;for(M(n)?f++:A("Unexpected "+I(f),f);B>f&&(n=U(f),S(n));)f++;return r=e.slice(t,f),R.hasOwnProperty(r)?{type:u,value:R[r],raw:r}:r===k?{type:a}:{type:o,name:r}},W=function(e){for(var r,n,o=[];B>f;){if(D(),r=U(f),r===e){f++;break}r===v?f++:(n=H(),n&&n.type!==t||A("Expected comma",f),o.push(n))}return o},z=function(){var e,r;for(e=U(f),r=e===b?Q():V(),D(),e=U(f);e===d||e===g||e===b;)f++,e===d?(D(),r={type:i,computed:!1,object:r,property:V()}):e===g?(r={type:i,computed:!0,object:r,property:H()},D(),e=U(f),e!==O&&A("Unclosed [",f),f++):e===b&&(r={type:c,arguments:W(h),callee:r}),D(),e=U(f);return r},Q=function(){f++;var e=H();return D(),U(f)===h?(f++,e):void A("Unclosed (",f)},Z=function(){return f++,{type:y,elements:W(O)}},$=[];B>f;)r=U(f),r===P||r===v?f++:(n=H())?$.push(n):B>f&&A('Unexpected "'+I(f)+'"',f);return 1===$.length?$[0]:{type:t,body:$}};U.version="0.3.0",U.toString=function(){return"JavaScript Expression Parser (JSEP) v"+U.version},U.addUnaryOp=function(e){return w[e]=j,this},U.addBinaryOp=function(e,r){return X=Math.max(e.length,X),L[e]=r,this},U.removeUnaryOp=function(e){return delete w[e],e.length===T&&(T=I(w)),this},U.removeBinaryOp=function(e){return delete L[e],e.length===X&&(X=I(L)),this};"undefined"!=typeof e&&e.exports?r=e.exports=U:r.parse=U}(this)}])}); -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var ExpressionTypes = exports.ExpressionTypes = { 7 | COMPOUND: 'Compound', 8 | IDENTIFIER: 'Identifier', 9 | MEMBER_EXP: 'MemberExpression', 10 | LITERAL: 'Literal', 11 | THIS_EXP: 'ThisExpression', 12 | CALL_EXP: 'CallExpression', 13 | UNARY_EXP: 'UnaryExpression', 14 | BINARY_EXP: 'BinaryExpression', 15 | LOGICAL_EXP: 'LogicalExpression', 16 | CONDITIONAL_EXP: 'ConditionalExpression', 17 | ARRAY_EXP: 'ArrayExpression' 18 | }; 19 | 20 | var ExpressionKeywords = exports.ExpressionKeywords = { 21 | NEW: 'new' 22 | }; -------------------------------------------------------------------------------- /lib/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _EXP_HANDLER; 4 | 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | exports.EXP_HANDLER = exports.ExpressionTypes = undefined; 9 | 10 | var _constants = require('./constants'); 11 | 12 | var constants = _interopRequireWildcard(_constants); 13 | 14 | var _utils = require('./utils'); 15 | 16 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 17 | 18 | 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; } 19 | 20 | var _constants$Expression = constants.ExpressionTypes; 21 | var COMPOUND = _constants$Expression.COMPOUND; 22 | var IDENTIFIER = _constants$Expression.IDENTIFIER; 23 | var MEMBER_EXP = _constants$Expression.MEMBER_EXP; 24 | var LITERAL = _constants$Expression.LITERAL; 25 | var THIS_EXP = _constants$Expression.THIS_EXP; 26 | var CALL_EXP = _constants$Expression.CALL_EXP; 27 | var UNARY_EXP = _constants$Expression.UNARY_EXP; 28 | var BINARY_EXP = _constants$Expression.BINARY_EXP; 29 | var LOGICAL_EXP = _constants$Expression.LOGICAL_EXP; 30 | var CONDITIONAL_EXP = _constants$Expression.CONDITIONAL_EXP; 31 | var ARRAY_EXP = _constants$Expression.ARRAY_EXP; 32 | var ExpressionTypes = exports.ExpressionTypes = constants.ExpressionTypes; 33 | 34 | var EXP_HANDLER = exports.EXP_HANDLER = (_EXP_HANDLER = {}, _defineProperty(_EXP_HANDLER, COMPOUND, function () { 35 | return function () {}; 36 | }), _defineProperty(_EXP_HANDLER, IDENTIFIER, function () { 37 | return function (node, scope) { 38 | return scope[node.name]; 39 | }; 40 | }), _defineProperty(_EXP_HANDLER, MEMBER_EXP, function (parseNode, cache) { 41 | return function (node) { 42 | var object = node.object; 43 | var property = node.property; 44 | var iterationObj = cache.iterationObj; 45 | 46 | switch (node.object.type) { 47 | case MEMBER_EXP: 48 | cache.iterationObj = parseNode(object)[property.name]; 49 | break; 50 | case THIS_EXP: 51 | cache.iterationObj = iterationObj[property.name]; 52 | break; 53 | case IDENTIFIER: 54 | cache.iterationObj = iterationObj[object.name][property.name]; 55 | } 56 | return cache.iterationObj; 57 | }; 58 | }), _defineProperty(_EXP_HANDLER, LITERAL, function () { 59 | return function (node) { 60 | return node.value; 61 | }; 62 | }), _defineProperty(_EXP_HANDLER, CALL_EXP, function (parseNode) { 63 | return function (node, scope) { 64 | var callee = node.callee; 65 | 66 | var args = (0, _utils.toArray)(node.arguments).map(function (arg) { 67 | return parseNode(arg, scope); 68 | }); 69 | if (callee.type == IDENTIFIER) { 70 | var method = scope[callee.name]; 71 | if (method) { 72 | if ((0, _utils.isFunc)(method)) { 73 | return method.apply(scope, args); 74 | } else if ((0, _utils.isObj)(method)) { 75 | if ((0, _utils.isFunc)(method.func)) { 76 | return method.func.apply(method.context, args); 77 | } else { 78 | //报错 79 | } 80 | } else { 81 | //报错 82 | } 83 | } else { 84 | //报错 85 | } 86 | } 87 | }; 88 | }), _defineProperty(_EXP_HANDLER, UNARY_EXP, function (parseNode) { 89 | return function (node) { 90 | if (node.argument.type !== UNARY_EXP) { 91 | return (0, _utils.calculator)(node.operator).unary(parseNode(node.argument))(); 92 | } else { 93 | //报错,不能在表达式内赋值 94 | } 95 | }; 96 | }), _defineProperty(_EXP_HANDLER, BINARY_EXP, function (parseNode) { 97 | return function (node) { 98 | return (0, _utils.calculator)(node.operator).binary(parseNode(node.left), parseNode(node.right))(); 99 | }; 100 | }), _defineProperty(_EXP_HANDLER, LOGICAL_EXP, function (parseNode) { 101 | return function (node) { 102 | return (0, _utils.calculator)(node.operator).binary(parseNode(node.left), parseNode(node.right))(); 103 | }; 104 | }), _defineProperty(_EXP_HANDLER, CONDITIONAL_EXP, function (parseNode) { 105 | return function (node) { 106 | if (parseNode(node.test)) { 107 | return parseNode(node.consequent); 108 | } else { 109 | return parseNode(node.alternate); 110 | } 111 | }; 112 | }), _defineProperty(_EXP_HANDLER, ARRAY_EXP, function (parseNode) { 113 | return function (node) { 114 | var res = []; 115 | (0, _utils.each)(node.elements, function (el) { 116 | res.push(parseNode(el)); 117 | }); 118 | return res; 119 | }; 120 | }), _EXP_HANDLER); -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _createClass = (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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 4 | 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | 9 | var _utils = require('./utils'); 10 | 11 | var _handler = require('./handler'); 12 | 13 | var _jsep = require('jsep'); 14 | 15 | var _jsep2 = _interopRequireDefault(_jsep); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | _jsep2.default.removeBinaryOp("|"); 22 | 23 | /** 24 | * 暂时还没注入处理器的功能,还有一个就是表达式内使用new操作符的功能, 25 | * 这个比较麻烦,要修改jsep的源码,后面打算给jsep提个pr吧 26 | **/ 27 | 28 | var parseTree = function parseTree(target, scope) { 29 | var cache = { 30 | iterationObj: scope 31 | }; 32 | function parseNode(node) { 33 | var result = null; 34 | 35 | (0, _utils.each)(_handler.ExpressionTypes, function (type) { 36 | if (node.type == type) { 37 | result = _handler.EXP_HANDLER[type](parseNode, cache)(node, scope); 38 | return false; 39 | } 40 | }); 41 | return result; 42 | } 43 | return parseNode((0, _jsep2.default)(target)); 44 | }; 45 | 46 | var ExpressionParser = (function () { 47 | function ExpressionParser(globalScope, globalFilter) { 48 | _classCallCheck(this, ExpressionParser); 49 | 50 | this.globalScope = globalScope; 51 | this.globalFilter = globalFilter; 52 | } 53 | 54 | _createClass(ExpressionParser, [{ 55 | key: 'filter', 56 | value: function filter(_filter) { 57 | (0, _utils.extend)(this.globalFilter, _filter); 58 | return this; 59 | } 60 | }, { 61 | key: 'scope', 62 | value: function scope(_scope) { 63 | (0, _utils.extend)(this.globalScope, _scope); 64 | return this; 65 | } 66 | }, { 67 | key: 'parse', 68 | value: function parse(str, scope, filter) { 69 | if ((0, _utils.isStr)(str)) { 70 | scope = (0, _utils.extend)(scope, this.globalScope); 71 | filter = (0, _utils.extend)(filter, this.globalFilter); 72 | 73 | var _parseStr2 = this._parseStr(str); 74 | 75 | var target = _parseStr2.target; 76 | var filterArr = _parseStr2.filterArr; 77 | 78 | return this._resolveFilter(filterArr, filter)(parseTree(target, scope), scope); 79 | } 80 | } 81 | }, { 82 | key: '_resolveFilter', 83 | value: function _resolveFilter(filterArr, filter) { 84 | return function (target, scope) { 85 | if (filterArr.length) { 86 | (0, _utils.each)(filterArr, function (filterStr) { 87 | filterStr = filterStr.split(":"); 88 | var name = filterStr[0], 89 | args = undefined, 90 | filterFunc = undefined; 91 | if (filter && filter[name]) { 92 | args = filterStr.slice(1).map(function (arg) { 93 | return parseTree(arg, scope); 94 | }); 95 | filterFunc = filter[name].apply(scope, args); 96 | if ((0, _utils.isFunc)(filterFunc)) { 97 | target = filterFunc(target); 98 | } else { 99 | //报错 100 | } 101 | } else { 102 | //报错 103 | } 104 | }); 105 | return target; 106 | } else { 107 | return target; 108 | } 109 | }; 110 | } 111 | }, { 112 | key: '_parseStr', 113 | value: function _parseStr(str) { 114 | str = str.replace(/\s*/g, "").split("|"); 115 | if (str.length > 1) { 116 | return { 117 | target: str[0], 118 | filterArr: str.slice(1) 119 | }; 120 | } else { 121 | return { 122 | target: str[0], 123 | filterArr: [] 124 | }; 125 | } 126 | } 127 | }]); 128 | 129 | return ExpressionParser; 130 | })(); 131 | 132 | ExpressionParser.injectExpHandler = function () {}; 133 | 134 | ExpressionParser.ExpressionTypes = _handler.ExpressionTypes; 135 | 136 | (0, _utils.extend)(ExpressionParser, _jsep2.default); 137 | 138 | exports.default = ExpressionParser; -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var is = function is(type) { 7 | return function (val) { 8 | return Object.prototype.toString.call(val) == '[object ' + type + ']'; 9 | }; 10 | }; 11 | 12 | var isFunc = exports.isFunc = is('Function'); 13 | 14 | var isStr = exports.isStr = is('String'); 15 | 16 | var isArr = exports.isArr = is('Array'); 17 | 18 | var isObj = exports.isObj = is('Object'); 19 | 20 | var each = exports.each = function each(obj, func) { 21 | if (!obj || !isFunc(func)) return; 22 | var keys = Object.keys(obj), 23 | i = undefined, 24 | val = undefined, 25 | key = undefined; 26 | for (i = 0; i < keys.length; i++) { 27 | key = keys[i]; 28 | val = obj[key]; 29 | if (obj.hasOwnProperty(key)) { 30 | if (func(val, key) === false) break; 31 | } 32 | } 33 | }; 34 | 35 | var extend = exports.extend = function extend(obj1, obj2) { 36 | if (isObj(obj1) || isArr(obj1)) { 37 | each(obj2, function (val, key) { 38 | obj1[key] = val; 39 | }); 40 | return obj1; 41 | } else { 42 | return obj1; 43 | } 44 | }; 45 | 46 | var calculator = exports.calculator = function calculator(operator) { 47 | return { 48 | unary: function unary(x) { 49 | return new Function('return ' + operator + x); 50 | }, 51 | binary: function binary(x, y) { 52 | return new Function('return ' + x + operator + y); 53 | } 54 | }; 55 | }; 56 | 57 | var toArray = exports.toArray = function toArray(obj) { 58 | return Array.prototype.slice.call(obj); 59 | }; -------------------------------------------------------------------------------- /lib/warn.js: -------------------------------------------------------------------------------- 1 | "use strict"; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exp-parser", 3 | "version": "1.0.1", 4 | "description": "expression parser", 5 | "main": "./lib", 6 | "scripts": { 7 | "test": "mocha --compilers js:babel-core/register --recursive", 8 | "test:watch": "npm test -- --watch", 9 | "build:lib": "babel src --out-dir lib", 10 | "build:umd": "webpack src/index.js dist/exp-parser.js --config webpack.config.development.js", 11 | "build:umd:min": "webpack src/index.js dist/exp-parser.min.js --config webpack.config.production.js", 12 | "build": "npm run build:lib && npm run build:umd && npm run build:umd:min", 13 | "cover": "babel-node node_modules/isparta/bin/isparta cover --report lcovonly node_modules/mocha/bin/_mocha -- -R spec", 14 | "coveralls": "babel-node node_modules/isparta/bin/isparta cover --report lcovonly node_modules/mocha/bin/_mocha -- -R spec && cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", 15 | "prepublish": "npm run build" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/janryWang/exp-parser.git" 20 | }, 21 | "keywords": [ 22 | "exp-parser" 23 | ], 24 | "author": "janryWang", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/janryWang/exp-parser/issues" 28 | }, 29 | "homepage": "https://github.com/janryWang/exp-parser#readme", 30 | "dependencies": { 31 | "jsep": "~0.3.0" 32 | }, 33 | "devDependencies": { 34 | "babel-core": "^6.1.x", 35 | "babel-istanbul": "^0.5.9", 36 | "babel-loader": "^6.1.x", 37 | "babel-preset-es2015": "^6.1.x", 38 | "babel-preset-react": "^6.1.x", 39 | "babel-preset-stage-0": "^6.1.x", 40 | "chai": "^3.4.x", 41 | "coveralls": "^2.11.4", 42 | "isparta": "^4.0.0", 43 | "mocha": "^2.3.x", 44 | "mocha-lcov-reporter": "^1.0.0", 45 | "sinon": "^1.17.x", 46 | "webpack": "^1.12.x" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export const ExpressionTypes = { 4 | COMPOUND: 'Compound', 5 | IDENTIFIER: 'Identifier', 6 | MEMBER_EXP: 'MemberExpression', 7 | LITERAL: 'Literal', 8 | THIS_EXP: 'ThisExpression', 9 | CALL_EXP: 'CallExpression', 10 | UNARY_EXP: 'UnaryExpression', 11 | BINARY_EXP: 'BinaryExpression', 12 | LOGICAL_EXP: 'LogicalExpression', 13 | CONDITIONAL_EXP: 'ConditionalExpression', 14 | ARRAY_EXP: 'ArrayExpression' 15 | } 16 | 17 | export const ExpressionKeywords = { 18 | NEW: 'new' 19 | } 20 | -------------------------------------------------------------------------------- /src/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import * as constants from './constants' 4 | import { toArray,isFunc,isObj,each,calculator } from './utils' 5 | 6 | const { 7 | COMPOUND, 8 | IDENTIFIER, 9 | MEMBER_EXP, 10 | LITERAL, 11 | THIS_EXP, 12 | CALL_EXP, 13 | UNARY_EXP, 14 | BINARY_EXP, 15 | LOGICAL_EXP, 16 | CONDITIONAL_EXP, 17 | ARRAY_EXP 18 | } = constants.ExpressionTypes 19 | 20 | export const ExpressionTypes = constants.ExpressionTypes 21 | 22 | 23 | export let EXP_HANDLER = { 24 | [COMPOUND](){ 25 | return ()=> { 26 | 27 | } 28 | }, 29 | [IDENTIFIER](){ 30 | return (node, scope)=> scope[node.name] 31 | }, 32 | [MEMBER_EXP](parseNode, cache){ 33 | return (node)=> { 34 | let {object,property} = node 35 | let {iterationObj} = cache 36 | switch (node.object.type) { 37 | case MEMBER_EXP: 38 | cache.iterationObj = parseNode(object)[property.name] 39 | break 40 | case THIS_EXP: 41 | cache.iterationObj = iterationObj[property.name] 42 | break 43 | case IDENTIFIER: 44 | cache.iterationObj = iterationObj[object.name][property.name] 45 | } 46 | return cache.iterationObj 47 | } 48 | }, 49 | [LITERAL](){ 50 | return (node)=> node.value 51 | }, 52 | [CALL_EXP](parseNode){ 53 | return (node, scope)=> { 54 | let {callee} = node 55 | let args = toArray(node.arguments).map(function (arg) { 56 | return parseNode(arg, scope) 57 | }) 58 | if (callee.type == IDENTIFIER) { 59 | let method = scope[callee.name] 60 | if (method) { 61 | if (isFunc(method)) { 62 | return method.apply(scope, args) 63 | } else if (isObj(method)) { 64 | if (isFunc(method.func)) { 65 | return method.func.apply(method.context, args) 66 | } else { 67 | //报错 68 | } 69 | } else { 70 | //报错 71 | } 72 | } else { 73 | //报错 74 | } 75 | } 76 | 77 | } 78 | }, 79 | [UNARY_EXP](parseNode){ 80 | return (node)=> { 81 | if(node.argument.type !== UNARY_EXP){ 82 | return calculator(node.operator) 83 | .unary(parseNode(node.argument))() 84 | } else { 85 | //报错,不能在表达式内赋值 86 | } 87 | } 88 | }, 89 | [BINARY_EXP](parseNode){ 90 | return (node)=> calculator(node.operator) 91 | .binary(parseNode(node.left),parseNode(node.right))() 92 | }, 93 | [LOGICAL_EXP](parseNode){ 94 | return (node)=> calculator(node.operator) 95 | .binary(parseNode(node.left),parseNode(node.right))() 96 | }, 97 | [CONDITIONAL_EXP](parseNode){ 98 | return (node)=> { 99 | if (parseNode(node.test)) { 100 | return parseNode(node.consequent) 101 | } else { 102 | return parseNode(node.alternate) 103 | } 104 | } 105 | }, 106 | [ARRAY_EXP](parseNode){ 107 | return (node)=> { 108 | let res = [] 109 | each(node.elements,el => { 110 | res.push(parseNode(el)) 111 | }) 112 | return res 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { extend,isStr,each,isFunc } from './utils' 4 | import {ExpressionTypes,EXP_HANDLER} from './handler' 5 | import jsep from 'jsep' 6 | 7 | jsep.removeBinaryOp("|") 8 | 9 | /** 10 | * 暂时还没注入处理器的功能,还有一个就是表达式内使用new操作符的功能, 11 | * 这个比较麻烦,要修改jsep的源码,后面打算给jsep提个pr吧 12 | **/ 13 | 14 | const parseTree = (target, scope)=> { 15 | let cache = { 16 | iterationObj: scope 17 | } 18 | function parseNode (node){ 19 | let result = null 20 | 21 | each(ExpressionTypes, type=> { 22 | if (node.type == type) { 23 | result = EXP_HANDLER[type](parseNode, cache)(node, scope) 24 | return false 25 | } 26 | }) 27 | return result 28 | } 29 | return parseNode(jsep(target)) 30 | } 31 | 32 | class ExpressionParser { 33 | constructor(globalScope, globalFilter) { 34 | this.globalScope = globalScope 35 | this.globalFilter = globalFilter 36 | } 37 | 38 | filter(filter) { 39 | extend(this.globalFilter, filter) 40 | return this 41 | } 42 | 43 | scope(scope) { 44 | extend(this.globalScope, scope) 45 | return this 46 | } 47 | 48 | parse(str, scope, filter) { 49 | if (isStr(str)) { 50 | scope = extend(scope, this.globalScope) 51 | filter = extend(filter, this.globalFilter) 52 | let {target,filterArr} = this._parseStr(str) 53 | return this._resolveFilter(filterArr, filter) 54 | (parseTree(target, scope), scope) 55 | } 56 | } 57 | 58 | _resolveFilter(filterArr, filter) { 59 | return (target, scope)=> { 60 | if (filterArr.length) { 61 | each(filterArr, (filterStr)=> { 62 | filterStr = filterStr.split(":") 63 | let name = filterStr[0],args,filterFunc 64 | if (filter && filter[name]) { 65 | args = filterStr.slice(1).map((arg)=>parseTree(arg, scope)) 66 | filterFunc = filter[name].apply(scope,args) 67 | if(isFunc(filterFunc)){ 68 | target = filterFunc(target) 69 | } else { 70 | //报错 71 | } 72 | } else { 73 | //报错 74 | } 75 | }) 76 | return target 77 | } else { 78 | return target 79 | } 80 | } 81 | } 82 | 83 | _parseStr(str) { 84 | str = str.replace(/\s*/g, "").split("|") 85 | if (str.length > 1) { 86 | return { 87 | target: str[0], 88 | filterArr: str.slice(1) 89 | } 90 | } else { 91 | return { 92 | target: str[0], 93 | filterArr: [] 94 | } 95 | } 96 | } 97 | 98 | static injectExpHandler = ()=>{ 99 | 100 | } 101 | 102 | static ExpressionTypes = ExpressionTypes 103 | 104 | } 105 | 106 | extend(ExpressionParser, jsep) 107 | 108 | export default ExpressionParser 109 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const is = type=>val=> Object.prototype.toString.call(val) == '[object ' + type + ']' 4 | 5 | export const isFunc = is('Function') 6 | 7 | export const isStr = is('String') 8 | 9 | export const isArr = is('Array') 10 | 11 | export const isObj = is('Object') 12 | 13 | export const each = (obj, func)=> { 14 | if (!obj || !isFunc(func)) return 15 | let keys = Object.keys(obj), i, val, key 16 | for (i = 0; i < keys.length; i++) { 17 | key = keys[i] 18 | val = obj[key] 19 | if (obj.hasOwnProperty(key)) { 20 | if (func(val, key) === false) break 21 | } 22 | } 23 | } 24 | 25 | export const extend = (obj1, obj2)=> { 26 | if (isObj(obj1) || isArr(obj1)) { 27 | each(obj2, (val, key)=> { 28 | obj1[key] = val 29 | }) 30 | return obj1 31 | } else { 32 | return obj1 33 | } 34 | } 35 | 36 | export const calculator = (operator) =>{ 37 | return { 38 | unary:(x)=>new Function(`return ${operator}${x}`), 39 | binary:(x,y)=>new Function(`return ${x}${operator}${y}`) 40 | } 41 | } 42 | 43 | export const toArray = obj => Array.prototype.slice.call(obj) 44 | 45 | -------------------------------------------------------------------------------- /src/warn.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janryWang/exp-parser/ce11fba9ba2bca18616e4cda647195a8bc0bd46b/src/warn.js -------------------------------------------------------------------------------- /test/parser.spec.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai' 2 | import ExpressionParser from '../src' 3 | let should = chai.should() 4 | let expect = chai.expect 5 | 6 | describe('test api', ()=>{ 7 | let parser = new ExpressionParser() 8 | it('should equal 5',()=>{ 9 | parser.parse('2+3').should.be.equal(5) 10 | }) 11 | 12 | it('should equal 5',()=>{ 13 | parser.parse('a + b',{ 14 | a:2, 15 | b:3 16 | }).should.be.equal(5) 17 | }) 18 | 19 | it('should equal 5',()=>{ 20 | parser.parse('max(a,b)',{ 21 | a:5, 22 | b:3, 23 | max:Math.max 24 | }).should.be.equal(5) 25 | }) 26 | 27 | it('should equal janry 8',()=>{ 28 | parser.parse('a+b | addPrefix : name',{ 29 | a:5, 30 | b:3, 31 | name:'janry ' 32 | },{ 33 | addPrefix:(name)=>(input)=> name + input 34 | }).should.be.equal('janry 8') 35 | }) 36 | 37 | }) 38 | -------------------------------------------------------------------------------- /webpack.config.development.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var baseConfig = require('./webpack.config'); 3 | 4 | var config = Object.create(baseConfig); 5 | config.plugins = [ 6 | new webpack.optimize.OccurenceOrderPlugin(), 7 | new webpack.DefinePlugin({ 8 | 'process.env.NODE_ENV': JSON.stringify('development') 9 | }) 10 | ]; 11 | 12 | module.exports = config; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | module.exports = { 4 | module: { 5 | loaders: [ 6 | { test: /\.js$/, loader: 'babel', exclude: /node_modules/,query: { 7 | cacheDirectory: true, 8 | presets: ['es2015','stage-0'] 9 | } } 10 | ] 11 | }, 12 | externals:{ 13 | }, 14 | output: { 15 | library: 'ExpressionParser', 16 | libraryTarget: 'umd' 17 | }, 18 | resolve: { 19 | extensions: ['', '.js'] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var baseConfig = require('./webpack.config'); 3 | 4 | var config = Object.create(baseConfig); 5 | config.plugins = [ 6 | new webpack.optimize.OccurenceOrderPlugin(), 7 | new webpack.DefinePlugin({ 8 | 'process.env.NODE_ENV': JSON.stringify('production') 9 | }), 10 | new webpack.optimize.UglifyJsPlugin({ 11 | compressor: { 12 | screw_ie8: true, 13 | warnings: false 14 | } 15 | }) 16 | ]; 17 | 18 | module.exports = config; --------------------------------------------------------------------------------