[t][TT#1509] Prevent core dumps by preventing negative length array creation. Tests...
[parrot.git] / src / multidispatch.c
blob3ee26eb99314357b56bae5792ca8024adf08f7a4
1 /*
2 Copyright (C) 2003-2009, Parrot Foundation.
3 $Id$
5 =head1 NAME
7 src/multidispatch.c - Multimethod dispatch for binary opcode functions
9 =head1 SYNOPSIS
11 This system is set up to handle type-based dispatching for binary (two
12 argument) functions. This includes, though isn't necessarily limited to, binary
13 operators such as addition or subtraction.
15 =head1 DESCRIPTION
17 The MMD system is straightforward, and currently must be explicitly invoked,
18 for example by a vtable function. (We reserve the right to use MMD in all
19 circumstances, but currently do not).
21 =head2 API
23 For the purposes of the API, each MMD-able function is assigned a unique
24 number which is used to find the correct function table. This is the
25 C<func_num> parameter in the following functions. While Parrot isn't
26 restricted to a predefined set of functions, it I<does> set things up so
27 that all the binary vtable functions have a MMD table preinstalled for
28 them, with default behaviour.
30 =head2 Remarks
32 =head2 Functions
34 =over 4
36 =cut
40 #include "parrot/compiler.h"
41 #include "parrot/parrot.h"
42 #include "parrot/multidispatch.h"
43 #include "parrot/oplib/ops.h"
44 #include "multidispatch.str"
45 #include "pmc/pmc_nci.h"
46 #include "pmc/pmc_sub.h"
47 #include "pmc/pmc_callcontext.h"
49 /* HEADERIZER HFILE: include/parrot/multidispatch.h */
51 /* HEADERIZER BEGIN: static */
52 /* Don't modify between HEADERIZER BEGIN / HEADERIZER END. Your changes will be lost. */
54 static void mmd_add_multi_global(PARROT_INTERP,
55 ARGIN(STRING *sub_name),
56 ARGIN(PMC *sub_obj))
57 __attribute__nonnull__(1)
58 __attribute__nonnull__(2)
59 __attribute__nonnull__(3);
61 static void mmd_add_multi_to_namespace(PARROT_INTERP,
62 ARGIN(STRING *ns_name),
63 ARGIN(STRING *sub_name),
64 ARGIN(PMC *sub_obj))
65 __attribute__nonnull__(1)
66 __attribute__nonnull__(2)
67 __attribute__nonnull__(3)
68 __attribute__nonnull__(4);
70 PARROT_CANNOT_RETURN_NULL
71 PARROT_WARN_UNUSED_RESULT
72 static PMC* mmd_build_type_tuple_from_long_sig(PARROT_INTERP,
73 ARGIN(STRING *long_sig))
74 __attribute__nonnull__(1)
75 __attribute__nonnull__(2);
77 PARROT_CANNOT_RETURN_NULL
78 PARROT_WARN_UNUSED_RESULT
79 static PMC* mmd_build_type_tuple_from_type_list(PARROT_INTERP,
80 ARGIN(PMC *type_list))
81 __attribute__nonnull__(1)
82 __attribute__nonnull__(2);
84 PARROT_WARN_UNUSED_RESULT
85 PARROT_CAN_RETURN_NULL
86 static STRING * mmd_cache_key_from_types(PARROT_INTERP,
87 ARGIN(const char *name),
88 ARGIN(PMC *types))
89 __attribute__nonnull__(1)
90 __attribute__nonnull__(2)
91 __attribute__nonnull__(3);
93 PARROT_WARN_UNUSED_RESULT
94 PARROT_CAN_RETURN_NULL
95 static STRING * mmd_cache_key_from_values(PARROT_INTERP,
96 ARGIN(const char *name),
97 ARGIN(PMC *values))
98 __attribute__nonnull__(1)
99 __attribute__nonnull__(2)
100 __attribute__nonnull__(3);
102 PARROT_WARN_UNUSED_RESULT
103 PARROT_CAN_RETURN_NULL
104 static PMC* mmd_cvt_to_types(PARROT_INTERP, ARGIN(PMC *multi_sig))
105 __attribute__nonnull__(1)
106 __attribute__nonnull__(2);
108 static UINTVAL mmd_distance(PARROT_INTERP,
109 ARGIN(PMC *pmc),
110 ARGIN(PMC *arg_tuple))
111 __attribute__nonnull__(1)
112 __attribute__nonnull__(2)
113 __attribute__nonnull__(3);
115 static void mmd_search_by_sig_obj(PARROT_INTERP,
116 ARGIN(STRING *name),
117 ARGIN(PMC *sig_obj),
118 ARGIN(PMC *candidates))
119 __attribute__nonnull__(1)
120 __attribute__nonnull__(2)
121 __attribute__nonnull__(3)
122 __attribute__nonnull__(4);
124 static void mmd_search_global(PARROT_INTERP,
125 ARGIN(STRING *name),
126 ARGIN(PMC *cl))
127 __attribute__nonnull__(1)
128 __attribute__nonnull__(2)
129 __attribute__nonnull__(3);
131 PARROT_WARN_UNUSED_RESULT
132 PARROT_CAN_RETURN_NULL
133 static PMC * Parrot_mmd_get_cached_multi_sig(PARROT_INTERP,
134 ARGIN(PMC *sub_pmc))
135 __attribute__nonnull__(1)
136 __attribute__nonnull__(2);
138 static int Parrot_mmd_maybe_candidate(PARROT_INTERP,
139 ARGIN(PMC *pmc),
140 ARGIN(PMC *cl))
141 __attribute__nonnull__(1)
142 __attribute__nonnull__(2)
143 __attribute__nonnull__(3);
145 PARROT_CANNOT_RETURN_NULL
146 static PMC * Parrot_mmd_sort_candidates(PARROT_INTERP,
147 ARGIN(PMC *arg_tuple),
148 ARGIN(PMC *cl))
149 __attribute__nonnull__(1)
150 __attribute__nonnull__(2)
151 __attribute__nonnull__(3);
153 #define ASSERT_ARGS_mmd_add_multi_global __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
154 PARROT_ASSERT_ARG(interp) \
155 , PARROT_ASSERT_ARG(sub_name) \
156 , PARROT_ASSERT_ARG(sub_obj))
157 #define ASSERT_ARGS_mmd_add_multi_to_namespace __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
158 PARROT_ASSERT_ARG(interp) \
159 , PARROT_ASSERT_ARG(ns_name) \
160 , PARROT_ASSERT_ARG(sub_name) \
161 , PARROT_ASSERT_ARG(sub_obj))
162 #define ASSERT_ARGS_mmd_build_type_tuple_from_long_sig \
163 __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
164 PARROT_ASSERT_ARG(interp) \
165 , PARROT_ASSERT_ARG(long_sig))
166 #define ASSERT_ARGS_mmd_build_type_tuple_from_type_list \
167 __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
168 PARROT_ASSERT_ARG(interp) \
169 , PARROT_ASSERT_ARG(type_list))
170 #define ASSERT_ARGS_mmd_cache_key_from_types __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
171 PARROT_ASSERT_ARG(interp) \
172 , PARROT_ASSERT_ARG(name) \
173 , PARROT_ASSERT_ARG(types))
174 #define ASSERT_ARGS_mmd_cache_key_from_values __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
175 PARROT_ASSERT_ARG(interp) \
176 , PARROT_ASSERT_ARG(name) \
177 , PARROT_ASSERT_ARG(values))
178 #define ASSERT_ARGS_mmd_cvt_to_types __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
179 PARROT_ASSERT_ARG(interp) \
180 , PARROT_ASSERT_ARG(multi_sig))
181 #define ASSERT_ARGS_mmd_distance __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
182 PARROT_ASSERT_ARG(interp) \
183 , PARROT_ASSERT_ARG(pmc) \
184 , PARROT_ASSERT_ARG(arg_tuple))
185 #define ASSERT_ARGS_mmd_search_by_sig_obj __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
186 PARROT_ASSERT_ARG(interp) \
187 , PARROT_ASSERT_ARG(name) \
188 , PARROT_ASSERT_ARG(sig_obj) \
189 , PARROT_ASSERT_ARG(candidates))
190 #define ASSERT_ARGS_mmd_search_global __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
191 PARROT_ASSERT_ARG(interp) \
192 , PARROT_ASSERT_ARG(name) \
193 , PARROT_ASSERT_ARG(cl))
194 #define ASSERT_ARGS_Parrot_mmd_get_cached_multi_sig \
195 __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
196 PARROT_ASSERT_ARG(interp) \
197 , PARROT_ASSERT_ARG(sub_pmc))
198 #define ASSERT_ARGS_Parrot_mmd_maybe_candidate __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
199 PARROT_ASSERT_ARG(interp) \
200 , PARROT_ASSERT_ARG(pmc) \
201 , PARROT_ASSERT_ARG(cl))
202 #define ASSERT_ARGS_Parrot_mmd_sort_candidates __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
203 PARROT_ASSERT_ARG(interp) \
204 , PARROT_ASSERT_ARG(arg_tuple) \
205 , PARROT_ASSERT_ARG(cl))
206 /* Don't modify between HEADERIZER BEGIN / HEADERIZER END. Your changes will be lost. */
207 /* HEADERIZER END: static */
210 #define MMD_DEBUG 0
215 =item C<PMC* Parrot_mmd_find_multi_from_sig_obj(PARROT_INTERP, STRING *name, PMC
216 *invoke_sig)>
218 Collect a list of possible candidates for a given sub name and call signature.
219 Rank the possible candidates by Manhattan Distance, and return the best
220 matching candidate. The candidate list is cached in the CallSignature object,
221 to allow for iterating through it.
223 Currently this only looks in the global "MULTI" namespace.
225 =cut
229 PARROT_EXPORT
230 PARROT_WARN_UNUSED_RESULT
231 PARROT_CANNOT_RETURN_NULL
232 PMC*
233 Parrot_mmd_find_multi_from_sig_obj(PARROT_INTERP, ARGIN(STRING *name), ARGIN(PMC *invoke_sig))
235 ASSERT_ARGS(Parrot_mmd_find_multi_from_sig_obj)
236 PMC * const candidate_list = Parrot_pmc_new(interp, enum_class_ResizablePMCArray);
238 mmd_search_by_sig_obj(interp, name, invoke_sig, candidate_list);
239 mmd_search_global(interp, name, candidate_list);
241 return Parrot_mmd_sort_manhattan_by_sig_pmc(interp, candidate_list, invoke_sig);
246 =item C<void Parrot_mmd_multi_dispatch_from_c_args(PARROT_INTERP, const char
247 *name, const char *sig, ...)>
249 Dispatches to a MultiSub from a variable-sized list of C arguments. The
250 multiple dispatch system will figure out which sub should be called based on
251 the types of the arguments passed in.
253 Return arguments must be passed as a reference to the PMC, string, number, or
254 integer, so the result can be set.
256 =cut
260 PARROT_EXPORT
261 PARROT_CAN_RETURN_NULL
262 void
263 Parrot_mmd_multi_dispatch_from_c_args(PARROT_INTERP,
264 ARGIN(const char *name), ARGIN(const char *sig), ...)
266 ASSERT_ARGS(Parrot_mmd_multi_dispatch_from_c_args)
267 PMC *sig_object, *sub;
269 va_list args;
270 va_start(args, sig);
271 sig_object = Parrot_pcc_build_sig_object_from_varargs(interp, PMCNULL, sig, args);
272 va_end(args);
274 /* Check the cache. */
275 sub = Parrot_mmd_cache_lookup_by_types(interp, interp->op_mmd_cache, name,
276 VTABLE_get_pmc(interp, sig_object));
278 if (PMC_IS_NULL(sub)) {
279 sub = Parrot_mmd_find_multi_from_sig_obj(interp,
280 Parrot_str_new_constant(interp, name), sig_object);
282 if (!PMC_IS_NULL(sub))
283 Parrot_mmd_cache_store_by_types(interp, interp->op_mmd_cache, name,
284 VTABLE_get_pmc(interp, sig_object), sub);
287 if (PMC_IS_NULL(sub))
288 Parrot_ex_throw_from_c_args(interp, NULL, EXCEPTION_METHOD_NOT_FOUND,
289 "Multiple Dispatch: No suitable candidate found for '%s',"
290 " with signature '%s'", name, sig);
292 #if MMD_DEBUG
293 Parrot_io_eprintf(interp, "candidate found for '%s', with signature '%s'\n",
294 name, sig);
295 Parrot_io_eprintf(interp, "type of candidate found: %Ss\n",
296 VTABLE_name(interp, sub));
297 #endif
299 Parrot_pcc_invoke_from_sig_object(interp, sub, sig_object);
305 =item C<PMC * Parrot_mmd_find_multi_from_long_sig(PARROT_INTERP, STRING *name,
306 STRING *long_sig)>
308 Find the best candidate multi for a given sub name and signature. The signature
309 is a string containing a comma-delimited list of type names.
311 Currently only searches the global MULTI namespace.
313 =cut
317 PARROT_EXPORT
318 PARROT_CAN_RETURN_NULL
319 PARROT_WARN_UNUSED_RESULT
320 PMC *
321 Parrot_mmd_find_multi_from_long_sig(PARROT_INTERP, ARGIN(STRING *name),
322 ARGIN(STRING *long_sig))
324 ASSERT_ARGS(Parrot_mmd_find_multi_from_long_sig)
325 STRING * const multi_str = CONST_STRING(interp, "MULTI");
326 PMC * const ns = Parrot_make_namespace_keyed_str(interp,
327 interp->root_namespace, multi_str);
328 PMC * const multi_sub = Parrot_get_global(interp, ns, name);
330 if (PMC_IS_NULL(multi_sub)) {
331 return PMCNULL;
333 else {
334 PMC * const type_tuple = mmd_build_type_tuple_from_long_sig(interp, long_sig);
335 return Parrot_mmd_sort_candidates(interp, type_tuple, multi_sub);
342 =item C<PMC * Parrot_mmd_sort_manhattan_by_sig_pmc(PARROT_INTERP, PMC
343 *candidates, PMC *invoke_sig)>
345 Given an array PMC (usually a MultiSub) and a CallSignature PMC, sorts the mmd
346 candidates by their manhattan distance to the signature args and returns the
347 best one.
349 =cut
353 PARROT_EXPORT
354 PARROT_CAN_RETURN_NULL
355 PARROT_WARN_UNUSED_RESULT
356 PMC *
357 Parrot_mmd_sort_manhattan_by_sig_pmc(PARROT_INTERP, ARGIN(PMC *candidates),
358 ARGIN(PMC *invoke_sig))
360 ASSERT_ARGS(Parrot_mmd_sort_manhattan_by_sig_pmc)
361 const INTVAL n = VTABLE_elements(interp, candidates);
363 if (!n)
364 return PMCNULL;
366 return Parrot_mmd_sort_candidates(interp,
367 VTABLE_get_pmc(interp, invoke_sig), candidates);
372 =item C<static PMC* mmd_build_type_tuple_from_type_list(PARROT_INTERP, PMC
373 *type_list)>
375 Construct a FixedIntegerArray of type numbers from an array of
376 type names. Used for multiple dispatch.
378 =cut
382 PARROT_CANNOT_RETURN_NULL
383 PARROT_WARN_UNUSED_RESULT
384 static PMC*
385 mmd_build_type_tuple_from_type_list(PARROT_INTERP, ARGIN(PMC *type_list))
387 ASSERT_ARGS(mmd_build_type_tuple_from_type_list)
388 PMC *multi_sig = Parrot_pmc_new_constant(interp, enum_class_FixedIntegerArray);
389 INTVAL param_count = VTABLE_elements(interp, type_list);
390 INTVAL i;
392 VTABLE_set_integer_native(interp, multi_sig, param_count);
394 for (i = 0; i < param_count; i++) {
395 STRING *type_name = VTABLE_get_string_keyed_int(interp, type_list, i);
396 INTVAL type;
398 if (Parrot_str_equal(interp, type_name, CONST_STRING(interp, "DEFAULT")))
399 type = enum_type_PMC;
400 else if (Parrot_str_equal(interp, type_name, CONST_STRING(interp, "STRING")))
401 type = enum_type_STRING;
402 else if (Parrot_str_equal(interp, type_name, CONST_STRING(interp, "INTVAL")))
403 type = enum_type_INTVAL;
404 else if (Parrot_str_equal(interp, type_name, CONST_STRING(interp, "FLOATVAL")))
405 type = enum_type_FLOATVAL;
406 else
407 type = Parrot_pmc_get_type_str(interp, type_name);
409 VTABLE_set_integer_keyed_int(interp, multi_sig, i, type);
412 return multi_sig;
418 =item C<static PMC* mmd_build_type_tuple_from_long_sig(PARROT_INTERP, STRING
419 *long_sig)>
421 Construct a FixedIntegerArray of type numbers from a comma-delimited string of
422 type names. Used for multiple dispatch.
424 =cut
428 PARROT_CANNOT_RETURN_NULL
429 PARROT_WARN_UNUSED_RESULT
430 static PMC*
431 mmd_build_type_tuple_from_long_sig(PARROT_INTERP, ARGIN(STRING *long_sig))
433 ASSERT_ARGS(mmd_build_type_tuple_from_long_sig)
434 PMC *type_list = Parrot_str_split(interp, CONST_STRING(interp, ","), long_sig);
436 return mmd_build_type_tuple_from_type_list(interp, type_list);
442 =item C<PMC* Parrot_mmd_build_type_tuple_from_sig_obj(PARROT_INTERP, PMC
443 *sig_obj)>
445 Construct a FixedIntegerArray of type numbers from the arguments of a Call
446 Signature object. Used for multiple dispatch.
448 =cut
452 PARROT_EXPORT
453 PARROT_CANNOT_RETURN_NULL
454 PARROT_WARN_UNUSED_RESULT
455 PMC*
456 Parrot_mmd_build_type_tuple_from_sig_obj(PARROT_INTERP, ARGIN(PMC *sig_obj))
458 ASSERT_ARGS(Parrot_mmd_build_type_tuple_from_sig_obj)
459 PMC * const type_tuple = Parrot_pmc_new(interp, enum_class_ResizableIntegerArray);
460 STRING *string_sig = VTABLE_get_string(interp, sig_obj);
461 INTVAL args_ended = 0;
462 INTVAL i, seen_invocant = 0;
463 INTVAL sig_len;
465 if (STRING_IS_NULL(string_sig)) {
466 Parrot_ex_throw_from_c_args(interp, NULL, 1,
467 "Call has no signature, unable to dispatch.\n");
470 sig_len = Parrot_str_byte_length(interp, string_sig);
472 for (i = 0; i < sig_len; ++i) {
473 INTVAL type = Parrot_str_indexed(interp, string_sig, i + seen_invocant);
474 if (args_ended)
475 break;
477 /* Regular arguments just set the value */
478 switch (type) {
479 case 'I':
480 VTABLE_set_integer_keyed_int(interp, type_tuple,
481 i, enum_type_INTVAL);
482 break;
483 case 'N':
484 VTABLE_set_integer_keyed_int(interp, type_tuple,
485 i, enum_type_FLOATVAL);
486 break;
487 case 'S':
489 INTVAL type_lookahead = Parrot_str_indexed(interp, string_sig, (i + 1));
490 if (type_lookahead == 'n') {
491 args_ended = 1;
492 break;
494 VTABLE_set_integer_keyed_int(interp, type_tuple,
495 i, enum_type_STRING);
496 break;
498 case 'P':
500 INTVAL type_lookahead = Parrot_str_indexed(interp, string_sig, (i + 1));
501 if (type_lookahead == 'i') {
502 if (i != 0)
503 Parrot_ex_throw_from_c_args(interp, NULL,
504 EXCEPTION_INVALID_OPERATION,
505 "Multiple Dispatch: only the first argument can be an invocant");
506 seen_invocant = 1;
508 else if (type_lookahead == 'f') {
509 args_ended = 1;
510 break;
512 else {
513 PMC *pmc_arg = VTABLE_get_pmc_keyed_int(interp, sig_obj, i);
514 if (PMC_IS_NULL(pmc_arg))
515 VTABLE_set_integer_keyed_int(interp, type_tuple,
516 i, enum_type_PMC);
517 else
518 VTABLE_set_integer_keyed_int(interp, type_tuple, i,
519 VTABLE_type(interp, pmc_arg));
522 break;
524 case '-':
525 args_ended = 1;
526 break;
527 default:
528 Parrot_ex_throw_from_c_args(interp, NULL,
529 EXCEPTION_INVALID_OPERATION,
530 "Multiple Dispatch: invalid argument type %c!", type);
534 return type_tuple;
540 =item C<static PMC* mmd_cvt_to_types(PARROT_INTERP, PMC *multi_sig)>
542 Given a ResizablePMCArray PMC containing some form of type identifier (either
543 the string name of a class or a PMC representing the type), resolves all type
544 references to type IDs, if possible. If that's not possible, returns PMCNULL.
545 In that case you can't dispatch to the multi variant with this type signature,
546 as Parrot doesn't yet know about the respective types requested -- you have to
547 register them first.
549 Otherwise, returns a ResizableIntegerArray PMC full of type IDs representing
550 the signature of a multi variant to which you may be able to dispatch.
552 {{**DEPRECATE**}}
554 =cut
558 PARROT_WARN_UNUSED_RESULT
559 PARROT_CAN_RETURN_NULL
560 static PMC*
561 mmd_cvt_to_types(PARROT_INTERP, ARGIN(PMC *multi_sig))
563 ASSERT_ARGS(mmd_cvt_to_types)
564 PMC *ar = PMCNULL;
565 const INTVAL n = VTABLE_elements(interp, multi_sig);
566 INTVAL i;
568 for (i = 0; i < n; ++i) {
569 PMC * const sig_elem = VTABLE_get_pmc_keyed_int(interp, multi_sig, i);
570 INTVAL type;
572 if (sig_elem->vtable->base_type == enum_class_String) {
573 STRING * const sig = VTABLE_get_string(interp, sig_elem);
575 if (!sig)
576 return PMCNULL;
578 type = Parrot_pmc_get_type_str(interp, sig);
580 if (type == enum_type_undef)
581 return PMCNULL;
583 else if (sig_elem->vtable->base_type == enum_class_Integer) {
584 type = VTABLE_get_integer(interp, sig_elem);
586 else
587 type = Parrot_pmc_get_type(interp, sig_elem);
589 /* create destination PMC only as necessary */
590 if (PMC_IS_NULL(ar)) {
591 ar = Parrot_pmc_new(interp, enum_class_FixedIntegerArray);
592 VTABLE_set_integer_native(interp, ar, n);
595 VTABLE_set_integer_keyed_int(interp, ar, i, type);
598 return ar;
604 =item C<static PMC * Parrot_mmd_get_cached_multi_sig(PARROT_INTERP, PMC
605 *sub_pmc)>
607 Get the cached multisig of the given sub, if one exists. The cached signature
608 might be in different formats, so put it into a type tuple like the rest of the
609 MMD system expects.
611 =cut
615 PARROT_WARN_UNUSED_RESULT
616 PARROT_CAN_RETURN_NULL
617 static PMC *
618 Parrot_mmd_get_cached_multi_sig(PARROT_INTERP, ARGIN(PMC *sub_pmc))
620 ASSERT_ARGS(Parrot_mmd_get_cached_multi_sig)
621 if (VTABLE_isa(interp, sub_pmc, CONST_STRING(interp, "Sub"))) {
622 Parrot_Sub_attributes *sub;
623 PMC *multi_sig;
625 PMC_get_sub(interp, sub_pmc, sub);
626 multi_sig = sub->multi_signature;
628 if (multi_sig->vtable->base_type == enum_class_FixedPMCArray) {
629 PMC *converted_sig = mmd_cvt_to_types(interp, multi_sig);
631 if (PMC_IS_NULL(converted_sig))
632 return PMCNULL;
634 multi_sig = sub->multi_signature = converted_sig;
637 return multi_sig;
640 return PMCNULL;
644 #define MMD_BIG_DISTANCE 0x7fff
648 =item C<static UINTVAL mmd_distance(PARROT_INTERP, PMC *pmc, PMC *arg_tuple)>
650 Create Manhattan Distance of sub C<pmc> against given argument types.
651 0xffff is the maximum distance
653 =cut
657 static UINTVAL
658 mmd_distance(PARROT_INTERP, ARGIN(PMC *pmc), ARGIN(PMC *arg_tuple))
660 ASSERT_ARGS(mmd_distance)
661 PMC *multi_sig, *mro;
662 Parrot_Sub_attributes *sub;
663 INTVAL args, dist, i, j, n, m;
665 /* has to be a builtin multi method */
666 if (pmc->vtable->base_type == enum_class_NCI) {
667 GETATTR_NCI_multi_sig(interp, pmc, multi_sig);
668 if (PMC_IS_NULL(multi_sig)) {
669 STRING *long_sig;
671 GETATTR_NCI_long_signature(interp, pmc, long_sig);
672 multi_sig = mmd_build_type_tuple_from_long_sig(interp, long_sig);
673 SETATTR_NCI_multi_sig(interp, pmc, multi_sig);
676 else {
677 /* not a multi; no distance */
678 PMC_get_sub(interp, pmc, sub);
679 if (!sub->multi_signature)
680 return 0;
682 multi_sig = Parrot_mmd_get_cached_multi_sig(interp, pmc);
685 if (PMC_IS_NULL(multi_sig))
686 return MMD_BIG_DISTANCE;
688 n = VTABLE_elements(interp, multi_sig);
689 args = VTABLE_elements(interp, arg_tuple);
692 * arg_tuple may have more arguments - only the
693 * n multi_sig invocants are counted
695 if (args < n)
696 return MMD_BIG_DISTANCE;
698 dist = 0;
700 if (args > n)
701 dist = PARROT_MMD_MAX_CLASS_DEPTH;
703 /* now go through args */
704 for (i = 0; i < n; ++i) {
705 const INTVAL type_sig = VTABLE_get_integer_keyed_int(interp, multi_sig, i);
706 const INTVAL type_call = VTABLE_get_integer_keyed_int(interp, arg_tuple, i);
707 if (type_sig == type_call)
708 continue;
710 /* promote primitives to their PMC equivalents, as PCC will autobox
711 * the distance penalty makes primitive variants look cheaper */
712 switch (type_call) {
713 case enum_type_INTVAL:
714 if (type_sig == enum_class_Integer) { dist++; continue; }
715 break;
716 case enum_type_FLOATVAL:
717 if (type_sig == enum_class_Float) { dist++; continue; }
718 break;
719 case enum_type_STRING:
720 if (type_sig == enum_class_String) { dist++; continue; }
721 break;
722 default:
723 break;
727 * different native types are very different, except a PMC
728 * which matches any PMC
730 if (type_call <= 0 && type_sig == enum_type_PMC) {
731 dist++;
732 continue;
735 if ((type_sig <= 0 && type_sig != enum_type_PMC) || type_call <= 0) {
736 dist = MMD_BIG_DISTANCE;
737 break;
741 * now consider MRO of types the signature type has to be somewhere
742 * in the MRO of the type_call
744 mro = interp->vtables[type_call]->mro;
745 m = VTABLE_elements(interp, mro);
747 for (j = 0; j < m; ++j) {
748 PMC * const cl = VTABLE_get_pmc_keyed_int(interp, mro, j);
750 if (cl->vtable->base_type == type_sig)
751 break;
752 if (VTABLE_type(interp, cl) == type_sig)
753 break;
755 ++dist;
759 * if the type wasn't in MRO check, if any PMC matches
760 * in that case use the distance + 1 (of an any PMC parent)
762 if (j == m && type_sig != enum_type_PMC) {
763 dist = MMD_BIG_DISTANCE;
764 break;
767 ++dist;
769 #if MMD_DEBUG
771 STRING *s1, *s2;
772 if (type_sig < 0)
773 s1 = Parrot_get_datatype_name(interp, type_sig);
774 else
775 s1 = interp->vtables[type_sig]->whoami;
777 if (type_call < 0)
778 s2 = Parrot_get_datatype_name(interp, type_call);
779 else
780 s2 = interp->vtables[type_call]->whoami;
782 Parrot_io_eprintf(interp, "arg %d: dist %d sig %Ss arg %Ss\n",
783 i, dist, s1, s2);
785 #endif
788 return dist;
794 =item C<static PMC * Parrot_mmd_sort_candidates(PARROT_INTERP, PMC *arg_tuple,
795 PMC *cl)>
797 Sort the candidate list C<cl> by Manhattan Distance, returning the best
798 candidate.
800 =cut
804 PARROT_CANNOT_RETURN_NULL
805 static PMC *
806 Parrot_mmd_sort_candidates(PARROT_INTERP, ARGIN(PMC *arg_tuple), ARGIN(PMC *cl))
808 ASSERT_ARGS(Parrot_mmd_sort_candidates)
809 PMC *best_candidate = PMCNULL;
810 INTVAL best_distance = MMD_BIG_DISTANCE;
811 const INTVAL n = VTABLE_elements(interp, cl);
812 INTVAL i;
814 for (i = 0; i < n; ++i) {
815 PMC * const pmc = VTABLE_get_pmc_keyed_int(interp, cl, i);
816 const INTVAL d = mmd_distance(interp, pmc, arg_tuple);
817 if (d < best_distance) {
818 best_candidate = pmc;
819 best_distance = d;
823 return best_candidate;
829 =item C<static int Parrot_mmd_maybe_candidate(PARROT_INTERP, PMC *pmc, PMC *cl)>
831 If the candidate C<pmc> is a Sub PMC, push it on the candidate list and
832 return TRUE to stop further search.
834 If the candidate is a MultiSub remember all matching Subs and return FALSE
835 to continue searching outer scopes.
837 =cut
841 static int
842 Parrot_mmd_maybe_candidate(PARROT_INTERP, ARGIN(PMC *pmc), ARGIN(PMC *cl))
844 ASSERT_ARGS(Parrot_mmd_maybe_candidate)
845 STRING * const _sub = CONST_STRING(interp, "Sub");
846 STRING * const _multi_sub = CONST_STRING(interp, "MultiSub");
848 INTVAL i, n;
850 if (VTABLE_isa(interp, pmc, _sub)) {
851 /* a plain sub stops outer searches */
852 VTABLE_push_pmc(interp, cl, pmc);
853 return 1;
856 /* not a Sub or MultiSub - ignore */
857 if (!VTABLE_isa(interp, pmc, _multi_sub))
858 return 0;
860 /* ok we have a multi sub pmc, which is an array of candidates */
861 n = VTABLE_elements(interp, pmc);
863 for (i = 0; i < n; ++i) {
864 PMC * const multi_sub = VTABLE_get_pmc_keyed_int(interp, pmc, i);
865 VTABLE_push_pmc(interp, cl, multi_sub);
868 return 0;
874 =item C<static void mmd_search_by_sig_obj(PARROT_INTERP, STRING *name, PMC
875 *sig_obj, PMC *candidates)>
877 Search the namespace of the first argument to the sub call for matching
878 candidates.
880 =cut
884 static void
885 mmd_search_by_sig_obj(PARROT_INTERP, ARGIN(STRING *name),
886 ARGIN(PMC *sig_obj), ARGIN(PMC *candidates))
888 ASSERT_ARGS(mmd_search_by_sig_obj)
889 PMC *first_arg = VTABLE_get_pmc_keyed_int(interp, sig_obj, 0);
890 PMC *ns, *multi_sub;
892 if (PMC_IS_NULL(first_arg))
893 return;
895 ns = VTABLE_get_namespace(interp, first_arg);
897 if (PMC_IS_NULL(ns))
898 return;
900 multi_sub = Parrot_get_global(interp, ns, name);
902 if (PMC_IS_NULL(multi_sub))
903 return;
905 Parrot_mmd_maybe_candidate(interp, multi_sub, candidates);
911 =item C<static void mmd_search_global(PARROT_INTERP, STRING *name, PMC *cl)>
913 Search the builtin namespace for matching candidates.
915 =cut
919 static void
920 mmd_search_global(PARROT_INTERP, ARGIN(STRING *name), ARGIN(PMC *cl))
922 ASSERT_ARGS(mmd_search_global)
923 STRING * const multi_str = CONST_STRING(interp, "MULTI");
924 PMC * const ns = Parrot_get_namespace_keyed_str(interp,
925 interp->root_namespace, multi_str);
926 PMC *multi_sub = Parrot_get_global(interp, ns, name);
928 if (PMC_IS_NULL(multi_sub))
929 return;
931 Parrot_mmd_maybe_candidate(interp, multi_sub, cl);
937 =item C<static void mmd_add_multi_global(PARROT_INTERP, STRING *sub_name, PMC
938 *sub_obj)>
940 Create a MultiSub, or add a variant to an existing MultiSub. The MultiSub is
941 stored in the global MULTI namespace.
943 =cut
947 static void
948 mmd_add_multi_global(PARROT_INTERP, ARGIN(STRING *sub_name), ARGIN(PMC *sub_obj))
950 ASSERT_ARGS(mmd_add_multi_global)
951 STRING * const multi_str = CONST_STRING(interp, "MULTI");
952 PMC * const ns = Parrot_make_namespace_keyed_str(interp,
953 interp->root_namespace, multi_str);
954 PMC *multi_sub = Parrot_get_global(interp, ns, sub_name);
956 if (PMC_IS_NULL(multi_sub)) {
957 multi_sub = Parrot_pmc_new_constant(interp, enum_class_MultiSub);
958 Parrot_set_global(interp, ns, sub_name, multi_sub);
961 PARROT_ASSERT(multi_sub->vtable->base_type == enum_class_MultiSub);
962 VTABLE_push_pmc(interp, multi_sub, sub_obj);
968 =item C<static void mmd_add_multi_to_namespace(PARROT_INTERP, STRING *ns_name,
969 STRING *sub_name, PMC *sub_obj)>
971 Create a MultiSub, or add a variant to an existing MultiSub. The MultiSub is
972 added as a method to a class.
974 =cut
978 static void
979 mmd_add_multi_to_namespace(PARROT_INTERP, ARGIN(STRING *ns_name),
980 ARGIN(STRING *sub_name), ARGIN(PMC *sub_obj))
982 ASSERT_ARGS(mmd_add_multi_to_namespace)
983 PMC * const hll_ns = VTABLE_get_pmc_keyed_int(interp,
984 interp->HLL_namespace,
985 Parrot_pcc_get_HLL(interp, CURRENT_CONTEXT(interp)));
986 PMC * const ns = Parrot_make_namespace_keyed_str(interp, hll_ns, ns_name);
987 PMC *multi_sub = Parrot_get_global(interp, ns, sub_name);
989 if (PMC_IS_NULL(multi_sub)) {
990 multi_sub = Parrot_pmc_new_constant(interp, enum_class_MultiSub);
991 Parrot_set_global(interp, ns, sub_name, multi_sub);
994 PARROT_ASSERT(multi_sub->vtable->base_type == enum_class_MultiSub);
995 VTABLE_push_pmc(interp, multi_sub, sub_obj);
1001 =item C<void Parrot_mmd_add_multi_from_long_sig(PARROT_INTERP, STRING *sub_name,
1002 STRING *long_sig, PMC *sub_obj)>
1004 Create a MultiSub, or add a variant to an existing MultiSub. The MultiSub is
1005 stored in the global MULTI namespace.
1007 =cut
1011 PARROT_EXPORT
1012 void
1013 Parrot_mmd_add_multi_from_long_sig(PARROT_INTERP,
1014 ARGIN(STRING *sub_name), ARGIN(STRING *long_sig), ARGIN(PMC *sub_obj))
1016 ASSERT_ARGS(Parrot_mmd_add_multi_from_long_sig)
1017 Parrot_Sub_attributes *sub;
1018 STRING *sub_str = CONST_STRING(interp, "Sub");
1019 STRING *closure_str = CONST_STRING(interp, "Closure");
1020 PMC *type_list = Parrot_str_split(interp, CONST_STRING(interp, ","), long_sig);
1021 STRING *ns_name = VTABLE_get_string_keyed_int(interp, type_list, 0);
1023 /* Attach a type tuple array to the sub for multi dispatch */
1024 PMC *multi_sig = mmd_build_type_tuple_from_type_list(interp, type_list);
1026 if (sub_obj->vtable->base_type == enum_class_NCI) {
1027 SETATTR_NCI_multi_sig(interp, sub_obj, multi_sig);
1029 else if (VTABLE_isa(interp, sub_obj, sub_str)
1030 || VTABLE_isa(interp, sub_obj, closure_str)) {
1031 PMC_get_sub(interp, sub_obj, sub);
1032 sub->multi_signature = multi_sig;
1035 mmd_add_multi_to_namespace(interp, ns_name, sub_name, sub_obj);
1036 mmd_add_multi_global(interp, sub_name, sub_obj);
1042 =item C<void Parrot_mmd_add_multi_from_c_args(PARROT_INTERP, const char
1043 *sub_name, const char *short_sig, const char *long_sig, funcptr_t
1044 multi_func_ptr)>
1046 Create a MultiSub, or add a variant to an existing MultiSub. The MultiSub is
1047 stored in the specified namespace.
1049 =cut
1053 PARROT_EXPORT
1054 void
1055 Parrot_mmd_add_multi_from_c_args(PARROT_INTERP,
1056 ARGIN(const char *sub_name), ARGIN(const char *short_sig),
1057 ARGIN(const char *long_sig), ARGIN(funcptr_t multi_func_ptr))
1059 ASSERT_ARGS(Parrot_mmd_add_multi_from_c_args)
1060 STRING *comma = CONST_STRING(interp, ",");
1061 STRING *sub_name_str = Parrot_str_new_constant(interp, sub_name);
1062 STRING *long_sig_str = Parrot_str_new_constant(interp, long_sig);
1063 STRING *short_sig_str = Parrot_str_new_constant(interp, short_sig);
1064 PMC *type_list = Parrot_str_split(interp, comma, long_sig_str);
1065 STRING *ns_name = VTABLE_get_string_keyed_int(interp, type_list, 0);
1067 /* Create an NCI sub for the C function */
1068 PMC *sub_obj = Parrot_pmc_new_constant(interp, enum_class_NCI);
1069 PMC *multi_sig = mmd_build_type_tuple_from_long_sig(interp,
1070 long_sig_str);
1072 VTABLE_set_pointer_keyed_str(interp, sub_obj, short_sig_str,
1073 F2DPTR(multi_func_ptr));
1075 /* Attach a type tuple array to the NCI sub for multi dispatch */
1076 SETATTR_NCI_multi_sig(interp, sub_obj, multi_sig);
1078 mmd_add_multi_to_namespace(interp, ns_name, sub_name_str, sub_obj);
1079 mmd_add_multi_global(interp, sub_name_str, sub_obj);
1084 =item C<void Parrot_mmd_add_multi_list_from_c_args(PARROT_INTERP, const
1085 multi_func_list *mmd_info, INTVAL elements)>
1087 Create a collection of multiple dispatch subs from a C structure of
1088 information. Iterate through the list of details passed in. For each entry
1089 create a MultiSub or add a variant to an existing MultiSub. MultiSubs are
1090 created in the global 'MULTI' namespace in the Parrot HLL.
1092 Typically used to create all the multiple dispatch routines
1093 declared in a PMC from the PMC's class initialization function.
1095 =cut
1099 PARROT_EXPORT
1100 void
1101 Parrot_mmd_add_multi_list_from_c_args(PARROT_INTERP,
1102 ARGIN(const multi_func_list *mmd_info), INTVAL elements)
1104 ASSERT_ARGS(Parrot_mmd_add_multi_list_from_c_args)
1105 INTVAL i;
1106 for (i = 0; i < elements; ++i) {
1107 funcptr_t func_ptr = mmd_info[i].func_ptr;
1109 STRING *sub_name = mmd_info[i].multi_name;
1110 STRING *long_sig = mmd_info[i].full_sig;
1111 STRING *short_sig = mmd_info[i].short_sig;
1112 STRING *ns_name = mmd_info[i].ns_name;
1114 /* Create an NCI sub for the C function */
1115 PMC *sub_obj = Parrot_pmc_new_constant(interp, enum_class_NCI);
1117 VTABLE_set_pointer_keyed_str(interp, sub_obj, short_sig,
1118 F2DPTR(func_ptr));
1120 /* Attach a type tuple array to the NCI sub for multi dispatch */
1121 SETATTR_NCI_long_signature(interp, sub_obj, long_sig);
1123 mmd_add_multi_to_namespace(interp, ns_name, sub_name, sub_obj);
1124 mmd_add_multi_global(interp, sub_name, sub_obj);
1131 =item C<MMD_Cache * Parrot_mmd_cache_create(PARROT_INTERP)>
1133 Creates and returns a new MMD cache.
1135 =cut
1139 PARROT_EXPORT
1140 PARROT_CANNOT_RETURN_NULL
1141 MMD_Cache *
1142 Parrot_mmd_cache_create(PARROT_INTERP)
1144 ASSERT_ARGS(Parrot_mmd_cache_create)
1145 /* String hash. */
1146 Hash *cache = parrot_new_hash(interp);
1147 return cache;
1153 =item C<static STRING * mmd_cache_key_from_values(PARROT_INTERP, const char
1154 *name, PMC *values)>
1156 Generates an MMD cache key from an array of values.
1158 =cut
1162 PARROT_WARN_UNUSED_RESULT
1163 PARROT_CAN_RETURN_NULL
1164 static STRING *
1165 mmd_cache_key_from_values(PARROT_INTERP, ARGIN(const char *name),
1166 ARGIN(PMC *values))
1168 ASSERT_ARGS(mmd_cache_key_from_values)
1169 /* Build array of type IDs, which we'll then use as a string to key into
1170 * the hash. */
1171 const INTVAL num_values = VTABLE_elements(interp, values);
1172 const INTVAL name_len = name ? strlen(name) + 1: 0;
1173 const size_t id_size = num_values * sizeof (INTVAL) + name_len;
1174 INTVAL *type_ids = mem_gc_allocate_n_typed(interp, num_values + name_len, INTVAL);
1175 STRING *key;
1176 INTVAL i;
1178 for (i = 0; i < num_values; i++) {
1179 const INTVAL id = VTABLE_type(interp, VTABLE_get_pmc_keyed_int(interp, values, i));
1180 if (id == 0) {
1181 mem_gc_free(interp, type_ids);
1182 return NULL;
1185 type_ids[i] = id;
1188 if (name)
1189 strcpy((char *)(type_ids + num_values), name);
1191 key = Parrot_str_new(interp, (char *)type_ids, id_size);
1192 mem_gc_free(interp, type_ids);
1194 return key;
1200 =item C<PMC * Parrot_mmd_cache_lookup_by_values(PARROT_INTERP, MMD_Cache *cache,
1201 const char *name, PMC *values)>
1203 Takes an array of values for the call and does a lookup in the MMD cache.
1205 =cut
1209 PARROT_EXPORT
1210 PARROT_WARN_UNUSED_RESULT
1211 PARROT_CAN_RETURN_NULL
1212 PMC *
1213 Parrot_mmd_cache_lookup_by_values(PARROT_INTERP, ARGMOD(MMD_Cache *cache),
1214 ARGIN(const char *name), ARGIN(PMC *values))
1216 ASSERT_ARGS(Parrot_mmd_cache_lookup_by_values)
1217 STRING * const key = mmd_cache_key_from_values(interp, name, values);
1219 if (key)
1220 return (PMC *)parrot_hash_get(interp, cache, key);
1222 return PMCNULL;
1228 =item C<void Parrot_mmd_cache_store_by_values(PARROT_INTERP, MMD_Cache *cache,
1229 const char *name, PMC *values, PMC *chosen)>
1231 Takes an array of values for the call along with a chosen candidate and puts
1232 it into the cache.
1234 =cut
1238 PARROT_EXPORT
1239 void
1240 Parrot_mmd_cache_store_by_values(PARROT_INTERP, ARGMOD(MMD_Cache *cache),
1241 ARGIN(const char *name), ARGIN(PMC *values), ARGIN(PMC *chosen))
1243 ASSERT_ARGS(Parrot_mmd_cache_store_by_values)
1244 STRING * const key = mmd_cache_key_from_values(interp, name, values);
1246 if (key)
1247 parrot_hash_put(interp, cache, key, chosen);
1253 =item C<static STRING * mmd_cache_key_from_types(PARROT_INTERP, const char
1254 *name, PMC *types)>
1256 Generates an MMD cache key from an array of types.
1258 =cut
1262 PARROT_WARN_UNUSED_RESULT
1263 PARROT_CAN_RETURN_NULL
1264 static STRING *
1265 mmd_cache_key_from_types(PARROT_INTERP, ARGIN(const char *name),
1266 ARGIN(PMC *types))
1268 ASSERT_ARGS(mmd_cache_key_from_types)
1269 /* Build array of type IDs, which we'll then use as a string to key into
1270 * the hash. */
1271 const INTVAL num_types = VTABLE_elements(interp, types);
1272 const INTVAL name_len = name ? strlen(name) + 1: 0;
1273 const size_t id_size = num_types * sizeof (INTVAL) + name_len;
1274 INTVAL * const type_ids = mem_gc_allocate_n_typed(interp, num_types + name_len, INTVAL);
1276 STRING *key;
1277 INTVAL i;
1279 for (i = 0; i < num_types; i++) {
1280 const INTVAL id = VTABLE_get_integer_keyed_int(interp, types, i);
1282 if (id == 0) {
1283 mem_gc_free(interp, type_ids);
1284 return NULL;
1287 type_ids[i] = id;
1290 if (name)
1291 strcpy((char *)(type_ids + num_types), name);
1293 key = Parrot_str_new(interp, (char *)type_ids, id_size);
1295 mem_gc_free(interp, type_ids);
1296 return key;
1302 =item C<PMC * Parrot_mmd_cache_lookup_by_types(PARROT_INTERP, MMD_Cache *cache,
1303 const char *name, PMC *types)>
1305 Takes an array of types for the call and does a lookup in the MMD cache.
1307 =cut
1311 PARROT_EXPORT
1312 PARROT_WARN_UNUSED_RESULT
1313 PARROT_CAN_RETURN_NULL
1314 PMC *
1315 Parrot_mmd_cache_lookup_by_types(PARROT_INTERP, ARGMOD(MMD_Cache *cache),
1316 ARGIN(const char *name), ARGIN(PMC *types))
1318 ASSERT_ARGS(Parrot_mmd_cache_lookup_by_types)
1319 const STRING * const key = mmd_cache_key_from_types(interp, name, types);
1321 if (key)
1322 return (PMC *)parrot_hash_get(interp, cache, key);
1324 return PMCNULL;
1330 =item C<void Parrot_mmd_cache_store_by_types(PARROT_INTERP, MMD_Cache *cache,
1331 const char *name, PMC *types, PMC *chosen)>
1333 Takes an array of types for the call along with a chosen candidate and puts
1334 it into the cache. The name parameter is optional, and if the cache is already
1335 tied to an individual multi can be null.
1337 =cut
1341 PARROT_EXPORT
1342 void
1343 Parrot_mmd_cache_store_by_types(PARROT_INTERP, ARGMOD(MMD_Cache *cache),
1344 ARGIN(const char *name), ARGIN(PMC *types), ARGIN(PMC *chosen))
1346 ASSERT_ARGS(Parrot_mmd_cache_store_by_types)
1347 STRING * const key = mmd_cache_key_from_types(interp, name, types);
1349 if (key)
1350 parrot_hash_put(interp, cache, key, chosen);
1356 =item C<void Parrot_mmd_cache_mark(PARROT_INTERP, MMD_Cache *cache)>
1358 GC-marks an MMD cache.
1360 =cut
1364 PARROT_EXPORT
1365 void
1366 Parrot_mmd_cache_mark(PARROT_INTERP, ARGMOD(MMD_Cache *cache))
1368 ASSERT_ARGS(Parrot_mmd_cache_mark)
1369 /* As a small future optimization, note that we only *really* need to mark
1370 * keys - the candidates will be referenced outside the cache, provided it's
1371 * invalidated properly. */
1372 parrot_mark_hash(interp, cache);
1378 =item C<void Parrot_mmd_cache_destroy(PARROT_INTERP, MMD_Cache *cache)>
1380 Destroys an MMD cache.
1382 =cut
1386 PARROT_EXPORT
1387 void
1388 Parrot_mmd_cache_destroy(PARROT_INTERP, ARGMOD(MMD_Cache *cache))
1390 ASSERT_ARGS(Parrot_mmd_cache_destroy)
1391 parrot_hash_destroy(interp, cache);
1397 =back
1399 =head1 SEE ALSO
1401 F<include/parrot/multidispatch.h>,
1402 F<http://svn.perl.org/perl6/doc/trunk/design/apo/A12.pod>,
1403 F<http://svn.perl.org/perl6/doc/trunk/design/syn/S12.pod>
1405 =cut
1411 * Local variables:
1412 * c-file-style: "parrot"
1413 * End:
1414 * vim: expandtab shiftwidth=4: