Skip to content

Instantly share code, notes, and snippets.

@gdanov
Last active May 28, 2016 22:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gdanov/3a0440255f7f4df262ce16ce87351a04 to your computer and use it in GitHub Desktop.
Save gdanov/3a0440255f7f4df262ce16ce87351a04 to your computer and use it in GitHub Desktop.
(ns core
(:require [clojure.spec :as sp]))
(sp/def ::backslash #(= % \\))
(sp/def ::quote #(= % \"))
;; \" ... \\ \" is invalid string *body* and it's better to
;; aggressively terminate as early as possible there is more than one
;; way to define (resp. catch) (in)valid string!
(sp/def ::string-body
(sp/*
(sp/&
(sp/alt
;; avoid matching single \\ at the end as well as unescaped \"
:c (comp not #{\\ \"})
;; second for better performance
:ch (sp/cat :sl ::backslash :c #{\\ \"}))
(sp/conformer last))))
(sp/def ::string
(sp/&
(sp/cat
:quote ::quote
:body ::string-body
:end-quote ::quote)
(sp/conformer #(or (:body %) []))))
(sp/def ::digit #{\0 \1 \2 \3 \4 \5 \6 \7 \8 \9})
(sp/def ::number
(sp/cat
:minus (sp/? #(= % \-))
:main (sp/+ ::digit)
:fract (sp/&
(sp/? (sp/cat :dot #(= % \.) :num (sp/+ ::digit)))
;; (sp/conformer (partial :num)) can't compile?
(sp/conformer #(:num %)))))
(sp/def ::array-delim #(= % \,))
(defmacro whitespaced [ws form]
`(sp/&
(sp/cat
:ws (sp/* ~ws)
:val ~form
:ws (sp/* ~ws))
(sp/conformer #(:val %))))
(defmacro delimited-seq [delimiter form]
`(sp/&
(sp/cat
:head ~form
:rest (sp/*
(sp/&
(sp/cat
:delim ~delimiter
:chunk ~form)
(sp/conformer #(:chunk %)))))
(sp/conformer #(cons (:head %) (:rest %)))))
(defmacro literal-seq [val]
`(sp/&
(sp/cat
~@(mapcat
(fn [v n] `(~(keyword (str n "-" v)) #(= % ~v)))
val (range (count val))))
(sp/conformer (fn [~'_] ~val))))
(sp/def ::whitespace
#{\ , \newline, \tab})
(sp/def ::true
(literal-seq "true"))
(sp/def ::false
(literal-seq "false"))
(sp/def ::null
(literal-seq "null"))
(sp/def ::value
(sp/alt
:num ::number
:str ::string
:arr ::array
:obj ::map
:true ::true
:false ::false
:null ::null))
(sp/def ::array
(sp/&
(sp/cat
:open-bracket #{\[}
:body (sp/? (delimited-seq ::array-delim (whitespaced ::whitespace ::value)))
:close-bracket #{\]})
(sp/conformer #(:body %))))
(sp/def ::map-chunk
(sp/&
(sp/cat
:key (whitespaced ::whitespace ::string)
:delim #{\:}
:value (whitespaced ::whitespace ::value))
(sp/conformer #(do {(:key %) (:value %)}))))
(sp/def ::map
(sp/&
(sp/cat
:start #{\{}
:body (sp/? (whitespaced ::whitespace
(delimited-seq ::array-delim ::map-chunk)))
:end #{\}})
;; WARN returning nil same as :invalid
(sp/conformer #(or (:body %) {}))))
(sp/def ::json
(whitespaced ::whitespace
(sp/alt
:arr ::array
:map ::map)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment