221V 1 неделя назад
Сommit
afaa8ad784
3 измененных файлов с 253 добавлено и 0 удалено
  1. 43 0
      README.md
  2. 102 0
      porttest.erl
  3. 108 0
      porttest.zig

+ 43 - 0
README.md

@@ -0,0 +1,43 @@
+# erlang + Zig external port example
+
+```
+erl 25
+zig 0.13.0
+(l)ubuntu 22.04 lts
+
+
+
+$ zig build-exe porttest.zig -femit-bin=porttest
+
+$ printf '\x00\x05hello' | ./porttest | hexdump -C
+00000000  00 0c 48 65 6c 6c 6f 20  77 6f 72 6c 64 21        |..Hello world!|
+0000000e
+
+$ printf '\x00\x0b\x66\x61\x63\x74\x6f\x72\x69\x61\x6c\x20\x35' | ./porttest | hexdump -C
+00000000  00 03 31 32 30                                    |..120|
+00000005
+
+$ erl
+Erlang/OTP 25 [erts-13.2] [source] [64-bit] [smp:20:20] [ds:20:20:10] [async-threads:1] [jit:ns]
+
+Eshell V13.2  (abort with ^G)
+1> c(porttest).
+{ok,porttest}
+2> porttest:start_link().
+{ok,<0.93.0>}
+3> porttest:hello().
+"Hello world!"
+4> porttest:factorial(5).
+120
+5> porttest:factorial(20).
+2432902008176640000
+6> porttest:factorial(34).
+295232799039604140847618609643520000000
+7> porttest:factorial(35).
+{error,<<"input too large, max is 34">>}
+8> porttest:factorial(-5).
+{error,badarg}
+9> q().
+ok
+```
+

+ 102 - 0
porttest.erl

@@ -0,0 +1,102 @@
+-module(porttest).
+
+-behavior(gen_server).
+
+-export([
+  hello/0,
+  factorial/1
+]).
+
+-export([
+  start_link/0,
+  stop/0
+]).
+
+-export([
+  init/1,
+  handle_call/3,
+  handle_cast/2,
+  terminate/2
+]).
+
+-record(state, {port}).
+
+
+start_link() ->
+  gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+
+stop() ->
+  gen_server:call(?MODULE, stop).
+
+
+
+hello() ->
+  gen_server:call(?MODULE, hello).
+
+
+factorial(N) when is_integer(N), N >= 0 ->
+  case gen_server:call(?MODULE, {factorial, N}) of
+    {ok, Result} when is_integer(Result) -> Result;
+    {error, _} = Error -> Error
+  end;
+
+factorial(_) ->
+  {error, badarg}.
+
+
+%% helper
+parse_result("error:" ++ Rest) ->
+  {error, list_to_binary(Rest)};
+parse_result(Str) ->
+  case string:to_integer(Str) of
+    {Int, []} -> {ok, Int};
+     _ -> {error, badarg}
+  end.
+
+
+
+init([]) ->
+  Path = "./porttest", %% compiled zig program
+  Port = open_port({spawn, Path}, [binary, exit_status]),
+  {ok, #state{port = Port}}.
+
+
+handle_call(hello, _From, State) ->
+  Port = State#state.port,
+  Cmd = <<"hello">>,
+  port_command(Port, <<(byte_size(Cmd)):16, Cmd/binary>>),
+  receive
+    {Port, {data, <<_Len:16, Resp/binary>>}} ->
+      {reply, binary_to_list(Resp), State}
+  after 5000 ->
+    {reply, {error, timeout}, State}
+  end;
+
+
+handle_call({factorial, N}, _From, State) ->
+  Port = State#state.port,
+  BinCmd = <<"factorial ", (integer_to_binary(N))/binary>>,
+  port_command(Port, <<(byte_size(BinCmd)):16, BinCmd/binary>>),
+  receive
+    {Port, {data, <<_Len:16, Resp/binary>>}} ->
+      Reply = parse_result( binary_to_list(Resp) ),
+      {reply, Reply, State}
+    after 5000 ->
+      {reply, {error, timeout}, State}
+    end;
+
+
+handle_call(stop, _From, State) ->
+  Port = State#state.port,
+  port_close(Port),
+  {stop, normal, ok, State}.
+
+
+handle_cast(_Msg, State) ->
+  {noreply, State}.
+
+
+terminate(_Reason, _State) ->
+  ok.
+

+ 108 - 0
porttest.zig

@@ -0,0 +1,108 @@
+
+const std = @import("std");
+const mem = std.mem;
+const fmt = std.fmt;
+const debug = std.debug;
+
+
+// read n bytes or return err
+fn readExactly(reader: anytype, buffer: []u8) !void{
+  var have: usize = 0;
+  while(have < buffer.len){
+    const amt = try reader.read(buffer[have..]);
+    if(amt == 0){ return error.EndOfStream; }
+    have += amt;
+  }
+}
+
+
+pub fn main() !void{
+  const stdin = std.io.getStdIn().reader();
+  const stdout = std.io.getStdOut().writer();
+  
+  var buffer: [64]u8 = [_]u8{0} ** 64;
+  var len_bytes_buf: [2]u8 = [_]u8{0} ** 2;
+  var reply_buffer: [64]u8 = [_]u8{0} ** 64;
+  
+  while(true){
+    // read msg length (2 bytes, big-endian)
+    readExactly(stdin, &len_bytes_buf) catch |err|{
+      if(err == error.EndOfStream){ break; }
+      return err;
+    };
+    
+    const msg_len = @as(u16, len_bytes_buf[0]) << 8 | len_bytes_buf[1]; // convert to u16, big-endian
+    
+    //std.debug.print("Full buffer: ", .{});
+    //for(buffer[0..msg_len]) |b|{
+    //  std.debug.print("{c}", .{b});
+    //}
+    //std.debug.print("\n", .{});
+    
+    if(msg_len == 0 or msg_len > buffer.len){
+      try stdout.writeAll(&[2]u8{ 0, 0 });
+      continue;
+    }
+    
+    readExactly(stdin, buffer[0..msg_len]) catch |err|{ // read msg
+      if(err == error.EndOfStream){ break; }
+      return err;
+    };
+    //debug.print("RECV: '{s}'\n", .{buffer[0..msg_len]});
+    
+    var fbs = std.io.fixedBufferStream(&reply_buffer);
+    var writer = fbs.writer();
+    
+    
+    if(mem.eql(u8, buffer[0..msg_len], "hello")){
+      try writer.writeAll("Hello world!");
+    
+    }else if(mem.startsWith(u8, buffer[0..msg_len], "factorial ")){
+      const prefix_len = "factorial ".len;
+      const n_str = buffer[prefix_len..msg_len];
+      //std.debug.print("n_str = '{s}' ({} chars)\n", .{ n_str, n_str.len });
+      
+      const n = fmt.parseInt(isize, n_str, 10) catch {
+        //std.debug.print("error:PARSE ERROR for '{s}'\n", .{n_str});
+        try writer.writeAll("error:badarg");
+        //try writer.writeAll("0");
+        try sendResponse(stdout, fbs.getWritten());
+        continue;
+      };
+      
+      if(n < 0){
+        //try writer.writeAll("error:badarg");
+        //std.debug.print("n < 0 -> badarg\n", .{});
+        //try writer.writeAll("0");
+        try writer.writeAll("error:badarg: n < 0");
+      }else if(n > 34){
+        //std.debug.print("n > 34 -> too large\n", .{});
+        //try writer.writeAll("0");
+        try writer.writeAll("error:input too large, max is 34");
+      }else{
+        var result: u128 = 1;
+        var i = @as(u128, @intCast(n));
+        while (i > 1) : (i -= 1) result *= i;
+        //std.debug.print("factorial({d}) = {d}\n", .{n, result});
+        try fmt.formatInt(result, 10, .lower, .{}, writer);
+      }
+    
+    }else{
+      try writer.writeAll("error:unknown command");
+    }
+    
+    try sendResponse(stdout, fbs.getWritten());
+  }
+}
+
+
+fn sendResponse(writer: anytype, response: []const u8) !void{
+  const len: u16 = @intCast(response.len);
+  
+  //std.debug.print("SEND: len={d} (0x{X:0>4}) data='{s}'\n", .{ len, len, response });
+  
+  try writer.writeByte(@intCast((len >> 8) & 0xFF)); // high byte
+  try writer.writeByte(@intCast(len & 0xFF));        // low byte
+  try writer.writeAll(response);
+}
+