A work-in-progress SQL parser written in TypeScript
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

lexer.ts 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import Error, { isError } from "./error";
  2. import Token, { TokenKind } from "./token";
  3. export default class Lexer {
  4. public source: string;
  5. public position: number;
  6. public line: number;
  7. constructor(source: string) {
  8. this.source = source;
  9. this.position = 0;
  10. this.line = 1;
  11. }
  12. public scan(): Token[] | Error {
  13. const tokens = [];
  14. while (!this.atEnd()) {
  15. const result = this.getToken();
  16. if (isError(result)) {
  17. return result;
  18. } else if (result) {
  19. tokens.push(result);
  20. }
  21. }
  22. tokens.push(new Token(TokenKind.EOF, null, this.line));
  23. return tokens;
  24. }
  25. private getToken(): Token | Error | null {
  26. const source = this.source.slice(this.position);
  27. if (source.match(/^select/i)) {
  28. this.advance(6);
  29. return new Token(TokenKind.SELECT, null, this.line);
  30. } else if (source.match(/^where/i)) {
  31. this.advance(5);
  32. return new Token(TokenKind.WHERE, null, this.line);
  33. } else if (source.match(/^from/i)) {
  34. this.advance(4);
  35. return new Token(TokenKind.FROM, null, this.line);
  36. } else if (source.match(/^as/i)) {
  37. this.advance(2);
  38. return new Token(TokenKind.AS, null, this.line);
  39. } else if (source.match(/^\+/)) {
  40. this.advance();
  41. return new Token(TokenKind.PLUS, null, this.line);
  42. } else if (source.match(/^-/)) {
  43. this.advance();
  44. return new Token(TokenKind.MINUS, null, this.line);
  45. } else if (source.match(/^\*/)) {
  46. this.advance();
  47. return new Token(TokenKind.STAR, null, this.line);
  48. } else if (source.match(/^\//)) {
  49. this.advance();
  50. return new Token(TokenKind.SLASH, null, this.line);
  51. } else if (source.match(/^=/)) {
  52. this.advance();
  53. return new Token(TokenKind.EQUALS, null, this.line);
  54. } else if (source.match(/^,/)) {
  55. this.advance();
  56. return new Token(TokenKind.COMMA, null, this.line);
  57. } else if (source.match(/^`/)) {
  58. this.advance();
  59. return new Token(TokenKind.BACKTICK, null, this.line);
  60. } else if (source.match(/^\.([^0-9]|$)/)) {
  61. this.advance();
  62. return new Token(TokenKind.DOT, null, this.line);
  63. } else if (source.match(/^;/)) {
  64. this.advance();
  65. return new Token(TokenKind.SEMICOLON, null, this.line);
  66. } else if (source.match(/^[0-9]+(\.[0-9]+)?/)) {
  67. const match = source.match(/^[0-9]+(\.[0-9]+)?/);
  68. if (match) {
  69. const numeric = match[0];
  70. this.advance(numeric.length);
  71. return new Token(TokenKind.NUMBER, numeric, this.line);
  72. }
  73. } else if (source.match(/^\.[0-9]+/)) {
  74. const match = source.match(/^\.[0-9]+/);
  75. if (match) {
  76. const numeric = match[0];
  77. this.advance(numeric.length);
  78. return new Token(TokenKind.NUMBER, numeric, this.line);
  79. }
  80. } else if (source.match(/^[a-zA-Z_][a-zA-Z0-9_]*/)) {
  81. const match = source.match(/^[a-zA-Z_][a-zA-Z0-9_]*/);
  82. if (match) {
  83. const identifier = match[0];
  84. this.advance(identifier.length);
  85. return new Token(TokenKind.IDENTIFIER, identifier, this.line);
  86. }
  87. } else if (source.match(/^\n/)) {
  88. this.advance();
  89. this.nextLine();
  90. return null;
  91. } else if (source.match(/^\s/)) {
  92. this.advance();
  93. return null;
  94. }
  95. return new Error(`Unrecognized character ${source[0]}`, this.line);
  96. }
  97. private advance(step: number = 1) {
  98. this.position += step;
  99. }
  100. private nextLine() {
  101. this.line += 1;
  102. }
  103. private atEnd(): boolean {
  104. return this.position === this.source.length;
  105. }
  106. }