git.fiddlerwoaroof.com
Raw Blame History
(in-package :edn)

(defun .satisfies (predicate &rest args)
  (.bind (.item)
         (lambda (x)
           (if (apply predicate x args)
               (.identity x)
               (.fail)))))

(defun .one-of (items &optional (test 'eql))
  (.satisfies
   (serapeum:op
     (member _ items :test test))))

(defun .zero-or-more (parser)
  (.plus (.let* ((x parser)
                 (xs (.zero-or-more parser)))
           (.identity (cons x xs)))
         (.identity ())))

(defun .one-or-more (parser)
  (.let* ((x parser)
          (y (.zero-or-more parser)))
    (.identity (cons x y))))

(defun .elements ()
  (.zero-or-more (.progn (.s)
                         (.element))))

(defun .s ()
  (.zero-or-more
   (.or (.whitespace)
        (.comment)
        (.discarded-element))))

(defun .whitespace ()
  (.one-of '(#\space
             #\tab
             #\return
             #\newline
             #\,)))

(defun .comment ()
  (.let* ((result (.prog2 (.char= #\;)
                          (.zero-or-more (.and (.not (.or (.char= #\newline)
                                                          (.char= #\nul)))
                                               (.item)))
                          (.or (.char= #\newline)
                               (.not (.item))))))
    (.identity (list :comment (coerce result 'string)))))

(defun .discarded-element ()
  (.progn (.string= "#_")
          (.s)
          (.element)))

(defun .alt (&rest r)
  (reduce '.plus r))

(defun .compound-element-start ()
  (.or (.string= "#{")
       (.char= #\{)
       (.char= #\[)
       (.char= #\()
       (.char= #\{)))

(defun .compound-element-finish (closing)
  (lambda ()
    (.prog1 (.first (.elements))
            (.s)
            (.char= closing))))

(defun .map-element ()
  (.prog2 (.char= #\{)
          (.progn (.s)
                  (.zero-or-more (.let* ((first (.prog1 (.element) (.s)))
                                         (second (.prog1 (.element) (.s))))
                                   (.identity (list :pair first second)))))
          (.char= #\})))

(defun .between (start-parser end-parser element-parser)
  (.prog2 start-parser
          (.first element-parser)
          (.s)
          end-parser))

(defun .tag (tag parser)
  (.let* ((item parser))
    (.identity (cons tag item))))

(defun .collection (tag start-parser end-parser)
  (.tag tag
        (.between start-parser end-parser
                  (.elements))))

(defun .primitive ()
  (.or (.nil)
       (.boolean)))

(defun .collections ()
  (.alt (.tag :map (.map-element))
        (.collection :set (.string= "#{") (.char= #\}))
        (.collection :vector (.char= #\[) (.char= #\]))
        (.collection :list (.char= #\() (.char= #\)))))

(defun .atoms ()
  (.alt (.number)
        (.symbol)
        (.keyword)
        (.character)
        (.string)))

(defun .element ()
  (.or (.primitive)
       (.alt (.atoms)
             (.collections)
             
             (.tag :tagged
                   (.let* ((tag (.progn (.char= #\#) (.tag-symbol)))
                           (element (.progn (.s) (.element))))
                     (.identity (list tag element)))))))

(defun .nil ()
  (.and (.string= "nil")
        (.identity edn-primitives:nil)))

(defun .boolean ()
  (.let* ((r (.or (.string= "true")
                  (.string= "false"))))
    (string-case:string-case (r)
      ("true" (.identity edn-primitives:true))
      ("false" (.identity edn-primitives:false)))))

(defun .symbol ()
  (.plus (.char= #\/)
         (.let* ((ns (.optional (.prog1 (.name) (.char= #\/))))
                 (name (.name)))
           (.identity (list :symbol ns name)))))

(defun .tag-symbol ()
  (.let* ((first (.satisfies #'alpha-char-p))
          (rest (.let* ((ns (.or (.char= #\/)
                                 (.optional (.prog1 (.name) (.char= #\/)))))
                        (name (.name)))
                  (.identity (list ns name)))))
    (destructuring-bind (ns name) rest
      (if ns
          (if (eql ns #\/)
              (.identity (list :symbol (format nil "~c" first) name))
              (.identity (list :symbol (format nil "~c~a" first ns) name)))
          (.identity (list :symbol nil (format nil "~c~a" first name)))))))

(defun .keyword ()
  (.progn (.char= #\:)
          (.let* ((ns (.optional (.prog1 (.name) (.char= #\/))))
                  (name (.name)))
            (.identity (list :keyword ns name)))))

(defun .name ()
  (.first (.plus (.let* ((first (.name-start-1))
                         (rest (.zero-or-more (.name-constituent))))
                   (.identity (format nil "~c~{~c~}" first rest)))
                 (.let* ((first (.name-start-2))
                         (second (.satisfies (complement #'digit-char-p)))
                         (rest (.zero-or-more (.name-constituent))))
                   (.identity (format nil "~c~c~{~c~}" first second rest))))))

(defun .name-start-1 ()
  (.satisfies (lambda (x)
                (or (alpha-char-p x)
                    (member x '(#\! #\* #\? #\_ #\$ #\% #\& #\=))))))

(defun .name-start-2 ()
  (.satisfies (lambda (x)
                (or (alpha-char-p x)
                    (member x '(#\. #\- #\+))))))

(defun .name-constituent ()
  (.or (.name-start-1)
       (.name-start-2)
       (.satisfies (lambda (x)
                     (or (digit-char-p x)
                         (member x '(#\# #\:)))))))
(defun apply-sign (sign num)
  (if sign
      (ecase sign
        (#\+ num)
        (#\- (* -1 num)))
      num))

(defun .frac-exp ()
  (.alt (.let* ((frac (.frac))
                (exp (.optional (.exp)))
                (flag (.optional (.char= #\M))))
          flag
          (.identity (list frac exp)))
        (.let* ((exp (.exp))
                (flag (.optional (.char= #\M))))
          flag
          (.identity (list 0 exp)))
        (.let* ((flag (.optional (.char= #\M))))
          flag
          (.identity (list 0 0)))))

(defun .frac ()
  (.let* ((nums (.first
                 (.progn (.char= #\.)
                         (.zero-or-more (.digit))))))
    (.identity
     (if nums
         (let ((num (parse-integer (coerce nums 'string))))
           (coerce (if (= num 0)
                       0
                       (/ num
                          (expt 10
                                (floor
                                 (1+ (log num
                                          10))))))
                   'double-float))
         0))))

(defun interpret-number (parts)
  (destructuring-bind (sign radix float-info flag) parts
    (let* ((base-value (if float-info
                           (destructuring-bind (mantissa exp) float-info
                             (coerce (* (+ radix
                                           (or mantissa 0))
                                        (if exp
                                            (expt 10 exp)
                                            1))
                                     'double-float))
                           radix))
           (signed (case sign
                     ((#\+ nil) base-value)
                     (#\- (- base-value)))))
      (typecase signed
        (integer (if (member flag '(nil #\N))
                     (.identity signed)
                     (.fail)))
        (float (if (member flag '(nil #\M))
                   (.identity signed)
                   (.fail)))))))

(defun .number ()
  (flet ((.sign () (.one-of '(#\+ #\-))))
    (.let* ((sign (.optional (.sign)))
            (num (.cardinal))
            (frac (.optional (.frac)))
            (exp (.optional (.exp)))
            (flag (.optional (.one-of '(#\N #\M)))))
      (interpret-number
       (list sign
             num
             (when (or frac exp)
               (list frac exp))
             flag)))))

(defun .exp ()
  (.progn (.char-equal #\e)
          (.let* ((sign (.optional
                         (.or (.char= #\+)
                              (.char= #\-))))
                  (num (.cardinal)))
            (.identity (apply-sign sign num)))))

(defun .cardinal ()
  (.let* ((nums (.or (.first
                      (.let* ((first (.non-zero-digit))
                              (rest (.zero-or-more
                                     (.digit))))
                        (.identity (list* first rest))))
                     (.let* ((c (.digit)))
                       (.identity (list c))))))
    (.identity (parse-integer (coerce nums 'string)))))

(defun .digit ()
  (.satisfies #'digit-char-p))

(defun .non-zero-digit ()
  (.satisfies (lambda (x)
                (and (digit-char-p x)
                     (not (eql #\0 x))))))

(defun .printable-character ()
  (.or (.satisfies (lambda (x) (char>= #\~ x #\!)))
       (.satisfies (lambda (x) (char<= #\space x)))))

(defun .character-name ()
  (.or (.string= "newline")
       (.string= "space")
       (.string= "tab")
       (.string= "return")
       (.string= "backspace")
       (.string= "formfeed")))

(defun .character ()
  (.let* ((char (.progn (.char= #\\)
                        (.or (.character-name)
                             (.printable-character)))))
    (.identity (list :character char))))

(defun .string-char ()
  (.and (.not (.char= #\nul))
        (.not (.char= #\"))
        (.not (.char= #\\))
        (.item)))

(defun .string-escape ()
  (.let* ((esc (.or (.char= #\")
                    (.char= #\b)
                    (.char= #\t)
                    (.char= #\n)
                    (.char= #\f)
                    (.char= #\r)
                    (.char= #\\))))
    (.identity (format nil "\\~c" esc))))

(defun combine (list)
  (format nil "~{~a~}" list))

(defun .string ()
  (.let* ((string (.prog2 (.char= #\")
                          (.zero-or-more (.or (.string-char)
                                              (.progn (.char= #\\)
                                                      (.string-escape))))
                          (.char= #\"))))
    (.identity (list :string (combine string)))))

(defun read-edn (s)
  (car
   (smug:parse (.prog1 (.elements)
                       (.s)
                       (.not (.item)))
               s)))