src/parser/: Allow parsers to commit to a parse while peeking.
[sod] / src / parser / parser-proto.lisp
index 5a10b77..189e503 100644 (file)
@@ -7,7 +7,7 @@
 
 ;;;----- Licensing notice ---------------------------------------------------
 ;;;
-;;; This file is part of the Sensble Object Design, an object system for C.
+;;; This file is part of the Sensible Object Design, an object system for C.
 ;;;
 ;;; SOD is free software; you can redistribute it and/or modify
 ;;; it under the terms of the GNU General Public License as published by
@@ -77,6 +77,7 @@
 ;;;--------------------------------------------------------------------------
 ;;; Utilities.
 
+(export 'combine-parser-failures)
 (defun combine-parser-failures (failures)
   "Combine the failure indicators listed in FAILURES.
 
     (:documentation
      "Expand a parser list-form given by HEAD and TAIL, in CONTEXT.")
     (:method (context head tail)
+      (declare (ignore context))
       (cons head tail)))
 
   (export 'wrap-parser)
   (defgeneric wrap-parser (context form)
     (:documentation
      "Enclose FORM in whatever is necessary to make the parser work.")
-    (:method (context form) form)))
+    (:method (context form)
+      (declare (ignore context))
+      form)))
 
 (export 'defparse)
 (defmacro defparse (name bvl &body body)
    body FORMs. The BVL is a destructuring lambda-list to be applied to the
    tail of the form.  The body forms are enclosed in a block called NAME.
 
-   Within the FORMs, a function `expand' is available: it takes a parser
-   specifier as its argument and returns its expansion in the parser's
-   context.
-
    If the :context key is provided, then the parser form is specialized on a
    particular class of parser contexts SPEC; specialized expanders take
    priority over less specialized or unspecialized expanders -- so you can
        `(defmethod expand-parser-form
             ((,context ,ctxclass) (,head (eql ',name)) ,tail)
           ,@doc
-          (block ,name
-            (destructuring-bind ,bvl ,tail
-              ,@decls
-              ,@body)))))))
+          (declare (ignorable ,context))
+          (destructuring-bind ,bvl ,tail
+            ,@decls
+            (block ,name ,@body)))))))
 
 (export '(with-parser-context parse))
 (defmacro with-parser-context ((class &rest initargs) &body body)
 
 (defmethod expand-parser-spec (context (spec (eql t)))
   "Always matches without consuming input."
+  (declare (ignore context))
   '(values t t nil))
 
 (defmethod expand-parser-spec (context (spec (eql nil)))
   "Always fails without consuming input.  The failure indicator is `:fail'."
+  (declare (ignore context))
   '(values '(:fail) nil nil))
 
 (export 'seq)
        (,func (lambda (,new)
                (declare (ignorable ,new))
                (setf ,accvar ,update))
-             (lambda ()
-               ,final)
+             (lambda () ,final)
              (parser () ,parser)
              ,@(and sepp (list `(parser () ,sep)))
              ,@(and minp `(:min ,min))
 
 (export 'list)
 (defparse list ((&rest keys) parser &optional (sep nil sepp))
-  "Like MANY, but simply returns a list of the parser results."
+  "Like `many', but simply returns a list of the parser results."
   (with-gensyms (acc)
     `(parse (many (,acc nil (cons it ,acc) :final (nreverse ,acc) ,@keys)
              ,parser ,@(and sepp (list sep))))))
 
 (export 'skip-many)
 (defparse skip-many ((&rest keys) parser &optional (sep nil sepp))
-  "Like MANY, but ignores the results."
+  "Like `many', but ignores the results."
   `(parse (many (nil nil nil ,@keys)
            ,parser ,@(and sepp (list sep)))))
 
    If a parser with the given TAG is already attached to SYMBOL then the new
    parser replaces the old one; otherwise it is added to the collection."
 
-  `(pluggable-parser-add ',symbol ',tag (lambda ,bvl ,@body)))
+  (multiple-value-bind (docs decls body) (parse-body body)
+    `(pluggable-parser-add ',symbol ',tag
+                          (lambda ,bvl
+                            ,@docs ,@decls
+                            (block ,symbol ,@body)))))
 
 ;;;--------------------------------------------------------------------------
 ;;; Rewindable parser context protocol.
    underlying scanner can use this call to determine whether there are
    outstanding captured places, and thereby optimize its behaviour.  Be
    careful: all of this is happening at macro-expansion time.")
-    (:method (context place) nil))
+    (:method (context place)
+      (declare (ignore context place))
+      nil))
 
   (export 'parser-places-must-be-released-p)
   (defgeneric parser-places-must-be-released-p (context)
    the correct cleanup.  If it returns false, then the `unwind-protect' is
    omitted so that the runtime code does't have to register cleanup
    handlers.")
-    (:method (context) t)))
+    (:method (context)
+      (declare (ignore context))
+      t)))
 
 (export 'with-parser-place)
 (defmacro with-parser-place ((place context) &body body)
           `(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.
    A token matches under the following conditions:
 
      * If the value of TYPE is `t' then the match succeeds if and only if the
-       parser it not at end-of-file.
+       parser is not at end-of-file.
 
      * If the value of TYPE is not `eql' to the token type then the match
        fails.