An implementation of Erlang C Nodes in the Pony language, allowing you to seamlessly exchange messages between the BEAM (Erlang/Elixir) and Pony runtimes utilizing standard Erlang node communication protocols.
You can add this library as a dependency to your Pony project using Corral (the Pony package manager).
Inside your project folder, run:
corral add github.com/d-led/otp_pony_node
corral fetchThis downloads the source code into your project's local dependency directory (typically _corral/github_com_d-led_otp_pony_node/).
Since Corral does not run post-install build scripts for compiled native components, you must build the custom C wrapper in the dependency directory.
Run make inside the dependency directory. It dynamically resolves your local Erlang/OTP erl_interface path using erl and compiles the shared library:
make -C _corral/github_com_d-led_otp_pony_node CONFIG=releaseGenerate the Visual Studio project files and compile with MSBuild:
cd _corral\github_com_d-led_otp_pony_node
premake\windows\premake5 vs2022
msbuild build\windows\vs2022\otp_pony_node.sln /p:Configuration=ReleaseIn your .pony source files, specify the library paths to tell ponyc where to link the native wrapper and locate the Pony bindings:
// Link the compiled C wrapper shared library
use "path:_corral/github_com_d-led_otp_pony_node"
use "lib:otp_pony_node_c"
// Import the erl_interface Pony package
use "github_com_d-led_otp_pony_node/erl_interface_pony"This library features a structured, recursive algebraic representation of Erlang terms (ErlangTerm) utilizing Pony's finite recursive type aliases. This allows you to match Erlang terms safely using standard pattern matching:
use "erl_interface_pony"
actor PonyNode
let erl: EInterface
new create(env: Env) =>
erl = EInterface("pony", "secretcookie")
// Connect to Elixir/Erlang node
match erl.connect("demo@localhost")
| ConnectionFailed => env.out.print("Connection failed.")
| ConnectionSucceeded => env.out.print("Connected!")
end
receive_loop(env)
be receive_loop(env: Env) =>
// Receive message from the C-Node connection
match erl.receive_with_timeout(5000)
| ReceiveFailed => erl.disconnect()
| ReceiveTimedOut => erl.disconnect()
| let m: EMessage =>
// Decode the raw message buffer into a structured ErlangTerm
match ErlangTermDecoder.decode(m)
| let root: ErlangTuple =>
try
// Expecting a tuple format: {Pid, Binary}
let pid = root.elements(0)? as ErlangPid
let msg = root.elements(1)? as ErlangBinary
env.out.print("Received text: " + msg.value + " from " + pid.node)
// Send a reply
let r = EMessage.begin()
r.encode_tuple_header(3)
r.encode_atom("reply")
r.encode_binary("hello from Pony!")
r.encode_pid(erl.self_pid())
erl.send_with_timeout(pid, r, 500)
end
end
end
receive_loop(env)If you are developing this library locally:
- Erlang/OTP (with
erl_interfaceheaders/libraries installed). - Elixir (needed for integration tests).
- Pony (installed via
ponyup).
To build and execute unit and integration tests locally on macOS/Linux:
# Compile C and Pony binaries
./build.sh
# Run unit tests and distributed node demo
./test.sh