Skip to content

Latest commit

 

History

History
1193 lines (1046 loc) · 45.7 KB

200406-14.md

File metadata and controls

1193 lines (1046 loc) · 45.7 KB

Truth of the Legend -- UNIX 考古学 第14回

Berkeley Pascal System

リード

今回は初期の BSD Unix での最大の目玉である Berkeley Pascal System について御紹介します。1970 年代当時、 隆盛を極めた Pascal の著名な処理系の1つとして知られたこのシステムは、 同時に今日の Java の処理系などとも共通する、 ユニークな実装方法を採用していたことでも有名でした。 Unix 上で動作するプログラミング言語の実装例としても 皆さんの参考になることでしょう。


冒頭から恐縮ですが、、、皆さんにお詫びしなければならないことがあります。 実は困ったことに 2BSD に収録されている Pascal System は、Version 7 Unix の上ではコンパイルさえ成功しません。2BSD の "Installation Instructions" には次のような記述があります。

Install the Pascal system.
Some of the parts of the Pascal system
will require special treatment on version 7
as they use the older i/o library of version 6.
See the file misc/v7pascal for more details.

つまり 2BSD に収録されている Pascal System は Version 6 Unix にのみ 対応しているというわけです。そこで /mnt/misc/v7pascal を除いてみると、、、

Call me if you need to get Pascal up on version 7.
I hope to have runnable binaries soon.
	Bill Joy
	(415) 642-4948
	(415) 642-1024 (messages)

うーん、、、今、この番号に電話をしても、おそらく Bill Joy が 電話口に出てくれることはないでしょうからねぇ、、、これは困った。 とはいえ、往生ぎわの悪い私のことですから、例によって反則技を使って でも Berkeley Pascal System を御紹介する所存です。(だって予告しちゃったから)

ということで、、、私がこの問題をどう決着をつけるかはちょっと棚上げにして、 まずは Pascal の参考書のお話から始めましょう。

Pascal に関する情報

Pascal に関する参考書は多数販売されていますし、 ホームページも多数存在しますので、 プログラミングに関する何らかの情報を手に入れるのはそれほど苦労しません。 が、「歴史的に重要な」という観点で幾つか御紹介しておきたい文献があります。

Pascal のリファレンス

C 言語のリファレンスとして Brian Kernighan と Dennis Ritchie の "The C Programming Language" ISBN 0-13-110362-8 [邦訳: プログラミング言語C ISBN 4-320-02692-6] は有名ですが、 同じように Pascal にもリファレンスと呼ばれている書籍が出版されています。 Kathleen Jennsen, Niklaus Wirth "PASCAL - User Manual and Report" ISBN 0-387-97649-3 この本は 1974 年に初版が出版されました。 邦訳としては Pascal (情報処理シリーズ2) ISBN 4-563-01466-4 が出版されています。 現在、新書として入手できるのは Andrew Mickel と James Miner の 手により改定された第4版で、 Pascal の標準仕様である ISO Pascal Standard に対する 改定内容が反映されています。

この書籍は User Manual と Report の2部構成で、 User Manual は Pascal の学習書、 Report は Pascal の言語仕様が解説されています。 Berkeley Pascal System もこの書籍を元に開発されましたので、 参考書としてはもっとも重要なものでしょう。

Pascal-S

Pascal の言語処理系の実装に関する具体的な方法を解説した文献としては、 第11回で御紹介した Pascal-S に関する論文 "PASCAL-S: A Subset and its Implementation" が上げられます。 Pascal の開発者である Niklaus Wirth が自ら書いた Pascal の言語処理系実装の解説は、 やはり歴史的に重要だと私は思います。 論文では Pascal の言語処理系の概要について 次のように解説されています。

The Pascal-S system is described as a compiler
that translates Pascal-S programs into code
for a hypothetical stack computer
especially designed for this purpose.
This computer is itself defined as an algorithm,
called the interpreter of the compiled code.
Both compiler and interpreter are described in
a largely machine-independent way
by using the high-level language Pascal exclusively.
In fact, these two parts form a single Pascal program.

Pascal-Sシステムは、Pascal-Sプログラムを、
その目的のためにとくに設計された仮想的なスタックコンピュータのための
コードに翻訳するコンパイラと解釈できます。
このコンピュータはそれ自体、コンパイルされたコードの
インタープリタと呼ばれるアルゴリズムとして定義されます。
コンパイラおよびインタープリタの両方は、高水準言語Pascalを
排他的に使用することによって大部分はマシンに依存しない方法
で記述されますが、実際にはこれらの2つのパートは
単一のPascalプログラムを形成しています。

ここで述べられているコンパイラ/インタープリタとは 一般的な意味でのそれではなく、 「仮想的なスタック・コンピューターのためのコードを生成するコンパイラ」 と 「仮想的なスタック・コンピューターそのものを実現するインタープリタ」 を意味しています。したがって Java のコンパイラとVMというイメージを 描いていただいたほうがよいでしょう。 但し、最後の部分で記述されているように、 オリジナルの Pascal-S では 両者は1つの Pascal の処理系プログラムの中に同居していたようです。

ちなみに 1BSD では、この Niklaus Wirth 自身が Pascal で記述した Pascal-S のコンパイラ/インタープリタのソースコードが収録されており、 Berkeley Pascal System を使って動かすことができたようです。

Berkeley Pascal System のコンポーネント

さて、今回インストール方法を御紹介する Berkeley Pascal System は次のような複数のコマンドから構成されます。

pi    - Pascal interpreter code translator
px    - Pascal interpreter
pix   - Pascal interpreter and executor
pxp   - Pascal execution profiler
pxref - Pascal cross-reference program

このうちシステムの中核となるのは、 Pascal-S でのコンパイラに相当する pi と、 Pascal-S でのインタープリタに相当する px です。 オリジナルの Pascal-S と大きく異なるのは pi と px が 各々独立したコマンド (プログラム) であるところで、 それ故 pi でコンパイルして、 その後に px を使ってコンパイル結果を実行する、 という手順が Berkeley Pascal System の基本的な使い方になります。

両者を見かけ上一体化した、 pix というコマンドも用意されていますが、 これは C コンパイラに置ける cc コマンドと同じようなフロントエンドでして、 その中ではコプロセスを fork して pi と px を順次実行しています。

pxp は Unix でお馴染みのプロファイルが作成できるコマンドです。 中身は px にプロファイルを作成する機能がついたものですが、 更にコンパイルコードをデコンパイル (逆変換) してソースコードを 生成するタイプの pretty-printer 機能もついています。

pxref は Pascal のクロスリファレンサですが、 実は Niklaus Wirth 自身が Pascal で記述したクロスリファレンサを コマンドとして実行できるようにしたものです。

文献によれば Pascal のコンパイルコードを対象にしたデバッガ pdb の 作成も検討されたようですが、結局 pdb は実装されませんでした。

Berkeley Pascal System のインストール

それでは Berkeley Pascal System のコンパイル/インストールを始めましょう。 といっても冒頭でお話したように、2BSD に収録されているソースコードは そのままではコンパイルが通りません。Version 6 Unix と Version 7 Unix は 互換性に問題がありますので。詳しくは★コラム1を御覧ください

Version 7 Unix で動く PDP-11 Berkeley Pascal System は?

というわけで、やむなく 2BSD 以降の PDP-11 版 BSD Unix を トレースバックすることにしました。 現在 The Unix Heritage Society (http://www.tuhs.org/) から入手できる ソースコードが現存する PDP-11 版 BSD Unix は以下の通りです。

1BSD 2BSD 2.79BSD 2.8BSD 2.9BSD 2.10BSD 2.10.1BSD 2.11BSD

2BSD の後がいきなり 2.79BSD にとんでいるので、 この間に多くの幻のバージョンのではないかと考えたくなります。 が、2.79BSD がリリースされたのは 1980 年 3 月あたりと推測され、 2BSD のリリースから1年あまり程度しか経過していないことから、 この間のアップデートは一般的な意味でのマイナーアップデートを 行っていたと考えた方がよいでしょう。(Version 7 Unix のリリースに 伴って AT&T が改正した Unix ライセンスの影響により、BSD Unix は メジャーアップデートなみの機能強化を行っても、メジャーバージョンを 更新しなくなりました)

2.79 という中途半端なバージョン番号は、 おそらく「2.8 の1つ前」という意味だと推測されます。 事実、続く 2.8BSD は 1981 年の 7 月、4.1BSD のリリースと ほぼ同時期にリリースされ、以降 4.XBSD と 2.XBSD のランデブー リリースがしばらく続きます。ディストリビューションの内容を比較すると、 2.79BSD は 2BSD の内容をそのまま受け継いでいるのに対し、 2.8BSD ではディレクトリ構造など多くの変更点が見受けられます。 したがって 2.79BSD がリリースされたとされる 1980 年 3 月とは、 それまでの 2BSD から 3BSD への各種移行作業が収束し、 3BSD あるいは 4.XBSD での開発成果が 2.XBSD へ反映されるようになる 切り替わり時期にあたるのではないかと私は考えています。

さて、問題の PDP-11 版 Berkeley Pascal System ですが、 ディストリビューションをくまなく探した結果、2.8BSD の /usr/src.v7 の下に収録されていることが分かりました。 そこで今回は 2.8BSD に収録されていた Berkeley Pascal System の ソースを元にコンパイル/インストールの方法を御紹介したいと思います。

今回の実習では前回使った 2BSD のソースコードに追加が必要です。 付録に 2.8BSD Berkeley Pascal System をコンパイル/インストールに 必要な追加ソース含む tar アーカイブファイル src.v7.tar.tap を収録 して置きました。まずは実行例1の手順でこのファイルを展開してください。

実行例1 追加ソースの展開

$ pdp11

PDP-11 simulator V3.0-2
sim> set cpu URH70
sim> set cpu 2M
Disabling XQ
sim> set rp0 RP06
sim> att rp0 rp0.dsk
sim> set rp1 RP06
sim> att rp1 rp1.dsk
sim> att tm0 src.v7.tar.tap
sim> boot rp0
boot
Boot
: hp(0,0)unix
mem = 2020544
#
# DATE 200404071328
WED APR  7 13:28:00 JST 2004
# <CNTL-D>
RESTRICTED RIGHTS: USE, DUPLICATION, OR DISCLOSURE
IS SUBJECT TO RESTRICTIONS STATED IN YOUR CONTRACT WITH
WESTERN ELECTRIC COMPANY, INC.
WED APR  7 13:28:07 JST 2004

login: root
Password:
You have mail.
#
# /etc/mount /dev/rp7 /mnt
# cd /mnt
# cat /dev/rmt0 | tar xvf -
x src.v7/pascal/pascal/assubs/fcrt0, 160 bytes, 1 tape blocks
x src.v7/pascal/pascal/assubs/fcrt0.s, 307 bytes, 1 tape blocks
x src.v7/pascal/pascal/assubs/makefile, 59 bytes, 1 tape blocks

    ....

x src.v7/eyacc/ey5.c, 784 bytes, 2 tape blocks
x src.v7/eyacc/makefile, 373 bytes, 1 tape blocks
x src.v7/eyacc/READ_ME, 967 bytes, 2 tape blocks
#

cpp の再インストール

さて、例によって Berkeley Pascal System をコンパイルするためにも、 準備作業が必要になります。その1つは cpp の再インストールです。 Berkeley Pascal System の C ソースコードにはかなり大きなものが含まれます。 そのため Version 7 Unix に添付される標準の cpp では、 シンボルテーブルが溢れてしまいコンパイルできないものがあります。 そこで、cpp のシンボルテーブルを広げてやる必要があります。 実行例2を御覧ください。

実行例2 cpp の再インストール

# cd /usr/src/cmd/cpp
# cp cpp.c cpp.c.orig
# vi cpp.c

    ....

# diff -e cpp.c.orig cpp.c
168c
# define symsiz 500
.
# make
cc -O -n -s -c -Dunix=1 -Dpdp11=1 cpp.c
yacc  cpy.y
cc -O -n -s -c y.tab.c
rm y.tab.c
mv y.tab.o cpy.o
cc -O -n -s cpp.o cpy.o -o cpp
# make cp
cp cpp /lib
rm *.o cpp
#

ここでは cpp のソースファイル cpp.c で定義されている マクロ symsiz の値を 400 から 500 に増やしています。 この symsiz がシンボルテーブルの大きさになります。 ファイルの修正が終ったら make と make cp を実行してください。 シンボルテーブルを広げた cpp がインストールされます。

eyacc のインストール

もう1つの準備作業は eyacc のインストールです。 eyacc コマンドは Pascal の構文パーサー pas.y を C ソースに正しく変換できるよう拡張を行った yacc です。 この eyacc コマンドは 1BSD のころから存在し、 オリジナルは Version 6 Unix の yacc に対して修正を行ったものでした。 今回は追加ソースに収録されている Version 7 Unix に対応した eyacc を インストールします。実行例3を御覧ください。

実行例3 eyacc のインストール

# cd /mnt/src.v7/eyacc
# make
cc -O -c ey0.c
cc -O -c ey1.c
cc -O -c ey2.c
cc -O -c ey3.c
cc -O -c ey4.c
cc -O -c ey5.c
cc -i ey0.o ey1.o ey2.o ey3.o ey4.o ey5.o
# make install
cp a.out /usr/ucb/eyacc
#

これはごく普通に make と make install するだけです。 以上で Berkeley Pascal System をコンパイルする準備が整いました。

ランタイムルーチンのコンパイル

それでは Berkeley Pascal System のコンパイルを始めます。 まずランタイムルーチンのコンパイルから始めます。 実行例4を御覧ください。

実行例4 ランタイムルーチンのコンパイル

# cd /mnt/src.v7/pascal/pascal/assubs
# rm *.o
# make
as -o pcrt0.o /usr/include/sys.s pcrt0.s
#
# cd /mnt/src.v7/pascal/pascal/fpterp
# rm *.o
# as -o fp.o fp?.s
#
# cd /mnt/src.v7/pascal/pascal
# cp how_* /usr/lib/
#

ここで作成しているのは Pascal 専用のランタイムルーチン pcrt0.o、 浮動少数点演算用の低水準ルーチン fp.o の2つです。pcrt0.o は px で、 fp.o は px と pxp で使用します。更に pi, px, pxp コマンドのヘルプ ファイルである how_pi, how_px, how_pxp もインストールしておきましょう。

pi のインストール

ではコマンドのインストールを始めましょう。 最初 pi コマンドからです。実行例5を御覧ください。

実行例5 pi コマンドのインストール

# cd /mnt/src.v7/pascal/pi
#
# cp makefile makefile.orig
# vi makefile

    ....

# diff -e makefile.orig makefile
34,35c
MKSTR=/usr/ucb/mkstr
EYACC=/usr/ucb/eyacc
.
#
# cp gram gram.orig
# vi gram

    ....

# diff -e gram.orig gram
1d
#
# cp main.c main.c.orig
# vi main.c

    ....

# diff -e main.c.orig main.c
33c
char    *errfile        = "/usr/lib/pi1.2strings";
.
# make clean
rm *.o tmp/*.c
rm: *.o nonexistent
rm: tmp/*.c nonexistent
*** Error code 2 (ignored)
rm y.tab.c y.tab.h
rm pi1.2strings
rm a.out core px_header.out *.list obj
rm: a.out nonexistent
rm: core nonexistent
*** Error code 2 (ignored)
# mkdir tmp
# make
rm -f tmp/ato.c
/usr/ucb/mkstr - pi1.2strings tmp/ ato.c
cd tmp ; cc  -I.. -c ato.c ; mv ato.o ../ato.o
rm -f tmp/ato.c

    ....

/usr/ucb/eyacc pas.y >pas.eyacc.list

conflicts: 18 shift/reduce, 4 reduce/reduce
ex - y.tab.c < gram

    ....

rm -f tmp/yytree.c
/usr/ucb/mkstr - pi1.2strings tmp/ yytree.c
cd tmp ; cc  -I.. -c yytree.c ; mv yytree.o ../yytree.o
rm -f tmp/yytree.c
./version > Version.c
cc  -i ato.o  call.o case.o clas.o const.o conv.o cset.o  error.o fdec.o func.o gen.o hash.o  lab.o lookup.o lval.o  main.o nl.o proc.o put.o  rec.o rval.o  stat.o string.o subr.o  tree.o type.o var.o Version.o  TRdata.o  treen.o putn.o yycopy.o  y.tab.o  yycosts.o yyerror.o yyget.o yyid.o yylex.o yymain.o yyoptions.o  yypanic.o yyparse.o yyprint.o yyput.o yyrecover.o yyseman.o yytree.o Version.c
# make install
cc -O -o px_header.out px_header.c
strip px_header.out
cp pi1.2strings /usr/lib/pi1.2strings
cp how_pi /usr/lib/how_pi
cp how_pix /usr/lib/how_pix
dd if=px_header.out of=/usr/lib/npx_header conv=sync
1+1 records in
2+0 records out
cp a.out /usr/bin/pi
#

まずコンパイル前に makefile, gram, main.c の 3つのファイルの修正が必要です。 makefile の修正は mkstr コマンド、および eyacc コマンドの インストールディレクトリを正しいものに変えています。 次に gram。 これは y.tab.c を修正して y.tab.h を作り出す ex のスクリプト ファイルなんですが、前回作成した ex は先頭にあるコメント行を 読み飛ばしてくれません。そこでコメント行を削除しています。 最後に main.c はエラーメッセージファイル (pi1.2strings) の インストールディレクトリを正しいものに変えています。

後は一般的なインストール手順、 make clean, make, make install を 実行すれば良いわけですが、注意して欲しいのは make を実行する前に サブディレクトリ tmp を作成することです。このディレクトリが 存在しないとソースファイルのコンパイルに失敗します。

なお pi コマンドは /usr/bin にインストールされます。 /usr/ucb ではないので注意してください。 もし /usr/bin ディレクトリがない場合には、 make install を実行する前に作成しておいてください。

px のインストール

次は px のインストールです。実行例6を御覧ください。

実行例6 px のインストール

# cd /mnt/src.v7/pascal/px
# cp makefile makefile.orig
# vi makefile

    ....

# diff -e makefile.orig makefile
27,28c
        rm -f a.out core E.h E.s 00head.s
        rm -f *.o
.
10c
        ld -X -i ../pascal/assubs/pcrt0.o ../pascal/fpterp/fp.o *.o -lm -lc
.
# make clean
rm -f a.out core E.h E.s 00head.s
rm -f *.o
# make
echo 'FP = 0' > 00head.s
ed - Edata < Emake
rm -f as.o
as -o as.o /usr/include/sys.s 00head.s 00int.s 02rel.s 02relset.s 03bool.s 04as.s 05lv.s   06add.s 07sub.s 10mul.s 12div.s 13mod.s 14neg.s   16dvd.s 17ind.s 17rv.s 20con.s 21rang.s 24case.s 24pxp.s 25set.s   26for.s 27conv.s 30atof.s 30getname.s 30io.s 30iosubs.s 30read.s   30write.s 34fun.s E.s opcode.s wait.s
cc -O -DHZ=60.0 -c exit.c
cc -O -DHZ=60.0 -c int.c
cc -O -DHZ=60.0 -c palloc.c
cc -O -DHZ=60.0 -c pcttot.c
cc -O -DHZ=60.0 -c pdattim.c
cc -O -DHZ=60.0 -c perror.c
cc -O -DHZ=60.0 -c pwrite.c
as -o getc.o /usr/include/sys.s getc.s
as -o putc.o /usr/include/sys.s putc.s
ld -X -i ../pascal/assubs/pcrt0.o ../pascal/fpterp/fp.o *.o -lm -lc
# make install
cp a.out /usr/bin/px
chmod 711 /usr/bin/px
#
# cat doub.p
program doub( output );
    begin
        writeln( 3.14159 )
    end.
# pi doub.p
# px obj
  3.141590000000000e+00
1 statement executed in 0.01 seconds cpu time
#

px の場合、修正しなければならないのは makefile のみです。 clean の rm コマンドに -f オプションを追加し、 a.out の ld コマンドの -lc と -lm の位置を入れ換えています。 あとは普通に make clean, make, make install を実行します。 px も /usr/bin にインストールされます。

併せて pi/px で簡単なテストをしておきましょう。 px のディレクトリには doub.p という Pascal のソースファイルがあります。 ここでは pi を使ってオブジェクトファイル obj を作成し、 それを px を使って実行しています。 どうやらちゃんと動いているようですね。

pxp のインストール

次は pxp のインストールです。実行例7を御覧ください。

実行例7 pxp のインストール

# cd /mnt/src.v7/pascal/pxp
# cp makefile makefile.orig
# vi makefile

    ....

# diff -e makefile.orig makefile
30,31c
        rm -f pi1:2strings a.out core y.tab.c
        rm -f *.o x*
.
27c
        ${EYACC} pas.y >pas.eyacc.list
.
18c
            ../pascal/fpterp/fp.o ../pi/TRdata.o ../px/getc.o ../px/putc.o
.
4c
EYACC=  /usr/ucb/eyacc
.
# make clean
rm -f pi1:2strings a.out core y.tab.c
rm -f *.o x*
# make
cc -O -DPXP -c call.c
cc -O -DPXP -c case.c
cc -O -DPXP -c const.c
cc -O -DPXP -c cset.c
cc -O -DPXP -c error.c
cc -O -DPXP -c fdec.c
cc -O -DPXP -c func.c
cc -O -DPXP -c hash.c
cc -O -DPXP -c lab.c
cc -O -DPXP -c lval.c
cc -O -DPXP -c main.c
cc -O -DPXP -c nl.c
cc -O -DPXP -c pmon.c
cc -O -DPXP -c pp.c
cc -O -DPXP -c proc.c
cc -O -DPXP -c rec.c
cc -O -DPXP -c rval.c
cc -O -DPXP -c stat.c
cc -O -DPXP -c string.c
cc -O -DPXP -c subr.c
cc -O -DPXP -c tree.c
cc -O -DPXP -c type.c
cc -O -DPXP -c var.c
/usr/ucb/eyacc pas.y >pas.eyacc.list

conflicts: 18 shift/reduce, 4 reduce/reduce
ed - y.tab.c < gram
cc -O -DPXP -c y.tab.c
cc -O -DPXP -c yycomm.c
cc -O -DPXP -c yycosts.c
cc -O -DPXP -c yyerror.c
cc -O -DPXP -c yyget.c
cc -O -DPXP -c yyid.c
cc -O -DPXP -c yylex.c
cc -O -DPXP -c yymain.c
cc -O -DPXP -c yypanic.c
cc -O -DPXP -c yyparse.c
cc -O -DPXP -c yyprint.c
cc -O -DPXP -c yyput.c
cc -O -DPXP -c yyrecover.c
cc -O -DPXP -c yyseman.c
cc -O -DPXP -c yytree.c
as -o printf.o printf.s
as -o treen.o treen.s
as -o yycopy.o yycopy.s
cc -c Version.c
cc -i call.o case.o const.o cset.o  error.o fdec.o func.o hash.o lab.o lval.o  main.o nl.o pmon.o pp.o proc.o rec.o rval.o  stat.o string.o subr.o tree.o type.o var.o y.tab.o  yycomm.o yycosts.o yyerror.o yyget.o yyid.o yylex.o yymain.o  yypanic.o yyparse.o yyprint.o yyput.o yyrecover.o  yyseman.o yytree.o printf.o treen.o yycopy.o Version.o  ../pascal/fpterp/fp.o ../pi/TRdata.o ../px/getc.o ../px/putc.o
# make install
cp a.out /usr/bin/pxp
chmod 711 /usr/bin/pxp
chown root /usr/bin/pxp
#

まず pi, px と同じように makefile を修正します。 ここでは eyacc コマンドのインストールディレクトリと rm コマンドの -f オプションに関する修正と、 eyacc コマンド実行時の出力をファイルにリダイレクトし、 TRdata.o を pi でコンパイルしたオブジェクトに差し替えています。 あとは px と同様 make clean, make, make install を実行します。 pxp も /usr/bin にインストールされます。

pix のインストール

次は pix コマンドのインストールです。実行例8を御覧ください。

実行例8 pix のインストール

# cd /mnt/src.v7/pascal/pascal
# rm -f pix
# cc -o pix pix.c
# cp pix /usr/bin/
#
# pix ../px/doub.p
Execution begins...
  3.141590000000000e+00
Execution terminated
1 statement executed in 0.01 seconds cpu time
#

pix には makefile がありませんので、 実行例8のように手作業でコマンドを作成します。 これは特に説明することはありませんよね?

ついでに先ほど px のところでテストに使った doub.p を pix から実行してみましょう。いかがですか?

pxref のインストール

最後のコマンド、pxref をインストールします。 実行例9を御覧ください。

実行例9 pxref のインストール

# cd /mnt/src/pxref
# make clean
rm -f pxref
# make
pi pxref.p
In program xref:
  w - field ord is used but never set
mv obj pxref
# make install
mv pxref /usr/ucb/pxref
#
# pxref /mnt/src.v7/pascal/px/doub.p
     1  program doub( output );
     2      begin
     3          writeln( 3.14159 )
     4      end.

 doub           1
 output         1
 writeln        3

         3 identifiers         3 occurrences
#

既に説明したように、このコマンドは Pascal で記述されていますので、 Version 7 Unix 対応問題には関係がありません。2BSD に収録されている ソースをそのままコンパイルすることができます。ここではテストとして、 先ほどの doub.p に対するクロスリファレンスを実行しています。

以上で Berkeley Pascal System のインストールは終りです。

Berkeley Pascal System の使い方

では、インストールした Berkeley Pascal System を動かしてみましょう。 とはいっても Pascal のプログラミングの習得を目的とする解説は、 この連載の主旨から大きく外れてしまいますので、 ここでは教養として知る程度に御紹介することにします。

ラッキーなことに、2BSD に付属する Berkeley Pascal User's Manual の 第2章は Berkeley Pascal の自習の手引の体裁になっており、 この目的にはぴったり Pascal プログラムのコードサンプルが 幾つか紹介されています。Berkeley Pascal でのプログラミングを 想定して、わざとバグも混入してあります。自習者はマニュアルの 記述にしたがって、作業を進めていくと自然と Pascal のプログラミングを 習得できるというわけです。時間のある方は原文を読んで取り組んで いただければ良いかと思いますが、ここでは時間のない方のために 内容を縮小して手っ取り早い解説をします。

なお、以降の実行例の冒頭で /usr/tmp に移動していますが、 これは適当な作業用のディレクトリへ移動するという意味です。 /usr/tmp で作業をした場合、システムの起動時に全てのファイルが 消されてしまいます。ファイルを保存しておきたければ、 任意の作業ディレクトリを作成してそこで作業をしてください。 あるいは Pascal のためのユーザーアカウントを作成しても良いかも知れません。 また Pascal のコードサンプルは /mnt/doc/pascal/ に置かれています。 実は、このディレクトリには Berkeley Pascal User's Manual のソースが 収められています。必要なコードサンプルは各実行例の中で明示します。

Pascal 版 Hello World!

Hello World! は "The C Programming Language" で最初に紹介される C プログラムですが、 ここでは Pascal による "Hello World!" を紹介しています。 実行例10を御覧ください。

実行例10 first.p

$ /usr/tmp
$ cp /mnt/doc/pascal/first.p
$ cat first.p
program first(output)
begin
	writeln('Hello, world!')
end.
$
$ pi first.p
     2  begin
e ------^--- Inserted ';'
$ ex first.p
"first.p" 4 lines, 59 characters
:1
program first(output)
:s/$/;/p
program first(output);
:w
"first.p" 4 lines, 60 characters
:q
$ pi first.p
$ px obj
Hello, world!
1 statement executed in 0.01 seconds cpu time
$

first.p が Pascal 版 "Hello World!" です。 "Hello World!" ぐらい単純なプログラムになると、 どんな言語で書いてもそのソースは似たものになりますね。

pi でコンパイルすると、期待に反してコンパイルエラーが発生します。 コンパイラが表示する "e " で始まる行がエラーメッセージです。 まず、ソースの該当行が表示され、 続いてエラーメッセージが表示されますが、'^' の位置が エラーが発生している位置を示しています。

ここではセミコロン (;) を挿入しろと言ってます。 1行目の行末でセミコロンを入力し忘れたため、 このエラーが発生したようです。1行目の行末に セミコロンを挿入して、再コンパイルすると 今度は正常にコンパイルが終了しました。 なお pi でのコンパイル時に特に指定をしなかった場合、 Pascal のオブジェクトコードは obj という名前のファイルにセーブされます。

次に px を使ってオブジェクトコードを実行します。 今度は正しく実行できましたね。

拡大する波の描画

さて、次は Berkeley Pascal System のエラー診断機構を実感してもらうためのサンプルコードで、 幾つかのバグが混入されています。実行例11を御覧ください。

実行例11 もう少し大きなプログラム

# PATH=$PATH:/usr/ucb
# export PATH
# cd /usr/tmp
# cp /mnt/doc/pascal/bigger.p .
# num bigger.p
     1  (*
     2   * Graphic representation of a function
     3   *    f(x) = exp(-x) * sin(2 * pi * x)
     4   *)
     5  program graph1(output);
     6  const
     7          d = 0.0625;   (* 1/16, 16 lines for interval [x, x+1] *)
     8          s = 32;       (* 32 character width for interval [x, x+1]
     9          h = 34;       (* Character position of x-axis *)
    10          c = 6.28138;  (* 2 * pi *)
    11          lim = 32;
    12  var
    13          x, y: real;
    14          i, n: integer;
    15  begin
    16          for i := 0 to lim begin
    17                  x := d / i;
    18                  y := exp(-x9 * sin(i * x);
    19                  n := Round(s * y) + h;
    20                  repeat
    21                          write(' ');
    22                          n := n - 1
    23                  writeln('*')
    24  end.
# pix bigger.p
     9          h = 34;       (* Character position of x-axis *)
w -----------------------------^--- (* in a (* ... *) comment
    16          for i := 0 to lim begin
e --------------------------------^--- Inserted keyword do
    18                  y := exp(-x9 * sin(i * x);
E --------------------------------^--- Undefined variable
e -----------------------------------------------^--- Inserted ')'
    19                  n := Round(s * y) + h;
E ---------------------------^--- Undefined function
E ------------------------------------------^--- Undefined variable
    23                  writeln('*')
e ----------------------^--- Inserted ';'
    24  end.
E ------^--- Expected keyword until
E ----------^--- Unexpected end-of-file - QUIT
Execution suppressed due to compilation errors
# ex bigger.p
"bigger.p" 24 lines, 512 characters
:/s =
        s = 32;       (* 32 character width for interval [x, x+1]
:s/$/ *)
        s = 32;       (* 32 character width for interval [x, x+1] *)
:/for
        for i := 0 to lim begin
:s/begin/do &
        for i := 0 to lim do begin
:/exp
                y := exp(-x9 * sin(i * x);
:s/x9/x)
                y := exp(-x) * sin(i * x);
:s/i /c /
                y := exp(-x) * sin(c * x);
:/Round
                n := Round(s * y) + h;
:s/Round/round
                n := round(s * y) + h;
:/writeln
                writeln('*')
:i
                until n = 0;
.
:/end.
end.
:i
        end
.
:w
"bigger.p" 26 lines, 538 characters
:q
# num bigger.p
     1  (*
     2   * Graphic representation of a function
     3   *    f(x) = exp(-x) * sin(2 * pi * x)
     4   *)
     5  program graph1(output);
     6  const
     7          d = 0.0625;   (* 1/16, 16 lines for interval [x, x+1] *)
     8          s = 32;       (* 32 character width for interval [x, x+1] *)
     9          h = 34;       (* Character position of x-axis *)
    10          c = 6.28138;  (* 2 * pi *)
    11          lim = 32;
    12  var
    13          x, y: real;
    14          i, n: integer;
    15  begin
    16          for i := 0 to lim do begin
    17                  x := d / i;
    18                  y := exp(-x) * sin(c * x);
    19                  n := round(s * y) + h;
    20                  repeat
    21                          write(' ');
    22                          n := n - 1
    23                  until n = 0;
    24                  writeln('*')
    25          end
    26  end.
# pi bigger.p
# px obj
Floating divide by zero

        Error at "graph1"+2 near line 17

2 statements executed in 0.01 seconds cpu time
# ex bigger.p
"bigger.p" 26 lines, 538 characters
:17p
                x := d / i;
:s;/;*;
                x := d * i;
:w
"bigger.p" 26 lines, 538 characters
:q
# pi bigger.p
# px
                                  *
                                              *
                                                      *
                                                           *
                                                           *
                                                        *
                                                  *
                                          *
                                  *
                           *
                      *
                   *
                   *
                     *
                         *
                             *
                                  *
                                      *
                                         *
                                           *
                                           *
                                          *
                                        *
                                     *
                                  *
                               *
                              *
                             *
                            *
                             *
                               *
                                *
                                  *
2550 statements executed in 0.12 seconds cpu time
#

pix で bigger.p を実行しようとしたところ、 9つもエラーメッセージが表示されました。

実はこのプログラム、前述の "Pascal User Manual & Report" に掲載されていたそうですが、マニュアルを見てソースを 打ち込む際に幾つもケアレスミスをしているようです。 9行目のエラーはコメントの開始シンボルが 繰り返し使用されていることを指摘しています。 8行目でコメントを閉じていないのが原因です。 16行目はエラーメッセージのとおり begin の前に do が抜けています。 18行目は Undefined variable と指摘されています。 おそらくタイプミスだと思われますが、 ')' の変わりに '9' が入力されたのが原因です。 同様に19行目もタイプミスで round を Round と入力してしまっています。 22行目はセミコロンが抜けていると指摘されていますが、 その後で "Expected keyword until" というエラーメッセージ、 つまり repeat と対になる util が存在していないと指摘されていますので、 util が含まれる行が抜け落ちているようです。

以上の問題の箇所を修正して再コンパイルをします。 今度は無事コンパイルができました。引き続き px を使って実行すると、 17行目で "Floating divide by zero" が発生したことを指摘しました。 '*' を入力すべきところで '/' を入力していたようです。

更に修正して再コンパイルした後、オブジェクトコードを実行します。 今度はちゃんとグラフが表示されましたね。

Berkeley Pascal System のエラー診断機構

いかがでしょうか?今日の ANSI C に慣れた皆さんには あまりピンとこないかも知れませんが、当時はこのような 丁寧なエラー診断機能を持つコンパイラは少なかったのです。

そもそも Pascal は大学でのプログラミング教育用に設計された言語でしたので、 上記のようなケアレスミスを実行以前に検出するため、 厳格な構文チェックができるように言語仕様は配慮されて設計されていました。 Berkeley Pascal System ではこの Pascal の言語としての特性に活用し、 ユーザーに親切なエラー診断機構が実装されていました。 Version 7 Unix に搭載されている PCC あるいは Ritchie C と比較すると 良く分かるのですが、非常に寡黙な C コンパイラに対して、 Berkeley Pascal System は大変饒舌な pi コンパイラを持っていました。 今日に比べあまり堅牢とは言えないシステムの上で、 初心者向けのプログラミング教育を行わなければ ならなかった当時の大学においてこのようなサポートは 大変重要な意味があったのです。初心者のケアレスミスが 原因でシステムがダウンするようなことがありましたからね。

当時、Unix ではユーザーが作成したプログラムは、 まず lint と呼ばれる別コマンドで構文チェックは行って、 その後 C コンパイラでコンパイルするという手順が望ましいと されていました。BTL の開発者たちは「構文チェッカとコンパイラが 独立していた方が、各々のプログラムサイズを小さくできるので合理的」と 主張していました。しかし、その後の ANSI C 言語の標準化作業では、 Pascal の例に習ってコンパイラによる構文チェックにも配慮した 言語仕様の策定がなされました。

今日、読者の皆さんの中で lint というコマンドをご存知の方は 非常に少ないのではないでしょうか?それがすなわち「Pascal もまた現在の C に影響を与えた」という動かぬ証拠ということに なるのでしょう。

プロファイリング

C のプロファイリングでは、 プログラムの実行時に関数を何回呼び出したか? また実行にどの程度の時間を要したか? を調べることができますが、 Berkeley Pascal System では、 pxp コマンドを使うことにより全てのステートメントを 何回実行したかを調べることができます。実行例12を御覧ください。

実行例12 素数の計算

# cd /usr/tmp
# cp /mnt/doc/pascal/primes.p .
# pi -z -l primes.p
Berkeley Pascal PI -- Version 1.2 (Wed Apr  7 14:32:10 2004)

Sun Apr 11 22:01 2004  primes.p

     1  program primes(output);
     2  const n = 50; n1 = 7; (*n1 = sqrt(n)*)
     3  var i,k,x,inc,lim,square,l: integer;
     4      prim: boolean;
     5      p,v: array[1..n1] of integer;
     6  begin
     7     write(2:6, 3:6); l := 2;
     8     x := 1; inc := 4; lim := 1; square := 9;
     9     for i := 3 to n do
    10     begin (*find next prime*)
    11        repeat x := x + inc; inc := 6-inc;
    12           if square <= x then
    13              begin lim := lim+1;
    14                 v[lim] := square; square := sqr(p[lim+1])
    15              end ;
    16           k := 2; prim := true;
    17           while prim and (k<lim) do
    18           begin k := k+1;
    19              if v[k] < x then v[k] := v[k] + 2*p[k];
    20              prim := x <> v[k]
    21           end
    22        until prim;
    23        if i <= n1 then p[i] := x;
    24        write(x:6); l := l+1;
    25        if l = 10 then
    26           begin writeln; l := 0
    27           end
    28     end ;
    29     writeln;
    30  end .
# ls
obj
primes.p
# px
     2     3     5     7    11    13    17    19    23    29
    31    37    41    43    47    53    59    61    67    71
    73    79    83    89    97   101   103   107   109   113
   127   131   137   139   149   151   157   163   167   173
   179   181   191   193   197   199   211   223   227   229

1404 statements executed in 0.06 seconds cpu time
# ls
obj
pmon.out
primes.p
# pxp -z primes.p
Berkeley Pascal PXP -- Version 1.1 (28 april 1979 5:30 pm)

Mon May 23 05:29 2011  primes.p

Profiled Sun Apr 11 22:53 2004

     1        1.---|program primes(output);
     2             |const
     2             |    n = 50;
     2             |    n1 = 7; (*n1 = sqrt(n)*)
     3             |var
     3             |    i, k, x, inc, lim, square, l: integer;
     4             |    prim: boolean;
     5             |    p, v: array [1..n1] of integer;
     6             |begin
     7             |    write(2: 6, 3: 6);
     7             |    l := 2;
     8             |    x := 1;
     8             |    inc := 4;
     8             |    lim := 1;
     8             |    square := 9;
     9             |    for i := 3 to n do begin (*find next prime*)
     9           48.---|    repeat
    11               76.---|    x := x + inc;
    11                     |    inc := 6 - inc;
    12                     |    if square <= x then begin
    13                    5.---|    lim := lim + 1;
    14                         |    v[lim] := square;
    14                         |    square := sqr(p[lim + 1])
    14                     |    end;
    16                     |    k := 2;
    16                     |    prim := true;
    17                     |    while prim and (k < lim) do begin
    18                  157.---|    k := k + 1;
    19                         |    if v[k] < x then
    19                       42.---|    v[k] := v[k] + 2 * p[k];
    20                         |    prim := x <> v[k]
    20                     |    end
    20                     |until prim;
    23                 |    if i <= n1 then
    23                5.---|    p[i] := x;
    24                 |    write(x: 6);
    24                 |    l := l + 1;
    25                 |    if l = 10 then begin
    26                5.---|    writeln;
    26                     |    l := 0
    26                 |    end
    26             |    end;
    29             |    writeln
    29             |end.
#

このサンプルコードは素数を50個計算して表示するプログラムです。 プログラム内部では3重のループが定義されており、 ループのレベルが深くなるほどステートメントの実行回数が増えます。

このサンプルコードに対してプロファイリングを行います。 まず pi コマンドでコンパイルする際 -z オプションを付けます。 このオプションを付けると、px 実行時にプロファイル情報を収めた ファイル pmon.out を作成してくれます。

プロファイル情報を表示するためには、実行例12のように -z オプションを指定して pxp コマンドを実行します。 9行目や18行目のソースコード中の数字が呼び出し回数を表します。 このサンプルコードでは変数 k は 18 行目で 157 回インクリメントされていますし、 24 行目のプロシージャ write は 48 回呼び出されています。

このようなステートメント単位でのプロファイルが可能なのも、 Berkeley Pascal System が Pascal のコンパイラ/インタープリタ方式を 採用しているからです。あまり大きくないプログラムであれば、 pxp のステートメント単位でのプロファイル機能はプログラム内の クリティカルパスを正確に把握するための良い方法でしょう。

3回に渡って 2BSD に収録されていた著名な成果である csh ex/vi pascal について御紹介しましたが、いかがだったでしょうか? 特に pascal についてはその実装の側面でも数多くの興味深い特徴があります。 が、本連載の主旨から大きく外れてしまうため割愛することにしました。 機会があれば別の形で御紹介したいと思います。

これまで御紹介してきた 2BSD の成果によって、UCB は 1970 年代末には Unix コミュニティーの中でも実力のあるグループとして認知されていました。 Bill Joy などの学生が主導するこのグループは、その若さを武器に 2BSD 以降も 自分たちの守備範囲をどんどん拡大していくことになります。Unix カーネルを 手掛けるようになったことも、また VAX-11 という新しい より大きなミニコンに取り組むようになったことも、 当初はそれまでの活動の延長上に位置するもので、 特別な経済的な野心はなかったと思われます。が、のちに結果として その活動がコンピュータに関わる業界に大きなインパクトを与えることとなります。 次回は DEC の VAX-11 というミニコンを軸に、 UCB に置ける Unix の 32 Bit 化の話を御紹介したいと思います。


★コラム1 Version 6 Unix vs Version 7 Unix

コンパイル手順を説明するのに都合が良いのは「Version 7 Unix に対応した Pascal」ということになりますが、最初に思いつくのは 3BSD に収録された Berkeley Pascal System です。Version 7 Unix を単純移植した UNIX/32V を ベースに開発された 3BSD の上で動く Pascal は当然 Version 7 Unix に 対応されているはずですからね。が、これも御紹介するには大いに問題が ありました。というのも px のソースは大部分がアセンブラで書かれています。 そう VAX のアセンブラです。どういう理由でかは定かではないのですが、 インタープリタ・コアのエンジン部分さえアセンブラで書かれています。 ひょっとしたら Ken Thompson が書いたとされる最初の Pascal System は 全てアセンブラで記述されていたのかも? いずれにせよ、VAX のアセンブラ コードを PDP-11 のアセンブラに書き直すのは、今時やるにしてはあまりに 不毛な作業ですよね?ということで却下。