Author: jonathan
Date: Wed Mar 28 16:17:55 2007
New Revision: 17818
Modified:
trunk/include/parrot/objects.h
trunk/src/objects.c
trunk/src/pmc/class.pmc
trunk/src/pmc/role.pmc
trunk/t/oo/composition.t
Log:
Implement roles doing roles. This pulls some code out of the Class PMC, since
composing a role into a class and composing a role into a role has a lot in
common and code duplication is suckful. Also a couple of tests, showing that
methods from indirect roles end up being composed and that collisions with
methods from indirect roles are detected.
Modified: trunk/include/parrot/objects.h
==============================================================================
--- trunk/include/parrot/objects.h (original)
+++ trunk/include/parrot/objects.h Wed Mar 28 16:17:55 2007
@@ -106,6 +106,11 @@
PARROT_API PMC* Parrot_ComputeMRO_C3(Interp *interp, PMC *class);
+PARROT_API void Parrot_ComposeRole(Interp *interp, PMC *role,
+ PMC *without, int got_without,
+ PMC *alias, int got_alias,
+ PMC *methods_hash, PMC *roles_list);
+
#endif /* PARROT_OBJECTS_H_GUARD */
/*
Modified: trunk/src/objects.c
==============================================================================
--- trunk/src/objects.c (original)
+++ trunk/src/objects.c Wed Mar 28 16:17:55 2007
@@ -1846,6 +1846,159 @@
/*
+=item C<void Parrot_ComposeRole(Interp *interp, PMC *role,
+ PMC *without, int got_without,
+ PMC *alias, int got_alias,
+ PMC *methods_hash, PMC *roles_list)>
+
+Used by the Class and Object PMCs internally to compose a role into either of
+them. The C<role> parameter is the role that we are composing into the class
+or role. C<methods_hash> is the hash of method names to invokable PMCs that
+contains the methods the class or role has. C<roles_list> is the list of roles
+the the class or method does.
+
+The C<role> parameter is only dealt with by its external interface. Whether
+this routine is usable by any other object system implemented in Parrot very
+much depends on how closely the role composition semantics they want are to
+the default implementation.
+
+=cut
+
+*/
+
+void Parrot_ComposeRole(Interp *interp, PMC *role,
+ PMC *without, int got_without,
+ PMC *alias, int got_alias,
+ PMC *methods_hash, PMC *roles_list)
+{
+ PMC *methods;
+ PMC *methods_iter;
+ PMC *roles_of_role;
+ PMC *proposed_add_methods;
+ int i, j, roles_count, roles_of_role_count;
+
+ /* Check we have not already composed the role; if so, just ignore it. */
+ roles_count = VTABLE_elements(interp, roles_list);
+ for (i = 0; i < roles_count; i++) {
+ if (VTABLE_get_pmc_keyed_int(interp, roles_list, i) == role)
+ return;
+ }
+
+ /* Get the methods from the role. */
+ Parrot_PCCINVOKE(interp, role, string_from_const_cstring(interp,
"methods", 0),
+ "->P", &methods);
+ if (PMC_IS_NULL(methods))
+ return;
+
+ /* We need to check for conflicts before we do the composition. We
+ * put each method that would be OK to add into a proposal list, and
+ * bail out right away if we find a problem. */
+ proposed_add_methods = pmc_new(interp, enum_class_Hash);
+ methods_iter = VTABLE_get_iter(interp, methods);
+ while (VTABLE_get_bool(interp, methods_iter)) {
+ /* Get current method and its name. */
+ PMC *method_name_pmc = VTABLE_shift_pmc(interp, methods_iter);
+ STRING *method_name = VTABLE_get_string(interp, method_name_pmc);
+ PMC *cur_method = VTABLE_get_pmc_keyed(interp, methods,
method_name_pmc);
+
+ /* Need to find the name we'll check for a conflict on. */
+ STRING *check_name = method_name;
+
+ /* Ignore if it's in the exclude list. */
+ if (got_without) {
+ int without_count = VTABLE_elements(interp, without);
+ for (i = 0; i < without_count; i++) {
+ STRING *check = VTABLE_get_string_keyed_int(interp, without,
i);
+ if (string_equal(interp, check, method_name) == 0) {
+ check_name = NULL;
+ break;
+ }
+ }
+ }
+
+ /* If we're not in the exclude list, now see if we've an alias. */
+ if (check_name != NULL && got_alias) {
+ if (VTABLE_exists_keyed_str(interp, alias, method_name))
+ check_name = VTABLE_get_string_keyed_str(interp, alias,
method_name);
+ }
+
+ /* If we weren't excluded... */
+ if (check_name != NULL) {
+ /* Is there a method with this name already in the class?
+ * XXX TODO: multi-method handling. */
+ if (VTABLE_exists_keyed_str(interp, methods_hash, check_name)) {
+ /* Conflicts with something already in the class. */
+ if (check_name == method_name)
+ real_exception(interp, NULL,
ROLE_COMPOSITOIN_METH_CONFLICT,
+ "A conflict occurred during role composition due to
method '%S'.",
+ method_name);
+ else
+ real_exception(interp, NULL,
ROLE_COMPOSITOIN_METH_CONFLICT,
+ "A conflict occurred during role composition due to
the aliasing of '%S' to '%S'.",
+ method_name, check_name);
+ return;
+ }
+
+ /* What about a conflict with ourslef? */
+ if (VTABLE_exists_keyed_str(interp, proposed_add_methods,
check_name)) {
+ /* If it's due to aliasing, say so. Otherwise, something
+ * very weird is going on. */
+ if (check_name != method_name)
+ real_exception(interp, NULL,
ROLE_COMPOSITOIN_METH_CONFLICT,
+ "A conflict occurred during role composition; '%S' was
aliased to '%S', but the role already has a '%S'.",
+ method_name, check_name, check_name);
+ else
+ real_exception(interp, NULL,
ROLE_COMPOSITOIN_METH_CONFLICT,
+ "A conflict occurred during role composition; the
method '%S' from the role managed to conflict with itself somehow.",
+ method_name);
+ return;
+ }
+
+ /* If we got here, no conflicts! Add it to the "to compose" list.
*/
+ VTABLE_set_pmc_keyed_str(interp, proposed_add_methods, check_name,
cur_method);
+ }
+ }
+
+ /* If we get here, we detected no conflicts. Go ahead and compose the
methods. */
+ methods_iter = VTABLE_get_iter(interp, proposed_add_methods);
+ while (VTABLE_get_bool(interp, methods_iter)) {
+ /* Get current method and its name. */
+ PMC *method_name_pmc = VTABLE_shift_pmc(interp, methods_iter);
+ STRING *method_name = VTABLE_get_string(interp, method_name_pmc);
+ PMC *cur_method = VTABLE_get_pmc_keyed(interp, proposed_add_methods,
+ method_name_pmc);
+
+ /* Add it to the methods of the class. */
+ VTABLE_set_pmc_keyed_str(interp, methods_hash, method_name,
+ cur_method);
+ }
+
+ /* Add this role to the roles list. */
+ VTABLE_push_pmc(interp, roles_list, role);
+ roles_count++;
+
+ /* As a result of composing this role, we will also now do the roles
+ * that it did itself. Note that we already have the correct methods
+ * as roles "flatten" the methods they get from other roles into their
+ * own method list. */
+ Parrot_PCCINVOKE(interp, role, string_from_const_cstring(interp, "roles",
0),
+ "->P", &roles_of_role);
+ roles_of_role_count = VTABLE_elements(interp, roles_of_role);
+ for (i = 0; i < roles_of_role_count; i++) {
+ /* Only add if we don't already have it in the list. */
+ PMC *cur_role = VTABLE_get_pmc_keyed_int(interp, roles_of_role, i);
+ for (j = 0; j < roles_count; j++) {
+ if (VTABLE_get_pmc_keyed_int(interp, roles_list, j) == cur_role) {
+ /* We ain't be havin' it. */
+ VTABLE_push_pmc(interp, roles_list, cur_role);
+ }
+ }
+ }
+}
+
+
+/*
+
=back
=head1 SEE ALSO
Modified: trunk/src/pmc/class.pmc
==============================================================================
--- trunk/src/pmc/class.pmc (original)
+++ trunk/src/pmc/class.pmc Wed Mar 28 16:17:55 2007
@@ -469,108 +469,8 @@
*/
PCCMETHOD void add_role(PMC* role, PMC* without :optional
:named["without"], int got_without :opt_flag, PMC* alias :optional
:named["alias"], int got_alias :opt_flag) {
Parrot_Class *class = PARROT_CLASS(SELF);
- PMC *methods;
- PMC *methods_iter;
- PMC *proposed_add_methods;
- int i, roles_count;
-
- /* Check we have not already composed the role; if so, just ignore it.
*/
- roles_count = VTABLE_elements(interp, class->roles);
- for (i = 0; i < roles_count; i++) {
- if (VTABLE_get_pmc_keyed_int(interp, class->roles, i) == role)
- return;
- }
-
- /* Get the methods from the role. */
- (PMC *methods) = PCCINVOKE(interp, role, "methods");
- if (PMC_IS_NULL(methods))
- return;
-
- /* We need to check for conflicts before we do the composition. We
- * put each method that would be OK to add into a proposal list, and
- * bail out right away if we find a problem. */
- proposed_add_methods = pmc_new(interp, enum_class_Hash);
- methods_iter = VTABLE_get_iter(interp, methods);
- while (VTABLE_get_bool(interp, methods_iter)) {
- /* Get current method and its name. */
- PMC *method_name_pmc = VTABLE_shift_pmc(interp, methods_iter);
- STRING *method_name = VTABLE_get_string(interp, method_name_pmc);
- PMC *cur_method = VTABLE_get_pmc_keyed(interp, methods,
method_name_pmc);
-
- /* Need to find the name we'll check for a conflict on. */
- STRING *check_name = method_name;
-
- /* Ignore if it's in the exclude list. */
- if (got_without) {
- int without_count = VTABLE_elements(interp, without);
- for (i = 0; i < without_count; i++) {
- STRING *check = VTABLE_get_string_keyed_int(interp,
without, i);
- if (string_equal(interp, check, method_name) == 0) {
- check_name = NULL;
- break;
- }
- }
- }
-
- /* If we're not in the exclude list, now see if we've an alias. */
- if (check_name != NULL && got_alias) {
- if (VTABLE_exists_keyed_str(interp, alias, method_name))
- check_name = VTABLE_get_string_keyed_str(interp, alias,
method_name);
- }
-
- /* If we weren't excluded... */
- if (check_name != NULL) {
- /* Is there a method with this name already in the class?
- * XXX TODO: multi-method handling. */
- if (VTABLE_exists_keyed_str(interp, class->methods,
check_name)) {
- /* Conflicts with something already in the class. */
- if (check_name == method_name)
- real_exception(interp, NULL,
ROLE_COMPOSITOIN_METH_CONFLICT,
- "A conflict occurred during role composition due
to method '%S'.",
- method_name);
- else
- real_exception(interp, NULL,
ROLE_COMPOSITOIN_METH_CONFLICT,
- "A conflict occurred during role composition due
to the aliasing of '%S' to '%S'.",
- method_name, check_name);
- return;
- }
-
- /* What about a conflict with ourslef? */
- if (VTABLE_exists_keyed_str(interp, proposed_add_methods,
check_name)) {
- /* If it's due to aliasing, say so. Otherwise, something
- * very weird is going on. */
- if (check_name != method_name)
- real_exception(interp, NULL,
ROLE_COMPOSITOIN_METH_CONFLICT,
- "A conflict occurred during role composition; '%S'
was aliased to '%S', but the role already has a '%S'.",
- method_name, check_name, check_name);
- else
- real_exception(interp, NULL,
ROLE_COMPOSITOIN_METH_CONFLICT,
- "A conflict occurred during role composition; the
method '%S' from the role managed to conflict with itself somehow.",
- method_name);
- return;
- }
-
- /* If we got here, no conflicts! Add it to the "to compose"
list. */
- VTABLE_set_pmc_keyed_str(interp, proposed_add_methods,
check_name, cur_method);
- }
- }
-
- /* If we get here, we detected no conflicts. Go ahead and compose the
methods. */
- methods_iter = VTABLE_get_iter(interp, proposed_add_methods);
- while (VTABLE_get_bool(interp, methods_iter)) {
- /* Get current method and its name. */
- PMC *method_name_pmc = VTABLE_shift_pmc(interp, methods_iter);
- STRING *method_name = VTABLE_get_string(interp, method_name_pmc);
- PMC *cur_method = VTABLE_get_pmc_keyed(interp,
proposed_add_methods,
- method_name_pmc);
-
- /* Add it to the methods of the class. */
- VTABLE_set_pmc_keyed_str(interp, class->methods, method_name,
- cur_method);
- }
-
- /* Add this role to the roles list. */
- VTABLE_push_pmc(interp, class->roles, role);
+ Parrot_ComposeRole(interp, role, without, got_without, alias,
got_alias,
+ class->methods, class->roles);
}
/*
Modified: trunk/src/pmc/role.pmc
==============================================================================
--- trunk/src/pmc/role.pmc (original)
+++ trunk/src/pmc/role.pmc Wed Mar 28 16:17:55 2007
@@ -277,6 +277,21 @@
}
}
+/*
+
+=item C<void add_role(PMC* role, PMC* without :optional :named["without"],
PMC* alias :optional :named["alias"])>
+
+Compose the given role into this one, using the given exclusions and aliases.
+
+=cut
+
+*/
+ PCCMETHOD void add_role(PMC* role, PMC* without :optional
:named["without"], int got_without :opt_flag, PMC* alias :optional
:named["alias"], int got_alias :opt_flag) {
+ Parrot_Role *role_info = PARROT_ROLE(SELF);
+ Parrot_ComposeRole(interp, role, without, got_without, alias,
got_alias,
+ role_info->methods, role_info->roles);
+ }
+
} /* END pmclass */
/*
Modified: trunk/t/oo/composition.t
==============================================================================
--- trunk/t/oo/composition.t (original)
+++ trunk/t/oo/composition.t Wed Mar 28 16:17:55 2007
@@ -6,7 +6,7 @@
use warnings;
use lib qw( . lib ../lib ../../lib );
use Test::More;
-use Parrot::Test tests => 7;
+use Parrot::Test tests => 9;
=head1 NAME
@@ -294,6 +294,92 @@
ok 5 - called method from role that was aliased
OUT
+pir_output_is( <<'CODE', <<'OUT', 'role that does a role' );
+.sub 'test' :main
+ .local pmc PHB, Manage, FirePeople
+
+ FirePeople = new Role
+ $P0 = find_global 'fire'
+ FirePeople.'add_method'("fire", $P0)
+
+ Manage = new Role
+ $P0 = find_global 'give_payrise'
+ FirePeople.'add_method'("give_payrise", $P0)
+ Manage.'add_role'(FirePeople)
+ print "ok 1 - adding one role to another happens\n"
+
+ PHB = new Class
+ PHB.'add_role'(Manage)
+ print "ok 2 - added one rule that does another role to the class\n"
+
+ $P0 = PHB.'new'()
+ $P0.give_payrise()
+ print "ok 3 - called method from direct role\n"
+
+ $P0.fire()
+ print "ok 4 - called method from indirect role\n"
+.end
+
+.sub fire
+ print "You're FIRED!\n"
+.end
+.sub give_payrise
+ print "You all get a pay rise of 0.0005%.\n"
+.end
+CODE
+ok 1 - adding one role to another happens
+ok 2 - added one rule that does another role to the class
+You all get a pay rise of 0.0005%.
+ok 3 - called method from direct role
+You're FIRED!
+ok 4 - called method from indirect role
+OUT
+
+pir_output_is( <<'CODE', <<'OUT', 'conflict from indirect role' );
+.sub 'test' :main
+ .local pmc BurninatorBoss, Manage, FirePeople, Burninator
+
+ FirePeople = new Role
+ $P0 = find_global 'fire'
+ FirePeople.'add_method'("fire", $P0)
+
+ Manage = new Role
+ $P0 = find_global 'give_payrise'
+ FirePeople.'add_method'("give_payrise", $P0)
+ Manage.'add_role'(FirePeople)
+
+ Burninator = new Role
+ $P0 = find_global 'fire2'
+ Burninator.'add_method'("fire", $P0)
+ print "ok 1 - all roles created\n"
+
+ BurninatorBoss = new Class
+ BurninatorBoss.'add_role'(Manage)
+ print "ok 2 - added first role with indirect role\n"
+
+ push_eh OK_3
+ BurninatorBoss.'add_role'(Burninator)
+ print "not "
+ clear_eh
+OK_3:
+ print "ok 3 - second role conflicts with method from indirect role\n"
+.end
+
+.sub fire
+ print "You're FIRED!\n"
+.end
+.sub fire2
+ print "BURNINATION!\n"
+.end
+.sub give_payrise
+ print "You all get a pay rise of 0.0005%.\n"
+.end
+CODE
+ok 1 - all roles created
+ok 2 - added first role with indirect role
+ok 3 - second role conflicts with method from indirect role
+OUT
+
# Local Variables:
# mode: cperl
# cperl-indent-level: 4