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)))
|