Parcourir la source

Parse binary expressions

master
Dylan Baker il y a 4 ans
Parent
révision
9ba1a12be5
7 fichiers modifiés avec 248 ajouts et 9 suppressions
  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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

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

Chargement…
Annuler
Enregistrer