git.fiddlerwoaroof.com
edn.lisp
2a63b94b
 (in-package :edn)
 
74868e98
 (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))))))
 
2a63b94b
 (defun .satisfies (predicate &rest args)
   (.bind (.item)
          (lambda (x)
            (if (apply predicate x args)
                (.identity x)
                (.fail)))))
 
e209b437
 (defun .one-of (items &optional (test 'eql))
   (.satisfies
    (serapeum:op
      (member _ items :test test))))
 
2a63b94b
 (defun .zero-or-more (parser)
   (.plus (.let* ((x parser)
                  (xs (.zero-or-more parser)))
            (.identity (cons x xs)))
          (.identity ())))
 
 (defun .elements ()
74868e98
   (.0-or-more (.progn (.s)
                       (.element))))
2a63b94b
 
 (defun .s ()
74868e98
   (.first
    (.0-or-more
     (.or (.whitespace)
          (.comment)
          (.discarded-element)))))
2a63b94b
 
 (defun .whitespace ()
e209b437
   (.one-of '(#\space
              #\tab
              #\return
              #\newline
              #\,)))
2a63b94b
 
74868e98
 (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))))
 
2a63b94b
 (defun .comment ()
   (.let* ((result (.prog2 (.char= #\;)
74868e98
                           (.first
                            (.0-or-more
                             (.and (.not (.or (.char= #\newline)
                                              (.char= #\nul)))
                                   (.item))))
2a63b94b
                           (.or (.char= #\newline)
                                (.not (.item))))))
     (.identity (list :comment (coerce result 'string)))))
 
 (defun .discarded-element ()
   (.progn (.string= "#_")
           (.element)))
 
 (defun .alt (&rest r)
   (reduce '.plus r))
 
ee000f9c
 (defun .map-element ()
   (.prog2 (.char= #\{)
           (.progn (.s)
74868e98
                   (.first
                    (.0-or-more (.let* ((first (.prog1 (.element) (.s)))
                                           (second (.prog1 (.element) (.s))))
                                     (.identity (list :pair first second))))))
ee000f9c
           (.char= #\})))
 
e209b437
 (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)))
 
2a63b94b
 (defun .element ()
e209b437
   (.or (.primitive)
        (.alt (.atoms)
              (.collections)
2a63b94b
              
e209b437
              (.tag :tagged
                    (.let* ((tag (.progn (.char= #\#) (.tag-symbol)))
                            (element (.progn (.s) (.element))))
                      (.identity (list tag element)))))))
2a63b94b
 
 (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)))))
 
74868e98
 (defun .juxt (a b)
   (.let* ((first a)
           (second b))
     (.identity (list first second))))
 
2a63b94b
 (defun .name ()
74868e98
   (.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 '(#\. #\- #\+)))
2a63b94b
 
 (defun .name-start-1 ()
74868e98
   (.or (.satisfies 'alpha-char-p)
        (.one-of '(#\! #\* #\? #\_ #\$ #\% #\& #\=))))
2a63b94b
 
 (defun .name-start-2 ()
74868e98
   (.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 '(#\# #\:))))
2a63b94b
 
 (defun .name-constituent ()
74868e98
   (.satisfies 'name-constituent-p))
 
2a63b94b
 (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= #\.)
74868e98
                          (.0-or-more (.digit))))))
2a63b94b
     (.identity
      (if nums
          (let ((num (parse-integer (coerce nums 'string))))
            (coerce (if (= num 0)
                        0
                        (/ num
                           (expt 10
                                 (floor
                                  (1+ (log num
                                           10))))))
ee000f9c
                    'double-float))
2a63b94b
          0))))
 
ee000f9c
 (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)
74868e98
                      (.identity (coerce signed 'double-float))))
ee000f9c
         (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)))))
 
2a63b94b
 (defun .exp ()
   (.progn (.char-equal #\e)
           (.let* ((sign (.optional
                          (.or (.char= #\+)
                               (.char= #\-))))
                   (num (.cardinal)))
             (.identity (apply-sign sign num)))))
 
 (defun .cardinal ()
ee000f9c
   (.let* ((nums (.or (.first
                       (.let* ((first (.non-zero-digit))
74868e98
                               (rest (.0-or-more
ee000f9c
                                      (.digit))))
                         (.identity (list* first rest))))
                      (.let* ((c (.digit)))
                        (.identity (list c))))))
2a63b94b
     (.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 #\!)))
ee000f9c
        (.satisfies (lambda (x) (char<= #\space x)))))
2a63b94b
 
 (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)))
 
74868e98
 (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)))))
2a63b94b
 
 (defun combine (list)
   (format nil "~{~a~}" list))
 
74868e98
 (define-condition invalid-string-ending (error)
   ())
 
2a63b94b
 (defun .string ()
   (.let* ((string (.prog2 (.char= #\")
74868e98
                           (.first
                            (.0-or-more (.or (.string-char)
                                             (.let* ((escape-char (.progn (.char= #\\)
                                                                          (.string-escape))))
                                               (.identity (translate-escape escape-char))))))
2a63b94b
                           (.char= #\"))))
     (.identity (list :string (combine string)))))
 
74868e98
 (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))))
 
ee000f9c
 (defun read-edn (s)
   (car
    (smug:parse (.prog1 (.elements)
                        (.s)
                        (.not (.item)))
                s)))