UNPKG

snakeskin

Version:

Компилятор блочных шаблонов c поддержкой наследования.

515 lines (412 loc) 12.4 kB
/** * Если true, то значит идёт декларация прототипа * @type {boolean} */ DirObj.prototype.protoStart = false; /** * Индекс анонимных прототипов * @type {number} */ DirObj.prototype.anonI = 0; /** * Вернуть строку декларации заданных аргументов прототипа * * @param {!Array.<!Array>} protoArgs - массив аргументов прототипа [название, значение по умолчанию] * @param {!Array} args - массив заданных аргументов * @return {string} */ DirObj.prototype.returnProtoArgs = function (protoArgs, args) { var tmp = []; var str = '', length = protoArgs.length; for (let i = -1; ++i < length;) { let val = this.prepareOutput(args[i] || 'void 0', true); let arg = protoArgs[i][0], def = protoArgs[i][1]; if (def !== void 0) { def = this.prepareOutput(def, true); } arg = arg.replace(scopeModRgxp, ''); if (protoArgs['__SNAKESKIN_TMP__needArgs'] && i === length - 1) { if (length - 1 < args.length) { tmp = tmp.concat(args.slice(length - 1, args.length)); } str += /* cbws */` var ${arg} = [${tmp.join()}]; ${arg}.callee = __CALLEE__; `; } else { tmp.push(arg); str += /* cbws */` var ${arg} = ${def !== void 0 ? val ? `${val} != null ? ${val} : ${this.prepareOutput(def, true)}` : def : val || 'void 0' }; `; } } return str; }; var anonRgxp = /^__ANONYMOUS__/, callProtoRgxp = /=>/; Snakeskin.addDirective( 'proto', { sys: true, block: true, notEmpty: true, group: [ 'template', 'define', 'inherit', 'blockInherit' ] }, function (command, commandLength) { var name = this.getFnName(command, true), tplName = this.tplName; if (!name) { if (!tplName || !callProtoRgxp.test(command)) { return this.error(`invalid "${this.name}" name`); } name = `__ANONYMOUS__${this.anonI}`; this.anonI++; let tmpLength = command.length; command = name + this.source.substring(this.i - tmpLength, this.i); commandLength += name.length; this.source = this.source.substring(0, this.i - tmpLength) + name + this.source.substring(this.i - tmpLength); this.i += name.length; } var parts = name.split('->'); if (parts[1]) { name = parts[1].trim(); if (!tplName) { if (this.structure.parent) { this.error(`directive "outer proto" can be used only within the global space`); return; } try { tplName = this.tplName = this.prepareNameDecl(parts[0]); } catch (err) { return this.error(err.message); } if (tplName in extMap) { delete extMap[tplName]; clearScopeCache(tplName); } let desc = this.preDefs[tplName] = this.preDefs[tplName] || { text: '' }; desc.startLine = this.info['line']; desc.i = this.i + 1; this.outerLink = name; } } else if (!this.outerLink && !this.tplName) { return this.error(`directive "${this.name}" can be used only within a ${groupsList['template'].join(', ')}`); } if (!name || !tplName || callBlockNameRgxp.test(name)) { return this.error(`invalid "${this.name}" declaration`); } var scope = scopeCache[this.name][tplName] = scopeCache[this.name][tplName] || {}; var parentScope, parentTplName = extMap[tplName]; if (parentTplName) { parentScope = scopeCache[this.name][parentTplName] = scopeCache[this.name][parentTplName] || {}; } let current = scope[name]; if (!scope[name]) { current = scope[name] = { id: this.module.id, children: {} }; } if (!this.outerLink && !current.root) { let parent = parentScope && parentScope[name]; current.parent = parent; current.overridden = Boolean(parentTplName && this.parentTplName); current.root = parent ? parent.root : scope[name]; if (parent) { parent.children[tplName] = scope[name]; } } var start = this.i - this.startTemplateI; this.startDir(null, { name: name, startTemplateI: this.i + 1, from: this.i - this.getDiff(commandLength), fromBody: start + 1, line: this.info['line'], sysSpace: this.sysSpace, strongSpace: this.strongSpace, chainSpace: this.chainSpace, space: this.space }); if (this.isAdvTest()) { let dir = String(this.name); if (protoCache[tplName][name] && !anonRgxp.test(name)) { return this.error(`proto "${name}" is already defined`); } let output = command.split('=>')[1], ouptupCache = this.getBlockOutput(dir); if (output != null) { ouptupCache[name] = output; } let args = this.prepareArgs( command, dir, null, this.parentTplName, name ); protoCache[tplName][name] = { length: commandLength, from: start - this.getDiff(commandLength), args: args.list, scope: args.scope, calls: {}, needPrfx: this.needPrfx, output: output }; } if (!this.parentTplName) { this.protoStart = true; } }, function (command, commandLength) { var tplName = this.tplName, struct = this.structure; var vars = struct.vars, params = struct.params, name = params.name, diff = this.getDiff(commandLength); var s = (this.needPrfx ? ADV_LEFT_BLOCK : '') + LEFT_BLOCK, e = RIGHT_BLOCK; var space = this.space, nl = this.lineSeparator; this.sysSpace = params.sysSpace; this.strongSpace = params.strongSpace; this.chainSpace = params.chainSpace; this.space = params.space; if (this.outerLink === name) { let obj = this.preDefs[tplName], i = Number(obj.i); obj.text += /* cbws */` ${nl}${this.source.substring(params.from, i)} ${s}__cutLine__${e} ${s}__switchLine__ ${obj.startLine}${e} ${this.source.substring(i, this.i - diff)} ${s}__end__${e} ${nl}${this.source.substring(this.i - diff, this.i + 1)} ${s}__cutLine__${e} `; this.outerLink = null; this.tplName = null; if (!this.hasParentBlock('proto')) { this.protoStart = false; } } else if (!this.outerLink) { let proto = protoCache[tplName][name], start = this.i - this.startTemplateI; if (this.isAdvTest()) { let scope = proto.scope; proto.to = start + 1; proto.content = this.source .substring(this.startTemplateI) .substring(params.fromBody, start - diff); fromProtoCache[tplName] = this.i - this.startTemplateI + 1; // Рекурсивно анализируем прототипы блоков proto.body = Snakeskin.compile( ( `${s}template ${tplName}()${e}` + (scope ? `${s}with ${scope}${e}` : '') + `${s}var __I_PROTO__ = 1${e}` + `${s}__protoWhile__ __I_PROTO__--${e}` + `${s}__setLine__ ${params.line}${e}` + this.source.substring(params.startTemplateI, this.i - diff) + `${s}__end__${e}` + (scope ? `${s}end${e}` : '') + `${s}end${e}` ).trim(), { inlineIterators: this.inlineIterators, renderMode: this.renderMode, replaceUndef: this.replaceUndef, escapeOutput: this.escapeOutput, doctype: this.doctype, autoReplace: this.autoReplace, macros: this.macros, language: this.language, localization: this.localization, lineSeparator: this.lineSeparator, tolerateWhitespace: this.tolerateWhitespace }, null, { parent: this, lines: this.lines.slice(), needPrfx: this.needPrfx, scope: this.scope.slice(), vars: struct.vars, consts: this.consts, proto: { name: name, recursive: params.recursive, parentTplName: this.parentTplName, pos: this.res.length, ctx: this, sysSpace: this.sysSpace, strongSpace: this.strongSpace, chainSpace: this.chainSpace, space: this.space } } ); } // Применение обратных прототипов let back = this.backTable[name]; if (back && !back.protoStart) { let args = proto.args, fin = true; for (let i = -1; ++i < back.length;) { let el = back[i]; if (this.canWrite) { if (!el.outer) { this.res = this.res.substring(0, el.pos) + this.returnProtoArgs(args, el.args) + protoCache[tplName][name].body + this.res.substring(el.pos); } else { struct.vars = el.vars; el.argsStr = this.returnProtoArgs(args, el.args); struct.vars = vars; fin = false; } } } if (fin) { delete this.backTable[name]; this.backTableI--; } } if (this.protoStart && !this.outerLink && !this.hasParentBlock('proto')) { this.protoStart = false; } if (proto) { let ouptupCache = this.getBlockOutput('proto'); if (ouptupCache[name] != null && this.isSimpleOutput()) { struct.vars = struct.parent.vars; this.save( this.returnProtoArgs( proto.args, this.getFnArgs(`(${ouptupCache[name]})`) ) + proto.body ); struct.vars = vars; } this.text = !space; } } } ); /** * Таблица обратных вызовов прототипа */ DirObj.prototype.backTable = { init() { return {}; } }; /** * Количество обратных вызовов прототипа * (когда apply до декларации вызываемого прототипа) * @type {number} */ DirObj.prototype.backTableI = 0; Snakeskin.addDirective( 'apply', { placement: 'template', notEmpty: true, text: true, replacers: { '+=': (cmd) => cmd.replace('+=', 'apply ') } }, function (command) { this.startInlineDir(); if (this.isSimpleOutput()) { let tplName = this.tplName; let name = this.getFnName(command), args = this.getFnArgs(command); if (name === '&' && this.proto) { name = this.proto.name; } let cache = protoCache[tplName]; let proto = cache[name], argsStr = ''; if (proto) { argsStr = this.returnProtoArgs(proto.args, args); } let selfProto = this.proto; if (selfProto && proto && proto.calls[selfProto.name]) { return this.error(`invalid form of recursion for the proto (apply "${name}" inside "${selfProto.name}")`); } // Рекурсивный вызов прототипа if (selfProto && selfProto.name === name) { this.save(argsStr + this.prepareOutput('__I_PROTO__++', true) + ';'); // Попытка применить не объявленный прототип // (запоминаем место вызова, чтобы вернуться к нему, // когда прототип будет объявлен) } else if (!proto || !proto.body) { let back = this.backTable; if (!back[name]) { back[name] = []; back[name].protoStart = this.protoStart; this.backTableI++; } let rand = Math.random().toString(), key = `${tplName.replace(/([.\[])/g, '\\$1')}_${name}_${rand.replace('.', '\\.')}`; back[name].push({ proto: selfProto ? cache[selfProto.name] : null, pos: this.res.length, label: new RegExp(`\\/\\* __APPLY__${key} \\*\\/`), args: args, recursive: Boolean(proto) }); this.save(`/* __APPLY__${tplName}_${name}_${rand} */`); if (selfProto && !proto) { cache[selfProto.name].calls[name] = true; } } else { this.save(argsStr + proto.body); } } } ); Snakeskin.addDirective( '__protoWhile__', { }, function (command) { this.startDir(); if (this.isSimpleOutput()) { let i = this.prepareOutput('__I_PROTO__', true); protoCache[this.tplName][this.proto.name].i = i; this.save(`${i}:while (${this.prepareOutput(command, true)}) {`); } }, function () { if (this.isSimpleOutput()) { this.save('}'); } } );