branch: master commit 3827d263fec87080fdacea6fa937a2dbb7598335 Author: Junpeng Qiu <qjpchm...@gmail.com> Commit: Junpeng Qiu <qjpchm...@gmail.com>
Add docs for user-interface functions & combinators --- parsec.el | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 132 insertions(+), 13 deletions(-) diff --git a/parsec.el b/parsec.el index 4911142..00073f6 100644 --- a/parsec.el +++ b/parsec.el @@ -68,6 +68,7 @@ (parsec-error-new-2 expected found))))))) (defun parsec-ch (ch) + "Parse a character CH." (let ((next-char (char-after))) (if (and (not (eobp)) (char-equal next-char ch)) @@ -77,6 +78,7 @@ :found (parsec-eof-or-char-as-string))))) (defun parsec-any-ch () + "Parse any character." (if (not (eobp)) (prog1 (char-to-string (char-after)) (forward-char)) @@ -84,6 +86,7 @@ :found (parsec-eof-or-char-as-string)))) (defun parsec-satisfy (pred) + "Parse any character that satisfies the predicate PRED." (let ((next-char (char-after))) (if (and (not (eobp)) (funcall pred next-char)) @@ -93,15 +96,19 @@ :found (parsec-eof-or-char-as-string))))) (defun parsec-newline () + "Parse a newline character \"\\n\"." (parsec-ch ?\n)) (defun parsec-crlf () + "Parse a carriage return (\'\\r\') followed by a newline \"\\n\"." (parsec-and (parsec-ch ?\r) (parsec-ch ?\n))) (defun parsec-eol () + "Parse a newline or a CRLF and return \"\\n\"." (parsec-or (parsec-newline) (parsec-crlf))) (defun parsec-eob () + "Indicate the end of file (buffer)." (unless (eobp) (parsec-stop :expected "`EOF'" :found (parsec-eof-or-char-as-string)))) @@ -109,9 +116,11 @@ (defalias 'parsec-eof 'parsec-eob) (defun parsec-eol-or-eof () + "Indicate either eol or eof." (parsec-or (parsec-eol) (parsec-eof))) (defun parsec-re (regexp) + "Parse the input matching the regular expression REGEXP." (if (looking-at regexp) (progn (goto-char (match-end 0)) (match-string 0)) @@ -139,27 +148,62 @@ (concat regex-head regex-str regex-end))) (defun parsec-one-of (&rest chars) + "Succeed if the current character is in the supplied list of CHARS. +Return the parsed character. + +> (parsec-one-of ?a ?e ?i ?o ?u) + +Note this function is just a wrapper of `parsec-re'. For complicated use cases, +consider using `parsec-re' instead." (parsec-re (format "[%s]" (parsec-make-alternatives chars)))) (defun parsec-none-of (&rest chars) + "Succeed if the current character not in the supplied list of CHARS. +Return the parsed character. + +> (parsec-none-of ?a ?e ?i ?o ?u) + +Note this function is just a wrapper of `parsec-re'. For complicated use cases, +consider using `parsec-re' instead." (parsec-re (format "[^%s]" (parsec-make-alternatives chars)))) (defsubst parsec-str (str) + "Parse STR and only consume the input for an exact match. +Return the parsed string. + +Note this function's behavior is different from the `string' +function of Haskll's Parsec. Use `parsec-string' if you want the +same behavior as in Haskell." (parsec-re (regexp-quote str))) (defsubst parsec-string (str) + "Parse STR and consume the input even for a partial match. +Return the parsed string. + +It is equivalent to calling `parsec-ch' multiples times so the +input will be consumed if the parser fails in the middle of the +STR. This function has the same behavior as the `string' function +of Haskell's Parsec. See also `parsec-str'." (mapc (lambda (c) (parsec-ch c)) str)) -(defsubst parsec-num (num &rest args) +(defsubst parsec-num (num) + "Parse the number NUM and return the parsed number as a string." (parsec-re (regexp-quote (number-to-string num)))) (defsubst parsec-letter () + "Parse any English letter." (parsec-re "[a-zA-Z]")) (defsubst parsec-digit () + "Parse any digit." (parsec-re "[0-9]")) (defmacro parsec-or (&rest parsers) + "Try the PARSERS one by one. +If the current parser succeeds, return its results. If the +current parser fails without consuming any input, try the next +parser if available. This combinator fails if the current parser +fails after consuming some input or there is no more parsers." (let ((outer-sym (make-symbol "outer")) (parser-sym (make-symbol "parser")) (error-sym (make-symbol "err")) @@ -182,29 +226,40 @@ (concat "None of the parsers succeeds:\n" (mapconcat #'identity ,error-str-list-sym "\n")))))))) -(defalias 'parsec-and 'progn) +(defalias 'parsec-and 'progn + "Eval BODY sequentially and return the result of the last parser. +This combinator fails if one of the parsers fails.") -(defalias 'parsec-return 'prog1) +(defalias 'parsec-return 'prog1 + "Eval FIRST and BODY sequentially and return the results of the first parser. +This combinator fails if one of the parsers fails.") -(defalias 'parsec-collect 'list) +(defalias 'parsec-collect 'list + "Collect the results of all the parsers OBJECTS into a list.") (defun parsec-collect* (&rest args) + "Collect the non-nil results of all the parsers ARGS into a list." (delq nil (apply #'parsec-collect args))) (defmacro parsec-collect-as-string (&rest forms) + "Collect the results of all the parsers FORMS as a string." `(parsec-list-to-string (parsec-collect ,@forms))) (defmacro parsec-start (&rest forms) + "Eval the parsers FORMS and return the results or a `parsec-error'. +This combinator should be used at the top level as the entry +point of your parsing program." `(catch 'parsec-failed ,@forms)) (defalias 'parsec-parse 'parsec-start) -(defmacro parsec-try (&rest forms) +(defmacro parsec-try (parser) + "Try PARSER, and pretend that no input is consumed when an error occurs." (let ((orig-pt-sym (make-symbol "orig-pt")) (error-sym (make-symbol "err"))) `(let ((,orig-pt-sym (point))) (parsec-eavesdrop-error ,error-sym - (parsec-and ,@forms) + (parsec-and ,parser) (goto-char ,orig-pt-sym))))) (defsubst parsec--atom-tag (name) @@ -237,24 +292,28 @@ (parsec-throw ,error-sym)))) (defmacro parsec-with-error-message (msg &rest forms) + "Use MSG as the error message if an error occurs when Evaling the FORMS." (declare (indent 1)) `(parsec-eavesdrop-error _ (parsec-and ,@forms) (parsec-throw (parsec-error-new ,msg)))) (defmacro parsec-ensure (&rest forms) + "Exit the program immediately if FORMS fail." (let ((error-sym (make-symbol "err"))) `(parsec-eavesdrop-error ,error-sym (parsec-and ,@forms) (error "%s" (parsec-error-str ,error-sym))))) (defmacro parsec-ensure-with-error-message (msg &rest forms) + "Exit the program immediately with MSG if FORMS fail." (declare (indent 1)) `(parsec-ensure (parsec-with-error-message ,msg (parsec-and ,@forms)))) (defmacro parsec-many (parser) + "Apply the PARSER zero or more times and return a list of the results." (let ((res-sym (make-symbol "results")) (error-sym (make-symbol "err"))) `(let (,res-sym) @@ -265,6 +324,7 @@ (nreverse ,res-sym)))) (defmacro parsec-many1 (parser) + "Apply the PARSER one or more times and return a list of the results." `(cons ,parser (parsec-many ,parser))) (defsubst parsec-list-to-string (l) @@ -273,12 +333,25 @@ (mapconcat #'identity l ""))) (defmacro parsec-many-as-string (parser) + "Apply the PARSER zero or more times and return the results as a string." `(mapconcat #'identity (parsec-many ,parser) "")) (defmacro parsec-many1-as-string (parser) + "Apply the PARSER one or more times and return the results as a string." `(mapconcat #'identity (parsec-many1 ,parser) "")) (defmacro parsec-many-till (parser end &optional type) + "Apply PARSER zero or more times until END succeeds. +The return value is determined by TYPE. If TYPE is `:both', return +the cons `(many . end)'. If TYPE is `:end', return the result of END. +In other cases, return the result of PARSER. + +Used to scan comments: + +> (parsec-and +> (parsec-str \"<--\") +> (parsec-many-till (parsec-any-ch) (parsec-str \"-->\")))" + (let ((res-sym (make-symbol "results")) (end-res-sym (make-symbol "end-result"))) `(let ((,res-sym nil) ,end-res-sym) @@ -294,21 +367,31 @@ (t res-sym))))) (defmacro parsec-many-till-as-string (parser end &optional type) + "Apply PARSER zero or more times until END succeeds. +Return the result of PARSER or END as a string. TYPE has the same +meaning as `parsec-many-till'." (let ((res-sym (make-symbol "results"))) (cond ((eq type :both) `(let ((,res-sym (parsec-many-till ,parser ,end ,type))) - (cons (parsec-list-to-string (car ,res-sym)) (cdr ,res-sym)))) + (cons (parsec-list-to-string (car ,res-sym)) + (parsec-list-to-string (cdr ,res-sym))))) (t `(parsec-list-to-string (parsec-many-till ,parser ,end ,type)))))) (defmacro parsec-until (parser &optional type) + "Parse any characters until PARSER succeeds. +TYPE has the same meaning as `parsec-many-till'." `(parsec-many-till (parsec-any-ch) ,parser ,type)) (defmacro parsec-until-as-string (parser &optional type) + "Parse any characters until PARSER succeeds. +Return the result of either part as a string. TYPE has the same +meaning as `parsec-many-till'." `(parsec-many-till-as-string (parsec-any-ch) ,parser ,type)) (defmacro parsec-not-followed-by (parser) + "Succeed only when PARSER fails. Consume no input." (let ((res-sym (make-symbol "results"))) `(catch 'parsec-not-followed-by (let ((,res-sym @@ -319,39 +402,69 @@ (parsec-stop :message (format "Unexpected followed by: %s" ,res-sym)))))) (defmacro parsec-endby (parser end) + "Parse zero or more occurrences of PARSER, separated and ended by END. +Return a list of values returned by PARSER." `(parsec-many (parsec-return ,parser ,end))) (defmacro parsec-sepby (parser separator) + "Parse zero or more occurrences of PARSER, separated by SEPARATOR. +Return a list of values returned by PARSER." `(parsec-or (cons ,parser (parsec-many (parsec-and ,separator ,parser))) nil)) (defmacro parsec-between (open close parser) + "Parse OPEN, followed by PARSER and CLOSE. +Return the value returned by PARSER." `(parsec-and ,open (parsec-return ,parser ,close))) (defmacro parsec-count (n parser) + "Parse N occurrences of PARSER. +Return a list of N values returned by PARSER." (let ((res-sym (make-symbol "results"))) `(let (,res-sym) (dotimes (_ ,n ,res-sym) (push ,parser ,res-sym))))) (defmacro parsec-count-as-string (n parser) + "Parse N occurrences of PARSER. +Return the N values returned by PARSER as a string." `(parsec-list-to-string (parsec-count ,n ,parser))) (defmacro parsec-option (opt parser) + "Try to apply PARSER and return OPT if PARSER fails without comsuming input." `(parsec-or ,parser ,opt)) -(defmacro parsec-optional (&rest forms) - `(parsec-or (parsec-and ,@forms) nil)) +(defmacro parsec-optional (parser) + "Apply PARSER zero or one time. Fail if PARSER fails after consuming input. +Return the result of PARSER or nil. + +Note this combinator doesn't discard the result of PARSER so it is +different from the `optional' function of Haskell's Parsec. If +you want the Haskell's behavior, use `parsec-optional*'." + `(parsec-or ,parser nil)) -(defmacro parsec-optional* (&rest forms) - `(parsec-and (parsec-optional ,@forms) nil)) +(defmacro parsec-optional* (parser) + "Apply PARSER zero or one time and discard the result. +Fail if PARSER fails after consuming input. + +This combinator has the same behavior as the `optional' function of +Haskell's Parsec." + `(parsec-and ,parser nil)) (defmacro parsec-query (parser &rest args) + "Get an alternative return value of the PARSER specified by the ARGS. + +The args can be in the following forms: + + :beg --> return the point before applying the PARSER + :end --> return the point after applying the PARSER + :nil --> return nil + :groups N --> return Nth group for `parsec-re'." (let ((orig-pt-sym (make-symbol "orig-pt")) (res-sym (make-symbol "results"))) `(let ((,orig-pt-sym (point)) @@ -383,18 +496,24 @@ (eq (car x) 'Just)))) (defun parsec-from-just (x) + "Retrieve the value from Maybe monad X. +If X is `(Just . p)', return p. Otherwise return nil." (and (consp x) (eq (car x) 'Just) (cdr x))) -(defmacro parsec-optional-maybe (&rest forms) +(defmacro parsec-optional-maybe (parser) + "Apply PARSER zero or one time and return the value in a Maybe monad. +If PARSER fails without consuming any input, return `parsec-nothing'. +Otherwise, return `(Just . p)' where p is the result of PARSER." (let ((res-sym (make-symbol "result"))) - `(let ((,res-sym (parsec-optional ,@forms))) + `(let ((,res-sym (parsec-optional ,parser))) (if ,res-sym (parsec-just ,res-sym) parsec-nothing)))) (defmacro parsec-with-input (input &rest parsers) + "With INPUT, start parsing by applying PARSERS sequentially." (declare (indent 1)) `(with-temp-buffer (insert ,input)