graphql
Version:
A Query Language and Runtime which can target any service.
417 lines (381 loc) • 12 kB
JavaScript
/* @flow /
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
;
/**
* A representation of a lexed Token. Value is optional, is it is
* not needed for punctuators like BANG or PAREN_L.
*/
var _Object$defineProperty = require('babel-runtime/core-js/object/define-property')['default'];
_Object$defineProperty(exports, '__esModule', {
value: true
});
exports.lex = lex;
exports.getTokenDesc = getTokenDesc;
exports.getTokenKindDesc = getTokenKindDesc;
var _error = require('./error');
/**
* Given a Source object, this returns a Lexer for that source.
* A Lexer is a function that acts like a generator in that every time
* it is called, it returns the next token in the Source. Assuming the
* source lexes, the final Token omitted by the lexer will be of kind
* EOF, after which the lexer will repeatedly return EOF tokens whenever
* called.
*
* The argument to the lexer function is optional, and can be used to
* rewind or fast forward the lexer to a new position in the source.
*/
function lex(source) {
var prevPosition = 0;
return function nextToken(resetPosition) {
var token = readToken(source, resetPosition === undefined ? prevPosition : resetPosition);
prevPosition = token.end;
return token;
};
}
/**
* An enum describing the different kinds of tokens that the lexer omits.
*/
var TokenKind = {
EOF: 1,
BANG: 2,
DOLLAR: 3,
PAREN_L: 4,
PAREN_R: 5,
SPREAD: 6,
COLON: 7,
EQUALS: 8,
AT: 9,
BRACKET_L: 10,
BRACKET_R: 11,
BRACE_L: 12,
PIPE: 13,
BRACE_R: 14,
NAME: 15,
VARIABLE: 16,
INT: 17,
FLOAT: 18,
STRING: 19
};
exports.TokenKind = TokenKind;
/**
* A helper function to describe a token as a string for debugging
*/
function getTokenDesc(token) {
return token.value ? '' + getTokenKindDesc(token.kind) + ' "' + token.value + '"' : getTokenKindDesc(token.kind);
}
/**
* A helper function to describe a token kind as a string for debugging
*/
function getTokenKindDesc(kind) {
return tokenDescription[kind];
}
var tokenDescription = {};
tokenDescription[TokenKind.EOF] = 'EOF';
tokenDescription[TokenKind.BANG] = '!';
tokenDescription[TokenKind.DOLLAR] = '$';
tokenDescription[TokenKind.PAREN_L] = '(';
tokenDescription[TokenKind.PAREN_R] = ')';
tokenDescription[TokenKind.SPREAD] = '...';
tokenDescription[TokenKind.COLON] = ':';
tokenDescription[TokenKind.EQUALS] = '=';
tokenDescription[TokenKind.AT] = '@';
tokenDescription[TokenKind.BRACKET_L] = '[';
tokenDescription[TokenKind.BRACKET_R] = ']';
tokenDescription[TokenKind.BRACE_L] = '{';
tokenDescription[TokenKind.PIPE] = '|';
tokenDescription[TokenKind.BRACE_R] = '}';
tokenDescription[TokenKind.NAME] = 'Name';
tokenDescription[TokenKind.VARIABLE] = 'Variable';
tokenDescription[TokenKind.INT] = 'Int';
tokenDescription[TokenKind.FLOAT] = 'Float';
tokenDescription[TokenKind.STRING] = 'String';
var charCodeAt = String.prototype.charCodeAt;
var fromCharCode = String.fromCharCode;
var slice = String.prototype.slice;
/**
* Helper function for constructing the Token object.
*/
function makeToken(kind, start, end, value) {
return { kind: kind, start: start, end: end, value: value };
}
/**
* Gets the next token from the source starting at the given position.
*
* This skips over whitespace and comments until it finds the next lexable
* token, then lexes punctuators immediately or calls the appropriate helper
* fucntion for more complicated tokens.
*/
function readToken(source, fromPosition) {
var body = source.body;
var bodyLength = body.length;
var position = positionAfterWhitespace(body, fromPosition);
var code = charCodeAt.call(body, position);
if (position >= bodyLength) {
return makeToken(TokenKind.EOF, position, position);
}
switch (code) {
// !
case 33:
return makeToken(TokenKind.BANG, position, position + 1);
// $
case 36:
return makeToken(TokenKind.DOLLAR, position, position + 1);
// (
case 40:
return makeToken(TokenKind.PAREN_L, position, position + 1);
// )
case 41:
return makeToken(TokenKind.PAREN_R, position, position + 1);
// .
case 46:
if (charCodeAt.call(body, position + 1) === 46 && charCodeAt.call(body, position + 2) === 46) {
return makeToken(TokenKind.SPREAD, position, position + 3);
}
break;
// :
case 58:
return makeToken(TokenKind.COLON, position, position + 1);
// =
case 61:
return makeToken(TokenKind.EQUALS, position, position + 1);
// @
case 64:
return makeToken(TokenKind.AT, position, position + 1);
// [
case 91:
return makeToken(TokenKind.BRACKET_L, position, position + 1);
// ]
case 93:
return makeToken(TokenKind.BRACKET_R, position, position + 1);
// {
case 123:
return makeToken(TokenKind.BRACE_L, position, position + 1);
// |
case 124:
return makeToken(TokenKind.PIPE, position, position + 1);
// }
case 125:
return makeToken(TokenKind.BRACE_R, position, position + 1);
// A-Z
case 65:case 66:case 67:case 68:case 69:case 70:case 71:case 72:
case 73:case 74:case 75:case 76:case 77:case 78:case 79:case 80:
case 81:case 82:case 83:case 84:case 85:case 86:case 87:case 88:
case 89:case 90:
// _
case 95:
// a-z
case 97:case 98:case 99:case 100:case 101:case 102:case 103:case 104:
case 105:case 106:case 107:case 108:case 109:case 110:case 111:
case 112:case 113:case 114:case 115:case 116:case 117:case 118:
case 119:case 120:case 121:case 122:
return readName(source, position);
// -
case 45:
// 0-9
case 48:case 49:case 50:case 51:case 52:
case 53:case 54:case 55:case 56:case 57:
return readNumber(source, position, code);
// "
case 34:
return readString(source, position);
}
throw (0, _error.error)(source, position, 'Unexpected character "' + fromCharCode(code) + '"');
}
/**
* Reads from body starting at startPosition until it finds a non-whitespace
* or commented character, then returns the position of that character for
* lexing.
*/
function positionAfterWhitespace(body, startPosition) {
var bodyLength = body.length;
var position = startPosition;
while (position < bodyLength) {
var code = charCodeAt.call(body, position);
// Skip whitespace
if (code === 32 || // space
code === 44 || // comma
code === 160 || // '\xa0'
code === 8232 || // line separator
code === 8233 || // paragraph separator
code > 8 && code < 14 // whitespace
) {
++position;
// Skip comments
} else if (code === 35) {
// #
++position;
while (position < bodyLength && (code = charCodeAt.call(body, position)) && code !== 10 && code !== 13 && code !== 8232 && code !== 8233) {
++position;
}
} else {
break;
}
}
return position;
}
/**
* Reads a number token from the source file, either a float
* or an int depending on whether a decimal point appears.
*
* Int: -?(0|[1-9][0-9]*)
* Float: -?(0|[1-9][0-9]*)\.[0-9]+(e-?[0-9]+)?
*/
function readNumber(source, start, firstCode) {
var code = firstCode;
var body = source.body;
var position = start;
var isFloat = false;
if (code === 45) {
// -
code = charCodeAt.call(body, ++position);
}
if (code === 48) {
// 0
code = charCodeAt.call(body, ++position);
} else if (code >= 49 && code <= 57) {
// 1 - 9
do {
code = charCodeAt.call(body, ++position);
} while (code >= 48 && code <= 57); // 0 - 9
} else {
throw (0, _error.error)(source, position, 'Invalid number');
}
if (code === 46) {
// .
isFloat = true;
code = charCodeAt.call(body, ++position);
if (code >= 48 && code <= 57) {
// 0 - 9
do {
code = charCodeAt.call(body, ++position);
} while (code >= 48 && code <= 57); // 0 - 9
} else {
throw (0, _error.error)(source, position, 'Invalid number');
}
if (code === 101) {
// e
code = charCodeAt.call(body, ++position);
if (code === 45) {
// -
code = charCodeAt.call(body, ++position);
}
if (code >= 48 && code <= 57) {
// 0 - 9
do {
code = charCodeAt.call(body, ++position);
} while (code >= 48 && code <= 57); // 0 - 9
} else {
throw (0, _error.error)(source, position, 'Invalid number');
}
}
}
return makeToken(isFloat ? TokenKind.FLOAT : TokenKind.INT, start, position, slice.call(body, start, position));
}
/**
* Reads a string token from the source file.
*
* "([^"\\\u000A\u000D\u2028\u2029]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*"
*/
function readString(source, start) {
var body = source.body;
var position = start + 1;
var chunkStart = position;
var code;
var value = '';
while (position < body.length && (code = charCodeAt.call(body, position)) && code !== 34 && code !== 10 && code !== 13 && code !== 8232 && code !== 8233) {
++position;
if (code === 92) {
// \
value += slice.call(body, chunkStart, position - 1);
code = charCodeAt.call(body, position);
switch (code) {
case 34:
value += '"';break;
case 47:
value += '/';break;
case 92:
value += '\\';break;
case 98:
value += '\b';break;
case 102:
value += '\f';break;
case 110:
value += '\n';break;
case 114:
value += '\r';break;
case 116:
value += '\t';break;
case 117:
var charCode = uniCharCode(charCodeAt.call(body, position + 1), charCodeAt.call(body, position + 2), charCodeAt.call(body, position + 3), charCodeAt.call(body, position + 4));
if (charCode < 0) {
throw (0, _error.error)(source, position, 'Bad character escape sequence');
}
value += fromCharCode(charCode);
position += 4;
break;
default:
throw (0, _error.error)(source, position, 'Bad character escape sequence');
}
++position;
chunkStart = position;
}
}
if (code !== 34) {
throw (0, _error.error)(source, position, 'Unterminated string');
}
value += slice.call(body, chunkStart, position);
return makeToken(TokenKind.STRING, start, position + 1, value);
}
/**
* Converts four hexidecimal chars to the integer that the
* string represents. For example, uniCharCode('0','0','0','f')
* will return 15, and uniCharCode('0','0','f','f') returns 255.
*
* Returns a negative number on error, if a char was invalid.
*
* This is implemented by noting that char2hex() returns -1 on error,
* which means the result of ORing the char2hex() will also be negative.
*/
function uniCharCode(a, b, c, d) {
return char2hex(a) << 12 | char2hex(b) << 8 | char2hex(c) << 4 | char2hex(d);
}
/**
* Converts a hex character to its integer value.
* '0' becomes 0, '9' becomes 9
* 'A' becomes 10, 'F' becomes 15
* 'a' becomes 10, 'f' becomes 15
*
* Returns -1 on error.
*/
function char2hex(a) {
return a >= 48 && a <= 57 ? a - 48 : // 0-9
a >= 65 && a <= 70 ? a - 55 : // A-F
a >= 97 && a <= 102 ? a - 87 : // a-f
-1;
}
/**
* Reads an alphanumeric + underscore name from the source.
*
* [_A-Za-z][_0-9A-Za-z]*
*/
function readName(source, position) {
var body = source.body;
var bodyLength = body.length;
var end = position + 1;
var code;
while (end !== bodyLength && (code = charCodeAt.call(body, end)) && (code === 95 || // _
code >= 48 && code <= 57 || // 0-9
code >= 65 && code <= 90 || // A-Z
code >= 97 && code <= 122 // a-z
)) {
++end;
}
return makeToken(TokenKind.NAME, position, end, slice.call(body, position, end));
}