2 Copyright (C) 2012-2014 Yusuke Suzuki <utatane.tea@gmail.com>
3 Copyright (C) 2014 Dan Tao <daniel.tao@gmail.com>
4 Copyright (C) 2013 Andrew Eisenberg <andrew@eisenberg.as>
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions are met:
9 * Redistributions of source code must retain the above copyright
10 notice, this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above copyright
12 notice, this list of conditions and the following disclaimer in the
13 documentation and/or other materials provided with the distribution.
15 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
19 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 // "typed", the Type Expression Parser for doctrine.
43 esutils = require('esutils');
44 utility = require('./utility');
47 NullableLiteral: 'NullableLiteral',
48 AllLiteral: 'AllLiteral',
49 NullLiteral: 'NullLiteral',
50 UndefinedLiteral: 'UndefinedLiteral',
51 VoidLiteral: 'VoidLiteral',
52 UnionType: 'UnionType',
53 ArrayType: 'ArrayType',
54 RecordType: 'RecordType',
55 FieldType: 'FieldType',
56 FunctionType: 'FunctionType',
57 ParameterType: 'ParameterType',
59 NonNullableType: 'NonNullableType',
60 OptionalType: 'OptionalType',
61 NullableType: 'NullableType',
62 NameExpression: 'NameExpression',
63 TypeApplication: 'TypeApplication'
67 ILLEGAL: 0, // ILLEGAL
85 NAME: 18, // name token
91 function isTypeName(ch) {
92 return '><(){}[],:*|?!='.indexOf(String.fromCharCode(ch)) === -1 && !esutils.code.isWhiteSpace(ch) && !esutils.code.isLineTerminator(ch);
95 function Context(previous, index, token, value) {
96 this._previous = previous;
102 Context.prototype.restore = function () {
103 previous = this._previous;
109 Context.save = function () {
110 return new Context(previous, index, token, value);
114 var ch = source.charAt(index);
119 function scanHexEscape(prefix) {
120 var i, len, ch, code = 0;
122 len = (prefix === 'u') ? 4 : 2;
123 for (i = 0; i < len; ++i) {
124 if (index < length && esutils.code.isHexDigit(source.charCodeAt(index))) {
126 code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
131 return String.fromCharCode(code);
134 function scanString() {
135 var str = '', quote, ch, code, unescaped, restore; //TODO review removal octal = false
136 quote = source.charAt(index);
139 while (index < length) {
145 } else if (ch === '\\') {
147 if (!esutils.code.isLineTerminator(ch.charCodeAt(0))) {
161 unescaped = scanHexEscape(ch);
180 if (esutils.code.isOctalDigit(ch.charCodeAt(0))) {
181 code = '01234567'.indexOf(ch);
183 // \0 is not octal escape sequence
184 // Deprecating unused code. TODO review removal
189 if (index < length && esutils.code.isOctalDigit(source.charCodeAt(index))) {
190 //TODO Review Removal octal = true;
191 code = code * 8 + '01234567'.indexOf(advance());
193 // 3 digits are only allowed when string starts
195 if ('0123'.indexOf(ch) >= 0 &&
197 esutils.code.isOctalDigit(source.charCodeAt(index))) {
198 code = code * 8 + '01234567'.indexOf(advance());
201 str += String.fromCharCode(code);
208 if (ch === '\r' && source.charCodeAt(index) === 0x0A /* '\n' */) {
212 } else if (esutils.code.isLineTerminator(ch.charCodeAt(0))) {
220 utility.throwError('unexpected quote');
227 function scanNumber() {
231 ch = source.charCodeAt(index);
233 if (ch !== 0x2E /* '.' */) {
235 ch = source.charCodeAt(index);
237 if (number === '0') {
238 if (ch === 0x78 /* 'x' */ || ch === 0x58 /* 'X' */) {
240 while (index < length) {
241 ch = source.charCodeAt(index);
242 if (!esutils.code.isHexDigit(ch)) {
248 if (number.length <= 2) {
250 utility.throwError('unexpected token');
253 if (index < length) {
254 ch = source.charCodeAt(index);
255 if (esutils.code.isIdentifierStart(ch)) {
256 utility.throwError('unexpected token');
259 value = parseInt(number, 16);
263 if (esutils.code.isOctalDigit(ch)) {
265 while (index < length) {
266 ch = source.charCodeAt(index);
267 if (!esutils.code.isOctalDigit(ch)) {
273 if (index < length) {
274 ch = source.charCodeAt(index);
275 if (esutils.code.isIdentifierStart(ch) || esutils.code.isDecimalDigit(ch)) {
276 utility.throwError('unexpected token');
279 value = parseInt(number, 8);
283 if (esutils.code.isDecimalDigit(ch)) {
284 utility.throwError('unexpected token');
288 while (index < length) {
289 ch = source.charCodeAt(index);
290 if (!esutils.code.isDecimalDigit(ch)) {
297 if (ch === 0x2E /* '.' */) {
299 while (index < length) {
300 ch = source.charCodeAt(index);
301 if (!esutils.code.isDecimalDigit(ch)) {
308 if (ch === 0x65 /* 'e' */ || ch === 0x45 /* 'E' */) {
311 ch = source.charCodeAt(index);
312 if (ch === 0x2B /* '+' */ || ch === 0x2D /* '-' */) {
316 ch = source.charCodeAt(index);
317 if (esutils.code.isDecimalDigit(ch)) {
319 while (index < length) {
320 ch = source.charCodeAt(index);
321 if (!esutils.code.isDecimalDigit(ch)) {
327 utility.throwError('unexpected token');
331 if (index < length) {
332 ch = source.charCodeAt(index);
333 if (esutils.code.isIdentifierStart(ch)) {
334 utility.throwError('unexpected token');
338 value = parseFloat(number);
343 function scanTypeName() {
347 while (index < length && isTypeName(source.charCodeAt(index))) {
348 ch = source.charCodeAt(index);
349 if (ch === 0x2E /* '.' */) {
350 if ((index + 1) >= length) {
351 return Token.ILLEGAL;
353 ch2 = source.charCodeAt(index + 1);
354 if (ch2 === 0x3C /* '<' */) {
368 while (index < length && esutils.code.isWhiteSpace(source.charCodeAt(index))) {
371 if (index >= length) {
376 ch = source.charCodeAt(index);
380 token = scanString();
395 token = Token.LPAREN;
400 token = Token.RPAREN;
405 token = Token.LBRACK;
410 token = Token.RBRACK;
415 token = Token.LBRACE;
420 token = Token.RBRACE;
424 if (index + 1 < length) {
425 ch = source.charCodeAt(index + 1);
426 if (ch === 0x3C /* '<' */) {
429 token = Token.DOT_LT;
433 if (ch === 0x2E /* '.' */ && index + 2 < length && source.charCodeAt(index + 2) === 0x2E /* '.' */) {
441 if (esutils.code.isDecimalDigit(ch)) {
442 token = scanNumber();
446 token = Token.ILLEGAL;
471 token = Token.QUESTION;
485 if (esutils.code.isDecimalDigit(ch)) {
486 token = scanNumber();
490 // type string permits following case,
492 // namespace.module.MyClass
494 // this reduced 1 token TK_NAME
495 utility.assert(isTypeName(ch));
496 token = scanTypeName();
501 function consume(target, text) {
502 utility.assert(token === target, text || 'consumed token not matched');
506 function expect(target, message) {
507 if (token !== target) {
508 utility.throwError(message || 'unexpected token');
513 // UnionType := '(' TypeUnionList ')'
517 // | NonemptyTypeUnionList
519 // NonemptyTypeUnionList :=
521 // | TypeExpression '|' NonemptyTypeUnionList
522 function parseUnionType() {
524 consume(Token.LPAREN, 'UnionType should start with (');
526 if (token !== Token.RPAREN) {
528 elements.push(parseTypeExpression());
529 if (token === Token.RPAREN) {
535 consume(Token.RPAREN, 'UnionType should end with )');
537 type: Syntax.UnionType,
542 // ArrayType := '[' ElementTypeList ']'
544 // ElementTypeList :=
547 // | '...' TypeExpression
548 // | TypeExpression ',' ElementTypeList
549 function parseArrayType() {
551 consume(Token.LBRACK, 'ArrayType should start with [');
553 while (token !== Token.RBRACK) {
554 if (token === Token.REST) {
557 type: Syntax.RestType,
558 expression: parseTypeExpression()
562 elements.push(parseTypeExpression());
564 if (token !== Token.RBRACK) {
568 expect(Token.RBRACK);
570 type: Syntax.ArrayType,
575 function parseFieldName() {
577 if (token === Token.NAME || token === Token.STRING) {
582 if (token === Token.NUMBER) {
583 consume(Token.NUMBER);
587 utility.throwError('unexpected token');
592 // | FieldName ':' TypeExpression
598 // | ReservedIdentifier
599 function parseFieldType() {
602 key = parseFieldName();
603 if (token === Token.COLON) {
604 consume(Token.COLON);
606 type: Syntax.FieldType,
608 value: parseTypeExpression()
612 type: Syntax.FieldType,
618 // RecordType := '{' FieldTypeList '}'
623 // | FieldType ',' FieldTypeList
624 function parseRecordType() {
627 consume(Token.LBRACE, 'RecordType should start with {');
629 if (token === Token.COMMA) {
630 consume(Token.COMMA);
632 while (token !== Token.RBRACE) {
633 fields.push(parseFieldType());
634 if (token !== Token.RBRACE) {
639 expect(Token.RBRACE);
641 type: Syntax.RecordType,
648 // | TagIdentifier ':' Identifier
650 // Tag identifier is one of "module", "external" or "event"
651 // Identifier is the same as Token.NAME, including any dots, something like
652 // namespace.module.MyClass
653 function parseNameExpression() {
657 if (token === Token.COLON && (
659 name === 'external' ||
661 consume(Token.COLON);
667 type: Syntax.NameExpression,
672 // TypeExpressionList :=
673 // TopLevelTypeExpression
674 // | TopLevelTypeExpression ',' TypeExpressionList
675 function parseTypeExpressionList() {
678 elements.push(parseTop());
679 while (token === Token.COMMA) {
680 consume(Token.COMMA);
681 elements.push(parseTop());
688 // | NameExpression TypeApplication
690 // TypeApplication :=
691 // '.<' TypeExpressionList '>'
692 // | '<' TypeExpressionList '>' // this is extension of doctrine
693 function parseTypeName() {
694 var expr, applications;
696 expr = parseNameExpression();
697 if (token === Token.DOT_LT || token === Token.LT) {
699 applications = parseTypeExpressionList();
702 type: Syntax.TypeApplication,
704 applications: applications
713 // | ':' TypeExpression
716 // but, we remove <<empty>> pattern, so token is always TypeToken::COLON
717 function parseResultType() {
718 consume(Token.COLON, 'ResultType should start with :');
719 if (token === Token.NAME && value === 'void') {
722 type: Syntax.VoidLiteral
725 return parseTypeExpression();
730 // | NonRestParametersType
731 // | NonRestParametersType ',' RestParameterType
733 // RestParameterType :=
737 // NonRestParametersType :=
738 // ParameterType ',' NonRestParametersType
740 // | OptionalParametersType
742 // OptionalParametersType :=
743 // OptionalParameterType
744 // | OptionalParameterType, OptionalParametersType
746 // OptionalParameterType := ParameterType=
748 // ParameterType := TypeExpression | Identifier ':' TypeExpression
750 // Identifier is "new" or "this"
751 function parseParametersType() {
752 var params = [], optionalSequence = false, expr, rest = false;
754 while (token !== Token.RPAREN) {
755 if (token === Token.REST) {
761 expr = parseTypeExpression();
762 if (expr.type === Syntax.NameExpression && token === Token.COLON) {
763 // Identifier ':' TypeExpression
764 consume(Token.COLON);
766 type: Syntax.ParameterType,
768 expression: parseTypeExpression()
771 if (token === Token.EQUAL) {
772 consume(Token.EQUAL);
774 type: Syntax.OptionalType,
777 optionalSequence = true;
779 if (optionalSequence) {
780 utility.throwError('unexpected token');
785 type: Syntax.RestType,
790 if (token !== Token.RPAREN) {
797 // FunctionType := 'function' FunctionSignatureType
799 // FunctionSignatureType :=
800 // | TypeParameters '(' ')' ResultType
801 // | TypeParameters '(' ParametersType ')' ResultType
802 // | TypeParameters '(' 'this' ':' TypeName ')' ResultType
803 // | TypeParameters '(' 'this' ':' TypeName ',' ParametersType ')' ResultType
804 function parseFunctionType() {
805 var isNew, thisBinding, params, result, fnType;
806 utility.assert(token === Token.NAME && value === 'function', 'FunctionType should start with \'function\'');
809 // Google Closure Compiler is not implementing TypeParameters.
810 // So we do not. if we don't get '(', we see it as error.
811 expect(Token.LPAREN);
816 if (token !== Token.RPAREN) {
817 // ParametersType or 'this'
818 if (token === Token.NAME &&
819 (value === 'this' || value === 'new')) {
821 // 'new' is Closure Compiler extension
822 isNew = value === 'new';
825 thisBinding = parseTypeName();
826 if (token === Token.COMMA) {
827 consume(Token.COMMA);
828 params = parseParametersType();
831 params = parseParametersType();
835 expect(Token.RPAREN);
838 if (token === Token.COLON) {
839 result = parseResultType();
843 type: Syntax.FunctionType,
848 // avoid adding null 'new' and 'this' properties
849 fnType['this'] = thisBinding;
851 fnType['new'] = true;
857 // BasicTypeExpression :=
866 function parseBasicTypeExpression() {
872 type: Syntax.AllLiteral
876 return parseUnionType();
879 return parseArrayType();
882 return parseRecordType();
885 if (value === 'null') {
888 type: Syntax.NullLiteral
892 if (value === 'undefined') {
895 type: Syntax.UndefinedLiteral
899 context = Context.save();
900 if (value === 'function') {
902 return parseFunctionType();
908 return parseTypeName();
911 utility.throwError('unexpected token');
916 // BasicTypeExpression
917 // | '?' BasicTypeExpression
918 // | '!' BasicTypeExpression
919 // | BasicTypeExpression '?'
920 // | BasicTypeExpression '!'
922 // | BasicTypeExpression '[]'
923 function parseTypeExpression() {
926 if (token === Token.QUESTION) {
927 consume(Token.QUESTION);
928 if (token === Token.COMMA || token === Token.EQUAL || token === Token.RBRACE ||
929 token === Token.RPAREN || token === Token.PIPE || token === Token.EOF ||
930 token === Token.RBRACK || token === Token.GT) {
932 type: Syntax.NullableLiteral
936 type: Syntax.NullableType,
937 expression: parseBasicTypeExpression(),
942 if (token === Token.BANG) {
945 type: Syntax.NonNullableType,
946 expression: parseBasicTypeExpression(),
951 expr = parseBasicTypeExpression();
952 if (token === Token.BANG) {
955 type: Syntax.NonNullableType,
961 if (token === Token.QUESTION) {
962 consume(Token.QUESTION);
964 type: Syntax.NullableType,
970 if (token === Token.LBRACK) {
971 consume(Token.LBRACK);
972 expect(Token.RBRACK, 'expected an array-style type declaration (' + value + '[])');
974 type: Syntax.TypeApplication,
976 type: Syntax.NameExpression,
986 // TopLevelTypeExpression :=
990 // This rule is Google Closure Compiler extension, not ES4
992 // { number | string }
993 // If strict to ES4, we should write it as
994 // { (number|string) }
995 function parseTop() {
998 expr = parseTypeExpression();
999 if (token !== Token.PIPE) {
1003 elements = [ expr ];
1004 consume(Token.PIPE);
1006 elements.push(parseTypeExpression());
1007 if (token !== Token.PIPE) {
1010 consume(Token.PIPE);
1014 type: Syntax.UnionType,
1019 function parseTopParamType() {
1022 if (token === Token.REST) {
1023 consume(Token.REST);
1025 type: Syntax.RestType,
1026 expression: parseTop()
1031 if (token === Token.EQUAL) {
1032 consume(Token.EQUAL);
1034 type: Syntax.OptionalType,
1042 function parseType(src, opt) {
1046 length = source.length;
1053 if (opt && opt.midstream) {
1060 if (token !== Token.EOF) {
1061 utility.throwError('not reach to EOF');
1067 function parseParamType(src, opt) {
1071 length = source.length;
1076 expr = parseTopParamType();
1078 if (opt && opt.midstream) {
1085 if (token !== Token.EOF) {
1086 utility.throwError('not reach to EOF');
1092 function stringifyImpl(node, compact, topLevel) {
1095 switch (node.type) {
1096 case Syntax.NullableLiteral:
1100 case Syntax.AllLiteral:
1104 case Syntax.NullLiteral:
1108 case Syntax.UndefinedLiteral:
1109 result = 'undefined';
1112 case Syntax.VoidLiteral:
1116 case Syntax.UnionType:
1123 for (i = 0, iz = node.elements.length; i < iz; ++i) {
1124 result += stringifyImpl(node.elements[i], compact);
1125 if ((i + 1) !== iz) {
1135 case Syntax.ArrayType:
1137 for (i = 0, iz = node.elements.length; i < iz; ++i) {
1138 result += stringifyImpl(node.elements[i], compact);
1139 if ((i + 1) !== iz) {
1140 result += compact ? ',' : ', ';
1146 case Syntax.RecordType:
1148 for (i = 0, iz = node.fields.length; i < iz; ++i) {
1149 result += stringifyImpl(node.fields[i], compact);
1150 if ((i + 1) !== iz) {
1151 result += compact ? ',' : ', ';
1157 case Syntax.FieldType:
1159 result = node.key + (compact ? ':' : ': ') + stringifyImpl(node.value, compact);
1165 case Syntax.FunctionType:
1166 result = compact ? 'function(' : 'function (';
1170 result += (compact ? 'new:' : 'new: ');
1172 result += (compact ? 'this:' : 'this: ');
1175 result += stringifyImpl(node['this'], compact);
1177 if (node.params.length !== 0) {
1178 result += compact ? ',' : ', ';
1182 for (i = 0, iz = node.params.length; i < iz; ++i) {
1183 result += stringifyImpl(node.params[i], compact);
1184 if ((i + 1) !== iz) {
1185 result += compact ? ',' : ', ';
1192 result += (compact ? ':' : ': ') + stringifyImpl(node.result, compact);
1196 case Syntax.ParameterType:
1197 result = node.name + (compact ? ':' : ': ') + stringifyImpl(node.expression, compact);
1200 case Syntax.RestType:
1202 if (node.expression) {
1203 result += stringifyImpl(node.expression, compact);
1207 case Syntax.NonNullableType:
1209 result = '!' + stringifyImpl(node.expression, compact);
1211 result = stringifyImpl(node.expression, compact) + '!';
1215 case Syntax.OptionalType:
1216 result = stringifyImpl(node.expression, compact) + '=';
1219 case Syntax.NullableType:
1221 result = '?' + stringifyImpl(node.expression, compact);
1223 result = stringifyImpl(node.expression, compact) + '?';
1227 case Syntax.NameExpression:
1231 case Syntax.TypeApplication:
1232 result = stringifyImpl(node.expression, compact) + '.<';
1233 for (i = 0, iz = node.applications.length; i < iz; ++i) {
1234 result += stringifyImpl(node.applications[i], compact);
1235 if ((i + 1) !== iz) {
1236 result += compact ? ',' : ', ';
1243 utility.throwError('Unknown type ' + node.type);
1249 function stringify(node, options) {
1250 if (options == null) {
1253 return stringifyImpl(node, options.compact, options.topLevel);
1256 exports.parseType = parseType;
1257 exports.parseParamType = parseParamType;
1258 exports.stringify = stringify;
1259 exports.Syntax = Syntax;
1261 /* vim: set sw=4 ts=4 et tw=80 : */