Turbo Pascal 3.0 の Pascal

# はじめに

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/3279c8a6-a63f-8ab4-b7f7-2d480cf01351.png)

# Turbo Pascal は Pascal

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

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

**See also:**

 - [PUG BOOKS の Turbo Pascal 関連書籍を読んでみる (Qiita)](https://qiita.com/ht_deko/items/e110443098055327ca61)
 - [イロイロな出版社から出ている Turbo Pascal 関連書籍を読んでみる (Qiita)](https://qiita.com/ht_deko/items/29d9167bf288f303eb93)
 - [標準 Pascal の規格票と解説書を読んでみる (Qiita)](https://qiita.com/ht_deko/items/091661d6fa3d72700a56)
 - [標準 Pascal 範囲内での Delphi 入門 (Qiita)](https://qiita.com/ht_deko/items/e2796788c65c2cda1de5)
 - [Delphi と標準 Pascal の比較 (Qiita)](https://qiita.com/ht_deko/items/a13d6459227e787422ae)
 - [Delphi 言語ガイド (Embarcadero)](http://docwiki.embarcadero.com/RADStudio/ja/Delphi_%E8%A8%80%E8%AA%9E%E3%82%AC%E3%82%A4%E3%83%89%EF%BC%9A%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9)

## ■ 標準 Pascal と Turbo Pascal の違い
標準 Pascal 範囲内での差異は次の通りです。

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

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

**See also:**

 - [(10.2.1.) New と Dispose の二番目の書式](https://qiita.com/ht_deko/items/745ad4a792d277d33831#1021-new-%E3%81%A8-dispose-%E3%81%AE%E4%BA%8C%E7%95%AA%E7%9B%AE%E3%81%AE%E6%9B%B8%E5%BC%8F)
 - [(10.2.1.) New と Dispose の二番目の書式〔裏〕](https://qiita.com/ht_deko/private/3be4294e82653fb234c4#1021-new-%E3%81%A8-dispose-%E3%81%AE%E4%BA%8C%E7%95%AA%E7%9B%AE%E3%81%AE%E6%9B%B8%E5%BC%8F)

### ○ 再帰 (CP/M-80 版)

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

```pascal: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 にある再帰のサンプル](https://wiki.freepascal.org/Recursion/ja)を改変したものです。標準 Pascal で実行してみると、確かに `6` が返ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/94c9fb22-cacc-aefa-a3f9-f39dcd455400.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/254fab61-a8ea-07a2-0aa1-dafe239816ab.png)

`5` が返ってきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/7d26d075-a825-9f10-f22f-aae174e8fbde.png)

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

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

**See also:**

 - [9.1. ファイル構造](https://qiita.com/ht_deko/items/986fa01c9e6cfc3fd673#91-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E6%A7%8B%E9%80%A0)

### ○ GOTO ステートメント

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

**See also:**

 - [(4.7.1.) Pascal と goto](https://qiita.com/ht_deko/items/05af01a81f7c3bb5eeb4#471-pascal-%E3%81%A8-goto)

### ○ Page()

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

**See also:**

 - [12.4. 手続き Page()](https://qiita.com/ht_deko/items/391d6c1a53c7aaa3416f#124-%E6%89%8B%E7%B6%9A%E3%81%8D-page)

### ○ パックされた変数
**PACKED** は文法上エラーになりませんが、Turbo Pascal では意味を持ちません。このため、`Pack()` と `Unpack()` は実装されていません。

**See also:**

 - [構造化型 (Structured Types)](https://qiita.com/ht_deko/items/eedda6d38b6d0887d4ac#%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B-structured-types)
 - [6.3. パックとアンパック](https://qiita.com/ht_deko/items/eedda6d38b6d0887d4ac#63-%E3%83%91%E3%83%83%E3%82%AF%E3%81%A8%E3%82%A2%E3%83%B3%E3%83%91%E3%83%83%E3%82%AF)

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

**See also:**

 - [11.1.4. 手続きパラメータ (Procedural parameters)](https://qiita.com/ht_deko/items/b93ac03bfee002f17137#1114-%E6%89%8B%E7%B6%9A%E3%81%8D%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF-procedural-parameters)
 - [11.2.1. 関数パラメータ (Functional parameters)](https://qiita.com/ht_deko/items/b93ac03bfee002f17137#1121-%E9%96%A2%E6%95%B0%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF-functional-parameters)

## ■ 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 を参照して知る事ができます。

以降のルーチンの説明では、パラメータや結果の型に次のシンボルが使われます。

| シンボル | 意味 |
|:---|:---|
| type | すべての型 |
| string | すべての文字列型 |
| file | すべてのファイル型 |
| scalar | すべての順序型 |
| pointer | すべてのポインタ型 |

パラメータの頭に **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 |

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

**See also:**

 - [標準手続きと標準関数 (標準 Pascal 範囲内での Delphi 入門)](https://qiita.com/ht_deko/items/15b27e00fac9d723d9f0#%E6%A8%99%E6%BA%96%E6%89%8B%E7%B6%9A%E3%81%8D%E3%81%A8%E6%A8%99%E6%BA%96%E9%96%A2%E6%95%B0)
 - [<2> データの概念: 単純型 (標準 Pascal 範囲内での Delphi 入門)](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b)
 - [Delphi 組み込みルーチン (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/Delphi_%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3)

### ○ 入出力ルーチン

**手続き**

```pascal: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);
```

**See also:**

 - [12. テキストファイルの入出力](https://qiita.com/ht_deko/items/391d6c1a53c7aaa3416f)


### ○ 算術ルーチン

**関数**

```pascal: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) | 実数の整数部分を返します。|


### ○ 順序ルーチン

**関数**

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

**See also:**

 - [2.1. 順序型 (Ordinal Types)](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#21-%E9%A0%86%E5%BA%8F%E5%9E%8B-ordinal-types)
 - [5. 列挙型と部分範囲型](https://qiita.com/ht_deko/items/335e59857cc78aea951a#5-%E5%88%97%E6%8C%99%E5%9E%8B%E3%81%A8%E9%83%A8%E5%88%86%E7%AF%84%E5%9B%B2%E5%9E%8B)

### ○ 変換ルーチン

**関数**

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

### ○ 文字列ルーチン

**手続き**

```pascal: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 パラメータ](https://qiita.com/ht_deko/items/391d6c1a53c7aaa3416f#1231-%E6%9B%B8%E3%81%8D%E5%87%BA%E3%81%97%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF-%E6%9B%B8%E3%81%8D%E8%BE%BC%E3%81%BF%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF--write-parameters)によって変換した文字列を S に格納します。|
| Val(S, V, C) |  文字列 -> 数値変換を行います。文字列 S を数値に変換した数値を V に返し、変換の可否を C に返します。変換に成功すると C には 0 が、失敗すると 0 以外が格納されます。|

**関数**

```pascal: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 を返します。 |

### ○ ファイル操作ルーチン

**手続き**

```pascal: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 の実行形式ファイルを実行します。詳細は[こちら](#-chain)。|
| Close(F) | 現在のファイルをクローズします。Delphi では CloseFile() という名前です。|
| Erase(F) | 現在のファイルを削除します。事前にファイルをオープンする必要はありません。|
| Flush(F) | バッファの内容をディスクに書き込みます。DOS では意味を持ちません。|
| Execute(F) | 拡張子が *.COM の実行形式ファイルを実行します。詳細は[こちら](#-execute)。|
| LongSeek(F, Num) | 型なしファイルまたは型付きファイルのファイルポインタを 128 バイト単位で移動します。65535 レコードを超えるサイズのファイルに対しても使えます。(DOS 版)|
| OvrDrive(No) | オーバレイファイルのあるドライブ (0=カレント, 1=A:, 2=B:...) を指定します。詳細は[こちら](#-オーバーレイファイル)。(CP/M 版)|
| OvrPath(path) | オーバレイファイルのあるパスを指定します。詳細は[こちら](#-オーバーレイファイル)。(DOS 版)|
| Execute(F) | 拡張子が *.COM の実行形式ファイルを実行します。詳細は[こちら](#-execute)。|
| Rename(F, FileName) | ファイル名を変更します。 |
| Reset(F [, RecSize]) | 既存のファイルを開きます。DOS 版ではブロック転送のためのレコードサイズを RecSize で変更する事ができます |
| Rewrite(F [, RecSize]) | ファイルを新規作成します。DOS 版ではブロック転送のためのレコードサイズを RecSize で変更する事ができます |
| Seek(F, Num) | 型なしファイルまたは型付きファイルのファイルポインタを 128 バイト単位で移動します。|
| Truncate(F) | ファイルの現在位置より後ろを削除します。事前にファイルをオープンしておく必要があります。(DOS 版)|

**関数**

```pascal: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 を判定します。ブランク文字をスキップします。 |

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

:::note warn
型なしファイルまたは型付きファイルのファイル操作の単位は 128 バイトです。ファイルサイズは 128 の倍数になります。

CP/M の場合、テキストファイルもファイルサイズは 128 の倍数になります (`0x1A` 以降は無視されます)。DOS 版の場合、Reset() や Rewrite() のオプションパラメータでレコードサイズを変更できます。
:::

:::note warn
SeekEof() / SeekEoln() はマニュアルによっては SkpEof() / SkpEoln() と誤記されています。
:::

**See also:**

 - [9. ファイル型](https://qiita.com/ht_deko/items/986fa01c9e6cfc3fd673)
 - [12. テキストファイルの入出力](https://qiita.com/ht_deko/items/391d6c1a53c7aaa3416f)

### ○ ヒープ制御ルーチン

Turbo Pascal 3.0 では 2 通りのヒープ制御方法があります。


**手続き**

```pascal: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) | P でセットされたポインタが指すアドレスから I バイトの領域を確保します。|
| Mark(P) | ポインタ変数 P にヒープ領域の最上位アドレスをセットします。|
| Release(P) | ポインタ変数 P よりも上位にあるすべてのヒープメモリを解放します。|

**関数**

```pascal:Functions
MaxAvail: Integer;
MemAvail: Integer;
Ord(P: pointer): Integer; { for CP/M-80 }
Ptr(I: Integer): pointer; { for CP/M-80 }
```

| 手続き | 説明 |
|:---|:---|
| MaxAvail() | ヒープ領域にある最大連続スペースを返します。 |
| MemAvail() | ヒープ領域の空き容量を返します (Mark &amp; Release 用)。 |
| Ptr() | 詳しくは[こちら](#-ポインタ型への値の代入)。 |

#### ・Mark() &amp; Release() によるヒープメモリの破棄

Mark &amp; Release 方式は、マークされたヒープポインタよりも上位のヒープメモリを解放する方法です。

```pascal
var
  HeapTop: ^Integer;
  Hoge1, Hoge2: ^Hoge;
begin
  Mark(HeapTop);
  ...
  New(Hoge1);
  ...
  New(Hoge2);
  ... 
  Release(HeapTop)
end;  
```

#### ・Dispose() によるヒープメモリの破棄

標準 Pascal にあった New() で確保されたヒープメモリを Dispose() で個別に解放する方法です。

```pascal
var
  Hoge1, Hoge2: ^Hoge;
begin
  New(Hoge1);
  ...
  Dispose(Hoge1);
  ...
  New(Hoge2);
  ...
  Dispose(Hoge2);
end;  
```

小さなメモリブロックの確保/解放を繰り返すと、虫食い状態になって大きなメモリブロックを確保できない事があります。

:::note
Dispose() は Turbo Pascal 2.0 以降で利用可能です。
:::


#### ・GetMem() で確保されたヒープメモリの破棄

 - Mark &amp; Release 方式では Release() で破棄します。
 - Mark &amp; Release 方式を使わない場合には、FreeMem() で個別に破棄します。

:::note
FreeMem() は Turbo Pascal 2.0 以降で利用可能です。
:::


### ○ 画面関連ルーチン

**手続き**

```pascal: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 で指定された、文字を標準輝度に変更する制御文字を出力します。 |

**See also:**

 - [Turbo Pascal 3.0 のスクリーン設定 (Qiita)](https://qiita.com/ht_deko/items/0d8976801c02ec685bd6)
 - [Delphi のコンソールアプリケーションで文字色変更と座標指定をしたい！ (Qiita)](https://qiita.com/ht_deko/items/e0a12036de0596c9cbae)

### ○ その他ルーチン

**手続き**

```pascal: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() | 詳細は[こちら](#-bdos--bdoshl-cpm)。(CP/M 版)|
| Bios() | 詳細は[こちら](#-bios--bioshl-cpm)。(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() | 詳細は[こちら](#-intr-8086)。(8086 版)|
| MkDir(Path) | Path で指定したディレクトリを作成します。(DOS 版)|
| Move(Src, Dst, Len) | 変数 Src の先頭から Len バイトを Dst にコピーします。|
| MsDos() | 詳細は[こちら](#-msdos-ms-dos)。(DOS 版)|
| Randomize() | 疑似乱数のシードを初期化します。 |
| RmDir(Path) | Path で指定したディレクトリを削除します。ディレクトリが空でない場合には削除に失敗します。(DOS 版)|

**関数**

```pascal: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() | 詳細は[こちら](#-bdos--bdoshl-cpm)。|
| Bios() / BiosHL() | 詳細は[こちら](#-bios--bioshl-cpm)。|
| 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:**

 - [Hardware dependent information - Turbo Pascal 3.0 のスクリーン設定 (Qiita)](https://qiita.com/ht_deko/items/0d8976801c02ec685bd6#hardware-dependent-information)
 - [Borland Turbo Pascal - RunCPM (Z80 CP/M 2.2 エミュレータ) (ht-deko.com)](https://ht-deko.com/arduino/runcpm.html#04)


### ○ PC DOS 固有のルーチン

#### ・基本グラフィックス、ウインドウ、サウンド

**手続き**

```pascal:Procedures
Draw(X1, Y1, X2, Y2, Color);
GraphBackground(Color: Integer);
GraphColorMode;
GraphMode;
GraphWindow(X1 ,Y1 ,X2, Y2: Integer);
HiRes;
HiResColor(Color: lnteger);
NoSound;
Palette(Color: lnteger);
Plot(X, Y, Color: lnteger);
Sound(I: Integer);
TextBackground(Color: Integer);
TextColor(Color: Integer);
TextMode(Color: Integer);
Window(X1 , Y1, X2, Y2: Integer);
```

**関数**

```pascal:Functions
WhereX: Integer;
WhereY: Integer;
```

**定数**

```pascal:Constants
BW40         = 0;
C40          = 1;
BW80         = 2;
C80          = 3;
Black        = 0;
Blue         = 1;
Green        = 2;
Cyan         = 3;
Red          = 4;
Magenta      = 5;
Brown        = 6;
LightGray    = 7;
DarkGray     = 8;
LightBlue    = 9;
LightGreen   = 10;
LightCyan    = 11;
LightRed     = 12;
LightMagenta = 13;
Yellow       = 14;
White        = 15;
Blink        = 16;
```

:::note warn
GraphWindow() でグラフィックスウィンドウを定義するとき、座標 (0,0) は物理的なスクリーンではなく (クリッピングされた) ウィンドウの左上隅になります。
:::

:::note warn
TextBackground() に指定できる値は `0..7` です。
:::

:::note warn
Window() は 43 行に対応しています (EGA)。
:::


#### ・拡張グラフィックス

**手続き**

```pascal:Procedures
Arc(X ,Y, Angle, Radius, Color: Integer);
Circle(X, Y, Radius, Color: Integer);
ColorTable(C1, C2, C3, C4: Integer);
FillScreen(Color: Integer);
FiIIShape(X, Y, FillColor, BorderColor: Integer);
FillPattern(X1, Y1 ,X2, Y2, Color: Integer);
GetPic(var Buffer: AnyType; X1 ,Y1 ,X2, Y2: Integer);
Pattern(P: array [0..7] of Byte);
PutPic(var Buffer: AnyType; X, Y: Integer);
```

**関数**

```pascal:Functions
GetDotColor(X, Y: Integer): Integer;
```

:::note
拡張グラフィックスを使うには、`GRAPH.P` を[インクルード](#-%E3%82%A4%E3%83%B3%E3%82%AF%E3%83%AB%E3%83%BC%E3%83%89%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB)する必要があります。
:::

:::note warn
GetDotColor() はマニュアルによっては GetPoint() と誤記されています。
:::


#### ・タートルグラフィックス

**手続き**

```pascal:Procedures
Back(Dist: Integer);
ClearScreen;
Forwd(Dist: Integer);
HideTurtle;
Home;
NoWrap;
PenDown;
PenUp;
SetHeading(Angle: Integer);
SetPenColor(Color: Integer);
SetPosition(X, Y: Integer);
ShowTurtle;
TurnLeft(Angle: Integer);
TurnRight(Angte: Integer);
TurtleDelay(Ms: Integer);
TurtleWindow(X, Y, W, H: Integer);
Wrap;
```

**関数**

```pascal:Functions
Heading: Integer;
Xcor: Integer;
Ycor: Integer;
TurtleThere: Boolean;
```

**定数**

```pascal:Constants
North = 0;
East  = 90;
South = 180;
West  = 270;
```

:::note
タートルグラフィックスを使うには、`GRAPH.P` を[インクルード](#-%E3%82%A4%E3%83%B3%E3%82%AF%E3%83%AB%E3%83%BC%E3%83%89%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB)する必要があります。
:::

:::note warn
Forwd() はマニュアルによっては Forward() と誤記されています。
:::

:::note warn
TurtleDelay() はマニュアルによっては記載されていません。
:::

## ■ コンパイラ指令

`{$` で始まるコメントのようなものはコンパイラ指令です。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] を使って、変数を特定のメモリアドレスに置くように指定する事ができます。

```pascal
var
  sys_musicf: byte absolute $FB3F;
```

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

```pascal
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 版) |

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

**See Also:**

 - [絶対アドレス (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E5%A4%89%E6%95%B0%EF%BC%88Delphi%EF%BC%89#.E7.B5.B6.E5.AF.BE.E3.82.A2.E3.83.89.E3.83.AC.E3.82.B9)

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

#### ・CP/M-80 版

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

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

#### ・8086 版

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

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

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

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

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

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

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

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

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

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

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

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

```pascal: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.
```

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

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

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

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

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

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

```pascal:lib.inc
  procedure Hello;
  begin
    Writeln('Hello,world.');
  end;
```

```pascal:Prog1.pas
procedure Prog1;
{$I lib.inc}
begin
  Hello;  
end.
```

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

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

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

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

**See Also:**

 - [(0.1.2.) インクルードファイル](https://qiita.com/ht_deko/items/afb3c008a83a20792169#012-%E3%82%A4%E3%83%B3%E3%82%AF%E3%83%AB%E3%83%BC%E3%83%89-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB)
 - [インクルードファイル（Delphi）(DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%82%A4%E3%83%B3%E3%82%AF%E3%83%AB%E3%83%BC%E3%83%89_%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%EF%BC%88Delphi%EF%BC%89)

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

```pascal:lib.inc
  overlay procedure Hello;
  begin
    Writeln('Hello,world.');
  end;
```

```pascal:Prog1.pas
procedure Prog1;
{$I lib.inc}
begin
  Hello;  
end.
```

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

```txt
PROG1.COM   PROG1.000
```

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

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

**See Also:**

 - [Overlay (Wikipedia: en)](https://en.wikipedia.org/wiki/Overlay_(programming))


### ○ Execute

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

```pascal: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.
```

```pascal:prog2.pas
program Prog2;
var
  x, y: Integer;
begin
  Writeln(x, ':', y);
end.
```

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

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

```txt:結果
100:200
100:200
```

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

:::note
ロード位置を合わせるにはコンパイラのオプションでスタートアドレス (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 形式のファイルを実行する手続です。

```pascal: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.
```

```pascal:prog2.pas
program Prog2;
var
  x, y: Integer;
begin
  Writeln(x, ':', y);
end.
```

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

```txt:結果
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 形式にしておくとディスクの節約になります。

:::note
ロード位置を合わせるにはコンパイラのオプションでスタートアドレス (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` を表します。`+` と `-` による演算が可能なコードもあります。

```pascal
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; 
```

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

#### ・CP/M-80 版

Z80 機械語のバイト列を得るには外部アセンブラとして Microsoft 社製の **M-80 マクロアセンブラ** (M80) を使います。

:::note warn
 CP/M-80 用のアセンブラである ASM / MAC / RMAC は Intel 8080 ニーモニックしか受け付けません。
:::

アセンブラソースは拡張子 `*.MAC` で書きます。最低限のソースは次の通りです。

```txt
        .Z80
START:
        ; ここにコード
        END
```

インライン機械語の説明で出てきた機械語のアセンブラソース (`SRC.MAC`) はこのようになります。

```txt:SRC.MAC
        .Z80

        EXT     Strg

START:
        LD      HL,(Strg)
        LD      B,(HL)   
        INC     B        
L1:     DEC     B        
        JP      Z, L2    
        INC     HL       
        LD      A,(HL)   
        CP      'a'      
        JP      C,L1     
        CP      'z'+1    
        JP      NC,L1    
        SUB     20H      
        LD      (HL),A   
        JP      L1       
L2:     
        END     
```

M80 のパラメータは次の通りです。

```
M80 [<オブジェクト(.REL)>][,<リスティング(.PRN)>]=<ソース(.MAC)> [/<スイッチ>]
```

拡張子がデフォルトのものと同じなら省略可能です。

| 生成 | コマンドライン |
|:---|:---|
| エラーチェックのみ | M80 ,=SRC |
| SRC.MAC から SRC.REL を生成 | M80 =SRC |
| SRC.MAC から DST.REL を生成 | M80 DST=SRC |
| SRC.MAC から DST.PRN を生成 | M80 ,DST=SRC |
| SRC.MAC からリスティングファイルを生成し画面に表示 | M80 ,TTY:=SRC |
| SRC.MAC からリスティングファイルを生成しプリンタに印字 | M80 ,PRN:=SRC |
| SRC.MAC から DST.REL と DST.PRN を生成 | M80 DST,DST=SRC |

オブジェクトファイル (`*.REL`) は不要なので、先述のソースコードは次のようにしてアセンブルします。

```
M80 ,DST=SRC
```

アセンブルすると `DST.PRN` というリスティングファイルが出力されます。

```txt:DST.PRN
	MSX.M-80 1.00	01-Apr-85	PAGE	1


                                .Z80
                        
                        	    EXT  	Strg
                        
  0000'                 START:
  0000'   2A 0000*           	LD  	HL,(Strg)
  0003'   46                 	LD  	B,(HL)   
  0004'   04                 	INC 	B        
  0005'   05            L1: 	DEC 	B        
  0006'   CA 001B'           	JP  	Z, L2    
  0009'   23                 	INC 	HL       
  000A'   7E                 	LD  	A,(HL)   
  000B'   FE 61              	CP  	'a'      
  000D'   DA 0005'           	JP  	C,L1     
  0010'   FE 7B              	CP  	'z'+1    
  0012'   D2 0005'           	JP  	NC,L1    
  0015'   D6 20              	SUB 	20H      
  0017'   77                 	LD  	(HL),A   
  0018'   C3 0005'           	JP  	L1       
  001B'                 L2: 	
                                END	
				
	MSX.M-80 1.00	01-Apr-85	PAGE	S


Macros:

Symbols:
0005'	L1              001B'	L2              0000I'	START           
0001*	STRG            



No Fatal error(s)


         	JP  	C,L1 
```

この情報を元にバイト列を記述していきます。

:::note
外部参照 (`*` の付いた値) やジャンプ命令 (`'` の付いたアドレス) の所は書き替える必要があります。
:::

`DST.COM` を生成してバイト列を確認したい場合には次のようにします。

```
M80 DST,DST=SRC
L80 DST,DST/N/E
```

**See also**

 - [CP/M による Z80 マクロ・アセンブラ入門 (国立国会図書館デジタルコレクション)](https://dl.ndl.go.jp/pid/12631142)
 - [CP/M によるマシン語 (Z80) 開発法 (国立国会図書館デジタルコレクション)](https://dl.ndl.go.jp/pid/12631237)

#### ・8086 版

8086 機械語のバイト列を得るには外部アセンブラとして MS-DOS 用の **Microsoft Macro Assembler** (MASM) を使います。

| DOS | アセンブラ |
|:---|:---|
| CP/M-86 | ASM86 |
| PC-DOS | IBM Macro Assembler [^2] |
| MS-DOS | Microsoft Macro Assembler [^3] |

各 OS ネイティブで出力するという事であれば、上記のアセンブラを使う事になると思います。DOS では **Borland Turbo Assembler** (TASM) も使えますが、年代的には Turbo Pascal 3.0 よりも後の製品となります。

:::note
MASM の使い方については Web 上に情報が豊富に存在するため割愛します。
:::

#### ・INLASS
Ulrich Kern 氏による Turbo Pascal 用アセンブラ **INLASS** を使う事もできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/798039c3-de6c-4c98-98d6-f7ceb53bc58e.png)

 - [INLASS.PDF (hansotten.file-hunter.com)](https://hansotten.file-hunter.com/uploads/files/inlass.pdf)
 - [INLASS.ZIP (hansotten.file-hunter.com)](https://hansotten.file-hunter.com/uploads/files/inlass.zip)

:::note
アセンブルには `INLASS.COM` と `INLASS.000` の 2 つのファイルが必要です。
:::

INLASS で扱うソースファイルは拡張子が `*.SOU` である必要があります。次のようなソースファイルを用意しました。

```SRC.SOU
        .Z80

Strg:	EXT

START:
        LD      HL,(Strg)
        LD      B,(HL)   
        INC     B        
L1:     DEC     B        
        JP      Z, L2    
        INC     HL       
        LD      A,(HL)   
        CP      'a'      
        JP      C,L1     
        CP      'z'+1    
        JP      NC,L1    
        SUB     20H      
        LD      (HL),A   
        JP      L1       
L2:     
        END
```

INLASS は単体で実行して `*` のプロンプトでファイル名 (拡張子の入力不要) を入力するか、コマンドラインパラメータにファイル名 (拡張子の入力不要) を指定して実行します。

```
A> INLASS SRC
```

実行すると次のようなファイル (`SRC.INL`) が得られます。

```pascal:SRC.INL
INLINE (
                        (*         .Z80                                *)
                        (*STRG     EXT                                 *)
                        (*START                                        *)
$2A/STRG                (*         LD HL,(STRG)                        *)
/$46                    (*         LD B,(HL)                           *)
/$04                    (*         INC B                               *)
/$05                    (*L1       DEC B                               *)
/$CA/*+$0014            (*         JP Z,L2                             *)
/$23                    (*         INC HL                              *)
/$7E                    (*         LD A,(HL)                           *)
/$FE/$61                (*         CP 'a'                              *)
/$DA/*+$FFF7            (*         JP C,L1                             *)
/$FE/$7B                (*         CP 'z'+1                            *)
/$D2/*+$FFF2            (*         JP NC,L1                            *)
/$D6/$20                (*         SUB 20H                             *)
/$77                    (*         LD (HL),A                           *)
/$C3/*+$FFEC            (*         JP L1                               *)
                        (*L2                                           *)
                        (*         END                                 *)
)
```

ジャンプ先も自動で計算してくれるようです。これは便利。

| 16 進数| 10 進数 |
|:---:|---:|
| +$0014 | +20 |
| +$FFF7 | -9 |
| +$FFF2 | -14 |
| +$FFEC | -20 |

INLASS は Turbo Pascal で書かれており、ソースコードも付属しています。そして、8080 や 6502、8086 のコードも吐けるっぽいんですよねぇ...。


### ○ 外部副プログラム

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

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

#### ・CP/M-80 版

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

```pascal
procedure DiskReset; external $ECOO;
```

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

```pascal
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 }
```

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

#### ・8086 版

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

```pascal
procedure DiskReset; external 'DSKRESET';
```

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

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

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

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

#### ・CP/M-80 版
いくつかの手続き・関数があります。

```pascal
  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 レジスタに別々の値をセットする事が多いので、下の例のようにすると便利です。

```pascal:
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])); |

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

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

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

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

```pascal
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;
```

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

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

```pascal
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 の機能を呼び出します。

```pascal
  procedure MsDos(var Regs); { DOS only }
```

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

```pascal
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;
```

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

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

```pascal
var
  Reg: register;
  v := Integer;
begin
  Reg.AH := $2C; { Reg.AX := $2C00; }
  MsDos(reg);
  v := Reg.CX;
end;
```
### ○ Intr() (8086 版)
割り込みを行います。

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

```pascal
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;
```

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

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

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

### ○ ユーザー定義の I/O ドライバ
スペシャルファイル (DOS ではデバイスファイル) への処理を独自の処理で置き換える事が可能です。

| スペシャルファイル<br>(デバイスファイル) | システム変数 | ハンドラ | 説明 |
|:---|:---|:---|:---|
|  | ConStPtr | **function** ConSt: Boolean; | KeyPressed() で呼び出されます。  |
| CON:<br>TRM:<br>KBD: | ConInPtr | **function** ConIn: Char; | コンソールからの入力用ハンドラです。  |
| CON:<br>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); | ユーザー装置 (デフォルトでコンソール) への出力用ハンドラです。 |

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

```pascal: 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:**

 - [Delphi のご先祖を辿る (Qiita)](https://qiita.com/ht_deko/items/b41b0cd45181aec522d4)

## 索引

 - [Turbo Pascal 3.0.x の使い方](https://qiita.com/ht_deko/items/ec212f5cc17cbe5f718b)
   - **Turbo Pascal 3.0.x の Pascal**
   - [Turbo Pascal 3.0.x のスクリーン設定](https://qiita.com/items/0d8976801c02ec685bd6)
   - [Turbo Pascal 3.0.x のキーボードショートカット](https://qiita.com/ht_deko/items/86ddf71d0c509125b7ba)

[^1]: Delphi では予約語ではなく指令です。
[^2]: IBM Macro Assembler は Microsoft Macro Assembler の OEM 製品です。
[^3]: Microsoft Macro Assembler は OEM 先によっては MS-DOS に付属しています。