1'use strict'; 2 3function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } 4function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 5var parser = require('./parser'); 6var transformer = require('./transformer'); 7var nodes = require('./nodes'); 8var _require = require('./lib'), 9 TemplateError = _require.TemplateError; 10var _require2 = require('./runtime'), 11 Frame = _require2.Frame; 12var _require3 = require('./object'), 13 Obj = _require3.Obj; 14 15// These are all the same for now, but shouldn't be passed straight 16// through 17var compareOps = { 18 '==': '==', 19 '===': '===', 20 '!=': '!=', 21 '!==': '!==', 22 '<': '<', 23 '>': '>', 24 '<=': '<=', 25 '>=': '>=' 26}; 27var Compiler = /*#__PURE__*/function (_Obj) { 28 _inheritsLoose(Compiler, _Obj); 29 function Compiler() { 30 return _Obj.apply(this, arguments) || this; 31 } 32 var _proto = Compiler.prototype; 33 _proto.init = function init(templateName, throwOnUndefined) { 34 this.templateName = templateName; 35 this.codebuf = []; 36 this.lastId = 0; 37 this.buffer = null; 38 this.bufferStack = []; 39 this._scopeClosers = ''; 40 this.inBlock = false; 41 this.throwOnUndefined = throwOnUndefined; 42 }; 43 _proto.fail = function fail(msg, lineno, colno) { 44 if (lineno !== undefined) { 45 lineno += 1; 46 } 47 if (colno !== undefined) { 48 colno += 1; 49 } 50 throw new TemplateError(msg, lineno, colno); 51 }; 52 _proto._pushBuffer = function _pushBuffer() { 53 var id = this._tmpid(); 54 this.bufferStack.push(this.buffer); 55 this.buffer = id; 56 this._emit("var " + this.buffer + " = \"\";"); 57 return id; 58 }; 59 _proto._popBuffer = function _popBuffer() { 60 this.buffer = this.bufferStack.pop(); 61 }; 62 _proto._emit = function _emit(code) { 63 this.codebuf.push(code); 64 }; 65 _proto._emitLine = function _emitLine(code) { 66 this._emit(code + '\n'); 67 }; 68 _proto._emitLines = function _emitLines() { 69 var _this = this; 70 for (var _len = arguments.length, lines = new Array(_len), _key = 0; _key < _len; _key++) { 71 lines[_key] = arguments[_key]; 72 } 73 lines.forEach(function (line) { 74 return _this._emitLine(line); 75 }); 76 }; 77 _proto._emitFuncBegin = function _emitFuncBegin(node, name) { 78 this.buffer = 'output'; 79 this._scopeClosers = ''; 80 this._emitLine("function " + name + "(env, context, frame, runtime, cb) {"); 81 this._emitLine("var lineno = " + node.lineno + ";"); 82 this._emitLine("var colno = " + node.colno + ";"); 83 this._emitLine("var " + this.buffer + " = \"\";"); 84 this._emitLine('try {'); 85 }; 86 _proto._emitFuncEnd = function _emitFuncEnd(noReturn) { 87 if (!noReturn) { 88 this._emitLine('cb(null, ' + this.buffer + ');'); 89 } 90 this._closeScopeLevels(); 91 this._emitLine('} catch (e) {'); 92 this._emitLine(' cb(runtime.handleError(e, lineno, colno));'); 93 this._emitLine('}'); 94 this._emitLine('}'); 95 this.buffer = null; 96 }; 97 _proto._addScopeLevel = function _addScopeLevel() { 98 this._scopeClosers += '})'; 99 }; 100 _proto._closeScopeLevels = function _closeScopeLevels() { 101 this._emitLine(this._scopeClosers + ';'); 102 this._scopeClosers = ''; 103 }; 104 _proto._withScopedSyntax = function _withScopedSyntax(func) { 105 var _scopeClosers = this._scopeClosers; 106 this._scopeClosers = ''; 107 func.call(this); 108 this._closeScopeLevels(); 109 this._scopeClosers = _scopeClosers; 110 }; 111 _proto._makeCallback = function _makeCallback(res) { 112 var err = this._tmpid(); 113 return 'function(' + err + (res ? ',' + res : '') + ') {\n' + 'if(' + err + ') { cb(' + err + '); return; }'; 114 }; 115 _proto._tmpid = function _tmpid() { 116 this.lastId++; 117 return 't_' + this.lastId; 118 }; 119 _proto._templateName = function _templateName() { 120 return this.templateName == null ? 'undefined' : JSON.stringify(this.templateName); 121 }; 122 _proto._compileChildren = function _compileChildren(node, frame) { 123 var _this2 = this; 124 node.children.forEach(function (child) { 125 _this2.compile(child, frame); 126 }); 127 }; 128 _proto._compileAggregate = function _compileAggregate(node, frame, startChar, endChar) { 129 var _this3 = this; 130 if (startChar) { 131 this._emit(startChar); 132 } 133 node.children.forEach(function (child, i) { 134 if (i > 0) { 135 _this3._emit(','); 136 } 137 _this3.compile(child, frame); 138 }); 139 if (endChar) { 140 this._emit(endChar); 141 } 142 }; 143 _proto._compileExpression = function _compileExpression(node, frame) { 144 // TODO: I'm not really sure if this type check is worth it or 145 // not. 146 this.assertType(node, nodes.Literal, nodes.Symbol, nodes.Group, nodes.Array, nodes.Dict, nodes.FunCall, nodes.Caller, nodes.Filter, nodes.LookupVal, nodes.Compare, nodes.InlineIf, nodes.In, nodes.Is, nodes.And, nodes.Or, nodes.Not, nodes.Add, nodes.Concat, nodes.Sub, nodes.Mul, nodes.Div, nodes.FloorDiv, nodes.Mod, nodes.Pow, nodes.Neg, nodes.Pos, nodes.Compare, nodes.NodeList); 147 this.compile(node, frame); 148 }; 149 _proto.assertType = function assertType(node) { 150 for (var _len2 = arguments.length, types = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { 151 types[_key2 - 1] = arguments[_key2]; 152 } 153 if (!types.some(function (t) { 154 return node instanceof t; 155 })) { 156 this.fail("assertType: invalid type: " + node.typename, node.lineno, node.colno); 157 } 158 }; 159 _proto.compileCallExtension = function compileCallExtension(node, frame, async) { 160 var _this4 = this; 161 var args = node.args; 162 var contentArgs = node.contentArgs; 163 var autoescape = typeof node.autoescape === 'boolean' ? node.autoescape : true; 164 if (!async) { 165 this._emit(this.buffer + " += runtime.suppressValue("); 166 } 167 this._emit("env.getExtension(\"" + node.extName + "\")[\"" + node.prop + "\"]("); 168 this._emit('context'); 169 if (args || contentArgs) { 170 this._emit(','); 171 } 172 if (args) { 173 if (!(args instanceof nodes.NodeList)) { 174 this.fail('compileCallExtension: arguments must be a NodeList, ' + 'use `parser.parseSignature`'); 175 } 176 args.children.forEach(function (arg, i) { 177 // Tag arguments are passed normally to the call. Note 178 // that keyword arguments are turned into a single js 179 // object as the last argument, if they exist. 180 _this4._compileExpression(arg, frame); 181 if (i !== args.children.length - 1 || contentArgs.length) { 182 _this4._emit(','); 183 } 184 }); 185 } 186 if (contentArgs.length) { 187 contentArgs.forEach(function (arg, i) { 188 if (i > 0) { 189 _this4._emit(','); 190 } 191 if (arg) { 192 _this4._emitLine('function(cb) {'); 193 _this4._emitLine('if(!cb) { cb = function(err) { if(err) { throw err; }}}'); 194 var id = _this4._pushBuffer(); 195 _this4._withScopedSyntax(function () { 196 _this4.compile(arg, frame); 197 _this4._emitLine("cb(null, " + id + ");"); 198 }); 199 _this4._popBuffer(); 200 _this4._emitLine("return " + id + ";"); 201 _this4._emitLine('}'); 202 } else { 203 _this4._emit('null'); 204 } 205 }); 206 } 207 if (async) { 208 var res = this._tmpid(); 209 this._emitLine(', ' + this._makeCallback(res)); 210 this._emitLine(this.buffer + " += runtime.suppressValue(" + res + ", " + autoescape + " && env.opts.autoescape);"); 211 this._addScopeLevel(); 212 } else { 213 this._emit(')'); 214 this._emit(", " + autoescape + " && env.opts.autoescape);\n"); 215 } 216 }; 217 _proto.compileCallExtensionAsync = function compileCallExtensionAsync(node, frame) { 218 this.compileCallExtension(node, frame, true); 219 }; 220 _proto.compileNodeList = function compileNodeList(node, frame) { 221 this._compileChildren(node, frame); 222 }; 223 _proto.compileLiteral = function compileLiteral(node) { 224 if (typeof node.value === 'string') { 225 var val = node.value.replace(/\\/g, '\\\\'); 226 val = val.replace(/"/g, '\\"'); 227 val = val.replace(/\n/g, '\\n'); 228 val = val.replace(/\r/g, '\\r'); 229 val = val.replace(/\t/g, '\\t'); 230 val = val.replace(/\u2028/g, "\\u2028"); 231 this._emit("\"" + val + "\""); 232 } else if (node.value === null) { 233 this._emit('null'); 234 } else { 235 this._emit(node.value.toString()); 236 } 237 }; 238 _proto.compileSymbol = function compileSymbol(node, frame) { 239 var name = node.value; 240 var v = frame.lookup(name); 241 if (v) { 242 this._emit(v); 243 } else { 244 this._emit('runtime.contextOrFrameLookup(' + 'context, frame, "' + name + '")'); 245 } 246 }; 247 _proto.compileGroup = function compileGroup(node, frame) { 248 this._compileAggregate(node, frame, '(', ')'); 249 }; 250 _proto.compileArray = function compileArray(node, frame) { 251 this._compileAggregate(node, frame, '[', ']'); 252 }; 253 _proto.compileDict = function compileDict(node, frame) { 254 this._compileAggregate(node, frame, '{', '}'); 255 }; 256 _proto.compilePair = function compilePair(node, frame) { 257 var key = node.key; 258 var val = node.value; 259 if (key instanceof nodes.Symbol) { 260 key = new nodes.Literal(key.lineno, key.colno, key.value); 261 } else if (!(key instanceof nodes.Literal && typeof key.value === 'string')) { 262 this.fail('compilePair: Dict keys must be strings or names', key.lineno, key.colno); 263 } 264 this.compile(key, frame); 265 this._emit(': '); 266 this._compileExpression(val, frame); 267 }; 268 _proto.compileInlineIf = function compileInlineIf(node, frame) { 269 this._emit('('); 270 this.compile(node.cond, frame); 271 this._emit('?'); 272 this.compile(node.body, frame); 273 this._emit(':'); 274 if (node.else_ !== null) { 275 this.compile(node.else_, frame); 276 } else { 277 this._emit('""'); 278 } 279 this._emit(')'); 280 }; 281 _proto.compileIn = function compileIn(node, frame) { 282 this._emit('runtime.inOperator('); 283 this.compile(node.left, frame); 284 this._emit(','); 285 this.compile(node.right, frame); 286 this._emit(')'); 287 }; 288 _proto.compileIs = function compileIs(node, frame) { 289 // first, we need to try to get the name of the test function, if it's a 290 // callable (i.e., has args) and not a symbol. 291 var right = node.right.name ? node.right.name.value 292 // otherwise go with the symbol value 293 : node.right.value; 294 this._emit('env.getTest("' + right + '").call(context, '); 295 this.compile(node.left, frame); 296 // compile the arguments for the callable if they exist 297 if (node.right.args) { 298 this._emit(','); 299 this.compile(node.right.args, frame); 300 } 301 this._emit(') === true'); 302 }; 303 _proto._binOpEmitter = function _binOpEmitter(node, frame, str) { 304 this.compile(node.left, frame); 305 this._emit(str); 306 this.compile(node.right, frame); 307 } 308 309 // ensure concatenation instead of addition 310 // by adding empty string in between 311 ; 312 _proto.compileOr = function compileOr(node, frame) { 313 return this._binOpEmitter(node, frame, ' || '); 314 }; 315 _proto.compileAnd = function compileAnd(node, frame) { 316 return this._binOpEmitter(node, frame, ' && '); 317 }; 318 _proto.compileAdd = function compileAdd(node, frame) { 319 return this._binOpEmitter(node, frame, ' + '); 320 }; 321 _proto.compileConcat = function compileConcat(node, frame) { 322 return this._binOpEmitter(node, frame, ' + "" + '); 323 }; 324 _proto.compileSub = function compileSub(node, frame) { 325 return this._binOpEmitter(node, frame, ' - '); 326 }; 327 _proto.compileMul = function compileMul(node, frame) { 328 return this._binOpEmitter(node, frame, ' * '); 329 }; 330 _proto.compileDiv = function compileDiv(node, frame) { 331 return this._binOpEmitter(node, frame, ' / '); 332 }; 333 _proto.compileMod = function compileMod(node, frame) { 334 return this._binOpEmitter(node, frame, ' % '); 335 }; 336 _proto.compileNot = function compileNot(node, frame) { 337 this._emit('!'); 338 this.compile(node.target, frame); 339 }; 340 _proto.compileFloorDiv = function compileFloorDiv(node, frame) { 341 this._emit('Math.floor('); 342 this.compile(node.left, frame); 343 this._emit(' / '); 344 this.compile(node.right, frame); 345 this._emit(')'); 346 }; 347 _proto.compilePow = function compilePow(node, frame) { 348 this._emit('Math.pow('); 349 this.compile(node.left, frame); 350 this._emit(', '); 351 this.compile(node.right, frame); 352 this._emit(')'); 353 }; 354 _proto.compileNeg = function compileNeg(node, frame) { 355 this._emit('-'); 356 this.compile(node.target, frame); 357 }; 358 _proto.compilePos = function compilePos(node, frame) { 359 this._emit('+'); 360 this.compile(node.target, frame); 361 }; 362 _proto.compileCompare = function compileCompare(node, frame) { 363 var _this5 = this; 364 this.compile(node.expr, frame); 365 node.ops.forEach(function (op) { 366 _this5._emit(" " + compareOps[op.type] + " "); 367 _this5.compile(op.expr, frame); 368 }); 369 }; 370 _proto.compileLookupVal = function compileLookupVal(node, frame) { 371 this._emit('runtime.memberLookup(('); 372 this._compileExpression(node.target, frame); 373 this._emit('),'); 374 this._compileExpression(node.val, frame); 375 this._emit(')'); 376 }; 377 _proto._getNodeName = function _getNodeName(node) { 378 switch (node.typename) { 379 case 'Symbol': 380 return node.value; 381 case 'FunCall': 382 return 'the return value of (' + this._getNodeName(node.name) + ')'; 383 case 'LookupVal': 384 return this._getNodeName(node.target) + '["' + this._getNodeName(node.val) + '"]'; 385 case 'Literal': 386 return node.value.toString(); 387 default: 388 return '--expression--'; 389 } 390 }; 391 _proto.compileFunCall = function compileFunCall(node, frame) { 392 // Keep track of line/col info at runtime by settings 393 // variables within an expression. An expression in javascript 394 // like (x, y, z) returns the last value, and x and y can be 395 // anything 396 this._emit('(lineno = ' + node.lineno + ', colno = ' + node.colno + ', '); 397 this._emit('runtime.callWrap('); 398 // Compile it as normal. 399 this._compileExpression(node.name, frame); 400 401 // Output the name of what we're calling so we can get friendly errors 402 // if the lookup fails. 403 this._emit(', "' + this._getNodeName(node.name).replace(/"/g, '\\"') + '", context, '); 404 this._compileAggregate(node.args, frame, '[', '])'); 405 this._emit(')'); 406 }; 407 _proto.compileFilter = function compileFilter(node, frame) { 408 var name = node.name; 409 this.assertType(name, nodes.Symbol); 410 this._emit('env.getFilter("' + name.value + '").call(context, '); 411 this._compileAggregate(node.args, frame); 412 this._emit(')'); 413 }; 414 _proto.compileFilterAsync = function compileFilterAsync(node, frame) { 415 var name = node.name; 416 var symbol = node.symbol.value; 417 this.assertType(name, nodes.Symbol); 418 frame.set(symbol, symbol); 419 this._emit('env.getFilter("' + name.value + '").call(context, '); 420 this._compileAggregate(node.args, frame); 421 this._emitLine(', ' + this._makeCallback(symbol)); 422 this._addScopeLevel(); 423 }; 424 _proto.compileKeywordArgs = function compileKeywordArgs(node, frame) { 425 this._emit('runtime.makeKeywordArgs('); 426 this.compileDict(node, frame); 427 this._emit(')'); 428 }; 429 _proto.compileSet = function compileSet(node, frame) { 430 var _this6 = this; 431 var ids = []; 432 433 // Lookup the variable names for each identifier and create 434 // new ones if necessary 435 node.targets.forEach(function (target) { 436 var name = target.value; 437 var id = frame.lookup(name); 438 if (id === null || id === undefined) { 439 id = _this6._tmpid(); 440 441 // Note: This relies on js allowing scope across 442 // blocks, in case this is created inside an `if` 443 _this6._emitLine('var ' + id + ';'); 444 } 445 ids.push(id); 446 }); 447 if (node.value) { 448 this._emit(ids.join(' = ') + ' = '); 449 this._compileExpression(node.value, frame); 450 this._emitLine(';'); 451 } else { 452 this._emit(ids.join(' = ') + ' = '); 453 this.compile(node.body, frame); 454 this._emitLine(';'); 455 } 456 node.targets.forEach(function (target, i) { 457 var id = ids[i]; 458 var name = target.value; 459 460 // We are running this for every var, but it's very 461 // uncommon to assign to multiple vars anyway 462 _this6._emitLine("frame.set(\"" + name + "\", " + id + ", true);"); 463 _this6._emitLine('if(frame.topLevel) {'); 464 _this6._emitLine("context.setVariable(\"" + name + "\", " + id + ");"); 465 _this6._emitLine('}'); 466 if (name.charAt(0) !== '_') { 467 _this6._emitLine('if(frame.topLevel) {'); 468 _this6._emitLine("context.addExport(\"" + name + "\", " + id + ");"); 469 _this6._emitLine('}'); 470 } 471 }); 472 }; 473 _proto.compileSwitch = function compileSwitch(node, frame) { 474 var _this7 = this; 475 this._emit('switch ('); 476 this.compile(node.expr, frame); 477 this._emit(') {'); 478 node.cases.forEach(function (c, i) { 479 _this7._emit('case '); 480 _this7.compile(c.cond, frame); 481 _this7._emit(': '); 482 _this7.compile(c.body, frame); 483 // preserve fall-throughs 484 if (c.body.children.length) { 485 _this7._emitLine('break;'); 486 } 487 }); 488 if (node.default) { 489 this._emit('default:'); 490 this.compile(node.default, frame); 491 } 492 this._emit('}'); 493 }; 494 _proto.compileIf = function compileIf(node, frame, async) { 495 var _this8 = this; 496 this._emit('if('); 497 this._compileExpression(node.cond, frame); 498 this._emitLine(') {'); 499 this._withScopedSyntax(function () { 500 _this8.compile(node.body, frame); 501 if (async) { 502 _this8._emit('cb()'); 503 } 504 }); 505 if (node.else_) { 506 this._emitLine('}\nelse {'); 507 this._withScopedSyntax(function () { 508 _this8.compile(node.else_, frame); 509 if (async) { 510 _this8._emit('cb()'); 511 } 512 }); 513 } else if (async) { 514 this._emitLine('}\nelse {'); 515 this._emit('cb()'); 516 } 517 this._emitLine('}'); 518 }; 519 _proto.compileIfAsync = function compileIfAsync(node, frame) { 520 this._emit('(function(cb) {'); 521 this.compileIf(node, frame, true); 522 this._emit('})(' + this._makeCallback()); 523 this._addScopeLevel(); 524 }; 525 _proto._emitLoopBindings = function _emitLoopBindings(node, arr, i, len) { 526 var _this9 = this; 527 var bindings = [{ 528 name: 'index', 529 val: i + " + 1" 530 }, { 531 name: 'index0', 532 val: i 533 }, { 534 name: 'revindex', 535 val: len + " - " + i 536 }, { 537 name: 'revindex0', 538 val: len + " - " + i + " - 1" 539 }, { 540 name: 'first', 541 val: i + " === 0" 542 }, { 543 name: 'last', 544 val: i + " === " + len + " - 1" 545 }, { 546 name: 'length', 547 val: len 548 }]; 549 bindings.forEach(function (b) { 550 _this9._emitLine("frame.set(\"loop." + b.name + "\", " + b.val + ");"); 551 }); 552 }; 553 _proto.compileFor = function compileFor(node, frame) { 554 var _this10 = this; 555 // Some of this code is ugly, but it keeps the generated code 556 // as fast as possible. ForAsync also shares some of this, but 557 // not much. 558 559 var i = this._tmpid(); 560 var len = this._tmpid(); 561 var arr = this._tmpid(); 562 frame = frame.push(); 563 this._emitLine('frame = frame.push();'); 564 this._emit("var " + arr + " = "); 565 this._compileExpression(node.arr, frame); 566 this._emitLine(';'); 567 this._emit("if(" + arr + ") {"); 568 this._emitLine(arr + ' = runtime.fromIterator(' + arr + ');'); 569 570 // If multiple names are passed, we need to bind them 571 // appropriately 572 if (node.name instanceof nodes.Array) { 573 this._emitLine("var " + i + ";"); 574 575 // The object could be an arroy or object. Note that the 576 // body of the loop is duplicated for each condition, but 577 // we are optimizing for speed over size. 578 this._emitLine("if(runtime.isArray(" + arr + ")) {"); 579 this._emitLine("var " + len + " = " + arr + ".length;"); 580 this._emitLine("for(" + i + "=0; " + i + " < " + arr + ".length; " + i + "++) {"); 581 582 // Bind each declared var 583 node.name.children.forEach(function (child, u) { 584 var tid = _this10._tmpid(); 585 _this10._emitLine("var " + tid + " = " + arr + "[" + i + "][" + u + "];"); 586 _this10._emitLine("frame.set(\"" + child + "\", " + arr + "[" + i + "][" + u + "]);"); 587 frame.set(node.name.children[u].value, tid); 588 }); 589 this._emitLoopBindings(node, arr, i, len); 590 this._withScopedSyntax(function () { 591 _this10.compile(node.body, frame); 592 }); 593 this._emitLine('}'); 594 this._emitLine('} else {'); 595 // Iterate over the key/values of an object 596 var _node$name$children = node.name.children, 597 key = _node$name$children[0], 598 val = _node$name$children[1]; 599 var k = this._tmpid(); 600 var v = this._tmpid(); 601 frame.set(key.value, k); 602 frame.set(val.value, v); 603 this._emitLine(i + " = -1;"); 604 this._emitLine("var " + len + " = runtime.keys(" + arr + ").length;"); 605 this._emitLine("for(var " + k + " in " + arr + ") {"); 606 this._emitLine(i + "++;"); 607 this._emitLine("var " + v + " = " + arr + "[" + k + "];"); 608 this._emitLine("frame.set(\"" + key.value + "\", " + k + ");"); 609 this._emitLine("frame.set(\"" + val.value + "\", " + v + ");"); 610 this._emitLoopBindings(node, arr, i, len); 611 this._withScopedSyntax(function () { 612 _this10.compile(node.body, frame); 613 }); 614 this._emitLine('}'); 615 this._emitLine('}'); 616 } else { 617 // Generate a typical array iteration 618 var _v = this._tmpid(); 619 frame.set(node.name.value, _v); 620 this._emitLine("var " + len + " = " + arr + ".length;"); 621 this._emitLine("for(var " + i + "=0; " + i + " < " + arr + ".length; " + i + "++) {"); 622 this._emitLine("var " + _v + " = " + arr + "[" + i + "];"); 623 this._emitLine("frame.set(\"" + node.name.value + "\", " + _v + ");"); 624 this._emitLoopBindings(node, arr, i, len); 625 this._withScopedSyntax(function () { 626 _this10.compile(node.body, frame); 627 }); 628 this._emitLine('}'); 629 } 630 this._emitLine('}'); 631 if (node.else_) { 632 this._emitLine('if (!' + len + ') {'); 633 this.compile(node.else_, frame); 634 this._emitLine('}'); 635 } 636 this._emitLine('frame = frame.pop();'); 637 }; 638 _proto._compileAsyncLoop = function _compileAsyncLoop(node, frame, parallel) { 639 var _this11 = this; 640 // This shares some code with the For tag, but not enough to 641 // worry about. This iterates across an object asynchronously, 642 // but not in parallel. 643 644 var i = this._tmpid(); 645 var len = this._tmpid(); 646 var arr = this._tmpid(); 647 var asyncMethod = parallel ? 'asyncAll' : 'asyncEach'; 648 frame = frame.push(); 649 this._emitLine('frame = frame.push();'); 650 this._emit('var ' + arr + ' = runtime.fromIterator('); 651 this._compileExpression(node.arr, frame); 652 this._emitLine(');'); 653 if (node.name instanceof nodes.Array) { 654 var arrayLen = node.name.children.length; 655 this._emit("runtime." + asyncMethod + "(" + arr + ", " + arrayLen + ", function("); 656 node.name.children.forEach(function (name) { 657 _this11._emit(name.value + ","); 658 }); 659 this._emit(i + ',' + len + ',next) {'); 660 node.name.children.forEach(function (name) { 661 var id = name.value; 662 frame.set(id, id); 663 _this11._emitLine("frame.set(\"" + id + "\", " + id + ");"); 664 }); 665 } else { 666 var id = node.name.value; 667 this._emitLine("runtime." + asyncMethod + "(" + arr + ", 1, function(" + id + ", " + i + ", " + len + ",next) {"); 668 this._emitLine('frame.set("' + id + '", ' + id + ');'); 669 frame.set(id, id); 670 } 671 this._emitLoopBindings(node, arr, i, len); 672 this._withScopedSyntax(function () { 673 var buf; 674 if (parallel) { 675 buf = _this11._pushBuffer(); 676 } 677 _this11.compile(node.body, frame); 678 _this11._emitLine('next(' + i + (buf ? ',' + buf : '') + ');'); 679 if (parallel) { 680 _this11._popBuffer(); 681 } 682 }); 683 var output = this._tmpid(); 684 this._emitLine('}, ' + this._makeCallback(output)); 685 this._addScopeLevel(); 686 if (parallel) { 687 this._emitLine(this.buffer + ' += ' + output + ';'); 688 } 689 if (node.else_) { 690 this._emitLine('if (!' + arr + '.length) {'); 691 this.compile(node.else_, frame); 692 this._emitLine('}'); 693 } 694 this._emitLine('frame = frame.pop();'); 695 }; 696 _proto.compileAsyncEach = function compileAsyncEach(node, frame) { 697 this._compileAsyncLoop(node, frame); 698 }; 699 _proto.compileAsyncAll = function compileAsyncAll(node, frame) { 700 this._compileAsyncLoop(node, frame, true); 701 }; 702 _proto._compileMacro = function _compileMacro(node, frame) { 703 var _this12 = this; 704 var args = []; 705 var kwargs = null; 706 var funcId = 'macro_' + this._tmpid(); 707 var keepFrame = frame !== undefined; 708 709 // Type check the definition of the args 710 node.args.children.forEach(function (arg, i) { 711 if (i === node.args.children.length - 1 && arg instanceof nodes.Dict) { 712 kwargs = arg; 713 } else { 714 _this12.assertType(arg, nodes.Symbol); 715 args.push(arg); 716 } 717 }); 718 var realNames = [].concat(args.map(function (n) { 719 return "l_" + n.value; 720 }), ['kwargs']); 721 722 // Quoted argument names 723 var argNames = args.map(function (n) { 724 return "\"" + n.value + "\""; 725 }); 726 var kwargNames = (kwargs && kwargs.children || []).map(function (n) { 727 return "\"" + n.key.value + "\""; 728 }); 729 730 // We pass a function to makeMacro which destructures the 731 // arguments so support setting positional args with keywords 732 // args and passing keyword args as positional args 733 // (essentially default values). See runtime.js. 734 var currFrame; 735 if (keepFrame) { 736 currFrame = frame.push(true); 737 } else { 738 currFrame = new Frame(); 739 } 740 this._emitLines("var " + funcId + " = runtime.makeMacro(", "[" + argNames.join(', ') + "], ", "[" + kwargNames.join(', ') + "], ", "function (" + realNames.join(', ') + ") {", 'var callerFrame = frame;', 'frame = ' + (keepFrame ? 'frame.push(true);' : 'new runtime.Frame();'), 'kwargs = kwargs || {};', 'if (Object.prototype.hasOwnProperty.call(kwargs, "caller")) {', 'frame.set("caller", kwargs.caller); }'); 741 742 // Expose the arguments to the template. Don't need to use 743 // random names because the function 744 // will create a new run-time scope for us 745 args.forEach(function (arg) { 746 _this12._emitLine("frame.set(\"" + arg.value + "\", l_" + arg.value + ");"); 747 currFrame.set(arg.value, "l_" + arg.value); 748 }); 749 750 // Expose the keyword arguments 751 if (kwargs) { 752 kwargs.children.forEach(function (pair) { 753 var name = pair.key.value; 754 _this12._emit("frame.set(\"" + name + "\", "); 755 _this12._emit("Object.prototype.hasOwnProperty.call(kwargs, \"" + name + "\")"); 756 _this12._emit(" ? kwargs[\"" + name + "\"] : "); 757 _this12._compileExpression(pair.value, currFrame); 758 _this12._emit(');'); 759 }); 760 } 761 var bufferId = this._pushBuffer(); 762 this._withScopedSyntax(function () { 763 _this12.compile(node.body, currFrame); 764 }); 765 this._emitLine('frame = ' + (keepFrame ? 'frame.pop();' : 'callerFrame;')); 766 this._emitLine("return new runtime.SafeString(" + bufferId + ");"); 767 this._emitLine('});'); 768 this._popBuffer(); 769 return funcId; 770 }; 771 _proto.compileMacro = function compileMacro(node, frame) { 772 var funcId = this._compileMacro(node); 773 774 // Expose the macro to the templates 775 var name = node.name.value; 776 frame.set(name, funcId); 777 if (frame.parent) { 778 this._emitLine("frame.set(\"" + name + "\", " + funcId + ");"); 779 } else { 780 if (node.name.value.charAt(0) !== '_') { 781 this._emitLine("context.addExport(\"" + name + "\");"); 782 } 783 this._emitLine("context.setVariable(\"" + name + "\", " + funcId + ");"); 784 } 785 }; 786 _proto.compileCaller = function compileCaller(node, frame) { 787 // basically an anonymous "macro expression" 788 this._emit('(function (){'); 789 var funcId = this._compileMacro(node, frame); 790 this._emit("return " + funcId + ";})()"); 791 }; 792 _proto._compileGetTemplate = function _compileGetTemplate(node, frame, eagerCompile, ignoreMissing) { 793 var parentTemplateId = this._tmpid(); 794 var parentName = this._templateName(); 795 var cb = this._makeCallback(parentTemplateId); 796 var eagerCompileArg = eagerCompile ? 'true' : 'false'; 797 var ignoreMissingArg = ignoreMissing ? 'true' : 'false'; 798 this._emit('env.getTemplate('); 799 this._compileExpression(node.template, frame); 800 this._emitLine(", " + eagerCompileArg + ", " + parentName + ", " + ignoreMissingArg + ", " + cb); 801 return parentTemplateId; 802 }; 803 _proto.compileImport = function compileImport(node, frame) { 804 var target = node.target.value; 805 var id = this._compileGetTemplate(node, frame, false, false); 806 this._addScopeLevel(); 807 this._emitLine(id + '.getExported(' + (node.withContext ? 'context.getVariables(), frame, ' : '') + this._makeCallback(id)); 808 this._addScopeLevel(); 809 frame.set(target, id); 810 if (frame.parent) { 811 this._emitLine("frame.set(\"" + target + "\", " + id + ");"); 812 } else { 813 this._emitLine("context.setVariable(\"" + target + "\", " + id + ");"); 814 } 815 }; 816 _proto.compileFromImport = function compileFromImport(node, frame) { 817 var _this13 = this; 818 var importedId = this._compileGetTemplate(node, frame, false, false); 819 this._addScopeLevel(); 820 this._emitLine(importedId + '.getExported(' + (node.withContext ? 'context.getVariables(), frame, ' : '') + this._makeCallback(importedId)); 821 this._addScopeLevel(); 822 node.names.children.forEach(function (nameNode) { 823 var name; 824 var alias; 825 var id = _this13._tmpid(); 826 if (nameNode instanceof nodes.Pair) { 827 name = nameNode.key.value; 828 alias = nameNode.value.value; 829 } else { 830 name = nameNode.value; 831 alias = name; 832 } 833 _this13._emitLine("if(Object.prototype.hasOwnProperty.call(" + importedId + ", \"" + name + "\")) {"); 834 _this13._emitLine("var " + id + " = " + importedId + "." + name + ";"); 835 _this13._emitLine('} else {'); 836 _this13._emitLine("cb(new Error(\"cannot import '" + name + "'\")); return;"); 837 _this13._emitLine('}'); 838 frame.set(alias, id); 839 if (frame.parent) { 840 _this13._emitLine("frame.set(\"" + alias + "\", " + id + ");"); 841 } else { 842 _this13._emitLine("context.setVariable(\"" + alias + "\", " + id + ");"); 843 } 844 }); 845 }; 846 _proto.compileBlock = function compileBlock(node) { 847 var id = this._tmpid(); 848 849 // If we are executing outside a block (creating a top-level 850 // block), we really don't want to execute its code because it 851 // will execute twice: once when the child template runs and 852 // again when the parent template runs. Note that blocks 853 // within blocks will *always* execute immediately *and* 854 // wherever else they are invoked (like used in a parent 855 // template). This may have behavioral differences from jinja 856 // because blocks can have side effects, but it seems like a 857 // waste of performance to always execute huge top-level 858 // blocks twice 859 if (!this.inBlock) { 860 this._emit('(parentTemplate ? function(e, c, f, r, cb) { cb(""); } : '); 861 } 862 this._emit("context.getBlock(\"" + node.name.value + "\")"); 863 if (!this.inBlock) { 864 this._emit(')'); 865 } 866 this._emitLine('(env, context, frame, runtime, ' + this._makeCallback(id)); 867 this._emitLine(this.buffer + " += " + id + ";"); 868 this._addScopeLevel(); 869 }; 870 _proto.compileSuper = function compileSuper(node, frame) { 871 var name = node.blockName.value; 872 var id = node.symbol.value; 873 var cb = this._makeCallback(id); 874 this._emitLine("context.getSuper(env, \"" + name + "\", b_" + name + ", frame, runtime, " + cb); 875 this._emitLine(id + " = runtime.markSafe(" + id + ");"); 876 this._addScopeLevel(); 877 frame.set(id, id); 878 }; 879 _proto.compileExtends = function compileExtends(node, frame) { 880 var k = this._tmpid(); 881 var parentTemplateId = this._compileGetTemplate(node, frame, true, false); 882 883 // extends is a dynamic tag and can occur within a block like 884 // `if`, so if this happens we need to capture the parent 885 // template in the top-level scope 886 this._emitLine("parentTemplate = " + parentTemplateId); 887 this._emitLine("for(var " + k + " in parentTemplate.blocks) {"); 888 this._emitLine("context.addBlock(" + k + ", parentTemplate.blocks[" + k + "]);"); 889 this._emitLine('}'); 890 this._addScopeLevel(); 891 }; 892 _proto.compileInclude = function compileInclude(node, frame) { 893 this._emitLine('var tasks = [];'); 894 this._emitLine('tasks.push('); 895 this._emitLine('function(callback) {'); 896 var id = this._compileGetTemplate(node, frame, false, node.ignoreMissing); 897 this._emitLine("callback(null," + id + ");});"); 898 this._emitLine('});'); 899 var id2 = this._tmpid(); 900 this._emitLine('tasks.push('); 901 this._emitLine('function(template, callback){'); 902 this._emitLine('template.render(context.getVariables(), frame, ' + this._makeCallback(id2)); 903 this._emitLine('callback(null,' + id2 + ');});'); 904 this._emitLine('});'); 905 this._emitLine('tasks.push('); 906 this._emitLine('function(result, callback){'); 907 this._emitLine(this.buffer + " += result;"); 908 this._emitLine('callback(null);'); 909 this._emitLine('});'); 910 this._emitLine('env.waterfall(tasks, function(){'); 911 this._addScopeLevel(); 912 }; 913 _proto.compileTemplateData = function compileTemplateData(node, frame) { 914 this.compileLiteral(node, frame); 915 }; 916 _proto.compileCapture = function compileCapture(node, frame) { 917 var _this14 = this; 918 // we need to temporarily override the current buffer id as 'output' 919 // so the set block writes to the capture output instead of the buffer 920 var buffer = this.buffer; 921 this.buffer = 'output'; 922 this._emitLine('(function() {'); 923 this._emitLine('var output = "";'); 924 this._withScopedSyntax(function () { 925 _this14.compile(node.body, frame); 926 }); 927 this._emitLine('return output;'); 928 this._emitLine('})()'); 929 // and of course, revert back to the old buffer id 930 this.buffer = buffer; 931 }; 932 _proto.compileOutput = function compileOutput(node, frame) { 933 var _this15 = this; 934 var children = node.children; 935 children.forEach(function (child) { 936 // TemplateData is a special case because it is never 937 // autoescaped, so simply output it for optimization 938 if (child instanceof nodes.TemplateData) { 939 if (child.value) { 940 _this15._emit(_this15.buffer + " += "); 941 _this15.compileLiteral(child, frame); 942 _this15._emitLine(';'); 943 } 944 } else { 945 _this15._emit(_this15.buffer + " += runtime.suppressValue("); 946 if (_this15.throwOnUndefined) { 947 _this15._emit('runtime.ensureDefined('); 948 } 949 _this15.compile(child, frame); 950 if (_this15.throwOnUndefined) { 951 _this15._emit("," + node.lineno + "," + node.colno + ")"); 952 } 953 _this15._emit(', env.opts.autoescape);\n'); 954 } 955 }); 956 }; 957 _proto.compileRoot = function compileRoot(node, frame) { 958 var _this16 = this; 959 if (frame) { 960 this.fail('compileRoot: root node can\'t have frame'); 961 } 962 frame = new Frame(); 963 this._emitFuncBegin(node, 'root'); 964 this._emitLine('var parentTemplate = null;'); 965 this._compileChildren(node, frame); 966 this._emitLine('if(parentTemplate) {'); 967 this._emitLine('parentTemplate.rootRenderFunc(env, context, frame, runtime, cb);'); 968 this._emitLine('} else {'); 969 this._emitLine("cb(null, " + this.buffer + ");"); 970 this._emitLine('}'); 971 this._emitFuncEnd(true); 972 this.inBlock = true; 973 var blockNames = []; 974 var blocks = node.findAll(nodes.Block); 975 blocks.forEach(function (block, i) { 976 var name = block.name.value; 977 if (blockNames.indexOf(name) !== -1) { 978 throw new Error("Block \"" + name + "\" defined more than once."); 979 } 980 blockNames.push(name); 981 _this16._emitFuncBegin(block, "b_" + name); 982 var tmpFrame = new Frame(); 983 _this16._emitLine('var frame = frame.push(true);'); 984 _this16.compile(block.body, tmpFrame); 985 _this16._emitFuncEnd(); 986 }); 987 this._emitLine('return {'); 988 blocks.forEach(function (block, i) { 989 var blockName = "b_" + block.name.value; 990 _this16._emitLine(blockName + ": " + blockName + ","); 991 }); 992 this._emitLine('root: root\n};'); 993 }; 994 _proto.compile = function compile(node, frame) { 995 var _compile = this['compile' + node.typename]; 996 if (_compile) { 997 _compile.call(this, node, frame); 998 } else { 999 this.fail("compile: Cannot compile node: " + node.typename, node.lineno, node.colno); 1000 } 1001 }; 1002 _proto.getCode = function getCode() { 1003 return this.codebuf.join(''); 1004 }; 1005 return Compiler; 1006}(Obj); 1007module.exports = { 1008 compile: function compile(src, asyncFilters, extensions, name, opts) { 1009 if (opts === void 0) { 1010 opts = {}; 1011 } 1012 var c = new Compiler(name, opts.throwOnUndefined); 1013 1014 // Run the extension preprocessors against the source. 1015 var preprocessors = (extensions || []).map(function (ext) { 1016 return ext.preprocess; 1017 }).filter(function (f) { 1018 return !!f; 1019 }); 1020 var processedSrc = preprocessors.reduce(function (s, processor) { 1021 return processor(s); 1022 }, src); 1023 c.compile(transformer.transform(parser.parse(processedSrc, extensions, opts), asyncFilters, name)); 1024 return c.getCode(); 1025 }, 1026 Compiler: Compiler 1027};