UNPKG

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