Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: cb1d793924
Fetching contributors…

Cannot retrieve contributors at this time

1269 lines (1047 sloc) 40.809 kB
/*=====================================================================
*
* Template::Stash::XS (Stash.xs)
*
* DESCRIPTION
* This is an XS implementation of the Template::Stash module.
* It is an alternative version of the core Template::Stash methods
* ''get'' and ''set'' (the ones that should benefit most from a
* speedy C implementation), along with some virtual methods (like
* first, last, reverse, etc.)
*
* AUTHORS
* Andy Wardley <abw@cpan.org>
* Doug Steinwand <dsteinwand@citysearch.com>
*
* COPYRIGHT
* Copyright (C) 1996-2012 Andy Wardley. All Rights Reserved.
* Copyright (C) 1998-2000 Canon Research Centre Europe Ltd.
*
* This module is free software; you can redistribute it and/or
* modify it under the same terms as Perl itself.
*
* NOTE
* Be very familiar with the perlguts, perlxs, perlxstut and
* perlapi manpages before digging through this code.
*
*=====================================================================*/
#ifdef __cplusplus
extern "C" {
#endif
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#define NEED_sv_2pv_flags
#define NEED_newRV_noinc
#include "ppport.h"
#include "XSUB.h"
#ifdef __cplusplus
}
#endif
#if defined(_MSC_VER) || defined(__SUNPRO_C)
#define debug()
#else
#ifdef WIN32
#define debug(format)
#else
#define debug(...)
/* #define debug(...) fprintf(stderr, __VA_ARGS__) */
#endif
#endif
#ifdef WIN32
#define snprintf _snprintf
#endif
#define TT_STASH_PKG "Template::Stash::XS"
#define TT_LIST_OPS "Template::Stash::LIST_OPS"
#define TT_HASH_OPS "Template::Stash::HASH_OPS"
#define TT_SCALAR_OPS "Template::Stash::SCALAR_OPS"
#define TT_PRIVATE "Template::Stash::PRIVATE"
#define TT_LVALUE_FLAG 1
#define TT_DEBUG_FLAG 2
#define TT_DEFAULT_FLAG 4
typedef enum tt_ret { TT_RET_UNDEF, TT_RET_OK, TT_RET_CODEREF } TT_RET;
static TT_RET hash_op(pTHX_ SV*, char*, AV*, SV**, int);
static TT_RET list_op(pTHX_ SV*, char*, AV*, SV**);
static TT_RET scalar_op(pTHX_ SV*, char*, AV*, SV**, int);
static TT_RET tt_fetch_item(pTHX_ SV*, SV*, AV*, SV**);
static TT_RET autobox_list_op(pTHX_ SV*, char*, AV*, SV**, int);
static SV* dotop(pTHX_ SV*, SV*, AV*, int);
static SV* call_coderef(pTHX_ SV*, AV*);
static SV* fold_results(pTHX_ I32);
static SV* find_perl_op(pTHX_ char*, char*);
static AV* mk_mortal_av(pTHX_ SV*, AV*, SV*);
static SV* do_getset(pTHX_ SV*, AV*, SV*, int);
static AV* convert_dotted_string(pTHX_ const char*, I32);
static int get_debug_flag(pTHX_ SV*);
static int cmp_arg(const void *, const void *);
static int looks_private(pTHX_ const char*);
static void die_object(pTHX_ SV *);
static struct xs_arg *find_xs_op(char *);
static SV* list_dot_first(pTHX_ AV*, AV*);
static SV* list_dot_join(pTHX_ AV*, AV*);
static SV* list_dot_last(pTHX_ AV*, AV*);
static SV* list_dot_max(pTHX_ AV*, AV*);
static SV* list_dot_reverse(pTHX_ AV*, AV*);
static SV* list_dot_size(pTHX_ AV*, AV*);
static SV* hash_dot_each(pTHX_ HV*, AV*);
static SV* hash_dot_keys(pTHX_ HV*, AV*);
static SV* hash_dot_values(pTHX_ HV*, AV*);
static SV* scalar_dot_defined(pTHX_ SV*, AV*);
static SV* scalar_dot_length(pTHX_ SV*, AV*);
#define THROW_SIZE 64
static char throw_fmt[] = "Can't locate object method \"%s\" via package \"%s\"";
/* dispatch table for XS versions of special "virtual methods",
* names must be in alphabetical order
*/
static const struct xs_arg {
const char *name;
SV* (*list_f) (pTHX_ AV*, AV*);
SV* (*hash_f) (pTHX_ HV*, AV*);
SV* (*scalar_f) (pTHX_ SV*, AV*);
} xs_args[] = {
/* name list (AV) ops. hash (HV) ops. scalar (SV) ops.
-------- ---------------- --------------- ------------------ */
{ "defined", NULL, NULL, scalar_dot_defined },
{ "each", NULL, hash_dot_each, NULL },
/* { "first", list_dot_first, NULL, NULL }, */
{ "join", list_dot_join, NULL, NULL },
{ "keys", NULL, hash_dot_keys, NULL },
/* { "last", list_dot_last, NULL, NULL }, */
{ "length", NULL, NULL, scalar_dot_length },
{ "max", list_dot_max, NULL, NULL },
{ "reverse", list_dot_reverse, NULL, NULL },
{ "size", list_dot_size, NULL, NULL },
{ "values", NULL, hash_dot_values, NULL },
};
/*------------------------------------------------------------------------
* tt_fetch_item(pTHX_ SV *root, SV *key_sv, AV *args, SV **result)
*
* Retrieves an item from the given hash or array ref. If item is found
* and a coderef then the coderef will be called and passed args. Returns
* TT_RET_CODEREF or TT_RET_OK and sets result. If not found, returns
* TT_RET_UNDEF and result is undefined.
*------------------------------------------------------------------------*/
static TT_RET tt_fetch_item(pTHX_ SV *root, SV *key_sv, AV *args, SV **result) {
STRLEN key_len;
char *key = SvPV(key_sv, key_len);
SV **value = NULL;
#ifndef WIN32
debug("fetch item: %s\n", key);
#endif
/* negative key_len is used to indicate UTF8 string */
if (SvUTF8(key_sv))
key_len = -key_len;
if (!SvROK(root))
return TT_RET_UNDEF;
switch (SvTYPE(SvRV(root))) {
case SVt_PVHV:
value = hv_fetch((HV *) SvRV(root), key, key_len, FALSE);
break;
case SVt_PVAV:
if (looks_like_number(key_sv))
value = av_fetch((AV *) SvRV(root), SvIV(key_sv), FALSE);
break;
}
if (value) {
/* trigger any tied magic to FETCH value */
SvGETMAGIC(*value);
/* call if a coderef */
if (SvROK(*value)
&& (SvTYPE(SvRV(*value)) == SVt_PVCV)
&& !sv_isobject(*value)) {
*result = call_coderef(aTHX_ *value, args);
return TT_RET_CODEREF;
}
else if (SvOK(*value)) {
*result = *value;
return TT_RET_OK;
}
}
*result = &PL_sv_undef;
return TT_RET_UNDEF;
}
/*------------------------------------------------------------------------
* dotop(pTHX_ SV *root, SV *key_sv, AV *args, int flags)
*
* Resolves dot operations of the form root.key, where 'root' is a
* reference to the root item, 'key_sv' is an SV containing the
* operation key (e.g. hash key, list index, first, last, each, etc),
* 'args' is a list of additional arguments and 'TT_LVALUE_FLAG' is a
* flag to indicate if, for certain operations (e.g. hash key), the item
* should be created if it doesn't exist. Also, 'TT_DEBUG_FLAG' is the
* debug flag.
*------------------------------------------------------------------------*/
static SV *dotop(pTHX_ SV *root, SV *key_sv, AV *args, int flags) {
dSP;
STRLEN item_len;
char *item = SvPV(key_sv, item_len);
SV *result = &PL_sv_undef;
I32 atroot;
#ifndef WIN32
debug("dotop(%s)\n", item);
#endif
/* ignore _private or .private members */
if (!root || looks_private(aTHX_ item))
return &PL_sv_undef;
if (SvROK(root)) {
atroot = sv_derived_from(root, TT_STASH_PKG);
if (atroot || ((SvTYPE(SvRV(root)) == SVt_PVHV) && !sv_isobject(root))) {
/* root is a HASH or Template::Stash */
switch(tt_fetch_item(aTHX_ root, key_sv, args, &result)) {
case TT_RET_OK:
/* return immediately */
return result;
break;
case TT_RET_CODEREF:
/* fall through */
break;
default:
/* for lvalue, create an intermediate hash */
if (flags & TT_LVALUE_FLAG) {
SV *newhash;
HV *roothv = (HV *) SvRV(root);
newhash = SvREFCNT_inc((SV *) newRV_noinc((SV *) newHV()));
debug("- auto-vivifying intermediate hash\n");
if (hv_store(roothv, item, item_len, newhash, 0)) {
/* trigger any tied magic to STORE value */
SvSETMAGIC(newhash);
}
else {
SvREFCNT_dec(newhash);
}
return sv_2mortal(newhash);
}
/* try hash virtual method (not at stash root, except import) */
if ((! atroot || (strcmp(item, "import") == 0))
&& hash_op(aTHX_ root, item, args, &result, flags) == TT_RET_UNDEF) {
/* try hash slice */
if (SvROK(key_sv) && SvTYPE(SvRV(key_sv)) == SVt_PVAV) {
AV *a_av = newAV();
AV *k_av = (AV *) SvRV(key_sv);
HV *r_hv = (HV *) SvRV(root);
char *t;
I32 i;
STRLEN tlen;
SV **svp;
for (i = 0; i <= av_len(k_av); i++) {
if ((svp = av_fetch(k_av, i, 0))) {
SvGETMAGIC(*svp);
t = SvPV(*svp, tlen);
if((svp = hv_fetch(r_hv, t, tlen, FALSE))) {
SvGETMAGIC(*svp);
av_push(a_av, SvREFCNT_inc(*svp));
}
}
}
return sv_2mortal(newRV_noinc((SV *) a_av));
}
}
}
}
else if ((SvTYPE(SvRV(root)) == SVt_PVAV) && !sv_isobject(root)) {
/* root is an ARRAY, try list virtuals */
if (list_op(aTHX_ root, item, args, &result) == TT_RET_UNDEF) {
switch (tt_fetch_item(aTHX_ root, key_sv, args, &result)) {
case TT_RET_OK:
return result;
break;
case TT_RET_CODEREF:
break;
default:
/* try array slice */
if (SvROK(key_sv) && SvTYPE(SvRV(key_sv)) == SVt_PVAV) {
AV *a_av = newAV();
AV *k_av = (AV *) SvRV(key_sv);
AV *r_av = (AV *) SvRV(root);
I32 i;
SV **svp;
for (i = 0; i <= av_len(k_av); i++) {
if ((svp = av_fetch(k_av, i, FALSE))) {
SvGETMAGIC(*svp);
if (looks_like_number(*svp) &&
(svp = av_fetch(r_av, SvIV(*svp), FALSE))) {
SvGETMAGIC(*svp);
av_push(a_av, SvREFCNT_inc(*svp));
}
}
}
return sv_2mortal(newRV_noinc((SV *) a_av));
}
}
}
}
else if (sv_isobject(root)) {
/* root is an object */
I32 n, i;
SV **svp;
HV *stash = SvSTASH((SV *) SvRV(root));
GV *gv;
/* char *error_string; */
result = NULL;
if ((gv = gv_fetchmethod_autoload(stash, item, 1))) {
/* eval { @result = $root->$item(@$args); }; */
PUSHMARK(SP);
XPUSHs(root);
n = (args && args != Nullav) ? av_len(args) : -1;
for (i = 0; i <= n; i++)
if ((svp = av_fetch(args, i, 0))) XPUSHs(*svp);
PUTBACK;
n = call_method(item, G_ARRAY | G_EVAL);
SPAGAIN;
if (SvTRUE(ERRSV)) {
char throw_str[THROW_SIZE+1];
(void) POPs; /* remove undef from stack */
PUTBACK;
result = NULL;
/* if we get an exception object throw ($@ is a
* ref) or a error other than "Can't locate object
* method "blah"" then it's a real error that need
* to be re-thrown.
*/
if (SvROK(ERRSV)) {
die_object(aTHX_ ERRSV);
}
else {
/* We use throw_str to construct the error message
* that indicates a missing method. We use snprintf() to
* avoid overflowing throw_str, and always ensure the
* last character is NULL (if the item name is too long
* to fit into throw_str then snprintf() doesn't add the
* terminating NULL
*/
snprintf(throw_str, THROW_SIZE, throw_fmt, item, HvNAME(stash));
throw_str[THROW_SIZE] = '\0';
if (! strstr( SvPV(ERRSV, PL_na), throw_str))
die_object(aTHX_ ERRSV);
}
} else {
result = fold_results(aTHX_ n);
}
}
if (!result) {
/* failed to call object method, so try some fallbacks */
if (SvTYPE(SvRV(root)) == SVt_PVHV) {
/* hash based object - first try to fetch item */
switch(tt_fetch_item(aTHX_ root, key_sv, args, &result)) {
case TT_RET_OK:
/* return immediately */
return result;
break;
case TT_RET_CODEREF:
/* fall through */
break;
default:
/* then try hash vmethod if that failed */
if (hash_op(aTHX_ root, item, args, &result, flags) == TT_RET_OK)
return result;
/* hash_op() will also try list_op([$hash]) */
}
}
else if (SvTYPE(SvRV(root)) == SVt_PVAV) {
/* list based object - first try to fetch item */
switch (tt_fetch_item(aTHX_ root, key_sv, args, &result)) {
case TT_RET_OK:
/* return immediately */
return result;
break;
case TT_RET_CODEREF:
/* fall through */
break;
default:
/* try list vmethod */
if (list_op(aTHX_ root, item, args, &result) == TT_RET_OK)
return result;
}
}
else if (scalar_op(aTHX_ root, item, args, &result, flags) == TT_RET_OK) {
/* scalar_op() will also try list_op([$scalar]) */
return result;
}
else if (flags & TT_DEBUG_FLAG) {
result = (SV *) mk_mortal_av(aTHX_ &PL_sv_undef, NULL, ERRSV);
}
}
}
}
/* it doesn't look like we've got a reference to anything we know about,
* so let's try the SCALAR_OPS pseudo-methods (but not for l-values)
*/
else if (!(flags & TT_LVALUE_FLAG)
&& (scalar_op(aTHX_ root, item, args, &result, flags)
== TT_RET_UNDEF)) {
if (flags & TT_DEBUG_FLAG)
croak("don't know how to access [ %s ].%s\n",
SvPV(root, PL_na), item);
}
/* if we have an arrayref and the first element is defined then
* everything is peachy, otherwise some ugliness may have occurred
*/
if (SvROK(result) && SvTYPE(SvRV(result)) == SVt_PVAV) {
SV **svp;
AV *array = (AV *) SvRV(result);
I32 len = (array == Nullav) ? 0 : (av_len(array) + 1);
if (len) {
svp = av_fetch(array, 0, FALSE);
if (svp && (*svp != &PL_sv_undef)) {
return result;
}
}
}
if ((flags & TT_DEBUG_FLAG)
&& (!result || !SvOK(result) || (result == &PL_sv_undef))) {
croak("%s is undefined\n", item);
}
return result;
}
/*------------------------------------------------------------------------
* assign(pTHX_ SV *root, SV *key_sv, AV *args, SV *value, int flags)
*
* Resolves the final assignment element of a dotted compound variable
* of the form "root.key(args) = value". 'root' is a reference to
* the root item, 'key_sv' is an SV containing the operation key
* (e.g. hash key, list item, object method), 'args' is a list of user
* provided arguments (passed only to object methods), 'value' is the
* assignment value to be set (appended to args) and 'deflt' (default)
* is a flag to indicate that the assignment should only be performed
* if the item is currently undefined/false.
*------------------------------------------------------------------------*/
static SV *assign(pTHX_ SV *root, SV *key_sv, AV *args, SV *value, int flags) {
dSP;
SV **svp, *newsv;
HV *roothv;
AV *rootav;
STRLEN key_len;
char *key = SvPV(key_sv, key_len);
char *key2 = SvPV(key_sv, key_len); /* TMP DEBUG HACK */
#ifndef WIN32
debug("assign(%s)\n", key2);
#endif
/* negative key_len is used to indicate UTF8 string */
if (SvUTF8(key_sv))
key_len = -key_len;
if (!root || !SvOK(key_sv) || key_sv == &PL_sv_undef || looks_private(aTHX_ key)) {
/* ignore _private or .private members */
return &PL_sv_undef;
}
else if (SvROK(root)) {
/* see if root is an object (but not Template::Stash) */
if (sv_isobject(root) && !sv_derived_from(root, TT_STASH_PKG)) {
HV *stash = SvSTASH((SV *) SvRV(root));
GV *gv;
/* look for the named method, or an AUTOLOAD method */
if ((gv = gv_fetchmethod_autoload(stash, key, 1))) {
I32 count = (args && args != Nullav) ? av_len(args) : -1;
I32 i;
/* push args and value onto stack, then call method */
PUSHMARK(SP);
XPUSHs(root);
for (i = 0; i <= count; i++) {
if ((svp = av_fetch(args, i, FALSE)))
XPUSHs(*svp);
}
XPUSHs(value);
PUTBACK;
debug(" - calling object method\n");
count = call_method(key, G_ARRAY);
SPAGAIN;
return fold_results(aTHX_ count);
}
}
/* drop-through if not an object or method not found */
switch (SvTYPE(SvRV(root))) {
case SVt_PVHV: /* HASH */
roothv = (HV *) SvRV(root);
debug(" - hash assign\n");
/* check for any existing value if ''default'' flag set */
if ((flags & TT_DEFAULT_FLAG)
&& (svp = hv_fetch(roothv, key, key_len, FALSE))) {
/* invoke any tied magical FETCH method */
debug(" - fetched default\n");
SvGETMAGIC(*svp);
if (SvTRUE(*svp))
return &PL_sv_undef;
}
/* avoid 'modification of read-only value' error */
newsv = newSVsv(value);
hv_store(roothv, key, key_len, newsv, 0);
SvSETMAGIC(newsv);
return value;
break;
case SVt_PVAV: /* ARRAY */
rootav = (AV *) SvRV(root);
debug(" - list assign\n");
if (looks_like_number(key_sv)) {
/* if the TT_DEFAULT_FLAG is set then first look to see if the
* target is already set to some true value; if it is then
* we return that value (after invoking any SvGETMAGIC required
* for tied arrays) and bypass the assignment altogether
*/
if ( (flags & TT_DEFAULT_FLAG)
&& (svp = av_fetch(rootav, SvIV(key_sv), FALSE))) {
debug(" - fetched default, invoking any tied magic\n");
SvGETMAGIC(*svp);
if (SvTRUE(*svp))
return &PL_sv_undef;
}
/* create a new SV for the value and call av_store(),
* incrementing the reference count on the way; we
* then invoke any set magic for tied arrays; if the
* return value from av_store is NULL (as appears to
* be the case with tied arrays - although the same
* isn't true of hv_store() for some reason???) then
* we decrement the reference counter because that's
* what perlguts tells us to do...
*/
newsv = newSVsv(value);
svp = av_store(rootav, SvIV(key_sv), newsv);
SvSETMAGIC(newsv);
return value;
}
else
return &PL_sv_undef;
break;
default: /* BARF */
/* TODO: fix [ %s ] */
croak("don't know how to assign to [ %s ].%s",
SvPV(SvRV(root), PL_na), key);
}
}
else { /* SCALAR */
/* TODO: fix [ %s ] */
croak("don't know how to assign to [ %s ].%s",
SvPV(SvRV(root), PL_na), key);
}
/* not reached */
return &PL_sv_undef; /* just in case */
}
/* dies and passes back a blessed object,
* or just a string if it's not blessed
*/
static void die_object (pTHX_ SV *err) {
if (sv_isobject(err) || SvROK(err)) {
/* throw object via ERRSV ($@) */
SV *errsv = get_sv("@", TRUE);
sv_setsv(errsv, err);
(void) die(Nullch);
}
/* error string sent back via croak() */
croak("%s", SvPV(err, PL_na));
}
/* pushes any arguments in 'args' onto the stack then calls the code ref
* in 'code'. Calls fold_results() to return a listref or die.
*/
static SV *call_coderef(pTHX_ SV *code, AV *args) {
dSP;
SV **svp;
I32 count = (args && args != Nullav) ? av_len(args) : -1;
I32 i;
PUSHMARK(SP);
for (i = 0; i <= count; i++)
if ((svp = av_fetch(args, i, FALSE)))
XPUSHs(*svp);
PUTBACK;
count = call_sv(code, G_ARRAY|G_EVAL);
SPAGAIN;
if (SvTRUE(ERRSV)) {
die_object(aTHX_ ERRSV);
}
return fold_results(aTHX_ count);
}
/* pops 'count' items off the stack, folding them into a list reference
* if count > 1, or returning the sole item if count == 1.
* Returns undef if count == 0.
* Dies if first value of list is undef
*/
static SV* fold_results(pTHX_ I32 count) {
dSP;
SV *retval = &PL_sv_undef;
if (count > 1) {
/* convert multiple return items into a list reference */
AV *av = newAV();
SV *last_sv = &PL_sv_undef;
SV *sv = &PL_sv_undef;
I32 i;
av_extend(av, count - 1);
for(i = 1; i <= count; i++) {
last_sv = sv;
sv = POPs;
if (SvOK(sv) && !av_store(av, count - i, SvREFCNT_inc(sv)))
SvREFCNT_dec(sv);
}
PUTBACK;
retval = sv_2mortal((SV *) newRV_noinc((SV *) av));
if (!SvOK(sv) || sv == &PL_sv_undef) {
/* if first element was undef, die */
die_object(aTHX_ last_sv);
}
return retval;
} else {
if (count)
retval = POPs;
PUTBACK;
return retval;
}
}
/* Iterates through array calling dotop() to resolve all items
* Skips the last if ''value'' is non-NULL.
* If ''value'' is non-NULL, calls assign() to do the assignment.
*
* SV *root; AV *ident_av; SV *value; int flags;
*
*/
static SV* do_getset(pTHX_ SV *root, AV *ident_av, SV *value, int flags) {
AV *key_args;
SV *key;
SV **svp;
I32 end_loop, i, size = av_len(ident_av);
if (value) {
/* make some adjustments for assign mode */
end_loop = size - 1;
flags |= TT_LVALUE_FLAG;
} else {
end_loop = size;
}
for(i = 0; i < end_loop; i += 2) {
if (!(svp = av_fetch(ident_av, i, FALSE)))
croak(TT_STASH_PKG " %cet: bad element %d", value ? 's' : 'g', i);
key = *svp;
if (!(svp = av_fetch(ident_av, i + 1, FALSE)))
croak(TT_STASH_PKG " %cet: bad arg. %d", value ? 's' : 'g', i + 1);
if (SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVAV)
key_args = (AV *) SvRV(*svp);
else
key_args = Nullav;
root = dotop(aTHX_ root, key, key_args, flags);
if (!root || !SvOK(root))
return root;
}
if (value && SvROK(root)) {
/* call assign() to resolve the last item */
if (!(svp = av_fetch(ident_av, size - 1, FALSE)))
croak(TT_STASH_PKG ": set bad ident element at %d", i);
key = *svp;
if (!(svp = av_fetch(ident_av, size, FALSE)))
croak(TT_STASH_PKG ": set bad ident argument at %d", i + 1);
if (SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVAV)
key_args = (AV *) SvRV(*svp);
else
key_args = Nullav;
return assign(aTHX_ root, key, key_args, value, flags);
}
return root;
}
/* return [ map { s/\(.*$//; ($_, 0) } split(/\./, $str) ];
*/
static AV *convert_dotted_string(pTHX_ const char *str, I32 len) {
AV *av = newAV();
char *buf, *b;
int b_len = 0;
New(0, buf, len + 1, char);
if (!buf)
croak(TT_STASH_PKG ": New() failed for convert_dotted_string");
for(b = buf; len >= 0; str++, len--) {
if (*str == '(') {
for(; (len > 0) && (*str != '.'); str++, len--) ;
}
if ((len < 1) || (*str == '.')) {
*b = '\0';
av_push(av, newSVpv(buf, b_len));
av_push(av, newSViv((IV) 0));
b = buf;
b_len = 0;
} else {
*b++ = *str;
b_len++;
}
}
Safefree(buf);
return (AV *) sv_2mortal((SV *) av);
}
/* performs a generic hash operation identified by 'key'
* (e.g. keys, * values, each) on 'hash'.
* returns TT_RET_CODEREF if successful, TT_RET_UNDEF otherwise.
*/
static TT_RET hash_op(pTHX_ SV *root, char *key, AV *args, SV **result, int flags) {
struct xs_arg *a;
SV *code;
TT_RET retval;
/* look for XS version first */
if ((a = find_xs_op(key)) && a->hash_f) {
*result = a->hash_f(aTHX_ (HV *) SvRV(root), args);
return TT_RET_CODEREF;
}
/* look for perl version in Template::Stash module */
if ((code = find_perl_op(aTHX_ key, TT_HASH_OPS))) {
*result = call_coderef(aTHX_ code, mk_mortal_av(aTHX_ root, args, NULL));
return TT_RET_CODEREF;
}
/* try upgrading item to a list and look for a list op */
if (!(flags & TT_LVALUE_FLAG)) {
/* hash.method ==> [hash].method */
return autobox_list_op(aTHX_ root, key, args, result, flags);
}
/* not found */
*result = &PL_sv_undef;
return TT_RET_UNDEF;
}
/* performs a generic list operation identified by 'key' on 'list'.
* Additional arguments may be passed in 'args'.
* returns TT_RET_CODEREF if successful, TT_RET_UNDEF otherwise.
*/
static TT_RET list_op(pTHX_ SV *root, char *key, AV *args, SV **result) {
struct xs_arg *a;
SV *code;
/* look for and execute XS version first */
if ((a = find_xs_op(key)) && a->list_f) {
#ifndef WIN32
debug("calling internal list vmethod: %s\n", key);
#endif
*result = a->list_f(aTHX_ (AV *) SvRV(root), args);
return TT_RET_CODEREF;
}
/* look for and execute perl version in Template::Stash module */
if ((code = find_perl_op(aTHX_ key, TT_LIST_OPS))) {
#ifndef WIN32
debug("calling perl list vmethod: %s\n", key);
#endif
*result = call_coderef(aTHX_ code, mk_mortal_av(aTHX_ root, args, NULL));
return TT_RET_CODEREF;
}
#ifndef WIN32
debug("list vmethod not found: %s\n", key);
#endif
/* not found */
*result = &PL_sv_undef;
return TT_RET_UNDEF;
}
/* Performs a generic scalar operation identified by 'key'
* on 'sv'. Additional arguments may be passed in 'args'.
* returns TT_RET_CODEREF if successful, TT_RET_UNDEF otherwise.
*/
static TT_RET scalar_op(pTHX_ SV *sv, char *key, AV *args, SV **result, int flags) {
struct xs_arg *a;
SV *code;
TT_RET retval;
/* look for a XS version first */
if ((a = find_xs_op(key)) && a->scalar_f) {
*result = a->scalar_f(aTHX_ sv, args);
return TT_RET_CODEREF;
}
/* look for perl version in Template::Stash module */
if ((code = find_perl_op(aTHX_ key, TT_SCALAR_OPS))) {
*result = call_coderef(aTHX_ code, mk_mortal_av(aTHX_ sv, args, NULL));
return TT_RET_CODEREF;
}
/* try upgrading item to a list and look for a list op */
if (!(flags & TT_LVALUE_FLAG)) {
/* scalar.method ==> [scalar].method */
return autobox_list_op(aTHX_ sv, key, args, result, flags);
}
/* not found */
*result = &PL_sv_undef;
return TT_RET_UNDEF;
}
static TT_RET autobox_list_op(pTHX_ SV *sv, char *key, AV *args, SV **result, int flags) {
AV *av = newAV();
SV *avref = (SV *) newRV_inc((SV *) av);
TT_RET retval;
av_push(av, SvREFCNT_inc(sv));
retval = list_op(aTHX_ avref, key, args, result);
SvREFCNT_dec(av);
SvREFCNT_dec(avref);
return retval;
}
/* xs_arg comparison function */
static int cmp_arg(const void *a, const void *b) {
return (strcmp(((const struct xs_arg *)a)->name,
((const struct xs_arg *)b)->name));
}
/* Searches the xs_arg table for key */
static struct xs_arg *find_xs_op(char *key) {
struct xs_arg *ap, tmp;
tmp.name = key;
if ((ap = (struct xs_arg *)
bsearch(&tmp,
xs_args,
sizeof(xs_args)/sizeof(struct xs_arg),
sizeof(struct xs_arg),
cmp_arg)))
return ap;
return NULL;
}
/* Searches the perl Template::Stash.pm module for ''key'' in the
* hashref named ''perl_var''. Returns SV if found, NULL otherwise.
*/
static SV *find_perl_op(pTHX_ char *key, char *perl_var) {
SV *tt_ops;
SV **svp;
if ((tt_ops = get_sv(perl_var, FALSE))
&& SvROK(tt_ops)
&& (svp = hv_fetch((HV *) SvRV(tt_ops), key, strlen(key), FALSE))
&& SvROK(*svp)
&& SvTYPE(SvRV(*svp)) == SVt_PVCV)
return *svp;
return NULL;
}
/* Returns: @a = ($sv, @av, $more) */
static AV *mk_mortal_av(pTHX_ SV *sv, AV *av, SV *more) {
SV **svp;
AV *a;
I32 i = 0, size;
a = newAV();
av_push(a, SvREFCNT_inc(sv));
if (av && (size = av_len(av)) > -1) {
av_extend(a, size + 1);
for (i = 0; i <= size; i++)
if ((svp = av_fetch(av, i, FALSE)))
if(!av_store(a, i + 1, SvREFCNT_inc(*svp)))
SvREFCNT_dec(*svp);
}
if (more && SvOK(more))
if (!av_store(a, i + 1, SvREFCNT_inc(more)))
SvREFCNT_dec(more);
return (AV *) sv_2mortal((SV *) a);
}
/* Returns TT_DEBUG_FLAG if _DEBUG key is true in hashref ''sv''. */
static int get_debug_flag (pTHX_ SV *sv) {
const char *key = "_DEBUG";
const I32 len = 6;
SV **debug;
if (SvROK(sv)
&& (SvTYPE(SvRV(sv)) == SVt_PVHV)
&& (debug = hv_fetch((HV *) SvRV(sv), (char *) key, len, FALSE))
&& SvOK(*debug)
&& SvTRUE(*debug))
return TT_DEBUG_FLAG;
return 0;
}
static int looks_private(pTHX_ const char *name) {
/* SV *priv; */
/* For now we hard-code the regex to match _private or .hidden
* variables, but we do check to see if $Template::Stash::PRIVATE
* is defined, allowing a user to undef it to defeat the check.
* The better solution would be to match the string using the regex
* defined in the $PRIVATE package varible, but I've been searching
* for well over an hour now and I can't find any documentation or
* examples showing me how to match a string against a pre-compiled
* regex from XS. The Perl internals docs really suck in places.
*/
if (SvTRUE(get_sv(TT_PRIVATE, FALSE))) {
return (*name == '_' || *name == '.');
}
return 0;
}
/* XS versions of some common dot operations
* ----------------------------------------- */
/* list.first */
static SV *list_dot_first(pTHX_ AV *list, AV *args) {
SV **svp;
if ((svp = av_fetch(list, 0, FALSE))) {
/* entry fetched from arry may be code ref */
if (SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVCV) {
return call_coderef(aTHX_ *svp, args);
} else {
return *svp;
}
}
return &PL_sv_undef;
}
/* list.join */
static SV *list_dot_join(pTHX_ AV *list, AV *args) {
SV **svp;
SV *item, *retval;
I32 size, i;
STRLEN jlen;
char *joint;
if (args && (svp = av_fetch(args, 0, FALSE)) != NULL) {
joint = SvPV(*svp, jlen);
} else {
joint = " ";
jlen = 1;
}
retval = newSVpvn("", 0);
size = av_len(list);
for (i = 0; i <= size; i++) {
if ((svp = av_fetch(list, i, FALSE)) != NULL) {
item = *svp;
if (SvROK(item) && SvTYPE(SvRV(item)) == SVt_PVCV) {
item = call_coderef(aTHX_ *svp, args);
sv_catsv(retval, item);
} else {
sv_catsv(retval, item);
}
if (i != size)
sv_catpvn(retval, joint, jlen);
}
}
return sv_2mortal(retval);
}
/* list.last */
static SV *list_dot_last(pTHX_ AV *list, AV *args) {
SV **svp;
if ((av_len(list) > -1)
&& (svp = av_fetch(list, av_len(list), FALSE))) {
/* entry fetched from arry may be code ref */
if (SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVCV) {
return call_coderef(aTHX_ *svp, args);
} else {
return *svp;
}
}
return &PL_sv_undef;
}
/* list.max */
static SV *list_dot_max(pTHX_ AV *list, AV *args) {
return sv_2mortal(newSViv((IV) av_len(list)));
}
/* list.reverse */
static SV *list_dot_reverse(pTHX_ AV *list, AV *args) {
SV **svp;
AV *result = newAV();
I32 size, i;
if ((size = av_len(list)) >= 0) {
av_extend(result, size + 1);
for (i = 0; i <= size; i++) {
if ((svp = av_fetch(list, i, FALSE)) != NULL)
if (!av_store(result, size - i, SvREFCNT_inc(*svp)))
SvREFCNT_dec(*svp);
}
}
return sv_2mortal((SV *) newRV_noinc((SV *) result));
}
/* list.size */
static SV *list_dot_size(pTHX_ AV *list, AV *args) {
return sv_2mortal(newSViv((IV) av_len(list) + 1));
}
/* hash.each */
static SV *hash_dot_each(pTHX_ HV *hash, AV *args) {
AV *result = newAV();
HE *he;
hv_iterinit(hash);
while ((he = hv_iternext(hash))) {
av_push(result, SvREFCNT_inc((SV *) hv_iterkeysv(he)));
av_push(result, SvREFCNT_inc((SV *) hv_iterval(hash, he)));
}
return sv_2mortal((SV *) newRV_noinc((SV *) result));
}
/* hash.keys */
static SV *hash_dot_keys(pTHX_ HV *hash, AV *args) {
AV *result = newAV();
HE *he;
hv_iterinit(hash);
while ((he = hv_iternext(hash)))
av_push(result, SvREFCNT_inc((SV *) hv_iterkeysv(he)));
return sv_2mortal((SV *) newRV_noinc((SV *) result));
}
/* hash.values */
static SV *hash_dot_values(pTHX_ HV *hash, AV *args) {
AV *result = newAV();
HE *he;
hv_iterinit(hash);
while ((he = hv_iternext(hash)))
av_push(result, SvREFCNT_inc((SV *) hv_iterval(hash, he)));
return sv_2mortal((SV *) newRV_noinc((SV *) result));
}
/* scalar.defined */
static SV *scalar_dot_defined(pTHX_ SV *sv, AV *args) {
return &PL_sv_yes;
}
/* scalar.length */
static SV *scalar_dot_length(pTHX_ SV *sv, AV *args) {
return sv_2mortal(newSViv((IV) SvUTF8(sv) ? sv_len_utf8(sv): sv_len(sv)));
}
/*====================================================================
* XS SECTION
*====================================================================*/
MODULE = Template::Stash::XS PACKAGE = Template::Stash::XS
PROTOTYPES: DISABLED
#-----------------------------------------------------------------------
# get(SV *root, SV *ident, SV *args)
#-----------------------------------------------------------------------
SV *
get(root, ident, ...)
SV *root
SV *ident
CODE:
AV *args;
int flags = get_debug_flag(aTHX_ root);
int n;
STRLEN len;
char *str;
/* look for a list ref of arguments, passed as third argument */
args =
(items > 2 && SvROK(ST(2)) && SvTYPE(SvRV(ST(2))) == SVt_PVAV)
? (AV *) SvRV(ST(2)) : Nullav;
if (SvROK(ident) && (SvTYPE(SvRV(ident)) == SVt_PVAV)) {
RETVAL = do_getset(aTHX_ root, (AV *) SvRV(ident), NULL, flags);
}
else if (SvROK(ident)) {
croak(TT_STASH_PKG ": get (arg 2) must be a scalar or listref");
}
else if ((str = SvPV(ident, len)) && memchr(str, '.', len)) {
/* convert dotted string into an array */
AV *av = convert_dotted_string(aTHX_ str, len);
RETVAL = do_getset(aTHX_ root, av, NULL, flags);
av_undef(av);
}
else {
/* otherwise ident is a scalar so we call dotop() just once */
RETVAL = dotop(aTHX_ root, ident, args, flags);
}
if (!SvOK(RETVAL)) {
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs(root);
XPUSHs(ident);
PUTBACK;
n = call_method("undefined", G_SCALAR);
SPAGAIN;
if (n != 1)
croak("undefined() did not return a single value\n");
RETVAL = SvREFCNT_inc(POPs);
PUTBACK;
FREETMPS;
LEAVE;
}
else
RETVAL = SvREFCNT_inc(RETVAL);
OUTPUT:
RETVAL
#-----------------------------------------------------------------------
# set(SV *root, SV *ident, SV *value, SV *deflt)
#-----------------------------------------------------------------------
SV *
set(root, ident, value, ...)
SV *root
SV *ident
SV *value
CODE:
int flags = get_debug_flag(aTHX_ root);
STRLEN len;
char *str;
/* check default flag passed as fourth argument */
flags |= ((items > 3) && SvTRUE(ST(3))) ? TT_DEFAULT_FLAG : 0;
if (SvROK(ident) && (SvTYPE(SvRV(ident)) == SVt_PVAV)) {
RETVAL = do_getset(aTHX_ root, (AV *) SvRV(ident), value, flags);
}
else if (SvROK(ident)) {
croak(TT_STASH_PKG ": set (arg 2) must be a scalar or listref");
}
else if ((str = SvPV(ident, len)) && memchr(str, '.', len)) {
/* convert dotted string into a temporary array */
AV *av = convert_dotted_string(aTHX_ str, len);
RETVAL = do_getset(aTHX_ root, av, value, flags);
av_undef(av);
}
else {
/* otherwise a simple scalar so call assign() just once */
RETVAL = assign(aTHX_ root, ident, Nullav, value, flags);
}
if (!SvOK(RETVAL))
RETVAL = newSVpvn("", 0); /* new empty string */
else
RETVAL = SvREFCNT_inc(RETVAL);
OUTPUT:
RETVAL
Jump to Line
Something went wrong with that request. Please try again.