Perl Hackers Hub

第62回Perl歴史散策 ―インタプリタの実装と、構文の進化をたどる(1)

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーは清水隆博さんで、テーマは「Perl歴史散策」です。Perl 1.0から現在までのバージョンを、実装と構文の両面から追っていきます。

Perlのバージョン

Perlは、最初のリリースである1.0から現在に至るまで、数々のバージョンの遍歴を経ています。みなさんが現在利用しているのは、ほとんどがPerl 5のいずれかのバージョンでしょう。Perlはバージョンが上がるにつれて、構文はもちろんのこと、C言語によるインタプリタの実装もさまざまな改良や変更が行われてきました。そこで本稿では、最初のリリースから現在までの代表的なPerlのバージョンについて、Perlの構文とC言語による実装の両方の側面から追っていきます。

Perlインタプリタのソースコード

Perlの最初のリリース時にはGitは存在していませんでした。当時のPerlのソースコードは、メーリングリストやPerforce[1]で管理されていました。

2008年に、これらの履歴も含めてGitリポジトリに変換する作業が行われました。そして2019年に、GitリポジトリのGitHubへの移行が行われます。その結果、現在はGitHub上で、C言語で実装されたPerlインタプリタのソースコードを、すべての履歴を含め参照可能です。

なお、本稿の後半でPerl 6についても触れますが、上記のGitHubリポジトリにはPerl 6のソースコードは含まれません。

これまでのすべてのPerlのバージョン

Perlの各バージョンは、Gitのタグとして管理されています。実際にリポジトリをGitHubからcloneし、タグを確認してみます。

$ git clone git@github.com:Perl/perl5.git
$ cd perl5
$ git tag
GitLive-blead
(省略)
if-0.0605
perl-1.0
perl-1.0.15
perl-1.0.16
perl-2.0
perl-2.001
perl-3.000
perl-3.044
perl-4.0.00
perl-4.0.36
perl-5.000
(省略)

RCバージョンや考古学的にメンテナンスされた古いバージョンを含め、執筆時点(2020年5月)で374バージョン(タグ)が存在しています。過去の代表的なPerlのバージョンや、pumpkingと呼ばれるバージョンごとのメンテナンスリーダーの遍歴は、perldoc perlhistから参照できます。

過去のバージョンへの切り替え

git checkoutにより、以前のバージョンのソースコードに切り替えられます。Perl 1.0のソースコードであるタグperl-1.0に切り替えてみましょう。

$ git checkout perl-1.0
$ git log
commit 8d063cd8450e59ea1c611a2f4f5a21059a2804f1 (HEAD, tag: perl-1.0)
Author: Larry Wall <lwall@jpl-devvax.jpl.nasa.gov>
Date:   Fri Dec 18 00:00:00 1987 +0000

コミットログを見ると、Perl 1.0はLarry Wallによって1987年12月にリリースされていることがわかります。

Perlインタプリタのソースコードリーディング

Perlのインタプリタは、Perl 1.0の時点でそこそこの大きさのプログラムです。筆者が巨大なC言語のプログラムを読む際は、動的な読み方と静的な読み方を使い分けます。

動的な読み方とは、ビルドしたPerlインタプリタをgdblldbなどのCデバッガを用いてトレースしながら処理を追っていく方法です。このためには、Cコンパイラにデバッグオプションを指定したうえでPerlインタプリタをビルドする必要があります。

静的な読み方とは、greprgfindなどのファイル検索コマンドを用いて読むべきファイルを探し出し、エディタ上で読んでいく方法です。Gitリポジトリ限定ですが、git log --grep='perl6'などのコマンドで、読むべきコミットをログから検索する手法もあります。

Perl 1.0─⁠─最初のPerl

1987年12月にLarry WallによってリリースされたのがPerl 1.0です。Perl 1.0は、comp.sources.miscニュースグループ上で発表されました。当時、汎用的なスクリプト言語はまだ存在しませんでしたが、sed、awk、shやC言語などはありました。当時のPerlは、これらの言語の影響を強く受けてデザインされています。

リポジトリの構成

Perl 1.0のリポジトリは全108ファイルで構成されています。含まれるファイルの大半がC言語のソースコード、ヘッダファイルです。現在のPerl 5でも使用されているビルドツールのConfigure、README、次期バージョンでのToDoがまとめられたWhishlistなども含まれています。これらのファイルは、ほとんどがトップディレクトリに置かれています。

サブディレクトリとしては、tx2pがあります。tには、Perlインタプリタのテスト用コードが置かれています。x2pには、awk、sedのコードをPerl 1.0のコードに変換するコマンドを構成するソースコードが置かれています。

インタプリタの実装

C言語は1989年に制定されたC89(ANSI C)が最古の規格ですが、Perl 1.0はその規格が登場する前に発表されています。このため、Perl 1.0はいわゆるK&R Cスタイルと呼ばれる、C言語規格化前のスタイルで実装されています。たとえばPerl 1.0のmain関数のソースコードは次のものです。

main(argc,argv,env)
register int argc;
register char **argv;
register char **env;
{
    register STR *str;

現代のC言語では、registerキーワードや、main関数の3つ目の引数は非推奨です。また、現在のPerl 5のソースコードと比較すると、ソースコード中のコメントは非常に少ないです。

スクリプトの解析と実行

スクリプト言語の処理系は、入力されたソースコードに対して、基本的に次の3つを行います。

❶字句解析
与えられたソースコードを、内部で意味があるキーワードごとに分割する
❷構文解析
キーワードの並びから文脈を判断し、抽象構文木を作成する
❸抽象構文木の評価
実際に入力されたプログラムを実行する

字句解析

字句解析は、perly.c内のyylex関数で実装されています。

構文解析

構文解析は、UNIXの歴史的な構文解析ツールであるyaccを利用してperl.yで定義されています。このperl.yの中身が、当時のPerlの文法と対応します。

sexpr : sexpr '=' sexpr
      {   $1 = listish($1);
          if ($1->arg_type == O_LIST)
        $3 = listish($3);
          $$ = l(make_op(O_ASSIGN, 2, $1, $3, Nullarg,1)); }

上記のyaccコードは、Perlの代入文$someVar = "foo"の定義です。この処理の中のmake_op関数でPerl 1.0は抽象構文木を生成します。

Perl 1.0では、標準関数や制御構造、スクリプト中で宣言した変数や計算結果などがそれぞれC言語の構造体で表現されます。Perlで使用される変数や文字列リテラルなどは、すべてSTR構造体として表現されます。配列は、このSTR構造体の配列の参照を持つ構造体ARRAYで宣言されています。

ほかにも、制御フローのCMD内部表現に変換された各計算の構造体ARGも存在します。また、変数の情報の管理を行うシンボルテーブルSTABや、連想配列の構造体HASHが存在します。インタプリタの立ち上げ時には、シンボルテーブル内に標準入出力などをSTRにマッピングしたものを登録するなどの処理が実行されます。

抽象構文木の評価

Perl 1.0では、cmd_exec関数で抽象構文中のCMD構造体を評価していきます。抽象構文を構成する構造体であるCMDにはIFWHILEBLOCKなどのPerlの制御構造が含まれ、ARGにはADDPOPLTなどのPerl内の計算や関数名が含まれています。Perl 1.0では、まず抽象構文木のCMD構造体を再帰的に評価していき、CMDの子要素であるARGを評価して処理を実行します。Perlプログラムの最適化は、主にCMD構造体の評価時に行われます。

ドキュメントとテストコード

Perl 1.0時代にPerlの書籍は存在せず、現在のインターネットでも当時のプログラムを探すのは困難です。幸いPerlリポジトリにはmanページが付随し、さらにインタプリタのテストコードも同梱されています。これらを手がかりに、Perl 1.0の機能や構文を探ります。

Perl 1.0のmanページは2分割されているため、catコマンドなどを用いて1つのmanファイルを生成します。

perl.man.1とperl.man.2を連結してperl.manを作成
$ cat perl.man.1 perl.man.2 > perl.man

作成したperl.manをmanコマンドを使ってレンダリングする
$ man ./perl.man

テストコードはtディレクトリの中に置かれています。命名規則はテストしたい内部構造体.その内容です。たとえばio.fsは入出力のファイルハンドルのテスト、cmd.whileファイルは制御構造のwhile文のテストとなっています。これらのテストの多くは現在のPerl5でも実行可能で、かつ当時期待されていた動作が行われます。

基本文法

先ほど作成したmanページやテストコードを見ながら、当時の基本文法に迫っていきます。

データ構造

現代のPerl 5のデータ型は、スカラ型、配列、ハッシュの3種に分類されます。それに対してPerl 1.0のデータ型は、文字列string⁠、文字列の配列array of strings⁠、連想配列associative arraysの2.5種です。2.5種と書いた理由は、当時の連想配列は値の追加および参照はできますが、キーの一覧を取得することや削除などができないためです。そのためmanページでも、⁠まだ)使い道、価値がない」と書かれています。

演算子

演算子は現在のPerl 5と同様に、neなどの文字列比較演算子と==などの数値比較演算子が区分されています。直前の式を繰り返すx演算子なども、この時点ですでに存在しています。

現在は範囲演算子として広く利用されている..は、Perl 1.0の時代はいわゆるフリップフロップ演算子として実装されています[2]⁠。この演算子は$left .. $rightと使用し、左右の値が数値の場合は行番号と比較します。$leftが一度真の値になると、$rightが偽の値になるまで全体として真を返し、$rightが偽になると以降は全体で偽を返します。この挙動がフリップフロップ回路と似ているため、演算子に名前が付いています。..は、sedとawkでの「特定の行から特定の行まで取り出す」などの処理の記述に由来します。awkではawk'NR==2, NR==4 { print $0 }'と、記号が範囲を表現する演算子としてあります。これをPerlに取り入れた際に..として実装されたと考えられます。

フォーマット機能

Perlでは、formatから始まるテンプレート記法に基づいて、Perlスクリプトで機械的にレポートページを作るフォーマット機能が使用できます。⁠実用的なデータ抽出とレポート作成言語」Practical Extraction and Report Languageの名前のとおり、この時点でPerlのフォーマット機能は実装されています。

そのほかの機能

Perl 1.0の時点で、現代のPerl 5にもある、ifunlessforwhileなどの制御構造が実装されています。ただし、この時点ではforeachは実装されていません。

また、joinpoppushなどのリスト操作系の関数や、ファイルハンドルを開くopenなどの関数もこの時点で実装されています。

<続きの(2)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.130

2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8

  • 特集1
    イミュータブルデータモデルで始める
    実践データモデリング
  • 特集2
    いまはじめるFlutter
    iOS/Android両対応アプリを開発してみよう
  • 特集3
    ブロックチェーン、スマートコントラクト、NFT
    作って学ぶWeb3

おすすめ記事

記事・ニュース一覧