/// <summary> /// Takes a Source and a UTF-8 character offset, and returns the corresponding /// line and column as a SourceLocation. /// </summary> public static SourceLocation GetLocation(Source source, int position) { var line = 1; var column = position + 1; var matches = _lineRegexp.Matches(source.Body); for (int i = 0; i < matches.Count && matches[i].Index < position; i++) { line += 1; column = position + 1 - (matches[i].Index + matches[i].Length); } return new SourceLocation { Line = line, Column = column }; }
public Lexer(Source source) { _source = source; }
/// <summary> /// 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 emitted 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. /// </summary> /// <param name="source">The source.</param> /// <returns></returns> public Lexer Create(Source source) { return new Lexer(source); }
/** * 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. */ private Token readToken(Source source, int fromPosition) { var body = source.Body; var bodyLength = body.Length; var position = positionAfterWhitespace(body, fromPosition); if (position >= bodyLength) { return makeToken(TokenKind.EOF, position, position); } int code = body[position]; // SourceCharacter if (code < 0x0020 && code != 0x0009 && code != 0x000A && code != 0x000D) { throw Errors.GraphQLError.SyntaxError( source, position, String.Format("Invalid character {0}.", printCharCode(code)) ); } 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 (body[position + 1] == 46 && 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 Errors.GraphQLError.SyntaxError( source, position, String.Format("Unexpected character \"{0}\".", printCharCode(code)) ); }
/// <summary> /// Reads a string token from the source file. /// </summary> /// <param name="source">The source.</param> /// <param name="start">The start.</param> /// <remarks> /// "([^"\\\u000A\u000D\u2028\u2029]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*" /// </remarks> Token readString(Source source, int start) { var body = source.Body; var position = start + 1; var chunkStart = position; var code = 0; var value = String.Empty; while ( position < body.Length && (code = body.CharCodeAt(position)) != -1 && // not LineTerminator code != 0x000A && code != 0x000D && // not Quote (") code != 34 ) { // SourceCharacter if (code < 0x0020 && code != 0x0009) { throw Errors.GraphQLError.SyntaxError( source, position, String.Format("Invalid character within String: {0}.", printCharCode(code)) ); } ++position; if (code == 92) { // \ value += body.Slice(chunkStart, position - 1); code = 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( body[position + 1], body[position + 2], body[position + 3], body[position + 4] ); if (charCode < 0) { throw Errors.GraphQLError.SyntaxError( source, position, "Invalid character escape sequence: " + "\\u" + body.Slice(position + 1, position + 5) + "." ); } value += (char)charCode; position += 4; break; default: throw Errors.GraphQLError.SyntaxError( source, position, String.Format("Invalid character escape sequence: \\{0}.", code) ); } ++position; chunkStart = position; } } if (code != 34) { // quote (") throw Errors.GraphQLError.SyntaxError(source, position, "Unterminated string."); } value += body.Slice(chunkStart, position); return makeToken(TokenKind.STRING, start, position + 1, value); }
/** * 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]+)? */ private Token readNumber(Source source, int start, int firstCode) { var code = firstCode; var body = source.Body; var position = start; var isFloat = false; if (code == 45) { // - code = body[++position]; } if (code == 48) { // 0 code = body[++position]; if (code >= 48 && code <= 57) { throw Errors.GraphQLError.SyntaxError( source, position, String.Format("Invalid number, unexpected digit after 0: {0}", printCharCode(code)) ); } } else { position = readDigits(source, position, code); code = body[position]; } if (code == 46) { // . isFloat = true; code = body[++position]; position = readDigits(source, position, code); code = body[position]; } if (code == 69 || code == 101) { // E e isFloat = true; code = body[++position]; if (code == 43 || code == 45) { // + - code = body[++position]; } position = readDigits(source, position, code); } return makeToken( isFloat ? TokenKind.FLOAT : TokenKind.INT, start, position, body.Slice(start, position) ); }
/** * Reads an alphanumeric + underscore name from the source. * * [_A-Za-z][_0-9A-Za-z]* */ Token readName(Source source, int position) { var body = source.Body; var bodyLength = body.Length; var end = position + 1; var code = 0; while ( end != bodyLength && (code = body.CharCodeAt(end)) != -1 && ( 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, body.Slice(position, end) ); }
/** * Returns the new position in the source after reading digits. */ int readDigits(Source source, int start, int firstCode) { var body = source.Body; var position = start; var code = firstCode; if (code >= 48 && code <= 57) { // 0 - 9 do { code = body[++position]; } while (code >= 48 && code <= 57); // 0 - 9 return position; } throw Errors.GraphQLError.SyntaxError( source, position, String.Format("Invalid number, expected digit but got: ${0}.", printCharCode(code)) ); }