1 | module.exports = Compiler;
|
2 |
|
3 | function Compiler(node, opts) {
|
4 | this.opts = opts = opts || {};
|
5 | this.node = node;
|
6 | }
|
7 |
|
8 | Compiler.prototype.compile = function() {
|
9 | var ast = this.visit(this.node);
|
10 | return 'return ' + JSON.stringify(ast) + ';';
|
11 | };
|
12 |
|
13 | Compiler.prototype.visit = function(node) {
|
14 | if (!node || !node.type) return undefined;
|
15 | return this['visit' + node.type](node);
|
16 | };
|
17 |
|
18 | Compiler.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 |
|
29 | Compiler.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 |
|
40 | Compiler.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 |
|
51 | Compiler.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 |
|
63 | Compiler.prototype.visitMixinBlock = function(node, ast) {
|
64 |
|
65 | throw errorAtNode(node, new Error('Mixins are not supported at this time'));
|
66 | };
|
67 |
|
68 | Compiler.prototype.visitDoctype = function(node, ast) {
|
69 |
|
70 | throw errorAtNode(node, new Error('Doctypes are not supported at this time'));
|
71 | };
|
72 |
|
73 | Compiler.prototype.visitMixin = function(node, ast) {
|
74 |
|
75 | throw errorAtNode(node, new Error('Mixins are not supported at this time'));
|
76 | };
|
77 |
|
78 | Compiler.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 |
|
118 | Compiler.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 |
|
127 | Compiler.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 |
|
138 | Compiler.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 |
|
152 | Compiler.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 |
|
161 | Compiler.prototype.visitFilter = function(node, ast) {
|
162 |
|
163 | throw errorAtNode(node, new Error('Filters are not supported at this time'));
|
164 | };
|
165 |
|
166 | Compiler.prototype.visitText = function(node, ast) {
|
167 | if (!node.val) return false;
|
168 |
|
169 |
|
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 |
|
179 | Compiler.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 |
|
212 | Compiler.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 |
|
221 | Compiler.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 |
|
233 | Compiler.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 |
|
286 | Compiler.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 |
|
308 | Compiler.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 |
|
318 | Compiler.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 |
|
349 | function errorAtNode(node, error) {
|
350 | error.line = node.line;
|
351 | error.filename = node.filename;
|
352 | return error;
|
353 | }
|