Delphi と標準 Pascal の比較 (標準 Pascal コードを Delphi に移植する際の注意点)

# はじめに
Wikipedia の英語版に『[Comparison of Pascal and Delphi](https://en.wikipedia.org/wiki/Comparison_of_Pascal_and_Delphi)』って記事があるのですが、標準 Pascal の仕様 (規格) を知らないと、所々意味不明だと思うので、これをちょっと解説してみます。

**See also:**

 - [Comparison of Pascal and Delphi (Wikipedia)](https://en.wikipedia.org/wiki/Comparison_of_Pascal_and_Delphi)
 - [Turbo Pascal version 1.0, Delphi, and "Standard" Pascal (Internae Archive: blogs.embarcadero.com)](https://web.archive.org/web/20160410041240/http://blogs.embarcadero.com/davidi/2008/11/04/38938)


# Delphi と標準 Pascal の違い
**Delphi 側から見た標準 Pascal との違い**については次の記事中に既に書いています。

 - [標準 Pascal 範囲内での Delphi 入門 (Qiita)](https://qiita.com/ht_deko/items/e2796788c65c2cda1de5)
 - [標準 Pascal 範囲内での Delphi 入門〔裏〕(Qiita)](https://qiita.com/ht_deko/private/46fcdbb11cdc58872160)

本記事は、逆に**標準 Pascal 側から見た Delphi との違い**という事になります。

## 1. ルーチンをパラメータに渡す方法

手続き/関数そのものを手続き/関数のパラメータに渡す手段についてです。

### ・標準 Pascal

標準 Pascal では、次のようなルーチンに実パラメータとして手続き/関数が渡せます。

```pascal
  procedure ProcParamTest(procedure Proc(V: integer));
  begin
    ...
  end; { ProcParamTest }
```

```pascal
  procedure FuncParamTest(function Func(V: integer): Integer);
  begin
    ...
  end; { FuncParamTest }
```

但し、標準ルーチンは渡せません。標準ルーチンを渡したい場合には、適当なラッパールーチンを作ってそれを渡します。この制限は、標準関数のパラメータや結果の型が決まっていない事に起因しているのだろうと思われます。例えば標準関数 `Abs()` のパラメータは実数でも整数でもよく、オーバロード関数でもありません。言うなれば**疑似関数**です。

### ・Delphi

一旦、**手続き型**を定義し、それを仮パラメータとして指定します。

```pascal
 type
   TTestProcedure = procedure (V: integer);
   TTestFunction = function (V: integer): Integer;
```

```pascal
  procedure ProcParamTest(proc: TTestProcedure);
  begin
    ...
  end; { ProcParamTest }
```

```pascal
  procedure FuncParamTest(func: TTestFunction);
  begin
    ...
  end; { FuncParamTest }
```

少々回りくどくはありますが、名前等価の考え方からすると、このやり方の方が理に適っていると思います。

**See also:**

 - [11.1.4. 手続きパラメータ (Procedural parameters) - <11> 手続きと関数 (Qiita)](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) - <11> 手続きと関数 (Qiita)](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.4.) 手続き型 (Procedural Types) - <11> 手続きと関数 (Qiita)](https://qiita.com/ht_deko/items/b93ac03bfee002f17137#114-%E6%89%8B%E7%B6%9A%E3%81%8D%E5%9E%8B-procedural-types)
 - [(6.4.4) 構造等価 (Structual Equivalence) と 名前等価 (Name Equivalence) - <6> 構造化型の概要と配列型 (Qiita)](https://qiita.com/ht_deko/items/eedda6d38b6d0887d4ac#644-%E6%A7%8B%E9%80%A0%E7%AD%89%E4%BE%A1-structual-equivalence-%E3%81%A8-%E5%90%8D%E5%89%8D%E7%AD%89%E4%BE%A1-name-equivalence)


## 2. goto の扱い
どこまで飛べるか？というお話です。

### ・標準 Pascal
**Extraprocedural gotos** であり、ルーチンの外への大域ジャンプが可能です。

### ・Delphi
**Intraprocedural gotos** であり、ルーチンの外へのジャンプはできません。局所的なジャンプです。

より正確には goto 文自体と同じ手続き/関数ブロック内にないラベルへのジャンプが禁止されています。この制限は、多くの商用 Pascal が直接的あるいは間接的に参考にした Pascal-P の制限でもあります。

**See also:**

 - [4.7. goto 文 - <4> 動作の概念 (Qiita)](https://qiita.com/ht_deko/items/05af01a81f7c3bb5eeb4#47-goto-%E6%96%87)
 - [Delphi で多重ループを抜ける方法 (Qiita)](https://qiita.com/ht_deko/items/ec4963f6d6ade3db8734)
 - [Pascal-P シリーズについて (Qiita)](https://qiita.com/ht_deko/items/adab81f28295a124e9eb)
 
## 3. バッファ変数と Get() / Put()

端的に言えばファイルからの 1 文字先読み機能です。Read() / Readln() ではファイルポインタが進んでしまいますが、バッファ変数を使うことでファイルポインタを進めずに 1 文字読むことができます。バッファの名の通りですね。

### ・標準 Pascal
サポートしています。

```pascal
program BufferVar(Output);
var
  F: Text;
  C: Char;
begin
  Rewrite(F);
  Writeln(F, 'ABCDEFG');
  Reset(F);
  Writeln(F^); { ファイルポインタは進まない }
  Read(F, C);  { ファイルポインタが進む }
  Writeln(C);  
end.
```

### ・Delphi
サポートしていません。この機能を使用している標準 Pascal のコードを移植するのは少々難しいです。

Delphi 6 以降だと、`TTextRec` を使って、バッファ変数の代替を実装できます。次の関数はファイルポインタを進めずに 1 文字読み込みます。

```pascal
  function CurrentChar(var F: Text): AnsiChar;
  begin
    Eoln(F);
    Result := (TTextRec(F).BufPtr + TTextRec(F).BufPos)^;
  end;
``` 

:::note warn
テキストファイルに対してのみ使える手法です。
:::

**See also:**

 - [9.1. ファイル構造 - <9> ファイル型(Qiita)](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)
 
## 4. New() / Dispose() の二番目の書式 

New() / Dispose() の二番目の書式は、入れ子になった可変部のあるレコード型を一部に限定して確保するメモリを節約する機能です。

この二番目の書式は可変部のあるレコード型専用の書式となっており、少々特殊な手続きです。

### ・標準 Pascal
サポートしています。

### ・Delphi
サポートしていません。メモリが節約できないだけで、New() / Dispose() の最初の書式で代用できるので特に問題ありません。

**See also:**

 - [(10.2.1.) New と Dispose の二番目の書式 - <10> ポインタ型 (Qiita)](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 の二番目の書式 - <10> ポインタ型〔裏〕(Qiita)](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)
 
## 5. Pack() / Unpack()

**Pack()** はパックされていない配列変数 (**array**) の内容をパックされている配列変数 (**packed array**) へコピーするものです。**Unpack()** はその逆を行うものです。

名前から受ける印象だと構造化型全般で使える手続きのようにも思えますが、実際には配列型専用の手続きです。

### ・標準 Pascal
サポートしています。

### ・Delphi
サポートしていません。パック状態を気にせずに `Move()` 関数でコピーすればいいと思います。

**See also:**

 - [6.3. パックとアンパック - <6> 構造化型の概要と配列型 (Qiita)](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)
 - [Move() (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Move)
 - [Copy() (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Copy)
 
## 6. ブロックコメント `{` `}` と `(*` `*)` の併用

Pascal のブロックコメントは  `{` `}` で、`(*` `*)` は　`{` `}`　の文字代替です。次のテストコードで考えてみます。

```pascal
{
  (* TEST *)
  A := 1;
}
```

### ・標準 Pascal
`{` と `(*`、 `}` と `*)` は同じ文字として扱われるため、テストコードは正しく動作しません。次のように解釈されるからです。

```pascal
  A := 1;
}
```

### ・Delphi
テストコードはネストしたブロックコメントとして扱われます。

**See also:**

 - [コメント (Comment) - <1> 記法: トークンと区切り文字(Qiita)](https://qiita.com/ht_deko/items/ce1f56017fb4fcf0302a#%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88-comment)
 - [1.2. 特殊シンボルと予約語 - <1> 記法: トークンと区切り文字(Qiita)](https://qiita.com/ht_deko/items/ce1f56017fb4fcf0302a#12-%E7%89%B9%E6%AE%8A%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AB%E3%81%A8%E4%BA%88%E7%B4%84%E8%AA%9E)
 - [C++Builder とトライグラフ (Qiita)](https://qiita.com/ht_deko/items/36115af8a57636257445)
 
## 7. eoln() が True の場合の文字

解りにくい文章ですが、「`Eoln()` が真の状態で `Read()` によって文字が読み取られた場合には何を返すのか？」という話です。「`Eoln()` が真の状態でバッファ変数が何を返すのか？」と同義です。

「空白を返す」というのは標準 Pascal 規格書の `6.4.3.5` に書かれています。

```text:ISO7185
6.4.3.5 File-types

...
Any assertion in clause 6 that the end-of-line value is attributed to a variable 
other than a component of a sequence shall be construed as an assertion that the
variable has attributed to it the char-type value space.
...

```
```text:JISX3008
6.4.3.5 ファイル型

...
列の成分以外の変数に値end-of-lineを与えたときに成立すると6.で規定する表明は，
文字型の値空白をその変数に与えたときの表明と同じとする。
...

```

次のテストコードで考えてみます。

```pascal:copytext.pas
program copytext(input, output); 
{$APPTYPE CONSOLE}
var 
  ch: char; 
begin 
  while not eof do 
    begin 
      read(ch); 
      write(ch)  
    end 
end.
```

キーボードからの入力は次の通りとします。

```
Hello,〔Enter〕
World.〔Enter〕
〔Ctrl〕+〔Z〕〔Enter〕
```

### ・標準 Pascal

End-Of-Line (改行文字) が空白に変換されるため、入力した文字がそのままエコーされた感じにはなりません。

```
Hello,
Hello, World.
World. ^Z
```

### ・Delphi

End-Of-Line (改行文字) は空白に変換されません。

```
Hello,
Hello,
World.
World.
^Z
```

標準 Pascal の動作を Delphi と同じようにするには、次のようなコードにする必要があります。

```pascal:copytext.pas
program copytext(input, output);
{$APPTYPE CONSOLE}
var
  ch: char;
begin
  while not eof do
    begin
      while not eoln do
        begin
          read(ch);
          write(ch)
        end;
      readln; 
      writeln
    end
end.
```

**See also:**

 - [9.2. テキストファイル - <9> ファイル型 (Qiita)](https://qiita.com/ht_deko/items/986fa01c9e6cfc3fd673#92-%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB)
 - [9.2. テキストファイル - <9> ファイル型〔裏〕 (Qiita)](https://qiita.com/ht_deko/private/0e012da26f46b915307d#92-%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB)

## 8. Write() / WriteLn() の書き出しパラメータのデフォルト値

この件は標準 Pascal で**処理系定義**だとされているので、Delphi と標準 Pascal の違いに含めるべきではないかもしれません。次のテストコードで考えてみます。

```pascal
  Writeln(1234);
  Writeln(-1234);
  Write(True);
  Write(False);
```

### ・標準 Pascal

Pascal-P5 での実行結果です。

```
       1234
      -1234
 True
False
```

### ・Delphi

Delphi での実行結果です。

```
1234
-1234
TRUE
FALSE
```

**See also:**

 - [Delphi における Pascal の処理系定義 (Qiita)](https://qiita.com/ht_deko/items/2b7cab95ffc60de10c25)
 
## 9. 内部ファイル
標準 Pascal には**ファイル型**があります。これは一般的に言うファイルと関連付けて使うこともできますし、関連付けずに使う事もできます。Wikipedia で言っているのは、後者の内部ファイル (一時ファイル) の事です。

### ・標準 Pascal
サポートしています。

### ・Delphi
サポートしていません。すべてのユーザー定義のファイル型は `AssignFile()` を用いて外部ファイルと関連付ける必要があります。

**See also:**

 - [<9> ファイル型 (Qiita)](https://qiita.com/ht_deko/items/986fa01c9e6cfc3fd673)
 
## 10. RTL

自分自身の文法ですべてのルーチンを記述できるのか？という話です。

例えば Write() / Writeln() で使われる `書き出しパラメータ` を持つルーチンを Pascal のコードで記述することはできませんし、Write() / Writeln() や New() / Dispose() の二番目の書式のような`可変個パラメータ`を持つルーチンを記述することもできません。

### ・標準 Pascal
Pascal の文法では記述できない標準ルーチンがあります。

### ・Delphi
Delphi の文法で記述できないルーチンは殆どありません。但し、標準 Pascal に準じたルーチンは互換性のために用意されています。

`Format()` 関数は一見、可変個パラメータのように見えますが、**型可変オープン配列パラメータ**を使っているだけで、パラメータ数は固定です。

**See also:**

 - [(12.3.1.) 書き出しパラメータ (書き込みパラメータ / Write-parameters) - <12> テキストファイルの入出力 (Qiita)](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)
 - [(11.1.6.) 型可変オープン配列パラメータ (Variant Open Array Parameters) - <11> 手続きと関数 (Qiita)](https://qiita.com/ht_deko/items/b93ac03bfee002f17137#1116-%E5%9E%8B%E5%8F%AF%E5%A4%89%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E9%85%8D%E5%88%97%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF-variant-open-array-parameters)
 - [標準手続きと標準関数 - 付録 (Qiita)](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)
 - [Format() (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.Format)
 
## (11.) Page()

`Page()` 手続きの効果は**処理系定義**なのですが、Delphi には実装されていないので、これも差異に含めていいのではないかと思います。

### ・標準 Pascal
サポートしています。

### ・Delphi
サポートしていません。

**See also:**

 - [12.4. 手続き Page() - <12> テキストファイルの入出力 (Qiita)](https://qiita.com/ht_deko/items/391d6c1a53c7aaa3416f#124-%E6%89%8B%E7%B6%9A%E3%81%8D-page)
 - [Delphi における Pascal の処理系定義 (Qiita)](https://qiita.com/ht_deko/items/2b7cab95ffc60de10c25)

## (12.) 整合配列パラメータ
標準 Pascal の**水準 1** (Standard Pascal Level 1) には任意の長さの配列をパラメータにとるルーチンを作るための**整合配列パラメータ**があります。

Delphi の前身である **Turbo Pascal** は **ANSI Pascal** (ANSI/IEEE 770X3.97-1983) を参考にしているため、そもそも**水準 1** を言語仕様に含んでいないと思われます。

つまりは標準 Pascal 水準 1 から　標準 Pascal 水準 0 や ANSI Pascal へコードを移植する際にも同じ問題が発生するのですが、整合配列パラメータを積極的に使ったコードを見かけないので、問題が発生する事はまずないと思われます。

### ・標準 Pascal
水準 1 でサポートしています。

### ・Delphi
サポートしていません。

**See also:**

 - [11.1.2. 整合配列パラメータ (Conformant array parameters) - <11> 手続きと関数 (Qiita)](https://qiita.com/ht_deko/items/b93ac03bfee002f17137#1112-%E6%95%B4%E5%90%88%E9%85%8D%E5%88%97%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF-conformant-array-parameters)

## (13.) テキストファイルでの Write と Writeln

次のようなテキストファイルを操作するコードがあった場合、

```pascal
program EolnEofTest(Output, Dst);
var
  Dst: Text;
begin
{ AssignFile(Dst, 'DST.TXT'); // for Delphi }
  Rewrite(Dst);
  WriteLn(Dst, 'AAA');
  WriteLn(Dst, 'BBB');
  Write(Dst, 'CCC');
{ CloseFile(Dst); // for Delphi }
end.
```

次のような出力結果を期待しますが、

```
AAA[EOL]
BBB[EOL]
CCC[EOF]
```

標準 Pascal では不完全な行があった場合、行末が書き込まれるため、次のようなファイルが出力されます。

```
AAA[EOL]
BBB[EOL]
CCC[EOL]
[EOF]
```

### ・標準 Pascal
不完全な行は存在しません。

### ・Delphi
不完全な行が存在します。

:::note info
ISO 7185 / JIS X 3008 の 6.6.5.2. の reset() の説明がその仕様を表しているらしいのですが、私には理解できませんでした。
:::

## (14.) div と mod 演算子

標準 Pascal では、

```
div    除算し、小数点以下を切り捨てる (四捨五入はしない)。
mod    Remainder = A - (A div B) * B とすると、
       Remainder < 0 ならば、A mod B = Remainder + B
       そうでなければ、A mod B = Remainder を返す。
       B が 0 以下であれば、誤りである。
```

となっています。

なので、負数の **mod** の結果は標準 Pascal と Delphi では異なる事になります。

### ・標準 Pascal
```pascal
-31 mod 10 = 9
31 mod -10 = エラー
```

### ・Delphi
```pascal
-31 mod 10 = -1
31 mod -10 = 1
```

**See also:**

 - [剰余演算子について - <2> データの概念: 単純型 (標準 Pascal 範囲内での Delphi 入門)〔裏〕 (Qiita)](https://qiita.com/ht_deko/items/e36898d7aa4bb3300f78#%E5%89%B0%E4%BD%99%E6%BC%94%E7%AE%97%E5%AD%90%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)


# おわりに
古い書籍に載っている標準 Pascal のコードを Delphi で利用できるようになるので、Delphi と標準 Pascal の違いを知っておくのも悪くはないと思います。

 - サブセットである Pascal-P が由来の機能がある。
 - Delphi は`自分自身のルーチンを Delphi のコードで記述できる`という事を主眼に置いている。
 - Delphi には後継言語 (**Modula-2** 等) を参考にして導入された機能も多い。

このため、Delphi には標準 Pascal との非互換部分があるという感じですかね。それでも移植に困るような大きな問題はあまりないような気がします。


## Object Pascal Handbook
昨日、**『Object Pascal Handbook Delphi 10.4 Sydney Edition』** がリリースされました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/ec8bc11c-1c70-61b9-9c5b-93069026ca3f.png)

標準 Pascal の範囲にあるものについてはあまり詳しく書かれていませんが、Delphi で拡張された部分についてはこれ一冊で殆ど網羅されていると思います。英語版ですが、無償で DL できます。

 - [Object Pascal Handbook Delphi 10.4 Sydney Edition (Embarcadero)](https://lp.embarcadero.com/Object-Pascal-Handbook-2021)