О кодах, загрузке и компиляции.
Почти худлит получится, я думаю. Но тем не менее, поделиться хочется.
Разговор будет о том, как хранить созданные коды, как их загружать и компилировать. Естественно, что все написанное - сугубо личное мнение, и работать во всех условиях не будет. Но, может быть, кому-то и пригодится. Поехали?
Как уже договаривались в части О форматировании кодов, имена функций будут достаточно уникальными. Но имена функций именами, а в файлы их тоже как-то "засовывать" надо.
Можно, конечно, сохранять коды как "tmp.lsp", "MyCoolCode.lsp" и т.п. Только ориентироваться в них будет нереально. В свое время я принял для себя технологию, прекрасно описанную в книге "САПР на базе AutoCAD - как это делается" (замечу, что книгу можно назвать библией разработчика для AutoCAD. Именно разработчика, а не программиста - понятия все же немного разные в данном контексте). Так вот, в каждом файле хранится одна и только одна функция. Имя файла совпадает с именем функции, в нем определенной. В одном файле не может быть определено больше одной функции (хотя файлы без определения функций, или "самовыполняемые", конечно, существовать могут). Плюсы подобного подхода расписывать я не буду - они и так достаточно очевидны. А вот про минусы хотелось бы поговорить особо...
До версии AutoCAD 2006 загрузка и компиляция организованных подобным образом lisp-кодов особых трудностей не вызывала лично у меня: если их надо загружать, то просто получить весь перечень файлов, к этому списку применить конструкцию вида
1 2 3 | (foreach file lst (load file) ) |
и все работало замечательно. При программном создании проекта компиляции порядок следования имен функций также не имел никакого значения.
Но в 2008 ситуация изменилась. При компиляции fas-файла порядок следования функций становится критичным. Недавно, переустановив AutoCAD 2008, я столкнулся еще и с тем, что порядок загрузки некомпилированных (!) lisp-кодов тоже может вызывать ошибки.
Поясню на примере.
Допустим, есть функция
1 2 3 4 | (defun fun1() ;;; File : c:\\fun1.lsp ; что-то делает ) |
и
1 2 3 4 5 6 7 8 9 | (defun fun2() ;;; File : c:\\fun2.lsp ; что-то делает (fun1) ; опять что-то делает ) ; и теперь самовызов (fun2) |
В версиях до 2006 включительно конструкции
1 2 | (load "c:\\fun1.lsp") (load "c:\\fun2.lsp") |
и
1 2 | (load "c:\\fun2.lsp") (load "c:\\fun1.lsp") |
были абсолютно равнозначны. В 2008 уже сработает только первый вариант. И это только касательно загрузки! Если в компиляционный файл последовательность загрузки функций будет "не та", то все, пиши пропало - проект работать не будет.
При всем при этом: если все (подчеркиваю - все!) используемые в проекте коды закинуть в один файл, то все замечательно загружается и компилируется.
Я не знаю - то ли руки у меня кривые, то ли "так и задумывалось", но выкручиваться пришлось весьма серьезно.
Сначала определимся с задачей и с методикой ее решения.
- Необходимо получить все файлы. Поскольку ситуация, когда коды раскиданы по компьютеру и / или по всей сети, все же выходит за рамки нормальной, предполагаем, что коды лежат каком-то определенном каталоге (и, возможно, структурированы по каким-то подкаталогам. Глубина вложений заранее неизвестна. Количество файлов, кстати, тоже).
- Имена функций отвечают требованиям именования
- Имена файлов, содержащих рабочие коды, тоже опознаются по какой-то маске (в идеале, конечно, надо помнить, что "Имя файла совпадает с именем функции, в нем определенной." Но идеал идеалом, а жизнь способна подкидывать самые разные сюрпризы). Дополнительно: имена файлов, содержащих незагружаемые функции (временные коды, отладочные коды и т.п.) также можно как-то распознать. Например, по слову debug в имени файла.
- Функция должна быть самодостаточна и содержать в себе все необходимые компоненты, даже если они дублируют уже написанные коды.
Приступим...
Получается, что нам необходимо передавать (мы же делаем универсальный код, который можно будет элементарно переносить с машины на машину!) или 2, или 3 параметра: путь, по которому находятся lsp-файлы; маска имени файла; маска имени функции (которая по идее должна совпадать с маской имен файлов). Оставим, пожалуй, именно 3 параметра:
- Имя каталога: строка, без вариантов. Концевой слеш можно указывать, можно не указывать: сделать обработку несложно, а вот запутаться при последующих вызовах - запросто).
- Маска имени файла: расширение lsp указывать не надо - и так понятно, что нас сейчас интересует именно lisp. А так - получается, что тоже строка, сформированная для применения в wcmatch
- Маска имени функции: фактически то же самое.
Итак, сначала найдем (или напишем, или модифицируем) функцию, которая просто проходит по всем подкаталогам указанного пути и возвращает список файлов по маске и сразу ее переименовываем. Функция у нас будет локальной:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | (defun fun_browsefiles-in-directory-nested (path mask) ;;; Функция возвращает список файлов указанной маски, находящихся в ;;; заданном каталоге ;;; Параметры вызова: ;;; 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 сделать другим. Но так же скучно и неинтересно! А функция преобразования строки в список с указанным разделителем нам и так понадобится)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | (if (not vl-conv-string-to-list) (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 ) |
Имя этой функции немного "нестандартное". Я в свое время сознательно пошел на этот шаг: уж больно часто она используется. Параметры вызова в общем-то очевидны: обрабатываемая строка и разделитель.
Ну и "до кучи" напишем функцию обратного преобразования: списка строковых значений в одну строку.
1 2 3 4 5 6 7 8 9 10 11 12 | (if (not vl-conv-list-to-string) (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 |
В результате вместо
1 | (setq file_list (fun_browsefiles-in-directory-nested path file-mask) |
получаем
1 2 3 4 5 6 7 8 9 10 | (setq file_list (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 setq |
Ладно, список файлов мы получили. Но этого мало. Необходимо как минимум определить список вызываемых функций внутри каждого файла. А заодно и имя функции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | (mapcar (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. А если функция вызывает функцию, которая вызывает функцию, которая... Почти "Дом, который построил Джек" Приоритеты вызываемых функций должны суммироваться.
Если с подготовкой все просто:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | (mapcar (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 |
то с определением приоритета голову немного поломать пришлось. Понятно, что функция должна быть рекурсивной. И это фактически единственное, что понятно сразу.
В общем, вот результат:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | (defun loc:eval-prior (item / calls res _defun) (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 |
Полностью законченный код выдам чуть позже, и так пост длинный получился...
вопрос немножко не по теме: нельзя ли код печатать полностью без полос прокрутки? неудобно печатать, чтобы вникнуть и прочитать. спасибо
Это обусловлено движком сайта. По-другому не получится, как ни крутись.
Приветствую Алексей! ----> "Полностью законченный код выдам чуть позже, и так пост длинный получился…"
Где же можно с ним ознакомиться?
Я добавил пост: http://autolisp.ru/2010/10/25/load-compile-write2/
Продолжение следует Правда, сейчас запарка жуткая на работе, когда снова доберусь - не знаю