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
 

MissingBDD

Posted by yrashk

I’ve created a small umbrella project for BDD implementations I’m playing with (Common Lisp and Erlang by now).

Erlang BDD

Posted by yrashk

I’m definitely a BDD maniac. Recently I’ve created a quick-n-dirty Common Lisp BDD primitives and today I’ve hacked BDD primitives for Erlang. Just like Lisp version, it still lacks lots of BDD stuff, but only expectations. Here you are bdd.erl:

 
-module(bdd).

%%
%% Exported Functions
%%
-export([should/5,should_not/5,equal/2,be/2,raise/2,match/2,run/1]).

%%
%% Expectations
%%
should(Matcher,Args,Expr,File,Line) ->
    case apply(?MODULE,Matcher,[Args,Expr]) of
        false -> exit({expectation_not_met,{File,Line,Expr,should,Matcher,Args}});
        _ -> ok
    end.    

should_not(Matcher,Args,Expr,File,Line) ->
    case apply(?MODULE,Matcher,[Args,Expr]) of
        true -> exit({expectation_not_met,{File,Line,Expr,should_not,Matcher,Args}});
        _ -> ok
    end.    

%%
%% Matchers
%%

equal(Args,Expr) ->
  case Expr of
      Args -> true;
      _ -> false
  end.          

be([H|T],Expr) ->
  be(H,Expr,T);

be(Predicate, Expr) ->
  be(Predicate,Expr, []).  

be(Predicate,Expr,Args) ->
  Pred = list_to_atom("is_" ++ atom_to_list(Predicate)),
  case (catch apply(Pred,[Expr] ++ Args)) of
      {'EXIT',_} -> 
          % is it a bif?
          D = (catch apply(erlang,Pred,[Expr] ++ Args)),
          case (catch apply(erlang,Pred,[Expr] ++ Args)) of
              {'EXIT',_} ->
                  % last idea
                  apply(erlang,Predicate,[Expr] ++ Args);
              Result -> Result
          end;             
      Result -> Result
  end.    

raise(Args, Expr) ->
   case Expr of
        {'EXIT',{Args,_}} -> true;
        {'EXIT',Args} -> true;          
        _ -> false
   end.           

match(Args, Expr) ->
   not raise({badmatch,Expr},(catch Args=Expr)).        

run([M|Modules]) ->
    run(M) ++ run(Modules);

run([]) ->
    [];

run(M) ->
    Context = get_from_module(attributes,M,context),
    io:format("~s~n",[Context]),
    Fun = fun(Spec) ->
        case Spec of
            module_info -> skipped; 
            setup -> skipped;
            teardown -> skipped;
            context_setup -> skipped;
            context_teardown -> skipped;
            _ ->
            (catch M:setup()),
            io:format(" - ~s",[atom_to_list(Spec)]),
            case (catch apply(M,Spec,[])) of
                {'EXIT',{expectation_not_met,Value}} -> io:format(" (FAILED)~n",[]), {Spec,failed,Value};
                {'EXIT',Value} -> io:format(" (ERROR)~n",[]), {Spec,error,Value};
                _ -> (catch M:teardown()), io:format("~n",[]),{Spec,ok}
            end    
         end
    end,
    (catch M:context_setup()),
    R =lists:filter(fun(TestResult) -> not (TestResult =:= skipped) end, lists:map(fun(Item) -> hd(tuple_to_list(Item)) end,lists:keymap(Fun,1,M:module_info(exports)))),
    (catch M:context_teardown()),
    R.

get_from_module(Kind,Module,Name) ->
    {value, {Name, Val}} = lists:keysearch(Name,1,Module:module_info(Kind)),
    Val.
 

and bdd.hrl:

 
-define(bdd(Expr,Expectation,Matcher,Args),
        apply(bdd,Expectation,[Matcher,Args,(catch Expr),?FILE,?LINE])).
 

Now you can check various expectations:

 
test() ->
  ?bdd(1,should,equal,1),
  ?bdd(1,should_not,equal,2),
  ?bdd(1,should,be,integer),
  ?bdd(1,should_not,be,list),
  ?bdd(1,should,be,['>',0]),
  ?bdd(1,should,be,['>=',1]),
  ?bdd(1=2,should,raise,{badmatch,2}),
  ?bdd(1=1,should_not,raise,{badmatch,2}),
  ?bdd({ok,1+2},should,match,{ok,3}),
  ?bdd({error,-1},should_not,match,{ok,3}),
  ?bdd(#state{},should,be,[record,state]).
 

and create contexts with specifications as modules:

 
-module(empty_list_spec).
-context("Empty list").
%%
%% Include files
%%

-include("bdd.hrl").

%%
%% Exported Functions
%%
-export([setup/0,'should have zero length'/0]).

setup() ->
    put(empty_list,[]).

%%
%% Specifications
%%

'should have zero length'() ->
    ?bdd(length(get(empty_list)),should,equal,0).

%%
%% Local Functions
%%

 

Now just run it:

> bdd:run(empty_list_spec)
% or
> bdd:run([empty_list_spec])
Empty list
 - should have zero length
[{'should have zero length',ok}]

Just an Idea for Erlang

Posted by yrashk

I was using Erlang from time to time from 2000 or 2001. I’ve even done some commerical trading-related system in it. It’s definitely a good language for certain domains, and it has bright past and may be even brighter future. I was experimenting with some ideas in Erlang today, after long period of not using it and I should say that fairly speaking Erlang is far away from being a friendly language in terms of syntax.

What if Erlang could be considered a kind of “assembly” language for high-performance and distributed programming? What if we can use Lisp macro or Ruby metaprogramming capabilities to code generate Erlang? Taking in account the fact that Erlang code could be changed without a stop, why not generating source code on the fly and passing it to Erlang VM?

Ok, just as a nighty idea.