Browse Source

Allow dot expressions

master
Dylan Baker 4 years ago
parent
commit
8c863d1e94
7 changed files with 143 additions and 36 deletions
  1. 3
    3
      src/ast.ts
  2. 2
    2
      src/ast/alias.ts
  3. 1
    0
      src/ast/binary.ts
  4. 5
    5
      src/ast/selectStatement.ts
  5. 98
    26
      src/parser.ts
  6. 4
    0
      src/token.ts
  7. 30
    0
      test/parser.test.ts

+ 3
- 3
src/ast.ts View File

@@ -12,7 +12,7 @@ export * from "./ast/identifier";
12 12
 export * from "./ast/number";
13 13
 export * from "./ast/selectStatement";
14 14
 
15
-export type Primary = _Number | Identifier | Backtick;
16
-export type Secondary = Primary | Alias;
15
+export type Primary = _Number | Identifier | Backtick | Alias;
17 16
 export type Statement = SelectStatement;
18
-export type Expr = Secondary | Binary;
17
+export type Expr = Primary | Binary;
18
+export type FromTarget = Identifier | Backtick | Alias;

+ 2
- 2
src/ast/alias.ts View File

@@ -1,10 +1,10 @@
1 1
 import * as AST from "../ast";
2 2
 
3 3
 export class Alias {
4
-  public object: AST.Primary;
4
+  public object: AST.Expr;
5 5
   public name: AST.Identifier | AST.Backtick;
6 6
 
7
-  constructor(object: AST.Primary, name: AST.Identifier | AST.Backtick) {
7
+  constructor(object: AST.Expr, name: AST.Identifier | AST.Backtick) {
8 8
     this.object = object;
9 9
     this.name = name;
10 10
   }

+ 1
- 0
src/ast/binary.ts View File

@@ -4,6 +4,7 @@ export enum BinaryExpressionTypes {
4 4
   ADDITION = "ADDITION",
5 5
   AND = "AND",
6 6
   DIVISION = "DIVISION",
7
+  DOT = "DOT",
7 8
   EQUALITY = "EQUALITY",
8 9
   MULTIPLICATION = "MULTIPLICATION",
9 10
   OR = "OR",

+ 5
- 5
src/ast/selectStatement.ts View File

@@ -1,14 +1,14 @@
1 1
 import * as AST from "../ast";
2 2
 
3 3
 export interface ISelectStatementOptions {
4
-  arguments: AST.Secondary[];
5
-  from?: AST.Secondary | null;
6
-  where?: AST.Expr | null
4
+  arguments: AST.Expr[];
5
+  from?: AST.FromTarget | null;
6
+  where?: AST.Expr | null;
7 7
 }
8 8
 
9 9
 export class SelectStatement {
10
-  public arguments: AST.Secondary[];
11
-  public from: AST.Secondary | null;
10
+  public arguments: AST.Expr[];
11
+  public from: AST.FromTarget | null;
12 12
   public where: AST.Expr | null;
13 13
 
14 14
   constructor(opts: ISelectStatementOptions) {

+ 98
- 26
src/parser.ts View File

@@ -34,7 +34,7 @@ export default class Parser {
34 34
         return this.select();
35 35
     }
36 36
 
37
-    return new Error(`Unexpected token ${token.kind}`, token.line);
37
+    return new Error(`Unexpected token: ${token.repr()}`, token.line);
38 38
   }
39 39
 
40 40
   private select(): AST.Statement | Error {
@@ -61,11 +61,11 @@ export default class Parser {
61 61
     });
62 62
   }
63 63
 
64
-  private args(): AST.Secondary[] | Error {
64
+  private args(): AST.Expr[] | Error {
65 65
     const args = [];
66 66
 
67 67
     while (true) {
68
-      const arg = this.alias();
68
+      const arg = this.expr();
69 69
       if (isError(arg)) {
70 70
         return arg;
71 71
       }
@@ -81,14 +81,57 @@ export default class Parser {
81 81
     return args;
82 82
   }
83 83
 
84
-  private from(): AST.Secondary | Error {
84
+  private from(): AST.FromTarget | Error {
85 85
     this.eat(TokenKind.FROM);
86 86
 
87
-    if (this.match(TokenKind.BACKTICK)) {
88
-      return this.alias(this.backtick);
87
+    return this.fromTarget();
88
+  }
89
+
90
+  private fromTarget(): AST.FromTarget | Error {
91
+    const token = this.currentToken();
92
+    switch (token.kind) {
93
+      case TokenKind.BACKTICK:
94
+        const backtick = this.backtick();
95
+        if (isError(backtick)) {
96
+          return backtick;
97
+        }
98
+
99
+        if (this.match(TokenKind.AS)) {
100
+          return this.fromTargetAlias(backtick);
101
+        }
102
+
103
+        return backtick;
104
+      case TokenKind.IDENTIFIER:
105
+        const identifier = this.identifier();
106
+        if (isError(identifier)) {
107
+          return identifier;
108
+        }
109
+
110
+        if (this.match(TokenKind.AS)) {
111
+          return this.fromTargetAlias(identifier);
112
+        }
113
+
114
+        return identifier;
89 115
     }
90 116
 
91
-    return this.alias(this.identifier);
117
+    return new Error(`Unexpected token: ${token.repr()}`, token.line);
118
+  }
119
+
120
+  private fromTargetAlias(obj: AST.Identifier | AST.Backtick): AST.Alias | Error {
121
+    this.eat(TokenKind.AS);
122
+
123
+    const token = this.currentToken();
124
+    const alias = this.match(TokenKind.BACKTICK)
125
+      ? this.backtick()
126
+      : this.match(TokenKind.IDENTIFIER)
127
+        ? this.identifier()
128
+        : new Error(`Unexpected token: ${token.repr()}`, token.line);
129
+
130
+    if (isError(alias)) {
131
+      return alias;
132
+    }
133
+
134
+    return new AST.Alias(obj, alias);
92 135
   }
93 136
 
94 137
   private where(): AST.Expr | Error {
@@ -214,7 +257,7 @@ export default class Parser {
214 257
 
215 258
     if (this.match(TokenKind.STAR)) {
216 259
       this.eat(TokenKind.STAR);
217
-      const right = this.primary();
260
+      const right = this.alias();
218 261
       if (isError(right)) {
219 262
         return right;
220 263
       }
@@ -228,7 +271,7 @@ export default class Parser {
228 271
 
229 272
     if (this.match(TokenKind.SLASH)) {
230 273
       this.eat(TokenKind.SLASH);
231
-      const right = this.primary();
274
+      const right = this.alias();
232 275
       if (isError(right)) {
233 276
         return right;
234 277
       }
@@ -243,8 +286,8 @@ export default class Parser {
243 286
     return left;
244 287
   }
245 288
 
246
-  private alias(fn: () => AST.Primary | Error = this.primary): AST.Secondary | Error {
247
-    const primary = fn.bind(this)();
289
+  private alias(): AST.Expr | Error {
290
+    const primary = this.primary();
248 291
     if (isError(primary)) {
249 292
       return primary;
250 293
     }
@@ -264,21 +307,52 @@ export default class Parser {
264 307
     return primary;
265 308
   }
266 309
 
267
-  private primary(): AST.Primary | Error {
310
+  private primary(): AST.Expr | Error {
268 311
     const token = this.currentToken();
269
-    switch (token.kind) {
270
-      case TokenKind.NUMBER:
271
-        return this.number();
272
-      case TokenKind.IDENTIFIER:
273
-        return this.identifier();
274
-      case TokenKind.BACKTICK:
275
-        return this.backtick();
312
+
313
+    const primary = (() => {
314
+      switch (token.kind) {
315
+        case TokenKind.NUMBER:
316
+          return this.number();
317
+        case TokenKind.IDENTIFIER:
318
+          return this.identifier();
319
+        case TokenKind.BACKTICK:
320
+          return this.backtick();
321
+        default:
322
+          return new Error(`Unexpected token: ${token.repr()}`, token.line);
323
+      }
324
+    })();
325
+
326
+    if (isError(primary)) {
327
+      return primary;
276 328
     }
277 329
 
278
-    return new Error(
279
-      `Unexpected token ${token.kind} ${token.value}`,
280
-      token.line,
281
-    );
330
+    if (this.match(TokenKind.AS)) {
331
+      this.eat(TokenKind.AS);
332
+
333
+      const name = this.match(TokenKind.BACKTICK) ? this.backtick() : this.identifier();
334
+
335
+      if (isError(name)) {
336
+        return name;
337
+      }
338
+
339
+      return new AST.Alias(primary, name);
340
+    } else if (this.match(TokenKind.DOT)) {
341
+      this.eat(TokenKind.DOT);
342
+
343
+      const right = this.match(TokenKind.BACKTICK) ? this.backtick() : this.identifier();
344
+      if (isError(right)) {
345
+        return right;
346
+      }
347
+
348
+      return new AST.Binary({
349
+        left: primary,
350
+        right,
351
+        type: AST.BinaryExpressionTypes.DOT,
352
+      });
353
+    }
354
+
355
+    return primary;
282 356
   }
283 357
 
284 358
   private backtick(): AST.Backtick | Error {
@@ -319,9 +393,7 @@ export default class Parser {
319 393
       return token;
320 394
     }
321 395
 
322
-    const repr = `{ kind: ${token.kind}${token.value ? `, value: ${token.value} }` : " }"}`;
323
-
324
-    return new Error(`Unexpected token: ${repr}`, token.line);
396
+    return new Error(`Unexpected token: ${token.repr()}`, token.line);
325 397
   }
326 398
 
327 399
   private match(kind: TokenKind): boolean {

+ 4
- 0
src/token.ts View File

@@ -29,4 +29,8 @@ export default class Token {
29 29
     this.value = value;
30 30
     this.line = line;
31 31
   }
32
+
33
+  public repr(): string {
34
+    return `{ kind: ${this.kind}${this.value ? `, value: ${this.value} }` : " }"}`;
35
+  }
32 36
 }

+ 30
- 0
test/parser.test.ts View File

@@ -317,4 +317,34 @@ describe("Parser", () => {
317 317
       }),
318 318
     ]);
319 319
   });
320
+
321
+  it("should parse expressions as select arguments", () => {
322
+    const tree = parse("select a = b");
323
+    expect(tree).to.deep.equal([
324
+      new AST.SelectStatement({
325
+        arguments: [
326
+          new AST.Binary({
327
+            left: new AST.Identifier("a"),
328
+            right: new AST.Identifier("b"),
329
+            type: AST.BinaryExpressionTypes.EQUALITY,
330
+          }),
331
+        ],
332
+      }),
333
+    ]);
334
+  });
335
+
336
+  it("should parse dot operators", () => {
337
+    const tree = parse("select a.b");
338
+    expect(tree).to.deep.equal([
339
+      new AST.SelectStatement({
340
+        arguments: [
341
+          new AST.Binary({
342
+            left: new AST.Identifier("a"),
343
+            right: new AST.Identifier("b"),
344
+            type: AST.BinaryExpressionTypes.DOT,
345
+          }),
346
+        ],
347
+      }),
348
+    ]);
349
+  });
320 350
 });

Loading…
Cancel
Save