在Emacs Lisp中解析

13

我正在用Emacs Lisp编写解析器,它是用于解析类似以下文本文件的解析器:

rule:
  int: 1, 2, 3, ...
  string: and, or, then, when
  text:
  ----------
  Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Pellentesque
  in tellus. In pharetra consequat augue. In congue. Curabitur
  pellentesque iaculis eros. Proin magna odio, posuere sed, commodo nec,
  varius nec, tortor.
  ----------
  more: ...

rule:
  ...

我不关心键(key)的类型(int, string等),只想要值(value)。因此对于上面那个文件,int有值"1, 2, 3, ...",string有值"and, or, then, when",text有值"Lorem ..." (不包括破折号)。

我在考虑两种不同的解决方案,但我不知道哪种更好。我应该:

  1. 创建一个简单的解析器,循环遍历所有行,并针对每一行使用一些正则表达式进行匹配,然后分组提取出我想要的部分?

  2. 使用词法分析器和语法分析器进行更复杂的解析?

目前这些文件非常简单,我想不需要像第二个选项那样采用高级工具。但是这些文件可能会变得更加复杂,所以我希望能够轻松扩展。

您会如何解决这个问题?


看起来你正在重新发明YAML。 - myfreeweb
7
我并没有发明什么。它们是来自风站的日志文件。不过它们看起来有点像YAML。 - rejeep
3个回答

14

你是否已经熟悉了递归下降解析器?它们相对容易用你喜欢的编程语言手写,其中包括Emacs Lisp。对于非常简单的解析,你通常可以使用looking-atsearch-forward。这些也将成为任何递归下降解析器或其他类型解析器调用的标记化例程的基础。

[2009年2月11日] 我在下面添加了一个示例Emacs Lisp递归下降解析器。它解析包括加法、减法、乘法、除法、指数和括号子表达式在内的简单算术表达式。现在,它假设所有令牌都在全局变量*tokens*中,但如果您根据需要修改gettokpeektok,就可以让它们遍历缓冲区。要按原样使用它,请尝试以下操作:

(setq *token* '( 3 ^ 5 ^ 7 + 5 * 3 + 7 / 11))
(rdh/expr)
=> (+ (+ (^ 3 (^ 5 7)) (* 5 3)) (/ 7 11))

解析代码如下。

(defun gettok ()
  (and *token* (pop *token*)))
(defun peektok ()
  (and *token* (car *token*)))

(defun rdh/expr ()
  (rdh/expr-tail (rdh/factor)))

(defun rdh/expr-tail (expr)
  (let ((tok (peektok)))
    (cond ((or (null tok)
           (equal tok ")"))
       expr)
      ((member tok '(+ -))
       (gettok)
       (let ((fac (rdh/factor)))
         (rdh/expr-tail (list tok expr fac))))
      (t (error "bad expr")))))

(defun rdh/factor ()
  (rdh/factor-tail (rdh/term)))

(defun rdh/factor-tail (fac)
  (let ((tok (peektok)))
    (cond ((or (null tok)
           (member tok '(")" + -)))
       fac)
      ((member tok '(* /))
       (gettok)
       (let ((term (rdh/term)))
         (rdh/factor-tail (list tok fac term))))
      (t (error "bad factor")))))

(defun rdh/term ()
  (let* ((prim (rdh/prim))
         (tok (peektok)))
    (cond ((or (null tok)
               (member tok '(")" + - / *)))
           prim)
          ((equal tok '^)
           (gettok)
           (list tok prim (rdh/term)))
          (t (error "bad term")))))

(defun rdh/prim ()
  (let ((tok (gettok)))
    (cond ((numberp tok) tok)
      ((equal tok "(")
       (let* ((expr (rdh/expr))
          (tok (peektok)))
         (if (not (equal tok ")"))
         (error "bad parenthesized expr")
           (gettok)
           expr)))
      (t (error "bad prim")))))

我之前找到了递归下降解析器页面。可惜C语言示例不完整,无法测试。但我认为这是在我的情况下实现解析器的好方法。你知道有没有Lisp语言的示例吗? - rejeep
非常感谢您提供的示例!但是我无法运行它:调试器进入 - Lisp 错误:(wrong-type-argument symbolp (tok (peektok)))。 - rejeep
抱歉,我试过了,真的。不过我最后改了一点东西就忘记再测试了。我已经更新代码了,现在应该可以正常工作了。 - Dale Hagglund
不知道我是否做错了什么。但是我仍然无法让它工作。在函数rdh/term中,term变量是什么? - rejeep
我认为这是你问题的最佳答案! - e19293001
显示剩余5条评论

5

如果需要解析器相关的内容,可以查看CEDET项目的Semantic库。


我确实查看了那个。不过似乎有点过头了。我猜要学习很多才能用它做些有用的事情。 - rejeep
如果您认为代码会变得复杂,CEDET确实是一种可行的选择。CEDET已经被添加到即将发布的GNU Emacs 23.2中,因此它是官方支持的前进方式。这完全取决于语法的复杂程度以及您期望格式扩展的程度。除非您非常确定语法不会变得更加复杂,否则我建议使用CEDET的Semantic。 - haxney

5

在Emacs Wiki上可以找到一个相对简单的解析器: ParserCompiler

Emacs的解析器编译器可以使用纯elisp创建递归下降解析器。

该项目的目标是创建一个有创新性且实用的解析器编译器。这是由Mike Mattie - codermattie@gmail.com创建的原创作品。

解析器是通过一个宏来编译的,该宏将解析器定义DSL转换为纯elisp。目前支持PEG语法类。


如果我使用一个解析器编译器,我是否需要将该库与我的代码一起包含?我希望避免使用外部库并手动编写解析器。 - rejeep
@rejeep 是的,你必须包含这个库。 - Trey Jackson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接