jsep.iife.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  1. var jsep = (function () {
  2. 'use strict';
  3. /**
  4. * @implements {IHooks}
  5. */
  6. class Hooks {
  7. /**
  8. * @callback HookCallback
  9. * @this {*|Jsep} this
  10. * @param {Jsep} env
  11. * @returns: void
  12. */
  13. /**
  14. * Adds the given callback to the list of callbacks for the given hook.
  15. *
  16. * The callback will be invoked when the hook it is registered for is run.
  17. *
  18. * One callback function can be registered to multiple hooks and the same hook multiple times.
  19. *
  20. * @param {string|object} name The name of the hook, or an object of callbacks keyed by name
  21. * @param {HookCallback|boolean} callback The callback function which is given environment variables.
  22. * @param {?boolean} [first=false] Will add the hook to the top of the list (defaults to the bottom)
  23. * @public
  24. */
  25. add(name, callback, first) {
  26. if (typeof arguments[0] != 'string') {
  27. // Multiple hook callbacks, keyed by name
  28. for (let name in arguments[0]) {
  29. this.add(name, arguments[0][name], arguments[1]);
  30. }
  31. }
  32. else {
  33. (Array.isArray(name) ? name : [name]).forEach(function (name) {
  34. this[name] = this[name] || [];
  35. if (callback) {
  36. this[name][first ? 'unshift' : 'push'](callback);
  37. }
  38. }, this);
  39. }
  40. }
  41. /**
  42. * Runs a hook invoking all registered callbacks with the given environment variables.
  43. *
  44. * Callbacks will be invoked synchronously and in the order in which they were registered.
  45. *
  46. * @param {string} name The name of the hook.
  47. * @param {Object<string, any>} env The environment variables of the hook passed to all callbacks registered.
  48. * @public
  49. */
  50. run(name, env) {
  51. this[name] = this[name] || [];
  52. this[name].forEach(function (callback) {
  53. callback.call(env && env.context ? env.context : env, env);
  54. });
  55. }
  56. }
  57. /**
  58. * @implements {IPlugins}
  59. */
  60. class Plugins {
  61. constructor(jsep) {
  62. this.jsep = jsep;
  63. this.registered = {};
  64. }
  65. /**
  66. * @callback PluginSetup
  67. * @this {Jsep} jsep
  68. * @returns: void
  69. */
  70. /**
  71. * Adds the given plugin(s) to the registry
  72. *
  73. * @param {object} plugins
  74. * @param {string} plugins.name The name of the plugin
  75. * @param {PluginSetup} plugins.init The init function
  76. * @public
  77. */
  78. register(...plugins) {
  79. plugins.forEach((plugin) => {
  80. if (typeof plugin !== 'object' || !plugin.name || !plugin.init) {
  81. throw new Error('Invalid JSEP plugin format');
  82. }
  83. if (this.registered[plugin.name]) {
  84. // already registered. Ignore.
  85. return;
  86. }
  87. plugin.init(this.jsep);
  88. this.registered[plugin.name] = plugin;
  89. });
  90. }
  91. }
  92. // JavaScript Expression Parser (JSEP) 1.3.8
  93. class Jsep {
  94. /**
  95. * @returns {string}
  96. */
  97. static get version() {
  98. // To be filled in by the template
  99. return '1.3.8';
  100. }
  101. /**
  102. * @returns {string}
  103. */
  104. static toString() {
  105. return 'JavaScript Expression Parser (JSEP) v' + Jsep.version;
  106. };
  107. // ==================== CONFIG ================================
  108. /**
  109. * @method addUnaryOp
  110. * @param {string} op_name The name of the unary op to add
  111. * @returns {Jsep}
  112. */
  113. static addUnaryOp(op_name) {
  114. Jsep.max_unop_len = Math.max(op_name.length, Jsep.max_unop_len);
  115. Jsep.unary_ops[op_name] = 1;
  116. return Jsep;
  117. }
  118. /**
  119. * @method jsep.addBinaryOp
  120. * @param {string} op_name The name of the binary op to add
  121. * @param {number} precedence The precedence of the binary op (can be a float). Higher number = higher precedence
  122. * @param {boolean} [isRightAssociative=false] whether operator is right-associative
  123. * @returns {Jsep}
  124. */
  125. static addBinaryOp(op_name, precedence, isRightAssociative) {
  126. Jsep.max_binop_len = Math.max(op_name.length, Jsep.max_binop_len);
  127. Jsep.binary_ops[op_name] = precedence;
  128. if (isRightAssociative) {
  129. Jsep.right_associative.add(op_name);
  130. }
  131. else {
  132. Jsep.right_associative.delete(op_name);
  133. }
  134. return Jsep;
  135. }
  136. /**
  137. * @method addIdentifierChar
  138. * @param {string} char The additional character to treat as a valid part of an identifier
  139. * @returns {Jsep}
  140. */
  141. static addIdentifierChar(char) {
  142. Jsep.additional_identifier_chars.add(char);
  143. return Jsep;
  144. }
  145. /**
  146. * @method addLiteral
  147. * @param {string} literal_name The name of the literal to add
  148. * @param {*} literal_value The value of the literal
  149. * @returns {Jsep}
  150. */
  151. static addLiteral(literal_name, literal_value) {
  152. Jsep.literals[literal_name] = literal_value;
  153. return Jsep;
  154. }
  155. /**
  156. * @method removeUnaryOp
  157. * @param {string} op_name The name of the unary op to remove
  158. * @returns {Jsep}
  159. */
  160. static removeUnaryOp(op_name) {
  161. delete Jsep.unary_ops[op_name];
  162. if (op_name.length === Jsep.max_unop_len) {
  163. Jsep.max_unop_len = Jsep.getMaxKeyLen(Jsep.unary_ops);
  164. }
  165. return Jsep;
  166. }
  167. /**
  168. * @method removeAllUnaryOps
  169. * @returns {Jsep}
  170. */
  171. static removeAllUnaryOps() {
  172. Jsep.unary_ops = {};
  173. Jsep.max_unop_len = 0;
  174. return Jsep;
  175. }
  176. /**
  177. * @method removeIdentifierChar
  178. * @param {string} char The additional character to stop treating as a valid part of an identifier
  179. * @returns {Jsep}
  180. */
  181. static removeIdentifierChar(char) {
  182. Jsep.additional_identifier_chars.delete(char);
  183. return Jsep;
  184. }
  185. /**
  186. * @method removeBinaryOp
  187. * @param {string} op_name The name of the binary op to remove
  188. * @returns {Jsep}
  189. */
  190. static removeBinaryOp(op_name) {
  191. delete Jsep.binary_ops[op_name];
  192. if (op_name.length === Jsep.max_binop_len) {
  193. Jsep.max_binop_len = Jsep.getMaxKeyLen(Jsep.binary_ops);
  194. }
  195. Jsep.right_associative.delete(op_name);
  196. return Jsep;
  197. }
  198. /**
  199. * @method removeAllBinaryOps
  200. * @returns {Jsep}
  201. */
  202. static removeAllBinaryOps() {
  203. Jsep.binary_ops = {};
  204. Jsep.max_binop_len = 0;
  205. return Jsep;
  206. }
  207. /**
  208. * @method removeLiteral
  209. * @param {string} literal_name The name of the literal to remove
  210. * @returns {Jsep}
  211. */
  212. static removeLiteral(literal_name) {
  213. delete Jsep.literals[literal_name];
  214. return Jsep;
  215. }
  216. /**
  217. * @method removeAllLiterals
  218. * @returns {Jsep}
  219. */
  220. static removeAllLiterals() {
  221. Jsep.literals = {};
  222. return Jsep;
  223. }
  224. // ==================== END CONFIG ============================
  225. /**
  226. * @returns {string}
  227. */
  228. get char() {
  229. return this.expr.charAt(this.index);
  230. }
  231. /**
  232. * @returns {number}
  233. */
  234. get code() {
  235. return this.expr.charCodeAt(this.index);
  236. };
  237. /**
  238. * @param {string} expr a string with the passed in express
  239. * @returns Jsep
  240. */
  241. constructor(expr) {
  242. // `index` stores the character number we are currently at
  243. // All of the gobbles below will modify `index` as we move along
  244. this.expr = expr;
  245. this.index = 0;
  246. }
  247. /**
  248. * static top-level parser
  249. * @returns {jsep.Expression}
  250. */
  251. static parse(expr) {
  252. return (new Jsep(expr)).parse();
  253. }
  254. /**
  255. * Get the longest key length of any object
  256. * @param {object} obj
  257. * @returns {number}
  258. */
  259. static getMaxKeyLen(obj) {
  260. return Math.max(0, ...Object.keys(obj).map(k => k.length));
  261. }
  262. /**
  263. * `ch` is a character code in the next three functions
  264. * @param {number} ch
  265. * @returns {boolean}
  266. */
  267. static isDecimalDigit(ch) {
  268. return (ch >= 48 && ch <= 57); // 0...9
  269. }
  270. /**
  271. * Returns the precedence of a binary operator or `0` if it isn't a binary operator. Can be float.
  272. * @param {string} op_val
  273. * @returns {number}
  274. */
  275. static binaryPrecedence(op_val) {
  276. return Jsep.binary_ops[op_val] || 0;
  277. }
  278. /**
  279. * Looks for start of identifier
  280. * @param {number} ch
  281. * @returns {boolean}
  282. */
  283. static isIdentifierStart(ch) {
  284. return (ch >= 65 && ch <= 90) || // A...Z
  285. (ch >= 97 && ch <= 122) || // a...z
  286. (ch >= 128 && !Jsep.binary_ops[String.fromCharCode(ch)]) || // any non-ASCII that is not an operator
  287. (Jsep.additional_identifier_chars.has(String.fromCharCode(ch))); // additional characters
  288. }
  289. /**
  290. * @param {number} ch
  291. * @returns {boolean}
  292. */
  293. static isIdentifierPart(ch) {
  294. return Jsep.isIdentifierStart(ch) || Jsep.isDecimalDigit(ch);
  295. }
  296. /**
  297. * throw error at index of the expression
  298. * @param {string} message
  299. * @throws
  300. */
  301. throwError(message) {
  302. const error = new Error(message + ' at character ' + this.index);
  303. error.index = this.index;
  304. error.description = message;
  305. throw error;
  306. }
  307. /**
  308. * Run a given hook
  309. * @param {string} name
  310. * @param {jsep.Expression|false} [node]
  311. * @returns {?jsep.Expression}
  312. */
  313. runHook(name, node) {
  314. if (Jsep.hooks[name]) {
  315. const env = { context: this, node };
  316. Jsep.hooks.run(name, env);
  317. return env.node;
  318. }
  319. return node;
  320. }
  321. /**
  322. * Runs a given hook until one returns a node
  323. * @param {string} name
  324. * @returns {?jsep.Expression}
  325. */
  326. searchHook(name) {
  327. if (Jsep.hooks[name]) {
  328. const env = { context: this };
  329. Jsep.hooks[name].find(function (callback) {
  330. callback.call(env.context, env);
  331. return env.node;
  332. });
  333. return env.node;
  334. }
  335. }
  336. /**
  337. * Push `index` up to the next non-space character
  338. */
  339. gobbleSpaces() {
  340. let ch = this.code;
  341. // Whitespace
  342. while (ch === Jsep.SPACE_CODE
  343. || ch === Jsep.TAB_CODE
  344. || ch === Jsep.LF_CODE
  345. || ch === Jsep.CR_CODE) {
  346. ch = this.expr.charCodeAt(++this.index);
  347. }
  348. this.runHook('gobble-spaces');
  349. }
  350. /**
  351. * Top-level method to parse all expressions and returns compound or single node
  352. * @returns {jsep.Expression}
  353. */
  354. parse() {
  355. this.runHook('before-all');
  356. const nodes = this.gobbleExpressions();
  357. // If there's only one expression just try returning the expression
  358. const node = nodes.length === 1
  359. ? nodes[0]
  360. : {
  361. type: Jsep.COMPOUND,
  362. body: nodes
  363. };
  364. return this.runHook('after-all', node);
  365. }
  366. /**
  367. * top-level parser (but can be reused within as well)
  368. * @param {number} [untilICode]
  369. * @returns {jsep.Expression[]}
  370. */
  371. gobbleExpressions(untilICode) {
  372. let nodes = [], ch_i, node;
  373. while (this.index < this.expr.length) {
  374. ch_i = this.code;
  375. // Expressions can be separated by semicolons, commas, or just inferred without any
  376. // separators
  377. if (ch_i === Jsep.SEMCOL_CODE || ch_i === Jsep.COMMA_CODE) {
  378. this.index++; // ignore separators
  379. }
  380. else {
  381. // Try to gobble each expression individually
  382. if (node = this.gobbleExpression()) {
  383. nodes.push(node);
  384. // If we weren't able to find a binary expression and are out of room, then
  385. // the expression passed in probably has too much
  386. }
  387. else if (this.index < this.expr.length) {
  388. if (ch_i === untilICode) {
  389. break;
  390. }
  391. this.throwError('Unexpected "' + this.char + '"');
  392. }
  393. }
  394. }
  395. return nodes;
  396. }
  397. /**
  398. * The main parsing function.
  399. * @returns {?jsep.Expression}
  400. */
  401. gobbleExpression() {
  402. const node = this.searchHook('gobble-expression') || this.gobbleBinaryExpression();
  403. this.gobbleSpaces();
  404. return this.runHook('after-expression', node);
  405. }
  406. /**
  407. * Search for the operation portion of the string (e.g. `+`, `===`)
  408. * Start by taking the longest possible binary operations (3 characters: `===`, `!==`, `>>>`)
  409. * and move down from 3 to 2 to 1 character until a matching binary operation is found
  410. * then, return that binary operation
  411. * @returns {string|boolean}
  412. */
  413. gobbleBinaryOp() {
  414. this.gobbleSpaces();
  415. let to_check = this.expr.substr(this.index, Jsep.max_binop_len);
  416. let tc_len = to_check.length;
  417. while (tc_len > 0) {
  418. // Don't accept a binary op when it is an identifier.
  419. // Binary ops that start with a identifier-valid character must be followed
  420. // by a non identifier-part valid character
  421. if (Jsep.binary_ops.hasOwnProperty(to_check) && (
  422. !Jsep.isIdentifierStart(this.code) ||
  423. (this.index + to_check.length < this.expr.length && !Jsep.isIdentifierPart(this.expr.charCodeAt(this.index + to_check.length)))
  424. )) {
  425. this.index += tc_len;
  426. return to_check;
  427. }
  428. to_check = to_check.substr(0, --tc_len);
  429. }
  430. return false;
  431. }
  432. /**
  433. * This function is responsible for gobbling an individual expression,
  434. * e.g. `1`, `1+2`, `a+(b*2)-Math.sqrt(2)`
  435. * @returns {?jsep.BinaryExpression}
  436. */
  437. gobbleBinaryExpression() {
  438. let node, biop, prec, stack, biop_info, left, right, i, cur_biop;
  439. // First, try to get the leftmost thing
  440. // Then, check to see if there's a binary operator operating on that leftmost thing
  441. // Don't gobbleBinaryOp without a left-hand-side
  442. left = this.gobbleToken();
  443. if (!left) {
  444. return left;
  445. }
  446. biop = this.gobbleBinaryOp();
  447. // If there wasn't a binary operator, just return the leftmost node
  448. if (!biop) {
  449. return left;
  450. }
  451. // Otherwise, we need to start a stack to properly place the binary operations in their
  452. // precedence structure
  453. biop_info = { value: biop, prec: Jsep.binaryPrecedence(biop), right_a: Jsep.right_associative.has(biop) };
  454. right = this.gobbleToken();
  455. if (!right) {
  456. this.throwError("Expected expression after " + biop);
  457. }
  458. stack = [left, biop_info, right];
  459. // Properly deal with precedence using [recursive descent](http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm)
  460. while ((biop = this.gobbleBinaryOp())) {
  461. prec = Jsep.binaryPrecedence(biop);
  462. if (prec === 0) {
  463. this.index -= biop.length;
  464. break;
  465. }
  466. biop_info = { value: biop, prec, right_a: Jsep.right_associative.has(biop) };
  467. cur_biop = biop;
  468. // Reduce: make a binary expression from the three topmost entries.
  469. const comparePrev = prev => biop_info.right_a && prev.right_a
  470. ? prec > prev.prec
  471. : prec <= prev.prec;
  472. while ((stack.length > 2) && comparePrev(stack[stack.length - 2])) {
  473. right = stack.pop();
  474. biop = stack.pop().value;
  475. left = stack.pop();
  476. node = {
  477. type: Jsep.BINARY_EXP,
  478. operator: biop,
  479. left,
  480. right
  481. };
  482. stack.push(node);
  483. }
  484. node = this.gobbleToken();
  485. if (!node) {
  486. this.throwError("Expected expression after " + cur_biop);
  487. }
  488. stack.push(biop_info, node);
  489. }
  490. i = stack.length - 1;
  491. node = stack[i];
  492. while (i > 1) {
  493. node = {
  494. type: Jsep.BINARY_EXP,
  495. operator: stack[i - 1].value,
  496. left: stack[i - 2],
  497. right: node
  498. };
  499. i -= 2;
  500. }
  501. return node;
  502. }
  503. /**
  504. * An individual part of a binary expression:
  505. * e.g. `foo.bar(baz)`, `1`, `"abc"`, `(a % 2)` (because it's in parenthesis)
  506. * @returns {boolean|jsep.Expression}
  507. */
  508. gobbleToken() {
  509. let ch, to_check, tc_len, node;
  510. this.gobbleSpaces();
  511. node = this.searchHook('gobble-token');
  512. if (node) {
  513. return this.runHook('after-token', node);
  514. }
  515. ch = this.code;
  516. if (Jsep.isDecimalDigit(ch) || ch === Jsep.PERIOD_CODE) {
  517. // Char code 46 is a dot `.` which can start off a numeric literal
  518. return this.gobbleNumericLiteral();
  519. }
  520. if (ch === Jsep.SQUOTE_CODE || ch === Jsep.DQUOTE_CODE) {
  521. // Single or double quotes
  522. node = this.gobbleStringLiteral();
  523. }
  524. else if (ch === Jsep.OBRACK_CODE) {
  525. node = this.gobbleArray();
  526. }
  527. else {
  528. to_check = this.expr.substr(this.index, Jsep.max_unop_len);
  529. tc_len = to_check.length;
  530. while (tc_len > 0) {
  531. // Don't accept an unary op when it is an identifier.
  532. // Unary ops that start with a identifier-valid character must be followed
  533. // by a non identifier-part valid character
  534. if (Jsep.unary_ops.hasOwnProperty(to_check) && (
  535. !Jsep.isIdentifierStart(this.code) ||
  536. (this.index + to_check.length < this.expr.length && !Jsep.isIdentifierPart(this.expr.charCodeAt(this.index + to_check.length)))
  537. )) {
  538. this.index += tc_len;
  539. const argument = this.gobbleToken();
  540. if (!argument) {
  541. this.throwError('missing unaryOp argument');
  542. }
  543. return this.runHook('after-token', {
  544. type: Jsep.UNARY_EXP,
  545. operator: to_check,
  546. argument,
  547. prefix: true
  548. });
  549. }
  550. to_check = to_check.substr(0, --tc_len);
  551. }
  552. if (Jsep.isIdentifierStart(ch)) {
  553. node = this.gobbleIdentifier();
  554. if (Jsep.literals.hasOwnProperty(node.name)) {
  555. node = {
  556. type: Jsep.LITERAL,
  557. value: Jsep.literals[node.name],
  558. raw: node.name,
  559. };
  560. }
  561. else if (node.name === Jsep.this_str) {
  562. node = { type: Jsep.THIS_EXP };
  563. }
  564. }
  565. else if (ch === Jsep.OPAREN_CODE) { // open parenthesis
  566. node = this.gobbleGroup();
  567. }
  568. }
  569. if (!node) {
  570. return this.runHook('after-token', false);
  571. }
  572. node = this.gobbleTokenProperty(node);
  573. return this.runHook('after-token', node);
  574. }
  575. /**
  576. * Gobble properties of of identifiers/strings/arrays/groups.
  577. * e.g. `foo`, `bar.baz`, `foo['bar'].baz`
  578. * It also gobbles function calls:
  579. * e.g. `Math.acos(obj.angle)`
  580. * @param {jsep.Expression} node
  581. * @returns {jsep.Expression}
  582. */
  583. gobbleTokenProperty(node) {
  584. this.gobbleSpaces();
  585. let ch = this.code;
  586. while (ch === Jsep.PERIOD_CODE || ch === Jsep.OBRACK_CODE || ch === Jsep.OPAREN_CODE || ch === Jsep.QUMARK_CODE) {
  587. let optional;
  588. if (ch === Jsep.QUMARK_CODE) {
  589. if (this.expr.charCodeAt(this.index + 1) !== Jsep.PERIOD_CODE) {
  590. break;
  591. }
  592. optional = true;
  593. this.index += 2;
  594. this.gobbleSpaces();
  595. ch = this.code;
  596. }
  597. this.index++;
  598. if (ch === Jsep.OBRACK_CODE) {
  599. node = {
  600. type: Jsep.MEMBER_EXP,
  601. computed: true,
  602. object: node,
  603. property: this.gobbleExpression()
  604. };
  605. this.gobbleSpaces();
  606. ch = this.code;
  607. if (ch !== Jsep.CBRACK_CODE) {
  608. this.throwError('Unclosed [');
  609. }
  610. this.index++;
  611. }
  612. else if (ch === Jsep.OPAREN_CODE) {
  613. // A function call is being made; gobble all the arguments
  614. node = {
  615. type: Jsep.CALL_EXP,
  616. 'arguments': this.gobbleArguments(Jsep.CPAREN_CODE),
  617. callee: node
  618. };
  619. }
  620. else if (ch === Jsep.PERIOD_CODE || optional) {
  621. if (optional) {
  622. this.index--;
  623. }
  624. this.gobbleSpaces();
  625. node = {
  626. type: Jsep.MEMBER_EXP,
  627. computed: false,
  628. object: node,
  629. property: this.gobbleIdentifier(),
  630. };
  631. }
  632. if (optional) {
  633. node.optional = true;
  634. } // else leave undefined for compatibility with esprima
  635. this.gobbleSpaces();
  636. ch = this.code;
  637. }
  638. return node;
  639. }
  640. /**
  641. * Parse simple numeric literals: `12`, `3.4`, `.5`. Do this by using a string to
  642. * keep track of everything in the numeric literal and then calling `parseFloat` on that string
  643. * @returns {jsep.Literal}
  644. */
  645. gobbleNumericLiteral() {
  646. let number = '', ch, chCode;
  647. while (Jsep.isDecimalDigit(this.code)) {
  648. number += this.expr.charAt(this.index++);
  649. }
  650. if (this.code === Jsep.PERIOD_CODE) { // can start with a decimal marker
  651. number += this.expr.charAt(this.index++);
  652. while (Jsep.isDecimalDigit(this.code)) {
  653. number += this.expr.charAt(this.index++);
  654. }
  655. }
  656. ch = this.char;
  657. if (ch === 'e' || ch === 'E') { // exponent marker
  658. number += this.expr.charAt(this.index++);
  659. ch = this.char;
  660. if (ch === '+' || ch === '-') { // exponent sign
  661. number += this.expr.charAt(this.index++);
  662. }
  663. while (Jsep.isDecimalDigit(this.code)) { // exponent itself
  664. number += this.expr.charAt(this.index++);
  665. }
  666. if (!Jsep.isDecimalDigit(this.expr.charCodeAt(this.index - 1)) ) {
  667. this.throwError('Expected exponent (' + number + this.char + ')');
  668. }
  669. }
  670. chCode = this.code;
  671. // Check to make sure this isn't a variable name that start with a number (123abc)
  672. if (Jsep.isIdentifierStart(chCode)) {
  673. this.throwError('Variable names cannot start with a number (' +
  674. number + this.char + ')');
  675. }
  676. else if (chCode === Jsep.PERIOD_CODE || (number.length === 1 && number.charCodeAt(0) === Jsep.PERIOD_CODE)) {
  677. this.throwError('Unexpected period');
  678. }
  679. return {
  680. type: Jsep.LITERAL,
  681. value: parseFloat(number),
  682. raw: number
  683. };
  684. }
  685. /**
  686. * Parses a string literal, staring with single or double quotes with basic support for escape codes
  687. * e.g. `"hello world"`, `'this is\nJSEP'`
  688. * @returns {jsep.Literal}
  689. */
  690. gobbleStringLiteral() {
  691. let str = '';
  692. const startIndex = this.index;
  693. const quote = this.expr.charAt(this.index++);
  694. let closed = false;
  695. while (this.index < this.expr.length) {
  696. let ch = this.expr.charAt(this.index++);
  697. if (ch === quote) {
  698. closed = true;
  699. break;
  700. }
  701. else if (ch === '\\') {
  702. // Check for all of the common escape codes
  703. ch = this.expr.charAt(this.index++);
  704. switch (ch) {
  705. case 'n': str += '\n'; break;
  706. case 'r': str += '\r'; break;
  707. case 't': str += '\t'; break;
  708. case 'b': str += '\b'; break;
  709. case 'f': str += '\f'; break;
  710. case 'v': str += '\x0B'; break;
  711. default : str += ch;
  712. }
  713. }
  714. else {
  715. str += ch;
  716. }
  717. }
  718. if (!closed) {
  719. this.throwError('Unclosed quote after "' + str + '"');
  720. }
  721. return {
  722. type: Jsep.LITERAL,
  723. value: str,
  724. raw: this.expr.substring(startIndex, this.index),
  725. };
  726. }
  727. /**
  728. * Gobbles only identifiers
  729. * e.g.: `foo`, `_value`, `$x1`
  730. * Also, this function checks if that identifier is a literal:
  731. * (e.g. `true`, `false`, `null`) or `this`
  732. * @returns {jsep.Identifier}
  733. */
  734. gobbleIdentifier() {
  735. let ch = this.code, start = this.index;
  736. if (Jsep.isIdentifierStart(ch)) {
  737. this.index++;
  738. }
  739. else {
  740. this.throwError('Unexpected ' + this.char);
  741. }
  742. while (this.index < this.expr.length) {
  743. ch = this.code;
  744. if (Jsep.isIdentifierPart(ch)) {
  745. this.index++;
  746. }
  747. else {
  748. break;
  749. }
  750. }
  751. return {
  752. type: Jsep.IDENTIFIER,
  753. name: this.expr.slice(start, this.index),
  754. };
  755. }
  756. /**
  757. * Gobbles a list of arguments within the context of a function call
  758. * or array literal. This function also assumes that the opening character
  759. * `(` or `[` has already been gobbled, and gobbles expressions and commas
  760. * until the terminator character `)` or `]` is encountered.
  761. * e.g. `foo(bar, baz)`, `my_func()`, or `[bar, baz]`
  762. * @param {number} termination
  763. * @returns {jsep.Expression[]}
  764. */
  765. gobbleArguments(termination) {
  766. const args = [];
  767. let closed = false;
  768. let separator_count = 0;
  769. while (this.index < this.expr.length) {
  770. this.gobbleSpaces();
  771. let ch_i = this.code;
  772. if (ch_i === termination) { // done parsing
  773. closed = true;
  774. this.index++;
  775. if (termination === Jsep.CPAREN_CODE && separator_count && separator_count >= args.length){
  776. this.throwError('Unexpected token ' + String.fromCharCode(termination));
  777. }
  778. break;
  779. }
  780. else if (ch_i === Jsep.COMMA_CODE) { // between expressions
  781. this.index++;
  782. separator_count++;
  783. if (separator_count !== args.length) { // missing argument
  784. if (termination === Jsep.CPAREN_CODE) {
  785. this.throwError('Unexpected token ,');
  786. }
  787. else if (termination === Jsep.CBRACK_CODE) {
  788. for (let arg = args.length; arg < separator_count; arg++) {
  789. args.push(null);
  790. }
  791. }
  792. }
  793. }
  794. else if (args.length !== separator_count && separator_count !== 0) {
  795. // NOTE: `&& separator_count !== 0` allows for either all commas, or all spaces as arguments
  796. this.throwError('Expected comma');
  797. }
  798. else {
  799. const node = this.gobbleExpression();
  800. if (!node || node.type === Jsep.COMPOUND) {
  801. this.throwError('Expected comma');
  802. }
  803. args.push(node);
  804. }
  805. }
  806. if (!closed) {
  807. this.throwError('Expected ' + String.fromCharCode(termination));
  808. }
  809. return args;
  810. }
  811. /**
  812. * Responsible for parsing a group of things within parentheses `()`
  813. * that have no identifier in front (so not a function call)
  814. * This function assumes that it needs to gobble the opening parenthesis
  815. * and then tries to gobble everything within that parenthesis, assuming
  816. * that the next thing it should see is the close parenthesis. If not,
  817. * then the expression probably doesn't have a `)`
  818. * @returns {boolean|jsep.Expression}
  819. */
  820. gobbleGroup() {
  821. this.index++;
  822. let nodes = this.gobbleExpressions(Jsep.CPAREN_CODE);
  823. if (this.code === Jsep.CPAREN_CODE) {
  824. this.index++;
  825. if (nodes.length === 1) {
  826. return nodes[0];
  827. }
  828. else if (!nodes.length) {
  829. return false;
  830. }
  831. else {
  832. return {
  833. type: Jsep.SEQUENCE_EXP,
  834. expressions: nodes,
  835. };
  836. }
  837. }
  838. else {
  839. this.throwError('Unclosed (');
  840. }
  841. }
  842. /**
  843. * Responsible for parsing Array literals `[1, 2, 3]`
  844. * This function assumes that it needs to gobble the opening bracket
  845. * and then tries to gobble the expressions as arguments.
  846. * @returns {jsep.ArrayExpression}
  847. */
  848. gobbleArray() {
  849. this.index++;
  850. return {
  851. type: Jsep.ARRAY_EXP,
  852. elements: this.gobbleArguments(Jsep.CBRACK_CODE)
  853. };
  854. }
  855. }
  856. // Static fields:
  857. const hooks = new Hooks();
  858. Object.assign(Jsep, {
  859. hooks,
  860. plugins: new Plugins(Jsep),
  861. // Node Types
  862. // ----------
  863. // This is the full set of types that any JSEP node can be.
  864. // Store them here to save space when minified
  865. COMPOUND: 'Compound',
  866. SEQUENCE_EXP: 'SequenceExpression',
  867. IDENTIFIER: 'Identifier',
  868. MEMBER_EXP: 'MemberExpression',
  869. LITERAL: 'Literal',
  870. THIS_EXP: 'ThisExpression',
  871. CALL_EXP: 'CallExpression',
  872. UNARY_EXP: 'UnaryExpression',
  873. BINARY_EXP: 'BinaryExpression',
  874. ARRAY_EXP: 'ArrayExpression',
  875. TAB_CODE: 9,
  876. LF_CODE: 10,
  877. CR_CODE: 13,
  878. SPACE_CODE: 32,
  879. PERIOD_CODE: 46, // '.'
  880. COMMA_CODE: 44, // ','
  881. SQUOTE_CODE: 39, // single quote
  882. DQUOTE_CODE: 34, // double quotes
  883. OPAREN_CODE: 40, // (
  884. CPAREN_CODE: 41, // )
  885. OBRACK_CODE: 91, // [
  886. CBRACK_CODE: 93, // ]
  887. QUMARK_CODE: 63, // ?
  888. SEMCOL_CODE: 59, // ;
  889. COLON_CODE: 58, // :
  890. // Operations
  891. // ----------
  892. // Use a quickly-accessible map to store all of the unary operators
  893. // Values are set to `1` (it really doesn't matter)
  894. unary_ops: {
  895. '-': 1,
  896. '!': 1,
  897. '~': 1,
  898. '+': 1
  899. },
  900. // Also use a map for the binary operations but set their values to their
  901. // binary precedence for quick reference (higher number = higher precedence)
  902. // see [Order of operations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence)
  903. binary_ops: {
  904. '||': 1, '&&': 2, '|': 3, '^': 4, '&': 5,
  905. '==': 6, '!=': 6, '===': 6, '!==': 6,
  906. '<': 7, '>': 7, '<=': 7, '>=': 7,
  907. '<<': 8, '>>': 8, '>>>': 8,
  908. '+': 9, '-': 9,
  909. '*': 10, '/': 10, '%': 10
  910. },
  911. // sets specific binary_ops as right-associative
  912. right_associative: new Set(),
  913. // Additional valid identifier chars, apart from a-z, A-Z and 0-9 (except on the starting char)
  914. additional_identifier_chars: new Set(['$', '_']),
  915. // Literals
  916. // ----------
  917. // Store the values to return for the various literals we may encounter
  918. literals: {
  919. 'true': true,
  920. 'false': false,
  921. 'null': null
  922. },
  923. // Except for `this`, which is special. This could be changed to something like `'self'` as well
  924. this_str: 'this',
  925. });
  926. Jsep.max_unop_len = Jsep.getMaxKeyLen(Jsep.unary_ops);
  927. Jsep.max_binop_len = Jsep.getMaxKeyLen(Jsep.binary_ops);
  928. // Backward Compatibility:
  929. const jsep = expr => (new Jsep(expr)).parse();
  930. const staticMethods = Object.getOwnPropertyNames(Jsep);
  931. staticMethods
  932. .forEach((m) => {
  933. if (jsep[m] === undefined && m !== 'prototype') {
  934. jsep[m] = Jsep[m];
  935. }
  936. });
  937. jsep.Jsep = Jsep; // allows for const { Jsep } = require('jsep');
  938. const CONDITIONAL_EXP = 'ConditionalExpression';
  939. var ternary = {
  940. name: 'ternary',
  941. init(jsep) {
  942. // Ternary expression: test ? consequent : alternate
  943. jsep.hooks.add('after-expression', function gobbleTernary(env) {
  944. if (env.node && this.code === jsep.QUMARK_CODE) {
  945. this.index++;
  946. const test = env.node;
  947. const consequent = this.gobbleExpression();
  948. if (!consequent) {
  949. this.throwError('Expected expression');
  950. }
  951. this.gobbleSpaces();
  952. if (this.code === jsep.COLON_CODE) {
  953. this.index++;
  954. const alternate = this.gobbleExpression();
  955. if (!alternate) {
  956. this.throwError('Expected expression');
  957. }
  958. env.node = {
  959. type: CONDITIONAL_EXP,
  960. test,
  961. consequent,
  962. alternate,
  963. };
  964. // check for operators of higher priority than ternary (i.e. assignment)
  965. // jsep sets || at 1, and assignment at 0.9, and conditional should be between them
  966. if (test.operator && jsep.binary_ops[test.operator] <= 0.9) {
  967. let newTest = test;
  968. while (newTest.right.operator && jsep.binary_ops[newTest.right.operator] <= 0.9) {
  969. newTest = newTest.right;
  970. }
  971. env.node.test = newTest.right;
  972. newTest.right = env.node;
  973. env.node = test;
  974. }
  975. }
  976. else {
  977. this.throwError('Expected :');
  978. }
  979. }
  980. });
  981. },
  982. };
  983. // Add default plugins:
  984. jsep.plugins.register(ternary);
  985. return jsep;
  986. }());
  987. //# sourceMappingURL=jsep.iife.js.map