UNPKG

8.37 kBJavaScriptView Raw
1module.exports = Compiler;
2
3function Compiler(node, opts) {
4 this.opts = opts = opts || {};
5 this.node = node;
6}
7
8Compiler.prototype.compile = function() {
9 var ast = this.visit(this.node);
10 return 'return ' + JSON.stringify(ast) + ';';
11};
12
13Compiler.prototype.visit = function(node) {
14 if (!node || !node.type) return undefined;
15 return this['visit' + node.type](node);
16};
17
18Compiler.prototype.visitCase = function(node) {
19 return {
20 type: 'switch',
21 expression: node.expr,
22 children: this.visit(node.block),
23 line: node.line,
24 filename: node.filename,
25 buffer: true
26 }
27};
28
29Compiler.prototype.visitWhen = function(node) {
30 return {
31 type: node.expr === 'default' ? 'default' : 'case',
32 expression: node.expr !== 'default' ? node.expr : undefined,
33 children: this.visit(node.block),
34 line: node.line,
35 filename: node.filename,
36 buffer: true
37 };
38};
39
40Compiler.prototype.visitLiteral = function(node) {
41 return {
42 type: 'expression',
43 expression: node.str,
44 buffer: node.buffer,
45 escape: node.escape,
46 line: node.line,
47 filename: node.filename
48 };
49};
50
51Compiler.prototype.visitBlock = function(node) {
52 var length = node.nodes.length;
53
54 var ast = [];
55
56 for (var i = 0; i < length; ++i) {
57 var out = this.visit(node.nodes[i]);
58 if (out) ast.push(out);
59 }
60 return ast;
61};
62
63Compiler.prototype.visitMixinBlock = function(node, ast) {
64 // TODO
65 throw errorAtNode(node, new Error('Mixins are not supported at this time'));
66};
67
68Compiler.prototype.visitDoctype = function(node, ast) {
69 // TODO
70 throw errorAtNode(node, new Error('Doctypes are not supported at this time'));
71};
72
73Compiler.prototype.visitMixin = function(node, ast) {
74 // TODO
75 throw errorAtNode(node, new Error('Mixins are not supported at this time'));
76};
77
78Compiler.prototype.visitTag = function(node, ast) {
79 var self = this;
80 var name = node.name;
81
82 if (node.selfClosing &&
83 node.block &&
84 !(node.block.type === 'Block' && node.block.nodes.length === 0) &&
85 node.block.nodes.some(function(tag) {
86 return tag.type !== 'Text' || !/^\s*$/.test(tag.val)
87 })) {
88 throw errorAtNode(node, new Error(name + ' is self closing and should not have content.'));
89 }
90
91 if (name === 't') return self.visitTranslation(node, ast);
92 if (name === 'import') return self.visitImport(node, ast);
93 if (name === 'var') return self.visitVar(node, ast);
94 if (name === 'export') return self.visitExport(node, ast);
95 if (name === 'function') return self.visitFunction(node, ast);
96
97 var attrs = node.attrs.slice();
98
99 var children = (node.block && node.block.nodes.length && node.block.nodes || (node.code ? [node.code] : [])).reduce(function(acc, child) {
100 if (child.type !== 'Block' || !child.name) acc.push(self.visit(child));
101 else attrs.push({name: child.name, val: self.visit(child), block: true, args: child.args});
102 return acc;
103 }, []);
104
105 var el = {
106 type: 'tag',
107 name: name,
108 props: this.visitAttributes(attrs, node.attributeBlocks),
109 children: children,
110 line: node.line,
111 filename: node.filename,
112 buffer: true
113 };
114
115 return el;
116};
117
118Compiler.prototype.visitImport = function(node, ast) {
119 return {
120 type: 'import',
121 expression: node.block.nodes[0].val,
122 line: node.line,
123 filename: node.filename
124 };
125};
126
127Compiler.prototype.visitExport = function(node, ast) {
128 return {
129 type: 'export',
130 expression: node.block.nodes.map(function(node) {
131 return node.val;
132 }).join('\n'),
133 line: node.line,
134 filename: node.filename
135 }
136};
137
138Compiler.prototype.visitFunction = function(node, ast) {
139 var nodes = node.block.nodes;
140 var first = nodes[0];
141
142 var children = nodes.slice(1);
143
144 return {
145 type: 'function',
146 expression: first.val,
147 children: this.visitBlock({nodes: children}),
148 buffer: false
149 }
150};
151
152Compiler.prototype.visitVar = function(node, ast) {
153 return {
154 type: 'var',
155 expression: node.block.nodes[0].val,
156 line: node.line,
157 filename: node.filename
158 };
159};
160
161Compiler.prototype.visitFilter = function(node, ast) {
162 // TODO
163 throw errorAtNode(node, new Error('Filters are not supported at this time'));
164};
165
166Compiler.prototype.visitText = function(node, ast) {
167 if (!node.val) return false;
168 // TODO interpolation
169 // TODO unescape html expressions
170 return {
171 type: 'text',
172 expression: JSON.stringify(node.val),
173 line: node.line,
174 filename: node.filename,
175 buffer: true
176 };
177};
178
179Compiler.prototype.visitTranslation = function(node, ast) {
180 var self = this;
181 var attrs = node.attrs.slice();
182
183 var children = (node.block && node.block.nodes.length && node.block.nodes || (node.code ? [node.code] : [])).map(function(child) {
184 if (child.type !== 'Block') throw errorAtNode(node, new Error('Invalid child for translation'));
185 var name = child.name;
186 if (!name) throw errorAtNode(node, new Error('Block missing name'));
187
188 var visited = self.visit(child);
189
190 if (visited.length === 1) visited[0].props.key = {name: name, expression: JSON.stringify(name)};
191
192 attrs.push({
193 name: name,
194 val: visited,
195 block: true
196 });
197 });
198
199 var el = {
200 type: 'tag',
201 name: 't',
202 props: this.visitAttributes(attrs, node.attributeBlocks, true),
203 children: children,
204 line: node.line,
205 filename: node.filename,
206 buffer: true
207 };
208
209 return el;
210};
211
212Compiler.prototype.visitComment = function(node, ast) {
213 return {
214 type: node.buffer ? 'comment' : 'js_comment',
215 value: node.val,
216 line: node.line,
217 filename: node.filename
218 };
219};
220
221Compiler.prototype.visitBlockComment = function(node, ast) {
222 var value = node.block.nodes.map(function(comment) {
223 return comment.val;
224 }).join('\n');
225 return {
226 type: node.buffer ? 'comment' : 'js_comment',
227 value: value,
228 line: node.line,
229 filename: node.filename
230 };
231};
232
233Compiler.prototype.visitCode = function(node) {
234 var self = this;
235 var val = node.val;
236 var type = val.slice(0, 3);
237 var expr = val.slice(3);
238
239 var children = self.visit(node.block);
240
241 switch(type) {
242 case 'IFF':
243 return {
244 type: 'if',
245 expression: expr,
246 children: children,
247 line: node.line,
248 filename: node.filename
249 };
250 case 'NIF':
251 return {
252 type: 'unless',
253 expression: expr,
254 children: children,
255 line: node.line,
256 filename: node.filename
257 };
258 case 'ELF':
259 return {
260 type: 'elseif',
261 expression: expr,
262 children: children,
263 line: node.line,
264 filename: node.filename
265 };
266 case 'ELS':
267 return {
268 type: 'else',
269 children: children,
270 line: node.line,
271 filename: node.filename
272 };
273 case 'EXP':
274 return {
275 type: 'expression',
276 buffer: node.buffer,
277 expression: expr,
278 escape: node.escape,
279 line: node.line,
280 filename: node.filename
281 };
282 }
283 throw node;
284};
285
286Compiler.prototype.visitEach = function(node, ast) {
287 var parts = node.val.split(/ +in +/);
288
289 if (parts.length === 1) return {
290 type: 'for',
291 expression: node.val.replace(/^ *\(/, '').replace(/\) *$/, ''),
292 children: node.block && this.visit(node.block),
293 buffer: true
294 };
295
296 var kv = parts[0].split(/ *\, */);
297
298 return {
299 type: 'each',
300 key: (kv[1] || node.key).trim(),
301 value: kv[0].trim(),
302 expression: parts[1].trim(),
303 children: node.block && this.visit(node.block),
304 buffer: true
305 };
306};
307
308Compiler.prototype.visitYield = function(node, ast) {
309 return {
310 type: 'yield',
311 line: node.line,
312 filename: node.filename,
313 name: node.val,
314 args: node.args
315 };
316};
317
318Compiler.prototype.visitAttributes = function(attrs, blocks, isTranslate) {
319 var classes = [];
320
321 var out = attrs.reduce(function(acc, attr, i) {
322 if (attr.name === 'class') {
323 classes.push(attr.val);
324 } else if (isTranslate && i == 0 && attr.val === true) {
325 acc.path = {
326 expression: JSON.stringify(attr.name),
327 escaped: attr.escaped,
328 args: attr.args
329 };
330 } else {
331 acc[attr.name] = {
332 expression: attr.val,
333 escaped: attr.escaped,
334 args: attr.args
335 };
336 }
337 return acc;
338 }, {});
339
340 if (classes.length) {
341 out['class'] = {
342 expressions: classes
343 };
344 }
345
346 return out;
347};
348
349function errorAtNode(node, error) {
350 error.line = node.line;
351 error.filename = node.filename;
352 return error;
353}