RubyによるBrainf**k処理系を1行で!

# 概要

Rubyで一行で書くという縛りで、Brainf**kの処理系を書いてみました！
n番煎じの極みのような気がしないでもないですが、まあゆるしてちょ。
もっと改善できる余地があるかもしれないですが、もう見たくないです。

※ 作業環境のRubyのバージョンは以下のとおりです。
Ruby入ってなかったので、とりあえずその時の最新版入れました。
```bash
$ ruby -v
ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-linux]
```

# 結果
### 1行版
```Ruby:brainfook.rb
require('io/console');require('optparse');->o,s:((o['s'].nil?)?((!o['f'].nil?)?(File.read(o['f'])):gets(nil)):o['s']),step:o['step']{->t:'><+-.,[]'.split('').zip([*1..8]).to_h,b:(s.split(/\R/).join.split('').each_with_index.reduce([[0,0,[0]]]){|a,(c,i)|(!t[c].nil?)?((!step)?(((t[c]<5)?((a.last[0]==t[c]?a.push([a.last[0],a.last[1]+=1,a.pop[2]<<i]):a.push([t[c],1,[i]]))):a.push([t[c],0,[i]]))):a.push([t[c],1,[i]])):a}),m:(b.each_with_index.reduce([[],{}]){|a,(e,i)|(e[0]==7||e[0]==8)?(e[0]==7?a[0].push(i)&&a:(a[1][i]=a[0].last)&&(a[1][a[0].pop]=i+1)&&a):a}[1]),k:0,n:[],g:->{(n[k].nil?)?n[k]=0:n[k]},u:->m{n[k]=m&0xFF},c:-1,nc:0,uk:->q{(k=q)&&g.call},sib:'',st:->{($stdin.isatty)?($stdin.raw{|o|->l{l==3?exit(0):l}.call(o.readbyte)}):(!sib.nil?)?((sib.empty?)?(((sib=gets)&&sib.slice!(0)&.ord)||0):sib.slice!(0).ord):0},sb:[],stp:0{->f{(f[b[c][0]].call(b[c][1])&&step&&->{print("\033[2J\033[0;0H#{'+-'*10}\nPTR: #{k}, PC: #{c}, STP: #{stp}\n\nCELL(#{n.size}): [#{k}]=>(#{g.call},#{g.call.chr.dump})\n[#{n.map.with_index{|v,i|(i==k)?"\e[42m#{v}\e[0m":v}.join('|')}]\n#{'+-'*10}\n\n:::PROGRAM:::\n#{[s.split(/\R/).each_with_index.reduce([-1,0,[]]){|a,(v,i)|a[2].push("#{"\e[32m% 3d\e[0m"%i}: #{v.split('').map.with_index{|h,j|((a[0]+=1)==b[c][2].first)?((a[1]=([i-5,[i+5,10].max]))&&"\e[41m#{h}\e[0m"):h}.join}")&&a}].map{|v|v[2].slice([[v[1][0],v[2].size-11].min,0].max..[v[1][1],v[2].size].min)}.flatten.join("\n")}\n#{':'*12}\n\n:::OUT:::\n#{sb.join.lines.last(16).join}\n")||st.call}.call)while(((c=nc)&&(nc+=1))&&0<=c&&c<=b.size-1&&(stp+=1))}.call([->_{1},->q{uk.call(k+q)},->q{uk.call(k-q)},->q{u.call(g.call+q)},->q{u.call(g.call-q)},->_{print(g.call.chr)||sb<<g.call.chr},->_{u.call(st.call)},->_{(g.call==0&&!m[c].nil?)?nc=m[c]:c},->_{(g.call!=0&&!m[c].nil?)?nc=m[c]:c}])}.call}.call(ARGV.getopts('f:','s:','step'))
```
1825文字です！

### 全体が見やすいように改行入れた版
(構文上おかしくなさそうな位置で適当に改行を入れていますが、確認してないので(←しろよ)、たぶん動かないです)
```Ruby
require('io/console');require('optparse');->o,s:((o['s'].nil?)?((!o['f'].nil?)?
(File.read(o['f'])):gets(nil)):o['s']),step:o['step']{->t:'><+-.,[]'.split('').zip([*1..8]).to_h,b:
(s.split(/\R/).join.split('').each_with_index.reduce([[0,0,[0]]]){|a,(c,i)|(!t[c].nil?)?((!step)?
(((t[c]<5)?((a.last[0]==t[c]?a.push([a.last[0],a.last[1]+=1,a.pop[2]<<i]):a.push([t[c],1,[i]]))):a.push([t[c],0,[i]]))):a.push([t[c],1,[i]])):a}),
m:(b.each_with_index.reduce([[],{}]){|a,(e,i)|(e[0]==7||e[0]==8)?(e[0]==7?a[0].push(i)&&a:(a[1][i]=a[0].last)&&(a[1][a[0].pop]=i+1)&&a):a}[1]),
k:0,n:[],g:->{(n[k].nil?)?n[k]=0:n[k]},u:->m{n[k]=m&0xFF},c:-1,nc:0,uk:->q{(k=q)&&g.call},sib:'',
st:->{($stdin.isatty)?($stdin.raw{|o|->l{l==3?exit(0):l}.call(o.readbyte)}):(!sib.nil?)?((sib.empty?)?(((sib=gets)&&sib.slice!(0)&.ord)||0):sib.slice!(0).ord):0},sb:[],
stp:0{->f{(f[b[c][0]].call(b[c][1])&&step&&->{print("\033[2J\033[0;0H#{'+-'*10}\nPTR: #{k}, PC: #{c}, STP: #{stp}\n
\nCELL(#{n.size}): [#{k}]=>(#{g.call},#{g.call.chr.dump})\n[#{n.map.with_index{|v,i|(i==k)?"\e[42m#{v}\e[0m":v}.join('|')}]\n
#{'+-'*10}\n\n:::PROGRAM:::\n#{[s.split(/\R/).each_with_index.reduce([-1,0,[]]){|a,(v,i)|a[2].push("#{"\e[32m% 3d\e[0m"%i}: 
#{v.split('').map.with_index{|h,j|((a[0]+=1)==b[c][2].first)?((a[1]=([i-5,[i+5,10].max]))&&"\e[41m#{h}\e[0m"):h}.join}")&&a}]
.map{|v|v[2].slice([[v[1][0],v[2].size-11].min,0].max..[v[1][1],v[2].size].min)}.flatten.join("\n")}\n#{':'*12}\n\n:::OUT:::\n#{sb.join.lines.last(16).join}\n")||st.call}.call
)while(((c=nc)&&(nc+=1))&&0<=c&&c<=b.size-1&&(stp+=1))}
.call([->_{1},->q{uk.call(k+q)},->q{uk.call(k-q)},->q{u.call(g.call+q)},
->q{u.call(g.call-q)},->_{print(g.call.chr)||sb<<g.call.chr},->_{u.call(st.call)},
->_{(g.call==0&&!m[c].nil?)?nc=m[c]:c},->_{(g.call!=0&&!m[c].nil?)?nc=m[c]:c}])}.call}.call(ARGV.getopts('f:','s:','step'))
```

## 使い方

### ファイルから実行
`"Hello, world!\n"`と出力するだけの簡単なBFコードを用意します。

```brainfuck:helloworld.bf
++++++++++++[-
>++++++
>++++++++
>+++++++++
>+++++++++
>+++
>++
>+++++++++
>+++++++++
>++++++++
>++
<<<<<<<<<<]
>.
>+++++.
>..
>+++.
>++++++++.
>++++++++.
>+++++++++++.
<<<.
>>>>++++++.
<<<<<.
>>>>>>++++.
>+++++++++.
>++++++++++.
```

#### そのまま実行
```bash
$ ruby brainfook.rb -f helloworld.bf
Hello, world!

or

$ cat helloworld.rb | ruby brainfook.rb
Hello, world!
```

#### なにげについてるステップ実行
キー入力のたびに1ステップ進めます(Ctrl + Cで途中終了)。
```bash
$ ruby brainfook.rb --step -f helloworld.bf
```

![brainfook_step_compressed.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2901322/d12619ed-533f-c25d-0dd2-a2a1c7e29af9.gif)


### コードを直接渡して実行/パイプ(標準入力)も地味に対応

これで他のコマンドの実行結果を受け取って、Brainf**kで処理するという宇宙一素晴しいこともできます！

```bash
$ echo "Gdkkn Rsdud" | ruby brainfook.rb -s "+[>,+.->+<[>-<[-]]>[<<->>-]<<]"
Hello!Steve
```

# 縛り詳細

せっかくワンライナーなので、もっと~~苦しむ~~楽しむためになんとなく以下の縛りで書きました。
ワンライナーチャレンジされる場合はご参考にどうぞ！
- セミコロン禁止！
    - `a=1;b=2;c=a+b;print(c);` みたいな改行をただ置き替えたみたいな、軟弱な書き方ができるのでワンライナーではいらんのや！
    - ただし、requireは例外。ゆるして。
- if式,case式禁止！ (三項演算子はOK)
    - `case(c)when('+')then(p+=1)when('-')then(p-=1)end` みたいな軟弱な書き方ができるので、ワンライナーではいらんのや！
- 実装中もエンターキーを押さない!
    - ワンライナーでは(ry
- 変数名などのメモ禁止!
    - ワ(ry

# 解説

Brainf**kの言語仕様などは数多のサイトで解説されているので、とりあえずwikipedia貼っておきます。

https://ja.wikipedia.org/wiki/Brainfuck


```Ruby:コメント入れたコード
require("io/console")
require("optparse")

# ↓ オプションの処理部
->o, s: ((o["s"].nil?) ? ((!o["f"].nil?) ? (File.read(o["f"])) : gets(nil)) : o["s"]), step: o["step"] {
  # ↓ コード実行前処理部
  ->t: "><+-.,[]".split("").zip([*1..8]).to_h,
    # ↓ BFのソースコードを最適化(連続する+-><をまとめるだけ。ステップ実行時は最適化なし),不明な文字は無視して実行用の配列に変換
    b: (s.split(/\R/).join.split("").each_with_index.reduce([[0, 0, [0]]]) { |a, (c, i)| (!t[c].nil?) ? ((!step) ? (((t[c] < 5) ? ((a.last[0] == t[c] ? a.push([a.last[0], a.last[1] += 1, a.pop[2] << i]) : a.push([t[c], 1, [i]]))) : a.push([t[c], 0, [i]]))) : a.push([t[c], 1, [i]])) : a }),
    # ↓ 対応する '[' と ']' の位置をあらかじめ計算してHashで持っておく 
    m: (b.each_with_index.reduce([[], {}]) { |a, (e, i)| (e[0] == 7 || e[0] == 8) ? (e[0] == 7 ? a[0].push(i) && a : (a[1][i] = a[0].last) && (a[1][a[0].pop] = i + 1) && a) : a }[1]),
    # ↓ ポインタ
    k: 0,
    # ↓ Cell用の配列
    n: [],
    # ↓ ポインタの示すCellの値を取得
    g: -> { (n[k].nil?) ? n[k] = 0 : n[k] },
    # ↓ ポインタの示すCellの値を更新(mを0xFFで積してるので最大255、マイナス時は256から巻きもどる)
    u: ->m { n[k] = m & 0xFF },
    # ↓ プログラムカウンタ
    c: -1,
    # ↓ 次のプログラムカウンタ値
    nc: 0,
    # ↓ ポインタの値をqで更新
    uk: ->q { (k = q) && g.call },
    # ↓ stdin buffer (pipe時用)
    sib: "",
    # ↓ stdinから一文字得る
    st: -> { ($stdin.isatty) ? ($stdin.raw { |o| ->l { l == 3 ? exit(0) : l }.call(o.readbyte) }) : (!sib.nil?) ? ((sib.empty?) ? (((sib = gets) && sib.slice!(0)&.ord) || 0) : sib.slice!(0).ord) : 0 },
    # ↓ step実行時用の出力を保持するbuffer
    sb: [],
    # ↓ ステップ数カウンタ
    stp: 0 {
    # ↓ コード実行部
    ->f {
      # ↓ コード実行
      (f[b[c][0]].call(b[c][1]) &&
        # ↓ ステップ実行モードのときのみ処理
        step && -> {
         # ↓ ステップ実行の表示
         print("\033[2J\033[0;0H\
               #{"+-" * 10}\n\
               PTR: #{k}, PC: #{c}, STP: #{stp}\n\n\
               CELL(#{n.size}): [#{k}]=>(#{g.call},#{g.call.chr.dump})\n[#{n.map.with_index { |v, i| (i == k) ? "\e[42m#{v}\e[0m" : v }.join("|")}]\n\
               #{"+-" * 10}\n\n\
               :::PROGRAM:::\n\
               #{[s.split(/\R/).each_with_index.reduce([-1, 0, []]) { |a, (v, i)| a[2].push("#{"\e[32m% 3d\e[0m" % i}: #{v.split("").map.with_index { |h, j| ((a[0] += 1) == b[c][2].first) ? ((a[1] = ([i - 5, [i + 5, 10].max])) && "\e[41m#{h}\e[0m") : h }.join}") && a }].map { |v| v[2].slice([[v[1][0], v[2].size - 11].min, 0].max..[v[1][1], v[2].size].min) }.flatten.join("\n")}\n\
               #{":" * 12}\n\n\
               :::OUT:::\n\
               #{sb.join.lines.last(16).join}\n") || st.call
       }.call) while (
         # ↓ プログラムカウンタの処理/終了判定/ステップ数加算
         ((c = nc) && (nc += 1)) && 0 <= c && c <= b.size - 1 && (stp += 1)
       )
    }.call([
      ->_ { 1 }, # ← nop
      ->q { uk.call(k + q) }, # ← > ポインタを+q
      ->q { uk.call(k - q) }, # ← < ポインタを-q
      ->q { u.call(g.call + q) }, # ← + ポインタの差すCellの値を+q
      ->q { u.call(g.call - q) }, # ← - ポインタの差すCellの値を-q
      ->_ { print(g.call.chr) || sb << g.call.chr }, # ← . ポインタの差すCellの値を画面に表示(ASCII)
      ->_ { u.call(st.call) }, # ← , 標準入力から1文字得て、ポインタの差すCellへ入れる
      ->_ { (g.call == 0 && !m[c].nil?) ? nc = m[c] : c }, # ← [ ポインタの差す値が0だったら対応する']'の後ろへプログラムカウンタを更新
      ->_ { (g.call != 0 && !m[c].nil?) ? nc = m[c] : c }, # ← ] ポインタの差す値が0でないなら対応する'['へプログラムカウンタを更新
    ])
  }.call
}.call(ARGV.getopts("f:", "s:", "step"))
```

## オプションの処理

一番外側のラムダの引数部では、デフォルト引数を悪用してオプションを処理しています。
Brainf**kのソースコードの文字列をsに、ステップ実行フラグをstepに入れて中の処理で使用しています。

オプションのパースには、Ruby標準ライブラリのこちらを使ってます→https://docs.ruby-lang.org/ja/latest/library/optparse.html
```Ruby
# ↓ オプションの処理部
->o, s: ((o["s"].nil?) ? ((!o["f"].nil?) ? (File.read(o["f"])) : gets(nil)) : o["s"]), step: o["step"] {
  # ...処理...
}.call(ARGV.getopts("f:", "s:", "step"))
```

## 実行前処理

前処理としてBFのコードを最適化したり、位置を事前計算したり、BF実行時に使うステートを用意しています。
```Ruby
  # ↓ コード実行前処理部
  ->t: "><+-.,[]".split("").zip([*1..8]).to_h,
    # ↓ BFのソースコードを最適化(連続する+-><をまとめるだけ。ステップ実行時は最適化なし),不明な文字は無視して実行用の配列に変換
    b: (s.split(/\R/).join.split("").each_with_index.reduce([[0, 0, [0]]]) { |a, (c, i)| (!t[c].nil?) ? ((!step) ? (((t[c] < 5) ? ((a.last[0] == t[c] ? a.push([a.last[0], a.last[1] += 1, a.pop[2] << i]) : a.push([t[c], 1, [i]]))) : a.push([t[c], 0, [i]]))) : a.push([t[c], 1, [i]])) : a }),
    # ↓ 対応する '[' と ']' の位置をあらかじめ計算してHashで持っておく 
    m: (b.each_with_index.reduce([[], {}]) { |a, (e, i)| (e[0] == 7 || e[0] == 8) ? (e[0] == 7 ? a[0].push(i) && a : (a[1][i] = a[0].last) && (a[1][a[0].pop] = i + 1) && a) : a }[1]),
    # ...中略...
    stp: 0 {
      # ...処理...
  }.call
```

### コードの最適化

```ruby
  ->t: "><+-.,[]".split("").zip([*1..8]).to_h,
    # ↓ BFのソースコードを最適化(連続する+-><をまとめるだけ。ステップ実行時は最適化なし),不明な文字は無視して実行用の配列に変換
    b: (s.split(/\R/).join.split("").each_with_index.reduce([[0, 0, [0]]]) { |a, (c, i)| (!t[c].nil?) ? ((!step) ? (((t[c] < 5) ? ((a.last[0] == t[c] ? a.push([a.last[0], a.last[1] += 1, a.pop[2] << i]) : a.push([t[c], 1, [i]]))) : a.push([t[c], 0, [i]]))) : a.push([t[c], 1, [i]])) : a }),
```

`t` は、`{">"=>1, "<"=>2, "+"=>3, "-"=>4, "."=>5, ","=>6, "["=>7, "]"=>8}` のハッシュを生成する処理です。後に出てきますが、各文字ごとの処理をラムダ関数の配列で持ってるので、その対応するインデックスに変換する用のハッシュです。

`b` は、コメントに書いてあるとおり、最適化などをもろもろしています。`s`に入ってる文字列を配列に変えてreduceでこねくりまわして、`[tから得られた番号,個数,[コード上の位置,...]]`の配列を得ます。いわゆるバイトコード的なやつで、これをもとにBFの各命令を実行しています。
(コード上の位置はステップ実行時にのみ使っていて、ステップ実行しないなら不要ですが、変えるのが億劫だったのでそのままです)
```ruby
t = "><+-.,[]".split("").zip([*1..8]).to_h
s = '++--[]>>><<'
step = false
b = (s.split(/\R/).join.split("").each_with_index.reduce([[0, 0, [0]]]) { |a, (c, i)| (!t[c].nil?) ? ((!step) ? (((t[c] < 5) ? ((a.last[0] == t[c] ? a.push([a.last[0], a.last[1] += 1, a.pop[2] << i]) : a.push([t[c], 1, [i]]))) : a.push([t[c], 0, [i]]))) : a.push([t[c], 1, [i]])) : a })

pp b
# 実行後↓
[[0, 0, [0]],
 [3, 2, [0, 1]],
 [4, 2, [2, 3]],
 [7, 0, [4]],
 [8, 0, [5]],
 [1, 3, [6, 7, 8]],
 [2, 2, [9, 10]]]

# step = true に変えて 実行後↓
[[0, 0, [0]],
 [3, 1, [0]],
 [3, 1, [1]],
 [4, 1, [2]],
 [4, 1, [3]],
 [7, 1, [4]],
 [8, 1, [5]],
 [1, 1, [6]],
 [1, 1, [7]],
 [1, 1, [8]],
 [2, 1, [9]],
 [2, 1, [10]]]
```

## コード実行

`f`には、.callで渡している、BFの各命令に対応する、ラムダ関数が配列で入っています。
その配列に、前処理で作った`b`の配列の、プログラムカウンタ`c`の位置に入ってる命令インデックスで、`f`の配列にアクセスして、そこに入ってるラムダ関数に個数を渡して命令を実行、をひたすらwhileでプログラムカウンタを進めつつ、ぶんまわしてるだけです。

```ruby
   # ↓ コード実行部
    ->f {
      # ↓ コード実行
      (f[b[c][0]].call(b[c][1]) &&
        # ↓ ステップ実行モードのときのみ処理
        step && -> {
            # ...ステップ実行時の処理 略...
       }.call) while (
         # ↓ プログラムカウンタの処理/終了判定/ステップ数加算
         ((c = nc) && (nc += 1)) && 0 <= c && c <= b.size - 1 && (stp += 1)
       )
    }.call([
      ->_ { 1 }, # ← nop
      ->q { uk.call(k + q) }, # ← > ポインタを+q
      ->q { uk.call(k - q) }, # ← < ポインタを-q
      ->q { u.call(g.call + q) }, # ← + ポインタの差すCellの値を+q
      ->q { u.call(g.call - q) }, # ← - ポインタの差すCellの値を-q
      ->_ { print(g.call.chr) || sb << g.call.chr }, # ← . ポインタの差すCellの値を画面に表示(ASCII)
      ->_ { u.call(st.call) }, # ← , 標準入力から1文字得て、ポインタの差すCellへ入れる
      ->_ { (g.call == 0 && !m[c].nil?) ? nc = m[c] : c }, # ← [ ポインタの差す値が0だったら対応する']'の後ろへプログラムカウンタを更新
      ->_ { (g.call != 0 && !m[c].nil?) ? nc = m[c] : c }, # ← ] ポインタの差す値が0でないなら対応する'['へプログラムカウンタを更新
    ])
```

# 感想

Brainf**kのコードのほうが簡単に見えるようになりました。あたまおかしくなったんかな。