├── 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 | --------------------------------------------------------------------------------