/*
Copyright (C) 2005, The Perl Foundation.
$Id: namespace.pmc 17084 2007-02-20 20:03:37Z paultcochrane $

=head1 NAME

src/pmc/namespace.pmc - NameSpace PMC

=head1 DESCRIPTION

These are the vtable functions for the namespace PMC.

=head2 Data

  PMC_struct_val       ... the hash, bucket->value is either a
                           var/sub or a namespace, of a FixedPMCarray
                           of 2 PMCs (namespace, sub/var) slots
  PMC_pmc_val          ... parent namespace
  PMC_data             ... name STRING of this namespace part

=head2 Functions

=over 4

=cut

*/

#include "parrot/parrot.h"
#include <assert.h>

/*
 * Typically a named slot either contains another namespace or a
 * var/sub (not both).
 * In case that the bucket->value is occupied, a FixedPMCArray is
 * created, and the items are moved over to that extra storage.
 * The array is flagged with FPA_is_ns_ext to distinguish it from a
 * plain array variable.
 *
 * This could easily expanded to a full-fledged typed namespace if needed.
 */

enum {
    NS_slot_ns,
    NS_slot_var_sub,         /* unspecified ~half-raw slot */
    NS_max_slots
} NS_slot_enum;

#define FPA_is_ns_ext PObj_private0_FLAG

pmclass NameSpace extends Hash does hash need_ext no_ro {

/*

=item C<void init()>

Initialize a C<NameSpace> PMC by calling C<Hash.init> and clearing
other fields.

=cut

*/

    void init() {
        SUPER();                        /* _struct_val := Hash */
        PMC_pmc_val(SELF) = NULL;       /* parent */
        PMC_data(SELF)    = NULL;       /* namespace name */
    }

/*

=item C<void mark()>

Marks the namespace as live.

=cut

*/
    void mark() {
        SUPER();
        if (PMC_pmc_val(SELF))
            pobject_lives(INTERP, (PObj*)PMC_pmc_val(SELF));
        if (PMC_data(SELF))
            pobject_lives(INTERP, (PObj*)PMC_data(SELF));
    }

/*

=item C<void set_pmc_keyed_str(STRING *key, PMC *value)>

Sets C<*value> as the namespace item for C<*key>. This is part of the
raw interface. If the PMC C<value> is exactly a NameSpace, C<SELF>
will be set as the parent of that namespace and the name C<key> of
C<value> is stored too.

=item C<void set_pmc_keyed(PMC *key, PMC *value)>

If C<key> is a simple key, it works like above. If C<key> is an array
of strings or a chained key, add all components to the namespace.

=item C<PMC* get_pmc_keyed(PMC *key)>

Return the given namespace or PMCNULL. C<key> is either an array of
strings, or a possibly nested key.

=item C<PMC* get_pmc_keyed_str(PMC *key)>

Return the given namespace item or PMCNULL. If the named item is either
a NameSpace or a var, the NameSpace is returned.

=cut

*/

    void set_pmc_keyed_str(STRING *key, PMC *value) {
        const int val_is_NS = value->vtable->base_type == enum_class_NameSpace;
        HashBucket *b =
            parrot_hash_get_bucket(INTERP, (Hash*) PMC_struct_val(SELF), key);
        PMC *new_tuple = NULL;

        if (!b)
            SUPER(key, value);
        else {
            PMC *old = b->value;
            if ((old->vtable->base_type == enum_class_NameSpace) == val_is_NS) {
                /* simple ns or simple var/sub changed */
                SUPER(key, value);
            }
            else if ((PObj_get_FLAGS(old) & FPA_is_ns_ext) &&
                    old->vtable->base_type == enum_class_FixedPMCArray) {
                /* we have a tuple extension already */
                VTABLE_set_pmc_keyed_int(INTERP, old,
                        val_is_NS ? NS_slot_ns : NS_slot_var_sub,
                        value);
            }
            else {
                /* creat new tuple */
                new_tuple = pmc_new(INTERP, enum_class_FixedPMCArray);
                /* flag it as special */
                PObj_get_FLAGS(new_tuple) |= FPA_is_ns_ext;
                /* for a fully typed namespace, we'd need 3 or 4 */
                VTABLE_set_integer_native(INTERP, new_tuple, NS_max_slots);
            }
        }

        if (val_is_NS) {
            /* TODO - this hack needs to go */
            PMC_pmc_val(value) = SELF;  /* set parent */
            PMC_data(value)    = key;   /* and name */

            if (new_tuple) {
                VTABLE_set_pmc_keyed_int(INTERP, new_tuple, NS_slot_ns,
                                         value);
                VTABLE_set_pmc_keyed_int(INTERP, new_tuple, NS_slot_var_sub,
                                         b->value);
                b->value = new_tuple;
                /* distinction from a plain FPA, which doesn't extend the
                 * namespace storage
                 */
            }
        }
        else if (new_tuple) {
            VTABLE_set_pmc_keyed_int(INTERP, new_tuple, NS_slot_ns, b->value);
            VTABLE_set_pmc_keyed_int(INTERP, new_tuple, NS_slot_var_sub, value);
            b->value = new_tuple;
        }
    }

    void set_pmc_keyed(PMC *key, PMC *value) {
        PMC *ns = SELF;
        STRING *part;

        if (key->vtable->base_type == enum_class_String) {
            SELF.set_pmc_keyed_str(VTABLE_get_string(INTERP, key), value);
            return;
        }

        if (key->vtable->base_type == enum_class_Key) {
            while (1) {
                part = key_string(INTERP, key);
                key = key_next(INTERP, key);
                if (!key) {
                    Parrot_set_global(INTERP, ns, part, value);
                    return;
                }
                ns = Parrot_make_namespace_keyed_str(INTERP, ns, part);
            }
        }

        real_exception(INTERP, NULL, E_NameError, "Invalid namespace key");
    }

    PMC* get_pmc_keyed_str(STRING* key) {
        PMC *ns = parrot_hash_get(INTERP, (Hash*) PMC_struct_val(SELF), key);
        if (!ns)
            return PMCNULL;
        if ((PObj_get_FLAGS(ns) & FPA_is_ns_ext) &&
                ns->vtable->base_type == enum_class_FixedPMCArray)
            ns = VTABLE_get_pmc_keyed_int(INTERP, ns, NS_slot_ns);
        return ns;
    }

    PMC* get_pmc_keyed(PMC *key) {
        PMC *ns = SELF;
        STRING *part;

        if (key->vtable->base_type == enum_class_String) {
            return SELF.get_pmc_keyed_str(VTABLE_get_string(INTERP, key));
        }

        if (key->vtable->base_type == enum_class_Key) {
            while (key) {
                part = key_string(INTERP, key);
                key = key_next(INTERP, key);
                if (!key)
                    return VTABLE_get_pmc_keyed_str(INTERP, ns, part);
                ns = Parrot_get_namespace_keyed_str(INTERP, ns, part);
                if (PMC_IS_NULL(ns))
                    return PMCNULL;
            }
            return ns;
        }

        real_exception(INTERP, NULL, E_NameError, "Invalid namespace key");
        /* NOTREACHED */
        return PMCNULL;
    }

/*

=item C<void* get_pointer_keyed_str(STRING *key)>

=item C<void* get_pointer_keyed(PMC *key)>

Return the given namespace item or PMCNULL. If the named item is either
a NameSpace or a var, the var is returned.

TOTAL KLUDGE.  ON THE CHOPPING BLOCK.

=cut

*/

    void* get_pointer_keyed_str(STRING* key) {
        PMC *ns = parrot_hash_get(INTERP, (Hash*) PMC_struct_val(SELF), key);
        if (!ns)
            return PMCNULL;
        if ((PObj_get_FLAGS(ns) & FPA_is_ns_ext) &&
                ns->vtable->base_type == enum_class_FixedPMCArray)
            ns = VTABLE_get_pmc_keyed_int(INTERP, ns, NS_slot_var_sub);
        return ns;
    }

    void* get_pointer_keyed(PMC *key) {
        PMC *ns = SELF;
        STRING *part;

        if (key->vtable->base_type == enum_class_String) {
            return SELF.get_pointer_keyed_str(VTABLE_get_string(INTERP, key));
        }

        if (key->vtable->base_type == enum_class_Key) {
            while (key) {
                part = key_string(INTERP, key);
                key = key_next(INTERP, key);
                if (!key)
                    return VTABLE_get_pointer_keyed_str(INTERP, ns, part);
                ns = Parrot_get_namespace_keyed_str(INTERP, ns, part);
                if (PMC_IS_NULL(ns))
                    return PMCNULL;
            }
            return ns;
        }

        real_exception(INTERP, NULL, E_NameError, "Invalid namespace key");
        /* NOTREACHED */
        return PMCNULL;
    }

/*

=item C<STRING* get_string()>

Return the name of this namespace part.

=cut

*/

    STRING* get_string() {
        return PMC_data(SELF);
    }

/*

=back

=head2 Methods

=over 4

=cut

*/

/*

=item C<METHOD void PMC* add_namespace(STRING *name, PMC *namespace)>

Stores the given namespace under this namespace, with the given name.  Throws
an invalid type exception if C<namespace> is not a NameSpace PMC or subclass.

*/

    METHOD void add_namespace(STRING *name, PMC *namespace) {
        STRING *s_ns = CONST_STRING(INTERP, "NameSpace");
        if (!VTABLE_isa(INTERP, namespace, s_ns))
            real_exception(INTERP, NULL, E_TypeError,
                "Invalid type %d in add_namespace()",
                namespace->vtable->base_type);

        VTABLE_set_pmc_keyed_str(INTERP, SELF, name, namespace);
        return;
    }

/*

/*

=item C<METHOD void PMC* add_sub(STRING *name, PMC *sub)>

Stores the given sub under this namespace, with the given name.  Throws an
invalid type exception if C<sub> is not a Sub PMC or subclass.

*/

    METHOD void add_sub(STRING *name, PMC *sub) {
        STRING *s_sub = CONST_STRING(INTERP, "Sub");
        if (!VTABLE_isa(INTERP, sub, s_sub))
            real_exception(INTERP, NULL, E_TypeError,
                "Invalid type %d in add_sub()", sub->vtable->base_type);

        VTABLE_set_pmc_keyed_str(INTERP, SELF, name, sub);
        return;
    }

/*

/*

=item C<METHOD void PMC* add_var(STRING *name, PMC *var)>

Stores the given sub under this namespace, with the given name.

*/

    METHOD void add_var(STRING *name, PMC *var) {
        VTABLE_set_pmc_keyed_str(INTERP, SELF, name, var);
        return;
    }

/*

=item C<METHOD PMC* get_name()>

Returns the name of the namespace as an array of strings.

  $P2 = $P3.'get_name'()
  $S0 = join '::', $P2            # '::Foo::Bar'

=cut

*/

    METHOD PMC* get_name() {
        PMC *ar, *ns;

        ar = pmc_new(INTERP, enum_class_ResizableStringArray);
        ns = SELF;
        while (ns) {
            VTABLE_unshift_string(INTERP, ar, PMC_data(ns));
            ns = PMC_pmc_val(ns);
        }

        /* remove the NULL string of the namespace root */
        VTABLE_shift_string(INTERP, ar);

        return ar;
    }
/*

=item C<METHOD PMC* find_namespace(STRING* name)>

Return the namespace with the given name.

=cut

*/

    METHOD PMC* find_namespace(STRING* key) {
        STRING *s_ns = CONST_STRING(INTERP, "NameSpace");
        PMC *ns = parrot_hash_get(INTERP, (Hash*) PMC_struct_val(SELF), key);

        if (!ns)
            return PMCNULL;

        /* it's a NameSpace */
        if (VTABLE_isa(INTERP, ns, s_ns))
            return ns;

        return PMCNULL;
    }

/*

=item C<METHOD PMC* find_sub(STRING* name)>

Return the Sub PMC with the given name.

=cut

*/

    METHOD PMC* find_sub(STRING* key) {
        STRING *s_sub = CONST_STRING(INTERP, "Sub");
        PMC *sub = parrot_hash_get(INTERP, (Hash*) PMC_struct_val(SELF), key);

        if (!sub)
            return PMCNULL;

        /* it's a Sub */
        if (VTABLE_isa(INTERP, sub, s_sub))
            return sub;

        return PMCNULL;
    }

/*

=item C<METHOD PMC* find_var(STRING* name)>

Return the PMC with the given name.

=cut

*/

    METHOD PMC* find_var(STRING* key) {
        PMC *val = parrot_hash_get(INTERP, (Hash*) PMC_struct_val(SELF), key);

        if (!val)
            return PMCNULL;

        return val;
    }

/*

=item C<METHOD PMC* del_namespace(STRING* name)>

Deletes the contained NameSpace PMC with the given name.  Throws an invalid
type exception if the item to delete is not a NameSpace PMC or subclass, and
does not delete the PMC.

=cut

*/

    METHOD void del_namespace(STRING* name) {
        Hash   *hash = (Hash *) PMC_struct_val(SELF);
        PMC    *ns   = parrot_hash_get(INTERP, hash, name);
        STRING *s_ns = CONST_STRING(INTERP, "NameSpace");

        if (ns == PMCNULL)
            return;

        if (!VTABLE_isa(INTERP, ns, s_ns))
            real_exception(INTERP, NULL, E_TypeError,
                "Invalid type %d for '%s' in del_namespace()",
                ns->vtable->base_type, string_to_cstring(INTERP, name));

        parrot_hash_delete(INTERP, hash, name);
    }

/*

/*

=item C<METHOD PMC* del_sub(STRING* name)>

Deletes the contained Sub PMC with the given name.  Throws an invalid type
exception if the item to delete is not a Sub PMC or subclass, and does not
delete the PMC.

=cut

*/

    METHOD void del_sub(STRING* name) {
        Hash   *hash  = (Hash *) PMC_struct_val(SELF);
        PMC    *sub   = parrot_hash_get(INTERP, hash, name);
        STRING *s_sub = CONST_STRING(INTERP, "Sub");

        if (sub == PMCNULL)
            return;

        if (!VTABLE_isa(INTERP, sub, s_sub))
            real_exception(INTERP, NULL, E_TypeError,
                "Invalid type %d for '%s' in del_sub()",
                sub->vtable->base_type, string_to_cstring(INTERP, name));

        parrot_hash_delete(INTERP, hash, name);
    }

/*

=item C<METHOD PMC* del_var(STRING* name)>

Deletes the contained variable-like PMC with the given name.

=cut

*/

    METHOD void del_var(STRING* name) {
        parrot_hash_delete(INTERP, (Hash*) PMC_struct_val(SELF), name);
    }

/*

=item C<METHOD PMC* get_sym(STRING* name)>

Return the symbol (var or sub) with the given name. This can be used
to retrieve symbols, if a NameSpace with the same name exists.

=cut

*/

    METHOD PMC* get_sym(STRING* key) {
        PMC *ns = parrot_hash_get(INTERP, (Hash*) PMC_struct_val(SELF), key);
        if (!ns)
            return PMCNULL;
        if (ns->vtable == SELF->vtable) {
            /* it's a NameSpace */
            return PMCNULL;
        }
        if ((PObj_get_FLAGS(ns) & FPA_is_ns_ext) &&
                ns->vtable->base_type == enum_class_FixedPMCArray)
            ns = VTABLE_get_pmc_keyed_int(INTERP, ns, NS_slot_var_sub);
        return ns;
    }

/*

=item C<METHOD void export_to(PMC* dest, PMC* what)>

Export items from this NameSpace into the C<dest> NameSpace. C<what>
is an array of (string) names to be exported.

=cut

*/

    METHOD void export_to(PMC* dest, PMC* what) {
        INTVAL i, n;

        n = VTABLE_elements(INTERP, what);
        if (!n)
            return;
        for (i = 0; i < n; ++i) {
            STRING *name;
            PMC *el;

            name = VTABLE_get_string_keyed_int(INTERP, what, i);
            el = VTABLE_get_pmc_keyed_str(INTERP, SELF, name);
            VTABLE_set_pmc_keyed_str(INTERP, dest, name, el);
        }
    }

/*

=item C<METHOD PMC* get_parent()>

Return the parent NameSpace or PMCNULL, if none.

=cut

*/

    METHOD PMC* get_parent() {
        return PMC_pmc_val(SELF) ? PMC_pmc_val(SELF) : PMCNULL;
    }

}
/*

=back

=head1 SEE ALSO

F<docs/pdds/pdd21_namespaces.pod>

=cut

*/

/*
 * Local variables:
 *   c-file-style: "parrot"
 * End:
 * vim: expandtab shiftwidth=4:
 */
