不用正则表达式,用javascript从零写一个模板引擎(一)
前言模板引擎的作用就是将模板渲染成html, 预览功能写一个库,不可能一次性把所有功能全部实现,所以我们第一版就挑一些比较核心的功能 var jstemp = require('jstemp'); // 渲染变量 jstemp.render('{{value}}',{value: 'hello world'});// hello world // 渲染if/elseif/else表达式 jstemp.render('{% if value1 %}hello{% elseif value %}world{% else %}byebye{% endif %}',{value: 'hello world'});// world // 渲染列表 jstemp.render('{%for item : list %}{{item}}{%endfor%}',{list:[1,2,3]});// 123 词法分析词法分析就是将字符串分割成一个一个有意义的token,每个token都有它要表达的意义,供语法分析器去建AST。 { EOF: 0,// 文件结束 Character: 1,// 字符串 Variable: 2,// 变量开始{{ VariableName: 3,// 变量名 IfStatement: 4,// if 语句 IfCondition: 5,// if 条件 ElseIfStatement: 6,// else if 语句 ElseStatement: 7,// else 语句 EndTag: 8,// }},%}这种闭合标签 EndIfStatement: 9,// endif标签 ForStatement: 10,// for 语句 ForItemName: 11,// for item 的变量名 ForListName: 12,// for list 的变量名 EndForStatement: 13// endfor 标签 }; 一般来说,词法分析有几种方法(欢迎补充)
作者本着自虐的心理,采取了第三种方法。 举例说明有穷状态自动机,解析
结果是 代码的话就是一个循环加一堆switch 转化状态(特别很累,也很容易出错),有一些情况我也没考虑全。截一部分代码下来看 nextToken() { Tokenizer.currentToken = ''; while (this.baSEOffset < this.template.length) { switch (this.state) { case Tokenizer.InitState: if (this.template[this.baSEOffset] === '{') { this.state = Tokenizer.LeftBraceState; this.baSEOffset++; } else if (this.template[this.baSEOffset] === '') { this.state = Tokenizer.EscapeState; this.baSEOffset++; } else { this.state = Tokenizer.CharState; Tokenizer.currentToken += this.template[this.baSEOffset++]; } break; case Tokenizer.CharState: if (this.template[this.baSEOffset] === '{') { this.state = Tokenizer.LeftBraceState; this.baSEOffset++; return TokenType.Character; } else if (this.template[this.baSEOffset] === '') { this.state = Tokenizer.EscapeState; this.baSEOffset++; } else { Tokenizer.currentToken += this.template[this.baSEOffset++]; } break; case Tokenizer.LeftBraceState: if (this.template[this.baSEOffset] === '{') { this.baSEOffset++; this.state = Tokenizer.BeforeVariableState; return TokenType.Variable; } else if (this.template[this.baSEOffset] === '%') { this.baSEOffset++; this.state = Tokenizer.BeforeStatementState; } else { this.state = Tokenizer.CharState; Tokenizer.currentToken += '{' + this.template[this.baSEOffset++]; } break; // ...此处省去无数case default: console.log(this.state,this.template[this.baSEOffset]); throw Error('错误的语法'); } } if (this.state === Tokenizer.InitState) { return TokenType.EOF; } else if (this.state === Tokenizer.CharState) { this.state = Tokenizer.InitState; return TokenType.Character; } else { throw Error('错误的语法'); } } 具体代码看这里 语法分析当我们将字符串序列化成一个个token后,就需要建AST树。树的根节点rootNode为一个childNodes数组用来连接子节点 let rootNode = {childNodes:[]} 字符串节点 { type:'character',value:'123' } 变量节点 { type:'variable',valueName: 'name' } if 表达式的节点和for表达式节点可以嵌套其他语句,所以要多一个childNodes数组来装语句内的表达式,childNodes 可以装任意的node,然后我们解析的时候递归向下解析。elseifNodes 装elseif/else 节点,解析的时候,当if的conditon为false的时候,按顺序取elseifNodes数组里的节点,谁的condition为true,就执行谁的childNodes,然后返回结果。 // if node { type:'if',condition: '',elseifNodes: [],childNodes:[],} // elseif node { type: 'elseif',// 其实这个属性没用 condition: '',childNodes:[] } // else node { type: 'elseif',// 其实这个属性没用 condition: true,childNodes:[] } for节点 { type:'for',itemName: '',listName: '',childNodes: [] } 举例: let template = ` <p>how to</p> {%for num : list %} let say{{num.num}} {%endfor%} {%if obj%} {{obj.test}} {%else%} hello world {%endif%} `; // AST树为 let rootNode = { childNode:[ { type:'char',value: '<p>how to</p>' },{ type:'for',itemName: 'num',listName: 'list',childNodes:[ { type:'char',value:'let say',},{ type: 'variable',valueName: 'num.num' } ] },{ type:'if',condition: 'obj',childNodes: [ { type: 'variable',valueName: 'obj.test' } ],elseifNodes: [ { type: 'elseif',condition:true,childNodes:[ { type: 'char',value: 'hello world' } ] } ] } ] } 具体建树逻辑可以看代码 解析AST树从rootNode节点开始解析 let html = ''; for (let node of rootNode.childNodes) { html += calStatement(env,node); } calStatement为所有语句的解析入口 function calStatement(env,node) { let html = ''; switch (node.type) { case NodeType.Character: html += node.value; break; case NodeType.Variable: html += calVariable(env,node.valueName); break; case NodeType.IfStatement: html += calIfStatement(env,node); break; case NodeType.ForStatement: html += calForStatement(env,node); break; default: throw Error('未知node type'); } return html; } 解析变量 // env为数据变量如{value:'hello world'},valueName为变量名 function calVariable(env,valueName) { if (!valueName) { return ''; } let result = env; for (let name of valueName.split('.')) { result = result[name]; } return result; } 解析if 语句及condition 条件 // 目前只支持变量值判断,不支持||,&&,<=之类的表达式 function calConditionStatement(env,condition) { if (typeof condition === 'string') { return calVariable(env,condition) ? true : false; } return condition ? true : false; } function calIfStatement(env,node) { let status = calConditionStatement(env,node.condition); let result = ''; if (status) { for (let childNode of node.childNodes) { // 递归向下解析子节点 result += calStatement(env,childNode); } return result; } for (let elseifNode of node.elseifNodes) { let elseIfStatus = calConditionStatement(env,elseifNode.condition); if (elseIfStatus) { for (let childNode of elseifNode.childNodes) { // 递归向下解析子节点 result += calStatement(env,childNode); } return result; } } return result; } 解析for节点 function calForStatement(env,node) { let result = ''; let obj = {}; let name = node.itemName.split('.')[0]; for (let item of env[node.listName]) { obj[name] = item; let statementEnv = Object.assign(env,obj); for (let childNode of node.childNodes) { // 递归向下解析子节点 result += calStatement(statementEnv,childNode); } } return result; } 结束语目前的实现的jstemp功能还比较单薄,存在以下不足:
... (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |