Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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.

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

  1. コマンドライン引数をテンポラリファイルに保存する。その際 begin end. で挟む
  2. レジストリから Delphi 10.3 Rio の Win32 コンパイラ dcc32.exe のパスを取得する
  3. dcc32 に 1 で保存したファイルを渡す
  4. できあがった exe を実行する
  5. テンポラリファイルと exe ファイルを削除する

と、コレだけです。

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

実行するとこんな感じです。
さすが Delphi !まるでインタプリタみたいな速度で動く!
dcce.gif

ワンライナー 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 ブロック無しでインライン変数宣言と型推論が使えるようになったためセミコロンレスが以前よりは手軽に実現できています。

実行した結果
image.png

fizzbuzz.gif

まとめ

勢いで Advent Calendar 作っちゃダメ!

おまけ

もしも、この記事で Delphi が気になった方は、商用版と同じ機能が使える完全無料の Delphi 10.3 Rio Community Edition を使ってみて下さい!
無料で Windows, macOS, Android, iOS の開発ができるよ!

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away