snakeskin
Version:
Компилятор блочных шаблонов c поддержкой наследования.
274 lines (213 loc) • 6.3 kB
JavaScript
(() => {
var constNameRgxp = /\[(['"`])(.*?)\1]/g,
propAssignRgxp = /[.\[]/;
Snakeskin.addDirective(
'const',
{
group: [
'inherit',
'inlineInherit'
]
},
function (command, commandLength, type) {
var output = false;
if (command.slice(-1) === '?') {
output = true;
command = command.slice(0, -1);
}
var tplName = this.tplName,
source = `^[\$${symbols}_${!this.scope.length ? L_MOD : ''}][$${w}[\\].\\s]*=[^=]`;
var rgxp = rgxpCache[source] || new RegExp(source, 'i');
rgxpCache[source] = rgxp;
if (type === 'global' || (!tplName || rgxp.test(command)) && type !== 'output') {
if (tplName && type !== 'global') {
let parts = command.split('='),
prop = parts[0] && parts[0].trim();
if (!parts[1] || !parts[1].trim()) {
return this.error(`invalid "constant" declaration`);
}
let name = this.pasteDangerBlocks(prop);
if (name.charAt(0) === L_MOD) {
return this.error(`can\'t declare constant "${name.substring(1)}" with the context modifier (${L_MOD})`);
}
name = name.replace(constNameRgxp, '.$2');
this.startInlineDir('const', {
name: name
});
if (this.isReady()) {
if (!propAssignRgxp.test(prop)) {
this.consts.push(`var ${prop};`);
}
if (output) {
this.text = true;
this.append(this.wrap(`${prop} = ${this.prepareOutput(parts.slice(1).join('='))};`));
} else {
this.append(`${prop} = ${this.prepareOutput(parts.slice(1).join('='), true)};`);
}
}
if (this.isAdvTest()) {
if (constCache[tplName][name]) {
return this.error(`constant "${name}" is already defined`);
}
if (this.varCache[tplName][name]) {
return this.error(`constant "${name}" is already defined as variable`);
}
if (sysConst[name]) {
return this.error(`can't declare constant "${name}", try another name`);
}
let start = this.i - this.startTemplateI;
let parent,
parentTpl = this.parentTplName;
if (parentTpl) {
parent = constCache[parentTpl][name];
}
var insideCallBlock = this.hasParentBlock({
'block': true,
'proto': true
}, true);
if (insideCallBlock && insideCallBlock.name === 'block' && !insideCallBlock.params.args) {
insideCallBlock = false;
}
constCache[tplName][name] = {
from: start - commandLength,
to: start,
block: Boolean(insideCallBlock || parentTpl && parent && parent.block),
needPrfx: this.needPrfx,
output: output ?
'?' : null
};
if (!insideCallBlock) {
fromConstCache[tplName] = start + 1;
}
}
} else {
this.startInlineDir('global');
let desc = isAssign(command, true);
if (!desc) {
return this.error(`invalid "${this.name}" declaration`);
}
let mod = G_MOD + G_MOD;
if (command.charAt(0) !== G_MOD) {
command = mod + command;
} else {
command = command
.replace(scopeModRgxp, mod);
}
if (output && tplName) {
this.text = true;
this.append(this.wrap(`${this.prepareOutput(desc.key, true)} = ${this.prepareOutput(desc.value)};`));
} else {
this.save(`${this.prepareOutput(command, true)};`);
}
}
} else {
this.startInlineDir('output');
this.text = true;
if (!tplName) {
return this.error(`Directive "${this.name}" can be used only within a ${groupsList['template'].join(', ')}`);
}
if (this.isReady()) {
let desc = isAssign(command);
if (desc) {
if (output) {
this.append(this.wrap(`${this.prepareOutput(desc.key, true)} = ${this.prepareOutput(desc.value)};`));
} else {
this.text = false;
this.append(`${this.prepareOutput(command, true)};`);
}
return;
}
this.append(this.wrap(this.prepareOutput(command)));
}
}
}
);
Snakeskin.addDirective(
'output',
{
placement: 'template',
notEmpty: true,
text: true
},
function () {
Snakeskin.Directions['const'].apply(this, arguments);
}
);
Snakeskin.addDirective(
'global',
{
notEmpty: true
},
function () {
Snakeskin.Directions['const'].apply(this, arguments);
}
);
/**
* Вернуть объект-описание выражения,
* если в строке идёт присвоение значения переменной или свойству,
* или false
*
* @param {string} str - исходная строка
* @param {?boolean=} [opt_global=false] - если true, то идёт проверка суперглобальной переменной
* @return {({key: string, value: string}|boolean)}
*/
function isAssign(str, opt_global) {
var source = `^[${G_MOD}${L_MOD}$${symbols}_${opt_global ? '[' : ''}]`,
key = `${source}[i`;
var rgxp = rgxpCache[key] || new RegExp(source, 'i');
rgxpCache[key] = rgxp;
if (!rgxp.test(str)) {
return false;
}
var prop = '';
var count = 0,
eq = false;
var advEqMap = {
'+': true,
'-': true,
'*': true,
'/': true,
'^': true,
'~': true,
'|': true,
'&': true
};
var bAdvMap = {
'<': true,
'>': true
};
for (let i = -1; ++i < str.length;) {
let el = str.charAt(i);
prop += el;
if (bMap[el]) {
count++;
continue;
} else if (closeBMap[el]) {
count--;
continue;
}
let prev = str.charAt(i - 1),
next = str.charAt(i + 1);
if (!eq && !count &&
(
el === '=' && next !== '=' && prev !== '=' && !advEqMap[prev] && !bAdvMap[prev] ||
advEqMap[el] && next === '=' ||
bAdvMap[el] && bAdvMap[next] && str.charAt(i + 2) === '='
)
) {
let diff = 1;
if (advEqMap[el]) {
diff = 2;
} else if (bAdvMap[el]) {
diff = 3;
}
return {
key: prop.slice(0, -1),
value: str.substring(i + diff)
};
}
eq = el === '=';
}
return false;
}
})();