用于“惰性中缀或”的通用 Lisp 读取宏,以解构关键字

Wil*_*ark 2 macros common-lisp infix-notation infix-operator reader-macro

我有一个 Common Lisp 阅读器宏来解析“或”关系的惰性/延迟声明,使用由管道符(“|”)分隔的中缀语法以及标准列表括号和关键字文字。考虑形式 (:a :b|:c) - 它表示一个 2 部分元组,其中第一个元素肯定是 :a,第二个元素是 :b 或 :c。例如,可以推断整个元组的有效形式是 (:a :b) 或 (:a :c)。

我已经有函数封装的逻辑来解构 read 宏之后的这些元组列表形式。但在阅读时,我需要解析像 :a|:b|:c 这样的形式,并用移除的管道标记它,比如 (:lazy-or :a :b :c)。中缀语法的使用纯粹是为了面向读者的形式;中缀形式是短暂的,会在阅读阶段立即被丢弃,取而代之的是用 :lazy-or 标记的等效合法 lisp 形式。

所以我制作了一个几乎可以正常工作的 read 宏,但目前需要在第一个 or-form 关键字元素之前使用一个额外的管道作为一种读者标志(我希望这不是必需的完全),并且它目前无法使用嵌套括号或拼接符号推断类似形式作为等效(如在算术中,2+(3*4)具有相同的运算顺序,等效形式,如2+3*4)。

宏(源自此处的“斜线阅读器”:http : //www.lispworks.com/documentation/HyperSpec/Body/f_rd_rd.htm):

 (defun pipe-reader (stream char)                                                                   
   (declare (ignore char))                                                                          
   `(:lazy-or .  ,(loop for dir = (read stream t nil t)                       
                   then (progn (read-char stream t nil t)                                           
                               (read stream t nil t))                         
                   collect dir                                                                      
                   while (eql (peek-char nil stream nil nil t) #\|))))                              
(set-macro-character #\| #'pipe-reader)  
Run Code Online (Sandbox Code Playgroud)

预期目标是在读取时应用/替换此重写规则: (macroexpand '(:a (:b | :c))) -> (:a (:lazy-or :b :c)) 或,对于未包含在括号中的变体形式(一种默认的操作顺序;空格不会产生影响)也是如此: (macroexpand '(:a :b|:c)) -> (:a (: lazy-or :b :c)) (macroexpand '(:a :b | :c)) -> (:a (:lazy-or :b :c)) 嵌套表单应该直观地、递归地呈现: (macroexpand ' (:a (:b | (:c | :d)))) -> (:a (:lazy-or :b (:lazy-or :c :d)))

请注意,在基本形式的预期重写规则中 -- (macroexpand '(:a (:b | :c))) -> (:a (:lazy-or :b :c)) -- :a 不会以 or 形式加入,因为它没有中缀管道运算符来加入它;它以一种元组形式与 or 形式的结果一起存在。如前所述,这是这样的,在进一步评估惰性形式时,可以想象元组可能会产生 (:a :b) 或 (:a :c) - 以上意味着两者都是稍后解构的有效形式。

我非常接近。

问题是我不能完全理解,只有以下(使用上面的宏): (macroexpand '(:a |:b|:c )) -> (:A (:LAZY-OR :B :C) ) (macroexpand '(:a :b |:d|:e|:f|:g)) -> (:A :B '(:LAZY-OR :D :E :F :G)) 它实际上做的最多在我打算做的事情中,这是一个功能性的基本解决方案:稍微修改执行规则以允许在读取时在中缀或表单的开头添加一个额外的管道,而无需将表单连接到该管道的左侧,因此它可以使 :b 到 :c(第一种形式)或 :d 到 :g(第二种形式)成为为 :lazy-or 形式列出的有效情况,并使内部列表本身成为外部列表的成员/元组与非变量值 :a 和 :b。它接近我想要的: (macroexpand '(:a :b :d|:e|:f|:g)) -> 应该,不 -> (:A :B '(:LAZY-OR :D :E :F :

有 3 个错误,按重要性排序:

  1. 需要额外的“前缀”管道

    额外的开放管道,在每个 or-form 被读取的开头,我想没有。我想切割该管道以保持清洁和我自己在可读性方面的偏好,并且仅在此读取宏的完全中缀位置使用管道。

  2. 在递归解析的嵌套表单中添加了额外的括号

    它向嵌套表单添加了额外的括号,尽管它可以很好地递归处理嵌套表单。

  3. 不接受任意空格

    它不接受管道之间的空间。我尝试使用 read-preserving-whitespace 而不是 read,但在这里无济于事。它应该接受管道和关键字形式之间的空格,就好像它们不存在一样,因为形式:a|:b:a | :b是等效的。

read 宏主要封装了工作逻辑,擅长递归和嵌套形式:(macroexpand '(:a |(|:b|:c)|(|:e|:f))) -> yields -> (:A (:LAZY-OR ((:LAZY-OR :B :C)) ((:LAZY-OR :E :F))))
;(几乎完美,完全按照预期递归扩展,唯一的问题除了需要打开管道之外,形式是在最终的 :lazy-or 形式周围生成的双括号)。因此,从这种形式(当前是“不平衡的”读取括号错误)生成相同的结果:(macroexpand '(:a (:b | :c) | (:e | :f))) -> 应该,是否不产生 -> (:A (:LAZY-OR (:LAZY-OR :B :C) (:LAZY-OR :E :F)))

除了 read 宏添加的额外括号,以及它无法在中缀形式中允许空格之外,真正关键的错误是无法将 or 形式编写为中缀管道形式而不包括第一个非中缀管道(顺便说一下前缀)。我真的遇到了试图匹配流的砖墙,而无需使用第一个管道字符作为读取解析器的一种符号。也许对其中一个“窥视”函数进行一两次额外的调用会给我一个更专业的形式来匹配,但我一直无法弄清楚如何解析它。

我着眼于围绕现有的综合解决方案构建它,例如 NKF(“definfix”宏,https: //www.cliki.net/infix )和 CMU infix(https://github.com/rigetti/cmu-infix/blob /master/src/cmu-infix.lisp),但由于它们是更通用和更大的代码库,我认为我不需要为更多类型的表单/运算符重用中缀逻辑,只是这个。从我对一个相当小的宏的了解程度来看,我肯定更喜欢用一个小而简洁的解决方案来解决它,前提是它仍然是递归的并且不容易出错。

任何有关为此目的更有效地使用读取宏的观点都将不胜感激。

cor*_*ump 5

我会尝试不同的方法并使用不同的字符作为替代列表,例如[a|b],这样您就不需要前缀栏。

例如:

;; a bar is now the symbol 'or
(set-macro-character #\| (constantly 'or))

;; read a list of forms for alternative forms
(set-macro-character #\[
                     (lambda (stream char)
                       (declare (ignore char))
                       `(lazy-or-parse
                         ,@(read-delimited-list #\] stream t))))

;; this one is required too so that e.g. `a]` is not read as a single symbol.
(set-macro-character #\]
                     (lambda (stream char)
                       (declare (ignore char))
                       (error "Unmatched closing bracket")))
Run Code Online (Sandbox Code Playgroud)

有了上面的定义,形式如下:

[:a|:b|:c]
Run Code Online (Sandbox Code Playgroud)

读作:

(LAZY-OR-PARSE :A OR :B OR :C)
Run Code Online (Sandbox Code Playgroud)

预计这lazy-or-parse是一个宏。它的作用是解释一个标记列表,就像一个普通的解析器;您应该能够将它们分组为表达式(例如,具有定义的关联性/优先级的 Pratt 解析器)。

或者,您仍然可以在 reader 宏中进行解析,但我个人更喜欢在此阶段做最少的工作。

注意。对于我使用的测试,您需要调整 Emacs 以使其理解此语法read-from-string

(read-from-string "[a b (c|d)]")
(LAZY-OR-PARSE A B (C OR D))
11
Run Code Online (Sandbox Code Playgroud)

看了一点,这是我在语法表中更改的内容(这是 emacs-lisp 代码):

(modify-syntax-entry ?\[ "(]" lisp-mode-syntax-table)
(modify-syntax-entry ?\] ")[" lisp-mode-syntax-table)
(modify-syntax-entry ?| "_" lisp-mode-syntax-table)
Run Code Online (Sandbox Code Playgroud)

通过这些修改,编辑器似乎可以识别语法,我什至可以这样写,将光标放在右括号之后,评估 last-sexp 并正确找到表达式的开头:

'[ a | (c | d)]
=> (LAZY-OR-PARSE A OR (C OR D))
Run Code Online (Sandbox Code Playgroud)