summaryrefslogtreecommitdiff
path: root/bos-meta-class.sh
blob: bd83c291e20428c244b01a77ad8a9189c101266e (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
#!bash 
 
# these are instance methods of the base metaclass 
 
function bos/meta/class/isa-for-into() {
    bos-namespaces/store-array-for-into "$1" isa "$2"
}
 
function bos/meta/class/set-superclasses-for() {
    local isa_name; $self isa-for-into isa_name "$1"
    local -n isa="$isa_name"
    shift
 
    isa=( "$@" )
 
    return 0
}
 
function bos/meta/class/get-superclasses-for-into() {
    local -n dest="$1"
    local isa_name; $self isa-for-into isa_name "$2"
    local -n isa="$isa_name"
 
    dest=( "${isa[@]}" )
 
    return 0
}
 
function bos/meta/class/mro-for-into() {
    bos-namespaces/store-array-for-into "$1" mro "$2"
}
 
# 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/meta/class/make-mro-for() {
    local class="$1"
    local mro_name; $self mro-for-into mro_name "$class"
    local -n mro="$mro_name"
 
    # TODO: use C3 and support multiple inheritance 
 
    mro=( "$class" )
    local -a nextclasses
    $self get-superclasses-for-into nextclasses "$class"
 
    while [[ "${#nextclasses}" -gt 0 ]]; do
        mro+=( "${nextclasses[0]}" )
        class="${nextclasses[0]}"
        $self get-superclasses-for-into nextclasses "$class"
    done
 
    mro+=( "bos/base" )
 
    return 0
}
 
# find the first class (in MRO) that defines a given method 
function bos/meta/class/find-method-into() {
    local -n bos_meta_class_find_method_result="$1"
    # the class that started the method dispatch (the class of $self, 
    # usually) 
    local class="$2"
    # what class invoked next/method (if we're doing next/method) 
    local start_from_class="$3"
    # the method to look for 
    local method="$4"
 
    local mro_store; $self mro-for-into mro_store "$class"
    local -n mro="$mro_store"
 
    # minimal MRO: the class itself 
    if [[ "${#mro[@]}" -eq 0 ]]; then
        mro=( "$class" )
    fi
 
    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
            bos_meta_class_find_method_result="${full_name}"
            return 0
        fi
    done
 
    # TODO: better error / failure 
    >&2 echo "method $method not found via class $class"
    return 1
}
 
# get the attribute meta-object for the given attribute name, assuming 
# that the class that this metaclass describe, actuall has that 
# attribute 
function bos/meta/class/get-attribute-by-name-into() {
    >&2 echo "get-attribute-by-name-into($*)"
    local -n bos_meta_class_get_attribute_result="$1"; shift
    local bos_meta_class_get_attribute_name="$1"
    local bos_meta_class_attrs_name
    
    # very manual method call, because I'm not sure I can make this 
    # work otherwise 
 
    local attribute_type='assoc'
    local attribute_name='class_attributes'
    local associated_class='bos/meta/class'
 
    bos/meta/attribute/store-for-into bos_meta_class_attrs_name "$self"
 
    local -n bos_meta_class_attrs="$bos_meta_class_attrs_name"
 
    bos_meta_class_get_attribute_result="${bos_meta_class_attrs[$bos_meta_class_get_attribute_name]}"
 
    return 0
}
 
function bos/meta/class/set-attribute-by-name() {
    local bos_meta_class_attrs_name
 
    # very manual method call, because I'm not sure I can make this 
    # work otherwise 
 
    local attribute_type='assoc'
    local attribute_name='class_attributes'
    local associated_class='bos/meta/class'
 
    bos/meta/attribute/store-for-into bos_meta_class_attrs_name "$self"
 
    local -n bos_meta_class_attrs="$bos_meta_class_attrs_name"
 
    bos_meta_class_attrs["$1"]="$2"
 
    return 0
}
 
 
function bos/meta/class/find-attribute-into() {
    local -n bos_meta_class_find_attribute_result="$1"
    local bos_meta_class_find_attribute_class="$2"
    local bos_meta_class_find_attribute_attribute_name="$3"
 
    local mro_store; $self mro-for-into mro_store "$bos_meta_class_find_attribute_class"
    local -n mro="$mro_store"
 
    # minimal MRO: the class itself 
    if [[ "${#mro[@]}" -eq 0 ]]; then
        mro=( "$bos_meta_class_find_attribute_class" )
    fi
 
    local bos_meta_class_for_class
    local bos_class_from_mro
 
    for bos_class_from_mro in "${mro[@]}"; do
        bos-meta/metaclass-instance-for-class-into bos_meta_class_for_class "$bos_class_from_mro"
 
        >&2 echo "from mro(${mro[*]})=$bos_class_from_mro meta=$bos_meta_class_for_class"
 
        $bos_meta_class_for_class get-attribute-by-name-into bos_meta_class_find_attribute_result "$bos_meta_class_find_attribute_attribute_name"
 
        if [[ -n "$bos_meta_class_find_attribute_result" ]]; then
            return 0
        fi
    done
 
    # TODO: better error / failure 
    >&2 echo "attribute $bos_meta_class_find_attribute_attribute_name not found via class $bos_meta_class_find_attribute_class"
    return 1
}
 
# the method dispatcher 
function bos/meta/class/invoke() {
    local bos_invoke_class="$1";shift
    local bos_invoke_object_id="$1";shift
    local bos_invoke_method="$1";shift
 
    local bos_invoke_start_from_class=''
 
    if [[ "$bos_invoke_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 bos_invoke_caller; bos-dispatch/caller-into bos_invoke_caller
        bos_invoke_method="${bos_invoke_caller##*/}"
        bos_invoke_start_from_class="${bos_invoke_caller%/*}"
    fi
 
    # get the first class that defines the method 
 
    # there is some fuckery going on with namerefs, recursion, and 
    # local; this is a probably-bad-but-works workaround: use a 
    # different variable name in different recursive calls 
    local ffsname="to_invoke_${#FUNCNAME[*]}"
    $self find-method-into "$ffsname" "$bos_invoke_class" "$bos_invoke_start_from_class" "$bos_invoke_method"
 
    local bos_invoke_target_class="${!ffsname%/*}"
    local bos_invoke_attrs_attr
    $self find-attribute-into bos_invoke_attrs_attr "$bos_invoke_target_class" 'class_attributes'
    >&2 echo "for (${bos_invoke_class}->${bos_invoke_target_class}) class_attributes = $bos_invoke_attrs_attr"
    
    if [[ -n "$bos_invoke_attrs_attr" ]]; then
        local -A bos_invoke_attrs
        $bos_invoke_attrs_attr get-value-into bos_invoke_attrs "$self"
 
        local bos_invoke_attr_name bos_invoke_attr_obj bos_invoke_attr_type
        for bos_invoke_attr_name in "${!bos_invoke_attrs[@]}"; do
            bos_invoke_attr_obj="${bos_invoke_attrs[$bos_invoke_attr_name]}"
 
            >&2 echo "getting attribute type from $bos_invoke_attr_name=($bos_invoke_attr_obj)"
            $bos_invoke_attr_obj get-attribute-type-into bos_invoke_attr_type
 
            case "$bos_invoke_attr_type" in
                scalar) local "$bos_invoke_attr_name" ;;
                array) local -a "$bos_invoke_attr_name" ;;
                assoc) local -A "$bos_invoke_attr_name" ;;
            esac
 
            $bos_invoke_attr_obj get-value-into "$bos_invoke_attr_name" "$self"
        done
    fi
 
    local self
    bos-object-id/pack-self-into self "$bos_invoke_class" "$bos_invoke_object_id"
 
    # call it 
    eval "\"\$$ffsname\" \"\$@\""
    return $?
}
 
function bos/meta/class/create-object-into() {
    local -n bos_meta_class_create_object_result="$1";shift
    local class="$1";shift
 
    local object_id_store; bos-namespaces/store-scalar-for-into object_id_store object_id "$class"
    local -n object_id="$object_id_store"
    (( ++object_id ))
 
    bos-object-id/pack-self-into bos_meta_class_create_object_result "$class" "$object_id"
 
    # TODO invoke BUILD/BUILDALL? 
 
    return 0
}
 
function bos/meta/class() {
    bos-dispatch/invoke bos/meta/class 0 "$@"
}