summaryrefslogtreecommitdiff
path: root/defs.sh
blob: 1ab6fbef388d2d4c9ddd57675724f1a0a9d8ffaa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
#!bash 
 
### Bash Object System 
 
## namespace handling 
 
declare -A bos_saved_funcs
 
# save all currently-visible functions in an associative array 
function bos_save_funcs() {
    local _ name
 
    while read -r _ _ name; do
        bos_saved_funcs+=( ["$name"]="$(declare -pf "$name")" )
    done < <( declare -pF )
 
    return 0
}
 
# diff all currently-visible function against the saved ones, list the 
# ones that differ 
function bos_list_new_funcs() {
    local _ name new_function
 
    while read -r _ _ name; do
        new_function="$(declare -pf "$name")"
        if [[ "$new_function" != "${bos_saved_funcs["$name"]}" ]]; then
            echo "${name}"
        fi
    done < <( declare -pF )
 
    return 0
}
 
# rename a bunch of functions by prepending a prefix; restore the 
# saved function of the same name, if it exists 
function bos_qualify_funcs() {
    local prefix="$1"
    shift
    local _ name new_function
 
    for name in "$@"; do
        new_function="$(declare -pf "$name")"
        eval "${prefix}.${new_function}"
        if [[ -n "${bos_saved_funcs["$name"]}" ]]; then
            eval "${bos_saved_funcs["$name"]}"
        else
            unset -f "$name"
        fi
    done
 
    return 0
}
 
## the MOP 
 
bos_current_class=''
 
# begin the definition of a class (not really MOP, is it?) 
function bos_begin_class() {
    bos_current_class="$1"
 
    bos_save_funcs
 
    return 0
}
 
# set the superclasses of the current class 
# TODO: split this into MOP + helper 
function bos_set_superclasses() {
    local isa_name="${bos_current_class}__bos_isa"
    declare -ga "$isa_name"
    local -n isa="$isa_name"
 
    isa=( "$@" )
 
    return 0
}
 
# get the superclasses of the given class 
function bos_get_superclasses_for() {
    local class="$1"
    local -n dest="$2"
    local isa_name="${class}__bos_isa"
    local -n isa="$isa_name"
 
    dest=( "${isa[@]}" )
 
    return 0
}
 
# given a class and an array, sets the array to the list of clasess to 
# look into when resolving a method call for the given class, in order 
function bos_linearised_isa() {
    local class="$1"
    local -n _linearised_isa="$2"
 
    # TODO: use C3 and support multiple inheritance 
 
    _linearised_isa=( "$class" )
    local -a nextclasses
    bos_get_superclasses_for "$class" nextclasses
 
    while [[ "${#nextclasses}" -gt 0 ]]; do
        _linearised_isa+=( "${nextclasses[0]}" )
        class="${nextclasses[0]}"
        bos_get_superclasses_for "$class" nextclasses
    done
 
    return 0
}
 
# ends the definition of a class, moving its functions into the class 
# namespace and calculating the MRO 
# TODO: split this into MOP + helper 
# TODO: delay MRO computation until first method call? 
function bos_end_class() {
    local mro_name="${bos_current_class}__bos_mro"
    declare -ga "$mro_name"
 
    readarray -t method_names < <(bos_list_new_funcs)
    bos_qualify_funcs "$bos_current_class" "${method_names[@]}"
    bos_linearised_isa "$bos_current_class" "$mro_name"
 
    return 0
}
 
# find the first class (in MRO) that defines a given method 
function bos_find_class_for_method() {
    # the class that started the method dispatch (the class of $self, 
    # usually) 
    local class="$1"
    # what class invoked next.method (if we're doing next.method) 
    local start_from_class="$2"
    # the method to look for 
    local method="$3"
 
    local -n mro="${class}__bos_mro"
    local idx=0
 
    # if we don't have a $start_from_class, it means we're doing the 
    # initial method resolution, so don't skip any class in $mro 
    if [[ -n "$start_from_class" ]]; then
        for (( ; idx < "${#mro}" ; ++idx )); do
            if [[ "${mro[$idx]}" == "$start_from_class" ]]; then
                (( ++idx ))
                break
            fi
        done
    fi
 
    for (( ; idx < "${#mro}" ; ++idx )); do
        local full_name="${mro[$idx]}.${method}"
        if declare -F "$full_name" >/dev/null; then
            echo "${mro[$idx]}"
            return 0
        fi
    done
 
    # TODO: better error / failure 
    >&2 echo "method $method not found"
}
 
# objects are values, so they're stored in variables! 
# 
# the shape of that value is… a call to bos_invoke curry-ed on class and 
# id, so that `$my_obj the-method 1 2` is equivalent to `bos_invoke 
# TheClass $the_obj_id the-method 1 2` 
function bos_self_from_class_id() {
    local class="$1"
    local self_id="$2"
    echo "bos_invoke $class $self_id"
}
 
# get object id from a $self string 
function bos_id_from_self() {
    local -a parts=( $1 )
    echo "${parts[2]}"
}
 
function bos_storage_var_for_object() {
    local self_id="$(bos_id_from_self "$1")"
    local var="bos_obj_storage_${self_id}"
    echo "$var"
}
 
function bos_set_attribute_value() {
    local self="$1";shift
    local field="$1";shift
    local -n storage="$(bos_storage_var_for_object "$self")"
    storage[$field]="$*"
}
 
function bos_get_attribute_value() {
    local self="$1";shift
    local field="$1";shift
    local -n storage="$(bos_storage_var_for_object "$self")"
    echo "${storage[$field]}"
}
 
# declare getter / setter for an attribute in the current class 
# 
# TODO: maybe split? bos_add_attribute just declares the attribute, 
# then have MOP functions to create getter and/or setter, then join 
# the two in bos_end_class 
function bos_add_attribute() {
    local name="$1"
 
    eval "
function get_${name}() {
  bos_get_attribute_value \"\$self\" $name
}
function set_${name}() {
  bos_set_attribute_value \"\$self\" $name \"\$@\"
}
"
}
 
# return the closest non-BOS function name from the call stack 
function bos_caller() {
    local idx
    for caller in "${FUNCNAME[@]}"; do
        if [[ ! $caller =~ ^bos_ ]]; then
            echo "$caller"
            return 0
        fi
    done
}
 
# the method dispatcher 
function bos_invoke() {
    local class="$1";shift
    local self_id="$1";shift
    local method="$1";shift
    local self="$(bos_self_from_class_id $class $self_id)"
 
    local start_from_class=''
 
    if [[ "$method" == 'next.method' ]]; then
        # get the method and the calling class, so 
        # bos_find_class_for_method will return the *next* class to 
        # use 
        local caller="$(bos_caller)"
        method="${caller##*.}"
        start_from_class="${caller%.*}"
    fi
 
    # get the first class that defines the method 
    class="$(bos_find_class_for_method "$class" "$start_from_class" "$method")"
 
    # call it 
    "${class}.${method}" "$@"
}
 
bos_object_id=0
# constructor: given a variable name and a class, create an object of 
# that class, build it, and assign it to the variable 
function bos_create_object() {
    local -n dest="$1";shift
    local class="$1";shift
 
    (( ++bos_object_id ))
 
    local self="$(bos_self_from_class_id $class $bos_object_id)"
    declare -gA "$(bos_storage_var_for_object "$self")"
 
    $self BUILD "$@"
 
    dest="$self"
}
 
# ---- test ---- 
 
bos_begin_class one
 
bos_add_attribute first
 
BUILD() {
    echo "building one"
    $self set_first "$1"
}
 
function doit() {
    echo "one method ($*) first=$( $self get_first )"
}
 
bos_end_class
 
bos_begin_class two
 
bos_set_superclasses one
 
bos_add_attribute second
 
BUILD() {
    echo "building two"
    $self set_second "$1"
    $self next.method "$2"
}
 
function doit() {
    echo "two method ($*) second=$( $self get_second )"
 
    $self next.method "$@"
}
 
bos_end_class
 
bos_create_object A two 16 23
 
$A doit 1 2 3