Erlang and Ruby: Make things easier!

Posted by yrashk

Russian translation on Novemberain.com

While experimenting with Erluby (Ruby in Erlang) I got a feeling that I do not want to write specs in Erlang. RSpec is finally much better than missingbdd/erlang and I pesonally think that Ruby is a more expressive language.

That’s why I’ve though about some kind of bridge between erlang and ruby. I’ve googled for it and found Rebar and even its “secret” sources. How it works? It’s basically a JSON server implemented in Erlang. That’s basically nice, though it seems to be a deep allpha version and it does not support some Erlang-specific types like pids.

And I’ve decided to think more about how to make it better. I’ve resulted in throwing Rebar off and toying with the following idea: Ruby sends Erlang code to Erlang server and Erlang server returns Ruby code.

I’ve hacked Rebar runner a bit and here is what I’ve got:

 
 -module(erluby_bridge).

-export([start/0, handle/1, loop/1]).

%% parse
eval_erlang_expr(Expr) ->
    {ok, Tokens, _} = erl_scan:string(Expr),
    {ok, [Form]} = erl_parse:parse_exprs(Tokens),
    {value, Result, _} = erl_eval:expr(Form,[]),
    {ok, Result}.
encode_to_ruby(E) when is_list(E) -> list_to_binary("'" ++ E ++ "'");
encode_to_ruby(E) when is_binary(E) -> encode_to_ruby(binary_to_list(E));
encode_to_ruby(E) when is_integer(E) -> list_to_binary(integer_to_list(E));
encode_to_ruby(E) when is_float(E) -> list_to_binary(float_to_list(E));
encode_to_ruby(E) when is_pid(E) -> list_to_binary("ErlPid.new('" ++ pid_to_list(E) ++ "')").
%% server
start() ->
  {ok, LSock} = gen_tcp:listen(9900, [binary, {packet, 0}, {active, false}]),
  Pid = spawn(erluby_bridge,loop,[LSock]),
  register(erluby_bridge, Pid),
  Pid.

loop(LSock) ->
  {ok, Sock} = gen_tcp:accept(LSock),
  spawn(erluby_bridge, handle, [Sock]),
  loop(LSock).

handle(Sock) ->
  % read the request from the socket
  {ok, Bin} = gen_tcp:recv(Sock, 0),

  % call the function
  try
    {ok, Return} = eval_erlang_expr(binary_to_list(Bin)),
    % send the response
    gen_tcp:send(Sock, encode_to_ruby(Return))
  catch
    error:Err ->
      Stack = lists:flatten(io_lib:fwrite("~p", [{Err, erlang:get_stacktrace()}])),
      gen_tcp:send(Sock, encode_to_ruby(Stack));
    _:Ouch ->
     io:format("~p~n!!!",[Ouch])
  end,

  ok = gen_tcp:close(Sock).

and on Ruby side:

 
require 'rubygems'
gem 'activesupport' 
require 'active_support'
class Object
  def to_erl
    to_s
  end
end
class String
  def to_erl
    '"' + to_s + '"'
  end
end
class ErlPid
  def initialize(binary)
    @binary = binary
  end
  def to_erl
    'list_to_pid("' + @binary + '")'
  end
  def to_s
   @binary
  end
end

class Symbol
  def to_erl
    "'#{to_s}'" 
  end
end

class Erluby
  def initialize(host, port=9900, mod="erlang")
    @host, @port, @mod = host, port, mod
  end
  def __invoke__(sym,*args)
    sym = sym.to_s
    if args.empty? and sym.camelize == sym
      Erluby.new(@host,@port, sym.tableize.singularize)
    else
      erl_expr = "#{@mod}:#{sym}(#{args.collect(&:to_erl).join(',')})." 
      sock = TCPSocket.new(@host,@port)
      sock.write(erl_expr)
      res = sock.gets
      eval(res)
    end
  end
  def __send_message__(pid,msg)
    self.Erlang.__invoke__("send",pid,msg)
  end
  def method_missing(sym,*args)
    __invoke__(sym,*args)
  end
end

All this stuff is far away from being polished (it’s very hackish) but it allows me to play with Erlang from Ruby. Something like:

 
@erluby = Erluby.new("127.0.0.1",9900)
puts @erluby.Base64.encode("havanagila")
puts @erluby.self
@erluby.__send_message__(:main,3) # unless whereis(main) == undefined
 
Comments

Leave a response

  1. Scott FleckensteinMay 09, 2007 @ 09:41 AM

    I’m working on an alternative, taking a somewhat different approach than your solution here:

    http://nullstyle.com/2007/5/9/erlectricity-hi-ruby-i-m-erlang

    Cheers!