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

(defun .0-or-more (parser)
  (lambda (input)
    (loop
       for remaining-input = input then (cdr result)
       for result = (first (funcall parser remaining-input))
       while (and (car result) (> (length remaining-input) 0))
       collect (car result) into matches
       finally (return (list (cons matches remaining-input))))))

(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 .elements ()
  (.0-or-more (.progn (.s)
                      (.element))))

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

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

(defmacro read-if (s test)
  `(when (funcall (lambda (_)
                    ,test)
                  (peek-char nil ,s))
     (read-char s)))

(defun parse-whitespace (s)
  (read-if s (member _ '(#\space #\, #\tab #\return #\newline))))

(defun .comment ()
  (.let* ((result (.prog2 (.char= #\;)
                          (.first
                           (.0-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= "#_")
          (.element)))

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

(defun .map-element ()
  (.prog2 (.char= #\{)
          (.progn (.s)
                  (.first
                   (.0-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 .juxt (a b)
  (.let* ((first a)
          (second b))
    (.identity (list first second))))

(defun .name ()
  (.let* ((prefix (.or (.let* ((first (.name-start-1)))
                         (.identity (string first)))
                       (.let* ((first (.juxt (.name-start-2)
                                             (.satisfies (complement #'digit-char-p)))))
                         (.identity (coerce first 'string)))))
          (suffix (.0-or-more (.name-constituent))))
    (.identity (concatenate 'string prefix suffix))))

(defun name-start-1-p (c)
  (member c
          '(#\! #\* #\? #\_
            #\$ #\% #\& #\=)))

(defun name-start-2-p (c)
  (member c '(#\. #\- #\+)))

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

(defun .name-start-2 ()
  (.one-of '(#\. #\- #\+)))

(defun name-constituent-p (c)
  (or (alpha-char-p c)
      (digit-char-p c)
      (name-start-1-p c)
      (name-start-2-p c)
      (member c '(#\# #\:))))

(defun .name-constituent ()
  (.satisfies 'name-constituent-p))

(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= #\.)
                         (.0-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)
                     (.identity (coerce signed 'double-float))))
        (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 (.0-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 translate-escape (c)
  (ecase c
    ((#\" #\\) c)
    (#\t #\tab)
    (#\n #\newline)
    (#\r #\return)
    (#\b #\backspace)
    (#\f #.(code-char 12))))

(defun parse-string-ending-old (s)
  (let ((pos 0)
        (done nil))
    (flet ((consume-char ()
             (prog1 (elt s pos)
               (setf done (= pos (length s)))
               (incf pos))))
      (let ((result (loop
                       for char = (serapeum:case-let (next (consume-char))
                                    (#\\ (translate-escape (consume-char)))
                                    (#\" nil)
                                    (t next))
                       while char 
                       when (= pos (length s)) do (return nil)
                       collect char)))
        (if result
            (values (coerce result 'string) pos)
            (values nil 0))))))

(defun translate-escapes (s)
  (let ((parts (coerce (fwoar.string-utils:split #\\ s) 'list)))
    (serapeum:string-join (list* (car parts)
                                 (mapcan (lambda (part)
                                           (list (translate-escape (elt part 0))
                                                 (subseq part 1)))
                                         (cdr parts))))))

(defun parse-string-ending (s)
  (declare (optimize (speed 3))
           (type simple-string s))
  (loop
     for possible-quote = (position #\" s) then (position #\" s
                                                          :start (1+ possible-quote))

     while possible-quote
     when (not (char= #\\ (aref s (1- possible-quote)))) do
       (return (values (translate-escapes (subseq s 0 possible-quote))
                       (1+ possible-quote)))))

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

(define-condition invalid-string-ending (error)
  ())

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

(defun .string-ending ()
  (lambda (input)
    (multiple-value-bind (ending count) (parse-string-ending input)
      (if (> count 0)
          (list (cons ending
                      (subseq input count)))
          nil))))

(defun .string.old ()
  (.let* ((string (.progn (.char= #\")
                          (.string-ending))))
    (.identity (list :string string))))

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