1 |
|
2 | var debug = require('debug');
|
3 | var lg = debug('vash:codegen');
|
4 |
|
5 | var gens = {}
|
6 |
|
7 | gens.VashProgram = function(node, opts, generate) {
|
8 | return node.body.map(generate).join('');
|
9 | }
|
10 |
|
11 | gens.VashExplicitExpression = function(node, opts, generate) {
|
12 | var str = node.values.map(generate).join('');
|
13 | str = '(' + maybeHTMLEscape(node, opts, str) + ')';
|
14 | if (parentIsContent(node)) {
|
15 | str = bewrap(str);
|
16 | }
|
17 | return str;
|
18 | }
|
19 |
|
20 | gens.VashExpression = function(node, opts, generate) {
|
21 | var str = node.values.map(generate).join('');
|
22 | str = bewrap(maybeHTMLEscape(node, opts, str));
|
23 | return str;
|
24 | }
|
25 |
|
26 | gens.VashRegex = function(node, opts, generate) {
|
27 | var str = node.values.map(generate).join('');
|
28 | str = maybeHTMLEscape(node, opts, str);
|
29 | if (parentIsContent(node)) {
|
30 | str = bewrap(str);
|
31 | }
|
32 | return str;
|
33 | }
|
34 |
|
35 | gens.VashMarkup = function(node, opts, generate) {
|
36 | var isText = node.name === 'text';
|
37 | var name = node.name ? bcwrap(node.name) : '';
|
38 | var tagNameValue = name
|
39 | + (node.expression ? generate(node.expression) : '');
|
40 |
|
41 | var tagOpen = ''
|
42 | + bcwrap('<')
|
43 | + tagNameValue
|
44 | + bcwrap(node.attributes.length ? ' ' : '')
|
45 | + node.attributes.map(generate).join(bcwrap(' '))
|
46 |
|
47 | var values;
|
48 | var tagClose;
|
49 |
|
50 | if (node.isVoid) {
|
51 | tagOpen += bcwrap(node.voidClosed ? ' />' : '>');
|
52 | values = '';
|
53 | tagClose = '';
|
54 | } else {
|
55 | tagOpen += bcwrap('>');
|
56 | values = node.values.map(generate).join('');
|
57 | tagClose = node.isClosed ? bcwrap('</') + tagNameValue + bcwrap('>') : '';
|
58 | }
|
59 |
|
60 | if (isText) {
|
61 | tagOpen = tagClose = '';
|
62 | }
|
63 |
|
64 | return ''
|
65 | + (parentIsExpression(node) ? '(function () {' : '')
|
66 | + dbgstart(node, opts)
|
67 | + tagOpen
|
68 | + values
|
69 | + tagClose
|
70 | + dbgend(node, opts)
|
71 | + (parentIsExpression(node) ? '}())' : '')
|
72 | }
|
73 |
|
74 | gens.VashMarkupAttribute = function(node, opts, generate) {
|
75 | var quote = node.rightIsQuoted || '';
|
76 | quote = escapeMarkupContent(quote);
|
77 | return ''
|
78 | + dbgstart(node, opts)
|
79 | + node.left.map(generate).join('')
|
80 | + (node.right.length || node.rightIsQuoted
|
81 | ? bcwrap('=' + quote)
|
82 | + node.right.map(generate).join('')
|
83 | + bcwrap(quote)
|
84 | : '')
|
85 | + dbgend(node, opts);
|
86 | }
|
87 |
|
88 | gens.VashMarkupContent = function(node, opts, generate) {
|
89 | return ''
|
90 | + dbgstart(node, opts)
|
91 | + node.values.map(generate).join('')
|
92 | + dbgend(node, opts);
|
93 | }
|
94 |
|
95 | gens.VashMarkupComment = function(node, opts, generate) {
|
96 | return ''
|
97 | + bcwrap('<!--')
|
98 | + dbgstart(node, opts)
|
99 | + node.values.map(generate).join('')
|
100 | + dbgend(node, opts)
|
101 | + bcwrap('-->');
|
102 | }
|
103 |
|
104 | gens.VashBlock = function(node, opts, generate) {
|
105 | var hasValues = node.values.length > 0;
|
106 | var unsafeForDbg = node.keyword === 'switch'
|
107 | || !node.name
|
108 | || !hasValues;
|
109 | var openBrace = hasValues || node.hasBraces
|
110 | ? '{' + (unsafeForDbg ? '' : dbgstart(node, opts))
|
111 | : '';
|
112 | var closeBrace = hasValues || node.hasBraces
|
113 | ? (unsafeForDbg ? '' : dbgend(node, opts)) + '}'
|
114 | : '';
|
115 | return ''
|
116 | + (node.keyword ? node.keyword : '')
|
117 | + node.head.map(generate).join('')
|
118 | + openBrace
|
119 | + node.values.map(generate).join('')
|
120 | + closeBrace
|
121 | + node.tail.map(generate).join('');
|
122 | }
|
123 |
|
124 | gens.VashIndexExpression = function(node, opts, generate) {
|
125 | var str = node.values.map(generate).join('');
|
126 | return '[' + str + ']';
|
127 | }
|
128 |
|
129 | gens.VashText = function(node, opts, generate) {
|
130 | if (!node.value.length) return '';
|
131 | return parentIsContent(node)
|
132 | ? ''
|
133 | + dbgstart(node, opts)
|
134 | + bcwrap(escapeMarkupContent(node.value))
|
135 | + dbgend(node, opts)
|
136 | : node.value;
|
137 | }
|
138 |
|
139 | gens.VashComment = function(node, opts, generate) {
|
140 | return '';
|
141 | }
|
142 |
|
143 | var reQuote = /(['"])/g;
|
144 | var reEscapedQuote = /\\+(["'])/g;
|
145 | var reLineBreak = /\n/g;
|
146 | var reHelpersName = /HELPERSNAME/g;
|
147 | var reModelName = /MODELNAME/g;
|
148 | var reOriginalMarkup = /ORIGINALMARKUP/g;
|
149 |
|
150 | function escapeMarkupContent(str) {
|
151 | return str
|
152 | .replace(/\\/g, '\\\\')
|
153 | .replace(reQuote, '\\$1')
|
154 | .replace(reLineBreak, '\\n');
|
155 | }
|
156 |
|
157 | var BUFFER_HEAD = '\n__vbuffer.push(';
|
158 | var BUFFER_TAIL = ');\n';
|
159 |
|
160 |
|
161 | function bcwrap(str) {
|
162 | return BUFFER_HEAD + '\'' + str.replace(/\n/, '\\n') + '\'' + BUFFER_TAIL;
|
163 | }
|
164 |
|
165 |
|
166 | function bewrap(str) {
|
167 | return BUFFER_HEAD + str + BUFFER_TAIL;
|
168 | }
|
169 |
|
170 | function parentIsContent(node) {
|
171 | return node.parent.type === 'VashMarkup'
|
172 | || node.parent.type === 'VashMarkupContent'
|
173 | || node.parent.type === 'VashMarkupComment'
|
174 | || node.parent.type === 'VashMarkupAttribute'
|
175 | || node.parent.type === 'VashProgram';
|
176 | }
|
177 |
|
178 | function parentIsExpression(node) {
|
179 | return node.parent.type === 'VashExpression'
|
180 | || node.parent.type === 'VashExplicitExpression'
|
181 | || node.parent.type === 'VashIndexExpression';
|
182 | }
|
183 |
|
184 | function dbgstart(node, opts) {
|
185 | return opts.debug
|
186 | ? ''
|
187 | + opts.helpersName + '.vl = ' + node.startloc.line + ', '
|
188 | + opts.helpersName + '.vc = ' + node.startloc.column + '; \n'
|
189 | : '';
|
190 | }
|
191 |
|
192 | function dbgend(node, opts) {
|
193 | return opts.debug
|
194 | ? ''
|
195 | + opts.helpersName + '.vl = ' + node.endloc.line + ', '
|
196 | + opts.helpersName + '.vc = ' + node.endloc.column + '; \n'
|
197 | : '';
|
198 | }
|
199 |
|
200 | function maybeHTMLEscape(node, opts, str) {
|
201 | if (parentIsContent(node) && opts.htmlEscape) {
|
202 | return opts.helpersName + '.escape(' + str + ').toHtmlString()';
|
203 | } else {
|
204 | return str;
|
205 | }
|
206 | }
|
207 |
|
208 | function replaceDevTokens(str, opts){
|
209 | return str
|
210 | .replace( reHelpersName, opts.helpersName )
|
211 | .replace( reModelName, opts.modelName );
|
212 | }
|
213 |
|
214 | function head(opts){
|
215 | var str = ''
|
216 | + (opts.debug ? 'try { \n' : '')
|
217 | + 'var __vbuffer = HELPERSNAME.buffer; \n'
|
218 | + 'HELPERSNAME.options = __vopts; \n'
|
219 | + 'MODELNAME = MODELNAME || {}; \n'
|
220 | + (opts.useWith ? 'with( MODELNAME ){ \n' : '');
|
221 |
|
222 | str = replaceDevTokens(str, opts);
|
223 | return str;
|
224 | }
|
225 |
|
226 | function helperHead(opts){
|
227 | var str = ''
|
228 | + (opts.debug ? 'try { \n' : '')
|
229 | + 'var __vbuffer = this.buffer; \n'
|
230 | + 'var MODELNAME = this.model; \n'
|
231 | + 'var HELPERSNAME = this; \n';
|
232 |
|
233 | str = replaceDevTokens(str, opts);
|
234 | return str;
|
235 | }
|
236 |
|
237 | function tail(opts){
|
238 | var str = ''
|
239 | + (opts.simple
|
240 | ? 'return HELPERSNAME.buffer.join(""); \n'
|
241 | : ';(__vopts && __vopts.onRenderEnd && __vopts.onRenderEnd(null, HELPERSNAME)); \n'
|
242 | + 'return (__vopts && __vopts.asContext) \n'
|
243 | + ' ? HELPERSNAME \n'
|
244 | + ' : HELPERSNAME.toString(); \n' )
|
245 | + (opts.useWith ? '} \n' : '')
|
246 | + (opts.debug ? '} catch( e ){ \n'
|
247 | + ' HELPERSNAME.reportError( e, HELPERSNAME.vl, HELPERSNAME.vc, "ORIGINALMARKUP", "!LB!", true ); \n'
|
248 | + '} \n' : '');
|
249 |
|
250 | str = replaceDevTokens(str, opts)
|
251 | .replace(reOriginalMarkup, escapeForDebug(opts.source));
|
252 |
|
253 | return str;
|
254 | }
|
255 |
|
256 | function helperTail(opts){
|
257 | var str = ''
|
258 | + (opts.debug ? '} catch( e ){ \n'
|
259 | + ' HELPERSNAME.reportError( e, HELPERSNAME.vl, HELPERSNAME.vc, "ORIGINALMARKUP", "!LB!", true ); \n'
|
260 | + '} \n' : '');
|
261 |
|
262 | str = replaceDevTokens(str, opts)
|
263 | .replace(reOriginalMarkup, escapeForDebug(opts.source));
|
264 |
|
265 | return str;
|
266 | }
|
267 |
|
268 | function escapeForDebug( str ){
|
269 | return str
|
270 | .replace(reLineBreak, '!LB!')
|
271 | .replace(reQuote, '\\$1')
|
272 | .replace(reEscapedQuote, '\\$1')
|
273 | }
|
274 |
|
275 |
|
276 |
|
277 | function condenseContent(str) {
|
278 | return str
|
279 | .replace(/'\);\n+__vbuffer.push\('/g, '')
|
280 | .replace(/\n+/g, '\n');
|
281 | }
|
282 |
|
283 | function generate(node, opts) {
|
284 |
|
285 | function gen(opts, node) {
|
286 | lg('Entering ' + node.type);
|
287 | var str = gens[node.type](node, opts, genChild);
|
288 | lg('Leaving ' + node.type);
|
289 | return str;
|
290 |
|
291 | function genChild(child) {
|
292 | if (!child.parent) child.parent = node;
|
293 | lg('Generating child type %s of parent type %s', child.type, node.type)
|
294 | return gen(opts, child);
|
295 | }
|
296 | }
|
297 |
|
298 | var generated = gen(opts, node);
|
299 |
|
300 | var body;
|
301 | if(!opts.asHelper){
|
302 | body = head(opts) + generated + tail(opts);
|
303 | } else {
|
304 | body = helperHead(opts) + generated + helperTail(opts);
|
305 | }
|
306 |
|
307 | return opts.debug
|
308 | ? body
|
309 | : condenseContent(body);
|
310 | }
|
311 |
|
312 | module.exports = generate;
|