44
27

More than 1 year has passed since last update.

この記事誰得? 私しか得しないニッチな技術で記事投稿!

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

Last updated at Posted at 2023-06-14

はじめに

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

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

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

やること

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

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

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

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

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

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を使って実装していきます。

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

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で公開しました。

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

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

まとめ

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

44
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
27