Documentation (#3211)
* Add Compiler documentation * Add Intellisense documentation * Move documentation folder to root directory
This commit is contained in:
parent
79f9ab73a0
commit
271d5792ce
|
|
@ -0,0 +1,163 @@
|
|||
|
||||
Owner: Alexander Zemlyak
|
||||
Tags: Infrastructure
|
||||
|
||||
### Алгоритм на псевдокоде
|
||||
|
||||
- Полная версия
|
||||
|
||||
```
|
||||
CompileUnit(ИмяФайла)
|
||||
1.CompileUnit(new СписокМодулей, ИмяФайла)
|
||||
2.Докомпилировать модули из СписокОтложенойКомпиляции;
|
||||
|
||||
CompileUnit(СписокМодулей, ИмяФайла)
|
||||
1.ТекущийМодуль = ТаблицаМодулей[ИмяФайла];
|
||||
Если (ТекущийМодуль != 0) то
|
||||
Если (ТекущийМодуль.Состояние != BeginCompilation)
|
||||
СписокМодулей.Добавить(ТекущийМодуль);
|
||||
Выход;
|
||||
иначе перейти к пункту 5
|
||||
|
||||
2.Если ЭтоФайлDLL(ИмяФайла) то
|
||||
Если ((ТекущийМодуль = СчитатьDLL(ИмяФайла)) != 0) то
|
||||
СписокМодулей.Добавить(ТекущийМодуль);
|
||||
ТаблицаМодулей.Добавить(ТекущийМодуль);
|
||||
Выход;
|
||||
иначе
|
||||
Ошибка("Не могу подключить сборку");
|
||||
Выход;
|
||||
|
||||
3.Если ЭтоФайлPCU(ИмяФайла) то
|
||||
Если ((ТекущийМодуль = СчитатьPCU(ИмяФайла)) != 0) то
|
||||
СписокМодулей.Добавить(ТекущийМодуль);
|
||||
ТаблицаМодулей.Добавить(ТекущийМодуль);
|
||||
Выход;
|
||||
иначе
|
||||
иначе перейти к пункту 4;
|
||||
|
||||
4.ТекущийМодуль = новыйМодуль();
|
||||
ТекущийМодуль.СинтаксическоеДерево = Парасеры.Парсить(ИмяФайла,ТекущийМодуль.СписокОшибок);
|
||||
Если (ТекущийМодуль.СинтаксическоеДерево == 0) то
|
||||
Если (ТекущийМодуль.СписокОшибок.Количество == 0) то
|
||||
Ошибка("Модуль не неайден");
|
||||
иначе
|
||||
Ошибка(ТекущийМодуль.СписокОшибок[0]);
|
||||
ТаблицаМодулей[ИмяФайла] = ТекущийМодуль;
|
||||
ТекущийМодуль.Состояние = BeginCompilation;
|
||||
|
||||
5.СинтаксическийСписокМодулей = ТекущийМодуль.СинтаксическоеДерево.Interface.usesList;
|
||||
Для(i = СинтаксическийСписокМодулей.Количество - 1 - ТекущийМодуль.КомпилированыеВInterface.Количество; i >= 0; i--)
|
||||
ТекушийМодуль.ТекущийUsesМодуль = СинтаксическийСписокМодулей[i].ИмяФайла;
|
||||
ИмяUsesФайла = СинтаксическийСписокМодулей[i].ИмяФайла;
|
||||
Если (ТаблицаМодулей[ИмяUsesФайла] != 0)
|
||||
Если (ТаблицаМодулей[ИмяUsesФайла].Состояние == BeginCompilation)
|
||||
Если (ТаблицаМодулей[ТаблицаМодулей[ИмяUsesФайла].ТекущийUsesМодуль].Состояние=BeginCompilation)
|
||||
Ошибка("Циклическая связь модулей");
|
||||
CompileUnit(ТекущийМодуль.КомпилированыеВInterface, ИмяUsesФайла);
|
||||
Если (ТекушийМодуль.Состояние == Compiled) то
|
||||
СписокМодулей.Добавить(ТекушийМодуль);
|
||||
Выход;
|
||||
|
||||
6.ТекущийМодуль.СемантическоеДерево = КонверторДерева.КонвертироватьInterfaceЧасть(
|
||||
ТекущийМодуль.СинтаксическоеДерево,
|
||||
ТекущийМодуль.КомпилированыеВInterface,
|
||||
ТекущийМодуль.СписокОшибок);
|
||||
СписокМодулей.Добавить(ТекущийМодуль);
|
||||
СинтаксическийСписокМодулей = ТекущийМодуль.СинтаксическоеДерево.Implementation.usesList;
|
||||
Для(i = СинтаксическийСписокМодулей.Количество - 1; i >= 0; i--)
|
||||
Если (ТаблицаМодулей[СинтаксическийСписокМодулей[i].ИмяФайла].Состояние == BeginCompilation)
|
||||
СписокОтложенойКомпиляции.Добавить(ТаблицаМодулей[СинтаксическийСписокМодулей[i].ИмяФайла]);
|
||||
иначе
|
||||
CompileUnit(ТекущийМодуль.КомпилированыеВImplementation, СинтаксическийСписокМодулей[i].ИмяФайла);
|
||||
Если(ДобавлялиХотябыОдинВСписокОтложенойКомпиляции)
|
||||
СписокОтложенойКомпиляции.Добавить(ТекущийМодуль);
|
||||
выход;
|
||||
иначе
|
||||
КонверторДерева.КонвертироватьImplementationЧасть(
|
||||
ТекущийМодуль.СинтаксическоеДерево,
|
||||
ТекущийМодуль.СемантическоеДерево,
|
||||
ТекущийМодуль.КомпилированыеВImplementation
|
||||
ТекущийМодуль.СписокОшибок);
|
||||
ТекущийМодуль.Состояние = Compiled;
|
||||
СохранитьPCU(ТекущийМодуль);
|
||||
```
|
||||
|
||||
- Краткая версия (меньше технических подробностей)
|
||||
|
||||
```
|
||||
CompileUnit(ИмяФайла)
|
||||
1.CompileUnit(new СписокМодулей, ИмяФайла)
|
||||
2.Докомпилировать модули из СписокОтложенойКомпиляции;
|
||||
|
||||
CompileUnit(СписокМодулей, ИмяФайла);
|
||||
1.Если у этого модуля откомпилирован хотябы интерфейс то
|
||||
добавить его в СписокМодулей
|
||||
выход
|
||||
2.Если это DLL то
|
||||
считать
|
||||
добавить его в СписокМодулей
|
||||
выход
|
||||
3.Если это PCU то
|
||||
считать
|
||||
добавить его в СписокМодулей
|
||||
выход
|
||||
4.создать новый компилируемыйМодуль
|
||||
РаспарситьТекст(ИмяФайла)
|
||||
Состояние компилируемогоМодуля установить на BeginCompilation
|
||||
5.Для всех модулей из Interface части компилируемогоМодуля справа налево
|
||||
Если мы уже начаинали компилировать этот модуль
|
||||
Если состояние модуля BeginCompilation
|
||||
Если состояние последнего компилируемого им модуля BeginCompilation
|
||||
ошибка("Циклическая связь модулей")
|
||||
выход
|
||||
CompileUnit(Список из Interface части компилируемогоМодуля,модуль.имя)
|
||||
Если компилируемыйМодуль.Состояние Compiled то
|
||||
добавить его в СписокМодулей
|
||||
выход
|
||||
6.Откомпилировать Interface часть компилируемогоМодуля
|
||||
Для всех модулей из Implementation части компилируемогоМодуля справа налево
|
||||
Если состояние очередного модуля BeginCompilation то
|
||||
добавить его в список отложеной компиляции;
|
||||
иначе
|
||||
CompileUnit(Список из Implementation части компилируемогоМодуля,модуль.имя)
|
||||
Если Добавляли Хотя бы Один В Список Отложеной Компиляции то
|
||||
добавить компилируемыйМодуль в список отложеной компиляции
|
||||
выход
|
||||
Откомпилировать Implementation часть компилируемогоМодуля
|
||||
Состояние компилируемогоМодуля установить на Compiled
|
||||
добавить его в СписокМодулей
|
||||
Сохранить компилируемыйМодуль в виде PCU файла на диск
|
||||
```
|
||||
|
||||
|
||||
### Словесное описание алгоритма
|
||||
|
||||
Модули компилируются в порядке, соответствующем порядку **обхода в глубину** графа зависимостей. Обход в глубину реализуется рекурсивными вызовами функции CompileUnit.
|
||||
|
||||
Сначала рекурсивно обходятся все зависимости интерфейсных частей модулей, причем в порядке **обратном** порядку в списке uses. Ясно, что может существовать **циклическая зависимость интерфейсных частей** модулей. Она запрещается (во время компиляции выдается соотв. ошибка). Цикл обнаруживается, если мы встречаем модуль с не откомпилированным интерфейсом, в котором мы уже были.
|
||||
|
||||
<aside>
|
||||
💡 Однако после обнаружения требуется еще проверка на то, что цикл образован целиком из “интерфейсных связей”, поскольку в случае присутствия в нем связи через implementation часть модуля он не представляет опасности благодаря механизму отложенной компиляции, про которую читайте далее.
|
||||
|
||||
</aside>
|
||||
|
||||
**Пример допустимого цикла**
|
||||
![[допустимая циклическая зависимость.png]]
|
||||
---
|
||||
|
||||
Если все зависимости интерфейса скомпилированы, то в том же порядке начинают рекурсивно обходиться зависимости части реализации (implementation). **Отложенная компиляция** происходит сразу после окончания первого обхода всех модулей, когда абсолютно все интерфейсные части модулей уже скомпилированы. Это условие гарантирует отсутствие новых циклических интерфейсных зависимостей. Модули добавляются в список отложенной компиляции в случае, если в некотором модуле в списке uses части реализации есть ранее встреченный модуль с еще не откомпилированным интерфейсом (в список добавляются оба этих модуля).
|
||||
|
||||
<aside>
|
||||
💡 Может возникнуть вопрос, что происходит в случае циклической зависимости частей реализации. В этой ситуации мы в какой-то момент попадем во второй раз в модуль с откомпилированным интерфейсом и проверка, приведенная далее, в начале CompileUnit приведет к выходу из функции. А данный модуль докомпилируется позже в другом рекурсивном вызове.
|
||||
|
||||
</aside>
|
||||
|
||||
```csharp
|
||||
if (currentUnit.State != UnitState.BeginCompilation || currentUnit.SemanticTree != null)
|
||||
{
|
||||
AddCurrentUnitAndItsReferencesToUsesLists(unitsFromUsesSection, directUnitsFromUsesSection,
|
||||
currentUnitNode, currentUnit, GetReferences(currentUnit));
|
||||
return currentUnit;
|
||||
}
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
2412
documentation/Compiler/Compiler_api.md
Normal file
2412
documentation/Compiler/Compiler_api.md
Normal file
File diff suppressed because it is too large
Load diff
22
documentation/Compiler/PABCRtl/PABCRtl dll.md
Normal file
22
documentation/Compiler/PABCRtl/PABCRtl dll.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
Owner: Alexander Zemlyak
|
||||
Tags: Codebase
|
||||
|
||||
Dll-библиотека для ускорения времени выполнения “простых” программ без пользовательских uses (допускается использование пр-в имен и стандартных модулей Паскаля из самой RTL). Для использования требуется включить флажок *“Ускорять запуск из под оболочки”.*
|
||||
|
||||
---
|
||||
|
||||
На картинке можно видеть стандартные модули из библиотеки:
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
> [!NOTE] Особые стандартные модули. 1-я группа
|
||||
> Некоторые стандартные модули не входят в PABCRtl.dll, поскольку она 64-битная, а они используют нативные 32-битные библиотеки. Таковыми являются модули из следующего списка: PT4, CRT, Arrays, MPI, Collections, Core. При использовании их в программе PABCRtl.dll не загружается. *2-я группа* Если стандартные модули не входят в RTL и одновременно не входят в список исключения RTL, о котором говорилось выше, то RTL загружается. Но при этом возникает ошибка компиляции и компиляция запускается заново без использования RTL.
|
||||
|
||||
|
||||
> [!NOTE] Как сделано так, что при явном использовании стандартных модулей из RTL их pcu-файлы не используются?
|
||||
> Стандартные модули в этом случае воспринимаются как пространства имен и даже добавляются в соответствующие списки, такие как `possibleNamespaces` (см. [[Uses lists]]). Метод `CompileUnit` для них вообще не вызывается. Конечно, хотелось бы выделить логику обработки стандартных модулей из rtl в отдельный блок кода.
|
||||
|
||||
|
||||
BIN
documentation/Compiler/PABCRtl/Untitled.png
Normal file
BIN
documentation/Compiler/PABCRtl/Untitled.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
39
documentation/Compiler/Uses lists/Uses lists.md
Normal file
39
documentation/Compiler/Uses lists/Uses lists.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
Owner: Alexander Zemlyak
|
||||
|
||||
- **Получение списков зависимостей из синтаксического дерева юнита**
|
||||
1. *interfaceUsesList* может содержать юниты, пространства имен .NET и явные пространства имен (узлы SyntaxTree.unit_or_namepace)
|
||||
2. *references* содержит библиотеки dll (узлы TreeRealization.unit_node)
|
||||
3. *namespaces* содержит явные пользовательские пространства имен, подключенные с помощью директивы {$includenamespace} (узлы SyntaxTree.syntax_namespace_node)
|
||||
|
||||
```csharp
|
||||
CreateDependencyListsForCurrentUnit(currentUnit, currentDirectory, out var interfaceUsesList, out var references, out var namespaces);
|
||||
```
|
||||
|
||||
- **Списки зависимостей, содержащиеся в CompilationUnit**
|
||||
1. *possibleNamespaces* может содержать пространства имен .NET, стандартные паскалевские юниты из папки Lib, а также явные пространства имен, если они есть в секции uses
|
||||
2. *InterfaceUsedUnits* содержит все, что есть в *possibleNamespaces +* пользовательские юниты и dll, т.е., грубо говоря, все виды программных единиц, являющиеся зависимостями CompilationUnit
|
||||
3. *InterfaceUsedDirectUnits* содержит только пользовательские юниты
|
||||
4. *ImplementationUsedUnits* и *ImplementationUsedDirectUnits* устроены аналогично предыдущим двум
|
||||
|
||||
```csharp
|
||||
public class CompilationUnit
|
||||
{
|
||||
...
|
||||
internal List<SyntaxTree.unit_or_namespace> possibleNamespaces = new List<PascalABCCompiler.SyntaxTree.unit_or_namespace>();
|
||||
|
||||
public Dictionary<unit_node, CompilationUnit> InterfaceUsedDirectUnits { get; } = new Dictionary<unit_node, CompilationUnit>();
|
||||
|
||||
public unit_node_list InterfaceUsedUnits { get; } = new unit_node_list();
|
||||
|
||||
public Dictionary<unit_node, CompilationUnit> ImplementationUsedDirectUnits { get; } = new Dictionary<unit_node, CompilationUnit>();
|
||||
|
||||
public unit_node_list ImplementationUsedUnits { get; } = new unit_node_list();
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
<aside>
|
||||
💡 Узел using_list является устаревшим, так как он относился к ключевому слову using.
|
||||
|
||||
</aside>
|
||||
41
documentation/Intellisense/Функции Intellisense.md
Normal file
41
documentation/Intellisense/Функции Intellisense.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#### Подсказка при наведении мыши
|
||||
На верхнем уровне реализуется в классе `TooltipServiceManager`. Основная логика реализована в методе `GetPopupHintText`. Вначале вызывается `FindExpressionFromAnyPosition` для получения нужного `expression` в виде строки.
|
||||
Затем вызывается нужный парсер для получения дерева выражения (используется часть грамматики, связанная с нетерминалом parts). Если не получается распарсить, то делается попытка с решением неоднозначности, связанной с шаблонными параметрами (&<).
|
||||
После этого еще попытка с удалением разыменования, пока непонятно зачем это.
|
||||
Еще удаление разыменования было найдено в методе `GetDefinitionPosition` из `CodeCompletionActions`.
|
||||
Затем еще попытка с тем же выражением без скобочек. При этом, если без скобочек распарсить не удается, то вне зависимости от успешности парсинга полного `expression` возвращается `null`.
|
||||
В конце проверяется был ли успешен обход дерева программы визитором (`DomSyntaxTreeVisitor`). Если это так, то в словаре откомпилированных модулей будет данный модуль и соответствующий ему `DomConverter`.
|
||||
Тогда мы сможем вызвать метод `GetDescription` из этого класса, который выдаст нам описание выражения.
|
||||
|
||||
#### Подсказка параметров функции
|
||||
На нажатие клавиш (скобки, квадратной скобки или запятой) реагирует `TextAreaKeyEventHandler` из класса `CodeCompletionKeyHandler`. Там создается экземпляр `DefaultInsightDataProvider`, который загружается в стек провайдеров. Информация для подсказки формируется в методе `SetupDataProvider` инсайт-провайдера. После выяснения кода на текущей подстроке вызывается `FindExpressionForMethod` из `LanguageInformation`.
|
||||
Далее делается попытка парсинга стандартным парсером языка и наконец из `DomConverter` вызывается `GetNameOfMethod`, либо `GetIndex` в зависимости от нажатой клавиши.
|
||||
|
||||
#### Подсказка по нажатию клавиш (по точке или пробелу)
|
||||
`CodeCompletionKeyHandler` реагирует на нажатие вызовом метода `ShowCompletionWindow`. В нем есть обращение к `GenerateCompletionDataWithKeyword` из класса `CodeCompletionProvider`. В этом методе выделяется нужная подстрока и есть обращение к методу `GetCompletionData`.
|
||||
**Логика этого метода следующая:**
|
||||
1) если был нажат `ctrl + space` (`shift + space` входит в это понятие тоже)
|
||||
Вызывается `FindPattern` из `LanguageInformation` для получения "токена" до каретки. Если в процессе встретится точка, то вызывается `FindExpression`. Ниже если pattern пустой, то в итоговый список `ICompletionData` добавляются ключевые слова языка.
|
||||
2) если был нажат пробел после `new`
|
||||
Вызывается SkipNew для пропуска `new` и поиска expression перед ним.
|
||||
3) иначе, если не было введено `uses`
|
||||
вызывается `FindExpression` (c текущей позиции)
|
||||
|
||||
Далее, если [**не** `ctrl + space` или была найдена цепочка имен в выражении] и выражение не пусто, то делается попытка вызова функции `GetTypeAsExpression` стандартного парсера. Если это ничего не дало далее попытка вызова `GetExpression`. Если снова ничего не удалось и мы не в случае пробела после `new`, то возврат.
|
||||
|
||||
Далее, если Intellisense успешно не обошла текущий модуль, то для случая `uses` мы будем рассматривать SymInfo стандартных модулей (взятых из DomConverter). Если же у нас не `uses`случай и не случай ctrl + space, мы возвращаем пустой массив.
|
||||
|
||||
Иначе, если у нас есть `DomConverter`:
|
||||
- Если пробел после `new`, то вызываем `GetTypes`
|
||||
- Иначе, если `uses`, то работаем со списком стандартных модулей + пр-в имен в случае семантического Intellisense.
|
||||
- Иначе, если не `ctrl + space` (случай, когда мы поставили точку), то вызываем `GetName` для получения всех возможных имен после точки
|
||||
- Иначе, если `ctrl + space`, то либо ищем имена после точки - `GetName` (если точка была раньше), либо ищем имя по его началу - `GetNameByPattern`
|
||||
|
||||
|
||||
> [!NOTE] Замечание
|
||||
>Также на нажатие реагирует Execute из CodeCompletionAllNamesAction. key подменяется на доллар если это было нажатие `ctrl + space` сразу после точки.
|
||||
|
||||
В конце по полученным SymInfo строится результирующий список CompletionData (без повторений).
|
||||
|
||||
#### Переход к определению символа
|
||||
В `CodeCompletionActoins` есть специальный класс GotoAction, в котором вызывается метод `GoToDefinition`. В этом методе используется метод GetDefinitionPosition, который в свою очередь (тоже косвенно) использует `FindExpressionFromAnyPosition` из `LanguageInformation` и `GetDefinition` из `CodeCompletionProvider`. Последний вышеупомянутый метод является оберткой на `GetDefinition` из DomConverter.
|
||||
Loading…
Reference in a new issue