aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Wiegley2015-11-11 09:47:53 -0500
committerTed Zlatanov2015-11-11 09:47:53 -0500
commit710aa042c16c550f1cace936078148d45a405712 (patch)
tree0ee01f4a35c4cc5f71f34a4e4f425c6fc02a8d04
parentef75c3b56b8ff034eb47e0c69328227127cc93fa (diff)
downloademacs-scratch/tzz/import-pl.tar.gz
emacs-scratch/tzz/import-pl.zip
Import pl.el from https://github.com/jwiegley/emacs-plscratch/tzz/import-pl
* pl.el: New library.
-rw-r--r--lisp/emacs-lisp/pl.el180
1 files changed, 180 insertions, 0 deletions
diff --git a/lisp/emacs-lisp/pl.el b/lisp/emacs-lisp/pl.el
new file mode 100644
index 00000000000..5a5d0631751
--- /dev/null
+++ b/lisp/emacs-lisp/pl.el
@@ -0,0 +1,180 @@
1;;; pl --- Combinator parsing library for Emacs, similar to Haskell's Parsec
2
3;; Copyright (C) 2010-2015 Free Software Foundation, Inc.
4
5;; Author: John Wiegley <jwiegley@gmail.com>
6;; Created: 19 Mar 2015
7;; Keywords: languages, lisp, internal, parsing, indentation
8
9;; This program is free software; you can redistribute it and/or
10;; modify it under the terms of the GNU General Public License as
11;; published by the Free Software Foundation; either version 2, or (at
12;; your option) any later version.
13
14;; This program is distributed in the hope that it will be useful, but
15;; WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17;; General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
20;; along with GNU Emacs; see the file COPYING. If not, write to the
21;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22;; Boston, MA 02111-1307, USA.
23
24;;; Commentary:
25
26;; The compatibility version of this library for Emacsen pre-25 will
27;; be maintained at https://github.com/jwiegley/emacs-pl
28
29;; This is a parsing library in the spirit of Haskell's parsec.
30
31;; There are a few parsers, whose job is to inspect whatever is at the current
32;; buffer position, and return zero or more details regarding what was found:
33
34;; pl-ch Match a single character
35;; pl-str Match a string
36;; pl-re Match a regular expression
37;; pl-num Match an integer or floating-point number
38
39;; Other possibilities include: inspecting text properties, overlays, etc.
40
41;; If the parser succeeds, it returns the object matched (a string by default),
42;; and advances point to the next position after the match. Keywords may be
43;; given to return other details:
44
45;; :beg Beginning of the match
46;; :end End of the match
47;; :group N A particular regexp group
48;; :props All properties within the matched region
49;; :nil Return `nil` (same as using `ignore`)
50
51;; If a parser fails, it throws the exception `failed`. This is caught by the
52;; macro `pl-try`, which returns `nil` upon encountering the exception. This
53;; makes it possible to build certain combinators out of these few parts:
54
55;; pl-or Return result from first successful parser
56;; pl-and Return last result, if all parsers succeed
57;; pl-until If the parse fails, advance cursor position by
58;; one character and try again. Keywords can
59;; change the advance amount.
60
61;; Note that even though a parse may fail, and thus return no value, any
62;; side-effects that occur during the course of the parse will of course be
63;; retained. This can be used to good effect, by continuing an action for as
64;; long as a parse succeeds:
65
66;; (pl-parse
67;; (while t
68;; (delete-region (pl-str "<xml>" :beg)
69;; (pl-until
70;; (pl-str "</xml>" :end)))))
71
72;; This will delete blocks demarcated by `<xml>` and `</xml>`, for as long as
73;; such blocks continue to occur contiguously to one another.
74
75(eval-when-compile (require 'cl-lib))
76
77(defgroup pl nil
78 "Combinator parsing library for Emacs, similar to Haskell's Parsec"
79 :group 'development)
80
81(defun pl-ch (ch &rest args)
82 (if (char-equal (char-after) ch)
83 (prog1
84 (cond
85 ((memq :nil args) nil)
86 ((memq :beg args)
87 (point))
88 ((memq :end args)
89 (1+ (point)))
90 (t
91 (char-to-string ch)))
92 (forward-char 1))
93 (throw 'failed nil)))
94
95(defun pl-re (regexp &rest args)
96 (if (looking-at regexp)
97 (prog1
98 (cond
99 ((memq :nil args) nil)
100 ((memq :beg args)
101 (match-beginning 0))
102 ((memq :end args)
103 (match-end 0))
104 ((memq :group args)
105 (let ((group
106 (loop named outer for arg on args
107 when (eq (car arg) :group) do
108 (return-from outer (cadr arg)))))
109 (if group
110 (match-string group)
111 (error "Unexpected regexp :group %s" group))))
112 (t
113 (match-string 0)))
114 (goto-char (match-end 0)))
115 (throw 'failed nil)))
116
117(defsubst pl-str (str &rest args)
118 (pl-re (regexp-quote str)))
119
120(defsubst pl-num (num &rest args)
121 (pl-re (regexp-quote (number-to-string num))))
122
123(defmacro pl-or (&rest parsers)
124 (let ((outer-sym (make-symbol "outer"))
125 (parser-sym (make-symbol "parser")))
126 `(loop named ,outer-sym for ,parser-sym in ',parsers
127 finally (throw 'failed nil) do
128 (catch 'failed
129 (return-from ,outer-sym (eval ,parser-sym))))))
130
131(defmacro pl-try (&rest forms)
132 `(catch 'failed ,@forms))
133
134(defalias 'pl-and 'progn)
135(defalias 'pl-parse 'pl-try
136 "Evaluate some FORMS that define the grammar and act on it.
137
138FORMS are simply S-expressions that typically get evaluated in
139the current buffer. For example:
140
141 (pl-parse
142 (delete-region (pl-str \"<xml>\" :beg)
143 (pl-until
144 (pl-str \"</xml>\" :end))))
145
146For other constructs, such as returning the result of every
147parser as a list, just combine parsers with regular Lisp
148forms (`pl-parse' is just a synonym for `pl-try'):
149
150 (pl-parse
151 (list (pl-str \"Hello\") (pl-str \"World\")))
152")
153
154(defmacro pl-until (parser &optional &key skip)
155 `(catch 'done
156 (while (not (eobp))
157 (catch 'failed
158 (throw 'done ,parser))
159 ,(if skip
160 `(,skip 1)
161 `(forward-char 1)))))
162
163(defmacro pl-many (&rest parsers)
164 (let ((final-sym (make-symbol "final"))
165 (result-sym (make-symbol "result"))
166 (parsers-sym (make-symbol "parsers")))
167 `(let ((,parsers-sym ',parsers)
168 ,result-sym
169 ,final-sym)
170 (while (and ,parsers-sym
171 (setq ,result-sym
172 (catch 'failed
173 (list (eval (car ,parsers-sym))))))
174 (push (car ,result-sym) ,final-sym)
175 (setq ,parsers-sym (cdr ,parsers-sym)))
176 (nreverse ,final-sym))))
177
178(provide 'pl)
179
180;;; pl.el ends here