UNPKG

22.2 kBPlain TextView Raw
1import { isPromiseLike, PromiseStatus } from './promiseHelpers';
2import { Binding, PromiseBinding } from './binder';
3import * as formatters from './formatters';
4import { module } from './helpers';
5import { FormatterFactory } from './formatters/common';
6
7
8var jsonKeyRegex = /^ *(?:(?:"([^"]+)")|(?:'([^']+)')|(?:([^\: ]+)) *): */;
9// var jsonSingleQuoteKeyRegex = /^ *'([^']+)'|([^\: ]+) *: */;
10
11export interface ParsedAny
12{
13 $$length?: number;
14}
15
16export type ParsedOneOf = ParsedObject | ParsedArray | ParsedFunction | ParsedString | ParsedBoolean | ParsedNumber;
17
18export 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 '*': // b*(c+d) ==> (b*c)+d
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
146export interface ParsedObject extends ParsedAny
147{
148 [name: string]: any;
149}
150
151export interface ParsedArray extends ParsedAny, Array<ParsedAny>
152{
153}
154
155export 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
163export 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
178export 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
191export 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
205export 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