ピュア Perl YAML 1.2 ローダ YAML-Loaf-0.01

YAML 1.2 のバックトラック解析器からフォークして、ローダを作ってみました。フルスペックの構文をディレクティブを除いて解釈可能ながら、コード行数 700 行程度で済んでいます。もっとも、行数が少ないのは型解析部に手を抜いているせいでもありますが……。

0.03 でタグ・ディレクティブを解釈できるようにし、タグ・プレフィックスを展開するようにしました。同時に、アプリケーション独自のローカルおよびグローバルタグをコアおよび Perl データにマッピングできるようにしました。

https://github.com/tociyuki/libyaml-loaf-perl

テストの t/01-exams.tYAML 1.2 Specification 記載の例が詰め込んであり、仕様書の例のほとんどを解釈することができることを確認済みです。テストにする際、BNF を正しいとするなら、例にはいくつもの小さな記述ミスが潜んでいるのに気がついたので修正してあります。例えば、下のサンプルは例 10.9 からとってきたものですが、仕様書の例は 5 行目の Booleans の文字列のダブルクォートが閉じていません。他、コンマがあるべきところこになかったり、あってはいけないところについていたりとか、その手のミスがいくつもあるので、サンプルから構文を理解しようとするとハマることでしょう。

さて、型解析部の手抜ですけど、まず、YAML ディレクティブとタグ・ディレクティブは記述してあっても無視します。型として理解するのは YAML Core スキーマPerl スキーマだけです。それも、ビックリマーク2個のセカンダリ・タグを tag:yaml.org,2002: で扱う程度です。それだけではあんまりなので、フル URI の冗長タグも 同 URI の場合に限りセカンダリ・タグと同じ扱いをできるようにしています。他のタグはエラーも警告も出さず、単純にすべてさっぱりと無視します。

use YAML::Loaf;

my $exam10_9right = YAML::Loaf::Load(<<'EOS');
%YAML 1.2
---
!!map {
  !!str "A null" : !!null "null",
  !!str "Also a null" : !!null "",
  !!str "Not a null" : !!str "",
  !!str "Booleans": !!seq [
    !!bool "true", !!bool "True",
    !!bool "false", !!bool "FALSE",
  ],
  !!str "Integers": !!seq [
    !!int "0", !!int "0o7",
    !!int "0x3A", !!int "-19",
  ],
  !!str "Floats": !!seq [
    !!float "0.", !!float "-0.0", !!float ".5",
    !!float "+12e03", !!float "-2E+05"
  ],
  !!str "Also floats": !!seq [
    !!float ".inf", !!float "-.Inf",
    !!float "+.INF", !!float ".NAN",
  ],
}
EOS

コア・スキーマであっても、!!float タグは !!str タグと同じ扱いをしており、数記述の構文チェックをしていません。さらに、Perl はハッシュのキーに文字列を要求するので、キーへ !!null を指定したときは空文字列に強制的にすげ替えるようにしています。

Perl スキーマでは、!!perl/array!!perl/hash!!perl/scalar!!perl/regexp とそれぞれをブレスしたリファレンスを扱えます。その他の、io、glob、code があると例外を投げます。Perl 用の他の YAML ローダ実装と違っているのは、!!map!!perl/array にすることができる点です。これにより、マッピングのキーがスカラー以外の場合であっても Perl から拾うことができるようにしてあります。もちろん、マッピングを配列リファレンス扱いしたときは、Perl のハッシュを配列にするとき同様、キーと値が交互に並ぶリストになります。それ以外では、他のローダ同様、!!perl/scalar を指定すると、スカラースカラー・リファレンスへ変更します。!!perl/regexp は、qr/内容/msxにします。

my $x = YAML::Loaf::Load(<<'EOS');
--- !!perl/hash:Foo::Bar
? attr_a
: value a
attr_b:
  value b
EOS

my $x = bless {'attr_a' => 'value a', 'attr_b' => 'value b'}, 'Foo::Bar'; # と同じ

my $y = YAML::Loaf::Load(<<'EOS');
--- !!perl/array:Foo::Bar
? attr_a
: value a
attr_b:
  value b
EOS

my $y = bless ['attr_a' => 'value a', 'attr_b' => 'value b'], 'Foo::Bar'; # と同じ

アンカーとエイリアスも使えます。循環参照も作れてしまえるのですけど、ウィーク・リファレンス化しないので、循環参照によるメモリー・リークには気をつけないといけません。リファレンスへのエイリアスは参照になりオブジェクト実体を共用するのに対して、(リファレンスでない)スカラーは複製代入します。

my $x = YAML::Loaf::Load(<<'EOS');
--- &1
foo: &2 [*1, *2]
EOS

my $y = {
    'foo' => [],
};
$y->{'foo'}[0] = $y;
$y->{'foo'}[1] = $y->{'foo'}; # と同じ

ローダ手続きは YAML::Loaf::Load($string) です。裸で使うことはあまりないだろうと、シンボルをエクスポートする機能をつけていません。手続きへ引数として UTF-8 デコード済みの文字列を適用すると、デコードして Perl のオブジェクトを返します。スカラー・コンテキストでは先頭のドキュメントを、配列コンテキストではすべてのドキュメントを返します。

未実装の機能:

  1. UTF-8 デコード済みの文字列のみ受け付け、バイトオーダマーク付き UTF-8UTF-16UTF-32 を扱うことはできません。
  2. 改行文字は \r\n、\r、\n の3種類だけを扱えて、コードポイント u0080 以上の改行コードは改行として認識できません。
  3. ダブル・クォートとシングル・クォートの中ではインデントを無視します。
  4. 浮動小数点数は形式チェックなしで文字列扱いにします。