I am building a template engine for js and I need some help refactoring the code for fast and more efficient performance. If you can help modify or suggest some updates I'll be grateful.
var TEMPLATE_CONTEXT_REGISTER = new(function ContextRegister() {})();
var TEMPLATE_HELPERS_REGISTER = new(function HelpersRegister() {})();
var TEMPLATE_COMMENT_TYPE = Symbol.for("COMMENT");
var TEMPLATE_INIRIAL_TYPE = Symbol.for("INIRIAL");
var TEMPLATE_SEGMENT_TYPE = Symbol.for("SEGMENT");
var TEMPLATE_EXCLUDE_CHARS = "@";
var TEMPLATE_COMMENT_CHARS = ["(#", "#)"];
var TEMPLATE_CONTENT_CHARS = ["{{", "}}"];
var TEMPLATE_SEGMENT_CHARS = ["($", "$)"];
var TEMPLATE_REGEX_QUOTE = "(?=(?:[^'\"`]|[\"'`][^'\"`]*[\"'`])*$)";
var TEMPLATE_REGEX_START = TEMPLATE_SEGMENT_CHARS[0].replace(/(\(|\$)/g, "\\$1");
var TEMPLATE_REGEX_CLOSE = TEMPLATE_SEGMENT_CHARS[1].replace(/(\$|\))/g, "\\$1");
var TEMPLATE_REGEX_HELPER = [new RegExp("(@)" + TEMPLATE_REGEX_QUOTE, "g"), "$TEMPLATEHELPERS."];
var TEMPLATE_REGEX_COURSE = [new RegExp("(@loop)" + TEMPLATE_REGEX_QUOTE, "g"), "$TEMPLATELOOP"];
var TEMPLATE_REGEX_FACTOR = [new RegExp("(@this)" + TEMPLATE_REGEX_QUOTE, "g"), "$TEMPLATETHIS"];
var TEMPLATE_REGEX_SCRIPT = [
new RegExp(TEMPLATE_REGEX_START + "script" + TEMPLATE_REGEX_CLOSE + "([\\s\\S]*?)" + TEMPLATE_REGEX_START + "/script" + TEMPLATE_REGEX_CLOSE, "gmi"),
function(_, code) {
return TEMPLATE_SEGMENT_CHARS[0] + "script " + code.replace(/\s\s+|\n/g, "") + TEMPLATE_SEGMENT_CHARS[1];
},
];
var TEMPLATE_REGEX_ESCAPE = [
new RegExp("s*(</?[ws=\"/.':;#-/?]+>)s*", "gi"),
function(_, i) {
return i.trim();
},
];
var TEMPLATE_REGEX_OBJECT = [
new RegExp("([.]+[\\w\\d-_]+)" + TEMPLATE_REGEX_QUOTE, "g"),
function(obj) {
return "['" + obj.substring(1) + "']";
},
];
function _tpl_include_(partial, context, txtarr, jsxarr, index) {
var template = new Template(partial, context);
var jsx = _build_({ origins: template.origins, context: template.context, helpers: template.helpers, partial: template.partial }),
txt = jsx.shift();
txtarr = [].concat(txtarr, [txtarr.pop() + txt.shift()], txt);
jsxarr = [].concat(jsxarr, jsx);
return [txtarr, jsxarr, index + jsx.length];
}
function _tpl_length_(object) {
return Array.isArray(object) ? object.length : typeof object === "object" ? Object.keys(object).length : null;
}
function _tpl_range_(from, to, inc, func) {
to = inc ? (from < to ? to + 1 : to - 1) : to;
for (var i = from; from < to ? i < to : i > to; from < to ? i++ : i--) {
func(i, {
index: i,
round: i + 1,
odd: i % 2 > 0,
even: i % 2 === 0,
first: i === from,
last: i === from < to ? i - 1 : i + 1,
});
}
}
function _tpl_each_(obj, func) {
if (obj === null) {
return obj;
}
var index = -1;
if (Array.isArray(obj)) {
var length = obj.length,
count = 1;
while (++index < length) {
if (
func(index, obj[index], {
index: index,
round: count,
odd: index % 2 > 0,
even: index % 2 === 0,
first: index === 0,
last: count === length,
}) === false
) {
break;
}
count++;
}
}
var key = Object.keys(obj),
length = key.length,
count = 1;
while (++index < length) {
if (
func(key[index], obj[key[index]], {
index: index,
round: count,
odd: index % 2 > 0,
even: index % 2 === 0,
first: index === 0,
last: count === length,
}) === false
) {
break;
}
count++;
}
}
function _loops_args_(args) {
var mth = /\[(.*),(.*)\]/g.exec(args[1]),
arr = args[0].trim(),
val = args[1].trim(),
key = "_";
return [arr, mth ? mth[1].trim() : key, mth ? mth[2].trim() : val];
}
function _range_args_(args) {
var mth = /(.*)\s*(:?to|via)\s*(.*)/g.exec(args[1]),
arr = args[0].trim();
return [arr, mth[1].trim(), mth[3].trim(), mth[2].trim() === "via"];
}
function _replace_(line, ...regexArray) {
for (var i = 0, len = regexArray.length; i < len; i++) {
var regex = regexArray[i][0],
replace = regexArray[i][1];
line = line.replace(regex, replace);
}
return line;
}
function _token_(type, value, ln, cl) {
return {
type,
value: type === TEMPLATE_SEGMENT_TYPE ? _logic_(value, ln, cl) : value,
ln,
cl,
};
}
function _check_(source, niddle, index) {
return niddle.split("").every((e, i) => source[index + i] === e);
}
function _logic_(source, ln, cl) {
var args = _replace_(source, TEMPLATE_REGEX_COURSE, TEMPLATE_REGEX_FACTOR, TEMPLATE_REGEX_HELPER, TEMPLATE_REGEX_OBJECT).split(" "),
curs = "$TEMPLATELINNO=" + ln + ";$TEMPLATECOLNO=" + cl + ";",
line = args.slice(1).join(" "),
type = args[0];
switch (type) {
case "log":
case "info":
case "warn":
case "error":
case "debug":
return "console." + type + "(" + line + ");" + curs;
case "echo":
return "$TEMPLATEJSXOF(" + line + ");" + curs;
case "raw":
return "$TEMPLATETXTOF(" + line + ");" + curs;
case "set":
return "var " + line + ";" + curs;
case "elif":
return "}else if(" + line + "){" + curs;
case "else":
return "}else{" + curs;
case "empty":
return "});}else{" + curs;
case "script":
return line + curs;
case "include":
var [partial, context] = line.split("with");
return (
"[$TEMPLATETXTAR,$TEMPLATEJSXAR,$TEMPLATEINDEX]=$TEMPLATEINCLUDE(" +
partial +
"," +
context +
",$TEMPLATETXTAR,$TEMPLATEJSXAR,$TEMPLATEINDEX);" +
curs
);
case "each":
var [arr, key, val] = _loops_args_(line.split("as"));
return "$TEMPLATEEACH(" + arr + ",function(" + key + "," + val + ", $TEMPLATELOOP){" + curs;
case "range":
var [arr, from, to, is] = _range_args_(line.split("from"));
return "$TEMPLATERANGE(" + from + "," + to + "," + is + ",function(" + arr + ", $TEMPLATELOOP){" + curs;
case "/each":
case "/range":
return "});" + curs;
case "if":
return "if(" + line + "){" + curs;
case "unless":
return "if(!(" + line + ")){" + curs;
case "until":
return "while(!(" + line + ")){" + curs;
case "while":
return "while(" + line + "){" + curs;
case "forelse":
var [arr, key, val] = _loops_args_(line.split("as"));
return "if($TEMPLATELENGTH(" + arr + ")){$TEMPLATEEACH(" + arr + ",function(" + key + "," + val + ", $TEMPLATELOOP){" + curs;
case "/if":
case "/unless":
case "/until":
case "/while":
case "/forelse":
return "}" + curs;
default:
return "";
}
}
function _parse_(source) {
var ln = 1,
cl = 1,
dt = [],
al = "",
cu = "",
op = false;
for (let i = 0; i < source.length; i++) {
cl++;
if (source[i] === "\n")(cl = 1), ln++;
if (_check_(source, TEMPLATE_EXCLUDE_CHARS + TEMPLATE_COMMENT_CHARS[0], i)) {
if (cu !== "") {
al += cu + TEMPLATE_COMMENT_CHARS[0];
cu = "";
}
op = false;
i = i + TEMPLATE_COMMENT_CHARS[0].length;
} else if (_check_(source, TEMPLATE_EXCLUDE_CHARS + TEMPLATE_SEGMENT_CHARS[0], i)) {
if (cu !== "") {
al += cu + TEMPLATE_SEGMENT_CHARS[0];
cu = "";
}
op = false;
i = i + TEMPLATE_SEGMENT_CHARS[0].length;
} else if (_check_(source, TEMPLATE_EXCLUDE_CHARS + TEMPLATE_CONTENT_CHARS[0], i)) {
if (cu !== "") {
al += cu + TEMPLATE_CONTENT_CHARS[0];
cu = "";
}
op = false;
i = i + TEMPLATE_CONTENT_CHARS[0].length;
} else if (
_check_(source, TEMPLATE_COMMENT_CHARS[0], i) ||
_check_(source, TEMPLATE_SEGMENT_CHARS[0], i) ||
_check_(source, TEMPLATE_CONTENT_CHARS[0], i)
) {
var plus =
(_check_(source, TEMPLATE_COMMENT_CHARS[0], i) ?
TEMPLATE_COMMENT_CHARS[0] :
_check_(source, TEMPLATE_SEGMENT_CHARS[0], i) ?
TEMPLATE_SEGMENT_CHARS[0] :
TEMPLATE_CONTENT_CHARS[0]
).length - 1;
if (cu !== "") {
if (_check_(source, TEMPLATE_COMMENT_CHARS[0], i)) al += cu;
else {
dt.push(_token_(TEMPLATE_INIRIAL_TYPE, al + cu, ln, cl + plus));
al = "";
}
cu = "";
}
op = true;
i = i + plus;
} else if (_check_(source, TEMPLATE_COMMENT_CHARS[1], i)) {
var plus = TEMPLATE_COMMENT_CHARS[1].length - 1;
if (op) {
dt.push(_token_(TEMPLATE_COMMENT_TYPE, cu.trim(), ln, cl + plus));
} else {
al += cu + TEMPLATE_COMMENT_CHARS[1];
}
cu = "";
op = false;
i = i + plus;
} else if (_check_(source, TEMPLATE_SEGMENT_CHARS[1], i)) {
var plus = TEMPLATE_SEGMENT_CHARS[1].length - 1;
if (op) {
dt.push(_token_(TEMPLATE_SEGMENT_TYPE, cu.trim(), ln, cl + plus));
} else {
al += cu + TEMPLATE_SEGMENT_CHARS[1];
}
cu = "";
op = false;
i = i + plus;
} else if (_check_(source, TEMPLATE_CONTENT_CHARS[1], i)) {
var plus = TEMPLATE_CONTENT_CHARS[1].length - 1;
if (op) {
dt.push(_token_(TEMPLATE_SEGMENT_TYPE, cu.trim()[0] === ">" ? "raw " + cu.trim().slice(1) : "echo " + cu.trim(), ln, cl + plus));
} else {
al += cu + TEMPLATE_CONTENT_CHARS[1];
}
cu = "";
op = false;
i = i + plus;
} else {
cu += source[i];
}
}
dt.push(_token_(TEMPLATE_INIRIAL_TYPE, al + cu, ln + 1, cl + 1));
return dt;
}
function _compile_(source) {
var exe = _parse_(source);
var txt = exe.filter((e) => e.type === TEMPLATE_INIRIAL_TYPE);
var jsx = exe.filter((e) => e.type === TEMPLATE_SEGMENT_TYPE);
return Array.from({ length: Math.max(txt.length, jsx.length) }, function(_, i) {
return i;
}).reduce(function(acc, idx) {
var jx = jsx[idx] || { value: "" },
tx = txt[idx] || { value: "", ln: -1, cl: -1 };
return (acc += "$TEMPLATETXTOF(`" + tx.value + "`);$TEMPLATELINNO=" + tx.ln + ";$TEMPLATECOLNO=" + tx.cl + ";" + jx.value);
}, "");
}
function _bind_(object) {
for (var name in TEMPLATE_HELPERS_REGISTER)
if (
typeof TEMPLATE_HELPERS_REGISTER[name] === "function" &&
!/^(?:class\s+|function\s+(?:_class|_default|[A-Z]))/.test(TEMPLATE_HELPERS_REGISTER[name])
)
TEMPLATE_HELPERS_REGISTER[name] = TEMPLATE_HELPERS_REGISTER[name].bind(object);
else delete TEMPLATE_HELPERS_REGISTER[name];
}
function _build_({ origins, context, helpers, partial }) {
if (!variables._VCS_[partial]) variables._VCS_[partial] = _compile_(origins);
var exe =
"return function($TEMPLATETHIS,$TEMPLATEHELPERS,$TEMPLATEINCLUDE,$TEMPLATELENGTH,$TEMPLATERANGE,$TEMPLATEEACH,$TEMPLATEERROR){var $TEMPLATETXTAR=[],$TEMPLATEJSXAR=[],$TEMPLATEINDEX=0,$TEMPLATELINNO=0,$TEMPLATECOLNO=0;function $TEMPLATEESCAPE($LINE){var $MAP={'&':'&','<':'<','>':'>','\"':'"','\\'':'''};return typeof $LINE!=='string'?$LINE:$LINE.replace(/[&<>\"']/g,function($CHAR){return $MAP[$CHAR];});}function $TEMPLATETXTOF($LINE){if(!$TEMPLATETXTAR[$TEMPLATEINDEX]){$TEMPLATETXTAR[$TEMPLATEINDEX]='';}$TEMPLATETXTAR[$TEMPLATEINDEX]+=$LINE;}function $TEMPLATEJSXOF($LINE){$TEMPLATEJSXAR[$TEMPLATEINDEX]=$TEMPLATEESCAPE($LINE);$TEMPLATEINDEX++;}with($TEMPLATETHIS||{}){try{" +
variables._VCS_[partial] +
"}catch(e){throw new $TEMPLATEERROR(e.message+'\\nview: " +
partial +
"\\nline: '+$TEMPLATELINNO+'\\npos: '+$TEMPLATECOLNO);}}return [$TEMPLATETXTAR.map(function(txt){return txt.replace(/\\n+|\\n\\s+/g, '\\n')}),...$TEMPLATEJSXAR];}";
return new Function("", exe)()(context, helpers, _tpl_include_, _tpl_length_, _tpl_range_, _tpl_each_, CurseError);
}
function Template(partial, context) {
if (!(partial in variables._VMS_)) throw new CurseError('view "' + partial + '" not found');
this.origins = _replace_(variables._VMS_[partial], TEMPLATE_REGEX_SCRIPT, TEMPLATE_REGEX_ESCAPE, [/ {2,}/g, " "]);
this.context = {...TEMPLATE_CONTEXT_REGISTER, ...(context || {}) };
this.helpers = _bind_(this.context);
this.partial = partial;
}
Template.prototype.exec = function() {
var template = _build_({ origins: this.origins, context: this.context, helpers: this.helpers, partial: this.partial });
return new Parser(...template).exec();
};
Template.prototype.string = function() {
var template = _build_({ origins: this.origins, context: this.context, helpers: this.helpers, partial: this.partial });
return template.shift().reduce(function(acc, part, i) {
return acc + part + (["string", "number", "boolean"].includes(typeof template[i]) ? template[i] : "");
}, "");
};
function TemplateRegister() {}
TemplateRegister.prototype.helper = function(name, callback) {
if (name in TEMPLATE_HELPERS_REGISTER) throw new CurseError("Helper " + name + " already exists.");
TEMPLATE_HELPERS_REGISTER[name] = callback;
};
TemplateRegister.prototype.context = function(name, value) {
if (name in TEMPLATE_CONTEXT_REGISTER) throw new CurseError("Context " + name + " already exists.");
TEMPLATE_CONTEXT_REGISTER[name] = value;
};
Template.register = new TemplateRegister();