#!/bin/bash set -euo pipefail # Set to an incoming TurnEvent by `ds_dispatch`. # ds_event= # Counter for allocating handle IDs and OIDs. (We could have separate # counters for each of these, but why bother?) # ds_handle=0 # Buffer of pending TurnEvents to be sent out. `ds_action` augments # the buffer; `ds_flush` sends and resets it. # ds_buf="" # These two map user-supplied names to previously-asserted handle IDs # and previously-asserted target OIDs, respectively, for later use in # `ds_retract`. # declare -A ds_handles declare -A ds_handle_oids # Maps local OIDs to callback code to execute when events arrive for # that OID. # declare -A ds_object_map # ds_project # # Applies the Preserves-Path expression to the given term, writing the # result(s) to stdout, one per line. # ds_project() { # cargo install preserves-tools local input="$1" local selector="$2" shift 2 echo "$input" | preserves-tool convert --indent=no --select "$selector" "$@" } # ds_connect_stdio [] # # Uses stdin for input from our peer, and stdout to send output to our # peer, connecting them to fds 5 and 6 respectively before evalutating # `code` and entering a mainloop. # ds_connect_stdio() { exec 5<&0 6>&1 exec &2 eval "${1:-:}" ds_flush ds_mainloop } # ds_connect [] # # `addr` should match schema transportAddress.Tcp, # transportAddress.Unix, or transportAddress.Stdio. Uses `nc` for the # first two and `ds_connect_stdio` for the last. Causes `code` to be # evaluated in a context where fds 5 and 6 are for input from and # output to the peer, respectively, before entering a mainloop. # ds_connect() { local addr=$1 case $(ds_project "$addr" ".^") in tcp) nc_args="$(ds_project "$addr" ". 0" --output-format=unquoted) $(ds_project "$addr" ". 1")";; unix) nc_args="-U $(ds_project "$addr" ". 0" --output-format=unquoted)";; stdio) ds_connect_stdio "${2:-:}"; return;; *) echo "ds_connect: address '$addr' not supported"; return 1;; esac local f=$(mktemp -u /tmp/ds.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX) mkfifo $f trap "rm -f $f" 2 EXIT RETURN { eval "${2:-:}" ds_flush ds_mainloop } 5< <(nc $nc_args <$f) 6>$f } # ds_send # # Transmits `data` immediately to the peer via fd 6. # ds_send() { echo "$@" >&6 # echo "SENDING: $@" } # ds_mainloop # # Repeatedly reads a line from the peer (fd 5) and dispatches it to a # local object. # ds_mainloop() { while read ds_packet do # echo "RECEIVING: $ds_packet" while read ds_turnevent do ds_dispatch "$(ds_project "$ds_turnevent" '. 0')" "$(ds_project "$ds_turnevent" '. 1')" done < <(ds_project "$ds_packet" 'seq /') ds_flush done <&5 } # ds_dispatch # # Sets `ds_event` to `event`. Then, looks up and executes local object # code using `localoid`. # ds_dispatch() { ds_event="$2" eval "${ds_object_map[$1]}" } # ds_action # # `wireref` should be a sturdy.WireRef. If it's a `mine`, dispatches # `event` locally via `ds_dispatch`; if it's a `yours`, adds `event` # to `ds_buf` (for a subsequent `ds_flush` to actually transmit). # ds_action() { case $(ds_project "$1" '.embedded . 0') in 0) ds_dispatch "$(ds_project "$1" '.embedded . 1')" "$2" ;; 1) ds_buf="${ds_buf}[$(ds_project "$1" '.embedded . 1') $2]" ;; esac } # ds_flush # # Transmits (and resets) `ds_buf` if it's non-empty. # ds_flush() { if [ -n "$ds_buf" ] then ds_send "[$ds_buf]" ds_buf="" fi } # ds_assert [] # # Asserts `term` to `wireref`. If `localname` is supplied, records # enough information to allow `localname` to be used with `ds_retract` # later to retract the assertion. # ds_assert() { local h="$ds_handle" ds_handle=$(($ds_handle + 1)) ds_action "$1" "" if [ -n "${3:-}" ] then ds_handle_oids[$3]="$1" ds_handles[$3]="$h" fi } # ds_retract # # Retracts a previously-asserted term using a `localname` supplied to # `ds_assert`. # ds_retract() { ds_action "${ds_handle_oids[$1]}" "" } # ds_message # # Sends `term` to `wireref`. # ds_message() { ds_action "$1" "" } # ds_object # # Allocates a fresh local OID, and registers `code` to be called when # an event arrives for it. Stores a `wireref` for the new OID in the # local variable named `varname`. # ds_object() { local oid="$ds_handle" ds_handle=$(($ds_handle + 1)) ds_object_map[$oid]="$2" printf -v $1 '%s' "#![0,$oid]" }