UNPKG

@joker.front/ast

Version:

### Overview

671 lines (657 loc) 22.9 kB
'use strict'; /** * 是否空函数 */ /** * 判断字符串是否为空 * @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;