% Анализ исходного кода
Первым этапом компиляции программ на 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
-
у
@
есть назначение, о котором большинство людей забывают: он используется в паттернах для того, чтобы связать нетерминальную часть паттерна с именем. Даже член команды ядра Rust - тот, кто конкретно разрабатывал этот раздел, читая его перед одобрением, не вспомнил, что у@
есть этот смысл. Позор, какой позор. ↩ -
На самом деле препроцессор Си использует другие лексические структуры по отношению к самому Си, хотя различия очень незначительны. ↩
-
Будет ли это работать - отдельный вопрос. ↩