UNPKG

35.2 kBJavaScriptView Raw
1/*
2 * @fileoverview Type expression parser.
3 * @author Yusuke Suzuki <utatane.tea@gmail.com>
4 * @author Dan Tao <daniel.tao@gmail.com>
5 * @author Andrew Eisenberg <andrew@eisenberg.as>
6 */
7
8// "typed", the Type Expression Parser for doctrine.
9
10(function () {
11 'use strict';
12
13 var Syntax,
14 Token,
15 source,
16 length,
17 index,
18 previous,
19 token,
20 value,
21 esutils,
22 utility;
23
24 esutils = require('esutils');
25 utility = require('./utility');
26
27 Syntax = {
28 NullableLiteral: 'NullableLiteral',
29 AllLiteral: 'AllLiteral',
30 NullLiteral: 'NullLiteral',
31 UndefinedLiteral: 'UndefinedLiteral',
32 VoidLiteral: 'VoidLiteral',
33 UnionType: 'UnionType',
34 ArrayType: 'ArrayType',
35 RecordType: 'RecordType',
36 FieldType: 'FieldType',
37 FunctionType: 'FunctionType',
38 ParameterType: 'ParameterType',
39 RestType: 'RestType',
40 NonNullableType: 'NonNullableType',
41 OptionalType: 'OptionalType',
42 NullableType: 'NullableType',
43 NameExpression: 'NameExpression',
44 TypeApplication: 'TypeApplication'
45 };
46
47 Token = {
48 ILLEGAL: 0, // ILLEGAL
49 DOT_LT: 1, // .<
50 REST: 2, // ...
51 LT: 3, // <
52 GT: 4, // >
53 LPAREN: 5, // (
54 RPAREN: 6, // )
55 LBRACE: 7, // {
56 RBRACE: 8, // }
57 LBRACK: 9, // [
58 RBRACK: 10, // ]
59 COMMA: 11, // ,
60 COLON: 12, // :
61 STAR: 13, // *
62 PIPE: 14, // |
63 QUESTION: 15, // ?
64 BANG: 16, // !
65 EQUAL: 17, // =
66 NAME: 18, // name token
67 STRING: 19, // string
68 NUMBER: 20, // number
69 EOF: 21
70 };
71
72 function isTypeName(ch) {
73 return '><(){}[],:*|?!='.indexOf(String.fromCharCode(ch)) === -1 && !esutils.code.isWhiteSpace(ch) && !esutils.code.isLineTerminator(ch);
74 }
75
76 function Context(previous, index, token, value) {
77 this._previous = previous;
78 this._index = index;
79 this._token = token;
80 this._value = value;
81 }
82
83 Context.prototype.restore = function () {
84 previous = this._previous;
85 index = this._index;
86 token = this._token;
87 value = this._value;
88 };
89
90 Context.save = function () {
91 return new Context(previous, index, token, value);
92 };
93
94 function advance() {
95 var ch = source.charAt(index);
96 index += 1;
97 return ch;
98 }
99
100 function scanHexEscape(prefix) {
101 var i, len, ch, code = 0;
102
103 len = (prefix === 'u') ? 4 : 2;
104 for (i = 0; i < len; ++i) {
105 if (index < length && esutils.code.isHexDigit(source.charCodeAt(index))) {
106 ch = advance();
107 code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
108 } else {
109 return '';
110 }
111 }
112 return String.fromCharCode(code);
113 }
114
115 function scanString() {
116 var str = '', quote, ch, code, unescaped, restore; //TODO review removal octal = false
117 quote = source.charAt(index);
118 ++index;
119
120 while (index < length) {
121 ch = advance();
122
123 if (ch === quote) {
124 quote = '';
125 break;
126 } else if (ch === '\\') {
127 ch = advance();
128 if (!esutils.code.isLineTerminator(ch.charCodeAt(0))) {
129 switch (ch) {
130 case 'n':
131 str += '\n';
132 break;
133 case 'r':
134 str += '\r';
135 break;
136 case 't':
137 str += '\t';
138 break;
139 case 'u':
140 case 'x':
141 restore = index;
142 unescaped = scanHexEscape(ch);
143 if (unescaped) {
144 str += unescaped;
145 } else {
146 index = restore;
147 str += ch;
148 }
149 break;
150 case 'b':
151 str += '\b';
152 break;
153 case 'f':
154 str += '\f';
155 break;
156 case 'v':
157 str += '\v';
158 break;
159
160 default:
161 if (esutils.code.isOctalDigit(ch.charCodeAt(0))) {
162 code = '01234567'.indexOf(ch);
163
164 // \0 is not octal escape sequence
165 // Deprecating unused code. TODO review removal
166 //if (code !== 0) {
167 // octal = true;
168 //}
169
170 if (index < length && esutils.code.isOctalDigit(source.charCodeAt(index))) {
171 //TODO Review Removal octal = true;
172 code = code * 8 + '01234567'.indexOf(advance());
173
174 // 3 digits are only allowed when string starts
175 // with 0, 1, 2, 3
176 if ('0123'.indexOf(ch) >= 0 &&
177 index < length &&
178 esutils.code.isOctalDigit(source.charCodeAt(index))) {
179 code = code * 8 + '01234567'.indexOf(advance());
180 }
181 }
182 str += String.fromCharCode(code);
183 } else {
184 str += ch;
185 }
186 break;
187 }
188 } else {
189 if (ch === '\r' && source.charCodeAt(index) === 0x0A /* '\n' */) {
190 ++index;
191 }
192 }
193 } else if (esutils.code.isLineTerminator(ch.charCodeAt(0))) {
194 break;
195 } else {
196 str += ch;
197 }
198 }
199
200 if (quote !== '') {
201 utility.throwError('unexpected quote');
202 }
203
204 value = str;
205 return Token.STRING;
206 }
207
208 function scanNumber() {
209 var number, ch;
210
211 number = '';
212 ch = source.charCodeAt(index);
213
214 if (ch !== 0x2E /* '.' */) {
215 number = advance();
216 ch = source.charCodeAt(index);
217
218 if (number === '0') {
219 if (ch === 0x78 /* 'x' */ || ch === 0x58 /* 'X' */) {
220 number += advance();
221 while (index < length) {
222 ch = source.charCodeAt(index);
223 if (!esutils.code.isHexDigit(ch)) {
224 break;
225 }
226 number += advance();
227 }
228
229 if (number.length <= 2) {
230 // only 0x
231 utility.throwError('unexpected token');
232 }
233
234 if (index < length) {
235 ch = source.charCodeAt(index);
236 if (esutils.code.isIdentifierStart(ch)) {
237 utility.throwError('unexpected token');
238 }
239 }
240 value = parseInt(number, 16);
241 return Token.NUMBER;
242 }
243
244 if (esutils.code.isOctalDigit(ch)) {
245 number += advance();
246 while (index < length) {
247 ch = source.charCodeAt(index);
248 if (!esutils.code.isOctalDigit(ch)) {
249 break;
250 }
251 number += advance();
252 }
253
254 if (index < length) {
255 ch = source.charCodeAt(index);
256 if (esutils.code.isIdentifierStart(ch) || esutils.code.isDecimalDigit(ch)) {
257 utility.throwError('unexpected token');
258 }
259 }
260 value = parseInt(number, 8);
261 return Token.NUMBER;
262 }
263
264 if (esutils.code.isDecimalDigit(ch)) {
265 utility.throwError('unexpected token');
266 }
267 }
268
269 while (index < length) {
270 ch = source.charCodeAt(index);
271 if (!esutils.code.isDecimalDigit(ch)) {
272 break;
273 }
274 number += advance();
275 }
276 }
277
278 if (ch === 0x2E /* '.' */) {
279 number += advance();
280 while (index < length) {
281 ch = source.charCodeAt(index);
282 if (!esutils.code.isDecimalDigit(ch)) {
283 break;
284 }
285 number += advance();
286 }
287 }
288
289 if (ch === 0x65 /* 'e' */ || ch === 0x45 /* 'E' */) {
290 number += advance();
291
292 ch = source.charCodeAt(index);
293 if (ch === 0x2B /* '+' */ || ch === 0x2D /* '-' */) {
294 number += advance();
295 }
296
297 ch = source.charCodeAt(index);
298 if (esutils.code.isDecimalDigit(ch)) {
299 number += advance();
300 while (index < length) {
301 ch = source.charCodeAt(index);
302 if (!esutils.code.isDecimalDigit(ch)) {
303 break;
304 }
305 number += advance();
306 }
307 } else {
308 utility.throwError('unexpected token');
309 }
310 }
311
312 if (index < length) {
313 ch = source.charCodeAt(index);
314 if (esutils.code.isIdentifierStart(ch)) {
315 utility.throwError('unexpected token');
316 }
317 }
318
319 value = parseFloat(number);
320 return Token.NUMBER;
321 }
322
323
324 function scanTypeName() {
325 var ch, ch2;
326
327 value = advance();
328 while (index < length && isTypeName(source.charCodeAt(index))) {
329 ch = source.charCodeAt(index);
330 if (ch === 0x2E /* '.' */) {
331 if ((index + 1) >= length) {
332 return Token.ILLEGAL;
333 }
334 ch2 = source.charCodeAt(index + 1);
335 if (ch2 === 0x3C /* '<' */) {
336 break;
337 }
338 }
339 value += advance();
340 }
341 return Token.NAME;
342 }
343
344 function next() {
345 var ch;
346
347 previous = index;
348
349 while (index < length && esutils.code.isWhiteSpace(source.charCodeAt(index))) {
350 advance();
351 }
352 if (index >= length) {
353 token = Token.EOF;
354 return token;
355 }
356
357 ch = source.charCodeAt(index);
358 switch (ch) {
359 case 0x27: /* ''' */
360 case 0x22: /* '"' */
361 token = scanString();
362 return token;
363
364 case 0x3A: /* ':' */
365 advance();
366 token = Token.COLON;
367 return token;
368
369 case 0x2C: /* ',' */
370 advance();
371 token = Token.COMMA;
372 return token;
373
374 case 0x28: /* '(' */
375 advance();
376 token = Token.LPAREN;
377 return token;
378
379 case 0x29: /* ')' */
380 advance();
381 token = Token.RPAREN;
382 return token;
383
384 case 0x5B: /* '[' */
385 advance();
386 token = Token.LBRACK;
387 return token;
388
389 case 0x5D: /* ']' */
390 advance();
391 token = Token.RBRACK;
392 return token;
393
394 case 0x7B: /* '{' */
395 advance();
396 token = Token.LBRACE;
397 return token;
398
399 case 0x7D: /* '}' */
400 advance();
401 token = Token.RBRACE;
402 return token;
403
404 case 0x2E: /* '.' */
405 if (index + 1 < length) {
406 ch = source.charCodeAt(index + 1);
407 if (ch === 0x3C /* '<' */) {
408 advance(); // '.'
409 advance(); // '<'
410 token = Token.DOT_LT;
411 return token;
412 }
413
414 if (ch === 0x2E /* '.' */ && index + 2 < length && source.charCodeAt(index + 2) === 0x2E /* '.' */) {
415 advance(); // '.'
416 advance(); // '.'
417 advance(); // '.'
418 token = Token.REST;
419 return token;
420 }
421
422 if (esutils.code.isDecimalDigit(ch)) {
423 token = scanNumber();
424 return token;
425 }
426 }
427 token = Token.ILLEGAL;
428 return token;
429
430 case 0x3C: /* '<' */
431 advance();
432 token = Token.LT;
433 return token;
434
435 case 0x3E: /* '>' */
436 advance();
437 token = Token.GT;
438 return token;
439
440 case 0x2A: /* '*' */
441 advance();
442 token = Token.STAR;
443 return token;
444
445 case 0x7C: /* '|' */
446 advance();
447 token = Token.PIPE;
448 return token;
449
450 case 0x3F: /* '?' */
451 advance();
452 token = Token.QUESTION;
453 return token;
454
455 case 0x21: /* '!' */
456 advance();
457 token = Token.BANG;
458 return token;
459
460 case 0x3D: /* '=' */
461 advance();
462 token = Token.EQUAL;
463 return token;
464
465 default:
466 if (esutils.code.isDecimalDigit(ch)) {
467 token = scanNumber();
468 return token;
469 }
470
471 // type string permits following case,
472 //
473 // namespace.module.MyClass
474 //
475 // this reduced 1 token TK_NAME
476 utility.assert(isTypeName(ch));
477 token = scanTypeName();
478 return token;
479 }
480 }
481
482 function consume(target, text) {
483 utility.assert(token === target, text || 'consumed token not matched');
484 next();
485 }
486
487 function expect(target, message) {
488 if (token !== target) {
489 utility.throwError(message || 'unexpected token');
490 }
491 next();
492 }
493
494 // UnionType := '(' TypeUnionList ')'
495 //
496 // TypeUnionList :=
497 // <<empty>>
498 // | NonemptyTypeUnionList
499 //
500 // NonemptyTypeUnionList :=
501 // TypeExpression
502 // | TypeExpression '|' NonemptyTypeUnionList
503 function parseUnionType() {
504 var elements;
505 consume(Token.LPAREN, 'UnionType should start with (');
506 elements = [];
507 if (token !== Token.RPAREN) {
508 while (true) {
509 elements.push(parseTypeExpression());
510 if (token === Token.RPAREN) {
511 break;
512 }
513 expect(Token.PIPE);
514 }
515 }
516 consume(Token.RPAREN, 'UnionType should end with )');
517 return {
518 type: Syntax.UnionType,
519 elements: elements
520 };
521 }
522
523 // ArrayType := '[' ElementTypeList ']'
524 //
525 // ElementTypeList :=
526 // <<empty>>
527 // | TypeExpression
528 // | '...' TypeExpression
529 // | TypeExpression ',' ElementTypeList
530 function parseArrayType() {
531 var elements;
532 consume(Token.LBRACK, 'ArrayType should start with [');
533 elements = [];
534 while (token !== Token.RBRACK) {
535 if (token === Token.REST) {
536 consume(Token.REST);
537 elements.push({
538 type: Syntax.RestType,
539 expression: parseTypeExpression()
540 });
541 break;
542 } else {
543 elements.push(parseTypeExpression());
544 }
545 if (token !== Token.RBRACK) {
546 expect(Token.COMMA);
547 }
548 }
549 expect(Token.RBRACK);
550 return {
551 type: Syntax.ArrayType,
552 elements: elements
553 };
554 }
555
556 function parseFieldName() {
557 var v = value;
558 if (token === Token.NAME || token === Token.STRING) {
559 next();
560 return v;
561 }
562
563 if (token === Token.NUMBER) {
564 consume(Token.NUMBER);
565 return String(v);
566 }
567
568 utility.throwError('unexpected token');
569 }
570
571 // FieldType :=
572 // FieldName
573 // | FieldName ':' TypeExpression
574 //
575 // FieldName :=
576 // NameExpression
577 // | StringLiteral
578 // | NumberLiteral
579 // | ReservedIdentifier
580 function parseFieldType() {
581 var key;
582
583 key = parseFieldName();
584 if (token === Token.COLON) {
585 consume(Token.COLON);
586 return {
587 type: Syntax.FieldType,
588 key: key,
589 value: parseTypeExpression()
590 };
591 }
592 return {
593 type: Syntax.FieldType,
594 key: key,
595 value: null
596 };
597 }
598
599 // RecordType := '{' FieldTypeList '}'
600 //
601 // FieldTypeList :=
602 // <<empty>>
603 // | FieldType
604 // | FieldType ',' FieldTypeList
605 function parseRecordType() {
606 var fields;
607
608 consume(Token.LBRACE, 'RecordType should start with {');
609 fields = [];
610 if (token === Token.COMMA) {
611 consume(Token.COMMA);
612 } else {
613 while (token !== Token.RBRACE) {
614 fields.push(parseFieldType());
615 if (token !== Token.RBRACE) {
616 expect(Token.COMMA);
617 }
618 }
619 }
620 expect(Token.RBRACE);
621 return {
622 type: Syntax.RecordType,
623 fields: fields
624 };
625 }
626
627 // NameExpression :=
628 // Identifier
629 // | TagIdentifier ':' Identifier
630 //
631 // Tag identifier is one of "module", "external" or "event"
632 // Identifier is the same as Token.NAME, including any dots, something like
633 // namespace.module.MyClass
634 function parseNameExpression() {
635 var name = value;
636 expect(Token.NAME);
637
638 if (token === Token.COLON && (
639 name === 'module' ||
640 name === 'external' ||
641 name === 'event')) {
642 consume(Token.COLON);
643 name += ':' + value;
644 expect(Token.NAME);
645 }
646
647 return {
648 type: Syntax.NameExpression,
649 name: name
650 };
651 }
652
653 // TypeExpressionList :=
654 // TopLevelTypeExpression
655 // | TopLevelTypeExpression ',' TypeExpressionList
656 function parseTypeExpressionList() {
657 var elements = [];
658
659 elements.push(parseTop());
660 while (token === Token.COMMA) {
661 consume(Token.COMMA);
662 elements.push(parseTop());
663 }
664 return elements;
665 }
666
667 // TypeName :=
668 // NameExpression
669 // | NameExpression TypeApplication
670 //
671 // TypeApplication :=
672 // '.<' TypeExpressionList '>'
673 // | '<' TypeExpressionList '>' // this is extension of doctrine
674 function parseTypeName() {
675 var expr, applications;
676
677 expr = parseNameExpression();
678 if (token === Token.DOT_LT || token === Token.LT) {
679 next();
680 applications = parseTypeExpressionList();
681 expect(Token.GT);
682 return {
683 type: Syntax.TypeApplication,
684 expression: expr,
685 applications: applications
686 };
687 }
688 return expr;
689 }
690
691 // ResultType :=
692 // <<empty>>
693 // | ':' void
694 // | ':' TypeExpression
695 //
696 // BNF is above
697 // but, we remove <<empty>> pattern, so token is always TypeToken::COLON
698 function parseResultType() {
699 consume(Token.COLON, 'ResultType should start with :');
700 if (token === Token.NAME && value === 'void') {
701 consume(Token.NAME);
702 return {
703 type: Syntax.VoidLiteral
704 };
705 }
706 return parseTypeExpression();
707 }
708
709 // ParametersType :=
710 // RestParameterType
711 // | NonRestParametersType
712 // | NonRestParametersType ',' RestParameterType
713 //
714 // RestParameterType :=
715 // '...'
716 // '...' Identifier
717 //
718 // NonRestParametersType :=
719 // ParameterType ',' NonRestParametersType
720 // | ParameterType
721 // | OptionalParametersType
722 //
723 // OptionalParametersType :=
724 // OptionalParameterType
725 // | OptionalParameterType, OptionalParametersType
726 //
727 // OptionalParameterType := ParameterType=
728 //
729 // ParameterType := TypeExpression | Identifier ':' TypeExpression
730 //
731 // Identifier is "new" or "this"
732 function parseParametersType() {
733 var params = [], optionalSequence = false, expr, rest = false;
734
735 while (token !== Token.RPAREN) {
736 if (token === Token.REST) {
737 // RestParameterType
738 consume(Token.REST);
739 rest = true;
740 }
741
742 expr = parseTypeExpression();
743 if (expr.type === Syntax.NameExpression && token === Token.COLON) {
744 // Identifier ':' TypeExpression
745 consume(Token.COLON);
746 expr = {
747 type: Syntax.ParameterType,
748 name: expr.name,
749 expression: parseTypeExpression()
750 };
751 }
752 if (token === Token.EQUAL) {
753 consume(Token.EQUAL);
754 expr = {
755 type: Syntax.OptionalType,
756 expression: expr
757 };
758 optionalSequence = true;
759 } else {
760 if (optionalSequence) {
761 utility.throwError('unexpected token');
762 }
763 }
764 if (rest) {
765 expr = {
766 type: Syntax.RestType,
767 expression: expr
768 };
769 }
770 params.push(expr);
771 if (token !== Token.RPAREN) {
772 expect(Token.COMMA);
773 }
774 }
775 return params;
776 }
777
778 // FunctionType := 'function' FunctionSignatureType
779 //
780 // FunctionSignatureType :=
781 // | TypeParameters '(' ')' ResultType
782 // | TypeParameters '(' ParametersType ')' ResultType
783 // | TypeParameters '(' 'this' ':' TypeName ')' ResultType
784 // | TypeParameters '(' 'this' ':' TypeName ',' ParametersType ')' ResultType
785 function parseFunctionType() {
786 var isNew, thisBinding, params, result, fnType;
787 utility.assert(token === Token.NAME && value === 'function', 'FunctionType should start with \'function\'');
788 consume(Token.NAME);
789
790 // Google Closure Compiler is not implementing TypeParameters.
791 // So we do not. if we don't get '(', we see it as error.
792 expect(Token.LPAREN);
793
794 isNew = false;
795 params = [];
796 thisBinding = null;
797 if (token !== Token.RPAREN) {
798 // ParametersType or 'this'
799 if (token === Token.NAME &&
800 (value === 'this' || value === 'new')) {
801 // 'this' or 'new'
802 // 'new' is Closure Compiler extension
803 isNew = value === 'new';
804 consume(Token.NAME);
805 expect(Token.COLON);
806 thisBinding = parseTypeName();
807 if (token === Token.COMMA) {
808 consume(Token.COMMA);
809 params = parseParametersType();
810 }
811 } else {
812 params = parseParametersType();
813 }
814 }
815
816 expect(Token.RPAREN);
817
818 result = null;
819 if (token === Token.COLON) {
820 result = parseResultType();
821 }
822
823 fnType = {
824 type: Syntax.FunctionType,
825 params: params,
826 result: result
827 };
828 if (thisBinding) {
829 // avoid adding null 'new' and 'this' properties
830 fnType['this'] = thisBinding;
831 if (isNew) {
832 fnType['new'] = true;
833 }
834 }
835 return fnType;
836 }
837
838 // BasicTypeExpression :=
839 // '*'
840 // | 'null'
841 // | 'undefined'
842 // | TypeName
843 // | FunctionType
844 // | UnionType
845 // | RecordType
846 // | ArrayType
847 function parseBasicTypeExpression() {
848 var context;
849 switch (token) {
850 case Token.STAR:
851 consume(Token.STAR);
852 return {
853 type: Syntax.AllLiteral
854 };
855
856 case Token.LPAREN:
857 return parseUnionType();
858
859 case Token.LBRACK:
860 return parseArrayType();
861
862 case Token.LBRACE:
863 return parseRecordType();
864
865 case Token.NAME:
866 if (value === 'null') {
867 consume(Token.NAME);
868 return {
869 type: Syntax.NullLiteral
870 };
871 }
872
873 if (value === 'undefined') {
874 consume(Token.NAME);
875 return {
876 type: Syntax.UndefinedLiteral
877 };
878 }
879
880 context = Context.save();
881 if (value === 'function') {
882 try {
883 return parseFunctionType();
884 } catch (e) {
885 context.restore();
886 }
887 }
888
889 return parseTypeName();
890
891 default:
892 utility.throwError('unexpected token');
893 }
894 }
895
896 // TypeExpression :=
897 // BasicTypeExpression
898 // | '?' BasicTypeExpression
899 // | '!' BasicTypeExpression
900 // | BasicTypeExpression '?'
901 // | BasicTypeExpression '!'
902 // | '?'
903 // | BasicTypeExpression '[]'
904 function parseTypeExpression() {
905 var expr;
906
907 if (token === Token.QUESTION) {
908 consume(Token.QUESTION);
909 if (token === Token.COMMA || token === Token.EQUAL || token === Token.RBRACE ||
910 token === Token.RPAREN || token === Token.PIPE || token === Token.EOF ||
911 token === Token.RBRACK || token === Token.GT) {
912 return {
913 type: Syntax.NullableLiteral
914 };
915 }
916 return {
917 type: Syntax.NullableType,
918 expression: parseBasicTypeExpression(),
919 prefix: true
920 };
921 }
922
923 if (token === Token.BANG) {
924 consume(Token.BANG);
925 return {
926 type: Syntax.NonNullableType,
927 expression: parseBasicTypeExpression(),
928 prefix: true
929 };
930 }
931
932 expr = parseBasicTypeExpression();
933 if (token === Token.BANG) {
934 consume(Token.BANG);
935 return {
936 type: Syntax.NonNullableType,
937 expression: expr,
938 prefix: false
939 };
940 }
941
942 if (token === Token.QUESTION) {
943 consume(Token.QUESTION);
944 return {
945 type: Syntax.NullableType,
946 expression: expr,
947 prefix: false
948 };
949 }
950
951 if (token === Token.LBRACK) {
952 consume(Token.LBRACK);
953 expect(Token.RBRACK, 'expected an array-style type declaration (' + value + '[])');
954 return {
955 type: Syntax.TypeApplication,
956 expression: {
957 type: Syntax.NameExpression,
958 name: 'Array'
959 },
960 applications: [expr]
961 };
962 }
963
964 return expr;
965 }
966
967 // TopLevelTypeExpression :=
968 // TypeExpression
969 // | TypeUnionList
970 //
971 // This rule is Google Closure Compiler extension, not ES4
972 // like,
973 // { number | string }
974 // If strict to ES4, we should write it as
975 // { (number|string) }
976 function parseTop() {
977 var expr, elements;
978
979 expr = parseTypeExpression();
980 if (token !== Token.PIPE) {
981 return expr;
982 }
983
984 elements = [expr];
985 consume(Token.PIPE);
986 while (true) {
987 elements.push(parseTypeExpression());
988 if (token !== Token.PIPE) {
989 break;
990 }
991 consume(Token.PIPE);
992 }
993
994 return {
995 type: Syntax.UnionType,
996 elements: elements
997 };
998 }
999
1000 function parseTopParamType() {
1001 var expr;
1002
1003 if (token === Token.REST) {
1004 consume(Token.REST);
1005 return {
1006 type: Syntax.RestType,
1007 expression: parseTop()
1008 };
1009 }
1010
1011 expr = parseTop();
1012 if (token === Token.EQUAL) {
1013 consume(Token.EQUAL);
1014 return {
1015 type: Syntax.OptionalType,
1016 expression: expr
1017 };
1018 }
1019
1020 return expr;
1021 }
1022
1023 function parseType(src, opt) {
1024 var expr;
1025
1026 source = src;
1027 length = source.length;
1028 index = 0;
1029 previous = 0;
1030
1031 next();
1032 expr = parseTop();
1033
1034 if (opt && opt.midstream) {
1035 return {
1036 expression: expr,
1037 index: previous
1038 };
1039 }
1040
1041 if (token !== Token.EOF) {
1042 utility.throwError('not reach to EOF');
1043 }
1044
1045 return expr;
1046 }
1047
1048 function parseParamType(src, opt) {
1049 var expr;
1050
1051 source = src;
1052 length = source.length;
1053 index = 0;
1054 previous = 0;
1055
1056 next();
1057 expr = parseTopParamType();
1058
1059 if (opt && opt.midstream) {
1060 return {
1061 expression: expr,
1062 index: previous
1063 };
1064 }
1065
1066 if (token !== Token.EOF) {
1067 utility.throwError('not reach to EOF');
1068 }
1069
1070 return expr;
1071 }
1072
1073 function stringifyImpl(node, compact, topLevel) {
1074 var result, i, iz;
1075
1076 switch (node.type) {
1077 case Syntax.NullableLiteral:
1078 result = '?';
1079 break;
1080
1081 case Syntax.AllLiteral:
1082 result = '*';
1083 break;
1084
1085 case Syntax.NullLiteral:
1086 result = 'null';
1087 break;
1088
1089 case Syntax.UndefinedLiteral:
1090 result = 'undefined';
1091 break;
1092
1093 case Syntax.VoidLiteral:
1094 result = 'void';
1095 break;
1096
1097 case Syntax.UnionType:
1098 if (!topLevel) {
1099 result = '(';
1100 } else {
1101 result = '';
1102 }
1103
1104 for (i = 0, iz = node.elements.length; i < iz; ++i) {
1105 result += stringifyImpl(node.elements[i], compact);
1106 if ((i + 1) !== iz) {
1107 result += '|';
1108 }
1109 }
1110
1111 if (!topLevel) {
1112 result += ')';
1113 }
1114 break;
1115
1116 case Syntax.ArrayType:
1117 result = '[';
1118 for (i = 0, iz = node.elements.length; i < iz; ++i) {
1119 result += stringifyImpl(node.elements[i], compact);
1120 if ((i + 1) !== iz) {
1121 result += compact ? ',' : ', ';
1122 }
1123 }
1124 result += ']';
1125 break;
1126
1127 case Syntax.RecordType:
1128 result = '{';
1129 for (i = 0, iz = node.fields.length; i < iz; ++i) {
1130 result += stringifyImpl(node.fields[i], compact);
1131 if ((i + 1) !== iz) {
1132 result += compact ? ',' : ', ';
1133 }
1134 }
1135 result += '}';
1136 break;
1137
1138 case Syntax.FieldType:
1139 if (node.value) {
1140 result = node.key + (compact ? ':' : ': ') + stringifyImpl(node.value, compact);
1141 } else {
1142 result = node.key;
1143 }
1144 break;
1145
1146 case Syntax.FunctionType:
1147 result = compact ? 'function(' : 'function (';
1148
1149 if (node['this']) {
1150 if (node['new']) {
1151 result += (compact ? 'new:' : 'new: ');
1152 } else {
1153 result += (compact ? 'this:' : 'this: ');
1154 }
1155
1156 result += stringifyImpl(node['this'], compact);
1157
1158 if (node.params.length !== 0) {
1159 result += compact ? ',' : ', ';
1160 }
1161 }
1162
1163 for (i = 0, iz = node.params.length; i < iz; ++i) {
1164 result += stringifyImpl(node.params[i], compact);
1165 if ((i + 1) !== iz) {
1166 result += compact ? ',' : ', ';
1167 }
1168 }
1169
1170 result += ')';
1171
1172 if (node.result) {
1173 result += (compact ? ':' : ': ') + stringifyImpl(node.result, compact);
1174 }
1175 break;
1176
1177 case Syntax.ParameterType:
1178 result = node.name + (compact ? ':' : ': ') + stringifyImpl(node.expression, compact);
1179 break;
1180
1181 case Syntax.RestType:
1182 result = '...';
1183 if (node.expression) {
1184 result += stringifyImpl(node.expression, compact);
1185 }
1186 break;
1187
1188 case Syntax.NonNullableType:
1189 if (node.prefix) {
1190 result = '!' + stringifyImpl(node.expression, compact);
1191 } else {
1192 result = stringifyImpl(node.expression, compact) + '!';
1193 }
1194 break;
1195
1196 case Syntax.OptionalType:
1197 result = stringifyImpl(node.expression, compact) + '=';
1198 break;
1199
1200 case Syntax.NullableType:
1201 if (node.prefix) {
1202 result = '?' + stringifyImpl(node.expression, compact);
1203 } else {
1204 result = stringifyImpl(node.expression, compact) + '?';
1205 }
1206 break;
1207
1208 case Syntax.NameExpression:
1209 result = node.name;
1210 break;
1211
1212 case Syntax.TypeApplication:
1213 result = stringifyImpl(node.expression, compact) + '.<';
1214 for (i = 0, iz = node.applications.length; i < iz; ++i) {
1215 result += stringifyImpl(node.applications[i], compact);
1216 if ((i + 1) !== iz) {
1217 result += compact ? ',' : ', ';
1218 }
1219 }
1220 result += '>';
1221 break;
1222
1223 default:
1224 utility.throwError('Unknown type ' + node.type);
1225 }
1226
1227 return result;
1228 }
1229
1230 function stringify(node, options) {
1231 if (options == null) {
1232 options = {};
1233 }
1234 return stringifyImpl(node, options.compact, options.topLevel);
1235 }
1236
1237 exports.parseType = parseType;
1238 exports.parseParamType = parseParamType;
1239 exports.stringify = stringify;
1240 exports.Syntax = Syntax;
1241}());
1242/* vim: set sw=4 ts=4 et tw=80 : */