As stated by Arthur Ulfeldt, this grammar is not context-free due to the bencoded strings. Nonetheless, it is a simple one to parse, just not with A/EBNF. For example, using Parse-EZ instead:
A convenience macro:
(defmacro tagged-sphp-expr [tag parser]
`(fn [] (between #(string ~(str tag ":")) #(~parser) #(string ";"))))
The rest:
(def sphp-integer (tagged-sphp-expr "i" integer))
(def sphp-decimal (tagged-sphp-expr "d" decimal))
(defn sphp-boolean []
(= \1 ((tagged-sphp-expr "b" #(chr-in "01")))))
(defn sphp-null [] (string "N;") :null)
(defn sphp-string []
(let [tag (string "s:")
size (integer)
open (no-trim #(string ":\""))
contents (read-n size)
close (string "\";")]
contents))
(declare sphp-array)
(defn sphp-expr []
(any #(sphp-integer) #(sphp-decimal) #(sphp-boolean) #(sphp-null) #(sphp-string) #(sphp-array)))
(defn sphp-key []
(any #(sphp-string) #(sphp-integer)))
(defn sphp-kv-pair []
(apply array-map (series #(sphp-key) #(sphp-expr))))
(defn sphp-array []
(let [size (between #(string "a:") #(integer) #(string ":{"))
contents (times size sphp-kv-pair)]
(chr \})
(attempt #(chr \;))
contents))
The test:
(def test-str "i:1;d:2;s:16:\"{\"key\": \"value\"}\";a:2:{s:3:\"php\";s:3:\"sux\";s:3:\"clj\";s:3:\"rox\";};b:1;")
(println test-str)
;=> i:1;d:2;s:16:"{"key": "value"}";a:2:{s:3:"php";s:3:"sux";s:3:"clj";s:3:"rox";};b:1;
(parse #(multi* sphp-expr) test-str)
;=> [1 2.0 "{\"key\": \"value\"}" [{"php" "sux"} {"clj" "rox"}] true]