linkfly (linkfly) wrote,
linkfly
linkfly

Устройство ASDF. Подсистема поиска. RESOLVE-LOCATION

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

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

 
   Ф-ия resolve-location необходима для того, чтобы получить нормальный для данной системы путь. Причём учитывая некоторые специальные соглашения ASDF для предопределённых директорий и для использования групповых символов (вроде "*.*" или "**"). Ф-ия занимается обработкой этих соглашений и "разруливает" многочисленные различия в обработке путей на разных платформах/лисп-системах. Вот так выглядит её определение:

   (defun* resolve-location (x &key directory wilden)
      ...)

    В теле RESOLVE-LOCATION используются две функции RESOLVE-ABSOLUTE-LOCATION-COMPONENT и RESOLVE-RELATIVE-LOCATION-COMPONENT (чуть ниже будет представлена схема зависимостей ф-ий друг от друга). Причём RESOLVE-LOCATION может быть вызвана рекурсивно, но не явно (в теле определения ф-ии нет рекурсивных вызовов), а опосредовано - RESOLVE-ABSOLUTE-LOCATION-COMPONENT в определённых ситуациях вызывает RESOLVE-LOCATION.

    RESOLVE-ABSOLUTE-LOCATION-COMPONENT использует:
1) саму себя (вызывается рекурсивно).
2) RESOLVE-RELATIVE-LOCATION-COMPONENT.
3) RESOLVE-LOCATION (т.е. может делать опосредованные рекурсивные вызовы).
4) динамические переменные *HERE-DIRECTORY*, *USER-CACHE*, *SYSTEM-CACHE*.
5) общие ф-ии (назовём их так): MERGE-PATHNAMES*, ENSURE-DIRECTORY-PATHNAME, DEFAULT-DIRECTORY, USER-HOMEDIR, ABSOLUTE-PATHNAME-P.
6) ф-ию WILDEN.
   
    RESOLVE-RELATIVE-LOCATION-COMPONENT использует:

1) саму себя (вызывается рекурсивно).
2) дин. переменные: *WILD-DIRECTORY*, *WILD-INFERIORS* *WILD-FILE*.
3) общие ф-ии: RELATIVIZE-PATHNAME-DIRECTORY, MERGE-PATHNAMES*, ENSURE-DIRECTORY-PATHNAME, ABSOLUTE-PATHNAME-P.
4) ф-ии для получения текущей реализации: IMPLEMENTATION-IDENTIFIER, IMPLEMENTATION-TYPE.
5) ф-ию WILDEN.


На "общих ф-иях" не будем останавливаться, их названия говорят сами за себя. Ф-ии для получения текущей
реализации, в случае SBCL/Linux работают так:

(implementation-identifier) => "sbcl-1.0.48-linux-x86"
(implementation-type) => :SBCL

Ф-ия WILDEN добавляет путь из "wildcard" символов в конец пути, это говорит о том что это: "любой файл или директория имеющие началом указанную директорию", обычно это путь #P"**/*.*".

Примеры:

(resolve-location :home)
= > #P"/home/someuser/"

(resolve-location '(:root :implementation "main-source"))
=> #P"sbcl-1.0.48-linux-x86/main-source"

(resolve-location '(:home (:implementation-type "my-source") "main-source"))
=> #P"/home/lispuser/sbcl/my-source/main-source"

                    Логика работы RESOLVE-LOCATION.

1. Ф-ия передаёт управление (вызывает) ф-ию RESOLVE-ABSOLUTE-LOCATION-COMPONENT если X - атом.

   (if (atom x)
      (resolve-absolute-location-component x :directory directory :wilden wilden)

2. Если X не атом, а список, то организуется итерация по списку, при этом:

    2.1 Определяется сразу базовый абсолютный путь PATH (который дальше будет аргументом SUPER в вызовах RESOLVE-RELATIVE-LOCATION-COMPONENT). Для его определения берётся первый элемент из списка X:

   (loop :with path = (resolve-absolute-location-component
                                (car x) :directory (and (or directory (cdr x)) t)
                                :wilden (and wilden (null (cdr x))))
       ...)

    ... как видно ключ :DIRECTORY для RESOLVE-ABSOLUTE-LOCATION-COMPONENT равен T если либо задан соотв. ключ в вызове RESOLVE-LOCATION либо в списке X больше одного аргумента, а :WILDEN если задан соотв. ключ и в списке X нет больше аргументов.

    2.2 Собственно, декларирование прохода по списку X, начиная с его хвоста. Локальная переменная COMPONENT будет получать текущий элемент, а переменная MOREP будет нам говорить о том, остались ли в списке ещё элементы:

   (loop
       ...
       :for (component . morep) :on (cdr x)
       ...)

    2.3 Определяется локальная переменная DIR - она будет вычисляться на каждой итерации и будет равна T, если значение ключа DIRECTORY равно T либо если в списке ещё есть элементы (MOREP не равна NIL):

   (loop
       ...
       :for dir = (and (or morep directory) t)
       ...)
   
    2.4 Определяется локальная переменная WILD - она также вычисляется на каждой итерации и равна истине, только в случае если значение WILDEN = T и в списке нет больше элементов (то есть это последняя итерация):

   (loop
       ...
       :for wild = (and wilden (not morep))
       ...)

    2.5 В главном операторе итерации вычисляется новый PATH с помощью старого PATH и вычисленных локальных переменных. Старый PATH становится началом для вычисляемого пути (значением аргумента SUPER для RESOLVE-RELATIVE-LOCATION-COMPONENT):

   (loop
       ...
       :do (setf path (resolve-relative-location-component
               path component :directory dir :wilden wild))
       ...)

    2.6 После прохода по списку возвращается переменная PATH:

         (loop ... :finally (return path))


                RESOLVE-ABSOLUTE-LOCATION-COMPONENT.
                (defun* resolve-absolute-location-component (x &key directory wilden) ...)

    Ф-ия занимается тем, что формирует путь используя строки, имена путей (pathnames), предопределённые ключи :root :home :here :user-cache :system-cache :default-directory , а также списки. Если передаётся список, то для его головы происходит рекурсивный вызов и результат становится началом вычисляемого пути, а его окончанием - результат предварительной обработки хвоста ф-ей RESOLVE-RELATIVE-LOCATION-COMPONENT.

Примеры:

(resolve-absolute-location-component :root)
=> #P""

(resolve-absolute-location-component "/my/path")
=> #P"/my/path"

(resolve-absolute-location-component '(:home ("my" "path")))
=> #P"/home/lispuser/my/path"

                Логика работы RESOLVE-ABSOLUTE-LOCATION-COMPONENT.
1. Создаёт локальный контекст и устанавливает значение для лексической переменной R в соответствии с типом аргумента X:

   (let* ((r (etypecase x ...))
       ...)
      ...)

    LET -> ETYPECASE
    1.1 Если X путь (pathname) то она просто возвращается. Если строка, то  используется либо ENSURE-DIRECTORY-PATHNAME (которая выбрасывает ошибку в случае если данный pathspec не получается рассматривать как путь к директории) либо PARSE-NAMESTRING (что по крайней мере для SBCL чревато проблемами в обработке путей и лучше для этой системы было бы использовать SB-EXT:PARSE-NATIVE-NAMESTRING) в зависимости от переданного аргумента DIRECTORY:

     (etypecase x
            (pathname x)
            (string (if directory (ensure-directory-pathname x) (parse-namestring x)))
        ...)

    LET -> ETYPECASE
    1.2 Если X список (cons), то осуществляется выход из ф-ии с некоторым значением:
   
     (etypecase x
        ...
        (cons
             (return-from resolve-absolute-location-component (if ...) ...))
         ...)
        
         ... которое формируется следующим образом:

            LET -> ETYPECASE -> RETURN-FROM
            1.2.1 Если список состоит из одного элемента, то ф-ия просто рекурсивно вызывается для этого элемента:

               (if (null (cdr x))
                  (resolve-absolute-location-component   
                     (car x) :directory directory :wilden wilden)
              ...)

            LET -> ETYPECASE -> RETURN-FROM
            1.2.2 Иначе вычисляется путь, в котором началом станет результат вызова RESOLVE-ABSOLUTE-LOCATION-COMPONENT с первым элементом списка:

                (resolve-absolute-location-component (car x) :directory t :wilden nil)
                             
             ... а его окончанием, результат вызова RESOLVE-RELATIVE-LOCATION-COMPONENT с хвостом списка:

                (resolve-relative-location-component (cdr x) :directory directory :wilden wilden)   
                                                         
        LET -> ETYPECASE
        1.3 Также может быть задан один из предопределённых ключей:

            :root :home :here :user-cache :system-cache :default-directory
       
:root - просто некоторый относительный путь, возможно (если :wilden = t) с "wildcard" символами.

:home - путь становится равным домашней директории текущего пользователя (на Linux это #P"/home/someuser/").

:here - если была задана некоторая текущая директория для процесса обработки, через динамическую переменную *HERE-DIRECTORY*, то вызывается resolve-location для этой директории иначе она вызывается с ключом :default-directory :

   (resolve-location (or *here-directory*
              ;; give semantics in the case of use interactively
             :default-directory)
              :directory t :wilden nil)

   ... динамическая переменная *HERE-DIRECTORY* обычно задаётся при обработке файла конфигурации имя которого в *SOURCE-REGISTRY-FILE* (по умолчанию "source-registry.conf") и будет равна директории в которой этот файл находится, либо при обработке директории, имя которой в *SOURCE-REGISTRY-DIRECTORY* (по умолчанию "source-registry.conf.d/"), с конфигурационными файлами и будет равна ей же.

:user-cache - здесь вызывается RESOLVE-LOCATION со значением *USER-CACHE*, которая по умолчанию равна:

    '(:HOME ".cache" "common-lisp" :IMPLEMENTATION)

    ... результат вызова на Linux будет таким:

    (resolve-location *user-cache* :directory t :wilden nil)

    => #P"/home/someuser/.cache/common-lisp/sbcl-1.0.48-linux-x86/"

:system-cache - при использовании этого ключа сигнализируется ошибка с сообщением о том, что этот ключ устарел.

:default-directory - вычисляется директория из *DEFAULT-PATHNAME-DEFAULTS*.

    LET
    2. Далее в локальном контексте определяется переменная S. Её значение вычисляется с использованием определённой ранее R. Если был задан ключ :WILDEN и тип X не является типом PATHNAME то к значению R прибавляется путь #P"**/*.*" (для обозначения любого файла и директории с началом пути равным R), иначе просто берётся значение R:

   (let (...
           (s (if (and wilden (not (pathnamep x)))
                  (wilden r)
                  r)))
     ...)

    LET
    3. Проверяется что переменная S обозначает абсолютный путь, если это не так выбрасывается ошибка. Иначе S возвращается из ф-ии RESOLVE-ABSOLUTE-LOCATION-COMPONENT.


        RESOLVE-RELATIVE-LOCATION-COMPONENT.
        (defun* resolve-relative-location-component (super x &key directory wilden) ...)
       
    Зачем нужна RESOLVE-RELATIVE-LOCATION-COMPONENT ? Она необходима для обработки ещё нескольких соглашений ASDF по формированию пути. Они нужны для повышения гибкости в задании пути, с помощью так называемых "wildcard" символов (символов для обозначения группы файлов/директорий, например: "*"), предопределённых имён директорий и единообразного использования ASDF на разных платформах. В соглашении применяются следующие ключи:
   
          :default-directory :*/ :**/ :*.*.* :implementation :implementation-type :uid (только для unix-систем)
          
     Работа этой ф-ии напоминает работу ф-ии resolve-absolute-location-component.

Примеры:

(resolve-relative-location-component "/home/user/" :**/)
=> #P"/home/user/**/"

(resolve-relative-location-component nil :**/)
=> #P"**/"

(resolve-relative-location-component "/tmp/" '(:implementation :uid :**/))
=> #P"/tmp/sbcl-1.0.48-linux-x86/1000/**/"

                Логика работы RESOLVE-RELATIVE-LOCATION-COMPONENT.

1. Формируется локальный контекст в котором определяется переменная R, её значение вычисляется в зависимости от типа переданной переменной X:

(let* ((r (etypecase x   
              ...
)
)
          ...)
    ...)

    LET -> ETYPECASE
    1.1 Если X имеет тип PATHNAME или STRING то переменная R приравнивается к X:

   (etypecase x
      (pathname x)
      (string x)
      ...)

    LET -> ETYPECASE
    1.2 Если X список (cons), то осуществляется выход из ф-ии с некоторым значением:

   (etypecase x
      ...
      (cons
       (return-from resolve-relative-location-component ...))
      ...)
               
    ... которое вычисляется следующим образом:
           
        LET -> ETYPECASE -> RETURN-FROM   
        1.2.1 Если список из одного элемента ты происходит рекурсивный вызов с этим аргументом:

               (if (null (cdr x))
                    (resolve-relative-location-component
                           super (car x) :directory directory :wilden wilden)
                    ...)

        LET -> ETYPECASE -> RETURN-FROM
        1.2.2 Если же нет, то всё происходит почти также как и в RESOLVE-ABSOLUTE-LOCATION-COMPONENT:

            LET -> ETYPECASE -> RETURN-FROM -> IF
            1.2.2.1 Формируется локальный контекст с переменной CAR, в котором происходит рекурсивный вызов:
                     
                   (let* ((car (resolve-relative-location-component
                                                  super (car x) :directory t :wilden nil
)
)
                           ...)
                      ...)

        LET -> ETYPECASE -> RETURN-FROM -> IF
        1.2.2.2 Дальше значение CAR используется для задания начала вычисляемого пути, а его окончание формируется с помощью рекурсивного вызова RESOLVE-RELATIVE-LOCATION-COMPONENT:

                (merge-pathnames*
                     (resolve-relative-location-component
                  
        (cdr x) :directory directory :wilden wilden)
                     car
)


    LET -> ETYPECASE
    1.3 Если же X является одним из следующих ключей:

    :default-directory :*/ :**/ :*.*.* :implementation :implementation-type :uid (только для unix-систем)        

    ... то возвратить некоторое отображенное на этот ключ значение. Ниже представлены типичные значения на которые отображаются ключи для конфигурации SBCL/Linux:

:default-directory = <значение *default-pathname-defaults* превращенное в относительный путь>
:*/ = *wild-directory* = #P"*/"
:**/ = *wild-directory* = #P"**/"
:*.*.* = *wild-file* = #P"*.*"
:implementation = (implementation-identifier) = "sbcl-1.0.48-linux-x86"
:implementation-type = (string-downcase (implementation-type)) = "sbcl"
:uid = (princ-to-string (get-uid)) = "1000"

2. Проверка на относительность пути - выбрасывается ошибка в случае, если R представляет собой абсолютный путь:

   (when (absolute-pathname-p r)
      (error (compatfmt "~@<pathname ~S is not relative~@:>") x)
)


3. Пост-обработка окончательного результата. Если ключ WILDEN был задан и значение X не имеет тип PATHNAME , то значит надо добавить в конец пути относительный "wildcard" путь:

      (if (or (pathnamep x) (not wilden)) r (wilden r)))
                 
    ... например: (wilden "/home/someuser/") => #P"/home/someuser/**/*.*" - и это будет означать: "любые файлы и директории начинающиеся с /home/someuser/". Иначе R возвращается "как есть".

-------------------------------------------------------------
Продолжение следует ...
Tags: asdf, lisp, programming, лисп, программирование
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments