什么是词法分析
词法分析:根据词法规则,识别出字符序列 中的单词 ,并输出单词序列。
HTML词法分析就是根据HTML词法规则,识别出HTML文档字符流中的token,并输出token序列。
HTML的token有哪些
为了简化分析,本文使用最简单的情况进行描述:一个HTML元素由开始标签、元素内容和结束标签三部分组成。
<p>
hello world
</p>
例如上文中,包含的token有:
- "<p>"表示:p元素的开始标签;
- "hello world"表示:p元素的内容;
- "</p>"表示:p元素的结束标签;
为什么需要词法解析
HTML文档字符流经过词法分析得到的token序列,为后续的语法分析(DOM树构建)提供了原材料。
怎么实现词法解析
<p>hello world</p>
从左往右扫描字符序列,根据HTML词法规则,对以上p元素进行分析:
- 读到"<“字符,表示开始标签或结束标签的开始。读到"p"字符,表示开始标签p,读到”>"字符表示标签结束。这样就得到token 开始标签p;
- ">“字符后面读到字符"h"表示 元素内容,直到遇到”<"字符,元素内容结束。这样就得到token 元素内容hello world;
- 读到"<“字符,表示开始标签或结束标签的开始。读到”/“字符,表示结束标签的开始。读到"p"字符,表示结束标签p,读到”>"字符表示标签结束。这样就得到token 结束标签p;
总结:按顺序扫描字符序列,然后根据HTML词法规则对输入每个字符进行判断,并且每次判断都跟当前的状态有关,最终识别出每个token并输出相应token的描述对象。
有限状态机
有限状态机通常由多个状态组成,每个状态会对输入事件进行判断并执行对应动作,然后转移到下一个状态。
根据上面的概念,我们可以把词法分析的过程用有限状态机来实现:
把每个HTML token的特征看成是一个状态,每个状态会对输入的字符进行判断并输出token,最后根据词法规则转移到下一个状态。
<p>hello world</p>
用有限状态机模式解析上面的p元素:
- 初始状态为“data state”,当遇到“<”字符,状态变为“tag open state”。
- “tag open state”状态,当遇到a-z字符(这里为p)将创建开始开始标签token,状态变为“tag name state”。
- “tag name state”状态,当遇到“>”,则输出了一个token。重新回到“data state”状态。
- “data state”状态,当遇到a-z字符,则输出 元素内容token,直到遇到“<”(这里会创建字符hello world),状态变为“tag open state”。
- “tag open state”状态,当遇到“/”字符将创建结束标签token,状态变为“tag name state”。执行上面第三步骤,创建</p>标签。
代码实现
将每个状态封装成状态函数,在函数中对输入字符进行判断,然后返回下一个状态函数,并通过emitToken 函数输出解析好的 token。
// 初始data状态
var data = function(c){
if(c=="<") {
return tagOpen;
}
else if(c==EOF) {
emitToken(EOF);
return data;
}
else {
emitToken(new TextToken(c));
return data;
}
};
// 标签开始状态
var tagOpen = function tagOpenState(c){
if(c=="/") {
return endTagOpen;
}
if(c.match(/[a-z]/)) {
// 创建开始标签符号
token = new StartTagToken();
token.name = c;
return tagName;
}
};
// 标签符号状态
var tagName = function tagNameState(c){
if(c==">") {
// 输出一个token
emitToken(token);
return data;
}else if(c.match(/[a-z]/)) {
token.name += c;
return tagName;
}
};
// 结束标签状态
var endTagOpen = function endTagOpenState(c){
if(c.match(/[a-z]/)) {
// 创建结束标签符号
token = new EndTagToken();
token.name = c;
return tagName;
}
};
var state = data;
var char = "";
while(char = getInput())
state = state(char); // 通过状态转移函数,连接各个状态并输出对应的token