From ef5f167515aed68166215e1ab02c2434d93de0f1 Mon Sep 17 00:00:00 2001 From: Matthias Kruk Date: Sat, 3 Dec 2022 16:51:59 +0900 Subject: [PATCH] toolbox.sh: Implement naive interfaces and inheritance The ipc and uipc modules provide the same API to the module user, but the user cannot easily switch between the two implementations because toolbox modules do not have a notion of interfaces. This commit adds a very primitive means for modules to declare and implement interfaces: when a module declares an interface, a vtable is allocated in the form of an associative array, and call stubs are generated which call the functions referenced in the vtable. A module that implements an interface can then override entries in the interface's vtable. --- toolbox.sh | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/toolbox.sh b/toolbox.sh index 924b11b..e9cbfd2 100644 --- a/toolbox.sh +++ b/toolbox.sh @@ -42,13 +42,16 @@ __toolbox_init() { "$TOOLBOX_HOME/include" "$toolboxroot/include" ) - + declare -Axg __TOOLBOX_INTERFACES declare -Axg __TOOLBOX_INCLUDED readonly -f have readonly -f _try_include readonly -f include readonly -f command_not_found_handle + readonly -f interface + readonly -f implements + readonly -f calling_module return 0 } @@ -155,6 +158,94 @@ command_not_found_handle() { return 127 } +calling_module() { + local modpath + local modfile + local modname + + modpath="${BASH_SOURCE[2]}" # /path/to/module.sh + modfile="${modpath##*/}" # module.sh + modname="${modfile%.*}" # module + + echo "$modname" + return 0 +} + +interface() { + local methods=("$@") + + local name + local method + local call_stubs + local vtable_name + local -n interface + + if ! name=$(calling_module); then + echo "ERROR: Could not determine name of module" 1>&2 + return 1 + fi + + call_stubs="" + vtable_name="__TOOLBOX_INTERFACE_$name" + + # Create the vtable for the interface + declare -Agx "$vtable_name" + + interface="$vtable_name" + + __TOOLBOX_INTERFACES["$name"]="$vtable_name" + + # Generate and load call stubs that execute the functions referenced by the vtable + for method in "${methods[@]}"; do + # shellcheck disable=SC2016 # Reason: Don't want expansion in the format string + call_stubs+=$(printf '\n%s() {\n\t"${%s["%s"]}" "$@"\n\treturn "$?"\n}' \ + "${name}_$method" "$vtable_name" "$method") + done + + # shellcheck disable=SC1090 # Reason: Dynamically generated call-stubs can't be checked + if ! source <(echo "$call_stubs"); then + echo "ERROR: Could not generate call stubs for interface $name" 1>&2 + return 1 + fi + + # Set the entries in the vtable + for method in "${methods[@]}"; do + interface["$method"]="_${name}_$method" + done + + return 0 +} + +implements() { + local iface="$1" + + local module + local method + declare -n interface + + if [[ -z "${__TOOLBOX_INTERFACES[$iface]}" ]]; then + echo "ERROR: Unknown interface: \"$iface\"" 1>&2 + return 1 + fi + + interface="${__TOOLBOX_INTERFACES[$iface]}" + module=$(calling_module) + + # Check if all methods are implemented before modifying the vtable + for method in "${!interface[@]}"; do + if ! declare -f "${module}_$method" &>/dev/null; then + echo "ERROR: Module $module does not implement the method \"$method\"" 1>&2 + return 1 + fi + done + + for method in "${!interface[@]}"; do + interface["$method"]="${module}_$method" + done + + return 0 +} + { if ! compgen -v | grep "^__TOOLBOX_INCLUDED$" &> /dev/null; then if ! __toolbox_init; then -- 2.47.3