1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | (function() {
|
14 |
|
15 | var 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 |
|
30 |
|
31 | var 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 |
|
648 |
|
649 | var 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 |
|
1270 | function compile(path) {
|
1271 | return Function('data,subst', translate(parse(path)));
|
1272 | }
|
1273 |
|
1274 | var 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 |
|
1292 | var 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 |
|
1303 | decl.version = '0.3.4';
|
1304 |
|
1305 | decl.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 |
|
1318 | decl.compile = compile;
|
1319 |
|
1320 | decl.apply = decl;
|
1321 |
|
1322 | if(typeof module === 'object' && typeof module.exports === 'object') {
|
1323 | module.exports = decl;
|
1324 | }
|
1325 | else if(typeof modules === 'object') {
|
1326 | modules.define('jspath', function(provide) {
|
1327 | provide(decl);
|
1328 | });
|
1329 | }
|
1330 | else if(typeof define === 'function') {
|
1331 | define(function(require, exports, module) {
|
1332 | module.exports = decl;
|
1333 | });
|
1334 | }
|
1335 | else {
|
1336 | JSPath = decl;
|
1337 | }
|
1338 |
|
1339 | })();
|