Skip to content

Instantly share code, notes, and snippets.

@sugyan
Last active July 30, 2026 02:20
Show Gist options
  • Select an option

  • Save sugyan/f39cc0803f86c3be4cbd26a879b9a1cc to your computer and use it in GitHub Desktop.

Select an option

Save sugyan/f39cc0803f86c3be4cbd26a879b9a1cc to your computer and use it in GitHub Desktop.
punctuation-only Perl

記号だけで書く Perl (punctuation-only Perl)

ASCII の記号([[:punct:]] = !"#$%&'()*+,-./:;<=>?@[\]^_{|}~`)だけで書かれた、 実際に動く Perl プログラム集。英数字・空白は 1 文字も使っていません。

perl japh.pl        # Just another Perl hacker,
perl hello-ja.pl    # こんにちは、記号だけの世界!
perl fizzbuzz.pl    # FizzBuzz 1..100
perl sierpinski.pl  # シェルピンスキーのピラミッド (AA)
echo 'Hello' | perl rev.pl   # olleH  (バイト単位の反転なので ASCII 向き)

検証環境: perl 5.42.0 (darwin)。全プログラム perl -w で無警告。

仕掛け(4 つ)

1. 文字は記号のビット演算で作る

Perl の | ^ ~ は「両辺が文字列なら文字列のビット演算」になる。 記号のコードポイントを組み合わせれば任意のバイトが作れる。

('`'|'%')      # 0x60 | 0x25 = 0x65 -> 'e'
('^'^'.')      # 0x5e ^ 0x2e = 0x70 -> 'p'
('|'^'/')      # 0x7c ^ 0x2f = 0x53 -> 'S'
('`'^'@')      # 0x60 ^ 0x40 = 0x20 -> ' '
('!'^'.'^':')  # 3 つ XOR すると数字 '1' も作れる
(~('!'^'#'^'@'))   # ~ で 0x80 以上へ → UTF-8 の日本語も出せる

enc.pl / strenc.pl がこの変換を総当たりでやる。

2. 出力は「動的メソッド名」で

print と書けないので、グロブに対して文字列で組み立てたメソッド名を呼ぶ。 ハンドルへのメソッド呼び出しでは IO::File が自動ロードされる。

$;='STDOUT'; $:='print';   # 実際は上のビット演算で組む
*{$;}->$:('...');          # = print STDOUT '...'

&{'CORE::sprintf'}(...) のようにシンボリック参照で組み込み関数も呼べる (sprintf join chr ord length reverse substr uc warn などは可。 print sort eval require は不可)。

3. ループは正規表現のバックトラック

forwhile も書けない。そこでリテラル正規表現の中に (?{...}) を置き、 末尾の (?!)(常に失敗)でバックトラックを強制する。 $_ の 1 文字ごとに開始位置がずれて再試行されるので、 文字列長 = ループ回数になる。

$_ = &{'CORE::sprintf'}('%100s','');   # 100 個の空白 = 100 回まわす
/.(?{ ...ここが loop body... })(?!)/;

注意: perl 5.42 では実行時に組み立てたパターン内の (?{...})Eval-group not allowed at runtime で禁止される(use re 'eval' が必要)。 Acme::EyeDrops 的な ''=~('('.'?'.'{'. ... )もう通らないので、 (?{...}) はソースにリテラルで書き、中身だけを記号で構成している。

4. 変数と数値も記号で

  • 変数: $; $: $? $% $- $= $~ $" $@ $_ @_、 さらに ${'!!'}$_{'!'} で無限に増やせる
  • 数値: !'' が 1、$?-$? が 0、++$? でインクリメント、 大きい数は ('!'^'.'^':') などで "100" を作って数値文脈に置く
  • 条件分岐: ?: && ||

※ C/Python の -~$x == $x+1 は Perl では使えない~ が符号なしを返すため -~0-1.8e19 になる)。++ を使う。

ファイル

ファイル 中身
enc.pl 文字列 → 記号だけの式 に変換するライブラリ (E())
strenc.pl 上の CLI。perl strenc.pl 'print'
build.pl 骨組みを組んで下の 5 本を生成する。perl build.pl
japh.pl hello-ja.pl fizzbuzz.pl sierpinski.pl rev.pl 生成物(記号のみ)

build.pl は生成後に「英数字・空白が混ざっていないか」も検査する。

生成物の骨組み(英数字入りの読める版)

fizzbuzz.pl:

$; = 'STDOUT'; $: = 'print'; $" = '';
$_ = &{'CORE::sprintf'}('%100s', '');
/.(?{ $?++;
      $" .= ($? % 15 ? ($? % 3 ? ($? % 5 ? $? : 'Buzz') : 'Fizz') : 'FizzBuzz') . "\n"
   })(?!)/;
*{$;}->$:($");

sierpinski.pl:

$; = 'STDOUT'; $: = 'print';
$- = 16;            # 行数
$= = 32;            # 桁数
$@ = 0; $" = '';
$_ = &{'CORE::sprintf'}('%512s', '');
/.(?{ $~ = $? - $- + 1 + $%;                                  # x = col - rows + 1 + row
      $" .= (($~ < $@) || ($~ > $%) || ($~ & ($% - $~))) ? ' ' : '#';
      ($= <= ++$?) && ($? -= $?, ++$%, $" .= "\n")             # 行末で折り返す
   })(?!)/;
*{$;}->$:($");
$;=(('('^'{').('('^'|').('$'^'`').('/'^'`').('('^'}').('('^'|'));$:=(('+'^'[').(')'^'[').(')'|'@').('.'|'@').('('^'\\'));$"='';$_=&{(('#'^'`').('/'^'`').(')'^'{').('%'^'`').'::'.('('^'[').('+'^'[').(')'^'[').(')'|'@').('.'|'@').('('^'\\').('&'|'@'))}(('%'.('!'^'*'^':').('!'^'*'^';').('!'^'*'^';').('('^'[')),'');/.(?{$?++;$".=($?%(('!'^'*'^':').('!'^'('^'<'))?($?%('!'^'('^':')?($?%('!'^'('^'<')?$?:(('"'^'`').('('^']').(':'|'@').(':'|'@'))):(('&'^'`').(')'|'@').(':'|'@').(':'|'@'))):(('&'^'`').(')'|'@').(':'|'@').(':'|'@').('"'^'`').('('^']').(':'|'@').(':'|'@'))).('!'^'+')})(?!)/;*{$;}->$:($")
$;=(('('^'{').('('^'|').('$'^'`').('/'^'`').('('^'}').('('^'|'));$:=(('+'^'[').(')'^'[').(')'|'@').('.'|'@').('('^'\\'));*{$;}->$:((('*'^'`').('('^']').('('^'[').('('^'\\').('@'^'`').('!'|'@').('.'|'@').('/'|'@').('('^'\\').('('|'@').('%'|'@').(')'^'[').('@'^'`').('+'^'{').('%'|'@').(')'^'[').(','|'@').('@'^'`').('('|'@').('!'|'@').('#'|'@').('+'|'@').('%'|'@').(')'^'[').','.('!'^'+')))
$;=(('('^'{').('('^'|').('$'^'`').('/'^'`').('('^'}').('('^'|'));$:=(('+'^'[').(')'^'[').(')'|'@').('.'|'@').('('^'\\'));*{$;}->$:(((~('!'^'=')).(~'~').(~(','|'@')).(~('!'^'=')).(~'}').(~(','|'@')).(~('!'^'=')).(~'~').(~('('^'|')).(~('!'^'=')).(~'~').(~'^').(~('!'^'=')).(~'~').(~('+'^'{')).(~('!'^'=')).(~('!'|'^')).(~'~').(~('('^'?')).(~(')'^'~')).(~("'"|'@')).(~('!'^';')).(~('+'^'[')).(~('('^'`')).(~('!'^'=')).(~'~').(~'_').(~('!'^'=')).(~'~').(~('.'|'@')).(~('!'^'=')).(~'~').(~('*'^'{')).(~('!'^':')).(~("'"^'`')).(~(')'|'@')).(~('"'^':')).(~('*'|'@')).(~('('^'[')).(~('*'^':')).(~('#'^'`')).(~'~').('!'^'+')))
$;=(('('^'{').('('^'|').('$'^'`').('/'^'`').('('^'}').('('^'|'));$:=(('+'^'[').(')'^'[').(')'|'@').('.'|'@').('('^'\\'));$-=(('!'^'*'^':').('!'^'('^'?'));$==(('!'^'('^':').('!'^'('^';'));$@=$?-$?;$"='';$_=&{(('#'^'`').('/'^'`').(')'^'{').('%'^'`').'::'.('('^'[').('+'^'[').(')'^'[').(')'|'@').('.'|'@').('('^'\\').('&'|'@'))}(('%'.('!'^'('^'<').('!'^'*'^':').('!'^'('^';').('('^'[')),'');/.(?{$~=$?-$-+!''+$%;$".=(($~<$@)||($~>$%)||($~&($%-$~)))?('@'^'`'):'#';($=<=++$?)&&($?-=$?,++$%,$".=('!'^'+'))})(?!)/;*{$;}->$:($")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment