1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | export const code = {
|
8 | COMPOUND: "Compound",
|
9 | IDENTIFIER: "Identifier",
|
10 | MEMBER_EXP: "MemberExpression",
|
11 | LITERAL: "Literal",
|
12 | THIS_EXP: "ThisExpression",
|
13 | CALL_EXP: "CallExpression",
|
14 | UNARY_EXP: "UnaryExpression",
|
15 | BINARY_EXP: "BinaryExpression",
|
16 | LOGICAL_EXP: "LogicalExpression",
|
17 | CONDITIONAL_EXP: "ConditionalExpression",
|
18 | ARRAY_EXP: "ArrayExpression"
|
19 | };
|
20 |
|
21 | var PERIOD_CODE = 46,
|
22 | COMMA_CODE = 44,
|
23 | SQUOTE_CODE = 39,
|
24 | DQUOTE_CODE = 34,
|
25 | OPAREN_CODE = 40,
|
26 | CPAREN_CODE = 41,
|
27 | OBRACK_CODE = 91,
|
28 | CBRACK_CODE = 93,
|
29 | QUMARK_CODE = 63,
|
30 | SEMCOL_CODE = 59,
|
31 | COLON_CODE = 58,
|
32 | throwError = function(message, index) {
|
33 | var error = new Error(message + " at character " + index);
|
34 | error.index = index;
|
35 | error.description = message;
|
36 | throw error;
|
37 | },
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | t = true,
|
43 |
|
44 |
|
45 | unary_ops = { "-": t, "!": t, "~": t, "+": t },
|
46 |
|
47 |
|
48 |
|
49 | binary_ops = {
|
50 | "||": 1,
|
51 | "&&": 2,
|
52 | "|": 3,
|
53 | "^": 4,
|
54 | "&": 5,
|
55 | "==": 6,
|
56 | "!=": 6,
|
57 | "===": 6,
|
58 | "!==": 6,
|
59 | "<": 7,
|
60 | ">": 7,
|
61 | "<=": 7,
|
62 | ">=": 7,
|
63 | "<<": 8,
|
64 | ">>": 8,
|
65 | ">>>": 8,
|
66 | "+": 9,
|
67 | "-": 9,
|
68 | "*": 10,
|
69 | "/": 10,
|
70 | "%": 10
|
71 | },
|
72 |
|
73 | getMaxKeyLen = function(obj) {
|
74 | var max_len = 0,
|
75 | len;
|
76 | for (var key in obj) {
|
77 | if ((len = key.length) > max_len && obj.hasOwnProperty(key)) {
|
78 | max_len = len;
|
79 | }
|
80 | }
|
81 | return max_len;
|
82 | },
|
83 | max_unop_len = getMaxKeyLen(unary_ops),
|
84 | max_binop_len = getMaxKeyLen(binary_ops),
|
85 |
|
86 |
|
87 |
|
88 | literals = {
|
89 | true: true,
|
90 | false: false,
|
91 | null: null
|
92 | },
|
93 |
|
94 | this_str = "this",
|
95 |
|
96 | binaryPrecedence = function(op_val) {
|
97 | return binary_ops[op_val] || 0;
|
98 | },
|
99 |
|
100 |
|
101 | createBinaryExpression = function(operator, left, right) {
|
102 | var type =
|
103 | operator === "||" || operator === "&&"
|
104 | ? code.LOGICAL_EXP
|
105 | : code.BINARY_EXP;
|
106 | return {
|
107 | type: type,
|
108 | operator: operator,
|
109 | left: left,
|
110 | right: right
|
111 | };
|
112 | },
|
113 |
|
114 | isDecimalDigit = function(ch) {
|
115 | return ch >= 48 && ch <= 57;
|
116 | },
|
117 | isIdentifierStart = function(ch) {
|
118 | return (
|
119 | ch === 36 ||
|
120 | ch === 95 ||
|
121 | (ch >= 65 && ch <= 90) ||
|
122 | (ch >= 97 && ch <= 122) ||
|
123 | (ch >= 128 && !binary_ops[String.fromCharCode(ch)])
|
124 | );
|
125 | },
|
126 | isIdentifierPart = function(ch) {
|
127 | return (
|
128 | ch === 36 ||
|
129 | ch === 95 ||
|
130 | (ch >= 65 && ch <= 90) ||
|
131 | (ch >= 97 && ch <= 122) ||
|
132 | (ch >= 48 && ch <= 57) ||
|
133 | (ch >= 128 && !binary_ops[String.fromCharCode(ch)])
|
134 | );
|
135 | },
|
136 |
|
137 |
|
138 |
|
139 | jsep = function(expr) {
|
140 |
|
141 |
|
142 | var index = 0,
|
143 | charAtFunc = expr.charAt,
|
144 | charCodeAtFunc = expr.charCodeAt,
|
145 | exprI = function(i) {
|
146 | return charAtFunc.call(expr, i);
|
147 | },
|
148 | exprICode = function(i) {
|
149 | return charCodeAtFunc.call(expr, i);
|
150 | },
|
151 | length = expr.length,
|
152 |
|
153 | gobbleSpaces = function() {
|
154 | var ch = exprICode(index);
|
155 |
|
156 | while (ch === 32 || ch === 9) {
|
157 | ch = exprICode(++index);
|
158 | }
|
159 | },
|
160 |
|
161 | gobbleExpression = function() {
|
162 | var test = gobbleBinaryExpression(),
|
163 | consequent,
|
164 | alternate;
|
165 | gobbleSpaces();
|
166 | if (exprICode(index) === QUMARK_CODE) {
|
167 |
|
168 | index++;
|
169 | consequent = gobbleExpression();
|
170 | if (!consequent) {
|
171 | throwError("Expected expression", index);
|
172 | }
|
173 | gobbleSpaces();
|
174 | if (exprICode(index) === COLON_CODE) {
|
175 | index++;
|
176 | alternate = gobbleExpression();
|
177 | if (!alternate) {
|
178 | throwError("Expected expression", index);
|
179 | }
|
180 | return {
|
181 | type: code.CONDITIONAL_EXP,
|
182 | test: test,
|
183 | consequent: consequent,
|
184 | alternate: alternate
|
185 | };
|
186 | } else {
|
187 | throwError("Expected :", index);
|
188 | }
|
189 | } else {
|
190 | return test;
|
191 | }
|
192 | },
|
193 |
|
194 |
|
195 |
|
196 |
|
197 | gobbleBinaryOp = function() {
|
198 | gobbleSpaces();
|
199 | var to_check = expr.substr(index, max_binop_len),
|
200 | tc_len = to_check.length;
|
201 | while (tc_len > 0) {
|
202 | if (binary_ops.hasOwnProperty(to_check)) {
|
203 | index += tc_len;
|
204 | return to_check;
|
205 | }
|
206 | to_check = to_check.substr(0, --tc_len);
|
207 | }
|
208 | return false;
|
209 | },
|
210 |
|
211 |
|
212 | gobbleBinaryExpression = function() {
|
213 | var node, biop, prec, stack, biop_info, left, right, i;
|
214 |
|
215 |
|
216 |
|
217 | left = gobbleToken();
|
218 | biop = gobbleBinaryOp();
|
219 |
|
220 |
|
221 | if (!biop) {
|
222 | return left;
|
223 | }
|
224 |
|
225 |
|
226 |
|
227 | biop_info = { value: biop, prec: binaryPrecedence(biop) };
|
228 |
|
229 | right = gobbleToken();
|
230 | if (!right) {
|
231 | throwError("Expected expression after " + biop, index);
|
232 | }
|
233 | stack = [left, biop_info, right];
|
234 |
|
235 |
|
236 | while ((biop = gobbleBinaryOp())) {
|
237 | prec = binaryPrecedence(biop);
|
238 |
|
239 | if (prec === 0) {
|
240 | break;
|
241 | }
|
242 | biop_info = { value: biop, prec: prec };
|
243 |
|
244 |
|
245 | while (stack.length > 2 && prec <= stack[stack.length - 2].prec) {
|
246 | right = stack.pop();
|
247 | biop = stack.pop().value;
|
248 | left = stack.pop();
|
249 | node = createBinaryExpression(biop, left, right);
|
250 | stack.push(node);
|
251 | }
|
252 |
|
253 | node = gobbleToken();
|
254 | if (!node) {
|
255 | throwError("Expected expression after " + biop, index);
|
256 | }
|
257 | stack.push(biop_info, node);
|
258 | }
|
259 |
|
260 | i = stack.length - 1;
|
261 | node = stack[i];
|
262 | while (i > 1) {
|
263 | node = createBinaryExpression(stack[i - 1].value, stack[i - 2], node);
|
264 | i -= 2;
|
265 | }
|
266 | return node;
|
267 | },
|
268 |
|
269 |
|
270 | gobbleToken = function() {
|
271 | var ch, to_check, tc_len;
|
272 |
|
273 | gobbleSpaces();
|
274 | ch = exprICode(index);
|
275 |
|
276 | if (isDecimalDigit(ch) || ch === PERIOD_CODE) {
|
277 |
|
278 | return gobbleNumericLiteral();
|
279 | } else if (ch === SQUOTE_CODE || ch === DQUOTE_CODE) {
|
280 |
|
281 | return gobbleStringLiteral();
|
282 | } else if (isIdentifierStart(ch) || ch === OPAREN_CODE) {
|
283 |
|
284 |
|
285 | return gobbleVariable();
|
286 | } else if (ch === OBRACK_CODE) {
|
287 | return gobbleArray();
|
288 | } else {
|
289 | to_check = expr.substr(index, max_unop_len);
|
290 | tc_len = to_check.length;
|
291 | while (tc_len > 0) {
|
292 | if (unary_ops.hasOwnProperty(to_check)) {
|
293 | index += tc_len;
|
294 | return {
|
295 | type: code.UNARY_EXP,
|
296 | operator: to_check,
|
297 | argument: gobbleToken(),
|
298 | prefix: true
|
299 | };
|
300 | }
|
301 | to_check = to_check.substr(0, --tc_len);
|
302 | }
|
303 |
|
304 | return false;
|
305 | }
|
306 | },
|
307 |
|
308 |
|
309 | gobbleNumericLiteral = function() {
|
310 | var number = "",
|
311 | ch,
|
312 | chCode;
|
313 | while (isDecimalDigit(exprICode(index))) {
|
314 | number += exprI(index++);
|
315 | }
|
316 |
|
317 | if (exprICode(index) === PERIOD_CODE) {
|
318 |
|
319 | number += exprI(index++);
|
320 |
|
321 | while (isDecimalDigit(exprICode(index))) {
|
322 | number += exprI(index++);
|
323 | }
|
324 | }
|
325 |
|
326 | ch = exprI(index);
|
327 | if (ch === "e" || ch === "E") {
|
328 |
|
329 | number += exprI(index++);
|
330 | ch = exprI(index);
|
331 | if (ch === "+" || ch === "-") {
|
332 |
|
333 | number += exprI(index++);
|
334 | }
|
335 | while (isDecimalDigit(exprICode(index))) {
|
336 |
|
337 | number += exprI(index++);
|
338 | }
|
339 | if (!isDecimalDigit(exprICode(index - 1))) {
|
340 | throwError(
|
341 | "Expected exponent (" + number + exprI(index) + ")",
|
342 | index
|
343 | );
|
344 | }
|
345 | }
|
346 |
|
347 | chCode = exprICode(index);
|
348 |
|
349 | if (isIdentifierStart(chCode)) {
|
350 | throwError(
|
351 | "Variable names cannot start with a number (" +
|
352 | number +
|
353 | exprI(index) +
|
354 | ")",
|
355 | index
|
356 | );
|
357 | } else if (chCode === PERIOD_CODE) {
|
358 | throwError("Unexpected period", index);
|
359 | }
|
360 |
|
361 | return {
|
362 | type: code.LITERAL,
|
363 | value: parseFloat(number),
|
364 | raw: number
|
365 | };
|
366 | },
|
367 |
|
368 |
|
369 | gobbleStringLiteral = function() {
|
370 | var str = "",
|
371 | quote = exprI(index++),
|
372 | closed = false,
|
373 | ch;
|
374 |
|
375 | while (index < length) {
|
376 | ch = exprI(index++);
|
377 | if (ch === quote) {
|
378 | closed = true;
|
379 | break;
|
380 | } else if (ch === "\\") {
|
381 |
|
382 | ch = exprI(index++);
|
383 | switch (ch) {
|
384 | case "n":
|
385 | str += "\n";
|
386 | break;
|
387 | case "r":
|
388 | str += "\r";
|
389 | break;
|
390 | case "t":
|
391 | str += "\t";
|
392 | break;
|
393 | case "b":
|
394 | str += "\b";
|
395 | break;
|
396 | case "f":
|
397 | str += "\f";
|
398 | break;
|
399 | case "v":
|
400 | str += "\x0B";
|
401 | break;
|
402 | default:
|
403 | str += "\\" + ch;
|
404 | }
|
405 | } else {
|
406 | str += ch;
|
407 | }
|
408 | }
|
409 |
|
410 | if (!closed) {
|
411 | throwError('Unclosed quote after "' + str + '"', index);
|
412 | }
|
413 |
|
414 | return {
|
415 | type: code.LITERAL,
|
416 | value: str,
|
417 | raw: quote + str + quote
|
418 | };
|
419 | },
|
420 |
|
421 |
|
422 |
|
423 |
|
424 | gobbleIdentifier = function() {
|
425 | var ch = exprICode(index),
|
426 | start = index,
|
427 | identifier;
|
428 |
|
429 | if (isIdentifierStart(ch)) {
|
430 | index++;
|
431 | } else {
|
432 | throwError("Unexpected " + exprI(index), index);
|
433 | }
|
434 |
|
435 | while (index < length) {
|
436 | ch = exprICode(index);
|
437 | if (isIdentifierPart(ch)) {
|
438 | index++;
|
439 | } else {
|
440 | break;
|
441 | }
|
442 | }
|
443 | identifier = expr.slice(start, index);
|
444 |
|
445 | if (literals.hasOwnProperty(identifier)) {
|
446 | return {
|
447 | type: code.LITERAL,
|
448 | value: literals[identifier],
|
449 | raw: identifier
|
450 | };
|
451 | } else if (identifier === this_str) {
|
452 | return { type: code.THIS_EXP };
|
453 | } else {
|
454 | return {
|
455 | type: code.IDENTIFIER,
|
456 | name: identifier
|
457 | };
|
458 | }
|
459 | },
|
460 |
|
461 |
|
462 |
|
463 |
|
464 |
|
465 | gobbleArguments = function(termination) {
|
466 | var ch_i,
|
467 | args = [],
|
468 | node,
|
469 | closed = false;
|
470 | while (index < length) {
|
471 | gobbleSpaces();
|
472 | ch_i = exprICode(index);
|
473 | if (ch_i === termination) {
|
474 |
|
475 | closed = true;
|
476 | index++;
|
477 | break;
|
478 | } else if (ch_i === COMMA_CODE) {
|
479 |
|
480 | index++;
|
481 | } else {
|
482 | node = gobbleExpression();
|
483 | if (!node || node.type === code.COMPOUND) {
|
484 | throwError("Expected comma", index);
|
485 | }
|
486 | args.push(node);
|
487 | }
|
488 | }
|
489 | if (!closed) {
|
490 | throwError("Expected " + String.fromCharCode(termination), index);
|
491 | }
|
492 | return args;
|
493 | },
|
494 |
|
495 |
|
496 |
|
497 |
|
498 | gobbleVariable = function() {
|
499 | var ch_i, node;
|
500 | ch_i = exprICode(index);
|
501 |
|
502 | if (ch_i === OPAREN_CODE) {
|
503 | node = gobbleGroup();
|
504 | } else {
|
505 | node = gobbleIdentifier();
|
506 | }
|
507 | gobbleSpaces();
|
508 | ch_i = exprICode(index);
|
509 | while (
|
510 | ch_i === PERIOD_CODE ||
|
511 | ch_i === OBRACK_CODE ||
|
512 | ch_i === OPAREN_CODE
|
513 | ) {
|
514 | index++;
|
515 | if (ch_i === PERIOD_CODE) {
|
516 | gobbleSpaces();
|
517 | node = {
|
518 | type: code.MEMBER_EXP,
|
519 | computed: false,
|
520 | object: node,
|
521 | property: gobbleIdentifier()
|
522 | };
|
523 | } else if (ch_i === OBRACK_CODE) {
|
524 | node = {
|
525 | type: code.MEMBER_EXP,
|
526 | computed: true,
|
527 | object: node,
|
528 | property: gobbleExpression()
|
529 | };
|
530 | gobbleSpaces();
|
531 | ch_i = exprICode(index);
|
532 | if (ch_i !== CBRACK_CODE) {
|
533 | throwError("Unclosed [", index);
|
534 | }
|
535 | index++;
|
536 | } else if (ch_i === OPAREN_CODE) {
|
537 |
|
538 | node = {
|
539 | type: code.CALL_EXP,
|
540 | arguments: gobbleArguments(CPAREN_CODE),
|
541 | callee: node
|
542 | };
|
543 | }
|
544 | gobbleSpaces();
|
545 | ch_i = exprICode(index);
|
546 | }
|
547 | return node;
|
548 | },
|
549 |
|
550 |
|
551 |
|
552 |
|
553 |
|
554 | gobbleGroup = function() {
|
555 | index++;
|
556 | var node = gobbleExpression();
|
557 | gobbleSpaces();
|
558 | if (exprICode(index) === CPAREN_CODE) {
|
559 | index++;
|
560 | return node;
|
561 | } else {
|
562 | throwError("Unclosed (", index);
|
563 | }
|
564 | },
|
565 |
|
566 |
|
567 |
|
568 | gobbleArray = function() {
|
569 | index++;
|
570 | return {
|
571 | type: code.ARRAY_EXP,
|
572 | elements: gobbleArguments(CBRACK_CODE)
|
573 | };
|
574 | },
|
575 | nodes = [],
|
576 | ch_i,
|
577 | node;
|
578 |
|
579 | while (index < length) {
|
580 | ch_i = exprICode(index);
|
581 |
|
582 |
|
583 |
|
584 | if (ch_i === SEMCOL_CODE || ch_i === COMMA_CODE) {
|
585 | index++;
|
586 | } else {
|
587 |
|
588 | if ((node = gobbleExpression())) {
|
589 | nodes.push(node);
|
590 |
|
591 |
|
592 | } else if (index < length) {
|
593 | throwError('Unexpected "' + exprI(index) + '"', index);
|
594 | }
|
595 | }
|
596 | }
|
597 |
|
598 |
|
599 | if (nodes.length === 1) {
|
600 | return nodes[0];
|
601 | } else {
|
602 | return {
|
603 | type: code.COMPOUND,
|
604 | body: nodes
|
605 | };
|
606 | }
|
607 | };
|
608 |
|
609 |
|
610 |
|
611 |
|
612 |
|
613 |
|
614 | jsep.addUnaryOp = function(op_name) {
|
615 | max_unop_len = Math.max(op_name.length, max_unop_len);
|
616 | unary_ops[op_name] = t;
|
617 | return this;
|
618 | };
|
619 |
|
620 |
|
621 |
|
622 |
|
623 |
|
624 |
|
625 |
|
626 | jsep.addBinaryOp = function(op_name, precedence) {
|
627 | max_binop_len = Math.max(op_name.length, max_binop_len);
|
628 | binary_ops[op_name] = precedence;
|
629 | return this;
|
630 | };
|
631 |
|
632 |
|
633 |
|
634 |
|
635 |
|
636 |
|
637 |
|
638 | jsep.addLiteral = function(literal_name, literal_value) {
|
639 | literals[literal_name] = literal_value;
|
640 | return this;
|
641 | };
|
642 |
|
643 |
|
644 |
|
645 |
|
646 |
|
647 |
|
648 | jsep.removeUnaryOp = function(op_name) {
|
649 | delete unary_ops[op_name];
|
650 | if (op_name.length === max_unop_len) {
|
651 | max_unop_len = getMaxKeyLen(unary_ops);
|
652 | }
|
653 | return this;
|
654 | };
|
655 |
|
656 |
|
657 |
|
658 |
|
659 |
|
660 |
|
661 | jsep.removeBinaryOp = function(op_name) {
|
662 | delete binary_ops[op_name];
|
663 | if (op_name.length === max_binop_len) {
|
664 | max_binop_len = getMaxKeyLen(binary_ops);
|
665 | }
|
666 | return this;
|
667 | };
|
668 |
|
669 |
|
670 |
|
671 |
|
672 |
|
673 |
|
674 | jsep.removeLiteral = function(literal_name) {
|
675 | delete literals[literal_name];
|
676 | return this;
|
677 | };
|
678 |
|
679 | export default jsep;
|