UNPKG

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