Browse Source

Parse binary expressions

master
Dylan Baker 4 years ago
parent
commit
9ba1a12be5
7 changed files with 248 additions and 9 deletions
  1. 3
    1
      src/ast.ts
  2. 27
    0
      src/ast/binary.ts
  3. 9
    0
      src/lexer.ts
  4. 107
    6
      src/parser.ts
  5. 3
    0
      src/token.ts
  6. 5
    2
      test/lexer.test.ts
  7. 94
    0
      test/parser.test.ts

+ 3
- 1
src/ast.ts View File

@@ -1,9 +1,11 @@
1 1
 import { Alias } from "./ast/alias";
2
+import { Binary } from "./ast/binary";
2 3
 import { Identifier } from "./ast/identifier";
3 4
 import { Number as _Number } from "./ast/number";
4 5
 import { SelectStatement } from "./ast/selectStatement";
5 6
 
6 7
 export * from "./ast/alias";
8
+export * from "./ast/binary";
7 9
 export * from "./ast/identifier";
8 10
 export * from "./ast/number";
9 11
 export * from "./ast/selectStatement";
@@ -11,4 +13,4 @@ export * from "./ast/selectStatement";
11 13
 export type Primary = _Number | Identifier;
12 14
 export type Secondary = Primary | Alias;
13 15
 export type Statement = SelectStatement;
14
-export type Expr = Secondary;
16
+export type Expr = Secondary | Binary;

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

@@ -0,0 +1,27 @@
1
+import * as AST from "../ast";
2
+
3
+export enum BinaryExpressionTypes {
4
+  ADDITION,
5
+  SUBTRACTION,
6
+  MULTIPLICATION,
7
+  DIVISION,
8
+  EQUALITY,
9
+}
10
+
11
+interface IBinaryOptions {
12
+  type: BinaryExpressionTypes;
13
+  left: AST.Expr;
14
+  right: AST.Expr;
15
+}
16
+
17
+export class Binary {
18
+  public type: BinaryExpressionTypes;
19
+  public left: AST.Expr;
20
+  public right: AST.Expr;
21
+
22
+  constructor(opts: IBinaryOptions) {
23
+    this.type = opts.type;
24
+    this.left = opts.left;
25
+    this.right = opts.right;
26
+  }
27
+}

+ 9
- 0
src/lexer.ts View File

@@ -43,9 +43,18 @@ export default class Lexer {
43 43
     } else if (source.match(/^as/i)) {
44 44
       this.advance(2);
45 45
       return new Token(TokenKind.AS, null, this.line);
46
+    } else if (source.match(/^\+/)) {
47
+      this.advance();
48
+      return new Token(TokenKind.PLUS, null, this.line);
49
+    } else if (source.match(/^-/)) {
50
+      this.advance();
51
+      return new Token(TokenKind.MINUS, null, this.line);
46 52
     } else if (source.match(/^\*/)) {
47 53
       this.advance();
48 54
       return new Token(TokenKind.STAR, null, this.line);
55
+    } else if (source.match(/^\//)) {
56
+      this.advance();
57
+      return new Token(TokenKind.SLASH, null, this.line);
49 58
     } else if (source.match(/^=/)) {
50 59
       this.advance();
51 60
       return new Token(TokenKind.EQUALS, null, this.line);

+ 107
- 6
src/parser.ts View File

@@ -44,12 +44,12 @@ export default class Parser {
44 44
       return args;
45 45
     }
46 46
 
47
-    const from = this.currentToken().kind === TokenKind.FROM ? this.from() : null;
47
+    const from = this.match(TokenKind.FROM) ? this.from() : null;
48 48
     if (isError(from)) {
49 49
       return from;
50 50
     }
51 51
 
52
-    const where = this.currentToken().kind === TokenKind.WHERE ? this.where() : null;
52
+    const where = this.match(TokenKind.WHERE) ? this.where() : null;
53 53
     if (isError(where)) {
54 54
       return where;
55 55
     }
@@ -71,7 +71,7 @@ export default class Parser {
71 71
       }
72 72
       args.push(arg);
73 73
 
74
-      if (this.currentToken().kind === TokenKind.COMMA) {
74
+      if (this.match(TokenKind.COMMA)) {
75 75
         this.eat(TokenKind.COMMA);
76 76
       } else {
77 77
         break;
@@ -92,7 +92,104 @@ export default class Parser {
92 92
   }
93 93
 
94 94
   private expr(): AST.Expr | Error {
95
-    return this.alias();
95
+    return this.equality();
96
+  }
97
+
98
+  private equality(): AST.Expr | Error {
99
+    const left = this.addition();
100
+    if (isError(left)) {
101
+      return left;
102
+    }
103
+
104
+    if (this.match(TokenKind.EQUALS)) {
105
+      this.eat(TokenKind.EQUALS);
106
+      const right = this.addition();
107
+      if (isError(right)) {
108
+        return right;
109
+      }
110
+
111
+      return new AST.Binary({
112
+        left,
113
+        right,
114
+        type: AST.BinaryExpressionTypes.EQUALITY,
115
+      });
116
+    }
117
+
118
+    return left;
119
+  }
120
+
121
+  private addition(): AST.Expr | Error {
122
+    const left = this.multiplication();
123
+    if (isError(left)) {
124
+      return left;
125
+    }
126
+
127
+    if (this.match(TokenKind.PLUS)) {
128
+      this.eat(TokenKind.PLUS);
129
+      const right = this.multiplication();
130
+      if (isError(right)) {
131
+        return right;
132
+      }
133
+
134
+      return new AST.Binary({
135
+        left,
136
+        right,
137
+        type: AST.BinaryExpressionTypes.ADDITION,
138
+      });
139
+    }
140
+
141
+    if (this.match(TokenKind.MINUS)) {
142
+      this.eat(TokenKind.MINUS);
143
+      const right = this.multiplication();
144
+      if (isError(right)) {
145
+        return right;
146
+      }
147
+
148
+      return new AST.Binary({
149
+        left,
150
+        right,
151
+        type: AST.BinaryExpressionTypes.SUBTRACTION,
152
+      });
153
+    }
154
+
155
+    return left;
156
+  }
157
+
158
+  private multiplication(): AST.Expr | Error {
159
+    const left = this.primary();
160
+    if (isError(left)) {
161
+      return left;
162
+    }
163
+
164
+    if (this.match(TokenKind.STAR)) {
165
+      this.eat(TokenKind.STAR);
166
+      const right = this.primary();
167
+      if (isError(right)) {
168
+        return right;
169
+      }
170
+
171
+      return new AST.Binary({
172
+        left,
173
+        right,
174
+        type: AST.BinaryExpressionTypes.MULTIPLICATION,
175
+      });
176
+    }
177
+
178
+    if (this.match(TokenKind.SLASH)) {
179
+      this.eat(TokenKind.SLASH);
180
+      const right = this.primary();
181
+      if (isError(right)) {
182
+        return right;
183
+      }
184
+
185
+      return new AST.Binary({
186
+        left,
187
+        right,
188
+        type: AST.BinaryExpressionTypes.DIVISION,
189
+      });
190
+    }
191
+
192
+    return left;
96 193
   }
97 194
 
98 195
   private alias(fn: () => AST.Primary | Error = this.primary): AST.Secondary | Error {
@@ -101,7 +198,7 @@ export default class Parser {
101 198
       return primary;
102 199
     }
103 200
 
104
-    if (this.currentToken().kind === TokenKind.AS) {
201
+    if (this.match(TokenKind.AS)) {
105 202
       this.eat(TokenKind.AS);
106 203
       const name = this.identifier();
107 204
       if (isError(name)) {
@@ -156,6 +253,10 @@ export default class Parser {
156 253
     return new Error(`Unexpected token: ${repr}`, token.line);
157 254
   }
158 255
 
256
+  private match(kind: TokenKind): boolean {
257
+    return this.currentToken().kind === kind;
258
+  }
259
+
159 260
   private currentToken(): Token {
160 261
     return this.tokens[this.position];
161 262
   }
@@ -165,6 +266,6 @@ export default class Parser {
165 266
   }
166 267
 
167 268
   private atEnd(): boolean {
168
-    return this.currentToken().kind === TokenKind.EOF;
269
+    return this.match(TokenKind.EOF);
169 270
   }
170 271
 }

+ 3
- 0
src/token.ts View File

@@ -7,9 +7,12 @@ export enum TokenKind {
7 7
   EQUALS = "EQUALS",
8 8
   FROM = "FROM",
9 9
   IDENTIFIER = "IDENTIFIER",
10
+  MINUS = "MINUS",
10 11
   NUMBER = "NUMBER",
12
+  PLUS = "PLUS",
11 13
   SELECT = "SELECT",
12 14
   SEMICOLON = "SEMICOLON",
15
+  SLASH = "SLASH",
13 16
   STAR = "STAR",
14 17
   WHERE = "WHERE",
15 18
 }

+ 5
- 2
test/lexer.test.ts View File

@@ -33,14 +33,17 @@ describe("Lexer", () => {
33 33
   });
34 34
 
35 35
   it("scans symbols", () => {
36
-    const tokens = scan("*=,`;.");
36
+    const tokens = scan("=,`;.+-*/");
37 37
     expect(tokens).to.deep.equal([
38
-      new Token(TokenKind.STAR, null, 1),
39 38
       new Token(TokenKind.EQUALS, null, 1),
40 39
       new Token(TokenKind.COMMA, null, 1),
41 40
       new Token(TokenKind.BACKTICK, null, 1),
42 41
       new Token(TokenKind.SEMICOLON, null, 1),
43 42
       new Token(TokenKind.DOT, null, 1),
43
+      new Token(TokenKind.PLUS, null, 1),
44
+      new Token(TokenKind.MINUS, null, 1),
45
+      new Token(TokenKind.STAR, null, 1),
46
+      new Token(TokenKind.SLASH, null, 1),
44 47
       new Token(TokenKind.EOF, null, 1),
45 48
     ]);
46 49
   });

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

@@ -113,4 +113,98 @@ describe("Parser", () => {
113 113
       }),
114 114
     ]);
115 115
   });
116
+
117
+  it("should parse a where with an equality", () => {
118
+    const tree = parse("select a from b where c = d");
119
+    expect(tree).to.deep.equal([
120
+      new AST.SelectStatement({
121
+        arguments: [new AST.Identifier("a")],
122
+        from: new AST.Identifier("b"),
123
+        where: new AST.Binary({
124
+          left: new AST.Identifier("c"),
125
+          right: new AST.Identifier("d"),
126
+          type: AST.BinaryExpressionTypes.EQUALITY,
127
+        }),
128
+      }),
129
+    ]);
130
+  });
131
+
132
+  it("should parse a where with an addition", () => {
133
+    const tree = parse("select a from b where c + d");
134
+    expect(tree).to.deep.equal([
135
+      new AST.SelectStatement({
136
+        arguments: [new AST.Identifier("a")],
137
+        from: new AST.Identifier("b"),
138
+        where: new AST.Binary({
139
+          left: new AST.Identifier("c"),
140
+          right: new AST.Identifier("d"),
141
+          type: AST.BinaryExpressionTypes.ADDITION,
142
+        }),
143
+      }),
144
+    ]);
145
+  });
146
+
147
+  it("should parse a where with a subtraction", () => {
148
+    const tree = parse("select a from b where c - d");
149
+    expect(tree).to.deep.equal([
150
+      new AST.SelectStatement({
151
+        arguments: [new AST.Identifier("a")],
152
+        from: new AST.Identifier("b"),
153
+        where: new AST.Binary({
154
+          left: new AST.Identifier("c"),
155
+          right: new AST.Identifier("d"),
156
+          type: AST.BinaryExpressionTypes.SUBTRACTION,
157
+        }),
158
+      }),
159
+    ]);
160
+  });
161
+
162
+  it("should parse a where with a multiplication", () => {
163
+    const tree = parse("select a from b where c * d");
164
+    expect(tree).to.deep.equal([
165
+      new AST.SelectStatement({
166
+        arguments: [new AST.Identifier("a")],
167
+        from: new AST.Identifier("b"),
168
+        where: new AST.Binary({
169
+          left: new AST.Identifier("c"),
170
+          right: new AST.Identifier("d"),
171
+          type: AST.BinaryExpressionTypes.MULTIPLICATION,
172
+        }),
173
+      }),
174
+    ]);
175
+  });
176
+
177
+  it("should parse a where with a division", () => {
178
+    const tree = parse("select a from b where c / d");
179
+    expect(tree).to.deep.equal([
180
+      new AST.SelectStatement({
181
+        arguments: [new AST.Identifier("a")],
182
+        from: new AST.Identifier("b"),
183
+        where: new AST.Binary({
184
+          left: new AST.Identifier("c"),
185
+          right: new AST.Identifier("d"),
186
+          type: AST.BinaryExpressionTypes.DIVISION,
187
+        }),
188
+      }),
189
+    ]);
190
+  });
191
+
192
+  it("should parse a where with a complex binary expression", () => {
193
+    const tree = parse("select a from b where c + d = e");
194
+    expect(tree).to.deep.equal([
195
+      new AST.SelectStatement({
196
+        arguments: [new AST.Identifier("a")],
197
+        from: new AST.Identifier("b"),
198
+        where: new AST.Binary({
199
+          left: new AST.Binary({
200
+            left: new AST.Identifier("c"),
201
+            right: new AST.Identifier("d"),
202
+            type: AST.BinaryExpressionTypes.ADDITION,
203
+          }),
204
+          right: new AST.Identifier("e"),
205
+          type: AST.BinaryExpressionTypes.EQUALITY,
206
+        }),
207
+      }),
208
+    ]);
209
+  });
116 210
 });

Loading…
Cancel
Save