我有一个Common Lisp读取器宏来解析";或";关系,使用由管道chafoods("|")分隔的中缀语法以及标准列表括号和关键字文字。考虑形式(:a:b|:c)——它表示一个由两部分组成的元组,其中第一个元素肯定是:a,第二个元素是:b或:c。例如,可以推断出整个元组的有效形式是(:a:b)或(:a:c)。
我已经有了函数封装的逻辑,可以在读取宏之后销毁这些元组列表形式。但在读取时,我需要解析一个像:a|:b|:c这样的表单,并用删除的管道对其进行标记,比如(:lazy或:a:b:c)。中缀语法的使用纯粹是为了面向读者的形式;中缀形式是短暂的,在读取阶段会立即被丢弃,以支持标记为:lazy或的等效合法lisp形式。
因此,我制作了一个read宏,它几乎可以按我的意愿工作,但目前需要在第一个或form关键字元素之前使用一个额外的管道,作为一种reader sigil(我希望这根本没有必要),并且它目前不能使用嵌套括号或拼接符号来推断类似的形式作为等价形式(就像在算术中,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)
预期目标是在读取时应用/替换此重写规则:(宏展开'(:a(:b |:c))->(:a(:lazy或:b:c))或者,对于未包含在括号中的变体形式也是如此(一种默认的操作顺序;空格不会有什么区别):(宏展开'(:a:b|:c))->(:a(:lazy或:b:c))(宏展开'(:a:b|:c))->(:a(:lazy或:b:c))嵌套表单应该直观地、递归地呈现:(宏展开'(:a(:b|(:c|:d)))->(:a(:lazy或:b(:la懒或:c:d))
请注意,在基本形式的预期重写规则中--(macroexpand'(:a(:b|:c))->(:a(:lazy或:b:c))--:a不会在或形式中联接,因为它没有中缀管道运算符在那里联接;它存在于元组中或形式的结果旁边。如前所述,这使得在对惰性形式进行进一步评估时,元组可以产生(:a:b)或(:a:c)——以上意味着这两种形式都是以后要去结构的有效形式。
我非常接近。
问题是我不能完全理解,只有以下内容(使用上面的宏):(宏展开'(:a|:b|:c))->(:A(:LAZY或:B:C))(宏展开'(:a:b|:d|:e|:f|:g))->(:A:B'(:LAZY或:D:E:F:G))它实际上完成了我打算做的大部分事情,这是一个功能性的基本解决方案:稍微修改执行规则,允许在读取时在中缀或表单的开头添加一个额外的管道,而不将表单连接到管道的左侧,因此它可以将:b到:c(第一种形式)或:d到:g(第二种形式)转换为:lazy或form列出的有效案例,并使该内部列表本身与非变量值a和b一起成为外部列表/元组的成员。它接近我想要的:(宏展开'(:a:b:d|:e|:f|:g))->应该,不应该->(:A:B'(:LAZY或:D:E:F:G)
有3个错误,按重要性排序:
额外的";前缀";管道需要
在阅读每一个或表格的开头,我想去掉额外的开口管。为了干净和我自己的可读性偏好,我想切割那个管道,并且只在这个读取宏的完全中缀位置使用管道。
在递归解析中向嵌套表单添加了额外的圆括号
它为嵌套表单添加了额外的括号,尽管它在其他方面可以很好地递归处理嵌套表单。
不接受空白
它不接受管道之间的空间。我尝试使用保留空白的read而不是read,但在这里没有用。它应该接受管道和关键字表单之间的空格,就好像它们不存在一样,因为表单
:a|:b
和:a | :b
是等效的。
读取宏主要封装工作逻辑,擅长递归和嵌套形式:(宏展开'(:a|(|:b|:c)|(|;e|:f))->收益率->(:A(:LAZY-OR((:LAZY-OR:B:C))((:LAZY-OR:E:F))
;(几乎完美,完全按照预期递归扩展,这种形式除了需要开口管道之外,唯一的问题是在final周围生成的双括号:lazy或forms)。因此,将从该形式生成相同的一个(当前为"不平衡"读取圆括号错误):(宏展开'(:a(:b|:c)|(:e|:f))->应该,不屈服->(:A(:LAZY或(:lazzy或:B:C)(:laxy-或:E:F))
除了read宏添加了额外的括号,以及它无法在中缀形式中允许间距之外,真正关键的错误是,如果不包括第一个非中缀管道(顺便提一下前缀),就无法将or形式写为中缀管道形式。我真的遇到了一堵砖墙,试图在不需要使用第一个管道字符作为读取解析器的sigil的情况下匹配流。也许再打一两个电话给";peek";函数会给我提供一个更专业的形式来匹配,但我还没能弄清楚如何解析它。
我考虑了围绕现有的和全面的解决方案来构建它,例如NKF("definfix"macro,https://www.cliki.net/infix)和CMU中缀(https://github.com/rigetti/cmu-infix/blob/master/src/cmu-infix.lisp),但由于这些是更通用和更大的代码库,我认为我不需要为更多类型的形式/运算符重用中缀逻辑,只需要这一种。从我对一个相当小的宏的接近程度来看,我肯定更喜欢用一个小而简洁的解决方案来解决它,前提是它仍然是递归的,不容易出错。
任何关于为此目的更有效地使用读取宏的观点都将不胜感激。
我会尝试不同的方法,并使用不同的字符作为备选方案列表,如[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")))
有了以上定义,下面的表格:
[:a|:b|:c]
读作:
(LAZY-OR-PARSE :A OR :B OR :C)
预计lazy-or-parse
是一个宏。它的作用是像常规解析器一样解释令牌列表;您应该能够将它们分组为表达式(例如,像具有定义的关联性/优先级的Pratt解析器)。
或者,你仍然可以在阅读器宏中进行解析,但我个人更喜欢在这个阶段做尽可能少的工作。
注:。您需要调整Emacs以使其理解此语法,对于我使用read-from-string
:的测试
(read-from-string "[a b (c|d)]")
(LAZY-OR-PARSE A B (C OR D))
11
我看了一下,这是我在语法表中更改的内容(这是emacs-lisp代码):
(modify-syntax-entry ?[ "(]" lisp-mode-syntax-table)
(modify-syntax-entry ?] ")[" lisp-mode-syntax-table)
(modify-syntax-entry ?| "_" lisp-mode-syntax-table)
经过这些修改,语法似乎被编辑器识别了,我甚至可以写这篇文章,把光标放在右括号后面,评估最后一个sexp,它就能正确地找到表达式的开头:
'[ a | (c | d)]
=> (LAZY-OR-PARSE A OR (C OR D))