背景
在一个复杂的文章搜索匹配的需求里,匹配规则已经实现,但是原有的规则写法过于复杂,需要进行简化,例如原规则:
代码语言:javascript复制("小鹏" >= 1) and ("P7" >= 1)
这个规则的意思实际上是:小鹏这个关键词的出现次数大于等于1,P7这个关键词出现次数也大于等于1。
但是这个语法显然很罗嗦,客户要求进行简化。客户希望可以简化成这样:
代码语言:javascript复制小鹏 and P7
这是客户的习惯,实际上参考搜索引擎的查询语法是可以更加简洁的“ 小鹏 P7”,不过这暂时不再考虑范围。
上面这个只是一个简化的示例,实际客户写的匹配规则是可能很复杂的。
使用lex进行解释
同事们好像觉得这个功能实现很难,没什么信心,其实只要理解其中的逻辑,并不复杂,就算不借助工具也能实现,单单用正则和循环也能解决。不过,使用神器lex显然是更好的解决方案(lex经常和yacc搭配使用,不过我们的需求比较简单,并不需要用到yacc)。
下面是一个简单的示例:
代码语言:javascript复制import ply.lex as lex
# List of token names.
tokens = (
'KEYWORD', # 关键词
'LPAREN', # 左括号
'RPAREN', # 右括号
'LOGIT', # 逻辑操作
'CMP', # 比较操作
)
# 分组
t_LPAREN = r'('
t_RPAREN = r')'
# 忽略空格及tab
t_ignore = ' t'
# 关键词
def t_KEYWORD(t):
# 双引号内的,或者不非空格组成的字符串(不含括号)
r'("[^"] ")|([^s()] )'
val_lower = t.value.lower()
if val_lower in {'and', 'or', 'not'}:
t.type = 'LOGIT'
return t
if val_lower in {'>=', '>', '==', '<=', '<'}:
t.type = 'CMP'
return t
if t.value[0] == '"':
t.value = t.value[1:-1] # 关键词统一去掉双引号
return t
# Error handling rule
def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)
# Build the lexer
lexer = lex.lex()
测试代码:
代码语言:javascript复制def parse_tokens(data):
print('')
lexer.input(data)
while True:
tok = lexer.token()
if not tok:
break
print(tok)
data = '小鹏 and P7'
parse_tokens(data)
# 带双引号的测试
data = '小鹏 and "P7 价格"'
parse_tokens(data)
# 带括号及比较操作的测试
data = '(小鹏 >= 2) and "P7 价格"'
parse_tokens(data)
测试对应的输出:
代码语言:javascript复制LexToken(KEYWORD,'小鹏',1,0)
LexToken(LOGIT,'and',1,3)
LexToken(KEYWORD,'P7',1,7)
LexToken(KEYWORD,'小鹏',1,0)
LexToken(LOGIT,'and',1,4)
LexToken(KEYWORD,'P7 价格',1,8)
LexToken(LPAREN,'(',1,0)
LexToken(KEYWORD,'小鹏',1,1)
LexToken(CMP,'>=',1,4)
LexToken(KEYWORD,'2',1,7)
LexToken(RPAREN,')',1,8)
LexToken(LOGIT,'and',1,10)
LexToken(KEYWORD,'P7 价格',1,14)
有了这个结果,要处理成完整的表达式已经是很简单了。
lex与yacc
有了这两个神器,想实现一门简单的语言也是不难的。而且,理解了这两个工具,非常有助于理解编程语言本身,可谓大有益处。
程序员还是要保持好奇心。
备注:
ply是Python Lex Yacc的缩写,官方文档:http://www.dabeaz.com/ply/ply.html