peg的原生方法使用起来并不那么直观,我对几个主要的宏做了进一步封装,更符合一般的使用直觉。
主要定义了 define-peg-rule+
, peg+
和 peg-run+
这三个新的宏,封装了去掉最后加号的同名宏。
主要优化了:
- 带参数的自定义规则,参数在内部使用时,自动使用 funcall 调用。
- 使用自定义的规则时,参数自动使用 peg 方法包裹生成 peg-matcher。
这样无论是参数还是自定义的规则,都可以在规则中直接使用,而不用考虑它的类型。
使用示例
例子1
(define-peg-rule any-to-pex (pex)
(* (and (not (funcall pex)) (any))))
可以等价替换为:
(define-peg-rule+ any-to-pex (pex)
;; 匹配不满足 PEX 的任意字符为止
(* (and (not pex) (any))))
例子2
(define-peg-rule any-to-eol ()
(any-to-pex (peg (eol))))
可以等价替换为:
(define-peg-rule+ any-to-eol ()
(any-to-pex (eol)))
一个稍微复杂的例子
(define-peg-rule+ group (pex)
;; 捕获 PEX 表达式分组,返回 (start string end)
(list (region (substring pex))))
(peg-run+ (any-to-pex (and (bol) "*"))
(group (and (+ "*") " " (any-to-pex (eol))))
(group (any-to-pex (and (bol) "*"))))
上面定义的这个规则的含义:从当前光标位置开始匹配,分别捕获最近的org标题及其内容,如下图:
如果没有这种封装,需要写成下面的形式,多写了许多 peg 调用,增加心智负担。
(peg-run
(peg (any-to-pex (peg (bol) "*"))
(group (peg (and (+ "*") " " (any-to-pex (peg (eol))))))
(group (peg (any-to-pex (peg (and (bol) "*")))))))
源码
;; -*- lexical-binding: t -*-
(require 'peg)
(require 'dash)
(defun peg--rule-has-param (name)
(when (symbolp name)
(let ((rule-func (intern (concat "peg-rule " (symbol-name name)))))
(and (functionp rule-func)
(consp (help-function-arglist rule-func))))))
(defun peg--rule-tree-depth (tree)
(cond
((not (listp tree)) 0)
((null tree) 1)
(t (1+ (apply #'max 0 (mapcar #'peg--rule-tree-depth tree))))))
(defun peg--rule-args-add-peg-1 (pexs)
(--tree-map-nodes
(and (consp it)
(peg--rule-has-param (car it))
(if (consp (cadr it))
(not (eq 'peg (car (cadr it))))
t))
(cons (car it) (--map (list 'peg it) (cdr it)))
pexs))
(defun peg--rule-args-add-peg (pexs)
(let ((depth (peg--rule-tree-depth pexs)))
(dotimes (i (1- depth))
(setq pexs (peg--rule-args-add-peg-1 pexs)))
pexs))
(defun peg--rule-add-funcall (args pexs)
;; (peg--rule-add-funcall '(matcher) '((list (region (substring matcher)))))
(if args
(--tree-map
(if (member it args)
(list 'funcall it)
it)
pexs)
pexs))
(defmacro define-peg-rule+ (name args &rest pexs)
;; define-peg-rule 如果有参数,参数使用时用 funcall 调用;
;; 如果自定义的规则有参数,在所有参数加上 peg 生成 peg-matcher
(declare (indent defun))
(let ((new-pexs (peg--rule-add-funcall
args (peg--rule-args-add-peg pexs))))
`(define-peg-rule ,name ,args
,@new-pexs)))
(defmacro peg+ (&rest pexs)
`(peg ,@(peg--rule-args-add-peg pexs)))
(defmacro peg-run+ (&rest pexs)
`(peg-run (peg+ ,@pexs)))