# Turbo Pascal 3.0 の Pascal --- tags: Pascal TurboPascal created_at: 2021-01-24 updated_at: 2023-12-16 --- # はじめに Turbo Pascal 3.0 の Pascal について調べてみました。 ![image.png](./images/3279c8a6-a63f-8ab4-b7f7-2d480cf01351.png) # Turbo Pascal は Pascal 何を当たり前の事を言っているのかとお思いでしょうが、Delphi に比べると遥かに Pascal です。しかしながら標準 Pascal とも異なっています。 Turbo Pascal の文法に関してはマニュアル等の書籍があればそれが一番いいのですが、標準 Pascal や Delphi のドキュメントからもある程度知る事ができます。 **See also:** - [標準 Pascal の規格票と解説書を読んでみる (Qiita)](./091661d6fa3d72700a56.md) - [標準 Pascal 範囲内での Delphi 入門 (Qiita)](./e2796788c65c2cda1de5.md) - [Delphi と標準 Pascal の比較 (Qiita)](./a13d6459227e787422ae.md) - [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 の二番目の書式](./745ad4a792d277d33831.md#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 の二番目の書式〔裏〕](./3be4294e82653fb234c4.md#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](./images/94c9fb22-cacc-aefa-a3f9-f39dcd455400.png) これを CP/M-80 版の Turbo Pascal で実行してみると... ![image.png](./images/254fab61-a8ea-07a2-0aa1-dafe239816ab.png) `5` が返ってきます。 ![image.png](./images/7d26d075-a825-9f10-f22f-aae174e8fbde.png) MS-DOS 版では `6` が返ります。 ### ○ Get() と Put() Turbo Pascal には `Get()` と `Put()`、そして`バッファ変数`が使えません。これはファイルの 1 文字先読み機能がない事を意味します。 **See also:** - [9.1. ファイル構造](./986fa01c9e6cfc3fd673.md#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](./05af01a81f7c3bb5eeb4.md#471-pascal-%E3%81%A8-goto) ### ○ Page() `Page()` 手続きは実装されていません。 **See also:** - [12.4. 手続き Page()](./391d6c1a53c7aaa3416f.md#124-%E6%89%8B%E7%B6%9A%E3%81%8D-page) ### ○ パックされた変数 **PACKED** は文法上エラーになりませんが、Turbo Pascal では意味を持ちません。このため、`Pack()` と `Unpack()` は実装されていません。 **See also:** - [構造化型 (Structured Types)](./eedda6d38b6d0887d4ac.md#%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B-structured-types) - [6.3. パックとアンパック](./eedda6d38b6d0887d4ac.md#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)](./b93ac03bfee002f17137.md#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)](./b93ac03bfee002f17137.md#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 を参照して知る事ができます。 パラメータの頭に **var** が付いているものは **変数パラメータ** で、変数を渡す必要があります。定数を渡す事はできません。 Turbo Pascal 3.0 には、3 バージョンあるのですが、OS や CPU に固有のルーチンが存在します。固有のルーチンにはそれと判るようなコメントを入れておきます。 | | Z80 | Intel 8086 | |:---|:---:|:---:| | CP/M | CP/M-80 | CP/M-86 | | MS-DOS (IBM PC) | (N/A) | MS-DOS | | MS-DOS (非 IBM PC) | (N/A) | MS-DOS | :::note ・標準 Pascal に存在しないルーチンは軽い説明を添える事にします。 ・グラフィックやタートルグラフィックス等の固有ルーチンは紹介しません。 ::: **See also:** - [標準手続きと標準関数](./15b27e00fac9d723d9f0.md#%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 入門)](./5c04bb2b42947e29d94b.md) - [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); ``` ※ file of type = ファイル型。「**file** **of** 型」で定義されたユーザー定義の型です。 **See also:** - [12. テキストファイルの入出力](./391d6c1a53c7aaa3416f.md) ### ○ 算術ルーチン **関数** ```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; ``` ※ scalar = ここで言うスカラ (型) とは順序型の事です。 **See also:** - [2.1. 順序型 (Ordinal Types)](./5c04bb2b42947e29d94b.md#21-%E9%A0%86%E5%BA%8F%E5%9E%8B-ordinal-types) - [5. 列挙型と部分範囲型](./335e59857cc78aea951a.md#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 パラメータ](./391d6c1a53c7aaa3416f.md#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 MS-DOS } OvrDrive(d: integer); { for CP/M } OvrPath(path: string); { for MS-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 MS-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) | バッファの内容をディスクに書き込みます。MS-DOS では意味を持ちません。| | Execute(F) | 拡張子が *.COM の実行形式ファイルを実行します。詳細は[こちら](#-execute)。| | LongSeek(F, Num) | 型なしファイルまたは型付きファイルのファイルポインタを 128 バイト単位で移動します。65535 レコードを超えるサイズのファイルに対しても使えます。(MS-DOS のみ)| | OvrDrive(No) | オーバレイファイルのあるドライブ (0=カレント, 1=A:, 2=B:...) を指定します。詳細は[こちら](#-オーバーレイファイル)。(CP/M のみ)| | OvrPath(path) | オーバレイファイルのあるパスを指定します。詳細は[こちら](#-オーバーレイファイル)。(MS-DOS のみ)| | Execute(F) | 拡張子が *.COM の実行形式ファイルを実行します。詳細は[こちら](#-execute)。| | Rename(F, FileName) | ファイル名を変更します。 | | Seek(F, Num) | 型なしファイルまたは型付きファイルのファイルポインタを 128 バイト単位で移動します。| | Truncate(F) | ファイルの現在位置より後ろを削除します。事前にファイルをオープンしておく必要があります。(MS-DOS のみ)| ※ file of type = 型付きのファイル型。「**file** **of** 型」で定義されたユーザー定義の型です。 **関数** ```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 MS-DOS } LongFileSize(var F: file): Real; { for MS-DOS } SeekEof(var F: file): Boolean; SeekEoln(var F: Text): Boolean; ``` | 関数 | 説明 | |:---|:---| | FilePos(F) | ファイル位置を 0 ベースで返します。 | | FileSize(F) | ファイルサイズを返します。 | | LongFilePos(F) | ファイル位置を 0 ベースで返します。32KB を超えるサイズのファイルでも使えます。(MS-DOS のみ)| | LongFileSize(F) | ファイルサイズを返します。32KB を超えるサイズのファイルでも使えます。(MS-DOS のみ)| | SeekEof(F) | EOF を判定します。ブランク文字をスキップします。 | | SeekEoln(F) | EOL を判定します。ブランク文字をスキップします。 | :::note info Turbo Pascal にはバイナリファイルを扱うための **型なしファイル型** があります。 ::: **See also:** - [9. ファイル型](./986fa01c9e6cfc3fd673.md) - [12. テキストファイルの入出力](./391d6c1a53c7aaa3416f.md) ### ○ ヒープ制御ルーチン **手続き** ```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) | Mark() でセットされたポインタが指すアドレスから 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 & Release 方式で利用可能な空き容量を返します。 | | Ptr() | 詳しくは[こちら](#-ポインタ型への値の代入)。 | ### ○ 画面関連ルーチン **手続き** ```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 で指定された、文字を標準輝度に変更する制御文字を出力します。 | **関数** ```pascal:Functions WhereX: Integer; { for IBM PC } WhereY: Integer; { for IBM PC } ``` | 関数 | 説明 | |:---|:---| | WhereX() | 現在のカーソル位置 (X 座標) を返します。(IBM PC 用) | | WhereY() | 現在のカーソル位置 (Y 座標) を返します。(IBM PC 用) | **See also:** - [Turbo Pascal 3.0 のスクリーン設定 (Qiita)](./0d8976801c02ec685bd6.md) - [Delphi のコンソールアプリケーションで文字色変更と座標指定をしたい! (Qiita)](./e0a12036de0596c9cbae.md) ### ○ その他ルーチン **手続き** ```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 MS-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 MS-DOS } Halt; Intr(IntNo: Integer; var Result: record); { for 8086 } MkDir(Path: String); { for MS-DOS } Move(var Source, Dest: type; Length: Integer); MsDos(var Param: record); { for MS-DOS } Randomize; RmDir(Drv:integer; var Path: String); { for MS-DOS } ``` | 手続き | 説明 | |:---|:---| | Bdos() | 詳細は[こちら](#-bdos--bdoshl-cpm)。(CP/M 用)| | Bios() | 詳細は[こちら](#-bios--bioshl-cpm)。(CP/M 用)| | ChDir(path) | ディレクトリを変更します。(MS-DOS 用)| | Delay(mS) | ms で指定されたミリ秒待ちます。TINST で指定した周波数 (MHz) に依存します。| | FillChar(Dest, Len, Data) | Dest 変数の先頭から Len バイトを Data で埋めます。| | GetDir(Drive, Path) | Drive (0=カレント, 1=A:, 2=B:...) のカレントディレクトリを文字列変数 Path に格納します。(MS-DOS 用)| | Halt() | プログラムを停止します。| | Intr() | 詳細は[こちら](#-intr-8086)。(8086 用)| | MkDir(Path) | Path で指定したディレクトリを作成します。(MS-DOS 用)| | Move(Src, Dst, Len) | 変数 Src の先頭から Len バイトを Dst にコピーします。| | MsDos() | 詳細は[こちら](#-msdos-ms-dos)。(MS-DOS 用)| | Randomize() | 疑似乱数のシードを初期化します。 | | RmDir(Path) | Path で指定したディレクトリを削除します。ディレクトリが空でない場合には削除に失敗します。(MS-DOS 用)| **関数** ```pascal:Functions Addr(var Variable): Pointer; { for MS-DOS, CP/M-86 } Addr(var Variable): Integer; { for CP/M-80 } Addr(): Integer; { for CP/M-80 } Addr(): 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(): 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)](./0d8976801c02ec685bd6.md#hardware-dependent-information) - [Borland Turbo Pascal - RunCPM (Z80 CP/M 2.2 エミュレータ) (ht-deko.com)](https://ht-deko.com/arduino/runcpm.html#04) ## ■ コンパイラ指令 `{$` で始まるコメントのようなものはコンパイラ指令です。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 版 (CP/M-86 / MS-DOS) | 指令 | 指令 | 説明 | |:---|:---|:---| | スタックチェック | **{$K+}** または {$K-} | ローカル変数用のメモリ空間としてスタックが利用可能か調べるか? | ### ○ MS-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` を使って、変数を特定のメモリアドレスに置くように指定する事ができます。 ```pascal var sys_musicf: byte absolute $FB3F; ``` CP/M-86 版と MS-DOS 版はアドレスの指定方法が異なります。 ```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 (Z80) の場合 `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; ``` CP/M-86 版と MS-DOS 版には、配列の要素が 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; ``` CP/M-86 版と MS-DOS 版には、配列の要素が 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.) インクルードファイル](./afb3c008a83a20792169.md#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()` 手続き (MS-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) またはコードセグメントとデータセグメント (CP/M-86, MS-DOS) を合わせる必要があります。 ``` 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) またはコードセグメントとデータセグメント (CP/M-86, MS-DOS) を合わせる必要があります。 ``` 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; ``` インラインアセンブラではないので少々使い勝手は悪いのですが、機械語で書いたルーチンを使えるのは大きいです。 ### ○ 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() (MS-DOS) MS-DOS の機能を呼び出します。 ```pascal procedure MsDos(var Regs); { MS-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 ドライバ スペシャルファイル (MS-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); | ユーザー装置 (デフォルトでコンソール) への出力用ハンドラです。 | 例としてユーザー装置の出力をいじってみましょう。 ```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)](./b41b0cd45181aec522d4.md) ## 索引 - [Turbo Pascal 3.0.x の使い方](./ec212f5cc17cbe5f718b.md) - **Turbo Pascal 3.0.x の Pascal** - [Turbo Pascal 3.0.x のスクリーン設定](https://qiita.com/items/0d8976801c02ec685bd6) - [Turbo Pascal 3.0.x のキーボードショートカット](./86ddf71d0c509125b7ba.md) [^1]: **uses** は Turbo Pascal 4.0 以降で利用可能です。