Skip to content

Instantly share code, notes, and snippets.

@dakkar
Created July 10, 2020 11:47
Show Gist options
  • Save dakkar/7c492052eb114eccfe39d8628af622f9 to your computer and use it in GitHub Desktop.
Save dakkar/7c492052eb114eccfe39d8628af622f9 to your computer and use it in GitHub Desktop.
I seem to have build a perl-like OO system in bash
#!/bin/bash
set -e
function class() {
current_class="$1"
}
declare -A _inheritance
function extends() {
_inheritance[$current_class]="$*"
}
# walk the inheritance tree, depth-first, returning all the found
# methods' full-names
function _find_methods() {
local method="$1";shift
local -a found
local class
for class in "$@"; do
local full_name="${class}.${method}"
if declare -F "$full_name" >/dev/null; then
found+=( "$full_name" )
fi
local supers="${_inheritance[$class]}"
if [[ -n "$supers" ]]; then
found+=( $(_find_methods "$method" $supers) )
fi
done
echo "${found[@]}"
}
function _id_from_self() {
local -a parts=( $1 )
echo "${parts[2]}"
}
# objects are values, so they're stored in variables!
#
# the shape of that value is… a call to _invoke curry-ed on class and
# id, so that `$my_obj the-method 1 2` is equivalent to `_invoke
# TheClass $the_obj_id the-method 1 2`
function _self_from_class_id() {
local class="$1"
local self_id="$2"
echo "_invoke $class $self_id"
}
function _storage_var_for_object() {
local self_id="$(_id_from_self "$1")"
local var="_obj_storage_${self_id}"
echo "$var"
}
function _meta_set() {
local self="$1";shift
local field="$1";shift
local -n storage="$(_storage_var_for_object "$self")"
storage[$field]="$*"
}
function _meta_get() {
local self="$1";shift
local field="$1";shift
local -n storage="$(_storage_var_for_object "$self")"
echo "${storage[$field]}"
}
function has() {
local name="$1"
local getter="${current_class}.get_${name}"
local setter="${current_class}.set_${name}"
eval "
function $getter() {
local self=\"\$1\";shift
_meta_get \"\$self\" $name
}
function $setter() {
local self=\"\$1\";shift
_meta_set \"\$self\" $name \"\$@\"
}
"
}
# the method dispatcher
function _invoke() {
local class="$1";shift
local self_id="$1";shift
local method="$1";shift
local self="$(_self_from_class_id $class $self_id)"
local -a method_chain=( $(_find_methods "$method" "$class") )
if [[ ${#method_chain[*]} -eq 0 ]]; then
>&2 echo "can't find method '$method' for class '$class'"
exit 255
fi
# this builds the `next-method` function for calling the next
# method in the inheritance tree
local next_body='unset -f next-method'
local idx
for idx in $(eval "echo {${#method_chain[*]}..1..-1}"); do
local method_name=${method_chain[$idx]}
next_body="next-method() { $next_body; $method_name \"$self\" \"\$@\"; }"
done
# at this point next_body looks like:
#
# next-method() {
# next-method() {
# unset -f next-method
# UltraClass.the-method "$self" "$@"
# }
# SuperClass.the-method "$self" "$@"
# }
eval "$next_body"
local calling="${method_chain[0]}"
$calling "$self" "$@"
}
object_id=0
new() {
local class="$1"
shift
object_id=$[ $object_id + 1 ]
local self="$(_self_from_class_id $class $object_id)"
declare -gA "$(_storage_var_for_object "$self")"
$self BUILD "$@"
echo "$self"
}
# hack: we can't do `foo=$(new Class)` because that would run `new` in
# a sub-shell, and the allocations would be invisible to the main
# shell
function assign() {
local -n dest="$1";shift
local file="$(tempfile)" || exit 255
"$@" >"$file"
dest="$(< "$file")"
rm "$file"
}
#######
# the tests
class A
has x;has y
A.BUILD() {
local self="$1";shift
$self set_x "$1"
$self set_y "$2"
}
A.say() {
local self="$1";shift
echo "A=($($self get_x) - $($self get_y))"
}
class B
extends A
has z
B.BUILD() {
local self="$1";shift
next-method "$@"
$self set_z "$3"
}
B.say() {
local self="$1";shift
echo "B=($($self get_z))"
}
assign one new A 1 2
assign two new A 3 4
assign three new B 5 6 7
if [[ "$($one say)" == 'A=(1 - 2)' ]]; then
echo 'ok 1'
else
echo 'not ok 1'
fi
if [[ "$($two say)" == 'A=(3 - 4)' ]]; then
echo 'ok 2'
else
echo 'not ok 2'
fi
assign foo $three get_z
if [[ "$foo" == '7' ]]; then
echo 'ok 3'
else
echo 'not ok 3'
fi
if [[ "$($three get_x)" == '5' ]]; then
echo 'ok 4'
else
echo 'not ok 4'
fi
echo '1..4'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment