Files
bikoret/talmudic_engine/src/interface/socket_server.pl
T
Scott KarpandClaude Opus 4.6 ea3a70633f Step 14: Connect components — chamishim karsay zahav
Three connections built and tested:
- Haskell Shoresh Engine via Erlang Port (JSON lines over stdin/stdout)
- Prolog Talmudic Engine via Unix domain socket (JSON over /tmp/shorashim/talmudic.sock)
- Erlang Sanhedrin via native BEAM calls

New: Server.hs (JSON-line server loop), json_bridge.pl + socket_server.pl
(Prolog socket interface), ShoreshEnginePort + TalmudicEngineSocket +
SanhedrinClient (Elixir GenServer wrappers). BereishitCycle now routes
unresolved cases through Talmudic Engine and Sanhedrin. All connections
gracefully degrade — pipeline falls back to L1 data when engines unavailable.

46 Elixir tests, 33 Haskell tests, 57 Prolog tests — 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:39:21 -04:00

146 lines
4.7 KiB
Prolog

%% socket_server.pl — Unix domain socket listener for Talmudic Engine.
%%
%% Construction §3.3: "JSON over Unix domain socket"
%% Step 14 (Architecture §4): "Connect components"
%%
%% Listens on a Unix domain socket, accepts JSON requests,
%% dispatches to api:process_request/2 via json_bridge,
%% returns JSON responses.
%%
%% Protocol: One JSON object per line (same as Haskell server).
%% Connection model: accept, handle one or more requests per
%% connection, close when client disconnects.
:- module(socket_server, [
serve/0,
serve/1,
stop_server/0
]).
:- use_module(library(socket)).
:- use_module(library(json)).
:- use_module(library(readutil)).
:- use_module('../interface/api').
:- use_module('../interface/json_bridge').
%% Default socket path — dev path; production uses /var/run/shorashim/talmudic.sock
default_socket_path('/tmp/shorashim/talmudic.sock').
%% Dynamic flag to control server loop
:- dynamic server_running/1.
%% ============================================================
%% SERVER ENTRY POINTS
%% ============================================================
%% serve/0 — Start server on default socket path
serve :-
default_socket_path(Path),
serve(Path).
%% serve/1 — Start server on given socket path
serve(Path) :-
%% Clean up any stale socket file
( exists_file(Path)
-> delete_file(Path)
; true
),
%% Ensure parent directory exists
file_directory_name(Path, Dir),
( exists_directory(Dir)
-> true
; make_directory_path(Dir)
),
%% Create Unix domain socket
unix_domain_socket(ServerSocket),
tcp_bind(ServerSocket, Path),
tcp_listen(ServerSocket, 5),
format('Talmudic Engine listening on ~w~n', [Path]),
flush_output,
%% Set running flag
assert(server_running(true)),
%% Accept loop
accept_loop(ServerSocket, Path).
%% Stop the server gracefully
stop_server :-
retractall(server_running(_)).
%% ============================================================
%% CONNECTION HANDLING
%% ============================================================
%% Main accept loop — waits for connections
accept_loop(ServerSocket, Path) :-
( server_running(true)
-> catch(
( tcp_accept(ServerSocket, ClientSocket, _Peer),
tcp_open_socket(ClientSocket, InStream, OutStream),
%% Handle this client's requests
catch(
handle_connection(InStream, OutStream),
Error,
( format(user_error, 'Connection error: ~w~n', [Error]),
flush_output(user_error)
)
),
close(InStream, [force(true)]),
close(OutStream, [force(true)]),
accept_loop(ServerSocket, Path)
),
AcceptError,
( format(user_error, 'Accept error: ~w~n', [AcceptError]),
flush_output(user_error),
%% Clean up
tcp_close_socket(ServerSocket),
( exists_file(Path) -> delete_file(Path) ; true ),
fail
)
)
; %% Server stopped — clean up
tcp_close_socket(ServerSocket),
( exists_file(Path) -> delete_file(Path) ; true ),
format('Talmudic Engine stopped.~n'),
flush_output
).
%% Handle a single connection — read lines until EOF
handle_connection(InStream, OutStream) :-
read_line_to_string(InStream, Line),
( Line == end_of_file
-> true
; process_json_line(Line, ResponseJson),
%% Write JSON response on a single line
json_write_dict(OutStream, ResponseJson),
nl(OutStream),
flush_output(OutStream),
%% Continue reading from this connection
handle_connection(InStream, OutStream)
).
%% ============================================================
%% JSON REQUEST PROCESSING
%% ============================================================
%% Process a JSON line string and produce a JSON response string
process_json_line(Line, ResponseJson) :-
catch(
( %% Parse JSON string to dict
atom_json_dict(Line, RequestDict, []),
%% Convert JSON to Prolog request
json_to_request(RequestDict, Request),
%% Dispatch to API
process_request(Request, Response),
%% Convert response to JSON
response_to_json(Response, ResponseJson)
),
Error,
( term_string(Error, ErrorStr),
ResponseJson = json{status: "error",
error: json{code: "PROCESSING_ERROR",
message: ErrorStr}}
)
).