Skip to content

Latest commit

 

History

History
138 lines (115 loc) · 9.3 KB

mbe-syn-source-analysis.md

File metadata and controls

138 lines (115 loc) · 9.3 KB

% Анализ исходного кода

Первым этапом компиляции программ на Rust является токенизация. На этом этапе исходный текст преобразуется в набор токенов (т.е. неразделимых лексических единиц; эквиваленты "словам" на программном языке). Rust поддерживает различные типы токенов. Среди них:

  • Идентификаторы: foo, Bambous, self, we_can_dance, LaCaravane, …
  • Целые числа: 42, 72u32, 0_______0, …
  • Ключевые слова: _, fn, self, match, yield, macro, …
  • Время жизни: 'a, 'b, 'a_rare_long_lifetime_name, …
  • Строки: "", "Leicester", r##"venezuelan beaver"##, …
  • Символы: [, :, ::, ->, @, <-, …

Необходимо выделить из приведенного списка следующее: во-первых, self является как идентификатором, так и ключевым словом. Почти во всех случаях self - это ключевое слово, но оно может также трактоваться как идентификатор, который придет позже (вместе с проклятьями). Во-вторых, в список ключевых слов входят разные подозрительные фразы, такие как yield и macro, которые на самом деле не входят в язык, однако разбираются компилятором - они зарезервированы на будущее. В-третьих, список символов также включает элементы, которые не используются языком. Если взять <-, то это рудимент: он был удален из грамматики, но не из словаря. Наконец, помните, что :: - это особый токен; это не просто два токена :. То же самое справедливо для всех составных символьных токенов в Rust, начиная с Rust 1.2. 1

На этом этапе у некоторых других языков есть макро уровень, у Rust нет. Например, макросы C/C++ на самом деле выполняются на этом этапе. 2 Вот почему работает следующий код: 3

#define SUB void
#define BEGIN {
#define END }

SUB main() BEGIN
    printf("Ох, страх и ненависть!\n");
END

Следующий этап - разбор, в котором поток токенов превращается в абстрактное синтаксическое дерево (AST). Здесь строится синтаксическая структура программы в памяти. Например, сочетание токенов 1 + 2 преобразуется соответственно в:

┌─────────┐   ┌─────────┐
│ BinOp   │ ┌╴│ LitInt  │
│ op: Add │ │ │ val: 1  │
│ lhs: ◌  │╶┘ └─────────┘
│ rhs: ◌  │╶┐ ┌─────────┐
└─────────┘ └╴│ LitInt  │
              │ val: 2  │
              └─────────┘

AST содержит структуру всей программы, хотя основывается она исключительно на лексической информации. Например, компилятор на этом этапе может знать, что часть выражения относится к переменной "a", хотя он понятия не имеет, что такое "a", или откуда ее взять.

После того как AST было сконструировано, обрабатываются макросы. Однако, прежде чем мы это обсудим, поговорим о деревьях токенов.

Деревья токенов

Деревья токенов - это нечто среднее между токенами и AST. Для начала надо знать, что почти все токены являются деревьями токенов; говоря конкретней, они являются листьями. Листом дерева токенов может быть еще одна вещь, вернемся к ней позже.

Единственные базовые токены, которые не являются листьями, это токены "группировки": (...), [...] и {...}. Эти трое - внутренние узлы деревьев токенов, или то, что и конструирует структуру дерева. Рассмотрим конкретный пример,в котором эта связка токенов:

a + b + (c + d[0]) + e

будет разобрана в следующие деревья токенов:

«a» «+» «b» «+» «(   )» «+» «e»
          ╭────────┴──────────╮
           «c» «+» «d» «[   ]»
                        ╭─┴─╮
                         «0»

Заметьте, что это не имеет никакого отношения к выражениям, производимым AST; вместо одного корневого узла, здесь девять деревьев токенов на корневом уровне. Для справки, AST будет следующим:

              ┌─────────┐
              │ BinOp   │
              │ op: Add │
            ┌╴│ lhs: ◌  │
┌─────────┐ │ │ rhs: ◌  │╶┐ ┌─────────┐
│ Var     │╶┘ └─────────┘ └╴│ BinOp   │
│ name: a │                 │ op: Add │
└─────────┘               ┌╴│ lhs: ◌  │
              ┌─────────┐ │ │ rhs: ◌  │╶┐ ┌─────────┐
              │ Var     │╶┘ └─────────┘ └╴│ BinOp   │
              │ name: b │                 │ op: Add │
              └─────────┘               ┌╴│ lhs: ◌  │
                            ┌─────────┐ │ │ rhs: ◌  │╶┐ ┌─────────┐
                            │ BinOp   │╶┘ └─────────┘ └╴│ Var     │
                            │ op: Add │                 │ name: e │
                          ┌╴│ lhs: ◌  │                 └─────────┘
              ┌─────────┐ │ │ rhs: ◌  │╶┐ ┌─────────┐
              │ Var     │╶┘ └─────────┘ └╴│ Index   │
              │ name: c │               ┌╴│ arr: ◌  │
              └─────────┘   ┌─────────┐ │ │ ind: ◌  │╶┐ ┌─────────┐
                            │ Var     │╶┘ └─────────┘ └╴│ LitInt  │
                            │ name: d │                 │ val: 0  │
                            └─────────┘                 └─────────┘

Важно понимать различие между AST и деревьями токенов. При написании макросов вам придется иметь дело с обеими вещами по отдельности.

Другой важный аспект: нельзя использовать непарную круглую, фигурную или квадратную скобку; а также нельзя использовать неправильно вложенные группы в дереве токенов.

Footnotes

  1. у @ есть назначение, о котором большинство людей забывают: он используется в паттернах для того, чтобы связать нетерминальную часть паттерна с именем. Даже член команды ядра Rust - тот, кто конкретно разрабатывал этот раздел, читая его перед одобрением, не вспомнил, что у @ есть этот смысл. Позор, какой позор.

  2. На самом деле препроцессор Си использует другие лексические структуры по отношению к самому Си, хотя различия очень незначительны.

  3. Будет ли это работать - отдельный вопрос.