Я бы сказал, что проще всего разобраться с этим, как если бы компилятор:
- Tokenization
- Парсинг (создание AST)
Токенизация - это идентификация «слов», для вашего примера это дает:
"Select", "columnname1", ",", "columnanme2", "from", "table"
Парсинг - это интерпретация этого списка токенов в абстрактном синтаксическом дереве.Учитывая ваши требования:
- Первый токен должен быть "select" (без учета регистра)
- За ним может следовать либо "*", либо список имен столбцов через запятую
- "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]
Конечно, как вы понимаете, это ограниченный пример:
- Он не учитывает несколько таблиц
- Он не учитывает псевдонимы вaccount
- Он не учитывает вложенные операторы 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)
Здесь у вас есть древовидная структура, которую вы хотите создать.