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) を登録しなおすんですね。ここの機構になじむように改造しないといけないようです。むつかしい〜