本連載では第一線の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インタプリタをgdb
やlldb
などのCデバッガを用いてトレースしながら処理を追っていく方法です。このためには、Cコンパイラにデバッグオプションを指定したうえでPerlインタプリタをビルドする必要があります。
静的な読み方とは、grep
やrg
、find
などのファイル検索コマンドを用いて読むべきファイルを探し出し、エディタ上で読んでいく方法です。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なども含まれています。これらのファイルは、ほとんどがトップディレクトリに置かれています。
サブディレクトリとしては、t
とx2p
があります。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
にはIF
やWHILE
、BLOCK
などのPerlの制御構造が含まれ、ARG
にはADD
やPOP
、LT
などの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にもある、if
、unless
、for
やwhile
などの制御構造が実装されています。ただし、この時点ではforeach
は実装されていません。
また、join
、pop
やpush
などのリスト操作系の関数や、ファイルハンドルを開くopen
などの関数もこの時点で実装されています。
<続きの(2)はこちら 。>
特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT