Browse Source

Parse *

master
Dylan Baker 5 years ago
parent
commit
67fada86c5
6 changed files with 107 additions and 9 deletions
  1. 3
    1
      src/ast.ts
  2. 2
    2
      src/ast/binary.ts
  3. 2
    2
      src/ast/selectStatement.ts
  4. 1
    0
      src/ast/star.ts
  5. 50
    4
      src/parser.ts
  6. 49
    0
      test/parser.test.ts

+ 3
- 1
src/ast.ts View File

4
 import { Identifier } from "./ast/identifier";
4
 import { Identifier } from "./ast/identifier";
5
 import { Number as _Number } from "./ast/number";
5
 import { Number as _Number } from "./ast/number";
6
 import { SelectStatement } from "./ast/selectStatement";
6
 import { SelectStatement } from "./ast/selectStatement";
7
+import { Star } from "./ast/star";
7
 
8
 
8
 export * from "./ast/alias";
9
 export * from "./ast/alias";
9
 export * from "./ast/backtick";
10
 export * from "./ast/backtick";
11
 export * from "./ast/identifier";
12
 export * from "./ast/identifier";
12
 export * from "./ast/number";
13
 export * from "./ast/number";
13
 export * from "./ast/selectStatement";
14
 export * from "./ast/selectStatement";
15
+export * from "./ast/star";
14
 
16
 
15
 export type Primary = _Number | Identifier | Backtick | Alias;
17
 export type Primary = _Number | Identifier | Backtick | Alias;
16
 export type Statement = SelectStatement;
18
 export type Statement = SelectStatement;
17
 export type Expr = Primary | Binary;
19
 export type Expr = Primary | Binary;
18
-export type SelectArgument = Expr;
20
+export type SelectArgument = Expr | Star;
19
 export type FromTarget = Identifier | Backtick | Alias | SelectStatement;
21
 export type FromTarget = Identifier | Backtick | Alias | SelectStatement;

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

14
 interface IBinaryOptions {
14
 interface IBinaryOptions {
15
   type: BinaryExpressionTypes;
15
   type: BinaryExpressionTypes;
16
   left: AST.Expr;
16
   left: AST.Expr;
17
-  right: AST.Expr;
17
+  right: AST.Expr | AST.Star;
18
 }
18
 }
19
 
19
 
20
 export class Binary {
20
 export class Binary {
21
   public type: BinaryExpressionTypes;
21
   public type: BinaryExpressionTypes;
22
   public left: AST.Expr;
22
   public left: AST.Expr;
23
-  public right: AST.Expr;
23
+  public right: AST.Expr | AST.Star;
24
 
24
 
25
   constructor(opts: IBinaryOptions) {
25
   constructor(opts: IBinaryOptions) {
26
     this.type = opts.type;
26
     this.type = opts.type;

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

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

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

1
+export class Star {}

+ 50
- 4
src/parser.ts View File

65
     const args = [];
65
     const args = [];
66
 
66
 
67
     while (true) {
67
     while (true) {
68
-      const arg = this.expr();
69
-      if (isError(arg)) {
70
-        return arg;
68
+      if (this.match(TokenKind.STAR)) {
69
+        this.eat(TokenKind.STAR);
70
+        args.push(new AST.Star());
71
+      } else if (this.match(TokenKind.IDENTIFIER) && this.peek_match(TokenKind.DOT)) {
72
+        const left = this.identifier();
73
+        if (isError(left)) {
74
+          return left;
75
+        }
76
+        const right = this.selectArgumentDotExpression(left);
77
+        if (isError(right)) {
78
+          return right;
79
+        }
80
+        args.push(right);
81
+      } else if (this.match(TokenKind.BACKTICK) && this.peek_match(TokenKind.DOT, 3)) {
82
+        const left = this.backtick();
83
+        if (isError(left)) {
84
+          return left;
85
+        }
86
+        const right = this.selectArgumentDotExpression(left);
87
+        if (isError(right)) {
88
+          return right;
89
+        }
90
+        args.push(right);
91
+      } else {
92
+        const arg = this.expr();
93
+        if (isError(arg)) {
94
+          return arg;
95
+        }
96
+        args.push(arg);
71
       }
97
       }
72
-      args.push(arg);
73
 
98
 
74
       if (this.match(TokenKind.COMMA)) {
99
       if (this.match(TokenKind.COMMA)) {
75
         this.eat(TokenKind.COMMA);
100
         this.eat(TokenKind.COMMA);
81
     return args;
106
     return args;
82
   }
107
   }
83
 
108
 
109
+  private selectArgumentDotExpression(left: AST.Identifier | AST.Backtick): AST.Expr | Error {
110
+    this.eat(TokenKind.DOT);
111
+
112
+    if (this.match(TokenKind.STAR)) {
113
+      this.eat(TokenKind.STAR);
114
+      const right = new AST.Star();
115
+      return new AST.Binary({ left, right, type: AST.BinaryExpressionTypes.DOT });
116
+    } else {
117
+      const right = this.expr();
118
+      if (isError(right)) {
119
+        return right;
120
+      }
121
+      return new AST.Binary({ left, right, type: AST.BinaryExpressionTypes.DOT });
122
+    }
123
+  }
124
+
84
   private from(): AST.FromTarget | Error {
125
   private from(): AST.FromTarget | Error {
85
     this.eat(TokenKind.FROM);
126
     this.eat(TokenKind.FROM);
86
     return this.fromTarget();
127
     return this.fromTarget();
404
     return this.currentToken().kind === kind;
445
     return this.currentToken().kind === kind;
405
   }
446
   }
406
 
447
 
448
+  private peek_match(kind: TokenKind, step: number = 1): boolean {
449
+    const token = this.tokens[this.position + step];
450
+    return token && token.kind === kind;
451
+  }
452
+
407
   private currentToken(): Token {
453
   private currentToken(): Token {
408
     return this.tokens[this.position];
454
     return this.tokens[this.position];
409
   }
455
   }

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

397
       }),
397
       }),
398
     ]);
398
     ]);
399
   });
399
   });
400
+
401
+  it("should parse SELECT *", () => {
402
+    const tree = parse("select *");
403
+    expect(tree).to.deep.equal([
404
+      new AST.SelectStatement({
405
+        arguments: [
406
+          new AST.Star(),
407
+        ],
408
+      }),
409
+    ]);
410
+  });
411
+
412
+  it("should parse * on the right hand side of a dot in a SELECT", () => {
413
+    const tree = parse("select a.*");
414
+    expect(tree).to.deep.equal([
415
+      new AST.SelectStatement({
416
+        arguments: [
417
+          new AST.Binary({
418
+            left: new AST.Identifier("a"),
419
+            right: new AST.Star(),
420
+            type: AST.BinaryExpressionTypes.DOT,
421
+          }),
422
+        ],
423
+      }),
424
+    ]);
425
+  });
426
+
427
+  it("should parse * on the right of a dot where the left is a backtick", () => {
428
+    const tree = parse("select `a`.*");
429
+    expect(tree).to.deep.equal([
430
+      new AST.SelectStatement({
431
+        arguments: [
432
+          new AST.Binary({
433
+            left: new AST.Backtick(new AST.Identifier("a")),
434
+            right: new AST.Star(),
435
+            type: AST.BinaryExpressionTypes.DOT,
436
+          }),
437
+        ],
438
+      }),
439
+    ]);
440
+  });
441
+
442
+  it("should not allow stars in arithmetic operations", () => {
443
+    expect(() => parse("select 1 + *")).to.throw("Unexpected token: { kind: STAR }");
444
+    expect(() => parse("select 1 + a.*")).to.throw("Unexpected token: { kind: STAR }");
445
+    expect(() => parse("select * + 1")).to.throw("Unexpected token: { kind: PLUS }");
446
+    expect(() => parse("select a.* + b.*")).to.throw("Unexpected token: { kind: PLUS }");
447
+    expect(() => parse("select a from b where c.*")).to.throw("Unexpected token: { kind: STAR }");
448
+  });
400
 });
449
 });

Loading…
Cancel
Save