智慧水务管理系统 - 精河县供水工程综合管理平台

jsep.js 28KB

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