YAML::Syck とアンカー・エイリアス
途中経過の覚え書きです。
my $o = [ {} ]; $o->[1] = $o->[0];
みたいな構造があったときに,
$o->[0] = 'hello';
とすると,$o->[1] は {} (空ハッシュリファレンス)のままです。
ところが,
use YAML::Syck; my $o = [ {} ]; $o->[1] = $o->[0]; $o = Load(Dump($o)); $o->[0] = 'hello';
とすると,$o->[1] も 'hello' になってしまいます。
みたいな話をかつて RT に投げたんですが,ちょっと調べてみようかなと思って調べてみました。
最初の状態では,
$o := RV(#1: 1) => AV(#2: 1) as [ RV(#3: 1) => HV(#4: 2) as {}, RV(#5: 1) => HV(#4: 2) as {}, ];
のようになっています。括弧の前半は仮想的な ID,後半はリファレンスカウントだと思ってください。
ここで,
$o->[0] = 'hello';
とすると,
$o := RV(#1: 1) => AV(#2: 1) as [ SV(#6: 1) as 'hello', RV(#5: 1) => HV(#4: 1) as {}, ];
ところが一度 YAML::Syck でシリアライズして戻すと,
$o := RV(#1: 1) => AV(#2: 1) as [ RV(#3: 2) => HV(#4: 1) as {}, RV(#3: 2) => HV(#4: 1) as {}, ];
のようになってしまいます。ここで,$o->[0] = 'hello' とすると,RV(#3) が SV('hello') になるので,$o->[1] も 'hello' になってしまうのです。
これはパーサの方の問題なんですが,どうしようかと考えて,
- syck_seq_kind や syck_map_kind の際に,配列やハッシュにセットする値をすべて newSVsv() で duplicate する
- syck_add_sym() するとき,AV や HV の際は,RV ではなくおおもとの AV や HV を登録する;perl_syck_lookup_sym() する都度それらから RV を生成する
などの方法を試してみたんですが,いずれもうまくいきませんでした。
いずれの方法にしても,親参照な場合にうまくいきません。親参照とはどういうことかというと,
--- parent: &1 child: *1
このような構造の場合に,最初に枝葉末節たる子から parse しようとするのですが,ここで一度 bad_anchor が発生します。で,最後に親を parse するときにアンカーが振ってある場合,そのノードに対応する SV (RV) を登録しなおすんですね。ここの機構になじむように改造しないといけないようです。むつかしい〜