0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Turbo Pascal 3.0 の Pascal

Last updated at Posted at 2021-01-24

はじめに

Turbo Pascal 3.0 の Pascal について調べてみました。

image.png

Turbo Pascal は Pascal

何を当たり前の事を言っているのかとお思いでしょうが、Delphi に比べると遥かに Pascal です。しかしながら標準 Pascal とも異なっています。

Turbo Pascal の文法に関してはマニュアル等の書籍があればそれが一番いいのですが、標準 Pascal や Delphi のドキュメントからもある程度知る事ができます。

See also:

■ 標準 Pascal と Turbo Pascal の違い

標準 Pascal 範囲内での差異は次の通りです。

○ New() と Dispose() の二番目の書式

Turbo Pascal は New() と Dispose() の二番目の書式 に対応していません。

See also:

○ 再帰 (CP/M-80 版)

「ルーチン内のローカル変数を再帰呼び出しの var パラメータに渡してはならない」という制限があります。そもそもそんな事やらないと思うのですが...。

RECUR.PAS
program RECUR(Input, Output);
{$A-}
  function Summation(var num: Integer): Integer;
  var
    v: Integer;
  begin
    if num = 1  then
      Summation := 1
    else
      begin
        v := num;
        num := num - 1;
        Summation := Summation(num) + v;
      end;
  end; { Summation }

  function SubProgram: Integer;
  var
    v: Integer;
  begin
    v := 3;
    SubProgram := Summation(v);
  end; { SubProg }

begin
  Writeln(SubProgram);
end.

このプログラムは FreePascal Wiki にある再帰のサンプルを改変したものです。標準 Pascal で実行してみると、確かに 6 が返ります。

image.png

これを CP/M-80 版の Turbo Pascal で実行してみると...

image.png

5 が返ってきます。

image.png

PC DOS 版では 6 が返ります。

○ Get() と Put()

Turbo Pascal には Get()Put()、そしてバッファ変数が使えません。これはファイルの 1 文字先読み機能がない事を意味します。

See also:

○ GOTO ステートメント

Turbo Pascal の goto ステートメントは手続き内 goto (intraprocedural gotos) であり、手続き/関数の外側へジャンプする事はできません。

See also:

○ Page()

Page() 手続きは実装されていません。

See also:

○ パックされた変数

PACKED は文法上エラーになりませんが、Turbo Pascal では意味を持ちません。このため、Pack()Unpack() は実装されていません。

See also:

○ 手続きパラメータ / 関数パラメータ

手続きや関数をパラメータとして渡す事はできません。

See also:

■ Turbo Pascal と 標準 Pascal の違い

標準 Pascal から Turbo Pascal 3.0 までの主な機能拡張を次に挙げます。

  • 絶対アドレス変数 (absolute)
  • ビット/バイト操作 (シフト含む: shl, shr)
  • メモリ、データポートに対する直接アクセス (mem[], port[])
  • 動的文字列 (Pascal 文字列: string)
  • 宣言部における宣言順序の緩和
  • OS 機能のサポート (Bdos(), Bios())
  • インライン形式での機械語コードの埋め込み (inline)
  • インクルードファイル ({$I ファイル})
  • オーバーレイ (overlay)
  • 整数型変数に対する論理型操作 (ビット論理演算)
  • コモン変数を伴ったプログラムチェイニング (Execute(), Chain())
  • 直接アクセスデータファイル (型なしファイル)
  • ファイル名とファイル変数の関連付け (Assign())
  • 構造化定数 (型付き定数)
  • 型変換関数(Integer(), Char()...)
  • 16進プレフィクス ($xx)
  • 制御文字 (#xx, #$xx)
  • ルーチンやプログラムを抜ける(exit, Halt())。

■ ルーチン

Turbo Pascal で使えるルーチンです。標準 Pascal で使えるルーチンも含まれています。Delphi で使えるルーチンもあるので、ある程度の使い方は Embarcadero の DocWiki を参照して知る事ができます。

パラメータの頭に var が付いているものは 変数パラメータ で、変数を渡す必要があります。定数を渡す事はできません。

Turbo Pascal 3.0 には、4 種類あるのですが、OS や CPU に固有のルーチンが存在します。固有のルーチンにはそれと判るようなコメントを入れておきます。

Z80 Intel 8086
CP/M CP/M-80 CP/M-86
PC DOS (IBM PC) (N/A) PC DOS
MS-DOS (非 IBM PC) (N/A) MS-DOS

次の呼称は特定の製品の組み合わせを指します。

呼称 説明
CP/M 版 CP/M-80 および CP/M-86
DOS 版 PC DOS および MS-DOS
8086 版 CP/M-86, PC DOS および MS-DOS

・標準 Pascal に存在しないルーチンは軽い説明を添える事にします。
・グラフィックやタートルグラフィックス等の固有ルーチンは紹介しません。

See also:

○ 入出力ルーチン

手続き

Procedures
Read(var F: file of type; var V: type);
Read(var F: text; var I: Integer);
Read(var F: text; var R: Real);
Read(var F: text; var C: Char);
Read(var F: text; var S: string);
Readln(var F: text);
Write(var F: file of type; var V: type);
Write(var F: text; I: Integer);
Write(var F: text; R: Real);
Write(var F: text; B: Boolean);
Write(var F: text; C: Char);
Write(var F: text; S: string);
Writeln(var F: text);

※ file of type = ファイル型。「file of 型」で定義されたユーザー定義の型です。

See also:

○ 算術ルーチン

関数

Functions
Abs(I: Integer): Integer;
Abs(R: Real): Real;
ArcTan(R: Real): Real;
Cos(R: Real): Real;
Exp(R: Real): Real;
Frac(R: Real: Real;
Int(R: Real): Real;
Ln(R: Real): Real;
Sin(R: Real): Real;
Sqr(I: Integer): Integer;
Sqr(R: Real): Real;
Sqrt(R: Real): Real;
関数 説明
Frac(R) 実数の小数部分を返します。
Int(R) 実数の整数部分を返します。

○ 順序ルーチン

関数

Functions
Odd(I: Integer): Boolean;
Pred(X: scalar): scalar;
Succ(X: scalar): scalar;

※ scalar = ここで言うスカラ (型) とは順序型の事です。

See also:

○ 変換ルーチン

関数

Functions
Chr(I: Integer): Char;
Ord(X: scalar): Integer;
Round(R: Real): Integer;
Trunc(R: Real): Integer;

○ 文字列ルーチン

手続き

Procedures
Delete(var S: string; Pos, Len: Integer);
Insert(S: string; var D: string; Pos: Integer);
Str(I: Integer; var S: string);
Str(R: Real; var S: string);
Val(S: string; var R: Real; var P: Integer);
Val(S: string; var I, P: Integer);
手続き 説明
Delete(S, Idx, Cnt) S の Idx 文字目から Cnt バイトを削除します。
Insert(S, D, Idx) D の Idx 文字目に文字列 S を挿入します。
Str(Writeパラメータ, S) 数値 -> 文字列変換を行います。整数または実数を Write パラメータによって変換した文字列を S に格納します。
Val(S, V, C) 文字列 -> 数値変換を行います。文字列 S を数値に変換した数値を V に返し、変換の可否を C に返します。変換に成功すると C には 0 が、失敗すると 0 以外が格納されます。

関数

Functions
Concat(S1, S2, ... , Sn: string): string;
Copy(S: string; Pos, Len: Integer): string;
Length(S: string): Integer;
Pos(Pattern, Source: string): Integer;
関数 説明
Concat(S1, S2..., Sn) カンマで区切られた文字列を結合して返します。他の Pascal 処理系との互換性のために用意されています。
Copy(S, Idx, Cnt) S の Idx 文字目から Cnt バイトを返します。
Length(S) S に格納されている文字列の長さを返します。
Pos(Sub, S) 部分文字列 Sub が S に含まれている位置を返します。文字列の最初の位置は 1 で、部分文字列が見つからなかった場合には 0 を返します。

○ ファイル操作ルーチン

手続き

Procedures
Append(var F: file);
Assign(var F: file; Name: string);
BlockRead(var F: file; var Dest: Type; Num: Integer);
BlockWrite(var F: file; var Dest: Type; Num: Integer);
Chain(var F: file);
Close(var F: file);
Erase(var F: file);
Execute(var F: file);
Flush(var F: file);
LongSeek(var F: file of type; Pos: Real); { for DOS }
OvrDrive(d: integer); { for CP/M }
OvrPath(path: string); { for DOS }
Rename(var F: file; Name: string);
Reset(var F: file);
Rewrite(var F: file);
Seek(var F: file of type; Pos: Integer);
Truncate(var F: file); { for DOS }
手続き 説明
Append(F) 現在のファイルをオープンし、ファイルポインタをファイルの最後に移動します。追記モードです。
Assign(F, FileName) FileName で指定されたファイルをファイル型の変数に割り当てます。Delphi では AssignFile() という名前です。
BlockRead(F, Buf, Num [, NumOfRead]) 型なしファイルの内容を Buf に Num レコード (128 バイト) 分読み込みます。省略可能な 4 番目のパラメータ NumOfRead は実際に読み込まれたレコード数です。
BlockWrite(F, Buf, Num [, NumOfWrite]) Buf の内容を Num レコード (128 バイト) 分型なしファイルへ書き出します。省略可能な 4 番目のパラメータ NumOfWrite は実際に書き込まれたレコード数です。
Chain(F) 拡張子が *.CHN の実行形式ファイルを実行します。詳細はこちら
Close(F) 現在のファイルをクローズします。Delphi では CloseFile() という名前です。
Erase(F) 現在のファイルを削除します。事前にファイルをオープンする必要はありません。
Flush(F) バッファの内容をディスクに書き込みます。DOS では意味を持ちません。
Execute(F) 拡張子が *.COM の実行形式ファイルを実行します。詳細はこちら
LongSeek(F, Num) 型なしファイルまたは型付きファイルのファイルポインタを 128 バイト単位で移動します。65535 レコードを超えるサイズのファイルに対しても使えます。(DOS 版)
OvrDrive(No) オーバレイファイルのあるドライブ (0=カレント, 1=A:, 2=B:...) を指定します。詳細はこちら。(CP/M 版)
OvrPath(path) オーバレイファイルのあるパスを指定します。詳細はこちら。(DOS 版)
Execute(F) 拡張子が *.COM の実行形式ファイルを実行します。詳細はこちら
Rename(F, FileName) ファイル名を変更します。
Seek(F, Num) 型なしファイルまたは型付きファイルのファイルポインタを 128 バイト単位で移動します。
Truncate(F) ファイルの現在位置より後ろを削除します。事前にファイルをオープンしておく必要があります。(DOS 版)

※ file of type = 型付きのファイル型。「file of 型」で定義されたユーザー定義の型です。

関数

Functions
Eof(var F: file): Boolean;
Eoln(var F: Text): Boolean;
FilePos(var F: file of type): Integer;
FilePos(var F: file): Integer;
FileSize(var F: file of type): Integer;
FileSize(var F: file): Integer;
LongFilePos(var F: file): Real; { for DOS }
LongFileSize(var F: file): Real; { for DOS }
SeekEof(var F: file): Boolean;
SeekEoln(var F: Text): Boolean;
関数 説明
FilePos(F) ファイル位置を 0 ベースで返します。
FileSize(F) ファイルサイズを返します。
LongFilePos(F) ファイル位置を 0 ベースで返します。32KB を超えるサイズのファイルでも使えます。(DOS 版)
LongFileSize(F) ファイルサイズを返します。32KB を超えるサイズのファイルでも使えます。(DOS 版)
SeekEof(F) EOF を判定します。ブランク文字をスキップします。
SeekEoln(F) EOL を判定します。ブランク文字をスキップします。

Turbo Pascal にはバイナリファイルを扱うための 型なしファイル型 があります。

型なしファイルまたは型付きファイルのファイル操作の単位は 128 バイトです。ファイルサイズは 128 の倍数になります。CP/M の場合、テキストファイルもファイルサイズは 128 の倍数になります (0x1A 以降は無視されます)。

See also:

○ ヒープ制御ルーチン

手続き

Procedures
Dispose(var P: pointer);
FreeMem(var P: pointer, I: Integer);
GetMem(var P: pointer, I: Integer);
Mark(var P: pointer);
New(var P: pointer);
Release(var P: pointer);
手続き 説明
FreeMem(P, I) Mark() でセットされたポインタが指すアドレスから I バイトの領域を解放します。
GetMem(P, I) Mark() でセットされたポインタが指すアドレスから I バイトの領域を確保します。
Mark(P) ポインタ変数 P にヒープ領域の最上位アドレスをセットします。
Release(P) ポインタ変数 P よりも上位にあるすべてのヒープメモリを解放します。

関数

Functions
MaxAvail: Integer;
MemAvail: Integer;
Ord(P: pointer): Integer; { for CP/M-80 }
Ptr(I: Integer): pointer; { for CP/M-80 }
手続き 説明
MaxAvail() ヒープ領域にある最大連続スペースを返します。
MemAvail() Mark & Release 方式で利用可能な空き容量を返します。
Ptr() 詳しくはこちら

○ 画面関連ルーチン

手続き

Procedures
CrtExit;
Crtlnit;
ClrEol;
ClrScr;
Delline;
GotoXY(X, Y: Integer);
HighVideo;
Insline;
LowVideo;
NormVideo;
手続き 説明
CrtExit() TINST で指定された、ディスプレイのリセットを行う制御文字を出力します。
Crtlnit() TINST で指定された、ディスプレイの初期化を行う制御文字を出力します。
ClrEol() TINST で指定された、カーソル位置から行末までを削除する制御文字を出力します。
ClrScr() TINST で指定された、画面のクリアを行う制御文字を出力します。
Delline() TINST で指定された、現在行を削除する制御文字を出力します。
GotoXY(x, y) TINST で指定された、カーソルを指定位置に移動する制御文字を出力します。
HighVideo() TINST で指定された、文字を高輝度に変更する制御文字を出力します。
Insline() TINST で指定された、現在行に空行を挿入する制御文字を出力します。
LowVideo() TINST で指定された、文字を低輝度に変更する制御文字を出力します。
NormVideo() TINST で指定された、文字を標準輝度に変更する制御文字を出力します。

関数

Functions
WhereX: Integer; { for PC DOS }
WhereY: Integer; { for PC DOS }
関数 説明
WhereX() 現在のカーソル位置 (X 座標) を返します。(PC DOS 版)
WhereY() 現在のカーソル位置 (Y 座標) を返します。(PC DOS 版)

See also:

○ その他ルーチン

手続き

Procedures
Bdos(Func, Param: Integer); { for CP/M-80 }
Bdos(Param: record); { for CP/M-86 }
Bios(Func, Param: Integer); { for CP/M-80 }
ChDir(Path: String); { for DOS }
Delay(mS: Integer);
FillChar(var Dest, Length: Integer; Data: Char);
FillChar(var Dest, Length: Integer; Data: Byte);
GetDir(Drive: Integer, var Path: String); { for DOS }
Halt;
Intr(IntNo: Integer; var Result: record); { for 8086 }
MkDir(Path: String); { for DOS }
Move(var Source, Dest: type; Length: Integer);
MsDos(var Param: record); { for DOS }
Randomize;
RmDir(Drv:integer; var Path: String); { for DOS }
手続き 説明
Bdos() 詳細はこちら。(CP/M 版)
Bios() 詳細はこちら。(CP/M 版)
ChDir(path) ディレクトリを変更します。(DOS 版)
Delay(mS) ms で指定されたミリ秒待ちます。TINST で指定した周波数 (MHz) に依存します。
FillChar(Dest, Len, Data) Dest 変数の先頭から Len バイトを Data で埋めます。
GetDir(Drive, Path) Drive (0=カレント, 1=A:, 2=B:...) のカレントディレクトリを文字列変数 Path に格納します。(DOS 版)
Halt() プログラムを停止します。
Intr() 詳細はこちら。(8086 版)
MkDir(Path) Path で指定したディレクトリを作成します。(DOS 版)
Move(Src, Dst, Len) 変数 Src の先頭から Len バイトを Dst にコピーします。
MsDos() 詳細はこちら。(DOS 版)
Randomize() 疑似乱数のシードを初期化します。
RmDir(Path) Path で指定したディレクトリを削除します。ディレクトリが空でない場合には削除に失敗します。(DOS 版)

関数

Functions
Addr(var Variable): Pointer; { for 8086 }
Addr(var Variable): Integer; { for CP/M-80 }
Addr(<function identifier>): Integer; { for CP/M-80 }
Addr(<procedure identifier>): Integer; { for CP/M-80 }
Bdos(Func, Param: Integer): Byte; { for CP/M-80 }
BdosHL(Func, Param: Integer): Integer; { for CP/M-80 }
Bios(Func, Param: Integer): Byte;
BiosHL(Func, Param: Integer): Integer;
Hi(I: Integer): Integer;
IOresult: Boolean;
KeyPressed: Boolean;
Lo(I: Integer): Integer;
ParamCount: Integer;
ParamStr(N: Integer): string;
Random(Range: Integer): Integer;
Random: Real;
SizeOf(var Variable): Integer;
SizeOf(<type identifier>): Integer;
Swap(I: Integer}: Integer;
UpCase(Ch: Char): Char;
関数 説明
Addr() 変数のアドレスを返します。詳細はこちら
Bdos() / BdosHL() 詳細はこちら
Bios() / BiosHL() 詳細はこちら
Hi(v) v の上位バイトを整数型で返します (= v shr 8)。
IOresult() I/O エラーが発生していれば、そのエラーコードを返します。
KeyPressed() 何かのキーが押されていれば True を返します。何も押されていなければ False を返します。
Lo(v) v の下位バイトを整数型で返します (= v and $00FF)。
ParamCount() プログラムに渡されたコマンドラインパラメータの数を返します。
ParamStr(n) プログラムに渡された n 番目のコマンドラインパラメータを返します。(0=自分自身, 1=最初のパラメータ, 2=2 番目のパラメータ...)
Random() パラメータを指定しない場合、0 以上 1 未満の疑似乱数を実数で返します。パラメータを指定した場合、0以上パラメータ未満の疑似乱数を整数値で返します。
SizeOf() 型のサイズをバイト数で返します。
Swap() 上位/下位バイトを入れ替えた値を返します。
UpCase(c) 文字を大文字にして返します。

See Also:

■ コンパイラ指令

{$ で始まるコメントのようなものはコンパイラ指令です。C 言語の #pragma に相当します。

以下に Turbo Pascal 3.0 で有効なコンパイラ指令を示します。太字のコンパイラ指令はデフォルトの動作です。

○ 共通

指令 指令 説明
I/O モードの選択 {$B+} または {$B-} 入力が CON: か TRM: か?
〔Ctrl〕+〔C〕と〔Ctrl〕+〔S〕の制御 {$C+} または {$C-} 入出力中に〔Ctrl〕+〔C〕や〔Ctrl〕+〔S〕を受け付けるか?
I/O エラーの制御 {$I+} または {$I-} I/O エラーチェックを行うか、行わずに自分で IOResult() で調べるか?
インクルードファイル {$I ファイル名} インクルードファイルを指定する。ファイル名は大文字とみなされる。拡張子を省略した場合は *.PAS とみなされる。
添字の範囲チェック {$R+} または {$R-} 配列の宣言した添字の範囲または順序型の範囲を超えて操作した時にエラーを出すか?
変数パラメーターの型チェック {$V+} または {$V-} 手続き・関数の var パラメータに渡された文字列型変数の長さをチェックするか?
ユーザー割り込みの制御 {$U+} または {$U-} すべての場所で〔Ctrl〕+〔C〕を受け付けるか、入出力時のみ〔Ctrl〕+〔C〕を受け付けるか?

○ CP/M-80 版

指令 指令 説明
絶対コード {$A+} または {$A-} 絶対コードの生成。再帰的呼び出し (リカーシブコール) が不可能なコードを生成するか、可能なコードを生成するか?再帰を含むコードでは {$A-} を指定しなくてはならない。C 言語の #pragma nonrec に相当。
With 文のネスト {$W ネスト数} With 文のネスト数を指定。デフォルトで 2
配列の最適化 {$X+} または {$X-} 配列のコード生成を最適化するか、コードサイズを最小化するか?

○ 8086 版

指令 指令 説明
スタックチェック {$K+} または {$K-} ローカル変数用のメモリ空間としてスタックが利用可能か調べるか?

○ DOS 版

指令 指令 説明
標準入力バッファの指定 {$G バッファサイズ} 標準入力に対するバッファサイズを指定する。バッファサイズが指定されると I/O リダイレクトが可能になる。0 を指定した場合、ノンバッファリングモードとなる。
標準出力バッファの指定 {$P バッファサイズ} 標準出力に対するバッファサイズを指定する。バッファサイズが指定されると I/O リダイレクトが可能になる。0 を指定した場合、ノンバッファリングモードとなる。
デバイスチェック {$D+} または {$D-} Reset(), Rewrite(), Append() によってオープンされたファイルがディスクファイルなのかデバイスファイルなのかを調べ、デバイスファイルならバッファリングを行わないようにするか?
ファイル数の指定 {$F ファイル数} 同時にオープンできるファイル数を指定する。CONFIG.SYS に書かれた FILES= を超えた数のファイルをオープンする事はできない。

■ 高度な使い方

○ システム変数

『Turbo Pascal 3.0』にはいくつかのシステム変数があります。

変数 説明 Z80 8086
mem[] メモリアクセス用バイト配列
memW[] メモリアクセス用ワード配列
port[] ポートアクセス用バイト配列
portW[] ポートアクセス用ワード配列
ErrorPtr エラーハンドラーポインタ
HeapPtr ヒープポインタ
RecurPtr 再帰スタックポインタ
StackPtr CPU スタックポインタ

○ 絶対変数

予約語 absolute 1 を使って、変数を特定のメモリアドレスに置くように指定する事ができます。

var
  sys_musicf: byte absolute $FB3F;

8086 版はアドレスの指定方法が異なります。

var
  abc: Integer absolute $0000:$00EE;

○ 絶対アドレス関数

関数名 説明
Addr(Name) Name で示される変数の第 1 バイトのアドレスを返す
Ofs(Name) Name で示される変数, 手続き, 関数の第 1 バイトが存在するセグメント内でのオフセットを返す (8086 版)
Seg(Name) Name で示される変数, 手続き, 関数の第 1 バイトを含むセグメントのアドレスを返す (8086 版)
Cseg コードセグメントのベースアドレスを返す (8086 版)
Dseg データセグメントのベースアドレスを返す (8086 版)
Sseg スタックセグメントのベースアドレスを返す (8086 版)

Addr() は CP/M-80 版の場合に整数型を返し、それ以外の場合にポインタ型を返します。また、CP/M-80 版はパラメータに手続きあるいは関数の識別子 (名前) を渡す事も可能です。

See Also:

○ ポインタ型への値の代入

・CP/M-80 版

Ptr() は整数値をポインタに変換できます。ポインタ型が示すアドレスは Ord() で整数値に変換できます。

var
  PB: ^Byte;
begin
  PB := Ptr($0100);
  Writeln(Ord(PB));
  Writeln('Memory: ', PB^);
end;

・8086 版

Ptr() は整数値のペア (セグメントアドレス、オフセットアドレス) をポインタに変換できます。ポインタが示すアドレスを逆に変換しようとすると 2 つの値が必要となるため、変換に Ord() を使う事はできません。

var
  PB: ^Byte;
begin
  PB := Ptr(Seg(PB^), $100);
  Writeln(Seg(PB^), ':', Ofs(PB^));
  Writeln('Memory: ', PB^);
end;

○ 配列 mem[]

配列 mem[] はメモリにアクセスするのに使われます。配列の要素は byte 型です。

value := mem[$FCA9];
mem[$FCA9] := value;

8086 版には、配列の要素が word 型の memW[] も用意されています。

value := memW[$0000:$0081];
memW[Seg(Var):Ofs(Var)] := value;

○ 配列 port[]

配列 port[] は I/O ポートにアクセスするのに使われます。配列の要素は byte 型です。

value := port[98];
port[98] := value;

8086 版には、配列の要素が Integer 型の portW[] も用意されています。

value := portW[10];
portW[10] := value;

○ エラーハンドラ

ErrorPtr システム変数を使って、ユーザー定義のエラーハンドラを記述する事ができます。

エラーハンドラは 2 つの Integer 型引数を持つ手続きです。手続き名やパラメータ名は何でも構いません。エラーハンドラのアドレスを ErrorPtr システム変数に割り当てると、エラー時にエラーハンドラが実行されます。

ERRTRAP.PAS
{$I-}{$U+}
program ERRTRAP;
  procedure Error(ErrNo, ErrAddr: Integer);
  begin
    case Hi(ErrNo) of
      0: Writeln('User Break (Ctrl-C).');
      1: Writeln('I/O error.');
      2: Writeln('Run-time error.');
    end;
    Writeln(' Error No: ', Lo(ErrNo));
    Halt;
  end; { Error }
begin
  ErrorPtr := Addr(Error); { 8bit }
(*
  ErrorPtr := Ofs(Error); { 16bit }
*)
  while True do
    ;
end.

エラーハンドラのアドレスは CP/M-80 版の場合 Addr() 関数、8086 版の場合、Ofs() 関数を使って割り当てます。

ErrNo の上位バイトにはエラーの種類が格納され、下位バイトにはエラー番号 (マニュアルの巻末にあります) が格納されます。ErrAddr はエラーの発生したアドレスです。

種類 意味
0 ユーザーブレーク (Ctrl-C)
1 I/O エラー
2 ランタイムエラー

通常はエラーハンドラの最後で Halt を実行してプログラムを終了します。

Halt がエラーハンドラ内で実行されずにエラーハンドラを抜けた場合、またはエラーハンドラ内でエラーが発生した場合には、Turbo Pascal 自身がエラーを出力し、プログラムを終了します。

○ インクルードファイル

インクルードファイル コンパイラ指令 ({$I}) を使うと、ファイルを指定した位置に挿入する事ができます。

lib.inc
  procedure Hello;
  begin
    Writeln('Hello,world.');
  end;
Prog1.pas
procedure Prog1;
{$I lib.inc}
begin
  Hello;  
end.

上記ソースは次のように展開されます。

Prog1.pas
procedure Prog1;
  procedure Hello;
  begin
    Writeln('Hello,world.');
  end;
begin
  Hello;  
end.

ソースファイルの分割が可能になるため、大きなプロジェクトをコンパイルできるようになります。

・Turbo Pascal 3.0 ではインクルードファイルのネストはできません。
・Turbo Pascal 3.0 は、使っていないルーチンも組み込んでしまうため、実行形式ファイルのサイズが肥大しがちです。不要なインクルードは避けるようにしてください。

See Also:

○ オーバーレイファイル

手続きや関数の先頭に overlay を付けると、実行ファイルの一部をオーバーレイファイルとして出力します。オーバーレイファイルは *.000 からの連番で拡張子が付きます。オーバーレイファイルにするのに必ずしもインクルードファイルにする必要はありません。

lib.inc
  overlay procedure Hello;
  begin
    Writeln('Hello,world.');
  end;
Prog1.pas
procedure Prog1;
{$I lib.inc}
begin
  Hello;  
end.

上記ソースをコンパイルすると、2 つのファイルが出力されます。

PROG1.COM   PROG1.000

実行ファイルを分割するため、メインメモリよりも大きなプログラムを実行できます。デメリットとしてはオンメモリで実行できない (実行形式ファイルを生成する必要がある) のと、呼び出しのオーバーヘッドが挙げられます。

オーバーレイファイルの場所を指定する ovrdrive() 手続き (CP/M 版) や ovrpath() 手続き (DOS 版) が用意されています。

See Also:

○ Execute

Execute() は他の実行形式ファイルを実行する手続です。

prog1.pas
program Prog1;
var
  a, b: Integer;
  fp: file;
begin
  a := 100;
  b := 200;
  Writeln(a, ':', b);

  Assign(fp, 'Prog2.com'); { COM }
  Execute(fp);
end.
prog2.pas
program Prog2;
var
  x, y: Integer;
begin
  Writeln(x, ':', y);
end.

オプションで COM ファイルを出力するようにして、Prog1 と Prog2 をそれぞれコンパイルして PROG1.COMPROG2.COM を生成してみます。PROG1.COM を実行するとどうなるでしょうか?

  1. Prog1 が実行される。
  2. a, b の値は 100, 200 にセットされる。
  3. 100:200 が表示される。
  4. Prog2 が実行される。
  5. x, y の値は不定か 0 になる?
結果
100:200
100:200

Execute() は現プログラムの位置にロードされるため、先頭から同じ型で宣言されたグローバル変数は値を引き継ぎます。

ロード位置を合わせるにはコンパイラのオプションでスタートアドレス (CP/M-80 版) またはコードセグメントとデータセグメント (8086 版) を合わせる必要があります。

           Memory
compile -> Com-file
           cHn-file
minimum cOde segment size:   0000 (max 0D80 paragraphs)
minimum Data segment size:   0000 (max 0FDB paragraphs)
mInimum free dynamic memory: 0400 paragraphs
mAximum free dynamic memory: A000 paragraphs

Find run-time error  Quit

>

○ Chain

Chain() は CHN 形式のファイルを実行する手続です。

prog1.pas
program Prog1;
var
  a, b: Integer;
  fp: file;
begin
  a := 100;
  b := 200;
  Writeln(a, ':', b);

  Assign(fp, 'Prog2.chn'); { CHN }
  Chain(fp);
end.
prog2.pas
program Prog2;
var
  x, y: Integer;
begin
  Writeln(x, ':', y);
end.

オプションで COM ファイルを出力するようにして、Prog1 をコンパイル、オプションで CHN ファイルを出力するようにして、Prog2 をコンパイルして PROG1.COMPROG2.CHN を生成してみます。PROG1.COM を実行するとどうなるでしょうか?

結果
100:200
100:200

結果は Execute() の時と同じなのですが、

PROG1   COM      8320 23-10-10 11:30a
PROG2   COM      8320 23-10-10 11:31a

ファイルサイズが異なります。

PROG1   COM      8320 23-10-10 11:40a
PROG2   CHN       128 23-10-10 11:41a

CHN ファイルは COM ファイル同様、現プログラムの位置にロードされますが、Turbo Pascal のライブラリコードを含んでいないため、ファイルサイズが小さくなります。単独で起動する必要がない場合には、CHN 形式にしておくとディスクの節約になります。

ロード位置を合わせるにはコンパイラのオプションでスタートアドレス (CP/M-80 版) またはコードセグメントとデータセグメント (8086 版) を合わせる必要があります。

           Memory
           Com-file
compile -> cHn-file

minimum cOde segment size:   0000 (max 0D80 paragraphs)
minimum Data segment size:   0000 (max 0FDB paragraphs)
mInimum free dynamic memory: 0400 paragraphs
mAximum free dynamic memory: A000 paragraphs

Find run-time error  Quit

>

○ インライン機械語

inline() の中に / で区切った コード を記述する事ができます。コードの種類は次の通りです。

コード 説明
定数 定数 or 定数名 (Integer 型)。定数が 0..255 の範囲であれば 1 バイト、それ以外は 2 バイトが割り当てられる
変数名 2 バイト。変数の (オフセット) アドレス。
手続き名・関数名 2 バイト。手続き・関数の (オフセット) アドレス。
ロケーションカウンタ * とそれに続く符号付きの整数

コードに付ける <> は 8 ビットのシフトを表します。例えば <$1234$34 を表し、>$56$5600 を表します。+- による演算が可能なコードもあります。

procedure UpperCase(var Strg: Str); {Str is type string[255]}
{$A+}
begin
  inline(  $2A/Strg/  {     LD  HL,(Strg) }
           $46/       {     LD  B,(HL)    }
           $04/       {     INC B         }
           $05/       { L1: DEC B         }
           $CA/*+20/  {     JP  Z, L2     }
           $23/       {     INC HL        }
           $7E/       {     LD  A,(HL)    }
           $FE/$61/   {     CP  'a'       }
           $DA/*-9/   {     JP  C,L1      }
           $FE/$7B/   {     CP  'z'+1     }
           $D2/*-14/  {     JP  NC,L1     }
           $D6/$20/   {     SUB 20H       }
           $77/       {     LD  (HL),A    }
           $C3/*-20); {     JP  L1        }
                      { L2: EQU $         }
end; 

インラインアセンブラではないので少々使い勝手は悪いのですが、機械語で書いたルーチンを使えるのは大きいです。

○ 外部副プログラム

Turbo Pascal ではアセンブラで書かれたルーチンを呼び出す事ができます。

別途アセンブラが必要なのと、ルーチンの記述に制約がある (既存の機械語ルーチンがすべて呼び出せるわけではない) ため、あまり活用する場面はないかもしれません。

・CP/M-80 版

例えば DSKRESET.COM にあるルーチンを呼び出すには、外部ルーチンを external を付けて宣言します。

procedure DiskReset; external $ECOO;

プログラムが開始されたら、目的のファイルを外部ルーチンの宣言にあったアドレスへ 自力で ロードします。

procedure LoadRoutine;
var
  CodeFile: File;
  Buffer: array [0..1] of Byte absolute $ECOO;
  Index, Rec: Integer;
begin
  Assign(CodeFile, 'DSKRESET.COM');
  Reset(CodeFile);
  Index := 0; 
  Rec := 0;
  {$R-}
  while not EOF(CodeFile) do 
  begin
    BloekRead(CodeFile, Buffer[Index], Rec);
    Rec := Rec + 1;
    Index := Index + 128;
  end;
  {$R+}
  Close(CodeFile);
end; { of proc LoadRoutine }

ルーチンをアセンブラで記述する際の制約があります。詳しくはマニュアルを参照してください。

・8086 版

例えば DSKRESET.COM にあるルーチンを呼び出すには、外部ルーチンを external を付けて宣言します。

procedure DiskReset; external 'DSKRESET';

ファイル指定に拡張子は不要です。拡張子は DOS 版の場合に .COM、CP/M-86 版の場合は .CMD です。

ルーチンをアセンブラで記述する際の制約があります。詳しくはマニュアルを参照してください。

○ Bdos() / BdosHL() (CP/M 版)

BDOS の機能を呼び出します。CP/M 版の機能です。

・CP/M-80 版

いくつかの手続き・関数があります。

  procedure Bdos(Func, Param); { CP/M-80 only }
  function Bdos(Func, Param): byte; { CP/M-80 only }
  function BdosHL(Func, Param): byte; { CP/M-80 only }

Func には 呼び出すファンクション番号をセットします。ファンクション番号は C レジスタに格納されます。Param には DE レジスタに渡すパラメータを指定します。パラメータ Param は Integer で渡してもいいのですが、D, E レジスタに別々の値をセットする事が多いので、下の例のようにすると便利です。

var
  A: Byte;
  DE: array [0..1] of Byte;
  HL: Integer;
begin
  DE[0] := 1; { E }
  DE[1] := 2; { D }
  A := BDos(1, Addr(DE[0]));
  HL := BDosHL(2, Addr(DE[0]));
  A := BDos(3);
  HL := BDosHL(4);
  BDos(5, Addr(DE[0]));
end;

結果が A レジスタ (または L レジスタ) に返るものは Bdos() 関数、HL レジスタに返るものは BdosHL() 関数を使います。結果が返らないものに関しては、どちらを使っても構わないのですが、結果を返さない Bdos() 手続きも用意されています (CP/M-80 版)。パラメータを必要としない BDOS ファンクションのために Param を省略する事が可能です。

パラメータ 結果 手続き/関数
なし 返らない BDos(Func);
なし A レジスタに返る A := BDos(Func);
なし HL レジスタに返る HL := BDosHL(Func);
あり 返らない BDos(Func, Addr(DE[0]));
あり A レジスタに返る A := BDos(Func, Addr(DE[0]));
あり HL レジスタに返る HL := BDosHL(Func, Addr(DE[0]));

MSX-DOS の BDOS ファンクションコールには、Turbo Pascal の Bdos() / BdosHL() が使えないものがあります。

・CP/M-86 版

CP/M-86 版には一つの手続きしかありません。

  procedure Bdos(var Regs); { CP/M-86 only }

パラメーターとして次の register レコード型変数を渡す必要があります。

type
  register = record
    case Integer or
      1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
      2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte);
  end;

マニュアルの定義では SI と DI が逆になっています。

CL レジスタ (CX レジスタの下位) に呼び出すファンクション番号をセットし、パラメータを DX レジスタにセットしてから Bdos() を実行すると、AX (または下位の AL) や BX (または下位の BL) に値がセットされます。

var
  Reg: register;
  v := Integer;
begin
  Reg.CL := 5;
  Reg.DX := 123;
  Bdos(reg);
  v := Reg.AX;
end;

○ Bios() / BiosHL() (CP/M 版)

BIOS の機能を呼び出します。CP/M 版の機能です。呼び出し方は Bdos() / BdosHL() と全く同じです。

○ MsDos() (DOS 版)

DOS の機能を呼び出します。

  procedure MsDos(var Regs); { DOS only }

パラメーターとして次の register レコード型変数を渡す必要があります。

type
  register = record
    case Integer or
      1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
      2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte);
  end;

マニュアルの定義では SI と DI が逆になっています。

AH レジスタ (AX レジスタの上位) に呼び出すファンクション番号をセットし、パラメータを各レジスタにセットしてから MsDos() を実行すると、CX レジスタや DX レジスタに値がセットされます。

var
  Reg: register;
  v := Integer;
begin
  Reg.AH := $2C; { Reg.AX := $2C00; }
  MsDos(reg);
  v := Reg.CX;
end;

○ Intr() (8086 版)

割り込みを行います。

パラメーターとして次の register レコード型変数を渡す必要があります。

type
  register = record
    case Integer or
      1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
      2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte);
  end;

マニュアルの定義では SI と DI が逆になっています。

IntNo に割り込み番号をセットして Intr() を実行すると、register レコード型変数に結果が返ります。

var
  Regs: register;
begin
  Intr($21, Regs);
end;

○ ユーザー定義の I/O ドライバ

スペシャルファイル (DOS ではデバイスファイル) への処理を独自の処理で置き換える事が可能です。

スペシャルファイル
(デバイスファイル)
システム変数 ハンドラ 説明
ConStPtr function ConSt: Boolean; KeyPressed() で呼び出されます。
CON:
TRM:
KBD:
ConInPtr function ConIn: Char; コンソールからの入力用ハンドラです。
CON:
TRM:
ConOutPtr procedure ConOut(Ch: Char); コンソールへの出力用ハンドラです。
LST: LstOutPtr procedure LstOut(Ch: Char); プリンタ等への出力用ハンドラです。
AUX: AuxInPtr function AuxIn: Char; シリアルポート等、補助装置への入力用ハンドラです。
AUX: AuxOutPtr procedure AuxOut(Ch: Char); シリアルポート等、補助装置への出力用ハンドラです。
USR: UsrInPtr function UsrIn: Char; ユーザー装置 (デフォルトでコンソール) からの入力用ハンドラです。
USR: UsrOutPtr procedure UsrOut(Ch: Char); ユーザー装置 (デフォルトでコンソール) への出力用ハンドラです。

例としてユーザー装置の出力をいじってみましょう。

USERIO.PAS
program USERIO;
  procedure UsrOut(Ch: Char);
  begin       
    Write(Chr(Ord(Ch) + 1)); 
  end;
var 
  s: string[20];   
  i: Integer;   
begin 
  UsrOutPtr := Addr(UsrOut); { 8bit }
(*
  UsrOutPtr := Ofs(UsrOut); { 16bit }
*)
  s := 'Hello,world.';                           
  for i:=1 to Length(s) do                               
    Write(USR, s[i]);                   
end.   

出力がシーザー暗号になっています。

Ifmmp-xpsme/

おわりに

『Turbo Pascal 3.0』くらいの規模だと覚えるのもそんなに難しくないですよね。

See Also:

索引

  1. Delphi では予約語ではなく指令です。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?