Skip to content

Instantly share code, notes, and snippets.

@reznikmm
Created November 14, 2022 15:33
Show Gist options
  • Save reznikmm/ef59ce708c6788434c3f6e0e07f3a10d to your computer and use it in GitHub Desktop.
Save reznikmm/ef59ce708c6788434c3f6e0e07f3a10d to your computer and use it in GitHub Desktop.

Таблица имен

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

При такой реализации дольше не нужен стек, ведь можно просто сместиться к родительскому региону, содержимое которого будет восстановлено.

Таблица имен (прошлая версия)

Отложим пока идею с атрибутами, реализуем "живую" структуру, которую можем менять при обходе дерева в процессе анализа, создавая декларативные регионы и имена в них. Мы хотим иметь структуру данных, способную отвечать на следующие запросы:

  • Immediate_Visible (env, symbol) -> {view}
  • Use_Visible (env, symbol) -> {view}

Где

  • env - таблица имен,
  • symbol - представление строки-имени не зависимое от регистра символов,
  • {view} - последовательность элементов view, каждый из которых содержит
    • ссылку на узел defining_name в дереве (для реализации ASIS-запроса Corresponding_Defining_Name)
    • тип конструкции определяющей имя (например, определение типа, метка оператора, итератор цикла)
    • для каждого типа конструкции - дополнительные данные, например, для ссылочного типа - данные на указываемый тип, для массива - типы индексов и тип компонент, для записей - поля записи с типами и т.д.

Immediate_Visible возвращает непосредственно видимые имена, а Use_Visible имена видимые через спецификатор использования '''use'''. Use_Visible идет отдельно, т.к. имена с такой видимостью имеют меньший приоритет.

Нам нужны операции модификации

  • Создать новую конструкцию (пакет, тип и т.д.). При этом точка зрения представляемая таблицей "смещается" внутрь созданной конструкции.
  • Выйти из конструкции, сместив "точку зрения" к охватывающей конструкции.
  • Заменить конструкцию, например, заменить приватный тип полным описанием типа
  • Сделать снимок состояния, например, когда начинается приватная часть пакета
  • Войти в конструкцию повторно, используя ранее полученный снимок. Например, когда мы входим в тело пакета.

Главное свойство такой таблицы имен в том, чтобы в каждый момент предоставлять согласованную информацию. Например, как только тип из приватного стал целым, все упоминания о нём должны возвращать информацию, как о целом типе. Это должно работать и с циклическими зависимостями. Например:

type T;
type T_Access is access all T;
type T is record
   Next : T_Access;
end record;

Реализация

В основе структуры лежит элемент (Item). Обычно он хранит ссылку на defining_name и соответствующий view. Кроме того, этот Item ссылается на другие элементы, которые входят в его декларативный регион. Поскольку элементы обычно находятся в каком-то декларативном регионе, то объединим их в список - элементы декларативного региона. Обозначим ссылку на следующий элемент в списке, как next, а ссылку на первый элемент вложенного декларативного региона, как region. Элементы в списке next идут с порядке, обратном порядку добавления.

Пример структуры. При модификации структуры, мы можем сэкономить память, если будем повторно использовать уже существующие элементы. Нижним индексом обозначим версию элемента, а серым цветом - элементы, которые нельзя модифицировать. При этом сам env это стек из ссылок на (конкретные версии) Item, представляющие декларативные регионы. Соответственно поиск Immediate_Visible выполняется сначала по первому элементу стека (используя цепочку next), потом по второму, и т.д.

  1. Шаг 1
package P is
   E : exception;

P0

Объединенная структура - Та же. Env: [P₀]

  1. Шаг 2

      package P is
         E : exception;
         package Q is
            G : exception;

    P1

    Объединенная структура
    P1s

    Env: [Q₁, P₁]

  2. Шаг 3

      package P is
         E : exception;
         package Q is
            G : exception;
         private
            H : exception;

    P2

    Объединенная структура
    P2s Env: [Q₂, P₂]

Не все элементы мы можем модифицировать, а только те, для которых ещё не было операций взятия снимка. Мы можем определить их сравнивая "текущую версию" с версией Item. В основе операций модификации лежит нахождение замыкания модифицируемых элементов (т.к. между элементами может быть циклическая зависимость). Для этого нам нужен список всех ссылок на каждый элемент (на конкретную версию).

Выход из конструкции выполняется отбрасыванием (уничтожением?) первого элемента стека.

Замена это модификация элемента.

Сделать снимок - увеличить "текущую версию", запомнить env.

Войти в конструкцию повторно - нужно заменить все ссылки на устаревшие Item, на соответствующие последней версии.

Ниже идет описание прошлой версии. В прошлой версии мы храним только defining_name, а семантические свойства соответствующей имени конструкции вычисляем по AST и таблице имен. Вычислять оказалось сложно.

Таблица имен (позапрошлая версия)

В каждом узле AST мы храним атрибут env суть - ссылка на таблицу видимых из данной точки программы имен.

Целью этой структуры является отвечать за запросы:

  • Direct_Visible (env, symbol) -> {defining_name} - вернуть все варианты определений имени с данным символом. Может вернуть несколько элементов для перегруженных имен (типа "+").
  • Use_Visible (env, symbol) -> {defining_name} - аналогично для видимых через '''use'''. Такие определения имеют меньший приоритет, поэтому два запроса.
  • Visible (env, region_defining_name, symbol) -> found, {defining_name} - найти регион, соответствующий данному имени и искать в нем символ.
  • Completions (env, defining_name) -> {defining_name} - вернуть все определения "завершающие" одно другое для данного имени.

Имя может быть видимо следующими способами:

  • прямая видимость (когда используется идентификатор, без использования расширенных имен), делится в свою очередь на
    • непосредственная видимость (возникает ввиду вложенности данного элемента в конструкцию, где определено имя)
    • видимость от спецификатора использования (когда используется use <пакет>)
  • прочая видимость (когда используется расширенное имя Ada.Text_IO.Put_Line или специальный контекст)

Пример:

with Ada.Numerics.Complex_Types; use Ada.Numerics;
--  Ada - непосредственная видимость т.к. и Main и Ada определены, как дочерние модули Standard
--  Ada.Numerics - прочая видимость
procedure Main is
  X : Complex_Types.Complex;  --  Complex_Types - видимость от спецификатора использования
begin
  X := Ada.Numerics.Complex_Types.Complex'(Im => 0,  --  Im прочая видимость
    Re => X.Re);

Непосредственная видимость

Чтобы отслеживать непосредственную видимость мы храним стек из (частично заполненных) декларативных регионов. Каждый регион - набор отображений symbol -> defining_name. Пример

procedure Test is
   package Pkg is
      procedure N1;
   end Pkg;

   N2 : constant := 2;

   package body Pkg is
      --  Что видим тут?
Декларативный регион Содержимое
<корень> Standard
Standard со всей чюхней (Integer, String, ...) и Test в конце
Test Pkg, N2
Pkg N1

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

procedure Test is
   package Child is
     --  Тут регион Test содержит только Child
   end Child;
   N : constant := 1;
   package body Child is
     --  Тут регион Test содержит только Child и N
   end Child;

Поиск осуществляется последовательно внутри региона, затем в следующем регионе и т.д.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment