src/parser/: Allow parsers to commit to a parse while peeking.
authorMark Wooding <mdw@distorted.org.uk>
Thu, 26 May 2016 08:26:09 +0000 (09:26 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 29 May 2016 14:08:43 +0000 (15:08 +0100)
There is a new parser macro `commit' which may be used lexically within
the body of `peek' to commit to the current parse: if a failure is
encountered once the parse is committed, then the entire `peek' fails
having consumed input, rather than rewinding to the start.  This allows
parsers to limit the amount of lookahead they use once the ambiguity is
resolved.

Technically, the changes are as follows.

  * The `peek' parser macro introduces a new lexically-scoped macro into
    its body which releases its captured place and sets the
    corresponding variable to `nil'.

  * The `with-parser-place' and `with-scanner-place' macros don't try to
    release their captured places on unwind if they've been set to
    `nil'.  (It's not necessary to have changed `with-scanner-place'
    here, but it improves symmetry.)

  * There's a new `commit' parser macro which just invokes the magic
    macro introduced by `peek' and succeeds.

  * There's a top-level function with the same name as the local macro,
    to report errors in a slightly less confusing way.

doc/SYMBOLS
doc/parsing.tex
src/parser/parser-proto.lisp
src/parser/scanner-proto.lisp

index e19df8b..9df1bd9 100644 (file)
@@ -1467,6 +1467,7 @@ parser-proto.lisp
   cl:char                                       function setf c-type parser
   character-parser-context                      class
   combine-parser-failures                       function
+  commit                                        parser
   cond-parse                                    macro
   define-pluggable-parser                       macro
   defparse                                      macro
@@ -1672,6 +1673,7 @@ expand-parser-form
   t (eql cl:t) t
   t (eql cl:when) t
   t (eql ?) t
+  t (eql commit) t
   t (eql expr) t
   t (eql label) t
   t (eql lisp) t
index 28a747b..ec1667f 100644 (file)
@@ -601,6 +601,9 @@ file-location protocols.
 \begin{describe}{parseform}{peek @<parser>}
 \end{describe}
 
+\begin{describe}{parseform}{commit}
+\end{describe}
+
 \begin{describe}{cls}{character-parser-context () \&key}
 \end{describe}
 
index b97cd4b..189e503 100644 (file)
           `(let ((,,place ,(parser-capture-place ,context)))
              ,(if (parser-places-must-be-released-p ,context)
                   `(unwind-protect ,(,bodyfunc)
-                       ,(parser-release-place ,context ,place))
+                     (when ,,place
+                       ,(parser-release-place ,context ,place)))
                   (,bodyfunc))))))))
 
 (export 'peek)
   "Attempt to run PARSER, but rewind the underlying source if it fails."
   (with-gensyms (value win consumedp)
     (with-parser-place (place context)
-      `(multiple-value-bind (,value ,win ,consumedp) (parse ,parser)
-        (cond (,win
-               (values ,value ,win ,consumedp))
-              (t
-               ,(parser-restore-place context place)
-               (values ,value ,win nil)))))))
+      `(macrolet ((commit-peeked-place ()
+                   `(progn
+                      ,',(parser-release-place context place)
+                      (setf ,',place nil))))
+        (multiple-value-bind (,value ,win ,consumedp) (parse ,parser)
+          (cond ((or ,win (null ,place))
+                 (values ,value ,win ,consumedp))
+                (t
+                 ,(parser-restore-place context place)
+                 (values ,value ,win nil))))))))
+
+(defun commit-peeked-place ()
+  "Called by `commit' not lexically within `peek'."
+  (error "`commit' is not within `peek'."))
+
+(export 'commit)
+(defparse commit ()
+  "Commit to the current parse.
+
+   This releases the place captured by the innermost lexically enclosing
+  `peek'."
+  '(progn
+     (commit-peeked-place)
+     (values nil t nil)))
 
 ;;;--------------------------------------------------------------------------
 ;;; Character parser context protocol.
index 8f27f89..c6236c5 100644 (file)
       `(let ((,place (scanner-capture-place ,scanner)))
         ,@decls
         (unwind-protect (progn ,@body)
-          (scanner-release-place ,scanner ,place))))))
+          (when ,place (scanner-release-place ,scanner ,place)))))))
 
 ;;;--------------------------------------------------------------------------
 ;;; Character scanner protocol.