Browse Source

Start parsing

master
Dylan Baker 4 years ago
parent
commit
9d7750d513
5 changed files with 175 additions and 0 deletions
  1. 2
    0
      src/ast.ts
  2. 7
    0
      src/ast/number.ts
  3. 20
    0
      src/ast/statement.ts
  4. 101
    0
      src/parser.ts
  5. 45
    0
      test/parser.test.ts

+ 2
- 0
src/ast.ts View File

@@ -0,0 +1,2 @@
1
+export * from "./ast/number";
2
+export * from "./ast/statement";

+ 7
- 0
src/ast/number.ts View File

@@ -0,0 +1,7 @@
1
+export class Number {
2
+  public value: number;
3
+
4
+  constructor(value: number) {
5
+    this.value = value;
6
+  }
7
+}

+ 20
- 0
src/ast/statement.ts View File

@@ -0,0 +1,20 @@
1
+import * as AST from "../ast";
2
+
3
+export enum StatementType {
4
+  SELECT = "SELECT",
5
+}
6
+
7
+export interface IStatementOptions {
8
+  type: StatementType;
9
+  arguments: AST.Number[];
10
+}
11
+
12
+export class Statement {
13
+  public type: StatementType;
14
+  public arguments: AST.Number[];
15
+
16
+  constructor(opts: IStatementOptions) {
17
+    this.type = opts.type;
18
+    this.arguments = opts.arguments;
19
+  }
20
+}

+ 101
- 0
src/parser.ts View File

@@ -0,0 +1,101 @@
1
+import * as AST from "./ast";
2
+import Error, { isError } from "./error";
3
+import Token, { TokenKind } from "./token";
4
+
5
+export default class Parser {
6
+  public tokens: Token[];
7
+  public position: number;
8
+
9
+  constructor(tokens: Token[]) {
10
+    this.tokens = tokens;
11
+    this.position = 0;
12
+  }
13
+
14
+  public parse(): AST.Statement[] | Error {
15
+    const tree: AST.Statement[] = [];
16
+
17
+    while (!this.atEnd()) {
18
+      const stmt = this.statement();
19
+      if (isError(stmt)) {
20
+        return stmt;
21
+      } else {
22
+        tree.push(stmt);
23
+      }
24
+    }
25
+
26
+    return tree;
27
+  }
28
+
29
+  private statement(): AST.Statement | Error {
30
+    const token = this.currentToken();
31
+
32
+    switch (token.kind) {
33
+      case TokenKind.SELECT:
34
+        return this.select();
35
+    }
36
+
37
+    return new Error(`Unexpected token ${token.kind}`, token.line);
38
+  }
39
+
40
+  private select(): AST.Statement | Error {
41
+    this.eat(TokenKind.SELECT);
42
+    const args = this.args();
43
+    if (isError(args)) {
44
+      return args;
45
+    }
46
+    return new AST.Statement({
47
+      arguments: args,
48
+      type: AST.StatementType.SELECT,
49
+    });
50
+  }
51
+
52
+  private args(): AST.Number[] | Error {
53
+    const args = [];
54
+
55
+    while (true) {
56
+      const arg = this.number();
57
+      if (isError(arg)) {
58
+        return arg;
59
+      }
60
+      args.push(arg);
61
+
62
+      if (this.currentToken().kind === TokenKind.COMMA) {
63
+        this.eat(TokenKind.COMMA);
64
+      } else {
65
+        break;
66
+      }
67
+    }
68
+
69
+    return args;
70
+  }
71
+
72
+  private number(): AST.Number | Error {
73
+    const n = this.eat(TokenKind.NUMBER);
74
+    if (isError(n)) {
75
+      return n;
76
+    }
77
+    return new AST.Number(parseFloat(n.value || ""));
78
+  }
79
+
80
+  private eat(kind: TokenKind) {
81
+    const token = this.currentToken();
82
+    if (token.kind === kind) {
83
+      this.advance();
84
+      return token;
85
+    }
86
+
87
+    return new Error(`Unexpected token ${token}`, token.line);
88
+  }
89
+
90
+  private currentToken(): Token {
91
+    return this.tokens[this.position];
92
+  }
93
+
94
+  private advance() {
95
+    this.position += 1;
96
+  }
97
+
98
+  private atEnd(): boolean {
99
+    return this.currentToken().kind === TokenKind.EOF;
100
+  }
101
+}

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

@@ -0,0 +1,45 @@
1
+/* tslint:disable:no-unused-expression */
2
+import { expect } from "chai";
3
+
4
+import * as AST from "../src/ast";
5
+import { isError } from "../src/error";
6
+import Lexer from "../src/lexer";
7
+import Parser from "../src/parser";
8
+
9
+const parse = (source: string): AST.Statement[] => {
10
+  const tokens = new Lexer(source).scan();
11
+  if (isError(tokens)) {
12
+    throw tokens.message;
13
+  } else {
14
+    const tree = new Parser(tokens).parse();
15
+    if (isError(tree)) {
16
+      throw tree.message;
17
+    }
18
+    return tree;
19
+  }
20
+};
21
+
22
+describe("Parser", () => {
23
+  it("should parse a number selection", () => {
24
+    const tree = parse("select 5");
25
+    expect(tree).to.deep.equal([
26
+      new AST.Statement({
27
+        arguments: [new AST.Number(5)],
28
+        type: AST.StatementType.SELECT,
29
+      }),
30
+    ]);
31
+  });
32
+
33
+  it("should parse a multiple number selection", () => {
34
+    const tree = parse("select 5, 6");
35
+    expect(tree).to.deep.equal([
36
+      new AST.Statement({
37
+        arguments: [
38
+          new AST.Number(5),
39
+          new AST.Number(6),
40
+        ],
41
+        type: AST.StatementType.SELECT,
42
+      }),
43
+    ]);
44
+  });
45
+});

Loading…
Cancel
Save