/*
Copyright (C) 2004-2006, The Perl Foundation.
$Id: slice.pmc 16275 2006-12-28 01:20:44Z particle $

=head1 NAME

src/pmc/slice.pmc - Slice PMC

=head1 DESCRIPTION

These are the vtable functions for the slice PMC class.

A Slice PMC isa Key PMC, holding a chain of start and/or end values
for slice ranges. Private flags define the meaning of the values:

  [ s .. e ]    s .. KEY_start_slice_FLAG; e .. KEY_end_slice_FLAG
  [ x,     ]    KEY_start_slice_FLAG | KEY_end_slice_FLAG
  [  .. e  ]    KEY_inf_slice_FLAG   | KEY_end_slice_FLAG
  [ s ..   ]    KEY_start_slice_FLAG | KEY_inf_slice_FLAG

Infinite ranges are currently implemented for Array and PerlString only.

Run

  $ parrot -d2000 slice.pasm

to see slice constant flags.

During initialization above key chain gets converted to parrot_range_t
structures.

=head2 Methods

=over 4

=cut

*/

#include "parrot/parrot.h"

/*
 * create range_t structure
 *
 * set the Slice iter state to initial, first position
 * no backwards iterations for now
 *
 * XXX self is a key chain (PMC constant), which shouldn't be modified
 *     finally this stuff should be call from packfiles thaw
 *
 */
static void
set_slice_start(Interp *interp, PMC *self, PMC *key, PMC *agg)
{
    parrot_range_t *range = mem_sys_allocate(sizeof *range);

    PMC_struct_val(self) = range;
next_range:
    range->next = NULL;
    RVal_int(range->step) = 1;

    if (key_type(interp, key) & KEY_integer_FLAG) {
        range->type = enum_type_INTVAL;
        /* integer key */
        if ((PObj_get_FLAGS(key) &
                    (KEY_start_slice_FLAG|KEY_end_slice_FLAG)) ==
                (KEY_start_slice_FLAG|KEY_end_slice_FLAG)) {
            /* start == end */
            RVal_int(range->start) =
                RVal_int(range->end) = key_integer(interp, key);
        }
        else if ((PObj_get_FLAGS(key) &
                    (KEY_inf_slice_FLAG|KEY_end_slice_FLAG)) ==
                (KEY_inf_slice_FLAG|KEY_end_slice_FLAG)) {
            /*
             * first range is ".. end"
             * start at index 0
             * */
            RVal_int(range->start) = 0;
            RVal_int(range->end) = key_integer(interp, key);
        }
        else {
            /*
             * else start at range value
             */
            RVal_int(range->start) = key_integer(interp, key);
            if ((PObj_get_FLAGS(key) &
                    (KEY_inf_slice_FLAG|KEY_start_slice_FLAG)) ==
                (KEY_inf_slice_FLAG|KEY_start_slice_FLAG)) {
                /* last range "start .." */
                RVal_int(range->end) = VTABLE_elements(interp, agg) - 1;
                if (PMC_data(key))
                    internal_exception(1, "Illegal range after start..");
            }
            else {
                /* must have end in next key */
                key = PMC_data(key);
                if (!key)
                    internal_exception(1, "no end range specified");
                RVal_int(range->end) = key_integer(interp, key);
            }
        }
        if (agg->vtable->base_type == enum_class_Slice)
            --RVal_int(range->end);
        RVal_int(range->cur) = RVal_int(range->start);
range_end:
        key = PMC_data(key);
        if (key) {
            parrot_range_t *n = mem_sys_allocate(sizeof *range);
            range->next = n;
            range = n;
            goto next_range;
        }
        return;
    }
    else {
        if (PObj_get_FLAGS(key) & KEY_inf_slice_FLAG) {
            internal_exception(1,
                               "unlimited slice range for hash "
                               "not implemented");
        }
        range->type = enum_type_STRING;
        /*
         * string assumed
         * start at value
         */
        RVal_str(range->start) =
            RVal_str(range->cur) =
            key_string(interp, key);
        if ((PObj_get_FLAGS(key) &
                    (KEY_start_slice_FLAG|KEY_end_slice_FLAG)) ==
                (KEY_start_slice_FLAG|KEY_end_slice_FLAG)) {
            /* start == end */
            RVal_str(range->end) = RVal_str(range->start);
        }
        else {
            /* must have end in next key */
            key = PMC_data(key);
            if (!key)
                internal_exception(1, "no end range specified");
            RVal_str(range->end) = key_string(interp, key);
        }
        goto range_end;
    }
}

/*
 * increment Slice value according to range and/or advance to
 * next range PMC in Key chain
 */
static void
set_slice_next(Interp *interp, PMC *self, PMC *agg)
{
    parrot_range_t *r = PMC_struct_val(self);
    if (!r || (INTVAL) r == -1)
        real_exception(interp, NULL, E_StopIteration, "StopIteration");
    if (r->type == enum_type_INTVAL) {
        RVal_int(r->cur) += RVal_int(r->step);
        if (RVal_int(r->step) > 0) {
            if (RVal_int(r->cur) > RVal_int(r->end)) {
                parrot_range_t *n;
next_range:
                n = r->next;
                mem_sys_free(r);
                PMC_struct_val(self) = n;
                if (!n)
                    PMC_int_val(self) = -1;
            }
        }
        else {
            if (RVal_int(r->cur) < RVal_int(r->end)) {
                goto next_range;
            }
        }
    }
    else {
        STRING *cur = RVal_str(r->cur);
        STRING *end = RVal_str(r->end);
        if (string_compare(interp, cur, end) < 0)
            RVal_str(r->cur) = string_increment(interp, cur);
        else
            goto next_range;
    }
}

pmclass Slice need_ext extends Key {

    void init() {
        PMC_struct_val(SELF) = NULL;
        PMC_pmc_val(SELF) = NULL;
    }

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

    void init_pmc(PMC* key) {
        SELF.init();
        /* that's actually the KEY_number_FLAG - but I can't hardly
         * imagine that we get keyed by FLOATVAL slices on
         * arrays
         */
        PObj_get_FLAGS(SELF) |= PObj_private1_FLAG;
        PObj_custom_mark_destroy_SETALL(SELF);
        set_slice_start(INTERP, SELF, key, SELF);
    }

    void destroy() {
        parrot_range_t *r = PMC_struct_val(SELF), *n;
        /* iteration ended - all is freed */
        if ((INTVAL) r == -1)
            return;
        while (r) {
            n = r->next;
            mem_sys_free(r);
            r = n;
        }
    }

    PMC* clone() {
        real_exception(INTERP, NULL, E_NotImplementedError, "Unimplemented");
        return NULL;
    }

/*

=item C<INTVAL get_integer()>

Get the next integer key from the current slice range.

=item C<STRING* get_string()>

Get the next string key from the current slice range.

=cut

*/

    INTVAL get_integer() {
        parrot_range_t *r = PMC_struct_val(SELF);
        if (!r || (INTVAL) r == -1)
            real_exception(INTERP, NULL, E_StopIteration, "StopIteration");
        return RVal_int(r->cur);
    }

    STRING* get_string() {
        parrot_range_t *r = PMC_struct_val(SELF);
        return RVal_str(r->cur);
    }

/*

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

=item C<PMC* get_iter()>

A slice can serve as its own iterator, yielding values [start .. end-1].
This is used for implementing Pythons xrange()

=cut

*/

    PMC* slice(PMC *key, INTVAL f) {
        if (f) {
            real_exception(INTERP, NULL, E_ValueError,
                    "Slice: Unknown slice type");
            return NULL;
        }
        else {
            PMC *iter = pmc_new_init(INTERP, enum_class_Iterator, SELF);
            PMC_struct_val(iter) = key;
            return iter;
        }
    }

    PMC* get_iter() {
        PMC *iter = pmc_new_init(INTERP, enum_class_Iterator, SELF);
        PMC_struct_val(iter) = SELF;
        return iter;
    }

    INTVAL elements() {
        parrot_range_t *r = PMC_struct_val(SELF);
        /* only start .. end supported so:
         * TODO check flags somewhere
         * */
        INTVAL n = RVal_int(r->start) - RVal_int(r->end);
        return n;
    }

    INTVAL get_integer_keyed(PMC* key) {
        parrot_range_t *r = PMC_struct_val(key);
        return RVal_int(r->cur);
    }

    STRING* get_string_keyed(PMC* key) {
        INTVAL v = VTABLE_get_integer(INTERP, key);
        return string_from_int(INTERP, v);
    }

    PMC* get_pmc_keyed(PMC* key) {
        parrot_range_t *r = PMC_struct_val(key);
        PMC *res = PMC_pmc_val(SELF);
        PMC_int_val(res) = RVal_int(r->cur);
        return res;
    }
/*

=item C<PMC* nextkey_keyed(PMC* agg, INTVAL what)>

Prepare slice PMC SELF for iteration over the passed aggregate or
advance to next position in the range, depending on what.

=cut

*/
    PMC* nextkey_keyed(PMC* agg, INTVAL what) {
        PMC *ret = SELF;

        switch (what) {
            case ITERATE_FROM_START:
            case ITERATE_FROM_START_KEYS:    /* reset key */
                if (!agg) {
                    /* xrange implementation - the slice PMC
                     * itself serves as the aggregate. It's already
                     * initialized
                     */
                    return SELF;
                }

                /*
                 * need a new Slice PMC that hold's the state
                 * especially PMC_data() must be zero
                 *
                 * aggregate call get_integer/get_string on this
                 * PMC, because it's marked being a Key PMC
                 */
                ret = pmc_new(INTERP, enum_class_Slice);
                PObj_get_FLAGS(ret) |= KEY_pmc_FLAG;
                /*
                 * set start value
                 */
                set_slice_start(INTERP, ret, SELF, agg);
                break;
                /*
                 * we are passed now the new PMC, we had created above
                 */
            case ITERATE_GET_NEXT:
                set_slice_next(INTERP, SELF, agg);
                break;
            default:
                real_exception(INTERP, NULL, E_NotImplementedError,
                        "No backward iteration on slices yet");
                break;
        }
        return ret;
    }
}

/*

=back

=cut

*/

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