jsep.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. // JavaScript Expression Parser (JSEP) 0.3.1
  2. // JSEP may be freely distributed under the MIT License
  3. // http://jsep.from.so/
  4. var tmp = {};
  5. /*global module: true, exports: true, console: true */
  6. (function (root) {
  7. 'use strict';
  8. // Node Types
  9. // ----------
  10. // This is the full set of types that any JSEP node can be.
  11. // Store them here to save space when minified
  12. var COMPOUND = 'Compound',
  13. IDENTIFIER = 'Identifier',
  14. MEMBER_EXP = 'MemberExpression',
  15. LITERAL = 'Literal',
  16. THIS_EXP = 'ThisExpression',
  17. CALL_EXP = 'CallExpression',
  18. UNARY_EXP = 'UnaryExpression',
  19. BINARY_EXP = 'BinaryExpression',
  20. LOGICAL_EXP = 'LogicalExpression',
  21. CONDITIONAL_EXP = 'ConditionalExpression',
  22. ARRAY_EXP = 'ArrayExpression',
  23. PERIOD_CODE = 46, // '.'
  24. COMMA_CODE = 44, // ','
  25. SQUOTE_CODE = 39, // single quote
  26. DQUOTE_CODE = 34, // double quotes
  27. OPAREN_CODE = 40, // (
  28. CPAREN_CODE = 41, // )
  29. OBRACK_CODE = 91, // [
  30. CBRACK_CODE = 93, // ]
  31. QUMARK_CODE = 63, // ?
  32. SEMCOL_CODE = 59, // ;
  33. COLON_CODE = 58, // :
  34. throwError = function(message, index) {
  35. var error = new Error(message + ' at character ' + index);
  36. error.index = index;
  37. error.description = message;
  38. throw error;
  39. },
  40. // Operations
  41. // ----------
  42. // Set `t` to `true` to save space (when minified, not gzipped)
  43. t = true,
  44. // Use a quickly-accessible map to store all of the unary operators
  45. // Values are set to `true` (it really doesn't matter)
  46. unary_ops = {'-': t, '!': t, '~': t, '+': t},
  47. // Also use a map for the binary operations but set their values to their
  48. // binary precedence for quick reference:
  49. // see [Order of operations](http://en.wikipedia.org/wiki/Order_of_operations#Programming_language)
  50. binary_ops = {
  51. '||': 1, '&&': 2, '|': 3, '^': 4, '&': 5,
  52. '==': 6, '!=': 6, '===': 6, '!==': 6,
  53. '<': 7, '>': 7, '<=': 7, '>=': 7,
  54. '<<':8, '>>': 8, '>>>': 8,
  55. '+': 9, '-': 9,
  56. '*': 10, '/': 10, '%': 10
  57. },
  58. // Get return the longest key length of any object
  59. getMaxKeyLen = function(obj) {
  60. var max_len = 0, len;
  61. for(var key in obj) {
  62. if((len = key.length) > max_len && obj.hasOwnProperty(key)) {
  63. max_len = len;
  64. }
  65. }
  66. return max_len;
  67. },
  68. max_unop_len = getMaxKeyLen(unary_ops),
  69. max_binop_len = getMaxKeyLen(binary_ops),
  70. // Literals
  71. // ----------
  72. // Store the values to return for the various literals we may encounter
  73. literals = {
  74. 'true': true,
  75. 'false': false,
  76. 'null': null
  77. },
  78. // Except for `this`, which is special. This could be changed to something like `'self'` as well
  79. this_str = 'this',
  80. // Returns the precedence of a binary operator or `0` if it isn't a binary operator
  81. binaryPrecedence = function(op_val) {
  82. return binary_ops[op_val] || 0;
  83. },
  84. // Utility function (gets called from multiple places)
  85. // Also note that `a && b` and `a || b` are *logical* expressions, not binary expressions
  86. createBinaryExpression = function (operator, left, right) {
  87. var type = (operator === '||' || operator === '&&') ? LOGICAL_EXP : BINARY_EXP;
  88. return {
  89. type: type,
  90. operator: operator,
  91. left: left,
  92. right: right
  93. };
  94. },
  95. // `ch` is a character code in the next three functions
  96. isDecimalDigit = function(ch) {
  97. return (ch >= 48 && ch <= 57); // 0...9
  98. },
  99. isIdentifierStart = function(ch) {
  100. return (ch === 36) || (ch === 95) || // `$` and `_`
  101. (ch >= 65 && ch <= 90) || // A...Z
  102. (ch >= 97 && ch <= 122) || // a...z
  103. (ch >= 128 && !binary_ops[String.fromCharCode(ch)]); // any non-ASCII that is not an operator
  104. },
  105. isIdentifierPart = function(ch) {
  106. return (ch === 36) || (ch === 95) || // `$` and `_`
  107. (ch >= 65 && ch <= 90) || // A...Z
  108. (ch >= 97 && ch <= 122) || // a...z
  109. (ch >= 48 && ch <= 57) || // 0...9
  110. (ch >= 128 && !binary_ops[String.fromCharCode(ch)]); // any non-ASCII that is not an operator
  111. },
  112. // Parsing
  113. // -------
  114. // `expr` is a string with the passed in expression
  115. jsep = function(expr) {
  116. // `index` stores the character number we are currently at while `length` is a constant
  117. // All of the gobbles below will modify `index` as we move along
  118. var index = 0,
  119. charAtFunc = expr.charAt,
  120. charCodeAtFunc = expr.charCodeAt,
  121. exprI = function(i) { return charAtFunc.call(expr, i); },
  122. exprICode = function(i) { return charCodeAtFunc.call(expr, i); },
  123. length = expr.length,
  124. // Push `index` up to the next non-space character
  125. gobbleSpaces = function() {
  126. var ch = exprICode(index);
  127. // space or tab
  128. while(ch === 32 || ch === 9) {
  129. ch = exprICode(++index);
  130. }
  131. },
  132. // The main parsing function. Much of this code is dedicated to ternary expressions
  133. gobbleExpression = function() {
  134. var test = gobbleBinaryExpression(),
  135. consequent, alternate;
  136. gobbleSpaces();
  137. if(exprICode(index) === QUMARK_CODE) {
  138. // Ternary expression: test ? consequent : alternate
  139. index++;
  140. consequent = gobbleExpression();
  141. if(!consequent) {
  142. throwError('Expected expression', index);
  143. }
  144. gobbleSpaces();
  145. if(exprICode(index) === COLON_CODE) {
  146. index++;
  147. alternate = gobbleExpression();
  148. if(!alternate) {
  149. throwError('Expected expression', index);
  150. }
  151. return {
  152. type: CONDITIONAL_EXP,
  153. test: test,
  154. consequent: consequent,
  155. alternate: alternate
  156. };
  157. } else {
  158. throwError('Expected :', index);
  159. }
  160. } else {
  161. return test;
  162. }
  163. },
  164. // Search for the operation portion of the string (e.g. `+`, `===`)
  165. // Start by taking the longest possible binary operations (3 characters: `===`, `!==`, `>>>`)
  166. // and move down from 3 to 2 to 1 character until a matching binary operation is found
  167. // then, return that binary operation
  168. gobbleBinaryOp = function() {
  169. gobbleSpaces();
  170. var biop, to_check = expr.substr(index, max_binop_len), tc_len = to_check.length;
  171. while(tc_len > 0) {
  172. if(binary_ops.hasOwnProperty(to_check)) {
  173. index += tc_len;
  174. return to_check;
  175. }
  176. to_check = to_check.substr(0, --tc_len);
  177. }
  178. return false;
  179. },
  180. // This function is responsible for gobbling an individual expression,
  181. // e.g. `1`, `1+2`, `a+(b*2)-Math.sqrt(2)`
  182. gobbleBinaryExpression = function() {
  183. var ch_i, node, biop, prec, stack, biop_info, left, right, i;
  184. // First, try to get the leftmost thing
  185. // Then, check to see if there's a binary operator operating on that leftmost thing
  186. left = gobbleToken();
  187. biop = gobbleBinaryOp();
  188. // If there wasn't a binary operator, just return the leftmost node
  189. if(!biop) {
  190. return left;
  191. }
  192. // Otherwise, we need to start a stack to properly place the binary operations in their
  193. // precedence structure
  194. biop_info = { value: biop, prec: binaryPrecedence(biop)};
  195. right = gobbleToken();
  196. if(!right) {
  197. throwError("Expected expression after " + biop, index);
  198. }
  199. stack = [left, biop_info, right];
  200. // Properly deal with precedence using [recursive descent](http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm)
  201. while((biop = gobbleBinaryOp())) {
  202. prec = binaryPrecedence(biop);
  203. if(prec === 0) {
  204. break;
  205. }
  206. biop_info = { value: biop, prec: prec };
  207. // Reduce: make a binary expression from the three topmost entries.
  208. while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
  209. right = stack.pop();
  210. biop = stack.pop().value;
  211. left = stack.pop();
  212. node = createBinaryExpression(biop, left, right);
  213. stack.push(node);
  214. }
  215. node = gobbleToken();
  216. if(!node) {
  217. throwError("Expected expression after " + biop, index);
  218. }
  219. stack.push(biop_info, node);
  220. }
  221. i = stack.length - 1;
  222. node = stack[i];
  223. while(i > 1) {
  224. node = createBinaryExpression(stack[i - 1].value, stack[i - 2], node);
  225. i -= 2;
  226. }
  227. return node;
  228. },
  229. // An individual part of a binary expression:
  230. // e.g. `foo.bar(baz)`, `1`, `"abc"`, `(a % 2)` (because it's in parenthesis)
  231. gobbleToken = function() {
  232. var ch, to_check, tc_len;
  233. gobbleSpaces();
  234. ch = exprICode(index);
  235. if(isDecimalDigit(ch) || ch === PERIOD_CODE) {
  236. // Char code 46 is a dot `.` which can start off a numeric literal
  237. return gobbleNumericLiteral();
  238. } else if(ch === SQUOTE_CODE || ch === DQUOTE_CODE) {
  239. // Single or double quotes
  240. return gobbleStringLiteral();
  241. } else if(isIdentifierStart(ch) || ch === OPAREN_CODE) { // open parenthesis
  242. // `foo`, `bar.baz`
  243. return gobbleVariable();
  244. } else if (ch === OBRACK_CODE) {
  245. return gobbleArray();
  246. } else {
  247. to_check = expr.substr(index, max_unop_len);
  248. tc_len = to_check.length;
  249. while(tc_len > 0) {
  250. if(unary_ops.hasOwnProperty(to_check)) {
  251. index += tc_len;
  252. return {
  253. type: UNARY_EXP,
  254. operator: to_check,
  255. argument: gobbleToken(),
  256. prefix: true
  257. };
  258. }
  259. to_check = to_check.substr(0, --tc_len);
  260. }
  261. return false;
  262. }
  263. },
  264. // Parse simple numeric literals: `12`, `3.4`, `.5`. Do this by using a string to
  265. // keep track of everything in the numeric literal and then calling `parseFloat` on that string
  266. gobbleNumericLiteral = function() {
  267. var number = '', ch, chCode;
  268. while(isDecimalDigit(exprICode(index))) {
  269. number += exprI(index++);
  270. }
  271. if(exprICode(index) === PERIOD_CODE) { // can start with a decimal marker
  272. number += exprI(index++);
  273. while(isDecimalDigit(exprICode(index))) {
  274. number += exprI(index++);
  275. }
  276. }
  277. ch = exprI(index);
  278. if(ch === 'e' || ch === 'E') { // exponent marker
  279. number += exprI(index++);
  280. ch = exprI(index);
  281. if(ch === '+' || ch === '-') { // exponent sign
  282. number += exprI(index++);
  283. }
  284. while(isDecimalDigit(exprICode(index))) { //exponent itself
  285. number += exprI(index++);
  286. }
  287. if(!isDecimalDigit(exprICode(index-1)) ) {
  288. throwError('Expected exponent (' + number + exprI(index) + ')', index);
  289. }
  290. }
  291. chCode = exprICode(index);
  292. // Check to make sure this isn't a variable name that start with a number (123abc)
  293. if(isIdentifierStart(chCode)) {
  294. throwError('Variable names cannot start with a number (' +
  295. number + exprI(index) + ')', index);
  296. } else if(chCode === PERIOD_CODE) {
  297. throwError('Unexpected period', index);
  298. }
  299. return {
  300. type: LITERAL,
  301. value: parseFloat(number),
  302. raw: number
  303. };
  304. },
  305. // Parses a string literal, staring with single or double quotes with basic support for escape codes
  306. // e.g. `"hello world"`, `'this is\nJSEP'`
  307. gobbleStringLiteral = function() {
  308. var str = '', quote = exprI(index++), closed = false, ch;
  309. while(index < length) {
  310. ch = exprI(index++);
  311. if(ch === quote) {
  312. closed = true;
  313. break;
  314. } else if(ch === '\\') {
  315. // Check for all of the common escape codes
  316. ch = exprI(index++);
  317. switch(ch) {
  318. case 'n': str += '\n'; break;
  319. case 'r': str += '\r'; break;
  320. case 't': str += '\t'; break;
  321. case 'b': str += '\b'; break;
  322. case 'f': str += '\f'; break;
  323. case 'v': str += '\x0B'; break;
  324. default : str += '\\' + ch;
  325. }
  326. } else {
  327. str += ch;
  328. }
  329. }
  330. if(!closed) {
  331. throwError('Unclosed quote after "'+str+'"', index);
  332. }
  333. return {
  334. type: LITERAL,
  335. value: str,
  336. raw: quote + str + quote
  337. };
  338. },
  339. // Gobbles only identifiers
  340. // e.g.: `foo`, `_value`, `$x1`
  341. // Also, this function checks if that identifier is a literal:
  342. // (e.g. `true`, `false`, `null`) or `this`
  343. gobbleIdentifier = function() {
  344. var ch = exprICode(index), start = index, identifier;
  345. if(isIdentifierStart(ch)) {
  346. index++;
  347. } else {
  348. throwError('Unexpected ' + exprI(index), index);
  349. }
  350. while(index < length) {
  351. ch = exprICode(index);
  352. if(isIdentifierPart(ch)) {
  353. index++;
  354. } else {
  355. break;
  356. }
  357. }
  358. identifier = expr.slice(start, index);
  359. if(literals.hasOwnProperty(identifier)) {
  360. return {
  361. type: LITERAL,
  362. value: literals[identifier],
  363. raw: identifier
  364. };
  365. } else if(identifier === this_str) {
  366. return { type: THIS_EXP };
  367. } else {
  368. return {
  369. type: IDENTIFIER,
  370. name: identifier
  371. };
  372. }
  373. },
  374. // Gobbles a list of arguments within the context of a function call
  375. // or array literal. This function also assumes that the opening character
  376. // `(` or `[` has already been gobbled, and gobbles expressions and commas
  377. // until the terminator character `)` or `]` is encountered.
  378. // e.g. `foo(bar, baz)`, `my_func()`, or `[bar, baz]`
  379. gobbleArguments = function(termination) {
  380. var ch_i, args = [], node, closed = false;
  381. while(index < length) {
  382. gobbleSpaces();
  383. ch_i = exprICode(index);
  384. if(ch_i === termination) { // done parsing
  385. closed = true;
  386. index++;
  387. break;
  388. } else if (ch_i === COMMA_CODE) { // between expressions
  389. index++;
  390. } else {
  391. node = gobbleExpression();
  392. if(!node || node.type === COMPOUND) {
  393. throwError('Expected comma', index);
  394. }
  395. args.push(node);
  396. }
  397. }
  398. if (!closed) {
  399. throwError('Expected ' + String.fromCharCode(termination), index);
  400. }
  401. return args;
  402. },
  403. // Gobble a non-literal variable name. This variable name may include properties
  404. // e.g. `foo`, `bar.baz`, `foo['bar'].baz`
  405. // It also gobbles function calls:
  406. // e.g. `Math.acos(obj.angle)`
  407. gobbleVariable = function() {
  408. var ch_i, node;
  409. ch_i = exprICode(index);
  410. if(ch_i === OPAREN_CODE) {
  411. node = gobbleGroup();
  412. } else {
  413. node = gobbleIdentifier();
  414. }
  415. gobbleSpaces();
  416. ch_i = exprICode(index);
  417. while(ch_i === PERIOD_CODE || ch_i === OBRACK_CODE || ch_i === OPAREN_CODE) {
  418. index++;
  419. if(ch_i === PERIOD_CODE) {
  420. gobbleSpaces();
  421. node = {
  422. type: MEMBER_EXP,
  423. computed: false,
  424. object: node,
  425. property: gobbleIdentifier()
  426. };
  427. } else if(ch_i === OBRACK_CODE) {
  428. node = {
  429. type: MEMBER_EXP,
  430. computed: true,
  431. object: node,
  432. property: gobbleExpression()
  433. };
  434. gobbleSpaces();
  435. ch_i = exprICode(index);
  436. if(ch_i !== CBRACK_CODE) {
  437. throwError('Unclosed [', index);
  438. }
  439. index++;
  440. } else if(ch_i === OPAREN_CODE) {
  441. // A function call is being made; gobble all the arguments
  442. node = {
  443. type: CALL_EXP,
  444. 'arguments': gobbleArguments(CPAREN_CODE),
  445. callee: node
  446. };
  447. }
  448. gobbleSpaces();
  449. ch_i = exprICode(index);
  450. }
  451. return node;
  452. },
  453. // Responsible for parsing a group of things within parentheses `()`
  454. // This function assumes that it needs to gobble the opening parenthesis
  455. // and then tries to gobble everything within that parenthesis, assuming
  456. // that the next thing it should see is the close parenthesis. If not,
  457. // then the expression probably doesn't have a `)`
  458. gobbleGroup = function() {
  459. index++;
  460. var node = gobbleExpression();
  461. gobbleSpaces();
  462. if(exprICode(index) === CPAREN_CODE) {
  463. index++;
  464. return node;
  465. } else {
  466. throwError('Unclosed (', index);
  467. }
  468. },
  469. // Responsible for parsing Array literals `[1, 2, 3]`
  470. // This function assumes that it needs to gobble the opening bracket
  471. // and then tries to gobble the expressions as arguments.
  472. gobbleArray = function() {
  473. index++;
  474. return {
  475. type: ARRAY_EXP,
  476. elements: gobbleArguments(CBRACK_CODE)
  477. };
  478. },
  479. nodes = [], ch_i, node;
  480. while(index < length) {
  481. ch_i = exprICode(index);
  482. // Expressions can be separated by semicolons, commas, or just inferred without any
  483. // separators
  484. if(ch_i === SEMCOL_CODE || ch_i === COMMA_CODE) {
  485. index++; // ignore separators
  486. } else {
  487. // Try to gobble each expression individually
  488. if((node = gobbleExpression())) {
  489. nodes.push(node);
  490. // If we weren't able to find a binary expression and are out of room, then
  491. // the expression passed in probably has too much
  492. } else if(index < length) {
  493. throwError('Unexpected "' + exprI(index) + '"', index);
  494. }
  495. }
  496. }
  497. // If there's only one expression just try returning the expression
  498. if(nodes.length === 1) {
  499. return nodes[0];
  500. } else {
  501. return {
  502. type: COMPOUND,
  503. body: nodes
  504. };
  505. }
  506. };
  507. // To be filled in by the template
  508. jsep.version = '0.3.1';
  509. jsep.toString = function() { return 'JavaScript Expression Parser (JSEP) v' + jsep.version; };
  510. /**
  511. * @method jsep.addUnaryOp
  512. * @param {string} op_name The name of the unary op to add
  513. * @return jsep
  514. */
  515. jsep.addUnaryOp = function(op_name) {
  516. max_unop_len = Math.max(op_name.length, max_unop_len);
  517. unary_ops[op_name] = t; return this;
  518. };
  519. /**
  520. * @method jsep.addBinaryOp
  521. * @param {string} op_name The name of the binary op to add
  522. * @param {number} precedence The precedence of the binary op (can be a float)
  523. * @return jsep
  524. */
  525. jsep.addBinaryOp = function(op_name, precedence) {
  526. max_binop_len = Math.max(op_name.length, max_binop_len);
  527. binary_ops[op_name] = precedence;
  528. return this;
  529. };
  530. /**
  531. * @method jsep.addLiteral
  532. * @param {string} literal_name The name of the literal to add
  533. * @param {*} literal_value The value of the literal
  534. * @return jsep
  535. */
  536. jsep.addLiteral = function(literal_name, literal_value) {
  537. literals[literal_name] = literal_value;
  538. return this;
  539. };
  540. /**
  541. * @method jsep.removeUnaryOp
  542. * @param {string} op_name The name of the unary op to remove
  543. * @return jsep
  544. */
  545. jsep.removeUnaryOp = function(op_name) {
  546. delete unary_ops[op_name];
  547. if(op_name.length === max_unop_len) {
  548. max_unop_len = getMaxKeyLen(unary_ops);
  549. }
  550. return this;
  551. };
  552. /**
  553. * @method jsep.removeAllUnaryOps
  554. * @return jsep
  555. */
  556. jsep.removeAllUnaryOps = function() {
  557. unary_ops = {};
  558. max_unop_len = 0;
  559. return this;
  560. };
  561. /**
  562. * @method jsep.removeBinaryOp
  563. * @param {string} op_name The name of the binary op to remove
  564. * @return jsep
  565. */
  566. jsep.removeBinaryOp = function(op_name) {
  567. delete binary_ops[op_name];
  568. if(op_name.length === max_binop_len) {
  569. max_binop_len = getMaxKeyLen(binary_ops);
  570. }
  571. return this;
  572. };
  573. /**
  574. * @method jsep.removeAllBinaryOps
  575. * @return jsep
  576. */
  577. jsep.removeAllBinaryOps = function() {
  578. binary_ops = {};
  579. max_binop_len = 0;
  580. return this;
  581. };
  582. /**
  583. * @method jsep.removeLiteral
  584. * @param {string} literal_name The name of the literal to remove
  585. * @return jsep
  586. */
  587. jsep.removeLiteral = function(literal_name) {
  588. delete literals[literal_name];
  589. return this;
  590. };
  591. /**
  592. * @method jsep.removeAllLiterals
  593. * @return jsep
  594. */
  595. jsep.removeAllLiterals = function() {
  596. literals = {};
  597. return this;
  598. };
  599. root.jsep = jsep;
  600. }(tmp));
  601. export default tmp.jsep;