Ver código fonte

Parse binary expressions

master
Dylan Baker 4 anos atrás
pai
commit
9ba1a12be5
7 arquivos alterados com 248 adições e 9 exclusões
  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 Ver arquivo

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

+ 27
- 0
src/ast/binary.ts Ver arquivo

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 Ver arquivo

43
     } else if (source.match(/^as/i)) {
43
     } else if (source.match(/^as/i)) {
44
       this.advance(2);
44
       this.advance(2);
45
       return new Token(TokenKind.AS, null, this.line);
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
     } else if (source.match(/^\*/)) {
52
     } else if (source.match(/^\*/)) {
47
       this.advance();
53
       this.advance();
48
       return new Token(TokenKind.STAR, null, this.line);
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
     } else if (source.match(/^=/)) {
58
     } else if (source.match(/^=/)) {
50
       this.advance();
59
       this.advance();
51
       return new Token(TokenKind.EQUALS, null, this.line);
60
       return new Token(TokenKind.EQUALS, null, this.line);

+ 107
- 6
src/parser.ts Ver arquivo

44
       return args;
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
     if (isError(from)) {
48
     if (isError(from)) {
49
       return from;
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
     if (isError(where)) {
53
     if (isError(where)) {
54
       return where;
54
       return where;
55
     }
55
     }
71
       }
71
       }
72
       args.push(arg);
72
       args.push(arg);
73
 
73
 
74
-      if (this.currentToken().kind === TokenKind.COMMA) {
74
+      if (this.match(TokenKind.COMMA)) {
75
         this.eat(TokenKind.COMMA);
75
         this.eat(TokenKind.COMMA);
76
       } else {
76
       } else {
77
         break;
77
         break;
92
   }
92
   }
93
 
93
 
94
   private expr(): AST.Expr | Error {
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
   private alias(fn: () => AST.Primary | Error = this.primary): AST.Secondary | Error {
195
   private alias(fn: () => AST.Primary | Error = this.primary): AST.Secondary | Error {
101
       return primary;
198
       return primary;
102
     }
199
     }
103
 
200
 
104
-    if (this.currentToken().kind === TokenKind.AS) {
201
+    if (this.match(TokenKind.AS)) {
105
       this.eat(TokenKind.AS);
202
       this.eat(TokenKind.AS);
106
       const name = this.identifier();
203
       const name = this.identifier();
107
       if (isError(name)) {
204
       if (isError(name)) {
156
     return new Error(`Unexpected token: ${repr}`, token.line);
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
   private currentToken(): Token {
260
   private currentToken(): Token {
160
     return this.tokens[this.position];
261
     return this.tokens[this.position];
161
   }
262
   }
165
   }
266
   }
166
 
267
 
167
   private atEnd(): boolean {
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 Ver arquivo

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

+ 5
- 2
test/lexer.test.ts Ver arquivo

33
   });
33
   });
34
 
34
 
35
   it("scans symbols", () => {
35
   it("scans symbols", () => {
36
-    const tokens = scan("*=,`;.");
36
+    const tokens = scan("=,`;.+-*/");
37
     expect(tokens).to.deep.equal([
37
     expect(tokens).to.deep.equal([
38
-      new Token(TokenKind.STAR, null, 1),
39
       new Token(TokenKind.EQUALS, null, 1),
38
       new Token(TokenKind.EQUALS, null, 1),
40
       new Token(TokenKind.COMMA, null, 1),
39
       new Token(TokenKind.COMMA, null, 1),
41
       new Token(TokenKind.BACKTICK, null, 1),
40
       new Token(TokenKind.BACKTICK, null, 1),
42
       new Token(TokenKind.SEMICOLON, null, 1),
41
       new Token(TokenKind.SEMICOLON, null, 1),
43
       new Token(TokenKind.DOT, null, 1),
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
       new Token(TokenKind.EOF, null, 1),
47
       new Token(TokenKind.EOF, null, 1),
45
     ]);
48
     ]);
46
   });
49
   });

+ 94
- 0
test/parser.test.ts Ver arquivo

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

Carregando…
Cancelar
Salvar