Delphi のワンライナーとは
勢いで「ワンライナー自慢大会」なんて Advent Calendar を作ったは良い物の、そもそも Delphi ではワンライナーらしいワンライナー書けなくない!?という…
僕の中での「ワンライナーらしいワンライナー」は単純に1行で実行できる、という訳では無くコンソールから起動したいという思いがあって、そうするとコンパイル言語である Object Pascal はそれができない訳です。
ということで、まずはコンソールからコンパイラを呼び出すプログラムを作りました。
それが dcce です。
dcce
ソースコードはこう!
program dcce;
{$APPTYPE CONSOLE}
{$RTTI EXPLICIT METHODS([]) FIELDS([]) PROPERTIES([])}
{$WEAKLINKRTTI ON}
uses
System.SysUtils, System.IOUtils, Winapi.Windows;
function GetReg(const iKey: HKEY; const iAddress, iValue: String): String;
begin
var hReg: HKEY := 0;
var Ret := RegOpenKeyEx(iKey, PChar(iAddress), 0, KEY_QUERY_VALUE, hReg);
try
if Ret = ERROR_SUCCESS then
begin
SetLength(Result, $ff);
var RegType: DWORD := REG_SZ;
var VarSize: DWORD := Length(Result);
Ret :=
RegQueryValueEx(
hReg,
PChar(iValue),
nil,
@RegType,
PByte(PChar(Result)),
@VarSize
);
if Ret = ERROR_SUCCESS then
begin
SetLength(Result, VarSize div SizeOf(Char));
Result := Result.Trim;
end
else
Result := '';
end;
finally
RegCloseKey(hReg);
end;
end;
procedure Exec(const iCmd: String; const iNeedOutput: Boolean);
var
SI: TStartUpInfo;
PI: TProcessInformation;
begin
ZeroMemory(@SI, SizeOf(SI));
with SI do
begin
cb := SizeOf(SI);
if not iNeedOutput then
dwFlags := STARTF_USESTDHANDLES;
end;
if CreateProcess(nil, PChar(iCmd), nil, nil, True, 0, nil, nil, SI, PI) then
with PI do
try
while WaitForSingleObject(PI.hProcess, 100) = WAIT_TIMEOUT do
;
finally
CloseHandle(hProcess);
CloseHandle(hThread);
end;
end;
begin
var DprPath := TPath.ChangeExtension(TPath.GetTempFileName, '.dpr');
var ExePath := TPath.ChangeExtension(DprPath, '.exe');
try
var Writer := TFile.CreateText(DprPath);
try
Writer.Write('begin ');
for var i := 1 to ParamCount do
Writer.Write(ParamStr(i) + ' ');
Writer.Write(' end.');
finally
Writer.DisposeOf;
end;
Exec(
TPath.Combine(
GetReg(HKEY_CURRENT_USER, 'Software\Embarcadero\BDS\20.0', 'RootDir'),
'bin\dcc32.exe -Q -CC '
) + DprPath,
False);
Exec(ExePath, True);
finally
if TFile.Exists(DprPath) then
TFile.Delete(DprPath);
if TFile.Exists(ExePath) then
TFile.Delete(ExePath);
end;
end.
やっていることは単純で、
- コマンドライン引数をテンポラリファイルに保存する。その際 begin end. で挟む
- レジストリから Delphi 10.3 Rio の Win32 コンパイラ dcc32.exe のパスを取得する
- dcc32 に 1 で保存したファイルを渡す
- できあがった exe を実行する
- テンポラリファイルと exe ファイルを削除する
と、コレだけです。
ちなみに dcc32 に渡している引数の -Q はサイレントコンパイル(不要な情報が出力されなくなる), -CC はコンパイル対象がコンソール向け、という事を示します。
-CC を指定しないと GUI ターゲットとなり起動に失敗します。
実行するとこんな感じです。
さすが Delphi !まるでインタプリタみたいな速度で動く!
ワンライナー FizzBuzz
と dcce なんて作っていたせいで当初予定していたワンライン BrainF*ck が完成しませんでした…
ということで、ワンライン FizzBuzz でお茶を濁すことに…
それがコレです。
for var i := 1 to 100 do Writeln((function(const M: array of String): String begin Result := M[Ord(i mod 3 = 0) or Ord(i mod 5 = 0) * 2] end)([(function(N: Integer): String begin Str(N, Result) end)(i), 'Fizz','Buzz','FizzBuzz']))
上のコードを読みやすくしたものがコレです。
for var i := 1 to 100 do
Writeln(
(
function(const S: array of String): String
begin
Result := S[Ord(i mod 3 = 0) or Ord(i mod 5 = 0) * 2]
end
)([
(
function(N: Integer): String
begin
Str(N, Result)
end
)(i),
'Fizz',
'Buzz',
'FizzBuzz'
])
)
Object Pascal でのワンライナーは基本的にセミコロンレスと同じかなと思っています。
ということで、無名関数をガッツリ使っています。
もちろんセミコロンを使えないので uses も無し。System ユニットの関数だけで作ります。
Str 手続きとか久しぶりに使った。
ただ Delphi 10.3 Rio からは Pascal 伝統の var ブロック無しでインライン変数宣言と型推論が使えるようになったためセミコロンレスが以前よりは手軽に実現できています。
まとめ
勢いで Advent Calendar 作っちゃダメ!
おまけ
もしも、この記事で Delphi が気になった方は、商用版と同じ機能が使える完全無料の Delphi 10.3 Rio Community Edition を使ってみて下さい!
無料で Windows, macOS, Android, iOS の開発ができるよ!