A work-in-progress SQL parser written in TypeScript
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

parser.ts 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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.repr()}`, token.line);
  30. }
  31. private select(): AST.Statement | Error {
  32. this.eat(TokenKind.SELECT);
  33. const args = this.selectArguments();
  34. if (isError(args)) {
  35. return args;
  36. }
  37. const from = this.match(TokenKind.FROM) ? this.from() : null;
  38. if (isError(from)) {
  39. return from;
  40. }
  41. const where = this.match(TokenKind.WHERE) ? this.where() : null;
  42. if (isError(where)) {
  43. return where;
  44. }
  45. return new AST.SelectStatement({
  46. arguments: args,
  47. from,
  48. where,
  49. });
  50. }
  51. private selectArguments(): AST.SelectArgument[] | Error {
  52. const args = [];
  53. while (true) {
  54. if (this.match(TokenKind.STAR)) {
  55. this.eat(TokenKind.STAR);
  56. args.push(new AST.Star());
  57. } else if (this.match(TokenKind.IDENTIFIER) && this.peek_match(TokenKind.DOT)) {
  58. const left = this.identifier();
  59. if (isError(left)) {
  60. return left;
  61. }
  62. const right = this.selectArgumentDotExpression(left);
  63. if (isError(right)) {
  64. return right;
  65. }
  66. args.push(right);
  67. } else if (this.match(TokenKind.BACKTICK) && this.peek_match(TokenKind.DOT, 3)) {
  68. const left = this.backtick();
  69. if (isError(left)) {
  70. return left;
  71. }
  72. const right = this.selectArgumentDotExpression(left);
  73. if (isError(right)) {
  74. return right;
  75. }
  76. args.push(right);
  77. } else {
  78. const arg = this.expr();
  79. if (isError(arg)) {
  80. return arg;
  81. }
  82. args.push(arg);
  83. }
  84. if (this.match(TokenKind.COMMA)) {
  85. this.eat(TokenKind.COMMA);
  86. } else {
  87. break;
  88. }
  89. }
  90. return args;
  91. }
  92. private selectArgumentDotExpression(left: AST.Identifier | AST.Backtick): AST.Expr | Error {
  93. this.eat(TokenKind.DOT);
  94. if (this.match(TokenKind.STAR)) {
  95. this.eat(TokenKind.STAR);
  96. const right = new AST.Star();
  97. return new AST.Binary({ left, right, type: AST.BinaryExpressionTypes.DOT });
  98. } else {
  99. const right = this.expr();
  100. if (isError(right)) {
  101. return right;
  102. }
  103. return new AST.Binary({ left, right, type: AST.BinaryExpressionTypes.DOT });
  104. }
  105. }
  106. private from(): AST.FromTarget | Error {
  107. this.eat(TokenKind.FROM);
  108. return this.fromTarget();
  109. }
  110. private fromTarget(): AST.FromTarget | Error {
  111. if (this.match(TokenKind.LPAREN)) {
  112. this.eat(TokenKind.LPAREN);
  113. const stmt = this.select();
  114. if (isError(stmt)) {
  115. return stmt;
  116. }
  117. const rparen = this.eat(TokenKind.RPAREN);
  118. if (isError(rparen)) {
  119. return rparen;
  120. }
  121. if (this.match(TokenKind.AS)) {
  122. return this.alias(stmt);
  123. }
  124. return stmt;
  125. }
  126. const primary = this.backtickOrIdentifier();
  127. if (isError(primary)) {
  128. return primary;
  129. }
  130. return this.alias(primary);
  131. }
  132. private alias<T extends AST.Expr | AST.Statement>(obj: T): AST.Alias | T | Error {
  133. if (!this.match(TokenKind.AS)) {
  134. return obj;
  135. }
  136. this.eat(TokenKind.AS);
  137. const token = this.currentToken();
  138. const alias = this.match(TokenKind.BACKTICK)
  139. ? this.backtick()
  140. : this.match(TokenKind.IDENTIFIER)
  141. ? this.identifier()
  142. : new Error(`Unexpected token: ${token.repr()}`, token.line);
  143. if (isError(alias)) {
  144. return alias;
  145. }
  146. return new AST.Alias(obj, alias);
  147. }
  148. private where(): AST.Expr | Error {
  149. this.eat(TokenKind.WHERE);
  150. return this.expr();
  151. }
  152. private expr(): AST.Expr | Error {
  153. return this.or();
  154. }
  155. private or(): AST.Expr | Error {
  156. let left = this.and();
  157. if (isError(left)) {
  158. return left;
  159. }
  160. while (this.match(TokenKind.OR)) {
  161. this.eat(TokenKind.OR);
  162. const right = this.and();
  163. if (isError(right)) {
  164. return right;
  165. }
  166. left = new AST.Binary({
  167. left,
  168. right,
  169. type: AST.BinaryExpressionTypes.OR,
  170. });
  171. }
  172. return left;
  173. }
  174. private and(): AST.Expr | Error {
  175. let left = this.equality();
  176. if (isError(left)) {
  177. return left;
  178. }
  179. while (this.match(TokenKind.AND)) {
  180. this.eat(TokenKind.AND);
  181. const right = this.equality();
  182. if (isError(right)) {
  183. return right;
  184. }
  185. left = new AST.Binary({
  186. left,
  187. right,
  188. type: AST.BinaryExpressionTypes.AND,
  189. });
  190. }
  191. return left;
  192. }
  193. private equality(): AST.Expr | Error {
  194. const left = this.addition();
  195. if (isError(left)) {
  196. return left;
  197. }
  198. if (this.match(TokenKind.EQUALS)) {
  199. this.eat(TokenKind.EQUALS);
  200. const right = this.addition();
  201. if (isError(right)) {
  202. return right;
  203. }
  204. return new AST.Binary({
  205. left,
  206. right,
  207. type: AST.BinaryExpressionTypes.EQUALITY,
  208. });
  209. }
  210. return left;
  211. }
  212. private addition(): AST.Expr | Error {
  213. const left = this.multiplication();
  214. if (isError(left)) {
  215. return left;
  216. }
  217. if (this.match(TokenKind.PLUS)) {
  218. this.eat(TokenKind.PLUS);
  219. const right = this.multiplication();
  220. if (isError(right)) {
  221. return right;
  222. }
  223. return new AST.Binary({
  224. left,
  225. right,
  226. type: AST.BinaryExpressionTypes.ADDITION,
  227. });
  228. }
  229. if (this.match(TokenKind.MINUS)) {
  230. this.eat(TokenKind.MINUS);
  231. const right = this.multiplication();
  232. if (isError(right)) {
  233. return right;
  234. }
  235. return new AST.Binary({
  236. left,
  237. right,
  238. type: AST.BinaryExpressionTypes.SUBTRACTION,
  239. });
  240. }
  241. return left;
  242. }
  243. private multiplication(): AST.Expr | Error {
  244. const left = this.primary();
  245. if (isError(left)) {
  246. return left;
  247. }
  248. if (this.match(TokenKind.STAR)) {
  249. this.eat(TokenKind.STAR);
  250. const right = this.primary();
  251. if (isError(right)) {
  252. return right;
  253. }
  254. return new AST.Binary({
  255. left,
  256. right,
  257. type: AST.BinaryExpressionTypes.MULTIPLICATION,
  258. });
  259. }
  260. if (this.match(TokenKind.SLASH)) {
  261. this.eat(TokenKind.SLASH);
  262. const right = this.primary();
  263. if (isError(right)) {
  264. return right;
  265. }
  266. return new AST.Binary({
  267. left,
  268. right,
  269. type: AST.BinaryExpressionTypes.DIVISION,
  270. });
  271. }
  272. return left;
  273. }
  274. private primary(): AST.Expr | Error {
  275. const token = this.currentToken();
  276. const primary = (() => {
  277. switch (token.kind) {
  278. case TokenKind.NUMBER:
  279. return this.number();
  280. case TokenKind.IDENTIFIER:
  281. return this.identifier();
  282. case TokenKind.BACKTICK:
  283. return this.backtick();
  284. case TokenKind.LPAREN:
  285. return this.group();
  286. default:
  287. return new Error(`Unexpected token: ${token.repr()}`, token.line);
  288. }
  289. })();
  290. if (isError(primary)) {
  291. return primary;
  292. }
  293. if (this.match(TokenKind.AS)) {
  294. this.eat(TokenKind.AS);
  295. const name = this.match(TokenKind.BACKTICK) ? this.backtick() : this.identifier();
  296. if (isError(name)) {
  297. return name;
  298. }
  299. return new AST.Alias(primary, name);
  300. } else if (this.match(TokenKind.DOT)) {
  301. this.eat(TokenKind.DOT);
  302. const right = this.match(TokenKind.BACKTICK) ? this.backtick() : this.identifier();
  303. if (isError(right)) {
  304. return right;
  305. }
  306. return new AST.Binary({
  307. left: primary,
  308. right,
  309. type: AST.BinaryExpressionTypes.DOT,
  310. });
  311. }
  312. return primary;
  313. }
  314. private group(): AST.Expr | Error {
  315. this.eat(TokenKind.LPAREN);
  316. const expr = this.expr();
  317. const rparen = this.eat(TokenKind.RPAREN);
  318. if (isError(rparen)) {
  319. return rparen;
  320. }
  321. return expr;
  322. }
  323. private backtickOrIdentifier(): AST.Backtick | AST.Identifier | Error {
  324. const token = this.currentToken();
  325. switch (token.kind) {
  326. case TokenKind.BACKTICK:
  327. return this.backtick();
  328. case TokenKind.IDENTIFIER:
  329. return this.identifier();
  330. default:
  331. return new Error(`Unexpected token: ${token.repr()}`, token.line);
  332. }
  333. }
  334. private backtick(): AST.Backtick | Error {
  335. this.eat(TokenKind.BACKTICK);
  336. const identifier = this.identifier();
  337. if (isError(identifier)) {
  338. return identifier;
  339. }
  340. const closeTick = this.eat(TokenKind.BACKTICK);
  341. if (isError(closeTick)) {
  342. return closeTick;
  343. }
  344. return new AST.Backtick(identifier);
  345. }
  346. private identifier(): AST.Identifier | Error {
  347. const identifier = this.eat(TokenKind.IDENTIFIER);
  348. if (isError(identifier)) {
  349. return identifier;
  350. }
  351. return new AST.Identifier(identifier.value || "");
  352. }
  353. private number(): AST.Number | Error {
  354. const n = this.eat(TokenKind.NUMBER);
  355. if (isError(n)) {
  356. return n;
  357. }
  358. return new AST.Number(parseFloat(n.value || ""));
  359. }
  360. private eat(kind: TokenKind) {
  361. const token = this.currentToken();
  362. if (token.kind === kind) {
  363. this.advance();
  364. return token;
  365. }
  366. return new Error(`Unexpected token: ${token.repr()}`, token.line);
  367. }
  368. private match(kind: TokenKind): boolean {
  369. return this.currentToken().kind === kind;
  370. }
  371. private peek_match(kind: TokenKind, step: number = 1): boolean {
  372. const token = this.tokens[this.position + step];
  373. return token && token.kind === kind;
  374. }
  375. private currentToken(): Token {
  376. return this.tokens[this.position];
  377. }
  378. private advance() {
  379. this.position += 1;
  380. }
  381. private atEnd(): boolean {
  382. return this.match(TokenKind.EOF);
  383. }
  384. }