Browse Source

Implement mapping

master
Dylan Baker 5 years ago
parent
commit
db64e79472
6 changed files with 92 additions and 12 deletions
  1. 12
    2
      bin/oslo.js
  2. 17
    3
      src/compiler.js
  3. 25
    7
      src/lexer.js
  4. 18
    0
      src/parser.js
  5. 1
    0
      src/tokenTypes.js
  6. 19
    0
      test/compiler.js

+ 12
- 2
bin/oslo.js View File

@@ -8,6 +8,12 @@ const oslo = require('../src/index')
8 8
 
9 9
 class OsloCLI {
10 10
   constructor(opts) {
11
+    this.context = {}
12
+
13
+    if (opts.l) {
14
+      this.context = JSON.parse(fs.readFileSync(this.absolutePath(opts.l)))
15
+    }
16
+
11 17
     let output
12 18
 
13 19
     if (opts.f && opts.f !== true) {
@@ -62,7 +68,7 @@ class OsloCLI {
62 68
 
63 69
   file(file) {
64 70
     const contents = fs.readFileSync(file).toString()
65
-    return oslo(contents)
71
+    return oslo(contents, this.context)
66 72
   }
67 73
 
68 74
   directory(directory) {
@@ -80,7 +86,7 @@ class OsloCLI {
80 86
   }
81 87
 
82 88
   inline(source) {
83
-    return oslo(source)
89
+    return oslo(source, this.context)
84 90
   }
85 91
 
86 92
   absolutePath(file, root = null) {
@@ -107,6 +113,10 @@ class OsloCLI {
107 113
 
108 114
       oslo -f index.oslo
109 115
 
116
+  -l The path to a json file to use as context for compilation
117
+
118
+      oslo -f index.oslo -l data.json
119
+
110 120
   -o Where to direct the output. If no path is provided, output will be directed
111 121
      to stdout. When used with -f, the argument should be the path to a file.
112 122
      When used with -d, the argument should be the path to a directory, which

+ 17
- 3
src/compiler.js View File

@@ -16,13 +16,27 @@ module.exports = class Compiler {
16 16
         )
17 17
         const compiler = new Compiler(node.subtree, this.context)
18 18
         const content = compiler.compile()
19
-        this.result += `<${node.functionName}${
20
-          attributes.length ? ' ' : ''
21
-        }${attributes.join(' ')}>${content}</${node.functionName}>`
19
+
20
+        if (node.functionName) {
21
+          this.result += `<${node.functionName}${
22
+            attributes.length ? ' ' : ''
23
+          }${attributes.join(' ')}>${content}</${node.functionName}>`
24
+        } else {
25
+          this.result += content
26
+        }
22 27
       } else if (node.type === 'string') {
23 28
         this.result += node.content
24 29
       } else if (node.type === 'identifier') {
25 30
         this.result += this.lookup(node.name)
31
+      } else if (node.type === 'map') {
32
+        const symbol = node.symbol.value
33
+        const subject = this.lookup(node.subject.name)
34
+        subject.forEach(item => {
35
+          let context = {}
36
+          context[symbol] = item
37
+          const compiler = new Compiler([node.body], context)
38
+          this.result += compiler.compile()
39
+        })
26 40
       }
27 41
     })
28 42
 

+ 25
- 7
src/lexer.js View File

@@ -21,7 +21,7 @@ module.exports = class Lexer {
21 21
           line: line,
22 22
         })
23 23
         pos++
24
-      } else if (source[pos].match(/['"]/)) {
24
+      } else if (source[pos].match(/["]/)) {
25 25
         allowSpecialCharactersInLiterals = !allowSpecialCharactersInLiterals
26 26
         tokenStream.tokens.push({
27 27
           type: tokenTypes.QUOTE,
@@ -36,6 +36,14 @@ module.exports = class Lexer {
36 36
           value: value,
37 37
         })
38 38
         pos += value.length + 1 // the +1 is to account for the colon
39
+      } else if (source[pos].match(/\'/)) {
40
+        let value = /'([^()"\s]+)/.exec(source.slice(pos))[1].trim()
41
+        tokenStream.tokens.push({
42
+          type: tokenTypes.SYMBOL,
43
+          line: line,
44
+          value: value,
45
+        })
46
+        pos += value.length + 1 // the +1 is to account for the apostrophe
39 47
       } else if (source[pos].match(/\n/)) {
40 48
         line++
41 49
         pos++
@@ -48,12 +56,22 @@ module.exports = class Lexer {
48 56
           endPattern = /[^"']+/
49 57
         }
50 58
 
51
-        let value = endPattern.exec(source.slice(pos))[0]
52
-        tokenStream.tokens.push({
53
-          type: tokenTypes.LITERAL,
54
-          line: line,
55
-          value: value.trim(),
56
-        })
59
+        let value = endPattern.exec(source.slice(pos))[0].trim()
60
+
61
+        if (['if', 'map'].includes(value)) {
62
+          tokenStream.tokens.push({
63
+            type: tokenTypes.KEYWORD,
64
+            line: line,
65
+            value: value,
66
+          })
67
+        } else {
68
+          tokenStream.tokens.push({
69
+            type: tokenTypes.LITERAL,
70
+            line: line,
71
+            value: value.trim(),
72
+          })
73
+        }
74
+
57 75
         pos += value.length
58 76
       }
59 77
     }

+ 18
- 0
src/parser.js View File

@@ -35,6 +35,8 @@ module.exports = class Parser {
35 35
         elementNode.subtree.push(this.identifier())
36 36
       } else if (this.tokenStream.peek().type === tokenTypes.QUOTE) {
37 37
         elementNode.subtree.push(this.quotedString())
38
+      } else if (this.tokenStream.peek().type === tokenTypes.KEYWORD) {
39
+        elementNode.subtree.push(this.keyword())
38 40
       }
39 41
     }
40 42
 
@@ -72,6 +74,22 @@ module.exports = class Parser {
72 74
     })
73 75
   }
74 76
 
77
+  keyword() {
78
+    const keyword = this.tokenStream.eat(tokenTypes.KEYWORD)
79
+
80
+    if (keyword.value === 'map') {
81
+      const symbol = this.tokenStream.eat(tokenTypes.SYMBOL)
82
+      const body = this.expr()
83
+      const subject = this.identifier()
84
+      return new Node({
85
+        type: 'map',
86
+        symbol: symbol,
87
+        body: body,
88
+        subject: subject,
89
+      })
90
+    }
91
+  }
92
+
75 93
   string() {
76 94
     this.tokenStream.eat(tokenTypes.QUOTE)
77 95
     let stringValue = this.tokenStream.eat(tokenTypes.LITERAL).value

+ 1
- 0
src/tokenTypes.js View File

@@ -5,5 +5,6 @@ module.exports = {
5 5
   QUOTE: '"',
6 6
   LITERAL: 'literal',
7 7
   ATTRIBUTE: 'attribute',
8
+  SYMBOL: 'symbol',
8 9
   EOF: 'eof',
9 10
 }

+ 19
- 0
test/compiler.js View File

@@ -43,3 +43,22 @@ test('renders variables according to passed-in context', t => {
43 43
     '<div class="foobar"><p class="bazquux">Lorem ipsum dolor sit amet.</p></div>',
44 44
   )
45 45
 })
46
+
47
+test('compiles map operations', function(t) {
48
+  t.plan(1)
49
+  const lexer = new Lexer()
50
+  const tokenStream = lexer.scan(`
51
+    (ul
52
+      (map 'item (li item) items))
53
+  `)
54
+  const parser = new Parser(tokenStream)
55
+  const tree = parser.parse()
56
+  const compiler = new Compiler(tree, {
57
+    items: ['one', 'two', 'three'],
58
+  })
59
+  const result = compiler.compile()
60
+  t.deepEqual(
61
+    result.replace(/\n/g, '').replace(/  +/g, ''),
62
+    '<ul><li>one</li><li>two</li><li>three</li></ul>',
63
+  )
64
+})

Loading…
Cancel
Save