From 7bf9c22e47b44b72cddbeef4daf493f321cc0bce Mon Sep 17 00:00:00 2001
From: Greg Sexton <gregsexton@gmail.com>
Date: Tue, 25 Mar 2014 22:00:34 +0000
Subject: [PATCH] Implement monadic parsing

---
 origami.el | 98 ++++++++++++++++++++++++++++++++----------------------
 1 file changed, 58 insertions(+), 40 deletions(-)

diff --git a/origami.el b/origami.el
index ed42792..0eb86b0 100644
--- a/origami.el
+++ b/origami.el
@@ -158,58 +158,76 @@ used to nil out data. This mutates the node."
   (origami-content (+ (origami-content-consumed-count content) consumed)
                    (substring (origami-content-string content) consumed)))
 
-;;; scanner
+;;; parser
 
-(defun origami-scanner-bind (h f)
+(defun origami-run-parser (parser content)
+  (funcall parser content))
+
+(defun origami-parser-bind (m f)
   "State monad composed with the maybe monad."
-  (if (null h) nil
+  (if (null m) nil
     (lambda (s)
-      (let ((new-result (funcall h s)))
+      (let ((new-result (origami-run-parser m s)))
         (if (null new-result) nil
           (destructuring-bind (new-value . new-state) new-result
-            (funcall (funcall f new-value) new-state)))))))
+            (origami-run-parser (funcall f new-value) new-state)))))))
 
-;;; TODO: reimplement this as a macro so that intermediate results are
-;;; visible. Basically implement proper do notation.
-(defun origami-scanner-bind-chain (scanner &rest funs)
-  (-reduce-from (lambda (acc f) (origami-scanner-bind acc f)) scanner funs))
+(defmacro origami-do (expr &rest more)
+  (let ((assignment-p (and (listp expr) (equal (cadr expr) '<-))))
+    (let ((var (if assignment-p (car expr) (gensym)))
+          (f (if assignment-p (caddr expr) expr)))
+      (if more
+          `(origami-parser-bind
+            ,f (lambda (,var)
+                 (origami-do ,@more)))
+        f))))
 
-(defun origami-scanner-return (x)
+(defun origami-parser-return (x)
   (lambda (s) (cons x s)))
 
-(defun origami-run-scanner (scanner content)
-  (funcall scanner content))
+(defun origami-parser-zero ()
+  (lambda (s) nil))
 
-(defun origami-scan (buffer scanner)
+(defun origami-parser-get ()
+  (lambda (s) (cons s s)))
+
+(defun origami-parser-item ()
+  (lambda (content)
+    (let ((content-str (origami-content-string content)))
+      (unless (s-blank? content-str)
+        (cons (substring content-str 0 1) (origami-content-from content 1))))))
+
+(defun origami-parser-position ()
+  (origami-do (content <- (origami-parser-get))
+              (origami-parser-return (origami-content-consumed-count content))))
+
+(defun origami-parser-sat (p)
+  (origami-do (pos <- (origami-parser-position))
+              (a <- (origami-parser-item))
+              (if (funcall p a)
+                  (origami-parser-return pos)
+                (origami-parser-zero))))
+
+(defun origami-parser-char (x)
+  (origami-parser-sat (lambda (c) (equal x c))))
+
+(defun origami-parser-paired (start end children)
+  (origami-do (begin <- start)
+              (children <- children)
+              (end <- end)
+              (origami-parser-return (origami-fold-node begin end t children))))
+
+(origami-run-parser
+ (origami-parser-paired (origami-parser-char "{")
+                        (origami-parser-char "}")
+                        (origami-parser-return nil))
+ (origami-content 7 "{}"))
+
+;;; TODO: rework this
+(defun origami-parse (buffer parser)
   (with-current-buffer buffer
     (let ((contents (buffer-string)))
-      (origami-run-scanner scanner (origami-content 0 contents)))))
-
-(defun origami-scanner-consume (prefix content)
-  (lambda (state)
-    (when (s-prefix-p prefix (origami-content-string content))
-      (cons (origami-content-from content (length prefix)) state))))
-
-(defun origami-pair-scanner (start end skip)
-  (lambda (content)
-    (origami-scanner-bind-chain
-     (origami-scanner-consume start content)
-     ;; skip
-     (lambda (skip-content) (origami-scanner-consume end skip-content))
-     (lambda (new-content)
-       (lambda (state)
-         (cons new-content (cons (list (origami-content-consumed-count content)
-                                       (origami-content-consumed-count new-content))
-                                 state)))))))
-
-;;; TODO: maybe scan and build up a simple list of (start end) values,
-;;; in the next pass transform this in to the proper fold structure?
-
-(origami-run-scanner
- (origami-scanner-bind
-  (origami-scanner-return (origami-content 0 "{}"))
-  (origami-pair-scanner "{" "}" nil))
- 'foo) ;=> ((2 . "") . foo)
+      (origami-run-parser parser (origami-content 0 contents)))))
 
 ;;; commands