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

jsep.cjs.js 28KB

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