Простой скрипт
Вот пример в Common Lisp, предполагая, что ваш ввод находится в файле "/tmp/ex.cad"
(его также можно получить, прочитав поток вывода процесса).
Основной цикл обработки состоит в открытии файла для получения входного потока in
(который автоматически закрывается в конце with-open-file
), циклического перебора всех форм в файле, их обработки и, возможно, вывода их на стандартный вывод. Вы можете усложнить процесс столько, сколько захотите, но достаточно хорошего следующего:
(with-open-file (in #"/tmp/ex.cad")
(let ((*read-eval* nil))
(ignore-errors
(loop (process-form (read in))))))
Предположим, вы хотите увеличить ширину fp_line
записей, игнорировать fp_text
и в противном случае вывести форму без изменений, вы можете определить process-form
следующим образом:
(defun process-form (form)
(destructuring-bind (header . args) form
(print
(case header
(fp_line (let ((width (assoc 'width args)))
(when width (incf (second width) 3)))
form)
(fp_text (return-from process-form))
(t form)))))
Выполнение предыдущего цикла выдаст:
(FP_LINE (START -27.04996 -3.986) (END -27.24996 -3.786) (LAYER F.FAB) (WIDTH 3.1))
(PAD "" NP_THRU_HOLE CIRCLE (AT 35.56 0) (SIZE 3.175 3.175) (DRILL 3.175) (LAYERS *.CU *.MASK) (CLEARANCE 1.5875))
(PAD 96 SMD RECT (AT 1.25 3.08473) (SIZE 0.29972 1.45034) (LAYERS F.CU F.PASTE F.MASK) (CLEARANCE 0.09906))
Больше безопасности
Оттуда вы можете создавать более сложные конвейеры, с помощью сопоставления с образцом или макросов, если хотите. Вы должны принять во внимание некоторые меры безопасности, такие как привязка *read-eval*
к нулю, используя with-standard-io-syntax
и привязка *print-circte*
к T, как предложено tfb , запрещение полностью определенных символов (с помощью #\:
, сигнализирующих об ошибке), и т. д. В конечном счете, как и в случае однострочных сценариев сценариев оболочки, количество мер предосторожности, которые вы добавляете зависит от того, насколько вы доверяете своим данным:
;; Load libraries
(ql:quickload '(:alexandria :optima))
;; Import symbols in current package
(use-package :optima)
(use-package :alexandria)
;; Transform source into a stream
(defgeneric ensure-stream (source)
(:method ((source pathname)) (open source))
(:method ((source string)) (make-string-input-stream source))
(:method ((source stream)) source))
;; make reader stop on illegal characters
(defun abort-reader (&rest values)
(error "Aborting reader: ~s" values))
Специальный пакет для символов KiCad (экспорт не обязателен):
(defpackage :kicad
(:use)
(:export #:fp_text
#:fp_line
#:pad
#:size))
Зацикливание форм:
(defmacro do-forms ((form source &optional result) &body body)
"Loop over forms from source, eventually return result"
(with-gensyms (in form%)
`(with-open-stream (,in (ensure-stream ,source))
(with-standard-io-syntax
(let ((*read-eval* nil)
(*print-circle* t)
(*package* (find-package :kicad))
(*readtable* (copy-readtable)))
(set-macro-character #\: #'abort-reader nil)
(loop
:for ,form% := (read ,in nil ,in)
:until (eq ,form% ,in)
:do (let ((,form ,form%)) ,@body)
:finally (return ,result)))))))
Пример:
;; Print lines at which there is a size parameter, and its value
(let ((line 0))
(labels ((size (alist) (second (assoc 'kicad:size alist)))
(emit (size) (when size (print `(:line ,line :size ,size))))
(process (options) (emit (size options))))
(do-forms (form #P"/tmp/ex.cad")
(match form
((list* 'kicad:fp_text _ _ options) (process options))
((list* 'kicad:fp_line options) (process options))
((list* 'kicad:pad _ _ _ options) (process options)))
(incf line))))
выход
(:LINE 2 :SIZE 3.175)
(:LINE 3 :SIZE 0.29972)