О кодах, загрузке и компиляции.
Почти худлит получится, я думаю. Но тем не менее, поделиться хочется.
Разговор будет о том, как хранить созданные коды, как их загружать и компилировать. Естественно, что все написанное – сугубо личное мнение, и работать во всех условиях не будет. Но, может быть, кому-то и пригодится. Поехали?
Как уже договаривались в части О форматировании кодов, имена функций будут достаточно уникальными. Но имена функций именами, а в файлы их тоже как-то “засовывать” надо.
Можно, конечно, сохранять коды как “tmp.lsp”, “MyCoolCode.lsp” и т.п. Только ориентироваться в них будет нереально. В свое время я принял для себя технологию, прекрасно описанную в книге “САПР на базе AutoCAD – как это делается” (замечу, что книгу можно назвать библией разработчика для AutoCAD. Именно разработчика, а не программиста – понятия все же немного разные в данном контексте). Так вот, в каждом файле хранится одна и только одна функция. Имя файла совпадает с именем функции, в нем определенной. В одном файле не может быть определено больше одной функции (хотя файлы без определения функций, или “самовыполняемые”, конечно, существовать могут). Плюсы подобного подхода расписывать я не буду – они и так достаточно очевидны. А вот про минусы хотелось бы поговорить особо…
До версии AutoCAD 2006 загрузка и компиляция организованных подобным образом lisp-кодов особых трудностей не вызывала лично у меня: если их надо загружать, то просто получить весь перечень файлов, к этому списку применить конструкцию вида
(load file)
)
и все работало замечательно. При программном создании проекта компиляции порядок следования имен функций также не имел никакого значения.
Но в 2008 ситуация изменилась. При компиляции fas-файла порядок следования функций становится критичным. Недавно, переустановив AutoCAD 2008, я столкнулся еще и с тем, что порядок загрузки некомпилированных (!) lisp-кодов тоже может вызывать ошибки.
Поясню на примере.
Допустим, есть функция
;;; File : c:\\fun1.lsp
; что-то делает
)
и
;;; File : c:\\fun2.lsp
; что-то делает
(fun1)
; опять что-то делает
)
; и теперь самовызов
(fun2)
В версиях до 2006 включительно конструкции
(load "c:\\fun2.lsp")
и
(load "c:\\fun1.lsp")
были абсолютно равнозначны. В 2008 уже сработает только первый вариант. И это только касательно загрузки! Если в компиляционный файл последовательность загрузки функций будет “не та”, то все, пиши пропало – проект работать не будет.
При всем при этом: если все (подчеркиваю – все!) используемые в проекте коды закинуть в один файл, то все замечательно загружается и компилируется.
Я не знаю – то ли руки у меня кривые, то ли “так и задумывалось”, но выкручиваться пришлось весьма серьезно.
Сначала определимся с задачей и с методикой ее решения.
- Необходимо получить все файлы. Поскольку ситуация, когда коды раскиданы по компьютеру и / или по всей сети, все же выходит за рамки нормальной, предполагаем, что коды лежат каком-то определенном каталоге (и, возможно, структурированы по каким-то подкаталогам. Глубина вложений заранее неизвестна. Количество файлов, кстати, тоже).
- Имена функций отвечают требованиям именования
- Имена файлов, содержащих рабочие коды, тоже опознаются по какой-то маске (в идеале, конечно, надо помнить, что “Имя файла совпадает с именем функции, в нем определенной.” Но идеал идеалом, а жизнь способна подкидывать самые разные сюрпризы). Дополнительно: имена файлов, содержащих незагружаемые функции (временные коды, отладочные коды и т.п.) также можно как-то распознать. Например, по слову debug в имени файла.
- Функция должна быть самодостаточна и содержать в себе все необходимые компоненты, даже если они дублируют уже написанные коды.
Приступим…
Получается, что нам необходимо передавать (мы же делаем универсальный код, который можно будет элементарно переносить с машины на машину!) или 2, или 3 параметра: путь, по которому находятся lsp-файлы; маска имени файла; маска имени функции (которая по идее должна совпадать с маской имен файлов). Оставим, пожалуй, именно 3 параметра:
- Имя каталога: строка, без вариантов. Концевой слеш можно указывать, можно не указывать: сделать обработку несложно, а вот запутаться при последующих вызовах – запросто).
- Маска имени файла: расширение lsp указывать не надо – и так понятно, что нас сейчас интересует именно lisp. А так – получается, что тоже строка, сформированная для применения в wcmatch
- Маска имени функции: фактически то же самое.
Итак, сначала найдем (или напишем, или модифицируем) функцию, которая просто проходит по всем подкаталогам указанного пути и возвращает список файлов по маске и сразу ее переименовываем. Функция у нас будет локальной:
;;; Функция возвращает список файлов указанной маски, находящихся в
;;; заданном каталоге
;;; Параметры вызова:
;;; path путь к корневому каталогу. nil недопустим
;;; mask маска имени файла. nil или список недопустим
;;; Примеры вызова:
;|
(fun_browsefiles-in-directory-nested "c:\\documents" "*.dwg")
;;; На основе кода ZZZ с caduser.ru
|;
(apply
(function append)
(cons
(if (vl-directory-files path mask)
(mapcar (function (lambda (x) (strcat path "\\" x)))
(vl-directory-files path mask)
) ;_ end of mapcar
) ;_ if
(mapcar
(function (lambda (x)
(fun_browsefiles-in-directory-nested
(strcat path "\\" x)
mask
) ;_ end of fun_browsefiles-in-directory-nested
) ;_ end of lambda
) ;_ end of function
(vl-remove
".."
(vl-remove "." (vl-directory-files path nil -1))
) ;_ end of vl-remove
) ;_ mapcar
) ;_ cons
) ;_ end of apply
) ;_ end of defun
Допустим, у нас маска имени файла имеет вид “lispru-*,_lispru-*,dwgru-*,_dwgru-*” (мы же договаривались, что расширение файла не указываем!) И много ли файлов будет отвечать именно этой маске? Мне кажется, что ни одного. Поэтому преобразуем строку в список и обрабатывать будем уже список масок (да, я знаю, что можно было начальный код fun_browsefiles-in-directory-nested сделать другим. Но так же скучно и неинтересно! А функция преобразования строки в список с указанным разделителем нам и так понадобится)
(defun vl-conv-string-to-list (string separator)
;;; Код написан Евгением Елпановым
(cond
((or (= string "") (not string)) nil)
((setq i (vl-string-search separator string))
(cons (substr string 1 i)
(vl-conv-string-to-list
(substr string (+ (strlen separator) 1 i))
separator
) ;_ end of vl-conv-string-to-list
) ;_ end of cons
)
(t (list string))
) ;_ end of cond
) ;_ end of defun
)
Имя этой функции немного “нестандартное”. Я в свое время сознательно пошел на этот шаг: уж больно часто она используется. Параметры вызова в общем-то очевидны: обрабатываемая строка и разделитель.
Ну и “до кучи” напишем функцию обратного преобразования: списка строковых значений в одну строку.
(defun vl-conv-list-to-string (lst sep)
(if lst
(strcat (car lst)
(apply (function strcat)
(mapcar (function (lambda (x) (strcat sep x))) (cdr lst))
) ;_ end of apply
) ;_ end of strcat
""
) ;_ end of if
) ;_ end of defun
) ;_ end of if
В результате вместо
получаем
(function append)
(mapcar
(function (lambda (x) (fun_browsefiles-in-directory-nested path x)))
(mapcar (function (lambda (x) (strcat (vl-filename-base x) ".lsp")))
(vl-conv-string-to-list file-mask ",")
) ;_ end of mapcar
) ;_ end of mapcar
) ;_ end of apply
) ;_ end of setq
Ладно, список файлов мы получили. Но этого мало. Необходимо как минимум определить список вызываемых функций внутри каждого файла. А заодно и имя функции:
(function
(lambda (file / handle lst flag str res)
(setq handle (open file "r"))
(while (setq str (read-line handle))
(if (/= (setq str (vl-string-trim " \t" str)) "")
(cond
((wcmatch (strcase str)
(strcat "(defun *"
) ;_ end of strcat
) ;_ end of wcmatch
)
((and (not flag) (wcmatch str "*;|*"))
(setq flag t)
)
((and flag (not (wcmatch str "*|;*"))))
((and flag (wcmatch str "*|;*")) (setq flag nil))
((and (not flag) (not (wcmatch str "*;|*,*|;*")))
(setq
lst (cons
(vl-string-trim
" "
(if (not (wcmatch str "*\";\"*"))
(car (vl-conv-string-to-list str ";"))
(car (vl-conv-string-to-list str "; "))
) ;_ end of if
) ;_ end of vl-string-trim
lst
) ;_ end of cons
) ;_ end of setq
)
) ;_ end of cond
) ;_ end of if
) ;_ end of while
(close handle)
(if lst
(setq res
(list
(cons "file" file)
(cons
"defun"
(vl-remove-if-not
(function
(lambda (x)
(wcmatch (strcase x t) function-mask)
) ;_ end of lambda
) ;_ end of function
(mapcar
(function
(lambda (x)
(cadr (vl-conv-string-to-list x " "))
) ;_ end of lambda
) ;_ end of function
(vl-remove-if-not
(function
(lambda (x)
(wcmatch (strcase x t) "*(defun *")
) ;_ end of lambda
) ;_ end of function
(reverse lst)
) ;_ end of vl-remove-if-not
) ;_ end of mapcar
) ;(strcase (vl-filename-base file) t))
) ;_ end of cons
(cons
"calls"
(apply
(function append)
(fun_list-dublicates-remove
(mapcar
(function
(lambda (x)
(mapcar
(function
(lambda (b)
(strcase
(vl-string-trim " ()\"" b)
t
) ;_ end of strcase
) ;_ end of lambda
) ;_ end of function
(vl-remove-if-not
(function
(lambda (a)
(wcmatch (strcase a t) function-mask)
) ;_ end of lambda
) ;_ end of function
(vl-conv-string-to-list x " ")
) ;_ end of vl-remove-if-not
) ;_ end of mapcar
) ;_ end of lambda
) ;_ end of function
(vl-remove-if
(function
(lambda (x)
(or (not x)
(not (wcmatch
(strcase x t)
(mapcar
(function (lambda (a)
(strcat "*[ (]" a "*")
) ;_ end of lambda
) ;_ end of function
(vl-conv-string-to-list
function-mask
","
) ;_ end of vl-conv-string-to-list
) ;_ end of mapcar
) ;_ end of wcmatch
) ;_ end of not
) ;_ end of or
) ;_ end of lambda
) ;_ end of function
(reverse lst)
) ;_ end of vl-remove-if
) ;_ end of mapcar
) ;_ end of fun_list-dublicates-remove
) ;_ end of apply
) ;_ end of cons
) ;_ end of list
) ;_ end of setq
) ;_ end of if
) ;_ end of lambda
) ;_ end of function
(apply
(function append)
(mapcar
(function (lambda (x) (fun_browsefiles-in-directory-nested path x)))
(mapcar (function (lambda (x) (strcat (vl-filename-base x) ".lsp")))
(vl-conv-string-to-list file-mask ",")
) ;_ end of mapcar
) ;_ end of mapcar
) ;_ end of apply
) ;_ end of mapcar
Теперь у нас получен список из элементов вида ((”file” . <Имя файла>) (”defun” <Список имен определяемых функций>) (”calls” . <Список вызываемых функций>)). Осталось самое интересное: определить для них приоритет. Те функции, которые не вызывают никакие другие пользовательские функции, получат приоритет, равный 0. Вызывающие 1 функцию – приоритет равен 1. А если функция вызывает функцию, которая вызывает функцию, которая… Почти “Дом, который построил Джек”
Приоритеты вызываемых функций должны суммироваться.
Если с подготовкой все просто:
(function
(lambda (x)
(append
(setq x (subst
(cons "calls"
(vl-remove-if
(function
(lambda (a)
(member a (cdr (assoc "defun" x)))
) ;_ end of lambda
) ;_ end of function
(cdr (assoc "calls" x))
) ;_ end of vl-remove-if
) ;_ end of cons
(assoc "calls" x)
x
) ;_ end of subst
) ;_ end of setq
(list (cons "prior"
(if (or (not (cdr (assoc "calls" x)))
(not (cdr (assoc "defun" x)))
) ;_ end of or
0
) ;_ end of if
) ;_ end of cons
) ;_ end of list
) ;_ end of cons
) ;_ end of lambda
) ;_ end of function
file_list
) ;_ end of mapcar
то с определением приоритета голову немного поломать пришлось. Понятно, что функция должна быть рекурсивной. И это фактически единственное, что понятно сразу.
В общем, вот результат:
(cond
((setq res (cdr (assoc "prior" item))) (1+ res))
(t
(setq calls (strcase
(vl-conv-list-to-string (cdr (assoc "calls" item)) ",")
) ;_ end of strcase
_defun (cdr (assoc "defun" item))
file_list (subst
(subst (cons "prior"
(setq res
(1+
(apply
(function +)
(mapcar
(function
loc:eval-prior
) ;_ end of function
(vl-remove-if-not
(function
(lambda (a)
(or (not (cdr (assoc "defun" a)))
(wcmatch
(strcase
(vl-conv-list-to-string
(cdr (assoc "defun" a))
","
) ;_ end of vl-conv-list-to-string
) ;_ end of strcase
calls
) ;_ end of wcmatch
) ;_ end of or
) ;_ end of lambda
) ;_ end of function
file_list
) ;_ end of vl-remove-if-not
) ;_ end of mapcar
) ;_ end of apply
) ;_ end of 1+
) ;_ end of setq
) ;_ end of cons
(assoc "prior" item)
item
) ;_ end of subst
(if _defun
(car (vl-remove-if-not
(function
(lambda (x)
(= (cdr (assoc "defun" x)) _defun)
) ;_ end of lambda
) ;_ end of function
file_list
) ;_ end of vl-remove-if-not
) ;_ end of car
(car (vl-remove-if-not
(function
(lambda (x)
(= (strcase (vl-conv-list-to-string
(cdr (assoc "calls" x))
","
) ;_ end of vl-conv-list-to-string
) ;_ end of strcase
calls
) ;_ end of =
) ;_ end of lambda
) ;_ end of function
file_list
) ;_ end of vl-remove-if-not
) ;_ end of car
) ;_ end of if
file_list
) ;_ end of subst
) ;_ end of setq
res
)
) ;_ end of cond
) ;_ end of defun
Полностью законченный код выдам чуть позже, и так пост длинный получился…
Материалы для проектирования, работы и разработки (и не только в AutoCAD)
вопрос немножко не по теме: нельзя ли код печатать полностью без полос прокрутки? неудобно печатать, чтобы вникнуть и прочитать. спасибо
Это обусловлено движком сайта. По-другому не получится, как ни крутись.