1 /*
2     Text-ClearSilver.xs -  The template processor class
3 
4     Copyright(c) 2010 Craftworks. All rights reserved.
5 
6     See lib/Text/ClearSilver.pm for details.
7 */
8 
9 #define NEED_newSVpvn_flags_GLOBAL
10 #define NO_XSLOCKS
11 #include "Text-ClearSilver.h"
12 
13 #define MY_CXT_KEY "Text::ClearSilver::_guts" XS_VERSION
14 /* my_cxt_t is defined in Text-ClearSilver.h */
15 START_MY_CXT
16 
17 /* my_cxt accessor for HDF.xs */
18 my_cxt_t*
tcs_get_my_cxtp(pTHX)19 tcs_get_my_cxtp(pTHX) {
20     dMY_CXT;
21     return &MY_CXT;
22 }
23 
24 /* ClearSilver can access ibuf out of range of memory :(
25    so extra some memory must be allocated for cs_parse_string().
26 */
27 static const size_t extra_bytes = 8;
28 
29 /*
30     NOTE: Currently, file_cache is always enabled, although it can be disabled.
31  */
32 
33 static NEOERR*
tcs_fileload(void * vcsparse,HDF * const hdf,const char * filename,char ** const contents)34 tcs_fileload(void* vcsparse, HDF* const hdf, const char* filename, char** const contents) {
35     dTHX;
36     dMY_CXT;
37     I32 filename_len;
38     NEOERR* err = STATUS_OK;
39     char fpath[_POSIX_PATH_MAX];
40     Stat_t st;
41     bool stat_ok = FALSE;
42 
43     /* find file */
44     if (filename[0] != '/') {
45         err = hdf_search_path (hdf, filename, fpath);
46         if (((CSPARSE*)vcsparse)->global_hdf && nerr_handle(&err, NERR_NOT_FOUND)) {
47             err = hdf_search_path(((CSPARSE*)vcsparse)->global_hdf, filename, fpath);
48         }
49         if (err != STATUS_OK) return nerr_pass(err);
50 
51         filename      = fpath;
52     }
53     filename_len = strlen(filename);
54 
55     /* check cache */
56     if(MY_CXT.file_cache){
57         SV** const svp = hv_fetch(MY_CXT.file_cache, filename, filename_len, FALSE);
58 
59         if(svp){
60             SV* const mtime_cache    = AvARRAY(SvRV(*svp))[0];
61             SV* const contents_cache = AvARRAY(SvRV(*svp))[1];
62 
63             if(PerlLIO_stat(filename, &st) < 0) {
64                 return nerr_raise(NERR_IO, "Failed to stat(%s): %s", filename, Strerror(errno));
65             }
66             stat_ok = TRUE;
67 
68             assert(SvIOK(mtime_cache));
69             assert(SvPOK(contents_cache));
70 
71             if(st.st_mtime == SvIVX(mtime_cache)) {
72                 *contents = (char*)malloc(st.st_size + extra_bytes);
73                 Copy(SvPVX(contents_cache), *contents, st.st_size + 1, char);
74                 return STATUS_OK;
75             }
76         }
77     }
78 
79     /* load file normally */
80     if(!(stat_ok || PerlLIO_stat(filename, &st) >= 0)) {
81         return nerr_raise(NERR_IO, "Failed to stat(%s): %s", filename, Strerror(errno));
82     }
83 
84     ENTER;
85     SAVETMPS;
86     {
87         SV* namesv = newSVpvn_flags(filename, filename_len, SVs_TEMP);
88         SV* file_buf;
89         PerlIO* const ifp =  PerlIO_openn(aTHX_
90             MY_CXT.input_layer, "r", -1, O_RDONLY, 0, NULL, 1, &namesv);
91 
92         if(!ifp){
93             err = nerr_raise(NERR_IO, "Failed to open %s: %s", filename, Strerror(errno));
94             goto cleanup;
95         }
96 
97         file_buf = sv_2mortal(newSV(st.st_size));
98 
99         /* local $/ = undef */
100         SAVESPTR(PL_rs);
101         PL_rs = &PL_sv_undef;
102 
103         sv_gets(file_buf, ifp, FALSE);
104 
105         if(PerlIO_error(ifp)) {
106             PerlIO_close(ifp);
107             err = nerr_raise(NERR_IO, "Failed to gets");
108             goto cleanup;
109         }
110 
111         PerlIO_close(ifp);
112 
113         *contents = (char*)malloc(SvCUR(file_buf) + extra_bytes);
114         Copy(SvPVX(file_buf), *contents, SvCUR(file_buf) + 1, char);
115 
116         if(MY_CXT.file_cache){
117             SV* cache_entry[2]; /* mtime, contents */
118 
119             cache_entry[0] = newSViv(st.st_mtime);
120             cache_entry[1] = SvREFCNT_inc_simple_NN(file_buf);
121 
122             (void)hv_store(MY_CXT.file_cache, filename, filename_len,
123                 newRV_noinc((SV*)av_make(2, cache_entry)), 0U);
124         }
125     }
126 
127     cleanup:
128     FREETMPS;
129     LEAVE;
130     return err;
131 }
132 
133 /* in csparse.c */
134 NEOERR*
135 tcs_eval_expr(CSPARSE* parse, CSARG* arg, CSARG* result);
136 const char*
137 tcs_var_lookup(CSPARSE* parse, const char* name);
138 long
139 tcs_var_int_lookup(CSPARSE* parse, const char* name);
140 HDF*
141 tcs_var_lookup_obj(CSPARSE* parse, const char* name);
142 
143 static NEOERR*
tcs_push_args(pTHX_ CSPARSE * const parse,CSARG * args,const bool utf8)144 tcs_push_args(pTHX_ CSPARSE* const parse, CSARG* args, const bool utf8) {
145     dSP;
146 
147     PUSHMARK(SP);
148 
149     while(args) {
150         const char* str;
151         CSARG val;
152         NEOERR* err;
153         SV* sv;
154 
155         err = tcs_eval_expr(parse, args, &val);
156 
157         if(err){
158             (void)POPMARK;
159 
160             return nerr_pass(err);
161         }
162 
163         sv = sv_newmortal();
164         XPUSHs(sv);
165 
166         switch(val.op_type & CS_TYPES){
167         case CS_TYPE_STRING:
168             assert(val.s);
169             sv_setpv(sv, val.s);
170             if(utf8) {
171                 sv_utf8_decode(sv);
172             }
173             break;
174 
175         case CS_TYPE_VAR:
176             assert(val.s);
177             str = tcs_var_lookup(parse, val.s);
178             if(str) {
179                 sv_setpv(sv, str);
180                 if(utf8) {
181                     sv_utf8_decode(sv);
182                 }
183             }
184             else { /* HDF node */
185                 HDF* const hdf = tcs_var_lookup_obj(parse, val.s);
186                 if(hdf) {
187                     sv_setref_pv(sv, C_HDF, hdf);
188                 }
189             }
190             break;
191 
192         case CS_TYPE_NUM:
193             sv_setiv(sv, val.n);
194             break;
195 
196         case CS_TYPE_VAR_NUM:
197             assert(val.s);
198             sv_setiv(sv, tcs_var_int_lookup(parse, val.s));
199             break;
200 
201         default:
202             /* something's wrong? */
203             break;
204         }
205 
206         if(val.alloc){
207             free(val.s);
208         }
209         args = args->next;
210     }
211     PUTBACK;
212     return STATUS_OK;
213 }
214 
215 /* general cs function wrapper */
216 static NEOERR*
tcs_function_wrapper(CSPARSE * const parse,CS_FUNCTION * const csf,CSARG * const args,CSARG * const result)217 tcs_function_wrapper(CSPARSE* const parse, CS_FUNCTION* const csf, CSARG* const args, CSARG* const result) {
218     dTHX;
219     dMY_CXT;
220     SV** svp;
221     SV* retval;
222     NEOERR* err;
223 
224     assert(MY_CXT.functions);
225 
226     /* XXX: Hey! csf->name_len is not set!! */
227     //svp = hv_fetch(MY_CXT.functions, csf->name, csf->name_len, FALSE);
228     svp = hv_fetch(MY_CXT.functions, csf->name, strlen(csf->name), FALSE);
229     if(!( svp && SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVAV
230             && (svp = av_fetch((AV*)SvRV(*svp), 0, FALSE)) )){
231         return nerr_raise(NERR_ASSERT, "Function entry for %s() is broken", csf->name);
232     }
233 
234     ENTER;
235     SAVETMPS;
236 
237     err = tcs_push_args(aTHX_ parse, args, MY_CXT.utf8); /* PUSHMARK & PUSH & PUTBACK */
238     if(err != STATUS_OK) {
239         err = nerr_pass(err);
240         goto cleanup;
241     }
242 
243     call_sv(*svp, G_SCALAR | G_EVAL);
244 
245     {
246         dSP;
247         SPAGAIN;
248         retval = POPs;
249         PUTBACK;
250     }
251 
252     if(sv_true(ERRSV)){
253         err =  nerr_raise(NERR_ASSERT,
254             "Function %s() died: %s", csf->name, SvPV_nolen_const(ERRSV));
255         goto cleanup;
256     }
257 
258     if(!((SvTYPE(retval) & SVf_OK) == SVf_IOK && PERL_ABS(SvIVX(retval)) <= PERL_LONG_MAX)) {
259         STRLEN len;
260         const char* const pv = SvPV_const(retval, len);
261         len++; /* '\0' */
262 
263         result->op_type = CS_TYPE_STRING;
264         result->s       = (char*)malloc(len);
265         result->alloc    = TRUE;
266         Copy(pv, result->s, len, char);
267     }
268     else { /* SvIOK */
269         result->op_type = CS_TYPE_NUM;
270         result->n       = (long)SvIVX(retval);
271     }
272 
273     cleanup:
274     FREETMPS;
275     LEAVE;
276 
277     return err;
278 }
279 
280 NEOERR*
tcs_parse_sv(pTHX_ CSPARSE * const parse,SV * const sv)281 tcs_parse_sv(pTHX_ CSPARSE* const parse, SV* const sv) {
282     STRLEN str_len;
283     const char* const str = SvPV_const(sv, str_len);
284 
285     char* const ibuf = (char*)malloc(str_len + extra_bytes);
286     if(ibuf == NULL){
287         return nerr_raise (NERR_NOMEM,
288             "Unable to allocate memory");
289     }
290 
291     Copy(str, ibuf, str_len + 1, char); /* with '\0' */
292     return cs_parse_string(parse, ibuf, str_len);
293 }
294 
295 void
tcs_throw_error(pTHX_ NEOERR * const err)296 tcs_throw_error(pTHX_ NEOERR* const err) {
297     SV* sv;
298     STRING errstr;
299     string_init(&errstr);
300     nerr_error_string(err, &errstr);
301     sv = newSVpvn_flags(errstr.buf, errstr.len, SVs_TEMP);
302     string_clear(&errstr);
303 
304     Perl_croak(aTHX_ "ClearSilver: %"SVf, sv);
305 }
306 
307 
308 static CV*
tcs_sv2cv(pTHX_ SV * const func)309 tcs_sv2cv(pTHX_ SV* const func) {
310     HV* stash; /* unused */
311     GV* gv;    /* unused */
312     CV* const cv = sv_2cv(func, &stash, &gv, 0);
313     if(!cv){
314         croak("Not a CODE reference");
315     }
316     return cv;
317 }
318 
319 static HV*
tcs_deref_hv(pTHX_ SV * const hvref)320 tcs_deref_hv(pTHX_ SV* const hvref) {
321     if(!(SvROK(hvref) && SvTYPE(SvRV(hvref)) == SVt_PVHV)) {
322         croak("Not a HASH reference");
323     }
324     return (HV*)SvRV(hvref);
325 }
326 
327 
328 static const char*
tcs_get_class_name(pTHX_ SV * const self)329 tcs_get_class_name(pTHX_ SV* const self) {
330     if(SvROK(self) && SvOBJECT(SvRV(self))){
331         HV* const stash = SvSTASH(SvRV(self));
332         return HvNAME_get(stash);
333     }
334     else {
335         return SvPV_nolen_const(self);
336     }
337 }
338 
339 static bool
tcs_is_utf8(pTHX_ SV * const tcs)340 tcs_is_utf8(pTHX_ SV* const tcs) {
341     SV** const svp = hv_fetchs(tcs_deref_hv(aTHX_ tcs), "utf8", FALSE);
342     return svp ? sv_true(*svp) : FALSE;
343 }
344 
345 
346 static void
tcs_register_function(pTHX_ SV * const self,SV * const name,SV * const func,IV const n_args)347 tcs_register_function(pTHX_ SV* const self, SV* const name, SV* const func, IV const n_args) {
348     SV** const svp = hv_fetchs(tcs_deref_hv(aTHX_ self), "functions", FALSE);
349     HV* hv;
350     SV* pair[2];
351     if(svp) {
352         hv = tcs_deref_hv(aTHX_ *svp);
353     }
354     else {
355         hv = newHV();
356         (void)hv_stores(tcs_deref_hv(aTHX_ self), "functions", newRV_noinc((SV*)hv));
357     }
358 
359     pair[0] = newRV_inc((SV*)tcs_sv2cv(aTHX_ func));
360     pair[1] = newSViv(n_args);
361 
362     (void)hv_store_ent(hv, name, newRV_noinc((SV*)av_make(2, pair)), 0U);
363 }
364 static void
tcs_load_function_set(pTHX_ SV * const self,SV * const val)365 tcs_load_function_set(pTHX_ SV* const self, SV* const val) {
366     dMY_CXT;
367 
368     ENTER;
369     SAVETMPS;
370 
371     if(!MY_CXT.function_set_is_loaded){
372         require_pv("Text/ClearSilver/FunctionSet.pm");
373         if(sv_true(ERRSV)){
374             croak("ClearSilver: panic: %"SVf, ERRSV);
375         }
376         MY_CXT.function_set_is_loaded = TRUE;
377     }
378 
379     SAVESPTR(ERRSV);
380     ERRSV = sv_newmortal();
381     {
382         dSP;
383         HV* set;
384         HE* he;
385 
386         PUSHMARK(SP);
387         EXTEND(SP, 2);
388         PUSHs(newSVpvs_flags("Text::ClearSilver::FunctionSet", SVs_TEMP));
389         PUSHs(val);
390         PUTBACK;
391         call_method("load", G_SCALAR | G_EVAL);
392 
393         if(sv_true(ERRSV)){
394             croak("ClearSilver: cannot load a function set: %"SVf, ERRSV);
395         }
396 
397         SPAGAIN;
398         set = tcs_deref_hv(aTHX_ POPs);
399         PUTBACK;
400 
401         hv_iterinit(set);
402         while((he = hv_iternext(set))){
403             tcs_register_function(aTHX_ self, hv_iterkeysv(he), hv_iterval(set, he), -1);
404         }
405     }
406     FREETMPS;
407     LEAVE;
408 }
409 
410 static void
tcs_set_config(pTHX_ SV * const self,HV * const hv,HDF * const hdf,SV * const key,SV * const val)411 tcs_set_config(pTHX_ SV* const self, HV* const hv, HDF* const hdf, SV* const key, SV* const val) {
412     const char* const keypv = SvPV_nolen_const(key);
413     if(isUPPER(*keypv)){ /* builtin config */
414         HDF* config;
415         CHECK_ERR( hdf_get_node(hdf, "Config", &config) );
416         CHECK_ERR( hdf_set_value(config, keypv, SvPV_nolen_const(val)) );
417     }
418     else { /* extended config */
419         if(strEQ(keypv, "encoding")) {
420             const char* const valpv = SvPV_nolen_const(val);
421             bool utf8;
422             if(strEQ(valpv, "utf8")){
423                 utf8 = TRUE;
424             }
425             else if(strEQ(valpv, "bytes")){
426                 utf8 = FALSE;
427             }
428             else {
429                 croak("ClearSilver: encoding must be 'utf8' or 'bytes', not '%s'", valpv);
430             }
431             (void)hv_stores(hv, "utf8", newSViv(utf8));
432         }
433         else if(strEQ(keypv, "input_layer")){
434             (void)hv_stores(hv, "input_layer", newSVsv(val));
435         }
436         else if(strEQ(keypv, "dataset")) {
437             tcs_hdf_add(aTHX_ hdf, val, tcs_is_utf8(aTHX_ self));
438         }
439         else if(strEQ(keypv, "load_path")) {
440             HDF* loadpaths;
441             CHECK_ERR( hdf_get_node(hdf, "hdf.loadpaths", &loadpaths) );
442 
443             tcs_hdf_add(aTHX_ loadpaths, val, tcs_is_utf8(aTHX_ self));
444         }
445         else if(strEQ(keypv, "functions")) {
446             tcs_load_function_set(aTHX_ self, val);
447         }
448         else if(ckWARN(WARN_MISC)) {
449             Perl_warner(aTHX_ packWARN(WARN_MISC), "%s: unknown configuration variable '%s'",
450                 tcs_get_class_name(aTHX_ self), keypv);
451             (void)hv_store_ent(hv, key, newSVsv(val), 0U);
452         }
453     }
454 }
455 
456 static void
tcs_configure(pTHX_ SV * const self,HV * const hv,HDF * const hdf,I32 const ax,I32 const items)457 tcs_configure(pTHX_ SV* const self, HV* const hv, HDF* const hdf, I32 const ax, I32 const items) {
458     if(items == 1) {
459         SV* const args_ref = ST(0);
460         HV* args;
461         HE* he;
462 
463         SvGETMAGIC(args_ref);
464 
465         if(!(SvROK(args_ref) && SvTYPE(SvRV(args_ref)) == SVt_PVHV
466                 && !SvOBJECT(SvRV(args_ref)) )){
467             croak("%s: single parameters to configure must be a HASH ref",
468                 tcs_get_class_name(aTHX_ self));
469         }
470         args = (HV*)SvRV(args_ref);
471 
472         hv_iterinit(args);
473         while((he = hv_iternext(args))) {
474             tcs_set_config(aTHX_ self, hv, hdf, hv_iterkeysv(he), hv_iterval(args, he));
475         }
476     }
477     else {
478         I32 i;
479 
480         if( (items % 2) != 0 ){
481             croak("%s: odd number of parameters to configure",
482                 tcs_get_class_name(aTHX_ self));
483         }
484 
485         for(i = 0; i < items; i += 2){
486             tcs_set_config(aTHX_ self, hv, hdf, ST(i), ST(i+1));
487         }
488     }
489 }
490 
491 static PerlIO*
tcs_sv2io(pTHX_ SV * sv,const char * const mode,int const imode,bool * const need_closep)492 tcs_sv2io(pTHX_ SV* sv, const char* const mode, int const imode, bool* const need_closep) {
493     if(isGV(sv) || (SvROK(sv) && (isGV(SvRV(sv)) || SvTYPE(SvRV(sv)) == SVt_PVIO))){
494         return IoIFP(sv_2io(sv));
495     }
496     else {
497         PerlIO* const fp = PerlIO_openn(aTHX_
498             NULL, mode, -1, imode, 0, NULL, 1, &sv);
499         if(!fp){
500             croak("Cannot open %"SVf": %"SVf, sv, get_sv("!", GV_ADD));
501         }
502         *need_closep = TRUE;
503         return fp;
504     }
505 }
506 
507 void
tcs_register_funcs(pTHX_ CSPARSE * const cs,HV * const funcs)508 tcs_register_funcs(pTHX_ CSPARSE* const cs, HV* const funcs) {
509 
510     /* functions registered by users */
511     if(funcs) {
512         dMY_CXT;
513         char* key;
514         I32 keylen;
515         SV* val;
516 
517         SAVEVPTR(MY_CXT.functions);
518         MY_CXT.functions = funcs;
519 
520         hv_iterinit(funcs);
521         while((val = hv_iternextsv(funcs, &key, &keylen))) {
522             AV* pair;
523             if(!(SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV)){
524                 croak("Function entry for %s() is broken", key);
525             }
526             pair = (AV*)SvRV(val);
527 
528             CHECK_ERR( cs_register_function(cs, key,
529                 SvIVx(*av_fetch(pair, 1, TRUE)), tcs_function_wrapper) );
530         }
531     }
532 
533     /* functions from cgi_register_strfuncs() */
534     CHECK_ERR( cgi_register_strfuncs(cs) );
535 }
536 
537 void*
tcs_get_struct_ptr(pTHX_ SV * const arg,const char * const klass,const char * const func_fq_name,const char * var_name)538 tcs_get_struct_ptr(pTHX_ SV* const arg, const char* const klass,
539         const char* const func_fq_name, const char* var_name) {
540     if(SvROK(arg) && sv_derived_from(arg, klass) && SvIOK(SvRV(arg))){
541         return INT2PTR(void*, SvIVX(SvRV(arg)));
542     }
543 
544     croak("%s: %s is not of type %s", func_fq_name, var_name, klass);
545     return NULL; /* NOT REACHED */
546 }
547 
548 #define register_function(self, name, cb, nargs) tcs_register_function(aTHX_ self, name, cb, nargs)
549 
550 MODULE = Text::ClearSilver    PACKAGE = Text::ClearSilver
551 
552 PROTOTYPES: DISABLE
553 
554 BOOT:
555 {
556     XS(boot_Text__ClearSilver__HDF);
557     XS(boot_Text__ClearSilver__CS);
558     MY_CXT_INIT;
559     MY_CXT.sort_cmp_cb = NULL;
560     MY_CXT.functions   = NULL;
561     MY_CXT.input_layer = NULL;
562     MY_CXT.file_cache  = newHV();
563 
564     PUSHMARK(SP);
565     boot_Text__ClearSilver__HDF(aTHX_ cv);
566     SPAGAIN;
567 
568     PUSHMARK(SP);
569     boot_Text__ClearSilver__CS(aTHX_ cv);
570     SPAGAIN;
571 }
572 
573 #ifdef USE_ITHREADS
574 
575 void
CLONE(...)576 CLONE(...)
577 CODE:
578 {
579     MY_CXT_CLONE;
580     MY_CXT.sort_cmp_cb = NULL;
581     MY_CXT.functions   = NULL;
582     MY_CXT.input_layer = NULL;
583     MY_CXT.file_cache  = newHV();
584     PERL_UNUSED_VAR(items);
585 }
586 
587 #endif
588 
589 void
new(SV * klass,...)590 new(SV* klass, ...)
591 CODE:
592 {
593     HDF* hdf;
594     SV* self;
595     HV* hv;
596     if(SvROK(klass)){
597         croak("Cannot %s->new as an instance method", "Text::ClearSilver");
598     }
599     hv    = newHV();
600     self  = sv_2mortal( newRV_noinc((SV*)hv) );
601     ST(0) = sv_bless(self, gv_stashsv(klass, GV_ADD));
602 
603     CHECK_ERR( hdf_init(&hdf) );
604     sv_setref_pv(*hv_fetchs(hv, "dataset", TRUE), C_HDF, hdf);
605 
606     /* ax+1 && items-1 for shift @_ */
607     tcs_configure(aTHX_ self, hv, hdf, ax + 1, items - 1);
608     XSRETURN(1);
609 }
610 
611 void
612 register_function(SV* self, SV* name, SV* func, int n_args = -1)
613 
614 
615 void
dataset(SV * self)616 dataset(SV* self)
617 CODE:
618 {
619     ST(0) = *hv_fetchs(tcs_deref_hv(aTHX_ self), "dataset", TRUE);
620     XSRETURN(1);
621 }
622 
623 
624 #define DEFAULT_OUT ((SV*)PL_defoutgv)
625 
626 void
627 process(SV* self, SV* src, SV* vars, SV* volatile dest = DEFAULT_OUT, ...)
628 CODE:
629 {
630     dMY_CXT;
631     dXCPT;
632     CSPARSE*  cs         = NULL;
633     HDF*     hdf         = NULL;
634     bool need_ifp_close  = FALSE;
635     bool need_ofp_close  = FALSE;
636     PerlIO* volatile ifp = NULL;
637     PerlIO* volatile ofp = NULL;
638     bool save_utf8;
639     const char* save_input_layer;
640 
641     if(!( SvROK(self) && SvOBJECT(SvRV(self)) )){
642         croak("Cannot %s->process as a class method", "Text::ClearSilver");
643     }
644 
645     SvGETMAGIC(src);
646     SvGETMAGIC(dest);
647 
648     save_utf8   = MY_CXT.utf8;
649     MY_CXT.utf8 = FALSE;
650 
651     save_input_layer   = MY_CXT.input_layer;
652     MY_CXT.input_layer = NULL;
653 
654     XCPT_TRY_START {
655         HV* const hv = tcs_deref_hv(aTHX_ self);
656         const char* input_layer;
657         SV** svp;
658 
659         CHECK_ERR( hdf_init(&hdf) );
660 
661         CHECK_ERR( hdf_copy(hdf, "", (HDF*)tcs_get_struct_ptr(aTHX_
662             *hv_fetchs(hv, "dataset", TRUE), C_HDF, "Text::ClearSilver::process", "dataset")) );
663 
664         if(!(SvROK(dest) && SvTYPE(SvRV(dest)) <= SVt_PVMG)) { /* not a scalar ref */
665             ofp = tcs_sv2io(aTHX_ dest, "w", O_WRONLY|O_CREAT|O_TRUNC, &need_ofp_close);
666         }
667 
668         MY_CXT.utf8 = tcs_is_utf8(aTHX_ self);
669 
670         svp = NULL;
671         if(items > 4){
672             HV* const local_hv = newHV();
673             sv_2mortal((SV*)local_hv);
674             tcs_configure(aTHX_ self, local_hv, hdf, ax + 4, items - 4);
675 
676             svp = hv_fetchs(local_hv, "utf8", FALSE);
677             if(svp) {
678                 MY_CXT.utf8 = sv_true(*svp);
679             }
680 
681             svp = hv_fetchs(local_hv, "input_layer", FALSE);
682         }
683         if(!svp){
684             svp = hv_fetchs(hv, "input_layer", FALSE);
685         }
686 
687         if(svp) {
688             input_layer = SvPV_nolen_const(*svp);
689         }
690         else if(MY_CXT.utf8) {
691             input_layer = ":utf8";
692         }
693         else {
694             input_layer = NULL;
695         }
696 
697         tcs_hdf_add(aTHX_ hdf, vars, MY_CXT.utf8);
698 
699         CHECK_ERR( cs_init(&cs, hdf) );
700 
701         svp = hv_fetchs(hv, "functions", FALSE);
702         tcs_register_funcs(aTHX_ cs, svp ? tcs_deref_hv(aTHX_ *svp) : NULL);
703 
704         cs_register_fileload(cs, cs, tcs_fileload);
705 
706         MY_CXT.input_layer = input_layer;
707 
708         /* parse CS template */
709         if(SvROK(src)){
710             if(SvTYPE(SvRV(src)) > SVt_PVMG){
711                 croak("Source must be a scalar reference or a filename, not %"SVf, src);
712             }
713             CHECK_ERR( tcs_parse_sv(aTHX_ cs, SvRV(src)) );
714         }
715         else {
716             CHECK_ERR( cs_parse_file(cs, SvPV_nolen_const(src)) );
717         }
718 
719         /* render */
720         if(ofp) {
721             if(MY_CXT.utf8 && !PerlIO_isutf8(ofp)) {
722                 PerlIO_binmode(aTHX_ ofp, '>', O_TEXT, ":utf8");
723             }
724             CHECK_ERR( cs_render(cs, ofp, tcs_output_to_io) );
725         }
726         else {
727             sv_setpvs(SvRV(dest), "");
728             if(MY_CXT.utf8) {
729                 SvUTF8_on(SvRV(dest));
730             }
731             CHECK_ERR( cs_render(cs, SvRV(dest), tcs_output_to_sv) );
732         }
733     }
734     XCPT_TRY_END
735 
736     MY_CXT.utf8        = save_utf8;
737     MY_CXT.input_layer = save_input_layer;
738 
739     if(need_ifp_close) PerlIO_close(ifp);
740     if(need_ofp_close) PerlIO_close(ofp);
741 
742     cs_destroy(&cs);
743     hdf_destroy(&hdf);
744 
745     XCPT_CATCH {
746         XCPT_RETHROW;
747     }
748 }
749 
750 
751 void
clear_cache(self)752 clear_cache(self)
753 CODE:
754 {
755     dMY_CXT;
756     if(MY_CXT.file_cache){
757         ST(0) = sv_2mortal(newRV_noinc((SV*)MY_CXT.file_cache));
758         MY_CXT.file_cache = newHV();
759     }
760     else {
761         ST(0) = &PL_sv_undef;
762     }
763     XSRETURN(1);
764 }
765