| 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); |