#!/usr/bin/ruby
# copyright (c) 2001 ZnZ (Kazuhiro NISHIYAMA), all rights reserved.

require 'thread'

class SafeEval
  def initialize
    @code_q = Queue.new
    @result_q = Queue.new
    @binding = sandbox
    myfork
  end

  def push(code)
    myfork if @child.closed?
    @code_q.push code
  end

  def pop
    @result_q.pop
  end

  private
  def sandbox
    binding
  end

  def myfork
    Thread.critical = true
    if @child = IO.popen('-', 'r+')
      # parent
      Thread.critical = false
      @child.sync = true
      Thread.start do
        send_th = Thread.start do
          until @child.closed?
            begin
              @child.puts @code_q.pop
            rescue Exception
              @child.close
            end
          end
        end

        until @child.closed?
          begin
            n = @child.sysread(2).unpack('n')[0]
            m = @child.sysread n
            @result_q.push Marshal.load m
          rescue Exception
            @result_q.push $!
            @child.close
          end
        end

        send_th.kill
      end
    else
      # child
      (Thread.list - [Thread.current]).each do |th|
        th.kill
      end
      Thread.critical = false

      child_q = Queue.new

      Thread.start do
        result = nil
        while true
          result = child_q.pop
          begin
            result = Marshal.dump result
          rescue TypeError # can't dump
            result = Marshal.dump result.inspect
          rescue Exception
            result = Marshal.dump $!
          end
          print [result.size].pack('n'), result
        end
      end

      while code = gets
        Thread.start do
          begin
            child_q.push Thread.start{
              $SAFE = 4
              result = nil
              begin
                result = eval code, @binding
              rescue Exception
                result = $!
              end
            }.value
          rescue Exception
            child_q.push $!
          end
        end
      end

      exit!
    end
  end
end

if __FILE__ == $0
  e = SafeEval.new
  result = nil

  Thread.start do
    while true
      result = e.pop
      result = 'nil' if result.nil?
      puts "result=[#{result}]"
    end
  end

  while code = gets
    e.push(code)
  end
end

__END__

