Delphi をワンライナー書けるっぽくする

# Delphi のワンライナーとは

勢いで「ワンライナー自慢大会」なんて Advent Calendar を作ったは良い物の、そもそも Delphi ではワンライナーらしいワンライナー書けなくない！？という…

僕の中での「ワンライナーらしいワンライナー」は単純に１行で実行できる、という訳では無くコンソールから起動したいという思いがあって、そうするとコンパイル言語である Object Pascal はそれができない訳です。

ということで、まずはコンソールからコンパイラを呼び出すプログラムを作りました。
それが dcce です。

# dcce

ソースコードはこう！

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

やっていることは単純で、

1. コマンドライン引数をテンポラリファイルに保存する。その際 begin end. で挟む
2. レジストリから Delphi 10.3 Rio の [Win32 コンパイラ dcc32.exe](http://docwiki.embarcadero.com/RADStudio/Rio/ja/DCC32.EXE_-_Delphi_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3_%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9) のパスを取得する
3. dcc32 に 1 で保存したファイルを渡す
4. できあがった exe を実行する
5. テンポラリファイルと exe ファイルを削除する

と、コレだけです。

ちなみに dcc32 に渡している引数の -Q はサイレントコンパイル（不要な情報が出力されなくなる）, -CC はコンパイル対象がコンソール向け、という事を示します。
-CC を指定しないと GUI ターゲットとなり起動に失敗します。

実行するとこんな感じです。
さすが Delphi ！まるで**インタプリタみたいな速度で動く！**
![dcce.gif](https://qiita-image-store.s3.amazonaws.com/0/12977/63a06c66-5b67-29b7-342c-eadcf730b910.gif)


# ワンライナー FizzBuzz

と dcce なんて作っていたせいで当初予定していたワンライン BrainF*ck が完成しませんでした…

ということで、ワンライン FizzBuzz でお茶を濁すことに…
それがコレです。

```pascal
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']))
```

上のコードを読みやすくしたものがコレです。

```pascal
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 でのワンライナーは基本的に[セミコロンレス](https://qiita.com/pik/items/f2770a0591ef1196663b)と同じかなと思っています。
ということで、無名関数をガッツリ使っています。
もちろんセミコロンを使えないので uses も無し。System ユニットの関数だけで作ります。
[Str 手続き](http://docwiki.embarcadero.com/Libraries/Rio/ja/System.Str)とか久しぶりに使った。

ただ Delphi 10.3 Rio からは Pascal 伝統の var ブロック無しで[インライン変数宣言と型推論が使えるようになった](https://qiita.com/pik/items/ba2d26a05df6b0d555e5#%E8%A8%80%E8%AA%9E%E3%81%AE%E5%A4%89%E6%9B%B4%E7%AE%87%E6%89%80)ためセミコロンレスが以前よりは手軽に実現できています。

実行した結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/12977/c6dde033-9150-6372-b9d7-8ce1c77bfc8b.png)

![fizzbuzz.gif](https://qiita-image-store.s3.amazonaws.com/0/12977/552a69b8-7147-3bec-60ed-a6df8f83086c.gif)


# まとめ
勢いで Advent Calendar 作っちゃダメ！

# おまけ
もしも、この記事で Delphi が気になった方は、商用版と同じ機能が使える完全無料の [Delphi 10.3 Rio Community Edition](https://www.embarcadero.com/jp/products/delphi/starter/free-download) を使ってみて下さい！
無料で Windows, macOS, Android, iOS の開発ができるよ！