UNPKG

12 kBJavaScriptView Raw
1/*jshint latedef:false */
2(function(root, factory) {
3 if (typeof define === "function" && define.amd && define.amd.dust === true) {
4 define("dust.compile", ["dust.core", "dust.parse"], function(dust, parse) {
5 return factory(parse, dust).compile;
6 });
7 } else if (typeof exports === 'object') {
8 // in Node, require this file if we want to use the compiler as a standalone module
9 module.exports = factory(require('./parser').parse, require('./dust'));
10 } else {
11 // in the browser, store the factory output if we want to use the compiler directly
12 factory(root.dust.parse, root.dust);
13 }
14}(this, function(parse, dust) {
15 var compiler = {},
16 isArray = dust.isArray;
17
18
19 compiler.compile = function(source, name) {
20 // the name parameter is optional.
21 // this can happen for templates that are rendered immediately (renderSource which calls compileFn) or
22 // for templates that are compiled as a callable (compileFn)
23 //
24 // for the common case (using compile and render) a name is required so that templates will be cached by name and rendered later, by name.
25 if (!name && name !== null) {
26 throw new Error('Template name parameter cannot be undefined when calling dust.compile');
27 }
28
29 try {
30 var ast = filterAST(parse(source));
31 return compile(ast, name);
32 }
33 catch (err)
34 {
35 if (!err.line || !err.column) {
36 throw err;
37 }
38 throw new SyntaxError(err.message + ' At line : ' + err.line + ', column : ' + err.column);
39 }
40 };
41
42 function filterAST(ast) {
43 var context = {};
44 return compiler.filterNode(context, ast);
45 }
46
47 compiler.filterNode = function(context, node) {
48 return compiler.optimizers[node[0]](context, node);
49 };
50
51 compiler.optimizers = {
52 body: compactBuffers,
53 buffer: noop,
54 special: convertSpecial,
55 format: format,
56 reference: visit,
57 '#': visit,
58 '?': visit,
59 '^': visit,
60 '<': visit,
61 '+': visit,
62 '@': visit,
63 '%': visit,
64 partial: visit,
65 context: visit,
66 params: visit,
67 bodies: visit,
68 param: visit,
69 filters: noop,
70 key: noop,
71 path: noop,
72 literal: noop,
73 raw: noop,
74 comment: nullify,
75 line: nullify,
76 col: nullify
77 };
78
79 compiler.pragmas = {
80 esc: function(compiler, context, bodies, params) {
81 var old = compiler.auto,
82 out;
83 if (!context) {
84 context = 'h';
85 }
86 compiler.auto = (context === 's') ? '' : context;
87 out = compileParts(compiler, bodies.block);
88 compiler.auto = old;
89 return out;
90 }
91 };
92
93 function visit(context, node) {
94 var out = [node[0]],
95 i, len, res;
96 for (i=1, len=node.length; i<len; i++) {
97 res = compiler.filterNode(context, node[i]);
98 if (res) {
99 out.push(res);
100 }
101 }
102 return out;
103 }
104
105 // Compacts consecutive buffer nodes into a single node
106 function compactBuffers(context, node) {
107 var out = [node[0]],
108 memo, i, len, res;
109 for (i=1, len=node.length; i<len; i++) {
110 res = compiler.filterNode(context, node[i]);
111 if (res) {
112 if (res[0] === 'buffer' || res[0] === 'format') {
113 if (memo) {
114 memo[0] = (res[0] === 'buffer') ? 'buffer' : memo[0];
115 memo[1] += res.slice(1, -2).join('');
116 } else {
117 memo = res;
118 out.push(res);
119 }
120 } else {
121 memo = null;
122 out.push(res);
123 }
124 }
125 }
126 return out;
127 }
128
129 var specialChars = {
130 's': ' ',
131 'n': '\n',
132 'r': '\r',
133 'lb': '{',
134 'rb': '}'
135 };
136
137 function convertSpecial(context, node) {
138 return ['buffer', specialChars[node[1]], node[2], node[3]];
139 }
140
141 function noop(context, node) {
142 return node;
143 }
144
145 function nullify(){}
146
147 function format(context, node) {
148 if(dust.config.whitespace) {
149 // Format nodes are in the form ['format', eol, whitespace, line, col],
150 // which is unlike other nodes in that there are two pieces of content
151 // Join eol and whitespace together to normalize the node format
152 node.splice(1, 2, node.slice(1, -2).join(''));
153 return node;
154 }
155 return null;
156 }
157
158 function compile(ast, name) {
159 var context = {
160 name: name,
161 bodies: [],
162 blocks: {},
163 index: 0,
164 auto: 'h'
165 },
166 escapedName = dust.escapeJs(name),
167 body_0 = 'function(dust){dust.register(' +
168 (name ? '"' + escapedName + '"' : 'null') + ',' +
169 compiler.compileNode(context, ast) +
170 ');' +
171 compileBlocks(context) +
172 compileBodies(context) +
173 'return body_0;}';
174
175 if(dust.config.amd) {
176 return 'define("' + escapedName + '",["dust.core"],' + body_0 + ');';
177 } else {
178 return '(' + body_0 + ')(dust);';
179 }
180 }
181
182 function compileBlocks(context) {
183 var out = [],
184 blocks = context.blocks,
185 name;
186
187 for (name in blocks) {
188 out.push('"' + name + '":' + blocks[name]);
189 }
190 if (out.length) {
191 context.blocks = 'ctx=ctx.shiftBlocks(blocks);';
192 return 'var blocks={' + out.join(',') + '};';
193 }
194 return context.blocks = '';
195 }
196
197 function compileBodies(context) {
198 var out = [],
199 bodies = context.bodies,
200 blx = context.blocks,
201 i, len;
202
203 for (i=0, len=bodies.length; i<len; i++) {
204 out[i] = 'function body_' + i + '(chk,ctx){' +
205 blx + 'return chk' + bodies[i] + ';}body_' + i + '.__dustBody=!0;';
206 }
207 return out.join('');
208 }
209
210 function compileParts(context, body) {
211 var parts = '',
212 i, len;
213 for (i=1, len=body.length; i<len; i++) {
214 parts += compiler.compileNode(context, body[i]);
215 }
216 return parts;
217 }
218
219 compiler.compileNode = function(context, node) {
220 return compiler.nodes[node[0]](context, node);
221 };
222
223 compiler.nodes = {
224 body: function(context, node) {
225 var id = context.index++,
226 name = 'body_' + id;
227 context.bodies[id] = compileParts(context, node);
228 return name;
229 },
230
231 buffer: function(context, node) {
232 return '.w(' + escape(node[1]) + ')';
233 },
234
235 format: function(context, node) {
236 return '.w(' + escape(node[1]) + ')';
237 },
238
239 reference: function(context, node) {
240 return '.f(' + compiler.compileNode(context, node[1]) +
241 ',ctx,' + compiler.compileNode(context, node[2]) + ')';
242 },
243
244 '#': function(context, node) {
245 return compileSection(context, node, 'section');
246 },
247
248 '?': function(context, node) {
249 return compileSection(context, node, 'exists');
250 },
251
252 '^': function(context, node) {
253 return compileSection(context, node, 'notexists');
254 },
255
256 '<': function(context, node) {
257 var bodies = node[4];
258 for (var i=1, len=bodies.length; i<len; i++) {
259 var param = bodies[i],
260 type = param[1][1];
261 if (type === 'block') {
262 context.blocks[node[1].text] = compiler.compileNode(context, param[2]);
263 return '';
264 }
265 }
266 return '';
267 },
268
269 '+': function(context, node) {
270 if (typeof(node[1].text) === 'undefined' && typeof(node[4]) === 'undefined'){
271 return '.block(ctx.getBlock(' +
272 compiler.compileNode(context, node[1]) +
273 ',chk, ctx),' + compiler.compileNode(context, node[2]) + ', {},' +
274 compiler.compileNode(context, node[3]) +
275 ')';
276 } else {
277 return '.block(ctx.getBlock(' +
278 escape(node[1].text) +
279 '),' + compiler.compileNode(context, node[2]) + ',' +
280 compiler.compileNode(context, node[4]) + ',' +
281 compiler.compileNode(context, node[3]) +
282 ')';
283 }
284 },
285
286 '@': function(context, node) {
287 return '.h(' +
288 escape(node[1].text) +
289 ',' + compiler.compileNode(context, node[2]) + ',' +
290 compiler.compileNode(context, node[4]) + ',' +
291 compiler.compileNode(context, node[3]) +
292 ')';
293 },
294
295 '%': function(context, node) {
296 // TODO: Move these hacks into pragma precompiler
297 var name = node[1][1],
298 rawBodies,
299 bodies,
300 rawParams,
301 params,
302 ctx, b, p, i, len;
303 if (!compiler.pragmas[name]) {
304 return '';
305 }
306
307 rawBodies = node[4];
308 bodies = {};
309 for (i=1, len=rawBodies.length; i<len; i++) {
310 b = rawBodies[i];
311 bodies[b[1][1]] = b[2];
312 }
313
314 rawParams = node[3];
315 params = {};
316 for (i=1, len=rawParams.length; i<len; i++) {
317 p = rawParams[i];
318 params[p[1][1]] = p[2][1];
319 }
320
321 ctx = node[2][1] ? node[2][1].text : null;
322
323 return compiler.pragmas[name](context, ctx, bodies, params);
324 },
325
326 partial: function(context, node) {
327 return '.p(' +
328 compiler.compileNode(context, node[1]) +
329 ',' + compiler.compileNode(context, node[2]) +
330 ',' + compiler.compileNode(context, node[3]) + ')';
331 },
332
333 context: function(context, node) {
334 if (node[1]) {
335 return 'ctx.rebase(' + compiler.compileNode(context, node[1]) + ')';
336 }
337 return 'ctx';
338 },
339
340 params: function(context, node) {
341 var out = [];
342 for (var i=1, len=node.length; i<len; i++) {
343 out.push(compiler.compileNode(context, node[i]));
344 }
345 if (out.length) {
346 return '{' + out.join(',') + '}';
347 }
348 return '{}';
349 },
350
351 bodies: function(context, node) {
352 var out = [];
353 for (var i=1, len=node.length; i<len; i++) {
354 out.push(compiler.compileNode(context, node[i]));
355 }
356 return '{' + out.join(',') + '}';
357 },
358
359 param: function(context, node) {
360 return compiler.compileNode(context, node[1]) + ':' + compiler.compileNode(context, node[2]);
361 },
362
363 filters: function(context, node) {
364 var list = [];
365 for (var i=1, len=node.length; i<len; i++) {
366 var filter = node[i];
367 list.push('"' + filter + '"');
368 }
369 return '"' + context.auto + '"' +
370 (list.length ? ',[' + list.join(',') + ']' : '');
371 },
372
373 key: function(context, node) {
374 return 'ctx.get(["' + node[1] + '"], false)';
375 },
376
377 path: function(context, node) {
378 var current = node[1],
379 keys = node[2],
380 list = [];
381
382 for (var i=0,len=keys.length; i<len; i++) {
383 if (isArray(keys[i])) {
384 list.push(compiler.compileNode(context, keys[i]));
385 } else {
386 list.push('"' + keys[i] + '"');
387 }
388 }
389 return 'ctx.getPath(' + current + ', [' + list.join(',') + '])';
390 },
391
392 literal: function(context, node) {
393 return escape(node[1]);
394 },
395 raw: function(context, node) {
396 return ".w(" + escape(node[1]) + ")";
397 }
398 };
399
400 function compileSection(context, node, cmd) {
401 return '.' + (dust._aliases[cmd] || cmd) + '(' +
402 compiler.compileNode(context, node[1]) +
403 ',' + compiler.compileNode(context, node[2]) + ',' +
404 compiler.compileNode(context, node[4]) + ',' +
405 compiler.compileNode(context, node[3]) +
406 ')';
407 }
408
409 var BS = /\\/g,
410 DQ = /"/g,
411 LF = /\f/g,
412 NL = /\n/g,
413 CR = /\r/g,
414 TB = /\t/g;
415 function escapeToJsSafeString(str) {
416 return str.replace(BS, '\\\\')
417 .replace(DQ, '\\"')
418 .replace(LF, '\\f')
419 .replace(NL, '\\n')
420 .replace(CR, '\\r')
421 .replace(TB, '\\t');
422 }
423
424 var escape = (typeof JSON === 'undefined') ?
425 function(str) { return '"' + escapeToJsSafeString(str) + '"';} :
426 JSON.stringify;
427
428 // expose compiler methods
429 dust.compile = compiler.compile;
430 dust.filterNode = compiler.filterNode;
431 dust.optimizers = compiler.optimizers;
432 dust.pragmas = compiler.pragmas;
433 dust.compileNode = compiler.compileNode;
434 dust.nodes = compiler.nodes;
435
436 return compiler;
437
438}));