summaryrefslogtreecommitdiff
path: root/defs.sh
blob: dc711ecfecee554097e7614af07128d1095d4b56 (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
#!bash 
 
declare -A bos_saved_funcs
 
function bos_save_funcs() {
    local _ name
 
    while read -r _ _ name; do
        bos_saved_funcs+=( ["$name"]="$(declare -pf "$name")" )
    done < <( declare -pF )
 
    return 0
}
 
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
}
 
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
}
 
 
function bos_set_superclasses() {
    local isa_name="${bos_current_class}__bos_isa"
    declare -ga "$isa_name"
    local -n isa="$isa_name"
 
    isa=( "$@" )
 
    return 0
}
 
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
}
 
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
}
 
bos_current_class=''
 
function bos_begin_class() {
    bos_current_class="$1"
 
    bos_save_funcs
 
    return 0
}
 
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
}
 
function bos_find_class_for_method() {
    # we need to know: 
    # 
    # - what class started the method dispatch (we need a class name 
    #   or an object instance) 
    local class="$1"
    # - what class invoked us 
    local start_from_class="$2"
    # - what method invoked us (same) 
    local method="$3"
 
    local -n mro="${class}__bos_mro"
    local next_class
    local idx=0
 
    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
 
    >&2 echo "method $method not found"
}
 
function bos_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 bos_self_from_class_id() {
    local class="$1"
    local self_id="$2"
    echo "bos_invoke $class $self_id"
}
 
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]}"
}
 
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 \"\$@\"
}
"
}
 
function bos_find_methods() {
    local class="$1"
    local method="$2"
    local -n dest="$3"
 
    local -n mro="${class}__bos_mro"
 
    dest=()
 
    for class in "${mro[@]}"; do
        local full_name="${class}.${method}"
        if declare -F "$full_name" >/dev/null; then
            dest+=( "$full_name" )
        fi
    done
 
    return 0
}
 
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
        local caller="$(bos_caller)"
        method="${caller##*.}"
        start_from_class="${caller%.*}"
    fi
 
    class="$(bos_find_class_for_method "$class" "$start_from_class" "$method")"
 
    "${class}.${method}" "$@"
}
 
bos_object_id=0
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