はじめに
Wikipedia の英語版に『Comparison of Pascal and Delphi』って記事があるのですが、標準 Pascal の仕様 (規格) を知らないと、所々意味不明だと思うので、これをちょっと解説してみます。
See also:
- Comparison of Pascal and Delphi (Wikipedia)
- Turbo Pascal version 1.0, Delphi, and "Standard" Pascal (Internae Archive: blogs.embarcadero.com)
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:
- 11.1.4. 手続きパラメータ (Procedural parameters) - <11> 手続きと関数 (Qiita)
- 11.2.1. 関数パラメータ (Functional parameters) - <11> 手続きと関数 (Qiita)
- (11.4.) 手続き型 (Procedural Types) - <11> 手続きと関数 (Qiita)
- (6.4.4) 構造等価 (Structual Equivalence) と 名前等価 (Name Equivalence) - <6> 構造化型の概要と配列型 (Qiita)
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:
- (10.2.1.) New と Dispose の二番目の書式 - <10> ポインタ型 (Qiita)
- (10.2.1.) New と Dispose の二番目の書式 - <10> ポインタ型〔裏〕(Qiita)
5. Pack() / Unpack()
Pack() はパックされていない配列変数 (array) の内容をパックされている配列変数 (packed array) へコピーするものです。Unpack() はその逆を行うものです。
名前から受ける印象だと構造化型全般で使える手続きのようにも思えますが、実際には配列型専用の手続きです。
・標準 Pascal
サポートしています。
・Delphi
サポートしていません。パック状態を気にせずに Move()
関数でコピーすればいいと思います。
See also:
6. ブロックコメント {
}
と (*
*)
の併用
Pascal のブロックコメントは {
}
で、(*
*)
は {
}
の文字代替です。次のテストコードで考えてみます。
{
(* TEST *)
A := 1;
}
・標準 Pascal
{
と (*
、 }
と *)
は同じ文字として扱われるため、テストコードは正しく動作しません。次のように解釈されるからです。
A := 1;
}
・Delphi
テストコードはネストしたブロックコメントとして扱われます。
See also:
- コメント (Comment) - <1> 記法: トークンと区切り文字(Qiita)
- 1.2. 特殊シンボルと予約語 - <1> 記法: トークンと区切り文字(Qiita)
- C++Builder とトライグラフ (Qiita)
7. eoln() が True の場合の文字
解りにくい文章ですが、「Eoln()
が真の状態で Read()
によって文字が読み取られた場合には何を返すのか?」という話です。「Eoln()
が真の状態でバッファ変数が何を返すのか?」と同義です。
「空白を返す」というのは標準 Pascal 規格書の 6.4.3.5
に書かれています。
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.
...
6.4.3.5 ファイル型
...
列の成分以外の変数に値end-of-lineを与えたときに成立すると6.で規定する表明は,
文字型の値空白をその変数に与えたときの表明と同じとする。
...
次のテストコードで考えてみます。
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 と同じようにするには、次のようなコードにする必要があります。
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:
- (12.3.1.) 書き出しパラメータ (書き込みパラメータ / Write-parameters) - <12> テキストファイルの入出力 (Qiita)
- (11.1.6.) 型可変オープン配列パラメータ (Variant Open Array Parameters) - <11> 手続きと関数 (Qiita)
- 標準手続きと標準関数 - 付録 (Qiita)
- Format() (DocWiki)
(11.) Page()
Page()
手続きの効果は処理系定義なのですが、Delphi には実装されていないので、これも差異に含めていいのではないかと思います。
・標準 Pascal
サポートしています。
・Delphi
サポートしていません。
See also:
(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:
(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』 がリリースされました。
標準 Pascal の範囲にあるものについてはあまり詳しく書かれていませんが、Delphi で拡張された部分についてはこれ一冊で殆ど網羅されていると思います。英語版ですが、無償で DL できます。