repo_description: A reference implementation of GraphQL for JavaScript
repo_topics: graphql graphql-js
code: import { syntaxError } from '../error/syntaxError';
  
  import { Token } from './ast';
  import { isNameStart } from './characterClasses';
  import type { LexerInterface } from './lexer';
  import { createToken, printCodePointAt, readName } from './lexer';
  import type { Source } from './source';
  import { TokenKind } from './tokenKind';
  
  /**
   * Given a Source schema coordinate, creates a Lexer for that source.
   * A SchemaCoordinateLexer 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.
   */
  export class SchemaCoordinateLexer implements LexerInterface {
    source: Source;
  
    /**
     * The previously focused non-ignored token.
     */
    lastToken: Token;
  
    /**
     * The currently focused non-ignored token.
     */
    token: Token;
  
    /**
     * The (1-indexed) line containing the current token.
     * Since a schema coordinate may not contain newline, this value is always 1.
     */
    line: 1 = 1 as const;
  
    /**
     * The character offset at which the current line begins.
     * Since a schema coordinate may not contain newline, this value is always 0.
     */
    lineStart: 0 = 0 as const;
  
    constructor(source: Source) {
      const startOfFileToken = new Token(TokenKind.SOF, 0, 0, 0, 0);
  
      this.source = source;
      this.lastToken = startOfFileToken;
      this.token = startOfFileToken;
    }
  
    get [Symbol.toStringTag]() {
      return 'SchemaCoordinateLexer';
    }
  
    /**
     * Advances the token stream to the next non-ignored token.
     */
    advance(): Token {
      this.lastToken = this.token;
      const token = (this.token = this.lookahead());
      return token;
    }
  
    /**
     * Looks ahead and returns the next non-ignored token, but does not change
     * the current Lexer token.
     */
    lookahead(): Token {
      let token = this.token;
      if (token.kind !== TokenKind.EOF) {
        // Read the next token and form a link in the token linked-list.
        const nextToken = readNextToken(this, token.end);
        // @ts-expect-error next is only mutable during parsing.
        token.next = nextToken;
        // @ts-expect-error prev is only mutable during parsing.
        nextToken.prev = token;
        token = nextToken;
      }
      return token;
    }
  }
  
  /**
   * Gets the next token from the source starting at the given position.
   */
  function readNextToken(lexer: SchemaCoordinateLexer, start: number): Token {
    const body = lexer.source.body;
    const bodyLength = body.length;
    const position = start;
  
    if (position < bodyLength) {
      const code = body.charCodeAt(position);
  
      switch (code) {
        case 0x002e: // .
          return createToken(lexer, TokenKind.DOT, position, position + 1);
        case 0x0028: // (
          return createToken(lexer, TokenKind.PAREN_L, position, position + 1);
        case 0x0029: // )
          return createToken(lexer, TokenKind.PAREN_R, position, position + 1);
        case 0x003a: // :
          return createToken(lexer, TokenKind.COLON, position, position + 1);
        case 0x0040: // @
          return createToken(lexer, TokenKind.AT, position, position + 1);
      }
  
      // Name
      if (isNameStart(code)) {
        return readName(lexer, position);
      }
  
      throw syntaxError(
        lexer.source,
        position,
        `Invalid character: ${printCodePointAt(lexer, position)}.`,
      );
    }
  
    return createToken(lexer, TokenKind.EOF, bodyLength, bodyLength);
  }
  