1 | import { isPromiseLike, PromiseStatus } from './promiseHelpers';
|
2 | import { Binding, PromiseBinding } from './binder';
|
3 | import * as formatters from './formatters';
|
4 | import { module } from './helpers';
|
5 | import { FormatterFactory } from './formatters/common';
|
6 |
|
7 |
|
8 | var jsonKeyRegex = /^ *(?:(?:"([^"]+)")|(?:'([^']+)')|(?:([^\: ]+)) *): */;
|
9 |
|
10 |
|
11 | export interface ParsedAny
|
12 | {
|
13 | $$length?: number;
|
14 | }
|
15 |
|
16 | export type ParsedOneOf = ParsedObject | ParsedArray | ParsedFunction | ParsedString | ParsedBoolean | ParsedNumber;
|
17 |
|
18 | export class ParsedBinary implements ParsedAny
|
19 | {
|
20 | constructor(public operator: '+' | '-' | '*' | '/' | '&&' | '||' | '<' | '<=' | '>' | '>=' | '.', public left: ParsedOneOf, public right: ParsedOneOf)
|
21 | {
|
22 | this.$$length = this.left.$$length + this.operator.length + this.right.$$length;
|
23 | }
|
24 |
|
25 | public evaluate(value: any, asBinding?: boolean)
|
26 | {
|
27 | var operation = this;
|
28 | if (asBinding)
|
29 | {
|
30 | var left, right;
|
31 | if (operation.left instanceof Function)
|
32 | left = operation.left(value, asBinding);
|
33 | else if (operation.left instanceof ParsedBinary)
|
34 | left = operation.left.evaluate(value, asBinding);
|
35 | else if (operation.left instanceof ParsedString)
|
36 | left = operation.left.value;
|
37 | else if (operation.left instanceof ParsedNumber)
|
38 | left = operation.left.value;
|
39 | else if (operation.left instanceof Array)
|
40 | left = operation.left;
|
41 | else if (operation.left instanceof Object)
|
42 | left = operation.left;
|
43 |
|
44 | if (operation.right instanceof Function)
|
45 | right = operation.right(value, asBinding);
|
46 | else if (operation.right instanceof ParsedBinary)
|
47 | right = operation.right.evaluate(value, asBinding);
|
48 | else if (operation.right instanceof ParsedString)
|
49 | right = operation.right.value;
|
50 | else if (operation.right instanceof ParsedNumber)
|
51 | right = operation.right.value;
|
52 | else if (operation.right instanceof Array)
|
53 | right = operation.right;
|
54 | else if (operation.right instanceof Object)
|
55 | right = operation.right
|
56 |
|
57 | var binding = new Binding(null, null, false);
|
58 | if (left instanceof Binding)
|
59 | left.pipe(binding);
|
60 | if (right instanceof Binding)
|
61 | right.pipe(binding);
|
62 | binding['$$length'] = operation.$$length;
|
63 | binding.getValue = function ()
|
64 | {
|
65 | var fleft, fright;
|
66 | if (left instanceof Binding)
|
67 | fleft = left.getValue();
|
68 | else
|
69 | fleft = left;
|
70 | if (right instanceof Binding)
|
71 | fright = right.getValue();
|
72 | else
|
73 | fright = right;
|
74 | return Parser.operate(operation.operator, fleft, fright);
|
75 | }
|
76 | return binding;
|
77 | }
|
78 | else
|
79 | {
|
80 | var left, right;
|
81 | if (operation.left instanceof Function)
|
82 | left = operation.left(value, false);
|
83 | else if (operation.left instanceof ParsedBinary)
|
84 | left = operation.left.evaluate(value, asBinding);
|
85 | else if (operation.left instanceof ParsedString)
|
86 | left = operation.left.value;
|
87 | else if (operation.left instanceof ParsedNumber)
|
88 | left = operation.left.value;
|
89 | else if (operation.left instanceof Array)
|
90 | left = operation.left;
|
91 | else if (operation.left instanceof Object)
|
92 | left = operation.left;
|
93 |
|
94 | if (operation.right instanceof Function)
|
95 | right = operation.right(value, false);
|
96 | else if (operation.right instanceof ParsedBinary)
|
97 | right = operation.right.evaluate(value, asBinding);
|
98 | else if (operation.right instanceof ParsedString)
|
99 | right = operation.right.value;
|
100 | else if (operation.right instanceof ParsedNumber)
|
101 | right = operation.right.value;
|
102 | else if (operation.right instanceof Array)
|
103 | right = operation.right;
|
104 | else if (operation.right instanceof Object)
|
105 | right = operation.right;
|
106 | return <any>Parser.operate(operation.operator, left, right);
|
107 | }
|
108 | }
|
109 |
|
110 | public $$length: number;
|
111 |
|
112 | public static applyPrecedence(operation: ParsedBinary)
|
113 | {
|
114 | if (operation.operator != '+' && operation.operator != '-')
|
115 | {
|
116 | if (operation.right instanceof Function && operation.right.$$ast)
|
117 | {
|
118 | var right = ParsedBinary.applyPrecedence(operation.right.$$ast);
|
119 | switch (right.operator)
|
120 | {
|
121 | case '+':
|
122 | case '-':
|
123 | break;
|
124 | case '*':
|
125 | case '/':
|
126 | case '&&':
|
127 | case '||':
|
128 | case '.':
|
129 | var left = operation.left;
|
130 | operation.right = right.right;
|
131 | operation.left = new ParsedBinary(operation.operator, left, right.left);
|
132 | operation.operator = right.operator;
|
133 | break;
|
134 | }
|
135 | }
|
136 | }
|
137 | return operation;
|
138 | }
|
139 |
|
140 | public toString()
|
141 | {
|
142 | return '(' + this.left.toString() + this.operator + this.right.toString() + ')';
|
143 | }
|
144 | }
|
145 |
|
146 | export interface ParsedObject extends ParsedAny
|
147 | {
|
148 | [name: string]: any;
|
149 | }
|
150 |
|
151 | export interface ParsedArray extends ParsedAny, Array<ParsedAny>
|
152 | {
|
153 | }
|
154 |
|
155 | export interface ParsedFunction extends ParsedAny
|
156 | {
|
157 | $$ast?: ParsedBinary;
|
158 | (value: any, asBinding?: false): any;
|
159 | (value: any, asBinding: true): Binding;
|
160 | (value: any, asBinding?: boolean): any;
|
161 | }
|
162 |
|
163 | export class ParsedString implements ParsedAny
|
164 | {
|
165 | constructor(public value: string)
|
166 | {
|
167 | this.$$length = value.length + 2;
|
168 | }
|
169 |
|
170 | public $$length: number;
|
171 |
|
172 | public toString()
|
173 | {
|
174 | return this.value;
|
175 | }
|
176 | }
|
177 |
|
178 | export class ParsedNumber implements ParsedAny
|
179 | {
|
180 | constructor(value: string)
|
181 | {
|
182 | this.value = Number(value);
|
183 | this.$$length = value.length;
|
184 | }
|
185 |
|
186 | public value: number;
|
187 |
|
188 | public $$length: number;
|
189 | }
|
190 |
|
191 | export class ParsedBoolean implements ParsedAny
|
192 | {
|
193 | constructor(value: string)
|
194 | {
|
195 | this.value = Boolean(value);
|
196 | if (typeof value != 'undefined')
|
197 | this.$$length = value.toString().length;
|
198 | }
|
199 |
|
200 | public value: boolean;
|
201 |
|
202 | public $$length: number;
|
203 | }
|
204 |
|
205 | export class Parser
|
206 | {
|
207 | public static parse(expression: string, excludeFirstLevelFunction: false | undefined): ParsedFunction
|
208 | public static parse(expression: string, excludeFirstLevelFunction: true): ParsedOneOf
|
209 | public static parse(expression: string, excludeFirstLevelFunction?: boolean): ParsedFunction | ParsedOneOf
|
210 | public static parse(expression: string, excludeFirstLevelFunction?: boolean): ParsedFunction | ParsedOneOf
|
211 | {
|
212 | expression = expression.trim();
|
213 | var result = Parser.parseAny(expression, excludeFirstLevelFunction);
|
214 | if (!excludeFirstLevelFunction && result instanceof ParsedBinary)
|
215 | return result.evaluate.bind(result);
|
216 | return result;
|
217 | }
|
218 |
|
219 | public static parseAny(expression: string, excludeFirstLevelFunction: boolean): ParsedOneOf
|
220 | {
|
221 | switch (expression[0])
|
222 | {
|
223 | case '{':
|
224 | return Parser.parseObject(expression, excludeFirstLevelFunction);
|
225 | case '[':
|
226 | return Parser.parseArray(expression, excludeFirstLevelFunction);
|
227 | case '"':
|
228 | case "'":
|
229 | return Parser.parseString(expression, expression[0]);
|
230 | case '0':
|
231 | case '1':
|
232 | case '2':
|
233 | case '3':
|
234 | case '4':
|
235 | case '5':
|
236 | case '6':
|
237 | case '7':
|
238 | case '8':
|
239 | case '9':
|
240 | case '.':
|
241 | return Parser.parseNumber(expression);
|
242 | default:
|
243 | return Parser.parseEval(expression);
|
244 | }
|
245 | }
|
246 |
|
247 | public static parseNumber(expression): ParsedOneOf
|
248 | {
|
249 | var result = new ParsedNumber(/^[0-9.]/.exec(expression)[0]);
|
250 |
|
251 | return Parser.tryParseOperator(expression.substring(result.$$length), result);
|
252 | }
|
253 |
|
254 | public static parseBoolean(expression): ParsedBoolean
|
255 | {
|
256 | var formatter: (o: any) => any = formatters.identity;
|
257 | if (expression[0] == '!')
|
258 | {
|
259 | formatter = formatters.negate;
|
260 | expression = expression.substring(1);
|
261 | }
|
262 | if (expression[0] == '!')
|
263 | {
|
264 | formatter = formatters.booleanize;
|
265 | expression = expression.substring(1);
|
266 | }
|
267 |
|
268 | if (/^true|false|undefined/.exec(expression))
|
269 | {
|
270 | var result = new ParsedBoolean(/^true|false|undefined/.exec(expression)[0]);
|
271 | if (formatter !== formatters.identity)
|
272 | result.value = formatter(result.value);
|
273 | return result;
|
274 | }
|
275 | return null;
|
276 | }
|
277 |
|
278 | public static parseEval(expression: string): ParsedBoolean | ParsedFunction | ParsedBinary
|
279 | {
|
280 | var b = Parser.parseBoolean(expression);
|
281 | if (b)
|
282 | return b;
|
283 |
|
284 | return Parser.parseFunction(expression);
|
285 | }
|
286 |
|
287 | public static parseFunction(expression: string): ParsedFunction | ParsedBinary
|
288 | {
|
289 | var length = 0;
|
290 | var formatter: (o: any) => any = formatters.identity;
|
291 | if (expression[0] == '!')
|
292 | {
|
293 | formatter = formatters.negate;
|
294 | expression = expression.substring(1);
|
295 | length++;
|
296 | }
|
297 | if (expression[0] == '!')
|
298 | {
|
299 | formatter = formatters.booleanize;
|
300 | expression = expression.substring(1);
|
301 | length++;
|
302 | }
|
303 |
|
304 | var item = /^[\w0-9\.\$]*/.exec(expression)[0];
|
305 | length += item.length;
|
306 | var parts = Parser.parseBindable(item);
|
307 |
|
308 | var f: ParsedFunction = function (value, asBinding?: boolean)
|
309 | {
|
310 | if (asBinding)
|
311 | {
|
312 | if (isPromiseLike(value))
|
313 | {
|
314 | var binding = new PromiseBinding(item, value);
|
315 | binding['$$length'] = item.length;
|
316 | binding.formatter = formatter;
|
317 | return binding;
|
318 | }
|
319 | var binding = new Binding(item, value);
|
320 | binding['$$length'] = item.length;
|
321 | binding.formatter = formatter;
|
322 | return binding;
|
323 | }
|
324 |
|
325 | if (parts.length >= 1 && parts[0] != '')
|
326 | for (var i = 0; i < parts.length && value; i++)
|
327 | {
|
328 | value = value[parts[i]];
|
329 | if (isPromiseLike(value))
|
330 | {
|
331 | var promise: PromiseLike<any>;
|
332 | if (i == parts.length - 1)
|
333 | promise = value;
|
334 | else
|
335 | promise = value.then(<ParsedFunction>Parser.parseFunction(parts.slice(i + 1).join('.'))).then(formatter);
|
336 | promise['$$length'] = item.length;
|
337 | return promise;
|
338 | }
|
339 | }
|
340 | return value;
|
341 | }
|
342 | f.$$length = length;
|
343 | f = Parser.tryParseOperator(expression.substr(item.length), f);
|
344 |
|
345 | return f;
|
346 | }
|
347 |
|
348 | public static parseFormatter(expression: string, lhs: ParsedOneOf): ParsedOneOf
|
349 | {
|
350 | var item = /^ *# *([\w0-9\.\$]+) */.exec(expression);
|
351 | expression = expression.substring(item[0].length);
|
352 | var formatter: FormatterFactory<any, any> = module('$formatters').resolve('#' + item[1]);
|
353 | if (!formatter)
|
354 | throw new Error(`filter not found: ${item[1]}`)
|
355 | var settings: ParsedObject;
|
356 | if (expression[0] == ':')
|
357 | {
|
358 | settings = formatter.parse(expression.substring(1));
|
359 | }
|
360 |
|
361 | var result: ParsedFunction = function (value, asBinding?: boolean)
|
362 | {
|
363 | var left;
|
364 | if (lhs instanceof Function)
|
365 | left = lhs(value, asBinding);
|
366 | else if (lhs instanceof ParsedBinary)
|
367 | left = lhs.evaluate(value, asBinding);
|
368 | else if (lhs instanceof ParsedString)
|
369 | left = lhs.value;
|
370 | else if (lhs instanceof ParsedNumber)
|
371 | left = lhs.value;
|
372 | else if (lhs instanceof Array)
|
373 | left = lhs;
|
374 | else if (lhs instanceof Object)
|
375 | left = lhs;
|
376 |
|
377 | if (asBinding)
|
378 | {
|
379 | if (left instanceof Binding)
|
380 | {
|
381 | left.formatter = formatter.build(left.formatter, settings);
|
382 | return left;
|
383 | }
|
384 | else
|
385 | {
|
386 | var b = new Binding('', left);
|
387 | b.formatter = formatter.build(formatters.identity, settings);
|
388 | return b;
|
389 | }
|
390 | }
|
391 | else
|
392 | {
|
393 | if (left instanceof Binding)
|
394 | {
|
395 | left.formatter = formatter.build(left.formatter, settings);
|
396 | return left.getValue();
|
397 | }
|
398 | else
|
399 | {
|
400 | return formatter.build(formatters.identity, settings)(left);
|
401 | }
|
402 | }
|
403 | }
|
404 | // console.log({ lhs: lhs.$$length, item0: item[0].length, settings: settings && settings.$$length })
|
405 | result.$$length = lhs.$$length + item[0].length + ((settings && settings.$$length + 1) || 0);
|
406 | // console.log(result.$$length);
|
407 | return result;
|
408 | }
|
409 |
|
410 | public static tryParseOperator(expression: string, lhs: ParsedFunction): ParsedFunction
|
411 | public static tryParseOperator(expression: string, lhs: ParsedOneOf): ParsedOneOf
|
412 | public static tryParseOperator(expression: string, lhs: ParsedOneOf)
|
413 | {
|
414 | var operator = /^ *([<>=!\+\-\/\*&\|\.#]+) */.exec(expression);
|
415 | if (operator)
|
416 | {
|
417 | switch (operator[1])
|
418 | {
|
419 | case '#':
|
420 | return Parser.parseFormatter(expression, lhs);
|
421 | case '.':
|
422 | default:
|
423 | expression = expression.substring(operator[0].length);
|
424 | var rhs = Parser.parseAny(expression, false);
|
425 | var binary = new ParsedBinary(<any>operator[1], lhs, rhs)
|
426 | binary.$$length = lhs.$$length + operator[0].length + rhs.$$length;
|
427 | return ParsedBinary.applyPrecedence(binary);
|
428 | }
|
429 | }
|
430 | else
|
431 | return lhs;
|
432 | }
|
433 |
|
434 | public static parseArray(expression: string, excludeFirstLevelFunction?: boolean): ParsedArray | ParsedFunction
|
435 | {
|
436 | var results: ParsedArray = [];
|
437 | Object.defineProperty(results, '$$length', { value: 0, enumerable: false, configurable: true, writable: true });
|
438 | var isFunction = false;
|
439 | return Parser.parseCSV(expression, function (result)
|
440 | {
|
441 | var item = Parser.parseAny(result, false);
|
442 | item = Parser.tryParseOperator(result.substring(item.$$length), item);
|
443 |
|
444 |
|
445 | if (item instanceof ParsedBoolean || item instanceof ParsedString || item instanceof ParsedNumber)
|
446 | results.push(item);
|
447 | else if (item instanceof ParsedBinary)
|
448 | results.push(item.evaluate.bind(item));
|
449 | else
|
450 | results.push(item);
|
451 | results.$$length += item.$$length;
|
452 | return item;
|
453 | }, ']', results, excludeFirstLevelFunction);
|
454 | }
|
455 |
|
456 | public static parseString(expression: string, start: string): ParsedOneOf
|
457 | {
|
458 | var evaluatedRegex = new RegExp("^" + start + "((?:[^\\" + start + "]|\\.)+)" + start).exec(expression);
|
459 | // console.log(arguments);
|
460 | var result = evaluatedRegex[1];
|
461 | var parsedString = new ParsedString(result);
|
462 | return Parser.tryParseOperator(expression.substring(evaluatedRegex[0].length), parsedString);
|
463 | }
|
464 |
|
465 | public static operate(operator: string, left?: any, right?: any)
|
466 | {
|
467 | // if (arguments.length == 1)
|
468 | // return function (left: any, right: any)
|
469 | // {
|
470 | // return Parser.operate(operator, left, right);
|
471 | // }
|
472 | switch (operator)
|
473 | {
|
474 | case '==':
|
475 | return left == right;
|
476 | case '===':
|
477 | return left === right;
|
478 | case '<':
|
479 | return left < right;
|
480 | case '<=':
|
481 | return left <= right;
|
482 | case '>':
|
483 | return left > right;
|
484 | case '>=':
|
485 | return left >= right;
|
486 | case '!=':
|
487 | return left != right;
|
488 | case '!==':
|
489 | return left !== right;
|
490 | case '+':
|
491 | return left + right;
|
492 | case '-':
|
493 | return left - right;
|
494 | case '/':
|
495 | return left / right;
|
496 | case '*':
|
497 | return left * right;
|
498 | case '||':
|
499 | return left || right;
|
500 | case '&&':
|
501 | return left && right;
|
502 | case '.':
|
503 | if (right instanceof Function)
|
504 | return right(left);
|
505 | return left[right];
|
506 | default:
|
507 | throw new Error('invalid operator' + operator);
|
508 | }
|
509 | }
|
510 |
|
511 | private static parseCSV<T extends ParsedArray | ParsedObject>(expression: string, parseItem: (expression: string) => ParsedAny, end: string, output: T, excludeFirstLevelFunction: boolean): ParsedFunction | T
|
512 | {
|
513 | expression = expression.substring(1);
|
514 | output.$$length++;
|
515 | var isFunction = false;
|
516 | do
|
517 | {
|
518 | var item = parseItem(expression);
|
519 |
|
520 | if (item instanceof Function || item instanceof ParsedBinary)
|
521 | isFunction = true;
|
522 |
|
523 | expression = expression.substring(item.$$length);
|
524 | var next = /^ *, */.exec(expression);
|
525 | // console.log(expression)
|
526 | if (!next)
|
527 | break;
|
528 | expression = expression.substring(next[0].length);
|
529 | // console.log(expression);
|
530 | output.$$length += next[0].length;
|
531 | }
|
532 | while (expression[0] != end);
|
533 | output.$$length += end.length;
|
534 | // console.log(output.$$length);
|
535 | var result: any;
|
536 | if (output instanceof Array)
|
537 | result = [];
|
538 | else
|
539 | result = {};
|
540 | if (isFunction && !excludeFirstLevelFunction)
|
541 | {
|
542 | var f: ParsedFunction = function (value, asBinding: boolean)
|
543 | {
|
544 | for (var i in output)
|
545 | {
|
546 | if ((<any>output[i]) instanceof Function)
|
547 | result[i] = (<Function><any>output[i])(value, asBinding);
|
548 | else
|
549 | result[i] = output[i];
|
550 | }
|
551 | return result;
|
552 | }
|
553 | f.$$length = output.$$length;
|
554 | return f;
|
555 | }
|
556 | else
|
557 | return output;
|
558 | }
|
559 |
|
560 | public static parseObject(expression: string, excludeFirstLevelFunction?: boolean)
|
561 | {
|
562 | var keyMatch: RegExpExecArray;
|
563 | var parsedObject: ParsedObject = {};
|
564 | Object.defineProperty(parsedObject, '$$length', { value: 0, enumerable: false, writable: true, configurable: true });
|
565 | var result = Parser.parseCSV(expression, function (expression)
|
566 | {
|
567 | // var length = 0;
|
568 | var keyMatch = jsonKeyRegex.exec(expression);
|
569 |
|
570 | var key = keyMatch[1] || keyMatch[2] || keyMatch[3];
|
571 | //console.log(keyMatch);
|
572 | var length = keyMatch[0].length + keyMatch.index;
|
573 | expression = expression.substring(length);
|
574 | var item = Parser.parseAny(expression, false);
|
575 | length += item.$$length;
|
576 | if (item instanceof ParsedBoolean || item instanceof ParsedString || item instanceof ParsedNumber)
|
577 | parsedObject[key] = item.value;
|
578 | else if (item instanceof ParsedBinary)
|
579 | parsedObject[key] = item.evaluate.bind(item);
|
580 | else
|
581 | parsedObject[key] = item;
|
582 | // expression = expression.substring(result[key].$$length);
|
583 | item.$$length = length;
|
584 | parsedObject.$$length += length;
|
585 | // console.log(expression);
|
586 | //console.log(length);
|
587 | return item;
|
588 | }, '}', parsedObject, excludeFirstLevelFunction);
|
589 |
|
590 | return this.tryParseOperator(expression.substring(result.$$length), result)
|
591 | }
|
592 |
|
593 | public static parseBindable(expression: string)
|
594 | {
|
595 | return expression.split('.');
|
596 | }
|
597 |
|
598 | public static getSetter(expression: string, root: any)
|
599 | {
|
600 | var target = root;
|
601 | var parts = Parser.parseBindable(expression);
|
602 |
|
603 | while (parts.length > 1 && typeof (target) != 'undefined')
|
604 | {
|
605 | target = Parser.eval(parts[0], target);
|
606 | parts.shift();
|
607 | }
|
608 | if (typeof (target) == 'undefined')
|
609 | return null;
|
610 |
|
611 | return { expression: parts[0], target: target, set: function (value) { target[parts[0]] = value } };
|
612 | }
|
613 |
|
614 | public static evalAsFunction(expression: string, excludeFirstLevelFunction?: boolean): ParsedFunction
|
615 | {
|
616 | if (!expression && typeof (expression) != 'string')
|
617 | return null;
|
618 | var parts = Parser.parse(expression, excludeFirstLevelFunction);
|
619 | if (parts instanceof Array)
|
620 | return Parser.parseFunction(expression) as ParsedFunction;
|
621 |
|
622 | return <ParsedFunction>parts;
|
623 | }
|
624 |
|
625 | public static eval(expression: string, value: any)
|
626 | {
|
627 | return (Parser.evalAsFunction(expression, false) as ParsedFunction)(value, false);
|
628 | }
|
629 | } |
\ | No newline at end of file |