diff options
| author | John Wiegley | 2015-11-11 09:47:53 -0500 |
|---|---|---|
| committer | Ted Zlatanov | 2015-11-11 09:47:53 -0500 |
| commit | 710aa042c16c550f1cace936078148d45a405712 (patch) | |
| tree | 0ee01f4a35c4cc5f71f34a4e4f425c6fc02a8d04 | |
| parent | ef75c3b56b8ff034eb47e0c69328227127cc93fa (diff) | |
| download | emacs-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.el | 180 |
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 | |||
| 138 | FORMS are simply S-expressions that typically get evaluated in | ||
| 139 | the current buffer. For example: | ||
| 140 | |||
| 141 | (pl-parse | ||
| 142 | (delete-region (pl-str \"<xml>\" :beg) | ||
| 143 | (pl-until | ||
| 144 | (pl-str \"</xml>\" :end)))) | ||
| 145 | |||
| 146 | For other constructs, such as returning the result of every | ||
| 147 | parser as a list, just combine parsers with regular Lisp | ||
| 148 | forms (`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 | ||