A work-in-progress SQL parser written in TypeScript
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

parser.ts 2.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import * as AST from "./ast";
  2. import Error, { isError } from "./error";
  3. import Token, { TokenKind } from "./token";
  4. export default class Parser {
  5. public tokens: Token[];
  6. public position: number;
  7. constructor(tokens: Token[]) {
  8. this.tokens = tokens;
  9. this.position = 0;
  10. }
  11. public parse(): AST.Statement[] | Error {
  12. const tree: AST.Statement[] = [];
  13. while (!this.atEnd()) {
  14. const stmt = this.statement();
  15. if (isError(stmt)) {
  16. return stmt;
  17. } else {
  18. tree.push(stmt);
  19. }
  20. }
  21. return tree;
  22. }
  23. private statement(): AST.Statement | Error {
  24. const token = this.currentToken();
  25. switch (token.kind) {
  26. case TokenKind.SELECT:
  27. return this.select();
  28. }
  29. return new Error(`Unexpected token ${token.kind}`, token.line);
  30. }
  31. private select(): AST.Statement | Error {
  32. this.eat(TokenKind.SELECT);
  33. const args = this.args();
  34. if (isError(args)) {
  35. return args;
  36. }
  37. const from =
  38. this.currentToken().kind === TokenKind.FROM ? this.from() : null;
  39. if (isError(from)) {
  40. return from;
  41. }
  42. return new AST.SelectStatement({
  43. arguments: args,
  44. from,
  45. });
  46. }
  47. private args(): AST.Primary[] | Error {
  48. const args = [];
  49. while (true) {
  50. const arg = this.primary();
  51. if (isError(arg)) {
  52. return arg;
  53. }
  54. args.push(arg);
  55. if (this.currentToken().kind === TokenKind.COMMA) {
  56. this.eat(TokenKind.COMMA);
  57. } else {
  58. break;
  59. }
  60. }
  61. return args;
  62. }
  63. private from(): AST.Primary | Error {
  64. this.eat(TokenKind.FROM);
  65. return this.identifier();
  66. }
  67. private primary(): AST.Primary | Error {
  68. const token = this.currentToken();
  69. switch (token.kind) {
  70. case TokenKind.NUMBER:
  71. return this.number();
  72. case TokenKind.IDENTIFIER:
  73. return this.identifier();
  74. }
  75. return new Error(
  76. `Unexpected token ${token.kind} ${token.value}`,
  77. token.line,
  78. );
  79. }
  80. private identifier(): AST.Identifier | Error {
  81. const identifier = this.eat(TokenKind.IDENTIFIER);
  82. if (isError(identifier)) {
  83. return identifier;
  84. }
  85. return new AST.Identifier(identifier.value || "");
  86. }
  87. private number(): AST.Number | Error {
  88. const n = this.eat(TokenKind.NUMBER);
  89. if (isError(n)) {
  90. return n;
  91. }
  92. return new AST.Number(parseFloat(n.value || ""));
  93. }
  94. private eat(kind: TokenKind) {
  95. const token = this.currentToken();
  96. if (token.kind === kind) {
  97. this.advance();
  98. return token;
  99. }
  100. return new Error(`Unexpected token ${token}`, token.line);
  101. }
  102. private currentToken(): Token {
  103. return this.tokens[this.position];
  104. }
  105. private advance() {
  106. this.position += 1;
  107. }
  108. private atEnd(): boolean {
  109. return this.currentToken().kind === TokenKind.EOF;
  110. }
  111. }