File Coverage

lib/Object/PadX/Enum.xs
Criterion Covered Total %
statement 78 82 95.1
branch 18 28 64.2
condition n/a
subroutine n/a
pod n/a
total 96 110 87.2


line stmt bran cond sub pod time code
1             /* Object::PadX::Enum
2             *
3             * Thin XS layer registering two keywords (`enum`, `item`) via XS::Parse::Keyword.
4             * All non-trivial work happens in Object::PadX::Enum (the .pm) via the
5             * documented Object::Pad::MOP::Class API.
6             *
7             * Pattern reference: Object-Pad-0.825/lib/Object/Pad.xs:406-625 (build_classlike)
8             */
9              
10             #define PERL_NO_GET_CONTEXT
11             #include "EXTERN.h"
12             #include "perl.h"
13             #include "XSUB.h"
14              
15             #include "XSParseKeyword.h"
16              
17             /* lex_consume_unichar is not part of the stable lexer-API export surface in
18             * all perls; provide a tiny local equivalent. Same shim as
19             * Object-Pad-0.825/hax/perl-additions.c.inc.
20             */
21 42           static bool S_lex_consume_unichar(pTHX_ U32 c)
22             {
23 42 50         if (lex_peek_unichar(0) != c)
24 0           return FALSE;
25 42           lex_read_unichar(0);
26 42           return TRUE;
27             }
28             #define lex_consume_unichar(c) S_lex_consume_unichar(aTHX_ (c))
29              
30             /* Single-keyword compile-time state. We do not support nested enums; a saved
31             * snapshot at entry makes accidental nesting visible as a parse-time error
32             * via `item`'s check hook rather than as silent corruption.
33             */
34             static int inside_enum_depth = 0;
35             static SV *current_enum_classname = NULL;
36              
37             /* Construct an OP tree that, at runtime, calls a named fully-qualified Perl
38             * sub with the given pre-built list of argument OPs.
39             *
40             * `args_list` must be an OP_LIST op (newLISTOP(OP_LIST, ...)). Ownership of
41             * `args_list` is transferred to the returned op.
42             */
43 54           static OP *S_make_call_op(pTHX_ const char *subname, OP *args_list)
44             {
45 54           GV *gv = gv_fetchpv(subname, GV_ADD, SVt_PVCV);
46 54           OP *cv_ref = newCVREF(0, newGVOP(OP_GV, 0, gv));
47              
48 54           args_list = op_append_elem(OP_LIST, args_list, cv_ref);
49 54           return newUNOP(OP_ENTERSUB, OPf_STACKED, args_list);
50             }
51             #define make_call_op(name, list) S_make_call_op(aTHX_ (name), (list))
52              
53             /* --------------------------------------------------------------------- */
54             /* `enum NAME { BODY }` */
55             /* --------------------------------------------------------------------- */
56              
57 28           static int build_enum(pTHX_ OP **out, XSParseKeywordPiece *args[], size_t nargs, void *hookdata)
58             {
59             PERL_UNUSED_ARG(hookdata);
60             PERL_UNUSED_ARG(nargs);
61              
62 28           SV *packagename = args[0]->sv;
63 28           int nattrs = args[1]->i;
64              
65             /* Snapshot prior context so a stray nested `enum` won't corrupt state. */
66 28           SV *saved_classname = current_enum_classname;
67 28           int saved_depth = inside_enum_depth;
68              
69 28           current_enum_classname = packagename;
70 28           inside_enum_depth = 1;
71              
72             /* Marshal `[name, value_or_undef]` pairs into a Perl AV ref for the helper. */
73 28           AV *attrs_av = newAV();
74 47 100         for (int i = 0; i < nattrs; i++) {
75 19           AV *pair = newAV();
76 19           av_push(pair, SvREFCNT_inc(args[2 + i]->attr.name));
77 19           SV *value = args[2 + i]->attr.value;
78 19 50         av_push(pair, value ? SvREFCNT_inc(value) : newSV(0));
79 19           av_push(attrs_av, newRV_noinc((SV *)pair));
80             }
81 28           SV *attrs_ref = sv_2mortal(newRV_noinc((SV *)attrs_av));
82              
83             /* Drive Object::Pad::MOP::Class->begin_class via the Perl helper. This
84             * sets compclassmeta, registers UNITCHECK auto-seal, and adds $ordinal.
85             */
86             {
87 28           dSP;
88 28           ENTER;
89 28           SAVETMPS;
90 28 50         PUSHMARK(SP);
91 28 50         XPUSHs(packagename);
92 28 50         XPUSHs(attrs_ref);
93 28           PUTBACK;
94 28           call_pv("Object::PadX::Enum::_begin_enum", G_VOID | G_DISCARD);
95 21 50         FREETMPS;
96 21           LEAVE;
97             }
98              
99 21           lex_read_space(0);
100 21 50         if (!lex_consume_unichar('{'))
101 0           croak("Expected '{' after 'enum %" SVf "'", SVfARG(packagename));
102              
103 21           ENTER;
104              
105             /* Object::Pad's `field`/`method` keywords assert PL_curstname matches
106             * compclassmeta's name. begin_class sets compclassmeta but not the
107             * package; do that here, mirroring Pad.xs:546-555. SAVE machinery
108             * ensures restoration at the matching LEAVE.
109             */
110 21           SAVEGENERICSV(PL_curstash);
111 21           save_item(PL_curstname);
112 21           PL_curstash = (HV *)SvREFCNT_inc(gv_stashsv(packagename, GV_ADD));
113 21           sv_setsv(PL_curstname, packagename);
114              
115 21           I32 save_ix = block_start(TRUE);
116              
117 21           OP *body = parse_stmtseq(0);
118 21           body = block_end(save_ix, body);
119              
120 21 50         if (!lex_consume_unichar('}'))
121 0           croak("Expected '}' at end of 'enum %" SVf "' body", SVfARG(packagename));
122              
123 21           LEAVE;
124              
125 21           inside_enum_depth = saved_depth;
126 21           current_enum_classname = saved_classname;
127              
128             /* Trailing runtime call: Object::PadX::Enum::_finalize_enum("NAME"); */
129 21           OP *finalize_args = newLISTOP(OP_LIST, 0, NULL, NULL);
130 21           finalize_args = op_append_elem(OP_LIST, finalize_args,
131             newSVOP(OP_CONST, 0, SvREFCNT_inc(packagename)));
132              
133 21           OP *finalize_call = make_call_op("Object::PadX::Enum::_finalize_enum", finalize_args);
134 21           OP *finalize_stmt = newSTATEOP(0, NULL, finalize_call);
135              
136             /* body may be NULL for an empty enum block. */
137 21           OP *combined = body
138 21           ? op_append_elem(OP_LINESEQ, body, finalize_stmt)
139 21 50         : finalize_stmt;
140              
141             /* Wrap in a once-loop so it behaves as a single statement, mirroring
142             * Object::Pad's class-block emission (Pad.xs:589-591).
143             */
144 21           *out = op_append_elem(OP_LINESEQ,
145             newWHILEOP(0, 1, NULL, NULL, combined, NULL, 0),
146             newSVOP(OP_CONST, 0, &PL_sv_yes));
147              
148 21           return KEYWORD_PLUGIN_STMT;
149             }
150              
151             static const struct XSParseKeywordPieceType pieces_enum[] = {
152             XPK_PACKAGENAME,
153             XPK_ATTRIBUTES,
154             {0}
155             };
156              
157             static const struct XSParseKeywordHooks hooks_enum = {
158             .permit_hintkey = "Object::PadX::Enum/enum",
159             .pieces = pieces_enum,
160             .build = &build_enum,
161             };
162              
163             /* --------------------------------------------------------------------- */
164             /* `item NAME ( args, ... );` */
165             /* --------------------------------------------------------------------- */
166              
167 35           static void check_item(pTHX_ void *hookdata)
168             {
169             PERL_UNUSED_ARG(hookdata);
170              
171 35 100         if (!inside_enum_depth)
172 2           croak("'item' is only valid inside an 'enum { ... }' block");
173 33           }
174              
175 33           static int build_item(pTHX_ OP **out, XSParseKeywordPiece *args[], size_t nargs, void *hookdata)
176             {
177             PERL_UNUSED_ARG(hookdata);
178             PERL_UNUSED_ARG(nargs);
179              
180 33           SV *itemname = args[0]->sv;
181 33           int has_parens = args[1]->i;
182 33 100         OP *listexpr = has_parens ? args[2]->op : NULL;
183 33           int line = args[0]->line;
184              
185 33 50         if (!current_enum_classname)
186 0           croak("Internal error: 'item %" SVf "' has no enclosing enum class", SVfARG(itemname));
187              
188             /* Runtime call: _register_item(CLASSNAME, NAME, LINE, ARGS...); */
189 33           OP *call_args = newLISTOP(OP_LIST, 0, NULL, NULL);
190 33           call_args = op_append_elem(OP_LIST, call_args,
191             newSVOP(OP_CONST, 0, SvREFCNT_inc(current_enum_classname)));
192 33           call_args = op_append_elem(OP_LIST, call_args,
193             newSVOP(OP_CONST, 0, SvREFCNT_inc(itemname)));
194 33           call_args = op_append_elem(OP_LIST, call_args,
195             newSVOP(OP_CONST, 0, newSViv(line)));
196              
197 33 100         if (listexpr)
198 6           call_args = op_append_elem(OP_LIST, call_args, listexpr);
199              
200 33           *out = make_call_op("Object::PadX::Enum::_register_item", call_args);
201 33           return KEYWORD_PLUGIN_STMT;
202             }
203              
204             static const struct XSParseKeywordPieceType pieces_item[] = {
205             XPK_IDENT,
206             XPK_PARENS_OPT(XPK_LISTEXPR),
207             XPK_AUTOSEMI,
208             {0}
209             };
210              
211             static const struct XSParseKeywordHooks hooks_item = {
212             .permit_hintkey = "Object::PadX::Enum/item",
213             .pieces = pieces_item,
214             .check = &check_item,
215             .build = &build_item,
216             };
217              
218             /* --------------------------------------------------------------------- */
219              
220             MODULE = Object::PadX::Enum PACKAGE = Object::PadX::Enum
221              
222             PROTOTYPES: DISABLE
223              
224             BOOT:
225 8           boot_xs_parse_keyword(0.48);
226 8           register_xs_parse_keyword("enum", &hooks_enum, NULL);
227 8           register_xs_parse_keyword("item", &hooks_item, NULL);