From 98682c7a8792e7e79e487fea5024d25cee5aa310 Mon Sep 17 00:00:00 2001
From: nsfisis contained unnecessary whitespaces inside it
---
.../index.html | 102 +++++++--------------
1 file changed, 34 insertions(+), 68 deletions(-)
(limited to 'public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line')
diff --git a/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html b/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html
index abdb48e..f7ce047 100644
--- a/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html
+++ b/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html
@@ -125,8 +125,7 @@
特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。
- #\
+ #\
i\
n\
c\
@@ -199,8 +198,7 @@
10
);
- /* あとは同じように普通のプログラムを変形するだけなので省略 */
-
+ /* あとは同じように普通のプログラムを変形するだけなので省略 */
バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。
@@ -260,12 +258,10 @@
また、2文字だと文字列がまともに書けないのも辛い。''だけで 2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので
- $a
+ $a
='
a'
-;;
-
+;;
とすると$aは"\na"になるのだが、余計な改行が入ってしまう。
@@ -284,13 +280,11 @@ a'
まずは普通に書くとしよう。
- <?php
+ <?php
for ($i = 1; $i < 100; $i++) {
echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n";
- }
-
+ }
素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。
@@ -303,16 +297,14 @@ a'
forは、3文字もある長いキーワードである。こんなものは使えない。array_系の関数を使って、適当に置き換えるとしよう。
- <?php
+ <?php
$s = range(1, 100);
array_walk(
$s,
fn($i) =>
printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
- );
-
+ );
array_walkやrange、printfといったforよりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、echoは文 (statement) であり式 (expression) ではないので、式であるprintfに置き換えた。
@@ -325,8 +317,7 @@ a'
range、array_walk、printfは長すぎるのでどうにかせねばならない。ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。
- <?php
+ <?php
$r = 'range';
$w = 'array_walk';
@@ -337,8 +328,7 @@ a'
$s,
fn($i) =>
$p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
- );
-
+ );
これで関数を呼び出している所は短くなった。では、$rや$wや$p、また'Fizz'や'Buzz'はどうやって 1行2文字に収めるのか。次のテクニックへ移ろう。
@@ -365,21 +355,18 @@ a'
というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、Fizzという文字列が欲しければ、次のようにする。
- $f
+ $f
=F
.i
.z
.z
-;;
-
+;;
こうして簡単に文字列を作れる。なお、この仕様は 7.x 時点でも警告を受けるので、@演算子を使って抑制してやるとよい。
- $f
+ $f
=@
F.
@i
@@ -387,8 +374,7 @@ F.
@z
.#
@z
-;;
-
+;;
むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。
@@ -405,8 +391,7 @@ F.
ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 (&、|、^) をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。
- $a = "12345";
+ $a = "12345";
$b = "world";
// $a ^ $b は次のコードと同じ
@@ -416,26 +401,22 @@ $result .= $a[$i] ^ $b[$i];
}
echo $result;
-// => F]AXQ
-
+// => F]AXQ
これを踏まえ、次のコードを見てみよう。
-
- $x = "x\nOm\n";
+ $x = "x\nOm\n";
$y = "\nk!\no";
$r = $x ^ $y;
-echo "$r\n";
-
+echo "$r\n";
実行すると、rangeが表示される。さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。書きかえてみよう。
- $x
+ $x
='x
Om
';
@@ -446,15 +427,13 @@ o'
;
$r = $x ^ $y;
-echo "$r\n";
-
+echo "$r\n";
さらに#を使って適当に調整すると、次のようになる。
- $x
+ $x
=#
'x
Om
@@ -471,8 +450,7 @@ $x
$y
;#
-echo "$r\n";
-
+echo "$r\n";
1行あたり2文字で、rangeという文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。
@@ -490,8 +468,7 @@ echo "$r\n";
完成したものがこちら。
- <?php
+ <?php
$x
=#
@@ -639,8 +616,7 @@ echo "$r\n";
)#
.'
')
- );
-
+ );
shell_exec関数と等価である。さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。
-
- <?php
+ <?php
printf(`
e\
@@ -672,8 +647,7 @@ echo "$r\n";
1\
2\
3\
- `);
-
+ `);
なお、ここでは簡単のため出力にprintfをそのまま使っているが、実際にはprintfという文字列を合成して可変関数で呼び出す。
@@ -701,8 +675,7 @@ echo "$r\n";
もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。
- <?php
+ <?php
$c = 'chr';
@@ -730,33 +703,28 @@ ${
1\
2\
3\
-`);
-
+`);
先程と同じく、chrやprintfを生成する部分は長くなるので省いた。
- ${
+ ${
'_
-'}
-
+'}
は変数で、中にはスペースとエスケープが入っている (chr(32) . chr(92))。シェルに渡されている文字列は次のようになる。
- e\
+ e\
c\
h\
o\
\
1\
2\
-3\
-
+3\
これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。 @@ -770,11 +738,9 @@ o\ ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。
-
- ${
+ ${
'_
-'}
-
+'}
最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。 -- cgit v1.2.3-70-g09d2