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

Reply via email to