193 lines
4.7 KiB
Bash
Executable File
193 lines
4.7 KiB
Bash
Executable File
#!/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 <term> <preserves-path>
|
|
#
|
|
# 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 [<code>]
|
|
#
|
|
# 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 </dev/null >&2
|
|
eval "${1:-:}"
|
|
ds_flush
|
|
ds_mainloop
|
|
}
|
|
|
|
# ds_connect <addr> [<code>]
|
|
#
|
|
# `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 <data>
|
|
#
|
|
# 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 <localoid> <event>
|
|
#
|
|
# 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> <event>
|
|
#
|
|
# `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 <wireref> <term> [<localname>]
|
|
#
|
|
# 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" "<assert $2 $h>"
|
|
if [ -n "${3:-}" ]
|
|
then
|
|
ds_handle_oids[$3]="$1"
|
|
ds_handles[$3]="$h"
|
|
fi
|
|
}
|
|
|
|
# ds_retract <localname>
|
|
#
|
|
# Retracts a previously-asserted term using a `localname` supplied to
|
|
# `ds_assert`.
|
|
#
|
|
ds_retract() {
|
|
ds_action "${ds_handle_oids[$1]}" "<retract ${ds_handles[$1]}>"
|
|
}
|
|
|
|
# ds_message <wireref> <term>
|
|
#
|
|
# Sends `term` to `wireref`.
|
|
#
|
|
ds_message() {
|
|
ds_action "$1" "<message $2>"
|
|
}
|
|
|
|
# ds_object <varname> <code>
|
|
#
|
|
# 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]"
|
|
}
|