UNPKG

@dillonkearns/elm-graphql

Version:

<img src="https://cdn.jsdelivr.net/gh/martimatix/logo-graphqelm/logo.svg" alt="dillonearns/elm-graphql logo" width="40%" align="right">

501 lines (458 loc) 14 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.TokenKind = undefined; exports.createLexer = createLexer; exports.getTokenDesc = getTokenDesc; var _error = require('../error'); /** * Given a Source object, this returns a Lexer for that source. * A Lexer is a stateful stream generator in that every time * it is advanced, it returns the next token in the Source. Assuming the * source lexes, the final Token emitted by the lexer will be of kind * EOF, after which the lexer will repeatedly return the same EOF token * whenever called. */ function createLexer(source, options) { var startOfFileToken = new Tok(SOF, 0, 0, 0, 0, null); var lexer = { source: source, options: options, lastToken: startOfFileToken, token: startOfFileToken, line: 1, lineStart: 0, advance: advanceLexer }; return lexer; } /** * Copyright (c) 2015-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * */ function advanceLexer() { var token = this.lastToken = this.token; if (token.kind !== EOF) { do { token = token.next = readToken(this, token); } while (token.kind === COMMENT); this.token = token; } return token; } /** * The return type of createLexer. */ // Each kind of token. var SOF = '<SOF>'; var EOF = '<EOF>'; var BANG = '!'; var DOLLAR = '$'; var PAREN_L = '('; var PAREN_R = ')'; var SPREAD = '...'; var COLON = ':'; var EQUALS = '='; var AT = '@'; var BRACKET_L = '['; var BRACKET_R = ']'; var BRACE_L = '{'; var PIPE = '|'; var BRACE_R = '}'; var NAME = 'Name'; var INT = 'Int'; var FLOAT = 'Float'; var STRING = 'String'; var COMMENT = 'Comment'; /** * An exported enum describing the different kinds of tokens that the * lexer emits. */ var TokenKind = exports.TokenKind = { SOF: SOF, EOF: EOF, BANG: BANG, DOLLAR: DOLLAR, PAREN_L: PAREN_L, PAREN_R: PAREN_R, SPREAD: SPREAD, COLON: COLON, EQUALS: EQUALS, AT: AT, BRACKET_L: BRACKET_L, BRACKET_R: BRACKET_R, BRACE_L: BRACE_L, PIPE: PIPE, BRACE_R: BRACE_R, NAME: NAME, INT: INT, FLOAT: FLOAT, STRING: STRING, COMMENT: COMMENT }; /** * A helper function to describe a token as a string for debugging */ function getTokenDesc(token) { var value = token.value; return value ? token.kind + ' "' + value + '"' : token.kind; } var charCodeAt = String.prototype.charCodeAt; var slice = String.prototype.slice; /** * Helper function for constructing the Token object. */ function Tok(kind, start, end, line, column, prev, value) { this.kind = kind; this.start = start; this.end = end; this.line = line; this.column = column; this.value = value; this.prev = prev; this.next = null; } // Print a simplified form when appearing in JSON/util.inspect. Tok.prototype.toJSON = Tok.prototype.inspect = function toJSON() { return { kind: this.kind, value: this.value, line: this.line, column: this.column }; }; function printCharCode(code) { return ( // NaN/undefined represents access beyond the end of the file. isNaN(code) ? EOF : // Trust JSON for ASCII. code < 0x007F ? JSON.stringify(String.fromCharCode(code)) : // Otherwise print the escaped form. '"\\u' + ('00' + code.toString(16).toUpperCase()).slice(-4) + '"' ); } /** * 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 * function for more complicated tokens. */ function readToken(lexer, prev) { var source = lexer.source; var body = source.body; var bodyLength = body.length; var position = positionAfterWhitespace(body, prev.end, lexer); var line = lexer.line; var col = 1 + position - lexer.lineStart; if (position >= bodyLength) { return new Tok(EOF, bodyLength, bodyLength, line, col, prev); } var code = charCodeAt.call(body, position); // SourceCharacter if (code < 0x0020 && code !== 0x0009 && code !== 0x000A && code !== 0x000D) { throw (0, _error.syntaxError)(source, position, 'Cannot contain the invalid character ' + printCharCode(code) + '.'); } switch (code) { // ! case 33: return new Tok(BANG, position, position + 1, line, col, prev); // # case 35: return readComment(source, position, line, col, prev); // $ case 36: return new Tok(DOLLAR, position, position + 1, line, col, prev); // ( case 40: return new Tok(PAREN_L, position, position + 1, line, col, prev); // ) case 41: return new Tok(PAREN_R, position, position + 1, line, col, prev); // . case 46: if (charCodeAt.call(body, position + 1) === 46 && charCodeAt.call(body, position + 2) === 46) { return new Tok(SPREAD, position, position + 3, line, col, prev); } break; // : case 58: return new Tok(COLON, position, position + 1, line, col, prev); // = case 61: return new Tok(EQUALS, position, position + 1, line, col, prev); // @ case 64: return new Tok(AT, position, position + 1, line, col, prev); // [ case 91: return new Tok(BRACKET_L, position, position + 1, line, col, prev); // ] case 93: return new Tok(BRACKET_R, position, position + 1, line, col, prev); // { case 123: return new Tok(BRACE_L, position, position + 1, line, col, prev); // | case 124: return new Tok(PIPE, position, position + 1, line, col, prev); // } case 125: return new Tok(BRACE_R, position, position + 1, line, col, prev); // A-Z _ 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: 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, line, col, prev); // - 0-9 case 45: 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, line, col, prev); // " case 34: return readString(source, position, line, col, prev); } throw (0, _error.syntaxError)(source, position, unexpectedCharacterMessage(code)); } /** * Report a message that an unexpected character was encountered. */ function unexpectedCharacterMessage(code) { if (code === 39) { // ' return 'Unexpected single quote character (\'), did you mean to use ' + 'a double quote (")?'; } return 'Cannot parse the unexpected character ' + printCharCode(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, lexer) { var bodyLength = body.length; var position = startPosition; while (position < bodyLength) { var code = charCodeAt.call(body, position); // tab | space | comma | BOM if (code === 9 || code === 32 || code === 44 || code === 0xFEFF) { ++position; } else if (code === 10) { // new line ++position; ++lexer.line; lexer.lineStart = position; } else if (code === 13) { // carriage return if (charCodeAt.call(body, position + 1) === 10) { position += 2; } else { ++position; } ++lexer.line; lexer.lineStart = position; } else { break; } } return position; } /** * Reads a comment token from the source file. * * #[\u0009\u0020-\uFFFF]* */ function readComment(source, start, line, col, prev) { var body = source.body; var code = void 0; var position = start; do { code = charCodeAt.call(body, ++position); } while (code !== null && ( // SourceCharacter but not LineTerminator code > 0x001F || code === 0x0009)); return new Tok(COMMENT, start, position, line, col, prev, slice.call(body, start + 1, 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|e)(+|-)?[0-9]+)? */ function readNumber(source, start, firstCode, line, col, prev) { var body = source.body; var code = firstCode; var position = start; var isFloat = false; if (code === 45) { // - code = charCodeAt.call(body, ++position); } if (code === 48) { // 0 code = charCodeAt.call(body, ++position); if (code >= 48 && code <= 57) { throw (0, _error.syntaxError)(source, position, 'Invalid number, unexpected digit after 0: ' + printCharCode(code) + '.'); } } else { position = readDigits(source, position, code); code = charCodeAt.call(body, position); } if (code === 46) { // . isFloat = true; code = charCodeAt.call(body, ++position); position = readDigits(source, position, code); code = charCodeAt.call(body, position); } if (code === 69 || code === 101) { // E e isFloat = true; code = charCodeAt.call(body, ++position); if (code === 43 || code === 45) { // + - code = charCodeAt.call(body, ++position); } position = readDigits(source, position, code); } return new Tok(isFloat ? FLOAT : INT, start, position, line, col, prev, slice.call(body, start, position)); } /** * Returns the new position in the source after reading digits. */ function readDigits(source, start, firstCode) { var body = source.body; var position = start; var code = firstCode; if (code >= 48 && code <= 57) { // 0 - 9 do { code = charCodeAt.call(body, ++position); } while (code >= 48 && code <= 57); // 0 - 9 return position; } throw (0, _error.syntaxError)(source, position, 'Invalid number, expected digit but got: ' + printCharCode(code) + '.'); } /** * Reads a string token from the source file. * * "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*" */ function readString(source, start, line, col, prev) { var body = source.body; var position = start + 1; var chunkStart = position; var code = 0; var value = ''; while (position < body.length && (code = charCodeAt.call(body, position)) !== null && // not LineTerminator code !== 0x000A && code !== 0x000D && // not Quote (") code !== 34) { // SourceCharacter if (code < 0x0020 && code !== 0x0009) { throw (0, _error.syntaxError)(source, position, 'Invalid character within String: ' + printCharCode(code) + '.'); } ++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: // u 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.syntaxError)(source, position, 'Invalid character escape sequence: ' + ('\\u' + body.slice(position + 1, position + 5) + '.')); } value += String.fromCharCode(charCode); position += 4; break; default: throw (0, _error.syntaxError)(source, position, 'Invalid character escape sequence: \\' + String.fromCharCode(code) + '.'); } ++position; chunkStart = position; } } if (code !== 34) { // quote (") throw (0, _error.syntaxError)(source, position, 'Unterminated string.'); } value += slice.call(body, chunkStart, position); return new Tok(STRING, start, position + 1, line, col, prev, 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, line, col, prev) { var body = source.body; var bodyLength = body.length; var end = position + 1; var code = 0; while (end !== bodyLength && (code = charCodeAt.call(body, end)) !== null && (code === 95 || // _ code >= 48 && code <= 57 || // 0-9 code >= 65 && code <= 90 || // A-Z code >= 97 && code <= 122 // a-z )) { ++end; } return new Tok(NAME, position, end, line, col, prev, slice.call(body, position, end)); }