@joker.front/ast
Version:
### Overview
671 lines (657 loc) • 22.9 kB
JavaScript
;
/**
* 是否空函数
*/
/**
* 判断字符串是否为空
* @param val
* @returns
*/
function isEmptyStr(val) {
if (val) {
return val.trim() === "";
}
return true;
}
function matchGroupContent(str, format, isGlobal = true, excludeStr = true) {
// 匹配 ... 两端的分割符号
let keyParts = /^([\S\s]+?)\.\.\.([\S\s]+)/.exec(format);
if (keyParts === null || keyParts[1] === keyParts[2]) {
// 左右分隔符必须相等
throw new Error(format + ":format必须是 X...X 格式,并且前后关键字不可一致");
}
// 左分隔符
let opener = keyParts[1];
// 右分隔符
let closer = keyParts[2];
// 全局索引左右分割符
let reg = new RegExp(`[${(opener + closer).replace(/[-[\]{}()*+?.\\^$|,]/g, "\\$&")}]`, "g");
// 匹配后单引号或双引号中的值
let regSpecial = /(?<special>('([^']*)')|("([^']*)"))/;
// let regSpecial = /(?<special>((?<=').*?(?='))|((?<=").*?(?=")))/; //(a+parseInt('1')*b.c)
let strSpecial = str;
let resSpecial = [];
let startSpecialIndex = 0;
while (excludeStr && strSpecial) {
// 匹配出引号内的部分
let matchResult = strSpecial.match(regSpecial);
if (matchResult && matchResult.index !== undefined && matchResult.groups && matchResult.groups.special) {
// 根据匹配出的位置扩选出整个带引号的部分并存入数组中
let startIndex = matchResult.index + startSpecialIndex;
let endIndex = startIndex + matchResult.groups.special.length + 1;
resSpecial.push({
start: startIndex,
end: endIndex,
value: str.substring(startIndex, endIndex)
});
// 记录最后一个引号起始位置
startSpecialIndex = endIndex;
// 截取最后一个引号起始位置到整个字符串结尾
strSpecial = str.substring(endIndex);
}
else {
strSpecial = "";
}
}
let results = [], openTokens = 0, match, matchStartIndex = -1;
//逐个匹配字符串中的左右分隔符
while ((match = reg.exec(str))) {
/**
* 正则的实例属性 lastIndex, 初始情况下都是从零开始,当第二次调用这个实例匹配字符串时是从
* 上一次匹配完成位置的下一个位置开始。(当然正则实例是开启全局的)
* 1.如果上次匹配到了一个位置大于等于此次匹配的字符串的长度,那么此次匹配返回false或空数组
* 2.如果上次没有匹配到,那么此次匹配还是从零开始
*/
let lastIndex = reg.lastIndex;
let inSpecialStr = resSpecial.some((item) => {
// 如果分割符为引号内的则不进行处理
if (lastIndex > item.start && lastIndex < item.end) {
return true;
}
return false;
});
// 引号内分隔符继续下一循环
if (inSpecialStr) {
continue;
}
// 处理引号外的分隔符
if (match[0] === opener) {
// 记录第一个左分割符的起始位置
if (!openTokens) {
matchStartIndex = lastIndex;
}
// 如果为左分隔符,增加计数器
openTokens++;
}
else if (openTokens) {
// 处理右分隔符
openTokens--;
if (!openTokens) {
// 根据左分隔符位置数出一样个数右分隔符个数,截取两个分隔符之间的字符串
let content = str.slice(matchStartIndex - opener.length, match.index + closer.length);
results.push({
value: content,
index: matchStartIndex - opener.length
});
// 如果不是全局检索,则在处理完第一组分隔符内容后终止循环
if (isGlobal === false) {
break;
}
}
}
}
// 有不闭合的左分割符
if (openTokens !== 0 || (matchStartIndex > -1 && results.length === 0)) {
throw new Error(str + `:闭合标签配错误,请检查闭合标签。`);
}
return results;
}
/**
* AST 语法树(Joker)
*/
exports.AST = void 0;
(function (AST) {
(function (NodeType) {
NodeType[NodeType["TEXT"] = 0] = "TEXT";
NodeType[NodeType["COMMENT"] = 1] = "COMMENT";
NodeType[NodeType["ELEMENT"] = 2] = "ELEMENT";
NodeType[NodeType["COMMAND"] = 3] = "COMMAND";
NodeType[NodeType["COMPONENT"] = 4] = "COMPONENT";
})(AST.NodeType || (AST.NodeType = {}));
//#endregion
})(exports.AST || (exports.AST = {}));
const JOKER_TRACE_EXPRESSIONS = Symbol.for("__JOKER_TRACE_EXPRESSIONS__");
const RE_ALLOWEDKEYWORDS = new RegExp("^(" +
[
// 原有的全局对象
"Math",
"Global",
"Date",
// 新增基本数据类型和内置对象
"String",
"Number",
"Boolean",
"Array",
"Object",
"Function",
"RegExp",
// 原有的特殊值和关键字
"this",
"true",
"false",
"null",
"undefined",
"Infinity",
"NaN",
// 原有的全局函数
"isNaN",
"isFinite",
"decodeURI",
"decodeURIComponent",
"encodeURI",
"encodeURIComponent",
"parseInt",
"parseFloat"
].join("\\b|") +
"\\b)");
const NOTALLOWEDKEYWORDS = [
"break",
"case",
"class",
"catch",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"export",
"extends",
"finally",
"for",
"function",
"if",
"import",
"in",
"instanceof",
"let",
"return",
"super",
"switch",
"throw",
"try",
"var",
"while",
"with",
"yield",
"enum",
"await",
"implements",
"package",
"protected",
"static",
"interface",
"private",
"public"
];
const RE_NOTALLOWEDKEYWORDS = new RegExp("^(" + NOTALLOWEDKEYWORDS.join("\\b|") + "\\b)");
// const RE_STATICVALUE =
// /[\{,]\s*[\w\$_]+\s*:|('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`)|new |typeof |void /g;
const RE_STATICVALUE = /[\{,]\s*[\w\$_]+\s*:|('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:\\[^]|\${(?:[^{}]|{[^{}]*})*}|[^`\\$]+|\$(?!{))*`)|new |typeof |void /g;
const RE_STATICTEMPKEY = /"(\d+)"/g;
const RE_PROPERTY_TAG = /([^\w$\.])(?:([A-Za-z_$][\w$]*)|(\${([^}]+)}))/g;
const EXPRESSHANDLERTAG = "context";
function createFuntionBody(exp) {
if (RE_NOTALLOWEDKEYWORDS.test(exp)) {
throw new Error(`Keywords are not allowed in the expression: ${NOTALLOWEDKEYWORDS.join(",")}; Expression: ${exp}`);
}
let stateValues = [];
var body = (exp || "")
.trim()
.replace(RE_STATICVALUE, (str, isString) => {
// 如果是模板字符串(`...`),只保留 `${...}`,其余替换
if (isString && isString.startsWith("`")) {
return str.replace(/(\${(?:[^{}]|{[^{}]*})*}|[^`$\\]+|\$(?!{))/g, (match) => {
if (match.startsWith("${")) {
return match; // 保留 `${...}`
}
else {
const i = stateValues.length;
stateValues[i] = match;
return `"${i}"`; // 替换静态部分
}
});
}
// 其他情况(普通字符串、键值对、关键字)
const i = stateValues.length;
stateValues[i] = isString ? str.replace(/\n/g, "\\n") : str;
return `"${i}"`;
})
.replace(/\s/g, "");
body = (" " + body)
.replace(RE_PROPERTY_TAG, (str) => {
if (str === '"$')
return str;
var splitVal = str.charAt(0);
var path = str.slice(1);
if (RE_ALLOWEDKEYWORDS.test(path)) {
return str;
}
else {
path =
path.indexOf('"') > -1
? path.replace(RE_STATICTEMPKEY, (str, i) => {
return stateValues[i];
})
: path;
return splitVal + EXPRESSHANDLERTAG + "." + path;
}
})
.replace(RE_STATICTEMPKEY, (str, i) => {
return stateValues[i];
});
if (body.charAt(0) === " ") {
body = body.substring(1);
}
return body;
}
function transformDynamic(exporessStr) {
let reg = /@(?<express>(?:[a-zA-Z_][\\\-\.0-9a-zA-Z]*(?:\[[^\]]*\])*(?:\s*\((?:[^()]|\((?:[^()]|\([^()]*\))*\))*\))*)|(?:\([\s\S]*\)))\s*/;
if (reg.test(exporessStr)) {
let expressStrs = [];
while (exporessStr) {
let matchResult = exporessStr.match(reg);
if (matchResult && matchResult.groups && matchResult.index !== undefined) {
let prevText = exporessStr.substring(0, matchResult.index);
if (prevText) {
expressStrs.push(`"${prevText}"`);
}
let strExpress = matchResult.groups.express;
let funcCodeResult = matchGroupContent(strExpress, "(...)", false, true);
if (funcCodeResult.length) {
let funcCodeItem = funcCodeResult[0];
let strFuncBody = strExpress.substring(0, funcCodeItem.index + funcCodeItem.value.length);
expressStrs.push(createFuntionBody(strFuncBody));
exporessStr = exporessStr.substring(matchResult.index + 1 + strFuncBody.length);
}
else {
expressStrs.push(createFuntionBody(strExpress));
//trimeEnd 为了避免表达式后的空格丢失
//e.g. @f xx=> f+" xx";
exporessStr = exporessStr.substring(matchResult.index + matchResult[0].trimEnd().length);
}
}
else {
expressStrs.push(`"${exporessStr}"`);
exporessStr = "";
}
}
/**
* 使用运算符+ 进行拼接
* 不能使用数组的join,因为对于组件的pops ,不一定是字符串
* 如果字符串中使用+拼接出现问题,应该改数据类型,而不是运算符
* 例如两个紧邻的运算符使用则会@(1)@(2) 则会编译成1+2
*/
return expressStrs.join("+");
}
}
const CODEFUNCTIONTAGS = "Text";
const COMMANDGROUPKEYS = ["if", "elseif", "else", "for", "foreach", "section"];
/**let any in|of any */
const RE_FORLETINOF = /^let(\s?)(?<variable>(.*))\s+(?<keyword>(in|of))\s+(?<aimObj>.*)/;
/**带条件的for循环 */
const RE_FORCONDITION = /^let\s+(?<variable>.*);(?<condition>.*);(?<step>.*)/;
/**声明变量规则 */
const RE_LET = /^[0-9a-zA-Z_]+$/;
function getCommandAST(cmdName, param) {
if (cmdName === "for") {
return getForCommand(param);
}
else if (cmdName === "section") {
return getSectionCommand(param);
}
else if (["if", "elseif", "else"].includes(cmdName)) {
return getIfCommand(cmdName, param);
}
else {
return {
type: exports.AST.NodeType.COMMAND,
cmdName,
param: createFuntionBody(param)
};
}
}
function getSectionCommand(param) {
let arrParam = (param ?? "").split(",").map((v) => {
return (v || "").trim();
});
let sectionId = arrParam[0] || "";
arrParam = arrParam.slice(1);
return {
type: exports.AST.NodeType.COMMAND,
cmdName: "section",
childrens: [],
id: sectionId,
paramKeys: arrParam,
isGroup: COMMANDGROUPKEYS.includes("section")
};
}
function getForCommand(param) {
function checkLetKey(key) {
if (RE_LET.test(key) === false) {
throw new Error(`Error occurred while parsing 'let' declaration in 'for' command. Variable declarations must be combinations of [0-9a-zA-Z_]: ${key}`);
}
}
//@for(let i=0;i<ssss.length;i++)
let expressStr = param;
let conditionMatch = expressStr.match(RE_FORCONDITION);
if (conditionMatch && conditionMatch.groups) {
let variable = (conditionMatch.groups.variable || "").trim();
let condition = (conditionMatch.groups.condition || "").trim();
let step = (conditionMatch.groups.step || "").trim();
if (variable && condition && step) {
let tempVal = variable.split("=");
if (tempVal.length !== 2) {
throw new Error(`The 'let' declaration in the 'for' command does not conform to the specification requirement of 'variable=value'; ${variable}`);
}
let letKey = tempVal[0].trim();
let letKeyVal = tempVal[1].trim();
checkLetKey(letKey);
if (letKeyVal === "") {
throw new Error("The variable in the 'let' declaration of the 'for' command has no initial value set; " + variable);
}
return {
type: exports.AST.NodeType.COMMAND,
cmdName: "for",
keyType: "condition",
childrens: [],
isGroup: COMMANDGROUPKEYS.includes("for"),
param: {
letKey,
defaultKeyVal: createFuntionBody(letKeyVal),
step: createFuntionBody(step),
condition: createFuntionBody(condition)
}
};
}
}
//@for(let index in sss)
//@for(let item of sss)
//@for(let (index,item) in ssss)
let letInOfMatch = expressStr.match(RE_FORLETINOF);
if (letInOfMatch && letInOfMatch.groups) {
let variable = (letInOfMatch.groups.variable || "").trim();
//正则已做约束
let keyword = (letInOfMatch.groups.keyword || "").trim();
let aimObj = (letInOfMatch.groups.aimObj || "").trim();
if (variable && keyword && aimObj) {
let fullKeyParamMatch = variable.match(/^\((?<keys>.*)\)$/);
let lets;
if (fullKeyParamMatch && fullKeyParamMatch.groups?.keys) {
lets = fullKeyParamMatch.groups.keys.split(",").map((m) => {
return m.trim();
});
}
else {
lets = [variable];
}
if (lets.length > 1 && keyword === "of") {
throw new Error(`The 'for of' command can only accept one parameter: ${expressStr}`);
}
if (lets.length > 2) {
throw new Error(`The 'let' declaration in a 'for' command supports a maximum of two parameters: ${expressStr}`);
}
//合法性验证
lets.forEach((m) => {
checkLetKey(m);
});
let inOrOfParam;
if (keyword === "in") {
inOrOfParam = {
indexKey: lets[0],
itemKey: lets[1],
dataKey: createFuntionBody(aimObj)
};
}
else {
inOrOfParam = {
indexKey: undefined,
itemKey: lets[0],
dataKey: createFuntionBody(aimObj)
};
}
return {
type: exports.AST.NodeType.COMMAND,
cmdName: "for",
keyType: keyword,
childrens: [],
isGroup: COMMANDGROUPKEYS.includes("for"),
param: inOrOfParam
};
}
}
throw new Error("Invalid parameters for 'for' loop: " + expressStr);
}
function getIfCommand(kind, param) {
if (kind === "else" && param) {
console.warn("The 'else' directive does not require a condition parameter: " + param);
param = "";
}
return {
type: exports.AST.NodeType.COMMAND,
cmdName: "if",
childrens: [],
isGroup: COMMANDGROUPKEYS.includes("if"),
kind: kind,
condition: createFuntionBody(param)
};
}
/**
* 创建动态代码块节点
* @param code
* @returns
*/
let createCodeFunction = (code) => {
return {
type: exports.AST.NodeType.COMMAND,
cmdName: CODEFUNCTIONTAGS,
_code: window[JOKER_TRACE_EXPRESSIONS] ? (code.includes("(") ? `@${code}` : `@(${code})`) : undefined,
param: createFuntionBody((code || "").trim()),
isGroup: false
};
};
/**
* 创建命令节点
* @param cmdName
* @param param
* @param childrens
* @returns
*/
let createCommand = (cmdName, param, childrens) => {
if (isEmptyStr(cmdName)) {
throw new Error("Missing command name");
}
//这里不会存在空字符串,因为空字符串带引号,这里是表达式,不是运行时
param = (param || "").trim();
//不做param空判断,因为有空参数调用
let result = getCommandAST(cmdName, param);
if (window[JOKER_TRACE_EXPRESSIONS]) {
if (cmdName === "elseif") {
result._code = `else if(${param})`;
}
else if (cmdName !== "else") {
result._code = `@${cmdName}(${param})`;
}
}
if (CODEFUNCTIONTAGS === cmdName) {
throw new Error(`${CODEFUNCTIONTAGS} is a reserved keyword for code blocks. Use the createCodeFunction method to define code blocks.`);
}
else {
if (COMMANDGROUPKEYS.includes(cmdName) === false && childrens && childrens.length) {
console.warn(`Only special commands (${COMMANDGROUPKEYS.join(",")}) allow nested subgroups. Nested content will be ignored.`);
}
else {
result.childrens = childrens || [];
}
}
return result;
};
/**
* 创建注释节点
* @param text
* @returns
*/
let createComment = (text) => {
return {
type: exports.AST.NodeType.COMMENT,
text
};
};
function transformEvent(attrKey, attrVal) {
//eventFunctionParams 暂时解析成字符串
//后面交由表达式转换成表达式字符串
attrKey = attrKey.trim();
attrVal = attrVal?.trim();
if (isEmptyStr(attrKey)) {
throw new Error("Event name cannot be empty");
}
let invalidReg = /^(?<functionName>([a-zA-Z_][0-9\._a-zA-Z]*\s*))(\((?<functionParam>(.*))\))?$/;
if (attrVal && !invalidReg.test(attrVal)) {
throw new Error(`Failed to parse parameters for the ${attrKey} event: ${attrVal}`);
}
let eventNameSplit = attrKey.split(".");
// 匹配attrVal带参数的正则
let result = attrVal?.match(invalidReg);
let eventParam = {
name: eventNameSplit[0],
functionName: undefined,
modifiers: eventNameSplit.slice(1)
};
if (result && result.groups) {
// 函数名称
eventParam.functionName = (result.groups["functionName"] || "").trim();
// 事件传参
eventParam.functionParam = result.groups["functionParam"];
if (eventParam.functionParam) {
if (window[JOKER_TRACE_EXPRESSIONS]) {
eventParam._code = eventParam.functionParam;
}
eventParam.functionParam = createFuntionBody(eventParam.functionParam);
}
}
//注释:attrVal有值 && 修改functionParam 为 functionName
if (attrVal && isEmptyStr(eventParam.functionName)) {
throw new Error(`Event parameter parsing failed: ${attrKey}. Event handler name not specified.`);
}
return eventParam;
}
/**
* undefined占位符,用于保留类型
* 此值SFC和AST类库需要进行值同步
*/
const UNDEFINED_BUFFER = "__UNDEFINED_BUFFER__";
/**
* 创建Element 标签节点
* @param tagName
* @param attr
* @param childrens
* @returns
*/
let createElement = (tagName, attr, childrens) => {
if (isEmptyStr(tagName)) {
throw new Error("The tagName parameter is required for the createElement method");
}
let result = {
tagName,
childrens: childrens || [],
attributes: [],
events: [],
type: exports.AST.NodeType.ELEMENT
};
analyElemenet(result, attr);
return result;
};
function analyElemenet(ast, attr) {
if (attr) {
for (let name in attr) {
let key = name.trim();
let attrValue = attr[name];
if (key.startsWith("@")) {
attrValue = attrValue === UNDEFINED_BUFFER ? undefined : attrValue;
ast.events.push(transformEvent(key.substring(1), attrValue));
}
else {
attrValue = attrValue === UNDEFINED_BUFFER ? "@true" : attrValue;
let attrItem = {
name: key,
value: attrValue
};
if (attrItem.value) {
try {
attrItem.express = transformDynamic(attrItem.value);
if (attrItem.express && !window[JOKER_TRACE_EXPRESSIONS]) {
delete attrItem.value;
}
}
catch (e) {
console.error(`Invalid expression conversion: ${attrItem.value}`);
throw e;
}
}
ast.attributes.push(attrItem);
}
}
}
}
/**
* 创建文本节点
* @param text
* @returns
*/
let createText = (text) => {
return {
type: exports.AST.NodeType.TEXT,
text
};
};
/**
* 创建组件节点(一般适用于动态的Render,在SFC静态编译时应避免采用该方法,使用Element类型在运行时进行分流)
* @param component 组件
* @param attrs 属性
* @param childrens 子集
* @returns
*/
let createComponent = (component, attrs, childrens) => {
let result = {
childrens: childrens || [],
attributes: [],
events: [],
type: exports.AST.NodeType.COMPONENT,
component
};
// 借用 Element的解析
analyElemenet(result, attrs);
return result;
};
/**
* 渲染操作
*/
const RENDER_HANDLER = {
createCodeFunction,
createCommand,
createComment,
createElement,
createText,
createComponent
};
exports.EXPRESSHANDLERTAG = EXPRESSHANDLERTAG;
exports.JOKER_TRACE_EXPRESSIONS = JOKER_TRACE_EXPRESSIONS;
exports.RENDER_HANDLER = RENDER_HANDLER;
exports.createCodeFunction = createCodeFunction;
exports.createCommand = createCommand;
exports.createComment = createComment;
exports.createComponent = createComponent;
exports.createElement = createElement;
exports.createFuntionBody = createFuntionBody;
exports.createText = createText;