2018年4月8日日曜日

PDP8のプログラム技法

前回のブログで基本ループの動くのは分ったが, calのプログラムを書くにはまだmon0, mon1のデータを用意する仕事がある. そのプログラムの話だ.

mon0は各月の1日の曜日が決ったとき, その週の最左端の枠, つまり日曜の日付けである. 1日の曜日の計算は月の定数にその年の定数と(日付けの)1を足す.

その年の定数は, 年の下2桁をyとして, (y+floor(y/4)+世紀の定数)%7である. 世紀の定数は, 年の上2桁を4で除した剰余の0,1,2,3について, それぞれ+4, +2, 0, -2である.

この辺までを計算するのが次のプログラムの000行から034行である.



000行はプログラムの200番地からの格納の指定. 001行はアキュムレータとリンクのリセット. 002,3行で1文字(1数字)を読みaへ置く. 009行までで4文字(4数字)を読み, 1000,100,10,1の各桁がa,b,c,dへ入る. 次のgetchは改行を読み, eへ入れるがこれはそれを捨てアキュムレータをクリアする.
 
012行からが上のy+floor(y/4)の計算. 016行まででcを10倍し, dを足す. それをeへ格納し, 20行までで2桁右シフト. それにeを足して計算完了.

024行からは上2桁の4で除した剰余をとる. それにはa+a+bを3でマスクする. 一旦fに入れ(028行), 031行までで剰余を5倍し, 世紀内の定数eを足しさらに033行で5を足す.

0,1,2,3から7を法として4,2,0,-2を作るのに, 5倍して4を足した. つまり4,9,14,19は7を法として4,2,0,-2だからである. 4を足すのではなく5を足すのは, 1日の分を足すからである. これをyconstに置く(034行.)

035行から051行は閏年の判定である.



まずcとd(下2桁)を足し, 和が0(100の倍数)ならhへ. そうでないならc+c+dを持ってjへ. hではa+a+bを持ってjで合流. これらが4の倍数なら閏年である. 3でマスクし, 0でなければ048行で-1を置き, 閏年なら049へスキップしてきて1増やすから, 閏年なら1が出来, 平年なら-1が1増えるから0が出来る.

050行は上の結果をマイナスにし, 051行でそれをmleapへ入れる.
 
これまででyconstとmleapが出来たので, mon0とmon1の表の修正を開始する.



修正はまず月の定数のうち, 1月と2月から1を引くのと, 月の日数のうち, 2月を1増やすことである. 052でmon0のアドレスをeに入れ, 054で1月の値を取り出し, mleapを足してもとへ戻す. iszでアドレスを増やし, 2月についても同様にする.

061行からは2月の日数の調整である. mon1の表は負数になっていたから, 2月の値から1引くわけで, それが065行だ.

mon0はyconstを足した(072行)のが1日の曜日であった. これからその月の最初の枠の日付けの計算に移る. 067行のm14は十進では-12のことで, 12回ループの準備をする.

この計算はmon0の値にyconstを足し, 7の法をとり(073行,) その後, 各月の1日の値がnなら, -n+1で置き換える. -nの操作が074行. 1足すのが075行である. これを実行すると, 前回のブログにあったmon0とmon1の表が得られる.

次の081行から097行は定数で, m7770, m7774はand命令で使うマスクビットである.


 
085行からのmon1は各月の日数だが八進法なのでちょっと異様にも見える.

098行からは1文字読込み(getch)と7の剰余をとるサブルーチン(mod7)である.



getchでは西暦の年号を読むだけなので, 数字のコードの八進260を引く.
 
mod7は法をとる披除数をアキュムレータに置き, 上9ビットをa, 下3ビットをbとし, aが0でなくなるまでaを3ビット右シフトしてbに足す操作を繰り返す. aが0になったらbからを引き, 結果が正, つまり0ならそのまま, 負なら7を足して戻る.

これでcalのブログラムの主要な部分は大体完成した.

2018年4月1日日曜日

PDP8のプログラム技法

私がいろいろなプログラム言語で書いたcalのプログラムは, unixのcalのコマンドで印刷されるのと同じものだ.

例えば

cal 2000

とすると, 2000年の12ヶ月のカレンダーが印字される.


こういうプログラムをpdp-8用に作るための工夫を書いてみたい.

かなり前のことだが, アメリカにJerry Weinbergという心理学者が活躍していて, 私も何回か会ったことがある. 当時プログラムを書くのにトップダウンがいいかボトムアップがいいかという無益な議論があった. その時Jerryが「難しいところから書く」を主張していて, 私と同じ考えの人がいると思った.

というわけで, 一番中心部分のプログラミングから始めよう. 見出しの類いはあとからゆっくり追加すればよい.

まずは各月の第1日の曜日と各月の長さが必要である.

このブログでも何度か述べたように, 1月から12月について次の表が重要になる. (ある月の定数は前の月の定数に前の月の日数を足し, 7による法をとったもの(と等価なもの)だ.)

 
 
1234567819101112
2558361417250


この表とその年の定数から各月の0日の曜日が判る. 2000年の定数は4なので, 例えば2000年8月15日は(4+4+15)mod 7=2で火曜だ. 注意すべきは2000年が閏年だったので, 1月と2月はこの表から1引くことである. 従ってこの表は補正後は, 1,4,5,8,3,... のように始まる.

それに年の定数を足して7の法をとると, 5,1,2,5,0,... になる. すると1月1日は4+1+1=6で土曜である. 1日が土曜だと, 金曜が0, 木曜が-1, 水曜が-2, 火曜が-3, 月曜が-4, 日曜が-5に相当し, 1月の第1週は-5日から始まる. そこから1日ずつ増えて, 1になるのが土曜である. 従って次の図のmon0と書いた表の最上段(1月)に7773, つまり10000-5を入れる. mon0はこのように出来ている. 年の定数が変るとか, 年の定数が同じでも閏年であったりするから, この表の内容は年により違う.





右側にあるmon1の表は, 上から順に各月の日数の負の値である. 日付が順に増えてゆき, 日数を過ぎると表示を空白にしなければならず, それには日数を減算するが, PDP-8には減算がないので, 最初から負数にしてある.

最初のカレンダーの構造からわかるように, このプログラムは大局的には3ヶ月のカレンダーの4回の繰返しである. その1回分は, 1ヶ月の3回の繰返し; 1ヶ月は週の6回の繰返し, 週は日の7回の繰返しなので, 以下のプログラムは4重のループで出来ている.



00行はこのプログラムが八進の200番地から格納することを示す. 02行からmon0pの値をaへ, mon1pの値をbへ置く. 06行から4,6,3,7のカウンタを初期化する. 15行 aの指す内容(今は1月の先頭の日付け)の1未満を調べるため, アキュムレータに-1を置きmon0の値いを足す. それが負なら空白を出力するためl1へ. (0も-1に  なるので, 飛ぶ) 次にこの月の日数を引き(18行), 正になればこれも空白出力へ進む.

日付を出力できる条件を満すと, 21行からそれを出力し26行へ行き日付を進める. 日付の値は負から始まり, 正へ進むが, isz命令は0になった場合, 次の命令をスキップするから, isz命令の次にjmp .+1かnop(7000)を置く.

この後1週間分, つまり7回出力したかを見, そうなら次の月の1週目に進むから, aとbのポインタを進める(31,32行目.) 33行目はそれを3ヶ月やったかの確認で, 3ヶ月済むとまた左端の月に戻るので, 35行目からaとbを3ずつ減らす. これで1行出力したから, 改行する(41行目.)

42行めは6週間のテスト. 市販のカレンダーでは1日が金曜か土曜だと, 30日や31日は23日や24日と一枠を共有するが, calではそうせず, 次の行にするので, 1ヶ月は最大6行になる. その6行が終ったかを調べる.

それが終わると続く3ヶ月に移るので, 44行目からは, aとbを3増やす段取りだ.

 

53行目からは定数や作業場所, 基本的なルーチン類になる. 54行, 57行が上の図のmon0とmon1である. 59行からはカウンタの初期値, 64行からのa, b, c, d, e, fが作業場所になる.

このプログラムの出力が以下だ. 横7文字目, 14文字目の次に縦線を引き, 6行, 12行, 18行の下に横線を引くと, 最初のカレンダーと同じ情報が得られる. 1日がA, 2日がB, 26日がZだから, アルファベット各文字が何番目かを熟知しているひとには, カレンダーとして十分役立つと思われる(^^).

@@@@@@A@@ABCDE@@@ABCD
BCDEFGHFGHIJKLEFGHIJK
IJKLMNOMNOPQRSLMNOPQR
PQRSTUVTUVWXYZSTUVWXY
WXYZ[\][\]@@@@Z[\]^_@
^_@@@@@@@@@@@@@@@@@@@
@@@@@@A@ABCDEF@@@@ABC
BCDEFGHGHIJKLMDEFGHIJ
IJKLMNONOPQRSTKLMNOPQ
PQRSTUVUVWXYZ[RSTUVWX
WXYZ[\]\]^_@@@YZ[\]^@
^@@@@@@@@@@@@@@@@@@@@
@@@@@@A@@ABCDE@@@@@AB
BCDEFGHFGHIJKLCDEFGHI
IJKLMNOMNOPQRSJKLMNOP
PQRSTUVTUVWXYZQRSTUVW
WXYZ[\][\]^_@@XYZ[\]^
^_@@@@@@@@@@@@@@@@@@@
ABCDEFG@@@ABCD@@@@@AB
HIJKLMNEFGHIJKCDEFGHI
OPQRSTULMNOPQRJKLMNOP
VWXYZ[\STUVWXYQRSTUVW
]^_@@@@Z[\]^@@XYZ[\]^
@@@@@@@@@@@@@@_@@@@@@