/*
Permute.xs
Copyright (c) 1999 - 2008 Edwin Pratomo
You may distribute under the terms of either the GNU General Public
License or the Artistic License, as specified in the Perl README file,
with the exception that it cannot be placed on a CD-ROM or similar media
for commercial distribution without the prior approval of the author.
*/
#ifdef __cplusplus
extern "C" {
#endif
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include <stdio.h>
#include "coolex.h"
#ifdef __cplusplus
}
#endif
#ifdef TRUE
#undef TRUE
#endif
#ifdef FALSE
#undef FALSE
#endif
#define TRUE 1
#define FALSE 0
/* For 5.005 compatibility */
#ifndef aTHX_
# define aTHX_
#endif
#ifndef aTHX
# define aTHX
#endif
#ifdef ppaddr
# define PL_ppaddr ppaddr
#endif
/* (Robin) This hack is stolen from Graham Barr's Scalar-List-Utils package.
The comment therein runs:
Some platforms have strict exports. And before 5.7.3 cxinc (or Perl_cxinc)
was not exported. Therefore platforms like win32, VMS etc have problems
so we redefine it here -- GMB
With any luck, it will enable us to build under ActiveState Perl.
*/
#if PERL_VERSION < 7/* Not in 5.6.1. */
# define SvUOK(sv) SvIOK_UV(sv)
# ifdef cxinc
# undef cxinc
# endif
# define cxinc() my_cxinc(aTHX)
static I32
my_cxinc(pTHX)
{
cxstack_max = cxstack_max * 3 / 2;
Renew(cxstack, cxstack_max + 1, struct context); /* XXX should fix CXINC macro */
return cxstack_ix + 1;
}
#endif
/* (Robin) Assigning to AvARRAY(array) expands to an assignment which has a typecast on the left-hand side.
* So it was technically illegal, but GCC is decent enough to accept it
* anyway. Unfortunately other compilers are not usually so forgiving...
*/
#if PERL_VERSION >= 9
# define AvARRAY_set(av, val) ((av)->sv_u.svu_array) = val
#else
# define AvARRAY_set(av, val) ((XPVAV*) SvANY(av))->xav_array = (char*) val
#endif
typedef unsigned int UINT;
typedef unsigned long ULONG;
typedef struct {
bool is_done;
SV **items;
UINT *loc;
UINT *p;
IV num;
COMBINATION *c;
} Permute;
/* private _next */
static void _next(int n, UINT *p, UINT *loc, bool *is_done)
{
int i;
if (n > 1)
if (loc[n] < n)
{
p[loc[n]] = p[loc[n] + 1];
p[loc[n] + 1] = n;
loc[n] = loc[n] + 1;
}
else
{
_next(n - 1, p, loc, is_done);
for (i = n - 1; i >= 1; i--)
p[i + 1] = p[i];
p[1] = n;
loc[n] = 1;
}
else
*is_done = TRUE;
}
/* permute_engine() and afp_destructor() are from Robin Houston
* <robin@kitsite.com> */
void permute_engine(
AV* av,
SV** array,
I32 level,
I32 len, SV*** tmparea, OP* callback)
{
SV** copy = tmparea[level];
int index = level;
bool calling = (index + 1 == len);
SV* tmp;
Copy(array, copy, len, SV*);
if (calling)
AvARRAY_set(av, copy);
do {
if (calling) {
PL_op = callback;
CALLRUNOPS(aTHX);
}
else {
permute_engine(av, copy, level + 1, len, tmparea, callback);
}
if (index != 0) {
tmp = copy[index];
copy[index] = copy[index - 1];
copy[index - 1] = tmp;
}
} while (index-- > 0);
}
struct afp_cache {
SV*** tmparea;
AV* array;
I32 len;
SV** array_array;
U32 array_flags;
SSize_t array_fill;
SV** copy; /* Non-magical SV list for magical array */
};
static
void afp_destructor(void *cache)
{
struct afp_cache *c = cache;
I32 x;
/* PerlIO_stdoutf("DESTROY!\n"); */
for (x = c->len; x >= 0; x--) free(c->tmparea[x]);
free(c->tmparea);
if (c->copy) {
for (x = 0; x < c->len; x++) SvREFCNT_dec(c->copy[x]);
free(c->copy);
}
AvARRAY_set(c->array, c->array_array);
SvFLAGS(c->array) = c->array_flags;
AvFILLp(c->array) = c->array_fill;
free(c);
}
MODULE = Algorithm::Permute PACKAGE = Algorithm::Permute
PROTOTYPES: DISABLE
Permute*
new(CLASS, av, ...)
char *CLASS
AV *av
PREINIT:
IV i, num;
COMBINATION *c;
IV r, n;
CODE:
RETVAL = (Permute*) safemalloc(sizeof(Permute));
if (RETVAL == NULL) {
warn("Unable to create an instance of Algorithm::Permute");
XSRETURN_UNDEF;
}
RETVAL->is_done = FALSE;
if ((n = av_len(av) + 1) == 0)
XSRETURN_UNDEF;
/* init combination if necessary */
if (items > 2) {
r = SvIV(ST(2));
if (r > n) {
warn("Number of combination must be less or equal the number of elements");
XSRETURN_UNDEF;
}
if (r < n) {
c = init_combination(n, r, av);
/* PerlIO_stdoutf("passed init_combination()\n"); */
if (c == NULL) {
warn("Unable to initialize combination");
XSRETURN_UNDEF;
}
RETVAL->c = c;
num = r;
} else {
RETVAL->c = NULL;
num = n;
}
} else {
RETVAL->c = NULL;
num = n;
}
if ((RETVAL->items = (SV**) safemalloc(sizeof(SV*) * (num + 1))) == NULL)
XSRETURN_UNDEF;
if ((RETVAL->p = (UINT*) safemalloc(sizeof(UINT) * (num + 1))) == NULL)
XSRETURN_UNDEF;
if ((RETVAL->loc = (UINT*) safemalloc(sizeof(UINT) * (num + 1))) == NULL)
XSRETURN_UNDEF;
RETVAL->num = num;
/* initialize items, p, and loc */
for (i = 1; i <= num; i++) {
if (RETVAL->c) {
*(RETVAL->items + i) = &PL_sv_undef;
} else {
*(RETVAL->items + i) = av_shift(av);
}
*(RETVAL->p + i) = num - i + 1;
*(RETVAL->loc + i) = 1;
}
if (RETVAL->c) {
coolex(RETVAL->c);
coolex_visit(RETVAL->c, RETVAL->items + 1); /* base of items is 1 */
}
OUTPUT:
RETVAL
void
next(self)
Permute *self
PREINIT:
IV n;
IV i;
PPCODE:
if (self->is_done && self->c) { /* permutation done */
self->is_done = coolex(self->c); /* generate next combination */
/* reset self->p and self->loc */
/* and update self->items */
for (i = 1; i <= self->num; i++) {
*(self->p + i) = self->num - i + 1;
*(self->loc + i) = 1;
}
coolex_visit(self->c, self->items + 1);
}
if (self->is_done) { /* done permutation for all combination */
if (self->c) {
free_combination(self->c);
self->c = NULL;
}
XSRETURN_EMPTY;
}
else {
EXTEND(sp, self->num);
for (i = 1; i <= self->num; i++) {
PUSHs(sv_2mortal(newSVsv(*(self->items + *(self->p + i)))));
}
n = self->num;
if (*(self->loc + n) < n)
{
*(self->p + *(self->loc + n)) = *(self->p + *(self->loc + n) + 1);
*(self->p + *(self->loc + n) + 1) = n;
*(self->loc + n) = *(self->loc + n) + 1;
}
else
{
_next(n - 1, self->p, self->loc, &(self->is_done));
for (i = n - 1; i >= 1; i--)
*(self->p + i + 1) = *(self->p + i);
*(self->p + 1) = n;
*(self->loc + n) = 1;
}
}
void
DESTROY(self)
Permute *self
PREINIT:
int i;
CODE:
safefree(self->p); /* must free elements first? */
safefree(self->loc);
for (i = 1; i <= self->num; i++) { /* leakproof! */
SvREFCNT_dec(*(self->items + i));
}
safefree(self->items);
safefree(self);
void
peek(self)
Permute *self
PREINIT:
int i;
PPCODE:
if (self->is_done)
XSRETURN_EMPTY;
EXTEND(sp, self->num);
for (i = 1; i <= self->num; i++)
PUSHs(sv_2mortal(newSVsv(*(self->items + *(self->p + i)))));
void
reset(self)
Permute *self
PREINIT:
int i;
CODE:
self->is_done = FALSE;
for (i = 1; i <= self->num; i++) {
*(self->p + i) = self->num - i + 1;
*(self->loc + i) = 1;
}
void
permute(callback_sv, array_sv)
SV* callback_sv;
SV* array_sv;
PROTOTYPE: &\@
PREINIT:
CV* callback;
GV* agv;
I32 x;
PERL_CONTEXT* cx;
I32 gimme = G_VOID; /* We call our callback in VOID context */
bool old_catch;
struct afp_cache *c;
I32 hasargs = 0;
SV** newsp;
PPCODE:
{
if (!SvROK(callback_sv) || SvTYPE(SvRV(callback_sv)) != SVt_PVCV)
Perl_croak(aTHX_ "Callback is not a CODE reference");
if (!SvROK(array_sv) || SvTYPE(SvRV(array_sv)) != SVt_PVAV)
Perl_croak(aTHX_ "Array is not an ARRAY reference");
c = malloc(sizeof(struct afp_cache));
callback = (CV*)SvRV(callback_sv);
c->array = (AV*)SvRV(array_sv);
c->len = 1 + av_len(c->array);
agv = gv_fetchpv("A", TRUE, SVt_PVAV);
SAVESPTR(GvSV(agv));
if (SvREADONLY(c->array))
Perl_croak(aTHX_ "Can't permute a read-only array");
if (c->len == 0) {
/* Should we warn here? */
free(c);
return;
}
c->array_array = AvARRAY(c->array);
c->array_flags = SvFLAGS(c->array);
c->array_fill = AvFILLp(c->array);
/* Magical array. Realise it temporarily. */
if (SvRMAGICAL(c->array)) {
c->copy = (SV**) malloc (c->len * sizeof *(c->copy));
for (x = 0; x < c->len; x++) {
SV **svp = av_fetch(c->array, x, FALSE);
c->copy[x] = (svp) ? SvREFCNT_inc(*svp) : &PL_sv_undef;
}
SvRMAGICAL_off(c->array);
AvARRAY_set(c->array, c->copy);
AvFILLp(c->array) = c->len - 1;
} else {
c->copy = 0;
}
SvREADONLY_on(c->array); /* Can't change the array during permute */
/* Allocate memory for the engine to scribble on */
c->tmparea = (SV***) malloc((c->len + 1) * sizeof *(c->tmparea));
for (x = c->len; x >= 0; x--)
c->tmparea[x] = malloc(c->len * sizeof **(c->tmparea));
/* Set up the context for the callback */
SAVESPTR(CvROOT(callback)->op_ppaddr);
CvROOT(callback)->op_ppaddr = PL_ppaddr[OP_NULL]; /* Zap the OP_LEAVESUB */
#ifdef PAD_SET_CUR
PAD_SET_CUR(CvPADLIST(callback),1);
#else
SAVESPTR(PL_curpad);
PL_curpad = AvARRAY((AV*)AvARRAY(CvPADLIST(callback))[1]);
#endif
SAVETMPS;
SAVESPTR(PL_op);
PUSHBLOCK(cx, CXt_NULL, SP); /* make a pseudo block */
PUSHSUB(cx);
old_catch = CATCH_GET;
CATCH_SET(TRUE);
save_destructor(afp_destructor, c);
permute_engine(c->array, AvARRAY(c->array), 0, c->len,
c->tmparea, CvSTART(callback));
POPBLOCK(cx,PL_curpm);
CATCH_SET(old_catch);
}