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 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  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. const arg = this.expr();
  55. if (isError(arg)) {
  56. return arg;
  57. }
  58. args.push(arg);
  59. if (this.match(TokenKind.COMMA)) {
  60. this.eat(TokenKind.COMMA);
  61. } else {
  62. break;
  63. }
  64. }
  65. return args;
  66. }
  67. private from(): AST.FromTarget | Error {
  68. this.eat(TokenKind.FROM);
  69. return this.fromTarget();
  70. }
  71. private fromTarget(): AST.FromTarget | Error {
  72. const primary = this.backtickOrIdentifier();
  73. if (!this.match(TokenKind.AS) || isError(primary)) {
  74. return primary;
  75. }
  76. this.eat(TokenKind.AS);
  77. const token = this.currentToken();
  78. const alias = this.match(TokenKind.BACKTICK)
  79. ? this.backtick()
  80. : this.match(TokenKind.IDENTIFIER)
  81. ? this.identifier()
  82. : new Error(`Unexpected token: ${token.repr()}`, token.line);
  83. if (isError(alias)) {
  84. return alias;
  85. }
  86. return new AST.Alias(primary, alias);
  87. }
  88. private where(): AST.Expr | Error {
  89. this.eat(TokenKind.WHERE);
  90. return this.expr();
  91. }
  92. private expr(): AST.Expr | Error {
  93. return this.or();
  94. }
  95. private or(): AST.Expr | Error {
  96. let left = this.and();
  97. if (isError(left)) {
  98. return left;
  99. }
  100. while (this.match(TokenKind.OR)) {
  101. this.eat(TokenKind.OR);
  102. const right = this.and();
  103. if (isError(right)) {
  104. return right;
  105. }
  106. left = new AST.Binary({
  107. left,
  108. right,
  109. type: AST.BinaryExpressionTypes.OR,
  110. });
  111. }
  112. return left;
  113. }
  114. private and(): AST.Expr | Error {
  115. let left = this.equality();
  116. if (isError(left)) {
  117. return left;
  118. }
  119. while (this.match(TokenKind.AND)) {
  120. this.eat(TokenKind.AND);
  121. const right = this.equality();
  122. if (isError(right)) {
  123. return right;
  124. }
  125. left = new AST.Binary({
  126. left,
  127. right,
  128. type: AST.BinaryExpressionTypes.AND,
  129. });
  130. }
  131. return left;
  132. }
  133. private equality(): AST.Expr | Error {
  134. const left = this.addition();
  135. if (isError(left)) {
  136. return left;
  137. }
  138. if (this.match(TokenKind.EQUALS)) {
  139. this.eat(TokenKind.EQUALS);
  140. const right = this.addition();
  141. if (isError(right)) {
  142. return right;
  143. }
  144. return new AST.Binary({
  145. left,
  146. right,
  147. type: AST.BinaryExpressionTypes.EQUALITY,
  148. });
  149. }
  150. return left;
  151. }
  152. private addition(): AST.Expr | Error {
  153. const left = this.multiplication();
  154. if (isError(left)) {
  155. return left;
  156. }
  157. if (this.match(TokenKind.PLUS)) {
  158. this.eat(TokenKind.PLUS);
  159. const right = this.multiplication();
  160. if (isError(right)) {
  161. return right;
  162. }
  163. return new AST.Binary({
  164. left,
  165. right,
  166. type: AST.BinaryExpressionTypes.ADDITION,
  167. });
  168. }
  169. if (this.match(TokenKind.MINUS)) {
  170. this.eat(TokenKind.MINUS);
  171. const right = this.multiplication();
  172. if (isError(right)) {
  173. return right;
  174. }
  175. return new AST.Binary({
  176. left,
  177. right,
  178. type: AST.BinaryExpressionTypes.SUBTRACTION,
  179. });
  180. }
  181. return left;
  182. }
  183. private multiplication(): AST.Expr | Error {
  184. const left = this.primary();
  185. if (isError(left)) {
  186. return left;
  187. }
  188. if (this.match(TokenKind.STAR)) {
  189. this.eat(TokenKind.STAR);
  190. const right = this.alias();
  191. if (isError(right)) {
  192. return right;
  193. }
  194. return new AST.Binary({
  195. left,
  196. right,
  197. type: AST.BinaryExpressionTypes.MULTIPLICATION,
  198. });
  199. }
  200. if (this.match(TokenKind.SLASH)) {
  201. this.eat(TokenKind.SLASH);
  202. const right = this.alias();
  203. if (isError(right)) {
  204. return right;
  205. }
  206. return new AST.Binary({
  207. left,
  208. right,
  209. type: AST.BinaryExpressionTypes.DIVISION,
  210. });
  211. }
  212. return left;
  213. }
  214. private alias(): AST.Expr | Error {
  215. const primary = this.primary();
  216. if (isError(primary)) {
  217. return primary;
  218. }
  219. if (this.match(TokenKind.AS)) {
  220. this.eat(TokenKind.AS);
  221. const name = this.match(TokenKind.BACKTICK) ? this.backtick() : this.identifier();
  222. if (isError(name)) {
  223. return name;
  224. }
  225. return new AST.Alias(primary, name);
  226. }
  227. return primary;
  228. }
  229. private primary(): AST.Expr | Error {
  230. const token = this.currentToken();
  231. const primary = (() => {
  232. switch (token.kind) {
  233. case TokenKind.NUMBER:
  234. return this.number();
  235. case TokenKind.IDENTIFIER:
  236. return this.identifier();
  237. case TokenKind.BACKTICK:
  238. return this.backtick();
  239. default:
  240. return new Error(`Unexpected token: ${token.repr()}`, token.line);
  241. }
  242. })();
  243. if (isError(primary)) {
  244. return primary;
  245. }
  246. if (this.match(TokenKind.AS)) {
  247. this.eat(TokenKind.AS);
  248. const name = this.match(TokenKind.BACKTICK) ? this.backtick() : this.identifier();
  249. if (isError(name)) {
  250. return name;
  251. }
  252. return new AST.Alias(primary, name);
  253. } else if (this.match(TokenKind.DOT)) {
  254. this.eat(TokenKind.DOT);
  255. const right = this.match(TokenKind.BACKTICK) ? this.backtick() : this.identifier();
  256. if (isError(right)) {
  257. return right;
  258. }
  259. return new AST.Binary({
  260. left: primary,
  261. right,
  262. type: AST.BinaryExpressionTypes.DOT,
  263. });
  264. }
  265. return primary;
  266. }
  267. private backtickOrIdentifier(): AST.Backtick | AST.Identifier | Error {
  268. const token = this.currentToken();
  269. switch (token.kind) {
  270. case TokenKind.BACKTICK:
  271. return this.backtick();
  272. case TokenKind.IDENTIFIER:
  273. return this.identifier();
  274. default:
  275. return new Error(`Unexpected token: ${token.repr()}`, token.line);
  276. }
  277. }
  278. private backtick(): AST.Backtick | Error {
  279. this.eat(TokenKind.BACKTICK);
  280. const identifier = this.identifier();
  281. if (isError(identifier)) {
  282. return identifier;
  283. }
  284. const closeTick = this.eat(TokenKind.BACKTICK);
  285. if (isError(closeTick)) {
  286. return closeTick;
  287. }
  288. return new AST.Backtick(identifier);
  289. }
  290. private identifier(): AST.Identifier | Error {
  291. const identifier = this.eat(TokenKind.IDENTIFIER);
  292. if (isError(identifier)) {
  293. return identifier;
  294. }
  295. return new AST.Identifier(identifier.value || "");
  296. }
  297. private number(): AST.Number | Error {
  298. const n = this.eat(TokenKind.NUMBER);
  299. if (isError(n)) {
  300. return n;
  301. }
  302. return new AST.Number(parseFloat(n.value || ""));
  303. }
  304. private eat(kind: TokenKind) {
  305. const token = this.currentToken();
  306. if (token.kind === kind) {
  307. this.advance();
  308. return token;
  309. }
  310. return new Error(`Unexpected token: ${token.repr()}`, token.line);
  311. }
  312. private match(kind: TokenKind): boolean {
  313. return this.currentToken().kind === kind;
  314. }
  315. private currentToken(): Token {
  316. return this.tokens[this.position];
  317. }
  318. private advance() {
  319. this.position += 1;
  320. }
  321. private atEnd(): boolean {
  322. return this.match(TokenKind.EOF);
  323. }
  324. }