Rubyがブラウザで動くってことはRubyでプログラミング言語のインタプリタを作れればどんな言語でもブラウザで動かせるのでは...！？


## はじめに
Rubyはバージョン3.2からWebAssembly(WASM)に対応をしています。

詳しくは以下の記事をご覧ください。

https://qiita.com/getty104/items/67245fb24ad101a6ec97

RubyでWASMを触っているうちに、「Rubyがブラウザで動くってことはRubyでプログラミング言語のインタプリタを作れればどんな言語でもブラウザで動かせるのでは...！？」という当たり前なことに気づきました。
この記事では、ruby.wasmで簡単なプログラミング言語のインタプリタを作成した話を紹介します。
半分ネタ記事です。

## やること

今回は以下のようなステップでランタイムを作っていきます。

1. プログラミング言語の構文を定義し、それをパース、実行できるRubyのプログラムを実装する
2. ブラウザでプログラムを入力、結果を出力できるようにする

## プログラミング言語の構文を定義し、それをパース、実行できるRubyのプログラムを実装する

まずは、プログラミング言語の構文を定義し、それをパース、実行できるRubyのプログラムを実装します。
今回は簡単に作れるBrainf*ckのランタイムを実装します。

今回は以下のようにランタイムを実装してみました。

```ruby
class BrainfuckInterpreter
  def initialize
    @tape = Array.new(30000, 0)
    @pointer = 0
    @output = ""
  end

  def run(code)
    loop_stack = []
    code_ptr = 0

    while code_ptr < code.length
      case code[code_ptr]
      when ">"
        @pointer += 1
      when "<"
        @pointer -= 1
      when "+"
        @tape[@pointer] = (@tape[@pointer] + 1) % 256
      when "-"
        @tape[@pointer] = (@tape[@pointer] - 1) % 256
      when "."
        @output += @tape[@pointer].chr
      when ","
        @tape[@pointer] = $stdin.getbyte
      when "["
        if @tape[@pointer] == 0
          loop_level = 1
          while loop_level > 0
            code_ptr += 1
            if code[code_ptr] == "["
              loop_level += 1
            elsif code[code_ptr] == "]"
              loop_level -= 1
            end
          end
        else
          loop_stack.push(code_ptr)
        end
      when "]"
        if @tape[@pointer] == 0
          loop_stack.pop
        else
          code_ptr = loop_stack.last - 1
        end
      end

      code_ptr += 1
    end

    @output
  end
end
```

## ブラウザでプログラムを入力、結果を出力できるようにする

RubyでBrainf*ckは実行できるようになったので、次はブラウザでプログラムを入力、結果を出力できるようにします。
今回は、以前作成したruby.wasmの仮想DOMを使って実装していきます。

https://qiita.com/getty104/items/410448f7e7a20737c811

https://qiita.com/getty104/items/929af3742eb1dddacbaf

今回は以下のようなコードを実装します。

```ruby
state = {
  code: '>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.>>>++++++++[<++++>-]<.>>>++++++++++[<+++++++++>-]<---.<<<<.+++.------.--------.>>+.>++++++++++.',
  output: ''
}

actions = {
  update_code: -> (state, value) {
    state[:code] = value
  },
  run_code: ->(state, e) {
    e.preventDefault()
    interpreter = BrainfuckInterpreter.new
    output = interpreter.run(state[:code])
    state[:output] = output
  }
}

view = ->(state, actions) {
  eval DomParser.parse(<<-DOM)
    <div class="max-w-lg mx-auto bg-white p-4 rounded-lg shadow">
      <h1 class="text-2xl font-bold mb-4">Brainf*ck Interpreter</h1>
      <form onsubmit='{->(e) { actions[:run_code].call(state, e) }}'>
        <div class="code-container">
          <label for="code-input" class="block font-medium">Code:</label>
          <textarea
            id="code-input"
            class="code-input border border-gray-300 rounded-lg p-2 w-full h-40"
            oninput='{->(e) { actions[:update_code].call(state, e[:target][:value].to_s) }}'
          >
            {state[:code]}
          </textarea>
        </div>
        <div class="mt-4">
          <button
            type="submit"
            class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600"
          >Execute</button>
        </div>
      </form>
      <div class="mt-8">
        <h2 class="text-lg font-bold mb-4">Result:</h2>
        <div class="result-container">
          {state[:output]}
        </div>
      </div>
    </div>
  DOM
}

App.new(
  el: "#app",
  state:,
  view:,
  actions:
)
```

## 出来上がったもの

実際に実装したものをGitHub Actionsで公開しました。

https://getty104.github.io/ruby-brainfuck-interpreter/

以下のようにBrainf*ckを実行することができます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158501/dbcbee8f-ee8e-9f89-e5a1-7134d091de9c.gif)

リポジトリはこちらです。

https://github.com/getty104/ruby-brainfuck-interpreter

## まとめ
Rubyでインタプリタを作ればブラウザで動かせました！