Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Внутри CPython гид по интерпретатору Python.pdf
Скачиваний:
6
Добавлен:
07.04.2024
Размер:
8.59 Mб
Скачать

104    Лексический анализ и парсинг с использованием синтаксических деревьев

в Parser Python.asdl, а доступ к ним предоставляется через модуль ast в стандартной библиотеке.

Если у выражения или команды есть дочерние узлы, то соответствующая дочерняя функция ast_for_*() будет вызываться в порядке обхода в глубину.

ВАЖНЫЕ ТЕРМИНЫ

Ниже перечислены некоторые ключевые термины, встретившиеся в этой главе:

zz Абстрактное синтаксическое дерево (AST): представление грамматики и команд Python в виде контекстного дерева.

zz Конкретное синтаксическое дерево (CST): представление лексем и символических имен в виде неконтекстного дерева.

zz Дерево разбора (parse tree): другой термин для обозначения конкретного синтаксического дерева.

zz Лексема (token): тип символического имени (например, +).

zz Токенизация (tokenization): процесс преобразования текста в лексемы.

zz Парсинг (parsing): процесс преобразования текста в CST или AST.

ПРИМЕР: ДОБАВЛЕНИЕ ОПЕРАТОРА «ПОЧТИ РАВНО»

Чтобы объединить все сказанное, мы добавим в язык Python новый элемент синтаксиса и перекомпилируем CPython, чтобы он этот синтаксис поддерживал.

Оператор сравнения сравнивает два и более значений:

>>>a = 1

>>>b = 2

>>>a == b False

Книги для программистов: https://t.me/booksforits

Пример: добавление оператора «почти равно»    105

Операторы, используемые в выражениях сравнения, называются опера­ торами сравнения. Вероятно, вам знакомы следующие операторы сравнения:

zz Меньше: < zz Больше: > zz Равно: ==

zz Не равно: !=

СМ. ТАКЖЕ

Расширенные сравнения в модели данных были предложены для Python 2.1 в PEP 207. PEP содержит контекст, историю и обоснование для реализации методов сравнения в нестандартных типах Python.

Добавим еще один оператор сравнения «почти равно», который будет представляться символами ~=. Оператор будет обладать следующим поведением:

zz Число с плавающей точкой при сравнении с целым числом сначала будет приводиться к целочисленному типу.

zz При сравнении двух целых чисел будут использоваться обычные операторы проверки равенства.

Новый оператор должен возвращать следующий результат в REPL:

>>>1 ~= 1

True

>>>1 ~= 1.0

True

>>>1 ~= 1.01

True

>>>1 ~= 1.9 False

Чтобы добавить новый оператор, необходимо сначала обновить грамматику CPython. В файле Grammar python.gram операторы сравнения определяются символическим именем comp_op:

Книги для программистов: https://t.me/booksforits

106    Лексический анализ и парсинг с использованием синтаксических деревьев

comparison[expr_ty]:

| a=bitwise_or b=compare_op_bitwise_or_pair+ ...

| bitwise_or

compare_op_bitwise_or_pair[CmpopExprPair*]:

| eq_bitwise_or

| noteq_bitwise_or

| lte_bitwise_or

| lt_bitwise_or

| gte_bitwise_or

| gt_bitwise_or

| notin_bitwise_or

| in_bitwise_or

| isnot_bitwise_or

| is_bitwise_or

eq_bitwise_or[CmpopExprPair*]: '==' a=bitwise_or ...

noteq_bitwise_or[CmpopExprPair*]:

| (tok='!=' {_PyPegen_check_barry_as_flufl(p) ? NULL : tok}) ...

lte_bitwise_or[CmpopExprPair*]: '<=' a=bitwise_or ...

lt_bitwise_or[CmpopExprPair*]: '<' a=bitwise_or ...

gte_bitwise_or[CmpopExprPair*]: '>=' a=bitwise_or ...

gt_bitwise_or[CmpopExprPair*]: '>' a=bitwise_or ...

notin_bitwise_or[CmpopExprPair*]: 'not' 'in' a=bitwise_or ...

in_bitwise_or[CmpopExprPair*]: 'in' a=bitwise_or ...

isnot_bitwise_or[CmpopExprPair*]: 'is' 'not' a=bitwise_or ...

is_bitwise_or[CmpopExprPair*]: 'is' a=bitwise_or ...

Измените выражение compare_op_bitwise_or_pair, чтобы оно также допускало новую пару ale_bitwise_or:

compare_op_bitwise_or_pair[CmpopExprPair*]: | eq_bitwise_or

...

| ale_bitwise_or

Определите новое выражение ale_bitwise_or под существующим выражением is_bitwise_or:

...

is_bitwise_or[CmpopExprPair*]: 'is' a=bitwise_or ...

ale_bitwise_or[CmpopExprPair*]: '~=' a=bitwise_or

{ _PyPegen_cmpop_expr_pair(p, AlE, a) }

Новый тип определяет именованное выражение ale_bitwise_or, которое содержит терминал '~='.

Книги для программистов: https://t.me/booksforits

Пример: добавление оператора «почти равно»    107

Вызов функции _PyPegen_cmpop_expr_pair(p, AlE, a) является выражением для получения узла cmpop из AST. Название типа AlE происходит от слов «almost equal» («почти равно»).

Затем добавьте лексему в Grammar Tokens:

ATEQUAL

'@='

RARROW

'->'

ELLIPSIS

'...'

COLONEQUAL

':='

# Добавьте эту строку

 

ALMOSTEQUAL

'~='

Чтобы обновить грамматику и лексемы в C, необходимо заново сгенерировать заголовки.

В macOS и Linux используется следующая команда:

$ make regen-token regen-pegen

ВWindows выполните следующую команду из каталога PCBuild:

>build.bat --regen

Эти действия автоматически обновляют токенизатор. Например, откройте файл Parser/token.c и посмотрите, как изменилась секция case в функции

PyToken_TwoChars():

case '~':

switch (c2) {

case '=': return ALMOSTEQUAL;

}

break;

}

Если перекомпилировать CPython на этом этапе и открыть REPL, вы увидите, что токенизатор успешно распознает лексему, но AST не знает, как обработать ее:

$ ./python

>>> 1 ~= 2

SystemError: invalid comp_op: ~=

Книги для программистов: https://t.me/booksforits

108    Лексический анализ и парсинг с использованием синтаксических деревьев

Исключение поднимается функцией ast_for_comp_op() из файла Python ast.c, потому что ALMOSTEQUAL не распознается как допустимый оператор для коман­ ды сравнения.

Compare — тип выражения, определенный в Parser Python.asdl. Он содержит свойства для левого выражения, список операторов ops и список сравниваемых выражений comparators:

| Compare(expr left, cmpop* ops, expr* comparators)

Внутри определения Compare находится ссылка на перечисление cmpop:

cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn

Это список возможных листовых узлов AST, которые могут использоваться как операторы сравнения. Наш оператор в списке отсутствует, его необходимо добавить. Включите в этот список новый тип AlE:

cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn | AlE

Затем снова сгенерируйте AST, чтобы обновить заголовочные файлы C AST:

$ make regen-ast

Команда обновляет список операторов сравнения (_cmpop) в файле Include/ Python-ast.h и включает в него вариант AlE:

typedef enum _cmpop { Eq=1, NotEq=2, Lt=3, LtE=4, Gt=5, GtE=6, Is=7,

IsNot=8, In=9, NotIn=10, AlE=11 } cmpop_ty;

AST не знает, что лексема ALMOSTEQUAL эквивалентна оператору сравнения AlE. А значит, необходимо обновить код C для AST.

Перейдите к функции ast_for_comp_op() в Python ast.c. Найдите команду switch для лексем операторов. Она возвращает одно из списка значений _cmpop.

Добавьте две строки для обнаружения лексемы ALMOSTEQUAL и возвращения оператора сравнения AlE:

Python ast.c, строка 1222

static cmpop_ty

ast_for_comp_op(struct compiling *c, const node *n)

{

/* comp_op: '<'|'>'|'=='|'>='|'<='|'!='|'in'|'not' 'in'|'is' |'is' 'not'

Книги для программистов: https://t.me/booksforits