├── README.md
└── pl.el
/README.md:
--------------------------------------------------------------------------------
1 | This is a parsing library in the spirit of Haskell's parsec. For example:
2 |
3 | ``` elisp
4 | (pl-parse
5 | (delete-region (pl-str "" :beg)
6 | (pl-until
7 | (pl-str "" :end))))
8 | ```
9 |
10 | There are a few parsers, whose job is to inspect whatever is at the current
11 | buffer position, and return zero or more details regarding what was found:
12 |
13 | pl-ch Match a single character
14 | pl-str Match a string
15 | pl-re Match a regular expression
16 | pl-num Match an integer or floating-point number
17 |
18 | Other possibilities include: inspecting text properties, overlays, etc.
19 |
20 | If the parser succeeds, it returns the object matched (a string by default),
21 | and advances point to the next position after the match. Keywords may be
22 | given to return other details:
23 |
24 | :beg Beginning of the match
25 | :end End of the match
26 | :group N A particular regexp group
27 | :props All properties within the matched region
28 | :nil Return `nil` (same as using `ignore`)
29 |
30 | If a parser fails, it throws the exception `failed`. This is caught by the
31 | macro `pl-try`, which returns `nil` upon encountering the exception. This
32 | makes it possible to build certain combinators out of these few parts:
33 |
34 | pl-or Return result from first successful parser
35 | pl-and Return last result, if all parsers succeed
36 | pl-until If the parse fails, advance cursor position by
37 | one character and try again. Keywords can
38 | change the advance amount.
39 |
40 | For other constructs, such as returning the result of every parser as a list, just
41 | combine parsers with regular Lisp forms (`pl-parse` is just a synonym for
42 | `pl-try`):
43 |
44 | ``` elisp
45 | (pl-parse
46 | (list (pl-str "Hello") (pl-str "World")))
47 | ```
48 |
49 | Note that even though a parse may fail, and thus return no value, any
50 | side-effects that occur during the course of the parse will of course be
51 | retained. This can be used to good effect, by continuing an action for as
52 | long as a parse succeeds:
53 |
54 | ``` elisp
55 | (pl-parse
56 | (while t
57 | (delete-region (pl-str "" :beg)
58 | (pl-until
59 | (pl-str "" :end)))))
60 | ```
61 |
62 | This will delete blocks demarcated by `` and ``, for as long as
63 | such blocks continue to occur contiguously to one another.
64 |
--------------------------------------------------------------------------------
/pl.el:
--------------------------------------------------------------------------------
1 | ;;; pl --- Combinator parsing library for Emacs, similar to Haskell's Parsec
2 |
3 | ;; Copyright (C) 2015 John Wiegley
4 |
5 | ;; Author: John Wiegley
6 | ;; Created: 19 Mar 2015
7 | ;; Version: 1.0
8 | ;; Keywords: parsing lisp devel
9 | ;; X-URL: https://github.com/jwiegley/emacs-pl
10 |
11 | ;; This program is free software; you can redistribute it and/or
12 | ;; modify it under the terms of the GNU General Public License as
13 | ;; published by the Free Software Foundation; either version 2, or (at
14 | ;; your option) any later version.
15 |
16 | ;; This program is distributed in the hope that it will be useful, but
17 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 | ;; General Public License for more details.
20 |
21 | ;; You should have received a copy of the GNU General Public License
22 | ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 | ;; Boston, MA 02111-1307, USA.
25 |
26 | ;;; Commentary:
27 |
28 | ;; Please see the README
29 |
30 | (eval-when-compile
31 | (require 'cl))
32 | (require 'cl-lib)
33 |
34 | (defgroup pl nil
35 | "Combinator parsing library for Emacs, similar to Haskell's Parsec"
36 | :group 'development)
37 |
38 | (defun pl-ch (ch &rest args)
39 | (if (char-equal (char-after) ch)
40 | (prog1
41 | (cond
42 | ((memq :nil args) nil)
43 | ((memq :beg args)
44 | (point))
45 | ((memq :end args)
46 | (1+ (point)))
47 | (t
48 | (char-to-string ch)))
49 | (forward-char 1))
50 | (throw 'failed nil)))
51 |
52 | (defun pl-re (regexp &rest args)
53 | (if (looking-at regexp)
54 | (prog1
55 | (cond
56 | ((memq :nil args) nil)
57 | ((memq :beg args)
58 | (match-beginning 0))
59 | ((memq :end args)
60 | (match-end 0))
61 | ((memq :group args)
62 | (let ((group
63 | (loop named outer for arg on args
64 | when (eq (car arg) :group) do
65 | (return-from outer (cadr arg)))))
66 | (if group
67 | (match-string group)
68 | (error "Unexpected regexp :group %s" group))))
69 | (t
70 | (match-string 0)))
71 | (goto-char (match-end 0)))
72 | (throw 'failed nil)))
73 |
74 | (defsubst pl-str (str &rest args)
75 | (pl-re (regexp-quote str)))
76 |
77 | (defsubst pl-num (num &rest args)
78 | (pl-re (regexp-quote (number-to-string num))))
79 |
80 | (defmacro pl-or (&rest parsers)
81 | (let ((outer-sym (make-symbol "outer"))
82 | (parser-sym (make-symbol "parser")))
83 | `(loop named ,outer-sym for ,parser-sym in ',parsers
84 | finally (throw 'failed nil) do
85 | (catch 'failed
86 | (return-from ,outer-sym (eval ,parser-sym))))))
87 |
88 | (defmacro pl-try (&rest forms)
89 | `(catch 'failed ,@forms))
90 |
91 | (defalias 'pl-and 'progn)
92 | (defalias 'pl-parse 'pl-try)
93 |
94 | (defmacro pl-until (parser &optional &key skip)
95 | `(catch 'done
96 | (while (not (eobp))
97 | (catch 'failed
98 | (throw 'done ,parser))
99 | ,(if skip
100 | `(,skip 1)
101 | `(forward-char 1)))))
102 |
103 | (defmacro pl-many (&rest parsers)
104 | (let ((final-sym (make-symbol "final"))
105 | (result-sym (make-symbol "result"))
106 | (parsers-sym (make-symbol "parsers")))
107 | `(let ((,parsers-sym ',parsers)
108 | ,result-sym
109 | ,final-sym)
110 | (while (and ,parsers-sym
111 | (setq ,result-sym
112 | (catch 'failed
113 | (list (eval (car ,parsers-sym))))))
114 | (push (car ,result-sym) ,final-sym)
115 | (setq ,parsers-sym (cdr ,parsers-sym)))
116 | (nreverse ,final-sym))))
117 |
118 | (provide 'pl)
119 |
120 | ;;; pl.el ends here
121 |
--------------------------------------------------------------------------------