소스 검색

Parse binary expressions

master
Dylan Baker 4 년 전
부모
커밋
9ba1a12be5
7개의 변경된 파일248개의 추가작업 그리고 9개의 파일을 삭제
  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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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…
취소
저장