Erlang has two built-in interoperability mechanisms. One is distributed Erlang nodes and the other one is ports. Ports provide the basic Erlang mechanism for communication with the external world. They provide a byte-oriented interface to an external program. When a port has been created, Erlang can communicate with it by sending and receiving lists of bytes. This recipe cooks an Erlang port in python. Making it easy for Erlang to instantiate and use python objects. Like most simple port implementations it uses an external python program and lets Erlang communicate via standard input and write to standard output. Theoretically, the external program could be written in any programming language. This recipe is pretty abstract and you will have to implement your own encode and decoding scheme.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | import sys, os, struct, traceback
from cStringIO import StringIO
class ErlangPort(object):
PACK = '!h'
def __init__(self):
self._in = sys.stdin
self._out = sys.stdout
def recv(self):
buf = self._in.read(2)
if len(buf) ==2:
(sz,) = struct.unpack(self.PACK, buf)
return self._in.read(sz)
def send(self, what):
sz = len(what)
buf = struct.pack(self.PACK, sz)
self._out.write(buf)
return self._out.write(what)
def run(self):
buf = self.recv()
while buf:
try:
result = self.process(buf)
except:
result = traceback.format_exc()
self.send(result)
buf = self.recv()
class ErlangPortTest(ErlangPort):
cmds = (0,lambda x: x+2, lambda x: x*2)
def process(self, message):
fn,arg = struct.unpack('!BB', message)
res = self.cmds[fn](arg)
return struct.pack('!B', res)
class ErlangPyTest(ErlangPortTest):
class SandBox:
def process(self, message):
exec message
sandbox = SandBox()
def process(self, code):
try:
realout = sys.stdout
sys.stdout = StringIO()
self.sandbox.process(code)
result = sys.stdout.getvalue()
finally:
if sys.stdout: sys.stdout.close()
if realout: sys.stdout = realout
return result
if __name__ =='__main__':
import sys
try:
command = sys.argv[1]
if command == 'PortTest':
ErlangPortTest().run()
elif command =='pytest':
ErlangPyTest().run()
except IndexError:
print """
Usage:
First of all see the c Port section in the Erlang guide.
http://www.erlang.org/doc/tutorial/c_port.html#4
1. Start Erlang and compile the Erlang user guide example code:
http://www.erlang.org/doc/tutorial/complex1.erl
unix> erl
Erlang (BEAM) emulator version 4.9.1.2
Eshell V4.9.1.2 (abort with ^G)
1> c(complex1).
{ok,complex1}
3. Run the example.
2> complex1:start("python -u port.py PortTest").
<0.34.0>
3> complex1:foo(3).
4
4> complex1:bar(5).
10
5> complex1:stop().
stop
For more fun try.
6> c("c:\\tg\\python.erl").
:/tg/python.erl:42: Warning: variable 'Reason' is unused
{ok,python}
7> python:start("c:\\python25\\python.exe -u c:\\tg\\port.py pytest").
<0.38.0>
8> python:exec("import os")
9> python:exec("self.x = os.environ['PATH']").
* 2: syntax error before: python **
10> python:exec("import os").
[]
11> python:exec("self.x = os.environ['PATH']").
[]
12> python:exec("print self.x").
"H:\\PROGRA~1\\ERL55~1.5\\ERTS-5~1.5\\bin;H:\\PROGRA~1\\ERL55~1.5\\bin
m Files\\ActivePositionManager\\;C:\\WINNT\\system32;C:\\WINNT;C:\\WI
32\\Wbem;"
"""
|
First of all see the c Port section in the Erlang guide. http://www.erlang.org/doc/tutorial/c_port.html#4. Erlang is a great language for distributed programming especially if you use the OPT frame work, but python is nicer to program in. This class is my first of many Erlang/Python utility classes that will let me program in Python distributed nodes and yet manage them with Erlang code.
ErlangPort is the main base class in this recipe. For most cases all you will have to do is override process to return a string or a struct representation in network safe mode. Also remember to run python with the -u (unbuffered) flag set.
ErlangPortTest is just a pythonesque port of the c example given in the Erlang guide. You can find the Erlang example at http://www.erlang.org/doc/tutorial/complex1.erl
ErlangPyTest lets Erlang execute python code block by block. In this class I use a letter envelop idiom (Sandbox) to save Erlang form clobbering the port object. Be careful how you use it!
For This use python.erl
-module(python).
-export([start/1, stop/0, init/1]).
-export([exec/1]).
start(ExtPrg) ->
spawn(?MODULE, init, [ExtPrg]).
stop() ->
python ! stop.
exec(X) ->
call_port(X).
call_port(Msg) ->
python ! {call, self(), Msg},
receive
{python, Result} ->
Result
end.
init(ExtPrg) ->
register(python, self()),
process_flag(trap_exit, true),
Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
loop(Port).
loop(Port) ->
receive
{call, Caller, Msg} ->
Port ! {self(), {command, Msg}},
receive
{Port, {data, Data}} ->
Caller ! {python, Data}
end,
loop(Port);
stop ->
Port ! {self(), close},
receive
{Port, closed} ->
exit(normal)
end;
{'EXIT', Port, Reason} ->
exit(port_terminated)
end.