File Coverage

lib/Enum/Declare.xs
Criterion Covered Total %
statement 234 242 96.6
branch 118 180 65.5
condition n/a
subroutine n/a
pod n/a
total 352 422 83.4


line stmt bran cond sub pod time code
1             #include "EXTERN.h"
2             #include "perl.h"
3             #include "callparser1.h"
4             #include "XSUB.h"
5              
6             #ifndef XS_INTERNAL
7             #define XS_INTERNAL(name) static XSPROTO(name)
8             #endif
9              
10 69           static int has_attr(pTHX_ SV *attr_sv, const char *name) {
11             const char *p, *end;
12             STRLEN alen, nlen;
13 69 100         if (!SvOK(attr_sv)) return 0;
14 42           p = SvPV(attr_sv, alen);
15 42           end = p + alen;
16 42           nlen = strlen(name);
17 74 100         while (p < end) {
18 50           const char *comma = (const char *)memchr(p, ',', end - p);
19 50 100         STRLEN seg = comma ? (STRLEN)(comma - p) : (STRLEN)(end - p);
20 50 100         if (seg == nlen && memEQ(p, name, nlen)) return 1;
    50          
21 32           p += seg + 1;
22             }
23 24           return 0;
24             }
25              
26 118           static SV *lex_read_ident(pTHX) {
27 118           SV *buf = newSVpvs("");
28             I32 c;
29             while (1) {
30 647           c = lex_peek_unichar(0);
31 647 50         if (c == -1) break;
32 647 50         if (!isALNUM(c) && c != '_') break;
    100          
    50          
33 529           sv_catpvf(buf, "%c", (int)c);
34 529           lex_read_unichar(0);
35             }
36 118 50         if (SvCUR(buf) == 0) {
37 0           SvREFCNT_dec(buf);
38 0           return NULL;
39             }
40 118           return buf;
41             }
42              
43 23           static SV *lex_read_attrs(pTHX) {
44 23           SV *attr_sv = &PL_sv_undef;
45 23           I32 c = lex_peek_unichar(0);
46 41 100         while (c == ':') {
47             SV *a;
48 18           lex_read_unichar(0);
49 18           a = lex_read_ident(aTHX);
50 18 50         if (!a) croak("Expected attribute name after ':'");
51 18 100         if (attr_sv == &PL_sv_undef) {
52 14           attr_sv = a;
53             } else {
54 4           sv_catpvs(attr_sv, ",");
55 4           sv_catsv(attr_sv, a);
56 4           SvREFCNT_dec(a);
57             }
58 18           lex_read_space(0);
59 18           c = lex_peek_unichar(0);
60             }
61 23           return attr_sv;
62             }
63              
64 9           static SV *lex_read_quoted_string(pTHX) {
65 9           I32 quote = lex_peek_unichar(0);
66             SV *sv;
67             I32 c;
68 9           lex_read_unichar(0);
69 9           sv = newSVpvs("");
70             while (1) {
71 45           c = lex_read_unichar(0);
72 45 50         if (c == -1) croak("Unterminated string in enum declaration");
73 45 50         if (c == '\\' && quote == '"') {
    0          
74 0           I32 next = lex_read_unichar(0);
75 0 0         if (next == -1) croak("Unterminated string in enum declaration");
76 0           sv_catpvf(sv, "%c", (int)next);
77 45 100         } else if (c == quote) {
78 9           break;
79             } else {
80 36           sv_catpvf(sv, "%c", (int)c);
81             }
82             }
83 9           return sv;
84             }
85              
86 8           static SV *lex_read_integer(pTHX) {
87 8           int is_neg = 0;
88             SV *buf;
89             IV ival;
90 8           I32 c = lex_peek_unichar(0);
91 8 100         if (c == '-') {
92 1           is_neg = 1;
93 1           lex_read_unichar(0);
94 1           lex_read_space(0);
95 1           c = lex_peek_unichar(0);
96             }
97 8 50         if (!isDIGIT(c))
98 0           croak("Expected integer or string value after '=' in enum declaration");
99 8           buf = newSVpvs("");
100             while (1) {
101 25           c = lex_peek_unichar(0);
102 25 50         if (c == -1 || !isDIGIT(c)) break;
    100          
103 17           sv_catpvf(buf, "%c", (int)c);
104 17           lex_read_unichar(0);
105             }
106 8           ival = SvIV(buf);
107 8           SvREFCNT_dec(buf);
108 8 100         if (is_neg) ival = -ival;
109 8           return newSViv(ival);
110             }
111              
112 23           static AV *lex_read_variants(pTHX) {
113 23           AV *av = newAV();
114             I32 c;
115 23           lex_read_space(0);
116 23           c = lex_peek_unichar(0);
117 23 50         if (c != '{') croak("Expected '{' after enum name");
118 23           lex_read_unichar(0);
119 77           while (1) {
120             SV *vname;
121 100           lex_read_space(0);
122 100           c = lex_peek_unichar(0);
123 100 100         if (c == '}') { lex_read_unichar(0); break; }
124 77 50         if (c == -1) croak("Unexpected end of input in enum declaration");
125 77           vname = lex_read_ident(aTHX);
126 77 50         if (!vname) croak("Expected variant name in enum declaration");
127 77           av_push(av, vname);
128             /* optional = value */
129 77           lex_read_space(0);
130 77           c = lex_peek_unichar(0);
131 77 100         if (c == '=') {
132 17           lex_read_unichar(0);
133 17           lex_read_space(0);
134 17           c = lex_peek_unichar(0);
135 17 100         if (c == '"' || c == '\'') {
    100          
136 9           av_push(av, lex_read_quoted_string(aTHX));
137             } else {
138 8           av_push(av, lex_read_integer(aTHX));
139             }
140             } else {
141 60           av_push(av, &PL_sv_undef);
142             }
143 77           lex_read_space(0);
144 77 100         if (lex_peek_unichar(0) == ',') lex_read_unichar(0);
145             }
146 23           return av;
147             }
148              
149             typedef struct {
150             AV *names;
151             AV *values;
152             HV *name2val;
153             HV *val2name;
154             } EnumData;
155              
156 23           static EnumData build_enum_data(pTHX_ AV *variants, int is_str, int is_flags, const char *pkg, STRLEN pkg_len) {
157             EnumData d;
158 23           IV next_ival = is_flags ? 1 : 0;
159 23           I32 pair_count = av_len(variants) + 1;
160             I32 i;
161 23           d.names = newAV();
162 23           d.values = newAV();
163 23           d.name2val = newHV();
164 23           d.val2name = newHV();
165 100 100         for (i = 0; i < pair_count; i += 2) {
166 77           SV **name_p = av_fetch(variants, i, 0);
167 77           SV **val_p = av_fetch(variants, i + 1, 0);
168 77 50         SV *vname = name_p ? *name_p : &PL_sv_undef;
169 77 50         SV *vval = val_p ? *val_p : &PL_sv_undef;
170             SV *resolved;
171             const char *vname_pv;
172             STRLEN vname_len;
173 77           vname_pv = SvPV(vname, vname_len);
174 77 100         if (SvOK(vval)) {
175 17 100         if (is_str) {
176 9           resolved = newSVsv(vval);
177             } else {
178 8           next_ival = SvIV(vval);
179 8           resolved = newSViv(next_ival);
180             }
181 60 100         } else if (is_str) {
182             char *lc;
183             STRLEN j;
184 16           resolved = newSVpvn(vname_pv, vname_len);
185 16           lc = SvPVX(resolved);
186 97 100         for (j = 0; j < vname_len; j++) lc[j] = toLOWER(lc[j]);
    100          
187             } else {
188 44           resolved = newSViv(next_ival);
189             }
190 77           av_push(d.names, newSVpvn(vname_pv, vname_len));
191 77           av_push(d.values, newSVsv(resolved));
192 77           hv_store(d.name2val, vname_pv, vname_len, newSVsv(resolved), 0);
193             {
194             const char *val_pv;
195             STRLEN val_len;
196 77           SV *val_key = newSVsv(resolved);
197 77           val_pv = SvPV(val_key, val_len);
198 77           hv_store(d.val2name, val_pv, val_len,
199             newSVpvn(vname_pv, vname_len), 0);
200 77           SvREFCNT_dec(val_key);
201             }
202             {
203 77           SV *val_copy = newSVsv(resolved);
204 77           newCONSTSUB(gv_stashpvn(pkg, pkg_len, GV_ADD),
205             vname_pv, val_copy);
206             }
207 77 100         if (!is_str)
208 52 100         next_ival = is_flags ? next_ival << 1 : next_ival + 1;
209 77           SvREFCNT_dec(resolved);
210             }
211 23           return d;
212             }
213              
214 8           static void merge_enum_exports(pTHX_ const char *pkg) {
215 8           SV *buf = sv_newmortal();
216             AV *pending, *exp, *exp_ok;
217             I32 j;
218              
219 8           sv_setpvf(buf, "%s::_ENUM_EXPORTS", pkg);
220 8           pending = get_av(SvPV_nolen(buf), 0);
221 8 50         if (!pending || av_len(pending) < 0) return;
    100          
222              
223 4           sv_setpvf(buf, "%s::EXPORT", pkg);
224 4           exp = get_av(SvPV_nolen(buf), GV_ADD);
225              
226 4           sv_setpvf(buf, "%s::EXPORT_OK", pkg);
227 4           exp_ok = get_av(SvPV_nolen(buf), GV_ADD);
228              
229 25 100         for (j = 0; j <= av_len(pending); j++) {
230 21           SV **n = av_fetch(pending, j, 0);
231 21 50         if (n) {
232 21           av_push(exp, newSVsv(*n));
233 21           av_push(exp_ok, newSVsv(*n));
234             }
235             }
236 4           av_clear(pending);
237             }
238              
239 8           XS_INTERNAL(xs_enum_pkg_import) {
240 8           dXSARGS;
241             const char *pkg;
242             CV *exp_import;
243             I32 j;
244              
245 8 50         if (items < 1) croak("Usage: PKG->import(...)");
246 8           pkg = SvPV_nolen(ST(0));
247              
248 8           merge_enum_exports(aTHX_ pkg);
249              
250 8           exp_import = get_cv("Exporter::import", 0);
251 8 50         if (!exp_import) {
252 0           load_module(PERL_LOADMOD_NOIMPORT, newSVpvs("Exporter"), NULL);
253 0           exp_import = get_cv("Exporter::import", 0);
254             }
255 8 50         if (!exp_import) croak("Cannot find Exporter::import");
256              
257             {
258 8           dSP;
259 8           ENTER;
260 8           SAVETMPS;
261 8 50         PUSHMARK(SP);
262 21 100         for (j = 0; j < items; j++) {
263 13 50         XPUSHs(ST(j));
264             }
265 8           PUTBACK;
266 8           call_sv((SV*)exp_import, G_VOID | G_DISCARD);
267 8 50         FREETMPS;
268 8           LEAVE;
269             }
270              
271 8           XSRETURN_EMPTY;
272             }
273              
274 5           static void setup_exports(pTHX_ const char *pkg, STRLEN pkg_len, AV *names, SV *enum_name) {
275 5           SV *buf = sv_newmortal();
276             AV *pending;
277             AV *tag_list;
278             HV *export_tags;
279             I32 j;
280              
281             /* Store exports for deferred merge at import() time */
282 5           sv_setpvf(buf, "%s::_ENUM_EXPORTS", pkg);
283 5           pending = get_av(SvPV_nolen(buf), GV_ADD);
284              
285 21 100         for (j = 0; j <= av_len(names); j++) {
286 16           SV **n = av_fetch(names, j, 0);
287 16 50         if (n) av_push(pending, newSVsv(*n));
288             }
289 5 50         if (enum_name) av_push(pending, newSVsv(enum_name));
290              
291             /* Populate %EXPORT_TAGS with enum-name tag */
292 5 50         if (enum_name) {
293 5           sv_setpvf(buf, "%s::EXPORT_TAGS", pkg);
294 5           export_tags = get_hv(SvPV_nolen(buf), GV_ADD);
295              
296 5           tag_list = newAV();
297 21 100         for (j = 0; j <= av_len(names); j++) {
298 16           SV **n = av_fetch(names, j, 0);
299 16 50         if (n) av_push(tag_list, newSVsv(*n));
300             }
301 5           av_push(tag_list, newSVsv(enum_name));
302 5           (void)hv_store_ent(export_tags, enum_name, newRV_noinc((SV*)tag_list), 0);
303             }
304              
305             /* Install custom import if not already present */
306 5           sv_setpvf(buf, "%s::import", pkg);
307 5 100         if (!get_cv(SvPV_nolen(buf), 0)) {
308 4           newXS(SvPV_nolen(buf), xs_enum_pkg_import, __FILE__);
309             }
310 5           }
311              
312 23           static void install_meta(pTHX_ SV *name_sv, const char *pkg, STRLEN pkg_len, EnumData *d) {
313 23           dSP;
314             SV *meta;
315 23           const char *ename = SvPV_nolen(name_sv);
316 23           ENTER;
317 23           SAVETMPS;
318 23 50         PUSHMARK(SP);
319 23 50         mXPUSHs(newSVpvs("Enum::Declare::Meta"));
320 23 50         mXPUSHs(newSVpvs("enum_name")); mXPUSHs(newSVsv(name_sv));
    50          
321 23 50         mXPUSHs(newSVpvs("package")); mXPUSHs(newSVpvn(pkg, pkg_len));
    50          
322 23 50         mXPUSHs(newSVpvs("names")); mXPUSHs(newRV_noinc((SV*)d->names));
    50          
323 23 50         mXPUSHs(newSVpvs("values")); mXPUSHs(newRV_noinc((SV*)d->values));
    50          
324 23 50         mXPUSHs(newSVpvs("name2val")); mXPUSHs(newRV_noinc((SV*)d->name2val));
    50          
325 23 50         mXPUSHs(newSVpvs("val2name")); mXPUSHs(newRV_noinc((SV*)d->val2name));
    50          
326 23           PUTBACK;
327 23           call_method("new", G_SCALAR);
328 23           SPAGAIN;
329 23           meta = SvREFCNT_inc(POPs);
330 23           PUTBACK;
331 23 50         FREETMPS;
332 23           LEAVE;
333             {
334 23           HV *registry = get_hv("Enum::Declare::_registry", GV_ADD);
335 23           SV *key = newSVpvf("%s::%s", pkg, ename);
336 23           hv_store_ent(registry, key, newSVsv(meta), 0);
337 23           SvREFCNT_dec(key);
338             }
339 23           newCONSTSUB(gv_stashpvn(pkg, pkg_len, GV_ADD), ename, meta);
340 23           }
341              
342 23           XS_INTERNAL(xs_enum_stub) {
343 23           dXSARGS;
344             PERL_UNUSED_VAR(items);
345 23           XSRETURN_EMPTY;
346             }
347              
348 23           static OP *enum_parser_callback(pTHX_ GV *namegv, SV *psobj, U32 *flagsp) {
349             SV *name_sv;
350             SV *attr_sv;
351             AV *variants;
352             const char *pkg;
353             STRLEN pkg_len;
354             EnumData data;
355             PERL_UNUSED_ARG(namegv);
356             PERL_UNUSED_ARG(psobj);
357 23           lex_read_space(0);
358 23           name_sv = lex_read_ident(aTHX);
359 23 50         if (!name_sv) croak("Expected enum name");
360 23           lex_read_space(0);
361 23           attr_sv = lex_read_attrs(aTHX);
362 23           variants = lex_read_variants(aTHX);
363 23 50         pkg = HvNAME(PL_curstash);
    50          
    50          
    0          
    50          
    50          
364 23           pkg_len = strlen(pkg);
365 23           data = build_enum_data(aTHX_ variants,
366             has_attr(aTHX_ attr_sv, "Str"),
367             has_attr(aTHX_ attr_sv, "Flags"),
368             pkg, pkg_len
369             );
370 23 100         if (has_attr(aTHX_ attr_sv, "Export"))
371 5           setup_exports(aTHX_ pkg, pkg_len, data.names, name_sv);
372 23           install_meta(aTHX_ name_sv, pkg, pkg_len, &data);
373 23 100         if (attr_sv != &PL_sv_undef) SvREFCNT_dec(attr_sv);
374 23           SvREFCNT_dec(name_sv);
375 23           SvREFCNT_dec((SV*)variants);
376 23           *flagsp |= CALLPARSER_STATEMENT;
377 23           return newNULLLIST();
378             }
379              
380             MODULE = Enum::Declare PACKAGE = Enum::Declare
381             PROTOTYPES: DISABLE
382              
383             void import(...)
384             CODE:
385             {
386 25 50         const char *caller = CopSTASHPV(PL_curcop);
    50          
    50          
    50          
    0          
    50          
    50          
387 25           SV *fqn = newSVpvf("%s::enum", caller);
388 25           CV *cv = newXS(SvPV_nolen(fqn), xs_enum_stub, __FILE__);
389 25           cv_set_call_parser(cv, enum_parser_callback, &PL_sv_undef);
390 25           SvREFCNT_dec(fqn);
391             }