Bläddra i källkod

Parse binary expressions

master
Dylan Baker 4 år sedan
förälder
incheckning
9ba1a12be5
7 ändrade filer med 248 tillägg och 9 borttagningar
  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 Visa fil

@@ -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 Visa fil

@@ -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 Visa fil

@@ -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 Visa fil

@@ -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 Visa fil

@@ -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 Visa fil

@@ -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 Visa fil

@@ -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
 });

Laddar…
Avbryt
Spara