読者です 読者をやめる 読者になる 読者になる

yaml拡張の奇妙なバグ?

YAML PHP

【追記】バグではなく、YAMLの「落とし穴」らしいという結論に達しつつあります。

PECLのyaml拡張ナウでヤングなPHPerが腐りきったSpycやSyck拡張のかわりに使うらしいYAMLライブラリ。

が、

<?php
$yaml = <<<EOD
spam: 1,2
y: 1
EOD;

var_dump(yaml_parse($yaml));

のようなコードで、

array(2) {
  ["spam"]=>
  int(12)
  [1]=>
  int(1)
}

のような出力結果になる。

と、

  • Debian squeeze
  • PHP 5.3.3-6
  • libyaml-0-2 0.1.3-1

という二つの環境で確認した。共にPECL yaml 1.0.1。

PythonのPyYAMLで、

>>> import yaml
>>> import cStringIO as StringIO
>>> yaml.load(StringIO.StringIO("""
... spam: 1,2
... y: 1
... """))
{'y': 1, 'spam': '1,2'}

のようにやると期待通り解析できるので、libyamlの問題ではないという読み。

すぐにバグ登録してもいいが、ソースを見ていたらid:rskyの名前があったので、明日相談してから登録しよう。(すぐ直してくれるのを希望)


yが1になる件は症状が分かった。y/n, Yes/Noがboolに評価されている。

<?php
$yaml = <<<EOD
y: y
n: n
EOD;
var_dump(yaml_parse($yaml));

YAML 1.1のスペックとPyYAMLの挙動を見ていて、バグではないような気もしてきた・・・?

>>> yaml.load(StringIO.StringIO("""y: y"""))
{'y': 'y'}
>>> yaml.load(StringIO.StringIO("""y: yes"""))
{'y': True}
>>> yaml.load(StringIO.StringIO("""yes: yes"""))
{True: True}


,を含んだ文字列が整数に評価される件は、intのスペックを見るとバグのように見えるが、Integersの例を見ると正しいようにも見える。

PyYAMLの挙動は、

>>> yaml.load(StringIO.StringIO("""num: 12_333"""))
{'num': 12333}
>>> yaml.load(StringIO.StringIO("""num: 12,333"""))
{'num': '12,333'}
>>> yaml.load(StringIO.StringIO("""num: +12,333"""))
{'num': '+12,333'}

"_"を入れて数値を読みやすくすることができるなんて初めて知ったな。Perlみたいだな。


YAMLの仕様がよく分からないが、もし曖昧な部分があるならば、PHPでよく使われるSpycやSymfonyプロジェクトのYAMLモジュールに挙動を合わせておくのは理にかなっていると思う。

int1: 12,34     
int2: 12_34     
y: y            
yes: yes

のようなYAMLで他の言語のYAMLライブラリとの挙動も調べた。結構バラバラだな・・・

PyYAML*1 sfYaml*2 YAML::XS*3 ruby*4 PECL yaml*5
12,34 '12,34' 1234 '12,34' 1234 1234
12_34 1234 "12_34" '12_34' '12_34' 1234
y 'y' 'y' 'y' 'y' true
yes True 'yes' 'yes' true true

自分の中で結論に達しつつある。

  • '12,34'をintの1234に変換するのはYAML 1.1のスペックに反しているので間違い。YAML 1.1のスペックに変換を許すような記述があるのはYAML 1.0からの誤植ではないか?
  • '12_34'が1234に変換されるのは正しい。ただし、すべてのライブラリが対応しているわけではない。ライブラリ使用者側がこれを期待するのは、YAML 1.2のスペックを考えても危険。YAML 1.2では対応していない。
  • yがbooleanのtrueに変換されるのは正しい。が、実装者がこの仕様を満たすことに何らかの心理的抵抗があって、ほとんどのライブラリでは実装されていないのではないか?
  • yesがtrueに変換されるのは正しい。しかしこの仕様はYAML 1.2では撤廃される。Symfonyプロジェクトもbooleanにon, yesなどを使うのは非推奨にしている。
  • YAML::XSの実装はかなり強い制約があるが、おそらくYAML 1.2のスペックを意識してそうしているのではないか。

*1:3.0.9

*2:1.0.5

*3:0.33

*4:1.8.7

*5:1.0.1