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>
8 // "typed", the Type Expression Parser for doctrine.
26 esutils = require('esutils');
27 utility = require('./utility');
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',
42 NonNullableType: 'NonNullableType',
43 OptionalType: 'OptionalType',
44 NullableType: 'NullableType',
45 NameExpression: 'NameExpression',
46 TypeApplication: 'TypeApplication',
47 StringLiteralType: 'StringLiteralType',
48 NumericLiteralType: 'NumericLiteralType',
49 BooleanLiteralType: 'BooleanLiteralType'
53 ILLEGAL: 0, // ILLEGAL
71 NAME: 18, // name token
77 function isTypeName(ch) {
78 return '><(){}[],:*|?!='.indexOf(String.fromCharCode(ch)) === -1 && !esutils.code.isWhiteSpace(ch) && !esutils.code.isLineTerminator(ch);
81 function Context(previous, index, token, value) {
82 this._previous = previous;
88 Context.prototype.restore = function () {
89 previous = this._previous;
95 Context.save = function () {
96 return new Context(previous, index, token, value);
99 function maybeAddRange(node, range) {
101 node.range = [range[0] + rangeOffset, range[1] + rangeOffset];
107 var ch = source.charAt(index);
112 function scanHexEscape(prefix) {
113 var i, len, ch, code = 0;
115 len = (prefix === 'u') ? 4 : 2;
116 for (i = 0; i < len; ++i) {
117 if (index < length && esutils.code.isHexDigit(source.charCodeAt(index))) {
119 code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
124 return String.fromCharCode(code);
127 function scanString() {
128 var str = '', quote, ch, code, unescaped, restore; //TODO review removal octal = false
129 quote = source.charAt(index);
132 while (index < length) {
138 } else if (ch === '\\') {
140 if (!esutils.code.isLineTerminator(ch.charCodeAt(0))) {
154 unescaped = scanHexEscape(ch);
173 if (esutils.code.isOctalDigit(ch.charCodeAt(0))) {
174 code = '01234567'.indexOf(ch);
176 // \0 is not octal escape sequence
177 // Deprecating unused code. TODO review removal
182 if (index < length && esutils.code.isOctalDigit(source.charCodeAt(index))) {
183 //TODO Review Removal octal = true;
184 code = code * 8 + '01234567'.indexOf(advance());
186 // 3 digits are only allowed when string starts
188 if ('0123'.indexOf(ch) >= 0 &&
190 esutils.code.isOctalDigit(source.charCodeAt(index))) {
191 code = code * 8 + '01234567'.indexOf(advance());
194 str += String.fromCharCode(code);
201 if (ch === '\r' && source.charCodeAt(index) === 0x0A /* '\n' */) {
205 } else if (esutils.code.isLineTerminator(ch.charCodeAt(0))) {
213 utility.throwError('unexpected quote');
220 function scanNumber() {
224 ch = source.charCodeAt(index);
226 if (ch !== 0x2E /* '.' */) {
228 ch = source.charCodeAt(index);
230 if (number === '0') {
231 if (ch === 0x78 /* 'x' */ || ch === 0x58 /* 'X' */) {
233 while (index < length) {
234 ch = source.charCodeAt(index);
235 if (!esutils.code.isHexDigit(ch)) {
241 if (number.length <= 2) {
243 utility.throwError('unexpected token');
246 if (index < length) {
247 ch = source.charCodeAt(index);
248 if (esutils.code.isIdentifierStartES5(ch)) {
249 utility.throwError('unexpected token');
252 value = parseInt(number, 16);
256 if (esutils.code.isOctalDigit(ch)) {
258 while (index < length) {
259 ch = source.charCodeAt(index);
260 if (!esutils.code.isOctalDigit(ch)) {
266 if (index < length) {
267 ch = source.charCodeAt(index);
268 if (esutils.code.isIdentifierStartES5(ch) || esutils.code.isDecimalDigit(ch)) {
269 utility.throwError('unexpected token');
272 value = parseInt(number, 8);
276 if (esutils.code.isDecimalDigit(ch)) {
277 utility.throwError('unexpected token');
281 while (index < length) {
282 ch = source.charCodeAt(index);
283 if (!esutils.code.isDecimalDigit(ch)) {
290 if (ch === 0x2E /* '.' */) {
292 while (index < length) {
293 ch = source.charCodeAt(index);
294 if (!esutils.code.isDecimalDigit(ch)) {
301 if (ch === 0x65 /* 'e' */ || ch === 0x45 /* 'E' */) {
304 ch = source.charCodeAt(index);
305 if (ch === 0x2B /* '+' */ || ch === 0x2D /* '-' */) {
309 ch = source.charCodeAt(index);
310 if (esutils.code.isDecimalDigit(ch)) {
312 while (index < length) {
313 ch = source.charCodeAt(index);
314 if (!esutils.code.isDecimalDigit(ch)) {
320 utility.throwError('unexpected token');
324 if (index < length) {
325 ch = source.charCodeAt(index);
326 if (esutils.code.isIdentifierStartES5(ch)) {
327 utility.throwError('unexpected token');
331 value = parseFloat(number);
336 function scanTypeName() {
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;
346 ch2 = source.charCodeAt(index + 1);
347 if (ch2 === 0x3C /* '<' */) {
361 while (index < length && esutils.code.isWhiteSpace(source.charCodeAt(index))) {
364 if (index >= length) {
369 ch = source.charCodeAt(index);
373 token = scanString();
388 token = Token.LPAREN;
393 token = Token.RPAREN;
398 token = Token.LBRACK;
403 token = Token.RBRACK;
408 token = Token.LBRACE;
413 token = Token.RBRACE;
417 if (index + 1 < length) {
418 ch = source.charCodeAt(index + 1);
419 if (ch === 0x3C /* '<' */) {
422 token = Token.DOT_LT;
426 if (ch === 0x2E /* '.' */ && index + 2 < length && source.charCodeAt(index + 2) === 0x2E /* '.' */) {
434 if (esutils.code.isDecimalDigit(ch)) {
435 token = scanNumber();
439 token = Token.ILLEGAL;
464 token = Token.QUESTION;
478 token = scanNumber();
482 if (esutils.code.isDecimalDigit(ch)) {
483 token = scanNumber();
487 // type string permits following case,
489 // namespace.module.MyClass
491 // this reduced 1 token TK_NAME
492 utility.assert(isTypeName(ch));
493 token = scanTypeName();
498 function consume(target, text) {
499 utility.assert(token === target, text || 'consumed token not matched');
503 function expect(target, message) {
504 if (token !== target) {
505 utility.throwError(message || 'unexpected token');
510 // UnionType := '(' TypeUnionList ')'
514 // | NonemptyTypeUnionList
516 // NonemptyTypeUnionList :=
518 // | TypeExpression '|' NonemptyTypeUnionList
519 function parseUnionType() {
520 var elements, startIndex = index - 1;
521 consume(Token.LPAREN, 'UnionType should start with (');
523 if (token !== Token.RPAREN) {
525 elements.push(parseTypeExpression());
526 if (token === Token.RPAREN) {
532 consume(Token.RPAREN, 'UnionType should end with )');
533 return maybeAddRange({
534 type: Syntax.UnionType,
536 }, [startIndex, previous]);
539 // ArrayType := '[' ElementTypeList ']'
541 // ElementTypeList :=
544 // | '...' TypeExpression
545 // | TypeExpression ',' ElementTypeList
546 function parseArrayType() {
547 var elements, startIndex = index - 1, restStartIndex;
548 consume(Token.LBRACK, 'ArrayType should start with [');
550 while (token !== Token.RBRACK) {
551 if (token === Token.REST) {
552 restStartIndex = index - 3;
554 elements.push(maybeAddRange({
555 type: Syntax.RestType,
556 expression: parseTypeExpression()
557 }, [restStartIndex, previous]));
560 elements.push(parseTypeExpression());
562 if (token !== Token.RBRACK) {
566 expect(Token.RBRACK);
567 return maybeAddRange({
568 type: Syntax.ArrayType,
570 }, [startIndex, previous]);
573 function parseFieldName() {
575 if (token === Token.NAME || token === Token.STRING) {
580 if (token === Token.NUMBER) {
581 consume(Token.NUMBER);
585 utility.throwError('unexpected token');
590 // | FieldName ':' TypeExpression
596 // | ReservedIdentifier
597 function parseFieldType() {
598 var key, rangeStart = previous;
600 key = parseFieldName();
601 if (token === Token.COLON) {
602 consume(Token.COLON);
603 return maybeAddRange({
604 type: Syntax.FieldType,
606 value: parseTypeExpression()
607 }, [rangeStart, previous]);
609 return maybeAddRange({
610 type: Syntax.FieldType,
613 }, [rangeStart, previous]);
616 // RecordType := '{' FieldTypeList '}'
621 // | FieldType ',' FieldTypeList
622 function parseRecordType() {
623 var fields, rangeStart = index - 1, rangeEnd;
625 consume(Token.LBRACE, 'RecordType should start with {');
627 if (token === Token.COMMA) {
628 consume(Token.COMMA);
630 while (token !== Token.RBRACE) {
631 fields.push(parseFieldType());
632 if (token !== Token.RBRACE) {
638 expect(Token.RBRACE);
639 return maybeAddRange({
640 type: Syntax.RecordType,
642 }, [rangeStart, rangeEnd]);
647 // | TagIdentifier ':' Identifier
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;
656 if (token === Token.COLON && (
658 name === 'external' ||
660 consume(Token.COLON);
665 return maybeAddRange({
666 type: Syntax.NameExpression,
668 }, [rangeStart, previous]);
671 // TypeExpressionList :=
672 // TopLevelTypeExpression
673 // | TopLevelTypeExpression ',' TypeExpressionList
674 function parseTypeExpressionList() {
677 elements.push(parseTop());
678 while (token === Token.COMMA) {
679 consume(Token.COMMA);
680 elements.push(parseTop());
687 // | NameExpression TypeApplication
689 // TypeApplication :=
690 // '.<' TypeExpressionList '>'
691 // | '<' TypeExpressionList '>' // this is extension of doctrine
692 function parseTypeName() {
693 var expr, applications, startIndex = index - value.length;
695 expr = parseNameExpression();
696 if (token === Token.DOT_LT || token === Token.LT) {
698 applications = parseTypeExpressionList();
700 return maybeAddRange({
701 type: Syntax.TypeApplication,
703 applications: applications
704 }, [startIndex, previous]);
712 // | ':' TypeExpression
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') {
721 type: Syntax.VoidLiteral
724 return parseTypeExpression();
729 // | NonRestParametersType
730 // | NonRestParametersType ',' RestParameterType
732 // RestParameterType :=
736 // NonRestParametersType :=
737 // ParameterType ',' NonRestParametersType
739 // | OptionalParametersType
741 // OptionalParametersType :=
742 // OptionalParameterType
743 // | OptionalParameterType, OptionalParametersType
745 // OptionalParameterType := ParameterType=
747 // ParameterType := TypeExpression | Identifier ':' TypeExpression
749 // Identifier is "new" or "this"
750 function parseParametersType() {
751 var params = [], optionalSequence = false, expr, rest = false, startIndex, restStartIndex = index - 3, nameStartIndex;
753 while (token !== Token.RPAREN) {
754 if (token === Token.REST) {
760 startIndex = previous;
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,
770 expression: parseTypeExpression()
771 }, [nameStartIndex, previous]);
773 if (token === Token.EQUAL) {
774 consume(Token.EQUAL);
775 expr = maybeAddRange({
776 type: Syntax.OptionalType,
778 }, [startIndex, previous]);
779 optionalSequence = true;
781 if (optionalSequence) {
782 utility.throwError('unexpected token');
786 expr = maybeAddRange({
787 type: Syntax.RestType,
789 }, [restStartIndex, previous]);
792 if (token !== Token.RPAREN) {
799 // FunctionType := 'function' FunctionSignatureType
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\'');
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);
818 if (token !== Token.RPAREN) {
819 // ParametersType or 'this'
820 if (token === Token.NAME &&
821 (value === 'this' || value === 'new')) {
823 // 'new' is Closure Compiler extension
824 isNew = value === 'new';
827 thisBinding = parseTypeName();
828 if (token === Token.COMMA) {
829 consume(Token.COMMA);
830 params = parseParametersType();
833 params = parseParametersType();
837 expect(Token.RPAREN);
840 if (token === Token.COLON) {
841 result = parseResultType();
844 fnType = maybeAddRange({
845 type: Syntax.FunctionType,
848 }, [startIndex, previous]);
850 // avoid adding null 'new' and 'this' properties
851 fnType['this'] = thisBinding;
853 fnType['new'] = true;
859 // BasicTypeExpression :=
868 function parseBasicTypeExpression() {
869 var context, startIndex;
873 return maybeAddRange({
874 type: Syntax.AllLiteral
875 }, [previous - 1, previous]);
878 return parseUnionType();
881 return parseArrayType();
884 return parseRecordType();
887 startIndex = index - value.length;
889 if (value === 'null') {
891 return maybeAddRange({
892 type: Syntax.NullLiteral
893 }, [startIndex, previous]);
896 if (value === 'undefined') {
898 return maybeAddRange({
899 type: Syntax.UndefinedLiteral
900 }, [startIndex, previous]);
903 if (value === 'true' || value === 'false') {
905 return maybeAddRange({
906 type: Syntax.BooleanLiteralType,
907 value: value === 'true'
908 }, [startIndex, previous]);
911 context = Context.save();
912 if (value === 'function') {
914 return parseFunctionType();
920 return parseTypeName();
924 return maybeAddRange({
925 type: Syntax.StringLiteralType,
927 }, [previous - value.length - 2, previous]);
931 return maybeAddRange({
932 type: Syntax.NumericLiteralType,
934 }, [previous - String(value).length, previous]);
937 utility.throwError('unexpected token');
942 // BasicTypeExpression
943 // | '?' BasicTypeExpression
944 // | '!' BasicTypeExpression
945 // | BasicTypeExpression '?'
946 // | BasicTypeExpression '!'
948 // | BasicTypeExpression '[]'
949 function parseTypeExpression() {
950 var expr, rangeStart;
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]);
962 return maybeAddRange({
963 type: Syntax.NullableType,
964 expression: parseBasicTypeExpression(),
966 }, [rangeStart, previous]);
967 } else if (token === Token.BANG) {
968 rangeStart = index - 1;
970 return maybeAddRange({
971 type: Syntax.NonNullableType,
972 expression: parseBasicTypeExpression(),
974 }, [rangeStart, previous]);
976 rangeStart = previous;
979 expr = parseBasicTypeExpression();
980 if (token === Token.BANG) {
982 return maybeAddRange({
983 type: Syntax.NonNullableType,
986 }, [rangeStart, previous]);
989 if (token === Token.QUESTION) {
990 consume(Token.QUESTION);
991 return maybeAddRange({
992 type: Syntax.NullableType,
995 }, [rangeStart, previous]);
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,
1006 }, [rangeStart, previous]),
1007 applications: [expr]
1008 }, [rangeStart, previous]);
1014 // TopLevelTypeExpression :=
1018 // This rule is Google Closure Compiler extension, not ES4
1020 // { number | string }
1021 // If strict to ES4, we should write it as
1022 // { (number|string) }
1023 function parseTop() {
1026 expr = parseTypeExpression();
1027 if (token !== Token.PIPE) {
1032 consume(Token.PIPE);
1034 elements.push(parseTypeExpression());
1035 if (token !== Token.PIPE) {
1038 consume(Token.PIPE);
1041 return maybeAddRange({
1042 type: Syntax.UnionType,
1047 function parseTopParamType() {
1050 if (token === Token.REST) {
1051 consume(Token.REST);
1052 return maybeAddRange({
1053 type: Syntax.RestType,
1054 expression: parseTop()
1059 if (token === Token.EQUAL) {
1060 consume(Token.EQUAL);
1061 return maybeAddRange({
1062 type: Syntax.OptionalType,
1070 function parseType(src, opt) {
1074 length = source.length;
1077 addRange = opt && opt.range;
1078 rangeOffset = opt && opt.startIndex || 0;
1083 if (opt && opt.midstream) {
1090 if (token !== Token.EOF) {
1091 utility.throwError('not reach to EOF');
1097 function parseParamType(src, opt) {
1101 length = source.length;
1104 addRange = opt && opt.range;
1105 rangeOffset = opt && opt.startIndex || 0;
1108 expr = parseTopParamType();
1110 if (opt && opt.midstream) {
1117 if (token !== Token.EOF) {
1118 utility.throwError('not reach to EOF');
1124 function stringifyImpl(node, compact, topLevel) {
1127 switch (node.type) {
1128 case Syntax.NullableLiteral:
1132 case Syntax.AllLiteral:
1136 case Syntax.NullLiteral:
1140 case Syntax.UndefinedLiteral:
1141 result = 'undefined';
1144 case Syntax.VoidLiteral:
1148 case Syntax.UnionType:
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 ? '|' : ' | ';
1167 case Syntax.ArrayType:
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 ? ',' : ', ';
1178 case Syntax.RecordType:
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 ? ',' : ', ';
1189 case Syntax.FieldType:
1191 result = node.key + (compact ? ':' : ': ') + stringifyImpl(node.value, compact);
1197 case Syntax.FunctionType:
1198 result = compact ? 'function(' : 'function (';
1202 result += (compact ? 'new:' : 'new: ');
1204 result += (compact ? 'this:' : 'this: ');
1207 result += stringifyImpl(node['this'], compact);
1209 if (node.params.length !== 0) {
1210 result += compact ? ',' : ', ';
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 ? ',' : ', ';
1224 result += (compact ? ':' : ': ') + stringifyImpl(node.result, compact);
1228 case Syntax.ParameterType:
1229 result = node.name + (compact ? ':' : ': ') + stringifyImpl(node.expression, compact);
1232 case Syntax.RestType:
1234 if (node.expression) {
1235 result += stringifyImpl(node.expression, compact);
1239 case Syntax.NonNullableType:
1241 result = '!' + stringifyImpl(node.expression, compact);
1243 result = stringifyImpl(node.expression, compact) + '!';
1247 case Syntax.OptionalType:
1248 result = stringifyImpl(node.expression, compact) + '=';
1251 case Syntax.NullableType:
1253 result = '?' + stringifyImpl(node.expression, compact);
1255 result = stringifyImpl(node.expression, compact) + '?';
1259 case Syntax.NameExpression:
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 ? ',' : ', ';
1274 case Syntax.StringLiteralType:
1275 result = '"' + node.value + '"';
1278 case Syntax.NumericLiteralType:
1279 result = String(node.value);
1282 case Syntax.BooleanLiteralType:
1283 result = String(node.value);
1287 utility.throwError('Unknown type ' + node.type);
1293 function stringify(node, options) {
1294 if (options == null) {
1297 return stringifyImpl(node, options.compact, options.topLevel);
1300 exports.parseType = parseType;
1301 exports.parseParamType = parseParamType;
1302 exports.stringify = stringify;
1303 exports.Syntax = Syntax;
1305 /* vim: set sw=4 ts=4 et tw=80 : */