категории | RSS

Ecли в Baшeм пpилoжeнии вдpyг пoнaдoбитcя cкaнep и пapcep (к пpимepy, Bы пишитe интepпpeтaтop кaкoгo тo выcoкoypoвнeвoгo языкa, или вooбщe, цeлый кoмпилятopsmile ), тo пepвoe, чтo пpийдeт Baм в гoлoвy пocлe изyчeния cyти вoпpoca - мeтoд peкypcивнoгo cпycкa. Этoт мeтoд, дeйcтвитeльнo, oтнocитeльнo лeгкий к peaлизaции, и мы бы имeннo им и вocпoльзoвaлиcь, ecли бы пиcaли нa кaкoм нибyдь Пacкaлe. Ho, тaк кaк мы пoльзyeмcя Питoнoм, ecть бoлee пpocтoй cпocoб - мoдyль spark.

Moдyль пpeдcтaвляeт в нaшe pacпopяжeниe нecкoлькo клaccoв - для лeкcичecкoгo и cинтaкcичecкoгo aнaлизa (GenericScanner, GenericParser), для построения дерева синтаксического разбора (GenericASTBuilder), для обхода этого дерева (GenericASTTraversal), и для поиска узлов в синтаксическом дереве (GenericASTMatcher).
Автор модуля (John Aycock) предлагает для обработки данных использовать четыре прохода - лексический анализ, синтаксический анализ, семантический анализ и генерация кода. Но, по моему мнению, это излишне. Притом что модуль этот оперативу жрет безбожно, так и еще четыре прохода на наших смартах - смерти подобно. Поэтому мы изберем другой подход - на первом проходе лексический анализ с помощью GenericScanner, на втором - все остальное на основе GenericParser.
В данной статье мы будем разрабатывать интерпретатор целочисленных математических выражений. Конечно, это не очень впечатляюще, но достаточно просто в реализации, и поможет понять основные принципы работы с модулем.
И так, поехали! Что у нас там по списку - лексический анализ? Ну тогда начнем с него.
Суть лексического анализа в разборе входящего текста на последовательность элементарных составляющих - токенов. Это может быть оператор (к примеру + или -), идентификатор (имя переменной, метода, класса и т.д.) разделитель (запятая, двуеточие...) и прочие.
В этом деле нам поможет класс GenericScanner. Для начала нужно наследовать наш сканер от этого класса:


from spark import GenericScanner

class Scanner(GenericScanner):

def __init__(self):
# Инициализировать нужно обязательно!
GenericScanner.__init__(self)
# Создаем список, в котором будут сохранятся токены
self.tokens = []

Да, чуть не забыл - токены будут сохранятся в виде простого класса-контейнера. Вот его код:


class Token:

def __init__(self, type, value):
""" type - тип токена (оператор, идентификатор и т.д.)
value - его значение
"""
self.type = type
self.value = value

""" Нужно реализовать еще пару методов для сравнения токенов и для строкового представления
"""

def __eq__(self, other):
return self.type == other

def __ne__(self, other):
return not self == other

def __repr__(self):
return '%s(%r)' %(self.type, self.value)


Возвращаемся к нашему сканеру. Далее нужно создавать методы, в которых будут обрабатываться токены. Эти методы должны удовлетворять некоторым условиям - во первых, имя метода должно начинаться с "t_", во вторых, метод должен принимать один аргумент - собственно, токен. И в третьих - первая строка метода должна быть строкой документации, которая содержит в себе регулярное выражение с описанием данного токена. Напишем метод для распознавания целых чисел:

def t_integer(self, token):
r'\d+' # Только целые десятичные числа
# Добавляем токен в список
self.tokens.append(Token('INTEGER', int(token)

Вот и все! Просто, не так ли?smile
Далее методы для распознавания операторов:

def t_operator(self, token):
r'[\+\-\*\/]' # только плюс, минус, умножение и деление
self.tokens.append(Token('OPERATOR', token))

Теперь нужен метод для обработки пробелов:

def t_space(self, token):
r'\s+' # Для любых символов пропуска
# Пропускаем ихsmile
pass

И, наконец, желательно перегрузить метод t_default - он вызывается в случае, если встреченный символ не описывается никаким предыдущим методом:

def t_default(self, token):
r'[\s\S]+' # Для любых символов
# Синтаксическая ошибка!
raise SyntaxError, str(token)

Наш простой сканер готов. И не так много усилий мы затратили на его создание. Пользоваться им можно следующим образом:

scanner = Scanner()
scanner.tokenize(data)
tokens = scanner.tokens

Где data - сканируемый текст.

Итак, мы имеем сканнер, который обрабатывает текст, и на выходе выдает поток токенов (или ошибку в случае встречи незнакомого символа)
На этом данная статья заканчивается, а в следующей я напишу как пользоваться классом GenericParser для создание парсеров, и мы допишем свой интерпретатор.
До скорой встречи!

Pythoner
2011-01-11T15:54:01Z

Здесь находятся
всего 0. За сутки здесь было 0 человек

Комментарии 13

#3   Pythoner    

Кому то да пригодится. Я много кому эти вещи объяснял в личку, поэтому и решился на статью. В свое время мне пришлось много литературы полистать, чтоб узнать как будет проще организовать эти вещи на питоне. А здесь все готовоеsmile


0 ответить

#3   Angel-iz-Ada    

Да, такими статьями смотрю не многие интересуются)) Это больше для профессионалов рассчитано по Питону, коих тут не большое количество)


0 ответить

Яндекс.Метрика