Next: , Previous: , Up: Perl   [Contents][Index]


15.5.18.1 Perlコードをパースするときの一般的な問題

PerlだけがPerlをパースできるということを、しばしば耳にしますが、これは真実ではありません。結局Perlをパースすることはできず、実行することができるだけなのです。Perlにはビルトインでさまざまなあいまいさがあり、それを解決できるのは実行時だけなのです。

以下は一般的な例を示した例です:

print gettext "Hello World!";

これは関数呼び出しの"堅実"な例に見えるかもしれませんが、そうでもありません:

open gettext, ">testfile" or die;
print gettext "Hello world!"

上記のコンテキストでは、文字列gettextはファイルハンドルのように見えます。しかし必ずしもそうではありません:

use Locale::Messages qw (:libintl_h);
open gettext ">testfile" or die;
print gettext "Hello world!";

この例では、Perlのインクルードパスで最初に見つかるLocale::Messagesモジュールがエクスポートするgettext関数により提供されるファイル名は、多分文法エラーになるでしょう。しかし実際のLocale::Messagesモジュールが以下のようなものだったらどうなるでしょうか?

use vars qw (*gettext);

1;

このケースでは、文字列gettextは再びファイルハンドルとして解釈されるので、testfileというファイルを作成して、そのファイルに“Hello world!”という文字列を書き込む例になります。より高度なフロー分析による制御も、助けにはなりません:

if (0.5 < rand) {
   eval "use Sane";
} else {
   eval "use InSane";
}
print gettext "Hello world!";

わたしたちが期待するようなgettext関数をエクスポートするモジュールがSaneで、gettextというハンドルの出力ストリームを書き込み用にオープンするのがInSaneというモジュールの場合、わたしたちには実行時に何が起こるのか知る糸口がなく、完全に予測不能です。本当のところPerlには、実行時にそれ自身のシンボルテーブルにシンボルを追加するために非常に多くの方法があり、実行することなく特定のコードの断片を解釈するのは不可能なのです。

もちろんxgettextは翻訳可能な文字列を走査するときに、走査するPerlのソースを実行するわけではなく、コードを記述した人が何を意図したのかを推測するために発見的な手法を用います。

他にもスラッシュとクエスチョンマークの曖昧さという問題があります。これらの解釈はコンテキストに依存します:

# A pattern match.
print "OK\n" if /foobar/;

# A division.
print 1 / 2;

# Another pattern match.
print "OK\n" if ?foobar?;

# Conditional.
print $x ? "foo" : "bar";

スラッシュは除算のための演算子と、パターンマッチをあらわすための両方の用途で使用されます。一方、クエスチョン マークは3項演算の条件判定と、パターンマッチでも使用されます。他のawkのようなプログラム言語にも同様な問題はありますが、このようにソースを誤って解釈してしまう問題はPerlのソースの場合が特に顕著なのです。たとえばawkでは、命令文は決して1行を超えないので、パーサーはパースエラーから復帰して、次の行から残りの入力ストリームを正しく処理できます。これと異なりPerlは、コンテキストの意味とは無関係に、入力ストリームに次の区切り文字(スラッシュまたはクエスチョンマーク)が出現することでパターンマッチが終端されます。本来は除算演算子として使用されているスラッシュがパターンマッチと誤って解釈された場合、おそらく残りの入力ファイルは正常にパースされないでしょう。

以下に、あいまいさの解決が不可能なケースを示します:

$x = wantarray ? 1 : 0;

Perlのビルトイン関数であるwantarrayは引数をとらないので、Perlのパーサーはクエスチョンマークが正規表現の開始ではなく、3項演算子だと知ることができます。

sub wantarrays {}
$x = wantarrays ? 1 : 0;

今度は状況が異なります。関数wantarraysは、可変個の引数をとります(他のプロトタイプがない任意のPerl関数と同様です)。この場合、クエスチョンマークはパターンマッチの区切り文字として解釈されるので、このコードの断片はコンパイルされません。

sub wantarrays() {}
$x = wantarrays ? 1 : 0;

さて今度は関数がプロトタイプされているので、Perlは関数が引数をとらないことを知っているので、クエスチョンマークは再び3項演算子として解釈されます。しかし残念ながらxgettextはそうではありません。

xgettextのPerl用パーサーは、関数にプロトタイプがあるのか、そしてそのプロトタイプがどのように見えるのか知ることができないので、経験的に推測することになります。ある関数がPerlのビルトイン関数で、この関数が引数を受け付けない場合は、それに続くクエスチョンマーク(またはスラッシュ)は演算子として扱われ、それ以外の場合は以降に続く正規表現のための区切り文字として扱われます。Perlのビルトイン関数で引数をとらないのはwantarrayforktimetimesgetlogingetppidgetpwentgetgrentgethostentgetnetentgetprotoentgetserventsetpwentsetgrentendpwentendgrentendhostentendnetentendprotoentendserventです。

もしxgettextが、あなたのソースから文字列を抽出するのに失敗する場合には、このセクションで説明したようなスラッシュ(またはクエスチョンマーク)を探してみる必要があります。もしかしたらxgettextのPerlパーサーの、他のバグである可能性もあります(もちろんそのようなバグは報告する必要があるでしょう)。そのうちに、あなたはxgettextに "挑戦"するより、マナーにのっとってコードを整形する必要があると思うでしょう。

特に、引数をとらない関数をパーサーが認識できないときは、カッコを使ってください:

$x = somefunc() ? 1 : 0;
$y = (somefunc) ? 1 : 0;

実際のところはPerlのパーサーも、このような状況にたいする問題をもっていて、警告を発します。