[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/peg 9a8d239: * peg.el: New official entry points `peg`
From: |
Stefan Monnier |
Subject: |
[elpa] externals/peg 9a8d239: * peg.el: New official entry points `peg` and `peg-run` |
Date: |
Mon, 18 Mar 2019 11:59:03 -0400 (EDT) |
branch: externals/peg
commit 9a8d2396e7a63914df4df460a96bc8caaa14e7eb
Author: Stefan Monnier <address@hidden>
Commit: Stefan Monnier <address@hidden>
* peg.el: New official entry points `peg` and `peg-run`
(peg--actions, peg--errors): Move earlier in the file.
(peg-parse): Use peg-run and peg-signal-failure.
(peg-parse-p, peg--parse-no-error): Remove.
(peg): Rename from `peg-rule-ref`.
(peg-run): New function.
(define-peg-rule): Don't forget to make the definition known during
compilation, for the benefit of cycle detection.
(peg-parse-exp): Use parse-run.
(peg-signal-failure): New function.
(peg-parse-at-point): Use peg-run. Declare obsolete.
(peg-postprocess): Skip one `reverse` call.
Return t instead of an empty stack, so the result is never nil.
* peg-tests.el (peg-parse-string): Move from peg.el.
---
peg-tests.el | 47 +++++++++++------
peg.el | 168 +++++++++++++++++++++++++++++++----------------------------
2 files changed, 119 insertions(+), 96 deletions(-)
diff --git a/peg-tests.el b/peg-tests.el
index 98dbe43..bb47968 100644
--- a/peg-tests.el
+++ b/peg-tests.el
@@ -26,6 +26,21 @@
;;; Tests:
+(defmacro peg-parse-string (pex string &optional noerror)
+ "Parse STRING according to PEX.
+If NOERROR is non-nil, push nil resp. t if the parse failed
+resp. succeded instead of signaling an error."
+ (let ((oldstyle (consp (car-safe pex)))) ;PEX is really a list of rules.
+ `(with-temp-buffer
+ (insert ,string)
+ (goto-char (point-min))
+ ,(if oldstyle
+ `(with-peg-rules ,pex
+ (peg-run (peg ,(caar pex))
+ ,(unless noerror '#'peg-signal-failure)))
+ `(peg-run (peg ,pex)
+ ,(unless noerror '#'peg-signal-failure))))))
+
(define-peg-rule peg-test-natural ()
[0-9] (* [0-9]))
@@ -126,18 +141,18 @@
(should (equal (with-temp-buffer
(save-excursion (insert "abcdef"))
(list
- (peg-parse (x "a"
+ (peg-run (peg "a"
(replace "bc" "x")
(replace "de" "y")
"f"))
(buffer-string)))
- '(nil "axyf")))
+ '(t "axyf")))
(with-temp-buffer
(insert "toro")
(goto-char (point-min))
- (should (peg-parse-p "to"))
- (should-not (peg-parse-p "to"))
- (should (peg-parse-p "ro"))
+ (should (peg-run (peg "to")))
+ (should-not (peg-run (peg "to")))
+ (should (peg-run (peg "ro")))
(should (eobp)))
)
@@ -153,9 +168,10 @@
;; 2) [0-9] is the character range from 0 to 9. This can also be
;; written as (range ?0 ?9). Note that 0-9 is a symbol.
(defun peg-ex-recognize-int ()
- (peg-parse (number sign digit (* digit))
- (sign (or "+" "-" ""))
- (digit [0-9])))
+ (with-peg-rules ((number sign digit (* digit))
+ (sign (or "+" "-" ""))
+ (digit [0-9]))
+ (peg-run (peg number))))
;; peg-ex-parse-int recognizes integers and computes the corresponding
;; value. The grammer is the same as for `peg-ex-recognize-int'
@@ -173,13 +189,14 @@
;; The action `(sign val -- (* sign val)), multiplies val with the
;; sign (1 or -1).
(defun peg-ex-parse-int ()
- (peg-parse (number sign digit (* digit
- `(a b -- (+ (* a 10) b)))
- `(sign val -- (* sign val)))
- (sign (or (and "+" `(-- 1))
- (and "-" `(-- -1))
- (and "" `(-- 1))))
- (digit [0-9] `(-- (- (char-before) ?0)))))
+ (with-peg-rules ((number sign digit (* digit
+ `(a b -- (+ (* a 10) b)))
+ `(sign val -- (* sign val)))
+ (sign (or (and "+" `(-- 1))
+ (and "-" `(-- -1))
+ (and "" `(-- 1))))
+ (digit [0-9] `(-- (- (char-before) ?0))))
+ (peg-run (peg number))))
;; Put point after the ) and press C-x C-e
;; (peg-ex-parse-int)-234234
diff --git a/peg.el b/peg.el
index 48479b6..d71c707 100644
--- a/peg.el
+++ b/peg.el
@@ -5,7 +5,7 @@
;; Author: Helmut Eller <address@hidden>
;; Maintainer: Stefan Monnier <address@hidden>
;; Package-Requires: ((emacs "25"))
-;; Version: 0.9.1
+;; Version: 1.0
;;
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
@@ -35,10 +35,15 @@
;; E.g. we can match integers with:
;;
;; (with-peg-rules
-;; ((number sign digit (* digit))
-;; (sign (or "+" "-" ""))
-;; (digit [0-9]))
-;; (peg-parse number))
+;; ((number sign digit (* digit))
+;; (sign (or "+" "-" ""))
+;; (digit [0-9]))
+;; (peg-run (peg number)))
+;; or
+;; (define-peg-rule digit ()
+;; [0-9])
+;; (peg-parse (number sign digit (* digit))
+;; (sign (or "+" "-" "")))
;;
;; In contrast to regexps, PEGs allow us to define recursive "rules".
;; A "grammar" is a set of rules. A rule is written as (NAME PEX...)
@@ -74,7 +79,7 @@
;; Beginning-of-Symbol (bos)
;; End-of-Symbol (eos)
;;
-;; `peg-parse' also supports parsing actions, i.e. Lisp snippets which
+;; PEXs also support parsing actions, i.e. Lisp snippets which
;; are executed when a pex matches. This can be used to construct
;; syntax trees or for similar tasks. Actions are written as
;;
@@ -159,27 +164,71 @@
(eval-when-compile (require 'cl-lib))
+(defvar peg--actions nil
+ "Actions collected along the current parse.
+Used at runtime for backtracking. It's a list ((POS . THUNK)...).
+Each THUNK is executed at the corresponding POS. Thunks are
+executed in a postprocessing step, not during parsing.")
+
+(defvar peg--errors nil
+ "Data keeping track of the rightmost parse failure location.
+It's a pair (POSITION . EXPS ...). POSITION is the buffer position and
+EXPS is a list of rules/expressions that failed.")
+
;;;; Main entry points
+;; Sometimes (with-peg-rule ... (peg-run (peg ...))) is too
+;; longwinded for the task at hand, so `peg-parse' comes in handy.
(defmacro peg-parse (&rest pexs)
"Match PEXS at point.
PEXS is a sequence of PEG expressions, implicitly combined with `and'.
-Return STACK if the match succeed and signals an error on failure,
-moving point along the way."
+Returns STACK if the match succeed and signals an error on failure,
+moving point along the way.
+PEXS can also be a list of PEG rules, in which case the first rule is used."
(if (and (consp (car pexs))
(symbolp (caar pexs))
(not (ignore-errors (peg-normalize (car pexs)))))
- ;; Backward compatibility with old usage where the args were
- ;; a list of rules and we used the first rule as entry point.
- `(with-peg-rules ,pexs (peg-parse-at-point (peg-rule-ref ,(caar pexs))))
- `(peg-parse-at-point (peg-rule-ref ,@pexs))))
+ ;; `pexs' is a list of rules: use the first rule as entry point.
+ `(with-peg-rules ,pexs (peg-run (peg ,(caar pexs)) #'peg-signal-failure))
+ `(peg-run (peg ,@pexs) #'peg-signal-failure)))
-(defmacro peg-parse-p (&rest pexs)
- "Match PEXS at point.
-Like `peg-parse' but returns a boolean indicating success, instead of
-signaling an error on failure.
-PEXS is a sequence of PEG expressions, implicitly combined with `and'."
- `(peg--parse-no-error (peg-rule-ref ,@pexs)))
+(defmacro peg (&rest pexs)
+ "Return a PEG-matcher that matches PEXS."
+ (pcase (peg-normalize `(and . ,pexs))
+ (`(call ,name) `#',(peg--rule-id name)) ;Optimize this case by η-reduction!
+ (exp `(lambda () ,(peg-translate-exp exp)))))
+
+;; There are several "infos we want to return" when parsing a given PEX:
+;; 1- We want to return the success/failure of the parse.
+;; 2- We want to return the data of the successful parse (the stack).
+;; 3- We want to return the diagnostic of the failures.
+;; 4- We want to perform the actions (upon parse success)!
+;; `peg-parse' used an error signal to encode the (1) boolean, which
+;; lets it return all the info conveniently but the error signal was sometimes
+;; inconvenient. Other times one wants to just know (1) maybe without even
+;; performing (4).
+;; `peg-run' lets you choose all that, and by default gives you
+;; (1) as a simple boolean, while also doing (2), and (4).
+
+(defun peg-run (peg-matcher &optional failure-function success-function)
+ "Parse with PEG-MATCHER at point and run the success/failure function.
+If a match was found, move to the end of the match and call SUCCESS-FUNCTION
+with one argument: a function which will perform all the actions collected
+during the parse and then return the resulting stack (or t if empty).
+If no match was found, move to the (rightmost) point of parse failure and call
+FAILURE-FUNCTION with one argument, which is a list of PEG expressions that
+failed at this point.
+SUCCESS-FUNCTION defaults to `funcall' and FAILURE-FUNCTION
+defaults to `ignore'."
+ (let ((peg--actions '()) (peg--errors '(-1)))
+ (if (funcall peg-matcher)
+ ;; Found a parse: run the actions collected along the way.
+ (funcall (or success-function #'funcall)
+ (lambda ()
+ (save-excursion (peg-postprocess peg--actions))))
+ (goto-char (car peg--errors))
+ (when failure-function
+ (funcall failure-function (peg-merge-errors (cdr peg--errors)))))))
(defmacro define-peg-rule (name args &rest pexs)
"Define PEG rule NAME as equivalent to PEXS.
@@ -202,7 +251,8 @@ sequencing `and' operator of PEG grammars."
;; main purpose but we can live with it.
(apply #'peg--translate exp)
(peg--translate-rule-body name exp)))
- (put ',id 'peg--rule-definition ',exp)))))
+ (eval-and-compile
+ (put ',id 'peg--rule-definition ',exp))))))
(defmacro with-peg-rules (rules &rest body)
"Make PEG rules RULES available within the scope of BODY.
@@ -226,27 +276,15 @@ of PEG expressions, implicitly combined with `and'."
`((:peg-rules ,@(append rules (cdr ctx)))
,@macroexpand-all-environment))))
+;;;;; Old entry points
+
(defmacro peg-parse-exp (exp)
- "Match the parsing expression EXP at point.
-Note: a PE can't \"call\" rules by name."
+ "Match the parsing expression EXP at point."
(declare (obsolete peg-parse "peg-0.9"))
- `(let ((peg--actions nil) (peg--errors nil))
- (when ,(peg-translate-exp (peg-normalize exp))
- (peg-postprocess peg--actions))))
+ `(peg-run (peg ,exp)))
;;;; The actual implementation
-(defvar peg--actions nil
- "Actions collected along the current parse.
-Used at runtime for backtracking. It's a list ((POS . THUNK)...).
-Each THUNK is executed at the corresponding POS. Thunks are
-executed in a postprocessing step, not during parsing.")
-
-(defvar peg--errors nil
- "Data keeping track of the rightmost parse failure location.
-It's a pair (POSITION . EXPS ...). POSITION is the buffer position and
-EXPS is a list of rules/expressions that failed.")
-
(defun peg--lookup-rule (name)
(or (cdr (assq name (cdr (assq :peg-rules macroexpand-all-environment))))
(get (peg--rule-id name) 'peg--rule-definition)))
@@ -254,31 +292,17 @@ EXPS is a list of rules/expressions that failed.")
(defun peg--rule-id (name)
(intern (format "peg-rule %s" name)))
-(defmacro peg-rule-ref (&rest pexs)
- "Get a reference to the rule PEXS."
- (pcase (peg-normalize `(and . ,pexs))
- (`(call ,name) `#',(peg--rule-id name))
- (exp `(lambda () ,(peg-translate-exp exp)))))
-
(define-error 'peg-search-failed "Parse error at %d (expecting %S)")
-(defun peg-parse-at-point (rule-ref)
- "Parse text at point according to the PEG rule RULE-REF."
- (let ((peg--actions '()) (peg--errors '(-1)))
- (if (funcall rule-ref)
- ;; Found a parse: run the actions collected along the way.
- (save-excursion
- (peg-postprocess peg--actions))
- (goto-char (car peg--errors))
- (signal 'peg-search-failed
- (list (car peg--errors)
- (peg-merge-errors (cdr peg--errors)))))))
+(defun peg-signal-failure (failures)
+ (signal 'peg-search-failed (list (point) failures)))
-(defun peg--parse-no-error (rule-ref)
- "Parse text at point according to the PEG rule RULE-REF.
-Only moves point and returns a boolean."
- (let ((peg--actions '()) (peg--errors '(-1)))
- (funcall rule-ref)))
+(defun peg-parse-at-point (peg-matcher)
+ "Parse text at point according to the PEG rule PEG-MATCHER."
+ (declare (obsolete peg-run "peg-1.0"))
+ (peg-run peg-matcher
+ #'peg-signal-failure
+ (lambda (f) (let ((r (funcall f))) (if (listp r) r)))))
;; Internally we use a regularized syntax, e.g. we only have binary OR
;; nodes. Regularized nodes are lists of the form (OP ARGS...).
@@ -599,14 +623,14 @@ Only moves point and returns a boolean."
(defvar peg--stack nil)
(defun peg-postprocess (actions)
"Execute \"actions\"."
- (let ((peg--stack '()))
- (pcase-dolist (`(,pos . ,thunk)
- (mapcar (lambda (x)
- (cons (copy-marker (car x)) (cdr x)))
- (reverse actions)))
+ (let ((peg--stack '())
+ (forw-actions ()))
+ (pcase-dolist (`(,pos . ,thunk) actions)
+ (push (cons (copy-marker pos) thunk) forw-actions))
+ (pcase-dolist (`(,pos . ,thunk) forw-actions)
(goto-char pos)
(funcall thunk))
- peg--stack))
+ (or peg--stack t)))
;; Left recursion is presumably a common mistake when using PEGs.
;; Here we try to detect such mistakes. Essentailly we traverse the
@@ -727,24 +751,6 @@ input. PATH is the list of rules that we have visited so
far."
(cl-defmethod peg--merge-error (merged (_ (eql action)) _action) merged)
(cl-defmethod peg--merge-error (merged (_ (eql null))) merged)
-(defmacro peg-parse-string (pex string &optional noerror)
- "Parse STRING according to PEX.
-If NOERROR is non-nil, push nil resp. t if the parse failed
-resp. succeded instead of signaling an error."
- (let ((oldstyle (consp (car-safe pex)))) ;PEX is really a list of rules.
- `(with-temp-buffer
- (insert ,string)
- (goto-char (point-min))
- (peg-parse
- ,@(cond
- ((null noerror) (if oldstyle pex `(,pex)))
- ((not oldstyle) `((or (and ,pex `(-- t)) "")))
- (t
- (let ((entry (make-symbol "entry"))
- (start (caar pex)))
- `((,entry (or (and ,start `(-- t)) ""))
- . ,pex))))))))
-
(provide 'peg)
(require 'peg)
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [elpa] externals/peg 9a8d239: * peg.el: New official entry points `peg` and `peg-run`,
Stefan Monnier <=