Browse Source

Fix up some stuff and add a test for parser

master
Dylan Baker 6 years ago
parent
commit
f7d7e9f46f
4 changed files with 66 additions and 22 deletions
  1. 7
    0
      src/node.js
  2. 14
    20
      src/parser.js
  3. 1
    2
      src/tokenStream.js
  4. 44
    0
      test/parser.js

+ 7
- 0
src/node.js View File

@@ -1,2 +1,9 @@
1 1
 module.exports = class Node {
2
+  constructor(opts = false) {
3
+    if (opts) {
4
+      Object.keys(opts).forEach(opt => {
5
+        this[opt] = opts[opt]
6
+      })
7
+    }
8
+  }
2 9
 }

+ 14
- 20
src/parser.js View File

@@ -2,56 +2,50 @@ const Node = require('./node')
2 2
 const tokenTypes = require('./tokenTypes')
3 3
 
4 4
 module.exports = class Parser {
5
-  constructor(tokens) {
6
-    this.tokens = tokens
5
+  constructor(tokenStream) {
6
+    this.tokenStream = tokenStream
7 7
   }
8 8
 
9 9
   parse() {
10 10
     let tree = []
11
-    while (this.tokens.peek().type !== tokenTypes.EOF) {
11
+    while (this.tokenStream.peek().type !== tokenTypes.EOF) {
12 12
       tree.push(this.expr())
13 13
     }
14 14
     return tree
15 15
   }
16 16
 
17 17
   expr() {
18
-    this.tokens.eat(tokenTypes.OPAREN)
19
-
20
-    let tokenTypesToEndTheLoop = [
21
-      tokenTypes.OPAREN,
22
-      tokenTypes.CPAREN,
23
-      tokenTypes.EOF,
24
-    ]
18
+    this.tokenStream.eat(tokenTypes.OPAREN)
25 19
 
26 20
     let functionCallNode = new Node()
27
-    functionCallNode.functionName = this.tokens.eat(tokenTypes.LITERAL).value
21
+    functionCallNode.functionName = this.tokenStream.eat(tokenTypes.LITERAL).value
28 22
     functionCallNode.args = []
29 23
     functionCallNode.subtree = []
30 24
 
31
-    while (this.tokens.peek().type != tokenTypes.CPAREN) {
32
-      if (this.tokens.peek().type === tokenTypes.ATTRIBUTE) {
25
+    while (this.tokenStream.peek().type != tokenTypes.CPAREN && this.tokenStream.peek().type !== tokenTypes.EOF) {
26
+      if (this.tokenStream.peek().type === tokenTypes.ATTRIBUTE) {
33 27
         functionCallNode.args.push(this.attribute())
34
-      } else if (this.tokens.peek().type === tokenTypes.OPAREN) {
35
-        functionCallNode.subtree = this.expr()
28
+      } else if (this.tokenStream.peek().type === tokenTypes.OPAREN) {
29
+        functionCallNode.subtree.push(this.expr())
36 30
       }
37 31
     }
38 32
 
39
-    this.tokens.eat(tokenTypes.CPAREN)
33
+    this.tokenStream.eat(tokenTypes.CPAREN)
40 34
 
41 35
     return functionCallNode
42 36
   }
43 37
 
44 38
   attribute() {
45 39
     let attributeNode = new Node()
46
-    attributeNode.attributeName = this.tokens.eat(tokenTypes.ATTRIBUTE).value
40
+    attributeNode.attributeName = this.tokenStream.eat(tokenTypes.ATTRIBUTE).value
47 41
     attributeNode.attributeValue = this.string()
48 42
     return attributeNode
49 43
   }
50 44
 
51 45
   string() {
52
-    this.tokens.eat(tokenTypes.QUOTE)
53
-    let stringValue = this.tokens.eat(tokenTypes.LITERAL).value
54
-    this.tokens.eat(tokenTypes.QUOTE)
46
+    this.tokenStream.eat(tokenTypes.QUOTE)
47
+    let stringValue = this.tokenStream.eat(tokenTypes.LITERAL).value
48
+    this.tokenStream.eat(tokenTypes.QUOTE)
55 49
     return stringValue
56 50
   }
57 51
 }

+ 1
- 2
src/tokenStream.js View File

@@ -1,6 +1,5 @@
1
-module.exports = class TokenStream extends Array {
1
+module.exports = class TokenStream {
2 2
   constructor() {
3
-    super()
4 3
     this.tokens = []
5 4
     this.position = 0
6 5
   }

+ 44
- 0
test/parser.js View File

@@ -0,0 +1,44 @@
1
+const test = require('tape')
2
+
3
+const Lexer = require('../src/lexer')
4
+const Node = require('../src/node')
5
+const Parser = require('../src/parser')
6
+const tt = require('../src/tokenTypes')
7
+
8
+test('parses token stream into a tree', t => {
9
+  t.plan(1)
10
+  const lexer = new Lexer()
11
+  let tokenStream = lexer.scan(`
12
+    (div :class "foobar"
13
+      (p :class "bazquux"))
14
+  `)
15
+  let parser = new Parser(tokenStream)
16
+  let tree = parser.parse()
17
+
18
+  t.deepEqual(
19
+    tree,
20
+    [
21
+      new Node({
22
+        functionName: 'div',
23
+        args: [
24
+          new Node({
25
+            attributeName: 'class',
26
+            attributeValue: 'foobar',
27
+          })
28
+        ],
29
+        subtree: [
30
+          new Node({
31
+            functionName: 'p',
32
+            args: [
33
+              new Node({
34
+                attributeName: 'class',
35
+                attributeValue: 'bazquux',
36
+              })
37
+            ],
38
+            subtree: [],
39
+          })
40
+        ]
41
+      })
42
+    ]
43
+  )
44
+})

Loading…
Cancel
Save