UNPKG

37 kBJavaScriptView Raw
1/**
2* JSPath
3*
4* Copyright (c) 2012 Filatov Dmitry (dfilatov@yandex-team.ru)
5* With parts by Marat Dulin (mdevils@gmail.com)
6* Dual licensed under the MIT and GPL licenses:
7* http://www.opensource.org/licenses/mit-license.php
8* http://www.gnu.org/licenses/gpl.html
9*
10* @version 0.3.4
11*/
12
13(function() {
14
15var SYNTAX = {
16 PATH : 1,
17 SELECTOR : 2,
18 OBJ_PRED : 3,
19 POS_PRED : 4,
20 LOGICAL_EXPR : 5,
21 COMPARISON_EXPR : 6,
22 MATH_EXPR : 7,
23 CONCAT_EXPR : 8,
24 UNARY_EXPR : 9,
25 POS_EXPR : 10,
26 LITERAL : 11
27 };
28
29// parser
30
31var parse = (function() {
32
33 var TOKEN = {
34 ID : 1,
35 NUM : 2,
36 STR : 3,
37 BOOL : 4,
38 PUNCT : 5,
39 EOP : 6
40 },
41 MESSAGES = {
42 UNEXP_TOKEN : 'Unexpected token "%0"',
43 UNEXP_EOP : 'Unexpected end of path'
44 };
45
46 var path, idx, buf, len;
47
48 function parse(_path) {
49 path = _path.split('');
50 idx = 0;
51 buf = null;
52 len = path.length;
53
54 var res = parsePathConcatExpr(),
55 token = lex();
56
57 if(token.type !== TOKEN.EOP) {
58 throwUnexpected(token);
59 }
60
61 return res;
62 }
63
64 function parsePathConcatExpr() {
65 var expr = parsePathConcatPartExpr(),
66 operands;
67
68 while(match('|')) {
69 lex();
70 (operands || (operands = [expr])).push(parsePathConcatPartExpr());
71 }
72
73 return operands?
74 {
75 type : SYNTAX.CONCAT_EXPR,
76 args : operands
77 } :
78 expr;
79 }
80
81 function parsePathConcatPartExpr() {
82 return match('(')?
83 parsePathGroupExpr() :
84 parsePath();
85 }
86
87 function parsePathGroupExpr() {
88 expect('(');
89 var expr = parsePathConcatExpr();
90 expect(')');
91
92 var parts = [],
93 part;
94 while(part = parsePredicate()) {
95 parts.push(part);
96 }
97
98 if(!parts.length) {
99 return expr;
100 }
101 else if(expr.type === SYNTAX.PATH) {
102 expr.parts = expr.parts.concat(parts);
103 return expr;
104 }
105
106 parts.unshift(expr);
107
108 return {
109 type : SYNTAX.PATH,
110 parts : parts
111 };
112 }
113
114 function parsePredicate() {
115 if(match('[')) {
116 return parsePosPredicate();
117 }
118
119 if(match('{')) {
120 return parseObjectPredicate();
121 }
122
123 if(match('(')) {
124 return parsePathGroupExpr();
125 }
126 }
127
128 function parsePath() {
129 if(!matchPath()) {
130 throwUnexpected(lex());
131 }
132
133 var fromRoot = false,
134 subst;
135
136 if(match('^')) {
137 lex();
138 fromRoot = true;
139 }
140 else if(matchSubst()) {
141 subst = lex().val.substr(1);
142 }
143
144 var parts = [],
145 part;
146 while(part = parsePathPart()) {
147 parts.push(part);
148 }
149
150 return {
151 type : SYNTAX.PATH,
152 fromRoot : fromRoot,
153 subst : subst,
154 parts : parts
155 };
156 }
157
158 function parsePathPart() {
159 return matchSelector()?
160 parseSelector() :
161 parsePredicate();
162 }
163
164 function parseSelector() {
165 var selector = lex().val,
166 token = lookahead(),
167 prop;
168
169 if(match('*') || token.type === TOKEN.ID || token.type === TOKEN.STR) {
170 prop = lex().val;
171 }
172
173 return {
174 type : SYNTAX.SELECTOR,
175 selector : selector,
176 prop : prop
177 };
178 }
179
180 function parsePosPredicate() {
181 expect('[');
182 var expr = parsePosExpr();
183 expect(']');
184
185 return {
186 type : SYNTAX.POS_PRED,
187 arg : expr
188 };
189 }
190
191 function parseObjectPredicate() {
192 expect('{');
193 var expr = parseLogicalORExpr();
194 expect('}');
195
196 return {
197 type : SYNTAX.OBJ_PRED,
198 arg : expr
199 };
200 }
201
202 function parseLogicalORExpr() {
203 var expr = parseLogicalANDExpr(),
204 operands;
205
206 while(match('||')) {
207 lex();
208 (operands || (operands = [expr])).push(parseLogicalANDExpr());
209 }
210
211 return operands?
212 {
213 type : SYNTAX.LOGICAL_EXPR,
214 op : '||',
215 args : operands
216 } :
217 expr;
218 }
219
220 function parseLogicalANDExpr() {
221 var expr = parseEqualityExpr(),
222 operands;
223
224 while(match('&&')) {
225 lex();
226 (operands || (operands = [expr])).push(parseEqualityExpr());
227 }
228
229 return operands?
230 {
231 type : SYNTAX.LOGICAL_EXPR,
232 op : '&&',
233 args : operands
234 } :
235 expr;
236 }
237
238 function parseEqualityExpr() {
239 var expr = parseRelationalExpr();
240
241 while(match('==') || match('!=') || match('===') || match('!==') ||
242 match('^=') || match('^==') || match('$==') || match('$=') || match('*==') || match('*=')) {
243 expr = {
244 type : SYNTAX.COMPARISON_EXPR,
245 op : lex().val,
246 args : [expr, parseEqualityExpr()]
247 };
248 }
249
250 return expr;
251 }
252
253 function parseRelationalExpr() {
254 var expr = parseAdditiveExpr();
255
256 while(match('<') || match('>') || match('<=') || match('>=')) {
257 expr = {
258 type : SYNTAX.COMPARISON_EXPR,
259 op : lex().val,
260 args : [expr, parseRelationalExpr()]
261 };
262 }
263
264 return expr;
265 }
266
267 function parseAdditiveExpr() {
268 var expr = parseMultiplicativeExpr();
269
270 while(match('+') || match('-')) {
271 expr = {
272 type : SYNTAX.MATH_EXPR,
273 op : lex().val,
274 args : [expr, parseMultiplicativeExpr()]
275 };
276 }
277
278 return expr;
279 }
280
281 function parseMultiplicativeExpr() {
282 var expr = parseUnaryExpr();
283
284 while(match('*') || match('/') || match('%')) {
285 expr = {
286 type : SYNTAX.MATH_EXPR,
287 op : lex().val,
288 args : [expr, parseMultiplicativeExpr()]
289 };
290 }
291
292 return expr;
293 }
294
295 function parsePosExpr() {
296 if(match(':')) {
297 lex();
298 return {
299 type : SYNTAX.POS_EXPR,
300 toIdx : parseUnaryExpr()
301 };
302 }
303
304 var fromExpr = parseUnaryExpr();
305 if(match(':')) {
306 lex();
307 if(match(']')) {
308 return {
309 type : SYNTAX.POS_EXPR,
310 fromIdx : fromExpr
311 };
312 }
313
314 return {
315 type : SYNTAX.POS_EXPR,
316 fromIdx : fromExpr,
317 toIdx : parseUnaryExpr()
318 };
319 }
320
321 return {
322 type : SYNTAX.POS_EXPR,
323 idx : fromExpr
324 };
325 }
326
327 function parseUnaryExpr() {
328 if(match('!') || match('-')) {
329 return {
330 type : SYNTAX.UNARY_EXPR,
331 op : lex().val,
332 arg : parseUnaryExpr()
333 };
334 }
335
336 return parsePrimaryExpr();
337 }
338
339 function parsePrimaryExpr() {
340 var token = lookahead(),
341 type = token.type;
342
343 if(type === TOKEN.STR || type === TOKEN.NUM || type === TOKEN.BOOL) {
344 return {
345 type : SYNTAX.LITERAL,
346 val : lex().val
347 };
348 }
349
350 if(matchPath()) {
351 return parsePath();
352 }
353
354 if(match('(')) {
355 return parseGroupExpr();
356 }
357
358 return throwUnexpected(lex());
359 }
360
361 function parseGroupExpr() {
362 expect('(');
363 var expr = parseLogicalORExpr();
364 expect(')');
365
366 return expr;
367 }
368
369 function match(val) {
370 var token = lookahead();
371 return token.type === TOKEN.PUNCT && token.val === val;
372 }
373
374 function matchPath() {
375 return matchSelector() || matchSubst() || match('^');
376 }
377
378 function matchSelector() {
379 var token = lookahead();
380 if(token.type === TOKEN.PUNCT) {
381 var val = token.val;
382 return val === '.' || val === '..';
383 }
384
385 return false;
386 }
387
388 function matchSubst() {
389 var token = lookahead();
390 return token.type === TOKEN.ID && token.val[0] === '$';
391 }
392
393 function expect(val) {
394 var token = lex();
395 if(token.type !== TOKEN.PUNCT || token.val !== val) {
396 throwUnexpected(token);
397 }
398 }
399
400 function lookahead() {
401 if(buf !== null) {
402 return buf;
403 }
404
405 var pos = idx;
406 buf = advance();
407 idx = pos;
408
409 return buf;
410 }
411
412 function advance() {
413 while(isWhiteSpace(path[idx])) {
414 ++idx;
415 }
416
417 if(idx >= len) {
418 return {
419 type : TOKEN.EOP,
420 range : [idx, idx]
421 };
422 }
423
424 var token = scanPunctuator();
425 if(token ||
426 (token = scanId()) ||
427 (token = scanString()) ||
428 (token = scanNumeric())) {
429 return token;
430 }
431
432 token = { range : [idx, idx] };
433 idx >= len?
434 token.type = TOKEN.EOP :
435 token.val = path[idx];
436
437 throwUnexpected(token);
438 }
439
440 function lex() {
441 var token;
442
443 if(buf) {
444 idx = buf.range[1];
445 token = buf;
446 buf = null;
447 return token;
448 }
449
450 return advance();
451 }
452
453 function isDigit(ch) {
454 return '0123456789'.indexOf(ch) >= 0;
455 }
456
457 function isWhiteSpace(ch) {
458 return ch === ' ';
459 }
460
461 function isIdStart(ch) {
462 return (ch === '$') || (ch === '@') || (ch === '_') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
463 }
464
465 function isIdPart(ch) {
466 return isIdStart(ch) || (ch >= '0' && ch <= '9');
467 }
468
469 function scanId() {
470 var ch = path[idx];
471
472 if(!isIdStart(ch)) {
473 return;
474 }
475
476 var start = idx,
477 id = ch;
478
479 while(++idx < len) {
480 ch = path[idx];
481 if(!isIdPart(ch)) {
482 break;
483 }
484 id += ch;
485 }
486
487 return id === 'true' || id === 'false'?
488 {
489 type : TOKEN.BOOL,
490 val : id === 'true',
491 range : [start, idx]
492 } :
493 {
494 type : TOKEN.ID,
495 val : id,
496 range : [start, idx]
497 };
498 }
499
500 function scanString() {
501 if(path[idx] !== '"' && path[idx] !== "'") {
502 return;
503 }
504
505 var orig = path[idx],
506 start = ++idx,
507 str = '',
508 eosFound = false,
509 ch;
510
511 while(idx < len) {
512 ch = path[idx++];
513 if(ch === '\\') {
514 ch = path[idx++];
515 }
516 else if((ch === '"' || ch === "'") && ch === orig) {
517 eosFound = true;
518 break;
519 }
520 str += ch;
521 }
522
523 if(eosFound) {
524 return {
525 type : TOKEN.STR,
526 val : str,
527 range : [start, idx]
528 };
529 }
530 }
531
532 function scanNumeric() {
533 var start = idx,
534 ch = path[idx],
535 isFloat = ch === '.';
536
537 if(isFloat || isDigit(ch)) {
538 var num = ch;
539 while(++idx < len) {
540 ch = path[idx];
541 if(ch === '.') {
542 if(isFloat) {
543 return;
544 }
545 isFloat = true;
546 }
547 else if(!isDigit(ch)) {
548 break;
549 }
550
551 num += ch;
552 }
553
554 return {
555 type : TOKEN.NUM,
556 val : isFloat? parseFloat(num) : parseInt(num, 10),
557 range : [start, idx]
558 };
559 }
560 }
561
562 function scanPunctuator() {
563 var start = idx,
564 ch1 = path[idx],
565 ch2 = path[idx + 1];
566
567 if(ch1 === '.') {
568 if(isDigit(ch2)) {
569 return;
570 }
571
572 return path[++idx] === '.'?
573 {
574 type : TOKEN.PUNCT,
575 val : '..',
576 range : [start, ++idx]
577 } :
578 {
579 type : TOKEN.PUNCT,
580 val : '.',
581 range : [start, idx]
582 };
583 }
584
585 if(ch2 === '=') {
586 var ch3 = path[idx + 2];
587 if(ch3 === '=') {
588 if('=!^$*'.indexOf(ch1) >= 0) {
589 return {
590 type : TOKEN.PUNCT,
591 val : ch1 + ch2 + ch3,
592 range : [start, idx += 3]
593 };
594 }
595 }
596 else if('=!^$*><'.indexOf(ch1) >= 0) {
597 return {
598 type : TOKEN.PUNCT,
599 val : ch1 + ch2,
600 range : [start, idx += 2]
601 };
602 }
603 }
604
605 if(ch1 === ch2 && (ch1 === '|' || ch1 === '&')) {
606 return {
607 type : TOKEN.PUNCT,
608 val : ch1 + ch2,
609 range : [start, idx += 2]
610 };
611 }
612
613 if(':{}()[]^+-*/%!><|'.indexOf(ch1) >= 0) {
614 return {
615 type : TOKEN.PUNCT,
616 val : ch1,
617 range : [start, ++idx]
618 };
619 }
620 }
621
622 function throwUnexpected(token) {
623 if(token.type === TOKEN.EOP) {
624 throwError(token, MESSAGES.UNEXP_EOP);
625 }
626
627 throwError(token, MESSAGES.UNEXP_TOKEN, token.val);
628 }
629
630 function throwError(token, messageFormat) {
631 var args = Array.prototype.slice.call(arguments, 2),
632 msg = messageFormat.replace(
633 /%(\d)/g,
634 function(_, idx) {
635 return args[idx] || '';
636 }),
637 error = new Error(msg);
638
639 error.column = token.range[0];
640
641 throw error;
642 }
643
644 return parse;
645})();
646
647// translator
648
649var translate = (function() {
650
651 var body, vars, lastVarId, unusedVars;
652
653 function acquireVar() {
654 if(unusedVars.length) {
655 return unusedVars.shift();
656 }
657
658 var varName = 'v' + ++lastVarId;
659 vars.push(varName);
660 return varName;
661 }
662
663 function releaseVars() {
664 var args = arguments, i = args.length;
665 while(i--) {
666 unusedVars.push(args[i]);
667 }
668 }
669
670 function translate(ast) {
671 body = [];
672 vars = ['res'];
673 lastVarId = 0;
674 unusedVars = [];
675
676 translateExpr(ast, 'res', 'data');
677
678 body.unshift(
679 'var ',
680 Array.isArray?
681 'isArr = Array.isArray' :
682 'toStr = Object.prototype.toString, isArr = function(o) { return toStr.call(o) === "[object Array]"; }',
683 ', concat = Array.prototype.concat',
684 ',', vars.join(','), ';');
685
686 if(ast.type === SYNTAX.PATH) {
687 var lastPart = ast.parts[ast.parts.length - 1];
688 if(lastPart && lastPart.type === SYNTAX.POS_PRED && 'idx' in lastPart.arg) {
689 body.push('res = res[0];');
690 }
691 }
692
693 body.push('return res;');
694
695 return body.join('');
696 }
697
698 function translatePath(path, dest, ctx) {
699 var parts = path.parts,
700 i = 0, len = parts.length;
701
702 body.push(
703 dest, '=', path.fromRoot? 'data' : path.subst? 'subst.' + path.subst : ctx, ';',
704 'isArr(' + dest + ') || (' + dest + ' = [' + dest + ']);');
705
706 while(i < len) {
707 var item = parts[i++];
708 switch(item.type) {
709 case SYNTAX.SELECTOR:
710 item.selector === '..'?
711 translateDescendantSelector(item, dest, dest) :
712 translateSelector(item, dest, dest);
713 break;
714
715 case SYNTAX.OBJ_PRED:
716 translateObjectPredicate(item, dest, dest);
717 break;
718
719 case SYNTAX.POS_PRED:
720 translatePosPredicate(item, dest, dest);
721 break;
722
723 case SYNTAX.CONCAT_EXPR:
724 translateConcatExpr(item, dest, dest);
725 break;
726 }
727 }
728 }
729
730 function translateSelector(sel, dest, ctx) {
731 if(sel.prop) {
732 var propStr = escapeStr(sel.prop),
733 res = acquireVar(), i = acquireVar(), len = acquireVar(),
734 curCtx = acquireVar(),
735 j = acquireVar(), val = acquireVar(), tmpArr = acquireVar();
736
737 body.push(
738 res, '= [];', i, '= 0;', len, '=', ctx, '.length;', tmpArr, '= [];',
739 'while(', i, '<', len, ') {',
740 curCtx, '=', ctx, '[', i, '++];',
741 'if(', curCtx, '!= null) {');
742 if(sel.prop === '*') {
743 body.push(
744 'if(typeof ', curCtx, '=== "object") {',
745 'if(isArr(', curCtx, ')) {',
746 res, '=', res, '.concat(', curCtx, ');',
747 '}',
748 'else {',
749 'for(', j, ' in ', curCtx, ') {',
750 'if(', curCtx, '.hasOwnProperty(', j, ')) {',
751 val, '=', curCtx, '[', j, '];');
752 inlineAppendToArray(res, val);
753 body.push(
754 '}',
755 '}',
756 '}',
757 '}');
758 }
759 else {
760 body.push(
761 val, '=', curCtx, '[', propStr, '];');
762 inlineAppendToArray(res, val, tmpArr, len);
763 }
764 body.push(
765 '}',
766 '}',
767 dest, '=', len, '> 1 &&', tmpArr, '.length?', tmpArr, '.length > 1?',
768 'concat.apply(', res, ',', tmpArr, ') :', res, '.concat(', tmpArr, '[0]) :', res, ';');
769
770 releaseVars(res, i, len, curCtx, j, val, tmpArr);
771 }
772 }
773
774 function translateDescendantSelector(sel, dest, baseCtx) {
775 var prop = sel.prop,
776 ctx = acquireVar(), curCtx = acquireVar(), childCtxs = acquireVar(),
777 i = acquireVar(), j = acquireVar(), val = acquireVar(),
778 len = acquireVar(), res = acquireVar();
779
780 body.push(
781 ctx, '=', baseCtx, '.slice(),', res, '= [];',
782 'while(', ctx, '.length) {',
783 curCtx, '=', ctx, '.shift();');
784 prop?
785 body.push(
786 'if(typeof ', curCtx, '=== "object" &&', curCtx, ') {') :
787 body.push(
788 'if(typeof ', curCtx, '!= null) {');
789 body.push(
790 childCtxs, '= [];',
791 'if(isArr(', curCtx, ')) {',
792 i, '= 0,', len, '=', curCtx, '.length;',
793 'while(', i, '<', len, ') {',
794 val, '=', curCtx, '[', i, '++];');
795 prop && body.push(
796 'if(typeof ', val, '=== "object") {');
797 inlineAppendToArray(childCtxs, val);
798 prop && body.push(
799 '}');
800 body.push(
801 '}',
802 '}',
803 'else {');
804 if(prop) {
805 if(prop !== '*') {
806 body.push(
807 val, '=', curCtx, '["' + prop + '"];');
808 inlineAppendToArray(res, val);
809 }
810 }
811 else {
812 inlineAppendToArray(res, curCtx);
813 body.push(
814 'if(typeof ', curCtx, '=== "object") {');
815 }
816
817 body.push(
818 'for(', j, ' in ', curCtx, ') {',
819 'if(', curCtx, '.hasOwnProperty(', j, ')) {',
820 val, '=', curCtx, '[', j, '];');
821 inlineAppendToArray(childCtxs, val);
822 prop === '*' && inlineAppendToArray(res, val);
823 body.push(
824 '}',
825 '}');
826 prop || body.push(
827 '}');
828 body.push(
829 '}',
830 childCtxs, '.length &&', ctx, '.unshift.apply(', ctx, ',', childCtxs, ');',
831 '}',
832 '}',
833 dest, '=', res, ';');
834
835 releaseVars(ctx, curCtx, childCtxs, i, j, val, len, res);
836 }
837
838 function translateObjectPredicate(expr, dest, ctx) {
839 var resVar = acquireVar(), i = acquireVar(), len = acquireVar(),
840 cond = acquireVar(), curItem = acquireVar();
841
842 body.push(
843 resVar, '= [];',
844 i, '= 0;',
845 len, '=', ctx, '.length;',
846 'while(', i, '<', len, ') {',
847 curItem, '=', ctx, '[', i, '++];');
848 translateExpr(expr.arg, cond, curItem);
849 body.push(
850 convertToBool(expr.arg, cond), '&&', resVar, '.push(', curItem, ');',
851 '}',
852 dest, '=', resVar, ';');
853
854 releaseVars(resVar, i, len, curItem, cond);
855 }
856
857 function translatePosPredicate(item, dest, ctx) {
858 var arrayExpr = item.arg, fromIdx, toIdx;
859 if(arrayExpr.idx) {
860 var idx = acquireVar();
861 translateExpr(arrayExpr.idx, idx, ctx);
862 body.push(
863 idx, '< 0 && (', idx, '=', ctx, '.length +', idx, ');',
864 dest, '=', ctx, '[', idx, '] == null? [] : [', ctx, '[', idx, ']];');
865 releaseVars(idx);
866 return false;
867 }
868 else if(arrayExpr.fromIdx) {
869 if(arrayExpr.toIdx) {
870 translateExpr(arrayExpr.fromIdx, fromIdx = acquireVar(), ctx);
871 translateExpr(arrayExpr.toIdx, toIdx = acquireVar(), ctx);
872 body.push(dest, '=', ctx, '.slice(', fromIdx, ',', toIdx, ');');
873 releaseVars(fromIdx, toIdx);
874 }
875 else {
876 translateExpr(arrayExpr.fromIdx, fromIdx = acquireVar(), ctx);
877 body.push(dest, '=', ctx, '.slice(', fromIdx, ');');
878 releaseVars(fromIdx);
879 }
880 }
881 else {
882 translateExpr(arrayExpr.toIdx, toIdx = acquireVar(), ctx);
883 body.push(dest, '=', ctx, '.slice(0,', toIdx, ');');
884 releaseVars(toIdx);
885 }
886 }
887
888 function translateExpr(expr, dest, ctx) {
889 switch(expr.type) {
890 case SYNTAX.PATH:
891 translatePath(expr, dest, ctx);
892 break;
893
894 case SYNTAX.CONCAT_EXPR:
895 translateConcatExpr(expr, dest, ctx);
896 break;
897
898 case SYNTAX.COMPARISON_EXPR:
899 translateComparisonExpr(expr, dest, ctx);
900 break;
901
902 case SYNTAX.MATH_EXPR:
903 translateMathExpr(expr, dest, ctx);
904 break;
905
906 case SYNTAX.LOGICAL_EXPR:
907 translateLogicalExpr(expr, dest, ctx);
908 break;
909
910 case SYNTAX.UNARY_EXPR:
911 translateUnaryExpr(expr, dest, ctx);
912 break;
913
914 case SYNTAX.LITERAL:
915 var val = expr.val;
916 body.push(dest, '=', typeof val === 'string'? escapeStr(val) : val, ';');
917 break;
918 }
919 }
920
921 function translateComparisonExpr(expr, dest, ctx) {
922 var val1 = acquireVar(), val2 = acquireVar(),
923 isVal1Array = acquireVar(), isVal2Array = acquireVar(),
924 i = acquireVar(), j = acquireVar(),
925 len1 = acquireVar(), len2 = acquireVar(),
926 leftArg = expr.args[0], rightArg = expr.args[1];
927
928 body.push(dest, '= false;');
929
930 translateExpr(leftArg, val1, ctx);
931 translateExpr(rightArg, val2, ctx);
932
933 var isLeftArgPath = leftArg.type === SYNTAX.PATH,
934 isRightArgLiteral = rightArg.type === SYNTAX.LITERAL;
935
936 body.push(isVal1Array, '=');
937 isLeftArgPath? body.push('true;') : body.push('isArr(', val1, ');');
938
939 body.push(isVal2Array, '=');
940 isRightArgLiteral? body.push('false;') : body.push('isArr(', val2, ');');
941
942 body.push(
943 'if(');
944 isLeftArgPath || body.push(isVal1Array, '&&');
945 body.push(val1, '.length === 1) {',
946 val1, '=', val1, '[0];',
947 isVal1Array, '= false;',
948 '}');
949 isRightArgLiteral || body.push(
950 'if(', isVal2Array, '&&', val2, '.length === 1) {',
951 val2, '=', val2, '[0];',
952 isVal2Array, '= false;',
953 '}');
954
955 body.push(i, '= 0;',
956 'if(', isVal1Array, ') {',
957 len1, '=', val1, '.length;');
958
959 if(!isRightArgLiteral) {
960 body.push(
961 'if(', isVal2Array, ') {',
962 len2, '=', val2, '.length;',
963 'while(', i, '<', len1, '&& !', dest, ') {',
964 j, '= 0;',
965 'while(', j, '<', len2, ') {');
966 writeCondition(expr.op, [val1, '[', i, ']'].join(''), [val2, '[', j, ']'].join(''));
967 body.push(
968 dest, '= true;',
969 'break;',
970 '}',
971 '++', j, ';',
972 '}',
973 '++', i, ';',
974 '}',
975 '}',
976 'else {');
977 }
978 body.push(
979 'while(', i, '<', len1, ') {');
980 writeCondition(expr.op, [val1, '[', i, ']'].join(''), val2);
981 body.push(
982 dest, '= true;',
983 'break;',
984 '}',
985 '++', i, ';',
986 '}');
987
988 isRightArgLiteral || body.push(
989 '}');
990
991 body.push(
992 '}');
993
994 if(!isRightArgLiteral) {
995 body.push(
996 'else if(', isVal2Array,') {',
997 len2, '=', val2, '.length;',
998 'while(', i, '<', len2, ') {');
999 writeCondition(expr.op, val1, [val2, '[', i, ']'].join(''));
1000 body.push(
1001 dest, '= true;',
1002 'break;',
1003 '}',
1004 '++', i, ';',
1005 '}',
1006 '}');
1007 }
1008
1009 body.push(
1010 'else {',
1011 dest, '=', binaryOperators[expr.op](val1, val2), ';',
1012 '}');
1013
1014 releaseVars(val1, val2, isVal1Array, isVal2Array, i, j, len1, len2);
1015 }
1016
1017 function writeCondition(op, val1Expr, val2Expr) {
1018 body.push('if(', binaryOperators[op](val1Expr, val2Expr), ') {');
1019 }
1020
1021 function translateLogicalExpr(expr, dest, ctx) {
1022 var conditionVars = [],
1023 args = expr.args, len = args.length,
1024 i = 0, val;
1025
1026 body.push(dest, '= false;');
1027 switch(expr.op) {
1028 case '&&':
1029 while(i < len) {
1030 conditionVars.push(val = acquireVar());
1031 translateExpr(args[i], val, ctx);
1032 body.push('if(', convertToBool(args[i++], val), ') {');
1033 }
1034 body.push(dest, '= true;');
1035 break;
1036
1037 case '||':
1038 while(i < len) {
1039 conditionVars.push(val = acquireVar());
1040 translateExpr(args[i], val, ctx);
1041 body.push(
1042 'if(', convertToBool(args[i], val), ') {',
1043 dest, '= true;',
1044 '}');
1045 if(i++ + 1 < len) {
1046 body.push('else {');
1047 }
1048 }
1049 --len;
1050 break;
1051 }
1052
1053 while(len--) {
1054 body.push('}');
1055 }
1056
1057 releaseVars.apply(null, conditionVars);
1058 }
1059
1060 function translateMathExpr(expr, dest, ctx) {
1061 var val1 = acquireVar(),
1062 val2 = acquireVar(),
1063 args = expr.args;
1064
1065 translateExpr(args[0], val1, ctx);
1066 translateExpr(args[1], val2, ctx);
1067
1068 body.push(
1069 dest, '=',
1070 binaryOperators[expr.op](
1071 convertToSingleValue(args[0], val1),
1072 convertToSingleValue(args[1], val2)),
1073 ';');
1074
1075 releaseVars(val1, val2);
1076 }
1077
1078 function translateUnaryExpr(expr, dest, ctx) {
1079 var val = acquireVar(),
1080 arg = expr.arg;
1081
1082 translateExpr(arg, val, ctx);
1083
1084 switch(expr.op) {
1085 case '!':
1086 body.push(dest, '= !', convertToBool(arg, val) + ';');
1087 break;
1088
1089 case '-':
1090 body.push(dest, '= -', convertToSingleValue(arg, val) + ';');
1091 break;
1092 }
1093
1094 releaseVars(val);
1095 }
1096
1097 function translateConcatExpr(expr, dest, ctx) {
1098 var argVars = [],
1099 args = expr.args,
1100 len = args.length,
1101 i = 0;
1102
1103 while(i < len) {
1104 argVars.push(acquireVar());
1105 translateExpr(args[i], argVars[i++], ctx);
1106 }
1107
1108 body.push(dest, '= concat.call(', argVars.join(','), ');');
1109
1110 releaseVars.apply(null, argVars);
1111 }
1112
1113 function escapeStr(s) {
1114 return '\'' + s.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + '\'';
1115 }
1116
1117 function inlineAppendToArray(res, val, tmpArr, len) {
1118 body.push(
1119 'if(', val, '!= null) {',
1120 'if(isArr(', val, ')) {');
1121 if(tmpArr) {
1122 body.push(
1123 len, '> 1?');
1124 inlinePushToArray(tmpArr, val);
1125 body.push(
1126 ':');
1127 }
1128 body.push(
1129 res, '=', res, '.length?', res, '.concat(', val, ') :', val, '.slice()', ';',
1130 '}',
1131 'else {');
1132 tmpArr && body.push(
1133 'if(', tmpArr, '.length) {',
1134 res, '= concat.apply(', res, ',', tmpArr, ');',
1135 tmpArr, '= [];',
1136 '}');
1137 inlinePushToArray(res, val);
1138 body.push(';',
1139 '}',
1140 '}');
1141 }
1142
1143 function inlinePushToArray(res, val) {
1144 body.push(res, '.length?', res, '.push(', val, ') :', res, '[0] =', val);
1145 }
1146
1147 function convertToBool(arg, varName) {
1148 switch(arg.type) {
1149 case SYNTAX.LOGICAL_EXPR:
1150 return varName;
1151
1152 case SYNTAX.LITERAL:
1153 return '!!' + varName;
1154
1155 case SYNTAX.PATH:
1156 return varName + '.length > 0';
1157
1158 default:
1159 return ['(typeof ', varName, '=== "boolean"?',
1160 varName, ':',
1161 'isArr(', varName, ')?', varName, '.length > 0 : !!', varName, ')'].join('');
1162 }
1163 }
1164
1165 function convertToSingleValue(arg, varName) {
1166 switch(arg.type) {
1167 case SYNTAX.LITERAL:
1168 return varName;
1169
1170 case SYNTAX.PATH:
1171 return varName + '[0]';
1172
1173 default:
1174 return ['(isArr(', varName, ')?', varName, '[0] : ', varName, ')'].join('');
1175 }
1176 }
1177
1178 var binaryOperators = {
1179 '===' : function(val1, val2) {
1180 return val1 + '===' + val2;
1181 },
1182
1183 '==' : function(val1, val2) {
1184 return ['typeof ', val1, '=== "string" && typeof ', val2, '=== "string"?',
1185 val1, '.toLowerCase() ===', val2, '.toLowerCase() :' +
1186 val1, '==', val2].join('');
1187 },
1188
1189 '>=' : function(val1, val2) {
1190 return val1 + '>=' + val2;
1191 },
1192
1193 '>' : function(val1, val2) {
1194 return val1 + '>' + val2;
1195 },
1196
1197 '<=' : function(val1, val2) {
1198 return val1 + '<=' + val2;
1199 },
1200
1201 '<' : function(val1, val2) {
1202 return val1 + '<' + val2;
1203 },
1204
1205 '!==' : function(val1, val2) {
1206 return val1 + '!==' + val2;
1207 },
1208
1209 '!=' : function(val1, val2) {
1210 return val1 + '!=' + val2;
1211 },
1212
1213 '^==' : function(val1, val2) {
1214 return ['typeof ', val1, '=== "string" && typeof ', val2, '=== "string" &&',
1215 val1, '.indexOf(', val2, ') === 0'].join('');
1216 },
1217
1218 '^=' : function(val1, val2) {
1219 return [val1, '!= null &&', val2, '!= null &&',
1220 val1, '.toString().toLowerCase().indexOf(', val2, '.toString().toLowerCase()) === 0'].join('');
1221 },
1222
1223 '$==' : function(val1, val2) {
1224 return ['typeof ', val1, '=== "string" && typeof ', val2, '=== "string" &&',
1225 val1, '.length >=', val2, '.length &&',
1226 val1, '.lastIndexOf(', val2, ') ===', val1, '.length -', val2, '.length'].join('');
1227 },
1228
1229 '$=' : function(val1, val2) {
1230 return [val1, '!= null &&', val2, '!= null &&',
1231 '(', val1, '=', val1, '.toString()).length >=', '(', val2, '=', val2, '.toString()).length &&',
1232 '(', val1, '.toLowerCase()).lastIndexOf(', '(', val2, '.toLowerCase())) ===',
1233 val1, '.length -', val2, '.length'].join('');
1234 },
1235
1236 '*==' : function(val1, val2) {
1237 return ['typeof ', val1, '=== "string" && typeof ', val2, '=== "string" &&',
1238 val1, '.indexOf(', val2, ') > -1'].join('');
1239 },
1240
1241 '*=' : function(val1, val2) {
1242 return [val1, '!= null && ', val2, '!= null &&',
1243 val1, '.toString().toLowerCase().indexOf(', val2, '.toString().toLowerCase()) > -1'].join('');
1244 },
1245
1246 '+' : function(val1, val2) {
1247 return val1 + '+' + val2;
1248 },
1249
1250 '-' : function(val1, val2) {
1251 return val1 + '-' + val2;
1252 },
1253
1254 '*' : function(val1, val2) {
1255 return val1 + '*' + val2;
1256 },
1257
1258 '/' : function(val1, val2) {
1259 return val1 + '/' + val2;
1260 },
1261
1262 '%' : function(val1, val2) {
1263 return val1 + '%' + val2;
1264 }
1265 };
1266
1267 return translate;
1268})();
1269
1270function compile(path) {
1271 return Function('data,subst', translate(parse(path)));
1272}
1273
1274var cache = {},
1275 cacheKeys = [],
1276 params = {
1277 cacheSize : 100
1278 },
1279 setParamsHooks = {
1280 cacheSize : function(oldVal, newVal) {
1281 if(newVal < oldVal && cacheKeys.length > newVal) {
1282 var removedKeys = cacheKeys.splice(0, cacheKeys.length - newVal),
1283 i = removedKeys.length;
1284
1285 while(i--) {
1286 delete cache[removedKeys[i]];
1287 }
1288 }
1289 }
1290 };
1291
1292var decl = function(path, ctx, substs) {
1293 if(!cache[path]) {
1294 cache[path] = compile(path);
1295 if(cacheKeys.push(path) > params.cacheSize) {
1296 delete cache[cacheKeys.shift()];
1297 }
1298 }
1299
1300 return cache[path](ctx, substs || {});
1301};
1302
1303decl.version = '0.3.4';
1304
1305decl.params = function(_params) {
1306 if(!arguments.length) {
1307 return params;
1308 }
1309
1310 for(var name in _params) {
1311 if(_params.hasOwnProperty(name)) {
1312 setParamsHooks[name] && setParamsHooks[name](params[name], _params[name]);
1313 params[name] = _params[name];
1314 }
1315 }
1316};
1317
1318decl.compile = compile;
1319
1320decl.apply = decl;
1321
1322if(typeof module === 'object' && typeof module.exports === 'object') {
1323 module.exports = decl;
1324}
1325else if(typeof modules === 'object') {
1326 modules.define('jspath', function(provide) {
1327 provide(decl);
1328 });
1329}
1330else if(typeof define === 'function') {
1331 define(function(require, exports, module) {
1332 module.exports = decl;
1333 });
1334}
1335else {
1336 JSPath = decl;
1337}
1338
1339})();