2012.5.23
前へ
次へ
ホームページトップへ戻る

復活!CP/M ワンボードマイコンでCP/Mを!
CP/MがTK−80互換のワンボードマイコンの上で復活します
ND80ZVとMYCPU80の上でCP/Mが走ります

[第128回]


●ファンクションコール0A(コンソールバッファ入力)

CP/M2.2で[BS]の機能が正しく働くようになりましたので、あらためてその動作を確認してみましたところ、同じコード08Hでもキー入力コードと画面表示コードとでは、その動作が異なっていることがわかりました、というところまでが前回のお話でした。

CP/Mでキー入力というのはコンソール入力になります。
BIOSのコンソール入力ルーチンは、ただキー入力コードを取得するだけですが、ファンクションコール01のコンソール入力は、キー入力コードを取得すると同時に、それをエコー表示するため、コンソール出力も行ないます。
このときに行なうエコーとしてのコンソール出力では、08Hは[BS]の動作をします。
しかしファンクションコール02のコンソール出力では、08Hは[←]の動作をします。
そこのところを使い分けるように、CP/M互換DOSのプログラムを直さなければなりません。
なかなかに面倒なことです。

面倒ではありますが、そんな程度のことでめげてしまうような私ではありません。
なんとか直してしまいましたよ。

さてそこで。
そのように直したCP/M互換DOSプログラムが、CP/M2.2と同じ動作をするかどうかを検証していたのでありますが。

まてよ。
コンソール入力は、他にもあったのでは?
おお、そうでした。
ファンクションコール0A(コンソールバッファ入力)もコンソール入力です。
こちらのほうでは[BS]はどのように働くのでありましょうか?
そこに気が付いて、確認してみましたところ。
そこにはなんと。
まさかの複雑な仕組みがあったのでありました。

いえ。
「まさか」といいますのは、まあ、言葉のあやでありまして、ファンクションコール0Aにつきましては、それが複雑な仕組みらしいということは、おおよそはわかっておりました。
しかし、そんな面倒な仕組みは要らんだろうよ、ということで無視してしまうつもりだったのですよねえ。
はじめは。

ですけれど。
そこはそれ、持ち前の性格といいますか。
考えておりますうちに、そういう複雑なものだからこそ、なおさら自分の手で作ってしまいたい、という悪魔の誘惑につい乗ってしまうのでありますね。
かくして、いつも泥沼でもがくことになってしまいます。

よくよく落ち着いて考えてみましたら、そもそも[BS]の主たる活躍場所はファンクションコール01(コンソール入力)やファンクションコール02(コンソール出力)ではなくて、このファンクションコール0A(コンソールバッファ入力)であるように思えてきました。

[BS]はどういう時に使われるかといいますと、それはもちろんキー入力で誤入力をしてしまった場合にそれを訂正する時、ということになりますでしょう。
ではキー入力をする時、というのはどういう時かといいますと、ただ1文字だけの入力、という場合もありますけれど、ほとんどはある文字数を入力して、最後に[Enter]キーを押して入力を確定する、という使い方をしている時である、と言えますでしょう。

つまりそういうプログラムを書く時は、ふつうはファンクションコール01(コンソールから1文字を入力)ではなくて、ファンクションコール0A(コンソールバッファ入力)を使うことになると思います。

すると、その場合の[BS]キーの入力は、画面にエコー表示された文字を1文字消去すると同時に、コンソール入力バッファに入力されている文字コードも消去する、という動作になります。

勿論、そこまでは想定内だったのですよね。
ところが。
さらに詳細を調べていきましたら、コンソールバッファ入力の想像を超える複雑な仕組みが明らかになってきました。

あ。ここでひとつご注意を。
以下の説明は、あくまでCP/Mのファンクションコール0Aにおける動作についての説明です。
一般的なキー入力時でもそのような動作をする、ということではありません。
以上。念の為。

さて。
コンソールバッファ入力の複雑な仕組みのひとつは、[Ctrl]キー入力時のエコー表示に関わるものです。
ここで[Ctrl]キー入力といいますのは、[Ctrl]キーと同時にアルファベットキーを押す、という入力のことです。

[Ctrl]+[H]はコード08Hで、これは[BS]キーを入力したのと同じ動作になります。
また[Ctrl]+[J]は0AH、[Ctrl]+[M]は0DHで、それぞれLF、CRコードの入力と同じ動作になります。

しかしたとえば[Ctrl]+[K]は0BHですが、CP/Mではこのコードには特別の機能は与えられていません。
そのような[Ctrl]コードの入力があった場合には、エコーとして、 ^K のように ^ に続けてアルファベットを表示します。
しかしコンソールバッファには0BHの1文字コードが入力されるだけです。
するとエコー表示される画面上のカーソルアドレスとコンソール入力バッファのポインタには1文字分のずれが生じます。
何を言っているか、おわかりになりますでしょうか。
これは面倒なことなのですよ。

たとえば[A]、[B]、[C]、[Ctrl]+[K]、[D]と入力したとしますと、画面には、
ABC^KD
と表示されますが、コンソールバッファには
41・42・43・0B・44のように入力されます。

それではここで[BS]を2回入力するとどうなるでしょうか?
コンソールバッファは2文字消されますから、
41・42・43
の3文字が入力されていることになります。
このとき表示画面はどうなるかと言いますと、
ABC^
ではなくて、この場合には^も含めて3文字が消去され、
ABC
になります。

確かにつじつまが合いますけれど。
[Ctrl]入力をするたびに画面のカーソル位置カウンタとコンソールバッファの入力文字カウンタの値はどんどん相違していきます。
でも[BS]はそこのところをちゃんと間違い無く処理しなくてはなりません。
どうやっているのでしょうかねえ。

でも、こんなもんじゃないのです。
もっとすごいのはTABの扱いです。
TABのキーコードは09Hですが、このコードは画面表示コードとしては働きません。
それがTABとして働くのは、システム(OS)の働きによるのです。

CP/Mでは0、8、16、24、…というように8字ごとに固定したTABが設定されています(先頭が0カラムです)。
この数を16進数で示しますと、00H、08H、10H、18H、20H…になります。

今キーボードから、
[1][2][3][TAB][4][TAB][5] ……@
と入力したとします。
このときコンソールバッファには、
31・32・33・09・34・09・35 ……A
というようにコードが入ります。

では、このときコンソール出力には、エコーとしてどのようなコードが出力されるかといいますと、
31・32・33・20・20・20・20・20・34・20・20・20・20・20・20・20・35 ……B
というようにコードが送られます。
その結果、画面には
123□□□□□4□□□□□□□5_ ……C
と表示されます(□は1文字分の空白です。また_はカーソルの表示です)。

なんと、[TAB]入力のたびに、現在のカーソル位置から次のTAB位置までの文字数を計算して、そこまでに必要な空白の個数を算出して、コンソール出力を行なっているのです。
しかも、それはコンソールバッファの現在の入力文字カウンタをもとに計算されるのではなくて、あくまで画面表示の現在のカーソルポイントをもとに計算されているのです。
これがTABの仕組みです。

その仕組みがどれほど複雑であるか、想像できますでしょうか?

たとえばさきほどの@〜Cの例は、説明を簡単にするために、入力開始時にカーソルポイントが画面の左端にあるものと仮定して説明をしています。
でもいつもそうであるとは限りません。

もしも、
IN:_
と表示されたあとにファンクションコール0Aがコールされたとすると、そのときに@と同じ入力を行なった場合、A〜Cはどのように変化するでしょうか?

この場合でもAは同じです。
つまりコンソールバッファへの入力は画面のカーソル位置(カーソルポインタの値)からは独立しています。
それでありながら、TAB入力に対するエコーはカーソル位置に依存しているのです。

ですからさきほどはBだったものが、
31・32・33・20・20・34・20・20・20・20・20・20・20・35 ……B’
になります。
このようにカーソル位置から、必要な空白の数を計算しているのです。
その結果、画面には、
IN:123□□4□□□□□□□5_ ……C’
と表示されます。

このC’をさきほどのCと重ねて表示してみますと、

123□□□□□4□□□□□□□5_ ……C
IN:123□□4□□□□□□□5_ ……C’

というように、つねにTABが正しく反映された表示になります。

では、ここで[BS]を2回入力するとどうなりますでしょうか。
コンソールバッファは、35と09が削除されて
31・32・33・09・34
になります。
そして、そのときの表示画面は。

そうです。
このようになるのです。

123□□□□□4_ ……D
IN:123□□4_ ……D’

これはどえらいことです。
めちゃめちゃ面倒なプログラムを書かなければなりません。
最初に私がCP/M2.2のファンクションコール0Aがこの動作をするということを知ったとき、「有り得ない」と思ってしまったほどでした。

これがめちゃめちゃ面倒なプログラムになる、ということがだいたい想像できた方はかなりマシン語プログラムに経験がお有りの方です。
余り経験豊富ではない方ですと、どこが面倒なのかということすらおわかりになりませんでしょう。

「どこが面倒なのかわからん」と思われた方は、試しにここで説明をしておりますファンクションコール0Aの動作をするプログラムを実際にご自身で書いてみられるとよろしいと思います。
ええ。
プログラムの勉強です。
こう言ってしまうとなになのですけれど、おそらくほとんどの方はどこからどのように書き始めたらよいのか、というあたりで止まってしまって、そこから先に進めないのではないかと思います。

さて、お話をもとに戻しまして。
どこが「有り得ない」のかと言いますと。
[TAB]を入力したときは、現在のカーソルポイントから次のTABポイントまでの距離を計算することができます。
ですからそのようにして求めた値に従って空白を必要なだけコンソール出力することは可能です。

しかし、逆に現在のタブを削除するときはどうでしょうか?
コンソールバッファはこうなっていました。
31・32・33・09・34・09・35 ……A
ここで35と09(BS)を削除すると、

123□□□□□4□□□□□□□5_ ……C
IN:123□□4□□□□□□□5_ ……C’

だったものが、下のようになるのですが。

123□□□□□4_ ……D
IN:123□□4_ ……D’
このとき画面に置かれていた空白の個数はどのようにしたら算出できるのでしょうか?
ま。しかし、たまたまこの場合にはCもC’も間の空白は7個です。
でも、そのもうひとつ前のTABを削除するときはどうでしょうか?
Dでは5個の空白を削除するのに対してD’では2個の空白を削除します。
どちらもバッファの中味はAで変わりません。
CP/M2.2はいったいこの空白をどのようにして算出しているのでしょうか?
もちろん表示画面からその情報を得るような仕組みはCP/Mにはありません。
CONOUT(画面出力)とCONIN(キー入力)はありますが、画面入力などという機能は無いからです。
私が最初「有り得ない」と思ってしまったのは、そういうことからでした。

もちろん実際に試してみますと、ちゃんとそのように働いてくれるのですから、何か方法があることには間違いありません。

CP/Mの作者Gary Kildallはほんとうに賢い方だったのですねえ。
まあ、簡単にまねのできるようなプログラムではないと思います。

なんて言いながら、私はそのCP/Mの互換プログラムを書いているのでありますが。

ところがですね。
これで終わりではなかったのです。

コンソールバッファ入力には、ほかにもまだ特別の機能がありました。
[Ctrl]+[R]を入力すると、現在の表示位置に#を表示したうえで、改行してもう一度入力した文字列を再表示します。
[Ctrl]+[U]を入力すると、同じように#を表示したうえで、改行しますがバッファはクリアされて、もう一度最初から入力をうながします。
[Ctrl]+[X]は、改行しないでバッファをクリアしたうえで、最初に1桁目を入力したポイントにカーソルを戻して、そこからの再入力になります。

たとえば。
INPUT* と表示したあと、ファンクションコール0Aが呼ばれて、そこでABCと入力すると、画面の表示 は
INPUT*ABC_
になります。
そこで[Ctrl]+[R]、[Ctrl]+[U]、[Ctrl]+[X]をそれぞれ入力したとしますと、

[Ctrl]+[R]は、
INPUT*ABC#
      ABC_

[Ctrl]+[U]は、
INPUT*ABC#
      _

[Ctrl]+[X]は
INPUT*_

のようにそれぞれ表示されます。
そして、 いずれの場合においても、つねに[BS]も[TAB]も有効に働きます。

以上、今回書きましたことの全てが、ファンクションコール0Aの動作なのです。
本当にすごいプログラムです。

まだ説明の途中なのですが(ええ。まだ終わらないのです!)、ちょっと説明が長くなってしまいましたので、今回はここまでといたします。
この続きは次回にすることにいたします。

ワンボードマイコンでCP/Mを![第128回]
2012.5.23upload

前へ
次へ
ホームページトップへ戻る