Category: наука

Category was added automatically. Read all entries about "наука".

Устройство ASDF. Подсистема определения путей. COMPUTE-OUTPUT-TRANSLATIONS.

(сказаное ниже относится к версии 2.019.8)

Это 19-ая статья цикла.
Первая статья.
Архитектура ASDF.
Предыдущая статья.

    Ф-ия COMPUTE-OUTPUT-TRANSLATIONS вычисляет значения для инициализации глобальной переменной *OUTPUT-TRANSLATIONS*. Эта ф-ия во многом схожа с ф-ией COMPUTE-SOURCE-REGISTRY, но несколько проще. В ней тоже работает некий конвейр обрабатывающий переменную среды и конфигурационные файлы, если они есть. А используемые ф-ии имеют созвучные с используемыми в COMPUTE-SOURCE-REGISTRY имена.

Логика её функционирования следующая:

COMPUTE-OUTPUT-TRANSLATIONS
(defun* compute-output-translations (&optional parameter) ...)
1. Создаётся контекст для последующего вызова ф-ии INHERIT-OUTPUT-TRANSLATIONS. В этом контексте на самом "верху" находится ф-ия REMOVE-DUPLICATES - она из получившегося результата удалит повторяющиеся элементы. Кроме того, используется макрос WHILE-COLLECTING определяющий локальную ф-ию C для сбора промежуточных результатов:

   (remove-duplicates
     (while-collecting (c)
       (inherit-output-translations ...)
     :test 'equal :from-end t
)


    Список с данными для конвейера состоит из символов именующих ф-ии, которые метод PROCESS-OUTPUT-TRANSLATIONS будет вызывать для получения конфигурационных данных. Во главе списка идёт символ WRAPPING-OUTPUT-TRANSLATIONS. Её вызов приведёт к получению таких конфигурационных данных, которые обеспечат лисп-системам сохранение скомпилированных системных файлов рядом с их исходниками, например для Linux/SBCL результат будет следующим:

       (wrapping-output-translations)
       => (:OUTPUT-TRANSLATIONS
        ((#P"/media/COMMON_LISP/lisp-dev-tools/lisp/sbcl/sbcl-1.0.53/lib/sbcl/"
        #P"**/"
)

         NIL
)

        :INHERIT-CONFIGURATION :ENABLE-USER-CACHE
)


    Далее следует имя ф-ии заданное аргументом PARAMETER (или соответственно NIL если PARAMETER не задан). Все остальные символы именующие функции, берутся из глобальной переменной *DEFAULT-OUTPUT-TRANSLATIONS*, которая по умолчанию равна:

        '(ENVIRONMENT-OUTPUT-TRANSLATIONS
          USER-OUTPUT-TRANSLATIONS-PATHNAME
          USER-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME
          SYSTEM-OUTPUT-TRANSLATIONS-PATHNAME
          SYSTEM-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME)

    Эти ф-ии имеют следующее предназначение:

    ENVIRONMENT-OUTPUT-TRANSLATIONS - возвращает данные, сохранённые в переменной среды "ASDF_OUTPUT_TRANSLATIONS".

    USER-OUTPUT-TRANSLATIONS-PATHNAME - возвращает путь к конфигурационому файлу пользователя если он реально существует. Имя файла определяется глобальной динамической переменной *OUTPUT-TRANSLATIONS-FILE*, которая по умолчанию равна #P"asdf-output-translations.conf". Если выполнение происходит на UNIX-like -системе (например на Linux), то поиск файла производится в следующих директориях:
            - в директории, начальный путь которой, задан через переменную среды "XDG_CONFIG_HOME" а окончание пути - "common-lisp/".
            - в одной из директорий начинающихся с директорий в переменной среды "XDG_CONFIG_DIRS" (директории разделяются символом ":") и заканчивающихся, опять же, на "common-lisp/".
        - в директории начинающейся с домашней директории пользователя и оканчивающейся на ./config/common-lisp/, например: /home/user/common-lisp/.config

    USER-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME - то же самое что и USER-OUTPUT-TRANSLATIONS-PATHNAME, но ищет не конфигурационный файл, а директорию для конфигурационных файлов по тем же путям поиска, с именем *OUTPUT-TRANSLATIONS-DIRECTORY* (по-умолчанию #P"asdf-output-translations.conf.d/").

    SYSTEM-OUTPUT-TRANSLATIONS-PATHNAME - то же самое что и USER-OUTPUT-TRANSLATIONS-PATHNAME, но файл заданный *OUTPUT-TRANSLATIONS-FILE* ищет в директории #P"/etc/common-lisp/".

    SYSTEM-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME - то же самое что и USER-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME, но ищет директорию заданную *OUTPUT-TRANSLATIONS-DIRECTORY* в директории #P"/etc/common-lisp/".

    Итак, вызов COMPUTE-OUTPUT-TRANSLATIONS, допустим с аргументом PARAMETER = NIL, приведёт к вызову INHERIT-OUTPUT-TRANSLATIONS со следующими аргументами:

        (INHERIT-OUTPUT-TRANSLATIONS
            '(WRAPPING-OUTPUT-TRANSLATIONS
             NIL
             ENVIRONMENT-OUTPUT-TRANSLATIONS
             USER-OUTPUT-TRANSLATIONS-PATHNAME
             USER-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME
             SYSTEM-OUTPUT-TRANSLATIONS-PATHNAME
             SYSTEM-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME)
     :COLLECT #<CLOSURE (FLET C) {1004A48AAB}>)

        ... на месте не печатаемого объекта "#<CLOSURE (FLET C) {1004A48AAB}>" разумеется будет что-то другое.


INHERIT-OUTPUT-TRANSLATIONS
(defun* inherit-output-translations (inherit &key collect) ...)
2. Конвейрная обработка организуется функцией INHERIT-OUTPUT-TRANSLATIONS и методами PROCESS-OUTPUT-TRANSLATIONS. Они рекурсивно вызывают друг друга (хотя в большинстве случаев PROCESS-OUTPUT-TRANSLATIONS обходится без "неявно-рекурсивного" вызова INHERIT-OUTPUT-TRANSLATIONS), по пути складывая в список промежуточные результаты с помощью локальной функции-коллектора C, определённой как сказано выше макросом WHILE-COLLECTING (в теле ф-ии INHERIT-OUTPUT-TRANSLATIONS она связывается с ключом COLLECT) . Когда аргумент INHERIT не равен NIL, что сигнализирует о необходимости запуска "конвейера", она разбивает его на голову и хвост и передаёт ф-ии PROCESS-OUTPUT-TRANSLATIONS:

    (when inherit
      (process-output-translations (first inherit) :collect collect :inherit (rest inherit)))

    ... хвост (rest inherit) передаётся для того, чтобы метод PROCESS-OUTPUT-TRANSLATIONS мог рекурсивно (через вызов INHERIT-OUTPUT-TRANSLATIONS) продолжить дальнейшую обработку, если в конфигурационном DSL был указан ключ :inherit-configuration.


PROCESS-OUTPUT-TRANSLATIONS
(defgeneric process-output-translations (spec &key inherit collect) ...)
3. Эта обобщёная ф-ия включает 5 методов специализирующихся на следующих типах аргумента SPEC: NULL, SYMBOL, STRING, PATHNAME и CONS. В конечном счёте всё сводится к вызову метода специализирующегося на типе CONS, так как аргумент в этом случае, будет содержать список, представляющий конфигурационные данные (ради получения и обработки которых, собственно всё и затевается).

    3.1 Сначала я рассмотрю ф-ии на которых базируются разные специализации PROCESS-OUTPUT-TRANSLATIONS, после понимания работы этих ф-ий - анализ взаимодействия методов станет тривиальным.

        VALIDATE-CONFIGURATION-FILE
        (defun* validate-configuration-file (file validator &key description) ...)
        3.1.1 Ф-ия считывает с указанного файла FILE список, проверяет что он в файле единственный и вызывает функцию-валидатор VALIDATOR для проверки корректности данных. Ключ DESCRIPTION ей необходим в случае неудачи, для формирования сообщения об ошибке.

        VALIDATE-CONFIGURATION-DIRECTORY
        (defun* validate-configuration-directory (directory tag validator &key invalid-form-reporter) ...)
        3.1.2 А эта ф-ия ищет в указанной директории DIRECTORY файлы с расширением .conf (пропуская скрытые файлы), считывает из них множество форм, представляющих директивы анализируемого DSL (Domain Specific Language - язык предметной области), проверяет каждую их них функией-валидором VALIDATOR и составляет из них правильный формат конфигурационных данных (который, соответственно, возвращается в качестве результата), используя при этом,  переданный ключевой символ TAG (он становится первым элементом результирующего списка).

        VALIDATE-OUTPUT-TRANSLATIONS-FILE
        (defun* validate-output-translations-file (file) ...)
        3.1.3 Ф-ия для считывания и проверки конфигурационных данных обращается за помощью к ф-ии VALIDATE-CONFIGURATION-FILE, которая в качестве валидатора использует функцию VALIDATE-OUTPUT-TRANSLATIONS-FORM. Если проверка корректности данных прошла успешно, то они возвращаются в качестве результата.

        VALIDATE-OUTPUT-TRANSLATIONS-DIRECTORY
        (defun* validate-output-translations-directory (directory) ...)
        3.1.4 Для поиска файлов с конфигурационными данными в директории DIRECTORY, их считывания и проверки вызывается функция VALIDATE-CONFIGURATION-DIRECTORY. В качестве функции-валидатора она использует VALIDATE-OUTPUT-TRANSLATIONS-DIRECTIVE, проверяющей каждую прочитанную форму из конфигурационных файлов (директиву анализируемого DSL).

        PARSE-OUTPUT-TRANSLATIONS-STRING
        (defun* parse-output-translations-string (string &key location) ...)
        3.1.5 Эта ф-ия используется для преобразования данных их переданной строки в правильно сформированные конфигурационные данные. Ф-ия анализирует переданный аргумент и выбирает одно из следующих действий:
            - если STRING не является строкой, то ф-ия сигнализирует об ошибке.
            - если STRING равна пустой строке или NIL то вызвращается список '(:output-translations :inherit-configuration).
            - если STRING содержит вложенную строку (то есть, строку вида "\"...\""), то эта вложенная строка "вынимается" из STRING с помощью вызова (read-from-string string) и повторяется всё заново со вложенной строкой (т.е. ф-ия рекусивно вызывается с получившейся строкой).
            - если первый символ в STRING это "(", то значит в строке содержится список и тогда он читается из строки, с помощью того же вызова (read-from-string string) и также происходит рекурсивный вызов с получившимся результатом.
            - если же не сработал ни один из получившихся вариантов, тогда строка рассматривается как последовательное перечисление директорий, каждая их которых разделяется специфичным для системы симолом-разделителем директорий (для Linux это символ ":"). Причём кол-во директорий должно быть обязательно чётным, иначе сигнализируется ошибка. Отсутствие текста между двумя символами-разделителями (например: "/home/src:/some/compiled::") говорит об использовании ключа :INHERIT-CONFIGURATION (его допускается использовать один раз, при нарушении этого правило сигнализируется ошибка).

        VALIDATE-OUTPUT-TRANSLATIONS-FORM
        (defun* validate-output-translations-form (form &key location) ...)
        3.1.6 Ф-ия проверяет корректность конфигурационных данных с помощью ф-ии VALIDATE-CONFIGURATION-FORM. Для проверки каждой директивы она использует ф-ию VALIDATE-OUTPUT-TRANSLATIONS-DIRECTIVE. Ключ LOCATION используется в случае возникновения ошибки и содержит небольшую часть сообщения.

        VALIDATE-CONFIGURATION-FORM
        (defun* validate-configuration-form (form tag directive-validator
                          &key location invalid-form-reporter) ...)
    3.1.7 Ф-ия проверяет корректность конфигурационных данных, представленных аргументом FORM. В случае если аргумент FORM не является списком или его первый элемент не эквивалентен аргументу TAG, то глобальная переменная *IGNORED-CONFIGURATION-FORM* получает значение T и сигнализируется ошибка. Основная часть ф-ии состоит из итерирования по хвосту списка FORM. Ф-ия проверяет каждую директиву переданым валидатором DIRECTIVE-VALIDATOR, при этом она проверяет что ни один из ключей :INHERIT-CONFIGURATION или :IGNORE-INHERITED-CONFIGURATION не встречается более одного раза. А также она обрабатывает семантику ключа :IGNORE-INVALID-ENTRIES позволяющего игнорировать неправильные директивы, не сигнализируя ошибки.

        VALIDATE-OUTPUT-TRANSLATIONS-DIRECTIVE
        (defun* validate-output-translations-directive (directive) ...)
        3.1.8 Ф-ия отвечает непосредственно за валидацию директивы (обрабатываемого DSL). Если аргумент DIRECTIVE атом, то он обязан иметь только одно из следующих значений: :ENABLE-USER-CACHE, :DISABLE-CACHE, NIL. Если же он является списком, тогда считается корректной одна из следующих ситуаций:

            3.1.8.1 Длина списка = 2 и при этом:
                - либо в качестве первого элемента используется ключ :INCLUDE, а в качестве второго - объект типа STRING, PATHNAME или NIL.
                - либо первый элемент удовлетворяет предикату LOCATION-DESIGNATOR-P, а второй - либо предикату LOCATION-DESIGNATOR-P, либо предикату LOCATION-FUNCTION-P.                    

            3.1.8.2 Длина списка = 1 и его единственный элемент удовлетворяет предикату LOCATION-DESIGNATOR-P.

            Используемые предикаты работают следующим образом:

            LOCATION-DESIGNATOR-P
            Предикат возвращает значение T, в случае если аргумент:
                - атом и имеет тип STRING, PATHNAME или одно из следующих значений:  T, NIL, :ROOT :HOME :HERE :USER-CACHE :SYSTEM-CACHE :DEFAULT-DIRECTORY.
                - список, и тогда в качестве первого элемента в списке должно быть то, что описано выше, а в качестве оставшихся должны быть элементы типа STRING, PATHNAME, или следующие значения: :DEFAULT-DIRECTORY, :*/, :**/, :*.*.*, :IMPLEMENTATION, :IMPLEMENTATION-TYPE.

            LOCATION-FUNCTION-P
            Предикат возвращает T, в случае если аргумент:
                - список из двух элементов, первый из которых это :function, а второй: либо символ, либо список, представляющий лямбда-выражение, принимающее на вход два параметра.
        
        PROCESS-OUTPUT-TRANSLATIONS-DIRECTIVE
        (defun* process-output-translations-directive (directive &key inherit collect) ...)
        3.1.9 Это самая важная ф-ия, она занимается непосредственно обработкой директивы из, предварительно проверенных на корректность, конфигурационных данных. Смысл её в том, чтобы разобрать (распарсить) переданную директиву и сформировать данные для последующего помещения в результирующий список с помощью ф-ии передаваемой ключом COLLECT. Результаты обработки в конце-концов попадут в глобальную переменную *OUTPUT-TRANSLATIONS*, а она в свою очередь, необходима для работы ф-ии APPLY-OUTPUT-TRANSLATIONS (это ф-ия, вычисляющая пути к файлам содержащим результаты применения операций к компонентам - обычно к *.fasl файлам). В процессе работы ф-ия может рекурсивно вызвать сама себя или ф-ию INHERIT-OUTPUT-TRANSLATIONS с параметром INHERIT, если была вызывана с аргументом DIRECTIVE равным ключу :INHERIT-CONFIGURATION. Подробный разбор логики работы этой ф-ии будет дан в конце статьи.

    3.2 Теперь вернёмся к обобщённой ф-ии PROCESS-OUTPUT-TRANSLATIONS, её специализации работают следующим образом:

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS NULL)
Осуществляется переход к обработке следующего элемента:

    (inherit-output-translations inherit :collect collect)

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS SYMBOL)
Вызывается ф-ия, имя которой представлено символом, для получения конфигурационных данных для дальнейшей обработки:

    (process-output-translations (funcall x) :inherit inherit :collect collect)

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS STRING)
Аргумент обрабатывается ф-ией PARSE-OUTPUT-TRANSLATIONS-STRING, возвращающей конфигурационные данные для дальнейшей обработки:

    (process-output-translations (parse-output-translations-string string)
                 :inherit inherit :collect collect)

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS PATHNAME)
Если аргумент представляет собой файл, то для чтения конфигурационных данных и их валидации (и возврата в качестве результата) вызывается ф-ия VALIDATE-OUTPUT-TRANSLATIONS-FILE. Если аргумент содержит путь к директории, то файлы в ней рассматриваются как содержащие конфигурационные данные. За сбор этих данных и их валидацию отвечает ф-ия VALIDATE-OUTPUT-TRANSLATIONS-DIRECTORY. Если аргумент представляет собой не существующий файл, то происходит переход к дальнейшей обработки:

    (inherit-output-translations inherit :collect collect)

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS CONS)
В этом случае аргумент представляет собой конфигурационные данные. Производится их валидация и каждый элемент этих данных обработывается ф-ией PROCESS-OUTPUT-TRANSLATIONS-DIRECTIVE:

    (dolist (directive (cdr (validate-output-translations-form form)))
      (process-output-translations-directive directive
                         :inherit inherit
                         :collect collect
)
)


--------------------------------------------------------------
        
PROCESS-OUTPUT-TRANSLATIONS-DIRECTIVE
(defun* process-output-translations-directive (directive &key inherit collect) ...)
Это главная ф-ия, используемая в процессе обработки конфигурационных данных, используемых при формирования путей результатов применения операций к компонентам. Эти конфигурационные данные следуют специальным соглашениям ASDF для формирования этих путей, представляющих собой некий мини-DSL.

Логика работы:
    
1. Если аргумент DIRECTIVE - атом, то в зависимости от его значения-ключа происходит следующее:
    - :ENABLE-USER-CACHE - рекурсивный вызов с DIRECTIVE = '(t :user-cache)
    - :DISABLE-CACHE - рекурсивный вызов с DIRECTIVE = '(t t)
    - :INHERIT-CONFIGURATION - вызов INHERIT-OUTPUT-TRANSLATIONS с аргументом INHERIT.
    - для аргументов :IGNORE-INHERITED-CONFIGURATION, :IGNORE-INVALID-ENTRIES, NIL - возвращает NIL.

2. Если же аргумент список определяется локальное окружение с переменными, содержащими элементы списка:

   (let ((src (first directive))
      (dst (second directive))
)

      ...
)


3. Далее, если SRC = :INCLUDE то путь в DST обрабатывается "неявно-рекурсивным" вызовом ф-ии PROCESS-OUTPUT-TRANSLATIONS:

    (if (eq src :include)
        (when dst
     
     (process-output-translations (pathname dst) :inherit nil :collect collect))
    ...
)


4. Иначе, если переменная SRC не равна ни :INCLUDE ни NIL, то происходит следующее:

    4.1 Создаётся локальное окружение с переменной TRUSRC. Она становится равной либо T (если SRC = T):

       (let ((trusrc (or (eql src t)
                           
  ...)))
        ...
)


        ... либо её значение вычисляется из локальной SRC, которая должна представлять собой или путь к файлу непосредственно, или список описывающий путь (оформленный с помощью специальных соглашений ASDF по формированию путей):

       (let ((loc (resolve-location src :directory t :wilden t)))
          (if (absolute-pathname-p loc) (truenamize loc) loc))

        ... как видно используется ф-ия RESOLVE-LOCATION которая воспринимает SRC как директорию и создаёт, так называемый "wildcard" путь (путь использующий групповые символы). Например, вот результат применения RESOLVE-LOCATION с путём "/usr/somedir":

        (resolve-location "/usr/somedir" :directory t :wilden t)
        => #P"/usr/somedir/**/*.*"

        4.2 В созданном локальном окружении, возможные варианты выполнения распадаются на три ветки:
            
            4.2.1 Если переменная DST удовлетворяет предикату LOCATION-FUNCTION-P (т.е это список имеющий первый элемент :FUNCTION, а второй - либо символ, либо список с лямбда-выражением), то в результирующий список (собираемый ф-ией переданной в ключе COLLECT) попадает список из TRUSRC и из функционального объекта представленного вторым элементом DST:

               (cond
                  ((location-function-p dst)
                   (funcall collect
                            (list trusrc
                                  (if (symbolp (second dst))
                                      (fdefinition (second dst))
                                      (eval (second dst))
)
)
)
)

                  ...
)


            4.2.2 Если DST = T, то возвратить список из TRUSRC и T:

               (cond
         
         ...
       
           ((eq dst t)
              
       (funcall collect (list trusrc t)))
        
          ...)

            4.2.3 В остальных случаях в результирующий список попадают 2 списка:
                - список из обработанной (с помощью ф-ии RESOLVE-LOCATION) переменной DST если она не была равна NIL (а если была, то в список попадает значение переменной TRUSRC) и константы T.
                - и список из TRUSRC и обработанной DST (в случае если значение DST было равно NIL, то в списке оказывается значение TRUSRC).

--------------------------------------------------------
Продолжение следует ...

Блог о Common Lisp

  Всё-таки блоги на техническую тематику имеют множество положительных св-в как для авторов так и для читателей. Каждая мини-статья не претендуя на скрупулезноя научное исследование может быть весьма полезна читателям и не требовать массы времени на её изучение (как например, скажем, техническая статья средней величины). К тому же, её обсуждение с читателями может быть полезно и самому автору. В общем я решил завести привычку делится своими иследованиями с общественностью:)
Как это получится покажет время ... Так или иначе, а ещё один блог о программировании на Common Lisp - считайте, что стартовал!