从 0 到 1 搭建技术中台之报警平台实践:匹配器演进( 四 )

  • 大小写无关的字符串 “AND” 返回类型 AND;“OR” 返回类型 OR
  • “=~”、"="、"("、")" 直接返回相应的数据类型
  • 正则表达式 /[A-Za-z][A-Za-z0-9_]*/ 匹配的是原始表达式中的 LABEL
  • 正则表达式 /".*"/ 匹配的是原始表达式中的 VALUE
  • 正则表达式 /[ trn]+/ 匹配的是空格字符,即忽略所有类型的空格
最后使用 nex 和 goyacc 就可以生成词法分析器和解析器:
?复制代码
$ nex nex.l$ goyacc -o parser.go parser.y然后再把二者串起来即可:
?复制代码
// 忽略细节处理func Compile(ctx context.Context, in io.Reader) (m *Matcher, err error) { lr := NewLexer(in) yyParse(lr)if lr.parseResult == nil {err = SyntaxErrorreturn }m = lr.parseResult.(*Matcher) return}Rob Pike Style Lexer完成上面的工作,本可以告一段落,但有一个问题还困扰着我们:”为什么 Go 只推出了 yacc 的移植版本,而不顺便推出 lex 的移植版本?“ 几经周折找到了 Rob Pike 2011 年的一次演讲: “Lexical Scanning in Go” 。在演讲中他认为 ” lex 生成的代码太多,过于复杂,用 Go 语言实现一个并非难事,且 Go 的 channel 能方便地实现 lex 和 yacc 的流水线协作 。“ 尽管这种观点也是在为 Go 站台,我们还是决定试一试他提出的 lexical scanning 方案 。
词法分析的过程,就是从输入字符流起点扫描至终点的线性过程,在扫描期间,词法分析器需要正确地判断自己所处的状态,以起点为例,刚开始扫描,可能进入 LABEL 状态,也可能进入 ( 状态:
?复制代码
【从 0 到 1 搭建技术中台之报警平台实践:匹配器演进】labela = "a" AND (labelb = "b" OR labelc = "c")↑在 LABEL 中(labela = "a") OR labelb = "b"↑在'('中扫描完 VALUE 后,可能进入结束状态,也可能进入 ) 状态或 关系运算符 状态:
?复制代码
labela = "a" AND (labelb = "b" OR labelc = "c")↑进入 [关系运算符] 状态(labela = "a")↑进入 ')' 状态labela = "a"↑进入 [结束] 状态不难看出,这实际上就是一个状态机,详细的状态转移过程如下图所示:
?复制代码
# start: [开始]; leftParen: '('; label: [标签]; eq: [匹配符]; value: [文本];# rightParen: ')'; binaryOp: [关系运算符]; end: [结束]+------------+| rightParen | -------------++------------+|^|||||+----------------------+|----------------+||v|v||+-----------++-------++----++------------++-----+|||start| --> | label | --> | eq | --> |value| --> | end |||+-----------++-------++----++------------++-----+|||^||||||||v|v||+-----------+|+------------+|+- | leftParen |+--------------------- |binaryOp| <------------++-----------++------------+^|+------------------------------------------+接下来就需要让这个状态机动起来:
?复制代码
type lexer struct {namestring// used only for error reports.input string// the string being scanned.start int// start position of this item.posint// current position in the input.width int// width of last rune read from input.items chan item // channel of scanned items.} // stateFn represents the state of the scanner// as a function that returns the next state.type stateFn func(*lexer) stateFn func (l *lexer) run() {for state := lexStart; state != nil; {state = state(l)}close(l.items)}其中 stateFn 就是状态转移方程,约定当 stateFn == nil 时,状态机停止,即 nil 就是结束状态的转移方程 。接下来只需要定义各个状态转移方程即可:
?复制代码
func lexStart(l *lexer) stateFn {}func lexLabel(l *lexer) stateFn {}func lexLeftParen(l *lexer) stateFn {}func lexRightParen(l *lexer) stateFn {}func lexEq(l *lexer) stateFn {}func lexValue(l *lexer) stateFn {}func lexBinaryOp(l *lexer) stateFn {}每当状态即将转移时,stateFn 内部就会将在本状态中扫描到的词语传给 item channel,这个 channel 就是 lexer 与 parser 之间通信的媒介 。
值得一提的是,Go 的模板引擎 template,就是按照上述方式构建的,感兴趣可以阅读源码 。
 




推荐阅读