【问题标题】:Is there an Awk- or Lisp-like programming language that can process a stream of s-expressions?是否有可以处理 s 表达式流的类似 Awk 或 Lisp 的编程语言?
【发布时间】:2019-01-17 05:57:50
【问题描述】:

我最近在 KiCad 中创建了一些 PCB 封装,它们存储在 s-expression 文件中,数据如下所示:

(fp_text user %R (at 0 5.08) (layer F.Fab)
  (effects (font (size 1 1) (thickness 0.15)))
)
(fp_line (start -27.04996 -3.986) (end -27.24996 -3.786) (layer F.Fab) (width 0.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))

我希望能够编写 shell 单行代码来有效地编辑多个参数。我通常会使用 Awk 来完成类似的事情,但是 s 表达式的递归性质使其不适合该任务。我想知道是否有一种带有解释器的编程语言,用于处理管道数据并可以本地处理 s 表达式。也许 Lisp 的数据驱动方言可以做到这一点,但我不知道去哪里找。

总之,我希望能够以类似于 Awk 让我逐行处理数据列的方式对 s 表达式文件进行快速编辑;只有在 s 表达式的情况下,才会逐级执行处理。

示例:找到所有smd 类型为(size 0.29972 1.45034)pad 表达式,并根据其位置重新编号。

【问题讨论】:

标签: awk pipe lisp s-expression


【解决方案1】:

简单脚本

这是 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* 绑定到 nil,使用 with-standard-io-syntax 并按照 tfb 的建议将 *print-circte* 绑定到 T,禁止使用完全限定符号(通过让 #\: 发出错误信号)等等。最终,就像 Shell 脚本单行一样,您需要采取的预防措施add 取决于您对输入的信任程度:

;; 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)

【讨论】:

  • 虽然这是一个很好的例子,但防战版本可能应该使用with-standard-io-syntax 并将*print-circle* 绑定到T,以防输入中存在循环毒素。
【解决方案2】:

只需编写一个简单的 Lisp 或 Scheme 脚本,它会循环读取并根据需要递归处理您的 s-expr。在 Linux 上,我建议使用 Guile(一个很好的 Scheme 解释器)或者 Clisp(一个简单的 Common Lisp 实现)甚至 SBCL(一个非常强大的 Common Lisp)。

(你可能会考虑DSSSL,但在你的情况下它是矫枉过正的)

请注意,您的示例输入不是S-expression,因为(layer F.Fab) 不是一个(因为在点之后您应该有另一个s 表达式,而不是像Fab 这样的原子) .我想这是一个错字,应该是(layer "F.Fab");或者您的 KiCad 软件可能不处理 S 表达式,而是受 S 表达式启发的一些其他输入语言(应该指定,可能在 EBNF 表示法中)。

另请注意,KiCad 是一款免费软件,并拥有一个包含forums 的社区和一个邮件列表。也许你应该在那里问你的实际问题?

PS。我们不知道您的想法是什么转换,但 Scheme 和 Common Lisp 确实适合此类任务。在大多数情况下,它们的代码非常简单(可能只有几行)。

【讨论】:

  • 实际上,示例数据是从 kicad_mod 文件逐字复制的,但我只是检查了一下,它可以读取引号中的图层名称。但是,在 GUI 中保存后引号会被删除。
  • 这意味着 KiCad 使用 s 表达式,而是使用接近它们的东西。也许将 F.Fab 重命名为 F_Fab 可能会有所帮助(因为该文件将成为 S 表达式)
  • 我之所以去这里而不是 KiCad 论坛,是因为这不是 KiCad 特定的问题。我以 KiCad 足迹文件为例,但认为这也可以用于处理其他类似结构的文件。
  • 在提到基于 Lisp 和 Lisp-lipe 的东西时,可能还包括 CLIPS clipsrules.sourceforge.net
  • 至少在 Common Lisp 中,拥有一个名称中包含点的符号是绝对有效的。仅当您只想将点作为符号名称时,才需要对其进行转义(\.|.|)。
猜你喜欢
  • 2013-07-29
  • 2011-04-13
  • 2019-11-26
  • 2019-10-23
  • 2011-02-02
  • 2012-04-14
  • 2018-03-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多