7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DelphiAdvent Calendar 2020

Day 12

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

Last updated at Posted at 2020-12-11

はじめに

Wikipedia の英語版に『Comparison of Pascal and Delphi』って記事があるのですが、標準 Pascal の仕様 (規格) を知らないと、所々意味不明だと思うので、これをちょっと解説してみます。

See also:

Delphi と標準 Pascal の違い

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

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

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

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

・標準 Pascal

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

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

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

・Delphi

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

 type
   TTestProcedure = procedure (V: integer);
   TTestFunction = function (V: integer): Integer;
  procedure ProcParamTest(proc: TTestProcedure);
  begin
    ...
  end; { ProcParamTest }
  procedure FuncParamTest(func: TTestFunction);
  begin
    ...
  end; { FuncParamTest }

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

See also:

2. goto の扱い

どこまで飛べるか?というお話です。

・標準 Pascal

Extraprocedural gotos であり、ルーチンの外への大域ジャンプが可能です。

・Delphi

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

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

See also:

3. バッファ変数と Get() / Put()

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

・標準 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 文字読み込みます。

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

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

See also:

4. New() / Dispose() の二番目の書式

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

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

・標準 Pascal

サポートしています。

・Delphi

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

See also:

5. Pack() / Unpack()

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

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

・標準 Pascal

サポートしています。

・Delphi

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

See also:

6. ブロックコメント { }(* *) の併用

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

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

・標準 Pascal

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

  A := 1;
}

・Delphi

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

See also:

7. eoln() が True の場合の文字

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

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

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.
...

JISX3008
6.4.3.5 ファイル型

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

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

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 と同じようにするには、次のようなコードにする必要があります。

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:

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

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

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

・標準 Pascal

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

       1234
      -1234
 True
False

・Delphi

Delphi での実行結果です。

1234
-1234
TRUE
FALSE

See also:

9. 内部ファイル

標準 Pascal にはファイル型があります。これは一般的に言うファイルと関連付けて使うこともできますし、関連付けずに使う事もできます。Wikipedia で言っているのは、後者の内部ファイル (一時ファイル) の事です。

・標準 Pascal

サポートしています。

・Delphi

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

See also:

10. RTL

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

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

・標準 Pascal

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

・Delphi

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

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

See also:

(11.) Page()

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

・標準 Pascal

サポートしています。

・Delphi

サポートしていません。

See also:

(12.) 整合配列パラメータ

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

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

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

・標準 Pascal

水準 1 でサポートしています。

・Delphi

サポートしていません。

See also:

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

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

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

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

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

-31 mod 10 = 9
31 mod -10 = エラー

・Delphi

-31 mod 10 = -1
31 mod -10 = 1

See also:

おわりに

古い書籍に載っている標準 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

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

7
0
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
7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?