我想说更简单的方法是像编译器一样处理这个问题:
标记化是关于识别“单词”,对于您的示例,这给出了:
"Select", "columnname1", ",", "columnanme2", "from", "table"
解析是将这个标记列表解释为抽象语法树。鉴于您的要求:
- 第一个标记应该是“选择”(不区分大小写)
- 后跟“*”或逗号分隔的列名列表
- “from”(不区分大小写)标记列表的结尾
- 后跟表名
我将在 Python 中对此进行概述
class Select:
def __init__(self):
self.columnList = None
self.tableName = None
def Tokenize(statement):
result = []
for word in statement.split(" "):
sub = word.split(",") # the comma may not be separated by space
for i in range(len(sub)-1):
result.append(sub[i].lower()) # case insensitive
result.append(",")
result.append(sub[-1])
return result
def CreateSelect(tokens):
if len(tokens) == 0: raise NoToken()
if tokens[0] != "select": raise NotASelectStatement()
select = Select()
i = 1
while tokens[i] != "from":
if tokens[i] == "*":
select.columnList == ALL
break
else:
select.columnList.append(tokens[i])
i = i + 1
if tokens[i] != ",": raise ExpectedComma(i)
i = i + 1
select.tableName = tokens[i+1]
当然,如您所知,这是一个有限的例子:
- 不考虑多个表
- 不考虑别名
- 不考虑嵌套的 select 语句
但是它工作得很好而且效率很高。通常,如果我们将标记化和解析阶段结合起来,效率会更高,但我发现总的来说,它只会使代码更难阅读。
还请注意,为了正确报告错误,它会更好:
- 不更改标记 (
.lower()),而只是使用不区分大小写的比较
- 将正确的源位置连接到每个标记(行/列),以便能够将用户指向正确的位置
编辑:
一个不那么做作的例子的 AST 会是什么样子?
"select foo, bar from FooBar where category = 'example' and id < 500;"
我们走吧:
Select
|_ Columns (foo, bar)
|_ Table (FooBar)
\_ Where
\_ And
|_ Equal (category, 'example')
\_ LessThan (id, 500)
这里你有一个树状结构,这就是你想要生成的。