3

我有一个由 PHP 序列化的值,需要在 Clojure 中解码。我正在使用这个来反序列化它;它使用Instaparse,它利用 EBNF/ABNF 符号来定义语法。作为参考,这是完整的定义:

<S> = expr
<expr> = (string | integer | double | boolean | null | array)+
<digit> = #'[0-9]'
<number> = negative* (decimal-num | integer-num)
<negative> = '-'
<integer-num> = digit+
<decimal-num> = integer-num '.' integer-num
<zero-or-one> = '0'|'1'
size = digit+
key = (string | integer)
<val> = expr
array = <'a:'> <size> <':{'> (key val)+ <'}'> <';'>?
boolean = <'b:'> zero-or-one <';'>
null = <'N;'>
integer = <'i:'> number <';'>
double = <'d:'> number <';'>
string = <'s:'> <size> <':\\\"'> #'([^\"]|\\.)*' <'\\\";'>

我在这个库中发现了一个错误 - 它无法处理包含该"字符的序列化字符串。

php > echo serialize('{"key":"value"}');
s:15:"{"key":"value"}";

使用库反序列化,当它找到第二个时它会爆炸"

> (deserialize-php "s:15:\"{\"key\":\"value\"}\";")
[:index 7]

问题存在于语法定义的这一行:

string = <'s:'> <size> <':\\\"'> #'([^\"]|\\.)*' <'\\\";'>

您会注意到字符串定义不包括该"字符。但这不正确,我可以在该字符串中包含任何字符;大小是最重要的。我不是 BNF 专家,所以我想弄清楚我的选择是什么。

是否可以使用大小作为要抓取的正确字符数?如果那不可能,有人看到我可以调整语法定义以启用正确解析的方法吗?

4

3 回答 3

2

正如Arthur Ulfeldt 所说,由于经过编码的字符串,该语法不是上下文无关的。尽管如此,它是一个简单的解析,只是不使用 A/EBNF。例如,使用Parse-EZ代替:

一个方便的宏:

(defmacro tagged-sphp-expr [tag parser] 
  `(fn [] (between #(string ~(str tag ":")) #(~parser) #(string ";"))))

其余的部分:

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

考试:

(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]
于 2013-08-30T21:14:20.287 回答
2

我有理由确定您不能仅使用 EBNF 解析器来编写它,因为据我了解,该语法不是上下文无关的。

于 2013-08-29T20:26:48.843 回答
1

我认为最接近上下文无关语法的方法是显式枚举所有预期的长度前缀——类似于 ABNF 的内容:

 string = 's:0:"";' /
          's:1:"' CHAR '";' /
          's:2:"' 2CHAR '";' /
          's:3:"' 3CHAR '";' / ...

如果字符串的长度是有界的,这可能会很好地工作,但显然不适用于任意大小的字符串。

否则,要正确处理任意长度的字符串,最好的选择可能是手动解析。幸运的是,对于这种规模的语法,这应该不是太难的任务。

于 2013-08-30T14:30:02.253 回答