我正在参加「掘金·启航计划」
前面的章节中我们有提到Hugo的模板源自于Go的Text Tempalte。
上例中由{{...}}定义的,就是动态内容。Go将这种双括号语法,就叫做Action Block,下面我们着重看看她的工作原理。
将我们的模板片段和上一章节中分析结果对应上,就可以得到上图所示。
可以看到词法分析器就像语言大师,可以读懂句子里所有的细节,并能准确说出他们每一个人的名字。
Hugo又是怎么设计这个分析器的呢?
正如上图所示,左边就是简化后的分析器结构体,其中主要的字段有四个,实际分为两组。
一组是用来记录当前扫描的位置信息 - start, pos。 start指的是当前token起如的位置,如action block起始符{{,这里start指的就是第一个{符号的字节数组下标;而pos则是当前正在扫描的字符。
另一组是用来记录行数的信息 - startLine, line。 和上面类似,一个用于记录起始行,另一个用于记录当前行。
有了这两组信息,就可以精确地通过下标获取到具体的字节信息。
让我们来看看Hugo是怎么通过这些字段,将tokens一个个的精准识别出来的。
解析开始后,先进入到lexText处理函数,第一件做的事就是查找leftDelim - {{。 之所以这样设计,是因为在文本的眼里,她只关心哪些是文本,哪些不是。 而leftDelim的出现,意味着在当前字符串中不全是文本。
我们的例子正是这样,有文本,还有非文本片断。 通过识别,发现存在leftDelim,起始位置是32。 当前位置是0,因为刚开始扫描。 如果当前扫描位置大于起始位置,意味着在这一段中都是文本信息。 紧接着通过扫描换行符数量来判断,当前的文本行数信息。 示例中有一个换行符,意味接下来的扫描起始于第二行。
到目前为止,文本token的所有信息已经收集完毕。 可以正式提交第一个token了。 如左下方所示,当前text token中包含了起始位置信息pos,值val和行数信息line。 分别是起始位置0, 值从0到32,和行1。
在后续扫描的起始信息中,包含起始位置32,和起始行2。
进入到leftDelim token。 开始查看其中是否包含了注解,在我们的示例中没有注解。 所以直接可以进入到action主体,并扫描。
在这里,有一个中间状态lexInsideAction,会识别在action内部可能出现的token,起到一个分发器的作用。
判断的标准是基于next函数,也就是下一个字符。 我们的下一个字符是空格,会被isSpace捕获到,那分发器就会马上创建一个空格token,由右边的lexSpace函数继续处理,会合并多个空格。 在我们的示例中会出现两个space token,因此会由此函数提交两个space tokens。
再回到分发器。 当捕获到的下一个字符是dot - .,会被函数isDot进行处理。 由右下角的lexField函数进行具体处理。 Hugo支持的field格式是字母和数字,所以会先识别出field的名字,并提交field token。
最终,我们来到rightDelim - }},也就是action block的终止符。 同样,我们也需要将它作为一个token进行提交,如中下方所示。
这时我们就来到了这段字符的结尾了,也就是EOF - end of file处,分析器也会将这它作为一个独立的token进行提交。
这样,Hugo的action block词法分析器将分析流程分为不同的阶段。 由对应的处理函数专注于某一相应状态下,对特定字符进行具体分析,各司其职。 并将token的逻辑规则构建在函数转换的过程中,自然的从一个状态切换到另一个状态。 可以说分工明确,简单有效。