blob: bdd3c394fc7d1fb57c3beb38f9655fe327c4db0f [file] [log] [blame]
/*
* @fileoverview Type expression parser.
* @author Yusuke Suzuki <utatane.tea@gmail.com>
* @author Dan Tao <daniel.tao@gmail.com>
* @author Andrew Eisenberg <andrew@eisenberg.as>
*/
// "typed", the Type Expression Parser for doctrine.
(function () {
'use strict';
var Syntax,
Token,
source,
length,
index,
previous,
token,
value,
esutils,
utility,
rangeOffset,
addRange;
esutils = require('esutils');
utility = require('./utility');
Syntax = {
NullableLiteral: 'NullableLiteral',
AllLiteral: 'AllLiteral',
NullLiteral: 'NullLiteral',
UndefinedLiteral: 'UndefinedLiteral',
VoidLiteral: 'VoidLiteral',
UnionType: 'UnionType',
ArrayType: 'ArrayType',
RecordType: 'RecordType',
FieldType: 'FieldType',
FunctionType: 'FunctionType',
ParameterType: 'ParameterType',
RestType: 'RestType',
NonNullableType: 'NonNullableType',
OptionalType: 'OptionalType',
NullableType: 'NullableType',
NameExpression: 'NameExpression',
TypeApplication: 'TypeApplication',
StringLiteralType: 'StringLiteralType',
NumericLiteralType: 'NumericLiteralType',
BooleanLiteralType: 'BooleanLiteralType'
};
Token = {
ILLEGAL: 0, // ILLEGAL
DOT_LT: 1, // .<
REST: 2, // ...
LT: 3, // <
GT: 4, // >
LPAREN: 5, // (
RPAREN: 6, // )
LBRACE: 7, // {
RBRACE: 8, // }
LBRACK: 9, // [
RBRACK: 10, // ]
COMMA: 11, // ,
COLON: 12, // :
STAR: 13, // *
PIPE: 14, // |
QUESTION: 15, // ?
BANG: 16, // !
EQUAL: 17, // =
NAME: 18, // name token
STRING: 19, // string
NUMBER: 20, // number
EOF: 21
};
function isTypeName(ch) {
return '><(){}[],:*|?!='.indexOf(String.fromCharCode(ch)) === -1 && !esutils.code.isWhiteSpace(ch) && !esutils.code.isLineTerminator(ch);
}
function Context(previous, index, token, value) {
this._previous = previous;
this._index = index;
this._token = token;
this._value = value;
}
Context.prototype.restore = function () {
previous = this._previous;
index = this._index;
token = this._token;
value = this._value;
};
Context.save = function () {
return new Context(previous, index, token, value);
};
function maybeAddRange(node, range) {
if (addRange) {
node.range = [range[0] + rangeOffset, range[1] + rangeOffset];
}
return node;
}
function advance() {
var ch = source.charAt(index);
index += 1;
return ch;
}
function scanHexEscape(prefix) {
var i, len, ch, code = 0;
len = (prefix === 'u') ? 4 : 2;
for (i = 0; i < len; ++i) {
if (index < length && esutils.code.isHexDigit(source.charCodeAt(index))) {
ch = advance();
code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
} else {
return '';
}
}
return String.fromCharCode(code);
}
function scanString() {
var str = '', quote, ch, code, unescaped, restore; //TODO review removal octal = false
quote = source.charAt(index);
++index;
while (index < length) {
ch = advance();
if (ch === quote) {
quote = '';
break;
} else if (ch === '\\') {
ch = advance();
if (!esutils.code.isLineTerminator(ch.charCodeAt(0))) {
switch (ch) {
case 'n':
str += '\n';
break;
case 'r':
str += '\r';
break;
case 't':
str += '\t';
break;
case 'u':
case 'x':
restore = index;
unescaped = scanHexEscape(ch);
if (unescaped) {
str += unescaped;
} else {
index = restore;
str += ch;
}
break;
case 'b':
str += '\b';
break;
case 'f':
str += '\f';
break;
case 'v':
str += '\v';
break;
default:
if (esutils.code.isOctalDigit(ch.charCodeAt(0))) {
code = '01234567'.indexOf(ch);
// \0 is not octal escape sequence
// Deprecating unused code. TODO review removal
//if (code !== 0) {
// octal = true;
//}
if (index < length && esutils.code.isOctalDigit(source.charCodeAt(index))) {
//TODO Review Removal octal = true;
code = code * 8 + '01234567'.indexOf(advance());
// 3 digits are only allowed when string starts
// with 0, 1, 2, 3
if ('0123'.indexOf(ch) >= 0 &&
index < length &&
esutils.code.isOctalDigit(source.charCodeAt(index))) {
code = code * 8 + '01234567'.indexOf(advance());
}
}
str += String.fromCharCode(code);
} else {
str += ch;
}
break;
}
} else {
if (ch === '\r' && source.charCodeAt(index) === 0x0A /* '\n' */) {
++index;
}
}
} else if (esutils.code.isLineTerminator(ch.charCodeAt(0))) {
break;
} else {
str += ch;
}
}
if (quote !== '') {
utility.throwError('unexpected quote');
}
value = str;
return Token.STRING;
}
function scanNumber() {
var number, ch;
number = '';
ch = source.charCodeAt(index);
if (ch !== 0x2E /* '.' */) {
number = advance();
ch = source.charCodeAt(index);
if (number === '0') {
if (ch === 0x78 /* 'x' */ || ch === 0x58 /* 'X' */) {
number += advance();
while (index < length) {
ch = source.charCodeAt(index);
if (!esutils.code.isHexDigit(ch)) {
break;
}
number += advance();
}
if (number.length <= 2) {
// only 0x
utility.throwError('unexpected token');
}
if (index < length) {
ch = source.charCodeAt(index);
if (esutils.code.isIdentifierStartES5(ch)) {
utility.throwError('unexpected token');
}
}
value = parseInt(number, 16);
return Token.NUMBER;
}
if (esutils.code.isOctalDigit(ch)) {
number += advance();
while (index < length) {
ch = source.charCodeAt(index);
if (!esutils.code.isOctalDigit(ch)) {
break;
}
number += advance();
}
if (index < length) {
ch = source.charCodeAt(index);
if (esutils.code.isIdentifierStartES5(ch) || esutils.code.isDecimalDigit(ch)) {
utility.throwError('unexpected token');
}
}
value = parseInt(number, 8);
return Token.NUMBER;
}
if (esutils.code.isDecimalDigit(ch)) {
utility.throwError('unexpected token');
}
}
while (index < length) {
ch = source.charCodeAt(index);
if (!esutils.code.isDecimalDigit(ch)) {
break;
}
number += advance();
}
}
if (ch === 0x2E /* '.' */) {
number += advance();
while (index < length) {
ch = source.charCodeAt(index);
if (!esutils.code.isDecimalDigit(ch)) {
break;
}
number += advance();
}
}
if (ch === 0x65 /* 'e' */ || ch === 0x45 /* 'E' */) {
number += advance();
ch = source.charCodeAt(index);
if (ch === 0x2B /* '+' */ || ch === 0x2D /* '-' */) {
number += advance();
}
ch = source.charCodeAt(index);
if (esutils.code.isDecimalDigit(ch)) {
number += advance();
while (index < length) {
ch = source.charCodeAt(index);
if (!esutils.code.isDecimalDigit(ch)) {
break;
}
number += advance();
}
} else {
utility.throwError('unexpected token');
}
}
if (index < length) {
ch = source.charCodeAt(index);
if (esutils.code.isIdentifierStartES5(ch)) {
utility.throwError('unexpected token');
}
}
value = parseFloat(number);
return Token.NUMBER;
}
function scanTypeName() {
var ch, ch2;
value = advance();
while (index < length && isTypeName(source.charCodeAt(index))) {
ch = source.charCodeAt(index);
if (ch === 0x2E /* '.' */) {
if ((index + 1) >= length) {
return Token.ILLEGAL;
}
ch2 = source.charCodeAt(index + 1);
if (ch2 === 0x3C /* '<' */) {
break;
}
}
value += advance();
}
return Token.NAME;
}
function next() {
var ch;
previous = index;
while (index < length && esutils.code.isWhiteSpace(source.charCodeAt(index))) {
advance();
}
if (index >= length) {
token = Token.EOF;
return token;
}
ch = source.charCodeAt(index);
switch (ch) {
case 0x27: /* ''' */
case 0x22: /* '"' */
token = scanString();
return token;
case 0x3A: /* ':' */
advance();
token = Token.COLON;
return token;
case 0x2C: /* ',' */
advance();
token = Token.COMMA;
return token;
case 0x28: /* '(' */
advance();
token = Token.LPAREN;
return token;
case 0x29: /* ')' */
advance();
token = Token.RPAREN;
return token;
case 0x5B: /* '[' */
advance();
token = Token.LBRACK;
return token;
case 0x5D: /* ']' */
advance();
token = Token.RBRACK;
return token;
case 0x7B: /* '{' */
advance();
token = Token.LBRACE;
return token;
case 0x7D: /* '}' */
advance();
token = Token.RBRACE;
return token;
case 0x2E: /* '.' */
if (index + 1 < length) {
ch = source.charCodeAt(index + 1);
if (ch === 0x3C /* '<' */) {
advance(); // '.'
advance(); // '<'
token = Token.DOT_LT;
return token;
}
if (ch === 0x2E /* '.' */ && index + 2 < length && source.charCodeAt(index + 2) === 0x2E /* '.' */) {
advance(); // '.'
advance(); // '.'
advance(); // '.'
token = Token.REST;
return token;
}
if (esutils.code.isDecimalDigit(ch)) {
token = scanNumber();
return token;
}
}
token = Token.ILLEGAL;
return token;
case 0x3C: /* '<' */
advance();
token = Token.LT;
return token;
case 0x3E: /* '>' */
advance();
token = Token.GT;
return token;
case 0x2A: /* '*' */
advance();
token = Token.STAR;
return token;
case 0x7C: /* '|' */
advance();
token = Token.PIPE;
return token;
case 0x3F: /* '?' */
advance();
token = Token.QUESTION;
return token;
case 0x21: /* '!' */
advance();
token = Token.BANG;
return token;
case 0x3D: /* '=' */
advance();
token = Token.EQUAL;
return token;
case 0x2D: /* '-' */
token = scanNumber();
return token;
default:
if (esutils.code.isDecimalDigit(ch)) {
token = scanNumber();
return token;
}
// type string permits following case,
//
// namespace.module.MyClass
//
// this reduced 1 token TK_NAME
utility.assert(isTypeName(ch));
token = scanTypeName();
return token;
}
}
function consume(target, text) {
utility.assert(token === target, text || 'consumed token not matched');
next();
}
function expect(target, message) {
if (token !== target) {
utility.throwError(message || 'unexpected token');
}
next();
}
// UnionType := '(' TypeUnionList ')'
//
// TypeUnionList :=
// <<empty>>
// | NonemptyTypeUnionList
//
// NonemptyTypeUnionList :=
// TypeExpression
// | TypeExpression '|' NonemptyTypeUnionList
function parseUnionType() {
var elements, startIndex = index - 1;
consume(Token.LPAREN, 'UnionType should start with (');
elements = [];
if (token !== Token.RPAREN) {
while (true) {
elements.push(parseTypeExpression());
if (token === Token.RPAREN) {
break;
}
expect(Token.PIPE);
}
}
consume(Token.RPAREN, 'UnionType should end with )');
return maybeAddRange({
type: Syntax.UnionType,
elements: elements
}, [startIndex, previous]);
}
// ArrayType := '[' ElementTypeList ']'
//
// ElementTypeList :=
// <<empty>>
// | TypeExpression
// | '...' TypeExpression
// | TypeExpression ',' ElementTypeList
function parseArrayType() {
var elements, startIndex = index - 1, restStartIndex;
consume(Token.LBRACK, 'ArrayType should start with [');
elements = [];
while (token !== Token.RBRACK) {
if (token === Token.REST) {
restStartIndex = index - 3;
consume(Token.REST);
elements.push(maybeAddRange({
type: Syntax.RestType,
expression: parseTypeExpression()
}, [restStartIndex, previous]));
break;
} else {
elements.push(parseTypeExpression());
}
if (token !== Token.RBRACK) {
expect(Token.COMMA);
}
}
expect(Token.RBRACK);
return maybeAddRange({
type: Syntax.ArrayType,
elements: elements
}, [startIndex, previous]);
}
function parseFieldName() {
var v = value;
if (token === Token.NAME || token === Token.STRING) {
next();
return v;
}
if (token === Token.NUMBER) {
consume(Token.NUMBER);
return String(v);
}
utility.throwError('unexpected token');
}
// FieldType :=
// FieldName
// | FieldName ':' TypeExpression
//
// FieldName :=
// NameExpression
// | StringLiteral
// | NumberLiteral
// | ReservedIdentifier
function parseFieldType() {
var key, rangeStart = previous;
key = parseFieldName();
if (token === Token.COLON) {
consume(Token.COLON);
return maybeAddRange({
type: Syntax.FieldType,
key: key,
value: parseTypeExpression()
}, [rangeStart, previous]);
}
return maybeAddRange({
type: Syntax.FieldType,
key: key,
value: null
}, [rangeStart, previous]);
}
// RecordType := '{' FieldTypeList '}'
//
// FieldTypeList :=
// <<empty>>
// | FieldType
// | FieldType ',' FieldTypeList
function parseRecordType() {
var fields, rangeStart = index - 1, rangeEnd;
consume(Token.LBRACE, 'RecordType should start with {');
fields = [];
if (token === Token.COMMA) {
consume(Token.COMMA);
} else {
while (token !== Token.RBRACE) {
fields.push(parseFieldType());
if (token !== Token.RBRACE) {
expect(Token.COMMA);
}
}
}
rangeEnd = index;
expect(Token.RBRACE);
return maybeAddRange({
type: Syntax.RecordType,
fields: fields
}, [rangeStart, rangeEnd]);
}
// NameExpression :=
// Identifier
// | TagIdentifier ':' Identifier
//
// Tag identifier is one of "module", "external" or "event"
// Identifier is the same as Token.NAME, including any dots, something like
// namespace.module.MyClass
function parseNameExpression() {
var name = value, rangeStart = index - name.length;
expect(Token.NAME);
if (token === Token.COLON && (
name === 'module' ||
name === 'external' ||
name === 'event')) {
consume(Token.COLON);
name += ':' + value;
expect(Token.NAME);
}
return maybeAddRange({
type: Syntax.NameExpression,
name: name
}, [rangeStart, previous]);
}
// TypeExpressionList :=
// TopLevelTypeExpression
// | TopLevelTypeExpression ',' TypeExpressionList
function parseTypeExpressionList() {
var elements = [];
elements.push(parseTop());
while (token === Token.COMMA) {
consume(Token.COMMA);
elements.push(parseTop());
}
return elements;
}
// TypeName :=
// NameExpression
// | NameExpression TypeApplication
//
// TypeApplication :=
// '.<' TypeExpressionList '>'
// | '<' TypeExpressionList '>' // this is extension of doctrine
function parseTypeName() {
var expr, applications, startIndex = index - value.length;
expr = parseNameExpression();
if (token === Token.DOT_LT || token === Token.LT) {
next();
applications = parseTypeExpressionList();
expect(Token.GT);
return maybeAddRange({
type: Syntax.TypeApplication,
expression: expr,
applications: applications
}, [startIndex, previous]);
}
return expr;
}
// ResultType :=
// <<empty>>
// | ':' void
// | ':' TypeExpression
//
// BNF is above
// but, we remove <<empty>> pattern, so token is always TypeToken::COLON
function parseResultType() {
consume(Token.COLON, 'ResultType should start with :');
if (token === Token.NAME && value === 'void') {
consume(Token.NAME);
return {
type: Syntax.VoidLiteral
};
}
return parseTypeExpression();
}
// ParametersType :=
// RestParameterType
// | NonRestParametersType
// | NonRestParametersType ',' RestParameterType
//
// RestParameterType :=
// '...'
// '...' Identifier
//
// NonRestParametersType :=
// ParameterType ',' NonRestParametersType
// | ParameterType
// | OptionalParametersType
//
// OptionalParametersType :=
// OptionalParameterType
// | OptionalParameterType, OptionalParametersType
//
// OptionalParameterType := ParameterType=
//
// ParameterType := TypeExpression | Identifier ':' TypeExpression
//
// Identifier is "new" or "this"
function parseParametersType() {
var params = [], optionalSequence = false, expr, rest = false, startIndex, restStartIndex = index - 3, nameStartIndex;
while (token !== Token.RPAREN) {
if (token === Token.REST) {
// RestParameterType
consume(Token.REST);
rest = true;
}
startIndex = previous;
expr = parseTypeExpression();
if (expr.type === Syntax.NameExpression && token === Token.COLON) {
nameStartIndex = previous - expr.name.length;
// Identifier ':' TypeExpression
consume(Token.COLON);
expr = maybeAddRange({
type: Syntax.ParameterType,
name: expr.name,
expression: parseTypeExpression()
}, [nameStartIndex, previous]);
}
if (token === Token.EQUAL) {
consume(Token.EQUAL);
expr = maybeAddRange({
type: Syntax.OptionalType,
expression: expr
}, [startIndex, previous]);
optionalSequence = true;
} else {
if (optionalSequence) {
utility.throwError('unexpected token');
}
}
if (rest) {
expr = maybeAddRange({
type: Syntax.RestType,
expression: expr
}, [restStartIndex, previous]);
}
params.push(expr);
if (token !== Token.RPAREN) {
expect(Token.COMMA);
}
}
return params;
}
// FunctionType := 'function' FunctionSignatureType
//
// FunctionSignatureType :=
// | TypeParameters '(' ')' ResultType
// | TypeParameters '(' ParametersType ')' ResultType
// | TypeParameters '(' 'this' ':' TypeName ')' ResultType
// | TypeParameters '(' 'this' ':' TypeName ',' ParametersType ')' ResultType
function parseFunctionType() {
var isNew, thisBinding, params, result, fnType, startIndex = index - value.length;
utility.assert(token === Token.NAME && value === 'function', 'FunctionType should start with \'function\'');
consume(Token.NAME);
// Google Closure Compiler is not implementing TypeParameters.
// So we do not. if we don't get '(', we see it as error.
expect(Token.LPAREN);
isNew = false;
params = [];
thisBinding = null;
if (token !== Token.RPAREN) {
// ParametersType or 'this'
if (token === Token.NAME &&
(value === 'this' || value === 'new')) {
// 'this' or 'new'
// 'new' is Closure Compiler extension
isNew = value === 'new';
consume(Token.NAME);
expect(Token.COLON);
thisBinding = parseTypeName();
if (token === Token.COMMA) {
consume(Token.COMMA);
params = parseParametersType();
}
} else {
params = parseParametersType();
}
}
expect(Token.RPAREN);
result = null;
if (token === Token.COLON) {
result = parseResultType();
}
fnType = maybeAddRange({
type: Syntax.FunctionType,
params: params,
result: result
}, [startIndex, previous]);
if (thisBinding) {
// avoid adding null 'new' and 'this' properties
fnType['this'] = thisBinding;
if (isNew) {
fnType['new'] = true;
}
}
return fnType;
}
// BasicTypeExpression :=
// '*'
// | 'null'
// | 'undefined'
// | TypeName
// | FunctionType
// | UnionType
// | RecordType
// | ArrayType
function parseBasicTypeExpression() {
var context, startIndex;
switch (token) {
case Token.STAR:
consume(Token.STAR);
return maybeAddRange({
type: Syntax.AllLiteral
}, [previous - 1, previous]);
case Token.LPAREN:
return parseUnionType();
case Token.LBRACK:
return parseArrayType();
case Token.LBRACE:
return parseRecordType();
case Token.NAME:
startIndex = index - value.length;
if (value === 'null') {
consume(Token.NAME);
return maybeAddRange({
type: Syntax.NullLiteral
}, [startIndex, previous]);
}
if (value === 'undefined') {
consume(Token.NAME);
return maybeAddRange({
type: Syntax.UndefinedLiteral
}, [startIndex, previous]);
}
if (value === 'true' || value === 'false') {
consume(Token.NAME);
return maybeAddRange({
type: Syntax.BooleanLiteralType,
value: value === 'true'
}, [startIndex, previous]);
}
context = Context.save();
if (value === 'function') {
try {
return parseFunctionType();
} catch (e) {
context.restore();
}
}
return parseTypeName();
case Token.STRING:
next();
return maybeAddRange({
type: Syntax.StringLiteralType,
value: value
}, [previous - value.length - 2, previous]);
case Token.NUMBER:
next();
return maybeAddRange({
type: Syntax.NumericLiteralType,
value: value
}, [previous - String(value).length, previous]);
default:
utility.throwError('unexpected token');
}
}
// TypeExpression :=
// BasicTypeExpression
// | '?' BasicTypeExpression
// | '!' BasicTypeExpression
// | BasicTypeExpression '?'
// | BasicTypeExpression '!'
// | '?'
// | BasicTypeExpression '[]'
function parseTypeExpression() {
var expr, rangeStart;
if (token === Token.QUESTION) {
rangeStart = index - 1;
consume(Token.QUESTION);
if (token === Token.COMMA || token === Token.EQUAL || token === Token.RBRACE ||
token === Token.RPAREN || token === Token.PIPE || token === Token.EOF ||
token === Token.RBRACK || token === Token.GT) {
return maybeAddRange({
type: Syntax.NullableLiteral
}, [rangeStart, previous]);
}
return maybeAddRange({
type: Syntax.NullableType,
expression: parseBasicTypeExpression(),
prefix: true
}, [rangeStart, previous]);
} else if (token === Token.BANG) {
rangeStart = index - 1;
consume(Token.BANG);
return maybeAddRange({
type: Syntax.NonNullableType,
expression: parseBasicTypeExpression(),
prefix: true
}, [rangeStart, previous]);
} else {
rangeStart = previous;
}
expr = parseBasicTypeExpression();
if (token === Token.BANG) {
consume(Token.BANG);
return maybeAddRange({
type: Syntax.NonNullableType,
expression: expr,
prefix: false
}, [rangeStart, previous]);
}
if (token === Token.QUESTION) {
consume(Token.QUESTION);
return maybeAddRange({
type: Syntax.NullableType,
expression: expr,
prefix: false
}, [rangeStart, previous]);
}
if (token === Token.LBRACK) {
consume(Token.LBRACK);
expect(Token.RBRACK, 'expected an array-style type declaration (' + value + '[])');
return maybeAddRange({
type: Syntax.TypeApplication,
expression: maybeAddRange({
type: Syntax.NameExpression,
name: 'Array'
}, [rangeStart, previous]),
applications: [expr]
}, [rangeStart, previous]);
}
return expr;
}
// TopLevelTypeExpression :=
// TypeExpression
// | TypeUnionList
//
// This rule is Google Closure Compiler extension, not ES4
// like,
// { number | string }
// If strict to ES4, we should write it as
// { (number|string) }
function parseTop() {
var expr, elements;
expr = parseTypeExpression();
if (token !== Token.PIPE) {
return expr;
}
elements = [expr];
consume(Token.PIPE);
while (true) {
elements.push(parseTypeExpression());
if (token !== Token.PIPE) {
break;
}
consume(Token.PIPE);
}
return maybeAddRange({
type: Syntax.UnionType,
elements: elements
}, [0, index]);
}
function parseTopParamType() {
var expr;
if (token === Token.REST) {
consume(Token.REST);
return maybeAddRange({
type: Syntax.RestType,
expression: parseTop()
}, [0, index]);
}
expr = parseTop();
if (token === Token.EQUAL) {
consume(Token.EQUAL);
return maybeAddRange({
type: Syntax.OptionalType,
expression: expr
}, [0, index]);
}
return expr;
}
function parseType(src, opt) {
var expr;
source = src;
length = source.length;
index = 0;
previous = 0;
addRange = opt && opt.range;
rangeOffset = opt && opt.startIndex || 0;
next();
expr = parseTop();
if (opt && opt.midstream) {
return {
expression: expr,
index: previous
};
}
if (token !== Token.EOF) {
utility.throwError('not reach to EOF');
}
return expr;
}
function parseParamType(src, opt) {
var expr;
source = src;
length = source.length;
index = 0;
previous = 0;
addRange = opt && opt.range;
rangeOffset = opt && opt.startIndex || 0;
next();
expr = parseTopParamType();
if (opt && opt.midstream) {
return {
expression: expr,
index: previous
};
}
if (token !== Token.EOF) {
utility.throwError('not reach to EOF');
}
return expr;
}
function stringifyImpl(node, compact, topLevel) {
var result, i, iz;
switch (node.type) {
case Syntax.NullableLiteral:
result = '?';
break;
case Syntax.AllLiteral:
result = '*';
break;
case Syntax.NullLiteral:
result = 'null';
break;
case Syntax.UndefinedLiteral:
result = 'undefined';
break;
case Syntax.VoidLiteral:
result = 'void';
break;
case Syntax.UnionType:
if (!topLevel) {
result = '(';
} else {
result = '';
}
for (i = 0, iz = node.elements.length; i < iz; ++i) {
result += stringifyImpl(node.elements[i], compact);
if ((i + 1) !== iz) {
result += compact ? '|' : ' | ';
}
}
if (!topLevel) {
result += ')';
}
break;
case Syntax.ArrayType:
result = '[';
for (i = 0, iz = node.elements.length; i < iz; ++i) {
result += stringifyImpl(node.elements[i], compact);
if ((i + 1) !== iz) {
result += compact ? ',' : ', ';
}
}
result += ']';
break;
case Syntax.RecordType:
result = '{';
for (i = 0, iz = node.fields.length; i < iz; ++i) {
result += stringifyImpl(node.fields[i], compact);
if ((i + 1) !== iz) {
result += compact ? ',' : ', ';
}
}
result += '}';
break;
case Syntax.FieldType:
if (node.value) {
result = node.key + (compact ? ':' : ': ') + stringifyImpl(node.value, compact);
} else {
result = node.key;
}
break;
case Syntax.FunctionType:
result = compact ? 'function(' : 'function (';
if (node['this']) {
if (node['new']) {
result += (compact ? 'new:' : 'new: ');
} else {
result += (compact ? 'this:' : 'this: ');
}
result += stringifyImpl(node['this'], compact);
if (node.params.length !== 0) {
result += compact ? ',' : ', ';
}
}
for (i = 0, iz = node.params.length; i < iz; ++i) {
result += stringifyImpl(node.params[i], compact);
if ((i + 1) !== iz) {
result += compact ? ',' : ', ';
}
}
result += ')';
if (node.result) {
result += (compact ? ':' : ': ') + stringifyImpl(node.result, compact);
}
break;
case Syntax.ParameterType:
result = node.name + (compact ? ':' : ': ') + stringifyImpl(node.expression, compact);
break;
case Syntax.RestType:
result = '...';
if (node.expression) {
result += stringifyImpl(node.expression, compact);
}
break;
case Syntax.NonNullableType:
if (node.prefix) {
result = '!' + stringifyImpl(node.expression, compact);
} else {
result = stringifyImpl(node.expression, compact) + '!';
}
break;
case Syntax.OptionalType:
result = stringifyImpl(node.expression, compact) + '=';
break;
case Syntax.NullableType:
if (node.prefix) {
result = '?' + stringifyImpl(node.expression, compact);
} else {
result = stringifyImpl(node.expression, compact) + '?';
}
break;
case Syntax.NameExpression:
result = node.name;
break;
case Syntax.TypeApplication:
result = stringifyImpl(node.expression, compact) + '.<';
for (i = 0, iz = node.applications.length; i < iz; ++i) {
result += stringifyImpl(node.applications[i], compact);
if ((i + 1) !== iz) {
result += compact ? ',' : ', ';
}
}
result += '>';
break;
case Syntax.StringLiteralType:
result = '"' + node.value + '"';
break;
case Syntax.NumericLiteralType:
result = String(node.value);
break;
case Syntax.BooleanLiteralType:
result = String(node.value);
break;
default:
utility.throwError('Unknown type ' + node.type);
}
return result;
}
function stringify(node, options) {
if (options == null) {
options = {};
}
return stringifyImpl(node, options.compact, options.topLevel);
}
exports.parseType = parseType;
exports.parseParamType = parseParamType;
exports.stringify = stringify;
exports.Syntax = Syntax;
}());
/* vim: set sw=4 ts=4 et tw=80 : */