Skip to content

Commit 0aa7744

Browse files
committed
generate-accessors: extend struct annotation with default mode qualifier
Extend the !generate-accessors annotation so callers can set a struct-level default for accessor generation: struct foo { //!generate-accessors - both get+set (unchanged) struct foo { //!generate-accessors:none - no accessors by default struct foo { //!generate-accessors:readonly - getter only by default struct foo { //!generate-accessors:writeonly - setter only by default Add two new per-member annotations to complement the new struct-level defaults when individual members need to override them: //!accessors:writeonly - setter only for this member //!accessors:readwrite - both getter and setter for this member Internally, replace Member.is_const with explicit gen_getter/gen_setter flags and add parse_struct_annotation() to extract the mode qualifier. Update generate-accessors.md with the full annotation table. Signed-off-by: Daniel Wagner <[email protected]>
1 parent a1b498f commit 0aa7744

2 files changed

Lines changed: 165 additions & 45 deletions

File tree

libnvme/tools/generator/generate-accessors.md

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,29 @@ Struct inclusion and member behaviour are controlled by **annotations written as
2929

3030
### Struct inclusion — `generate-accessors`
3131

32-
Place the annotation on the same line as the struct's opening brace to opt that struct in to code generation:
32+
Place the annotation on the same line as the struct's opening brace to opt that struct in to code generation. An optional mode qualifier sets the **default behaviour for all members** of that struct:
33+
34+
| Annotation | Default for all members |
35+
| --------------------------------------- | --------------------------------- |
36+
| `//!generate-accessors` | getter **and** setter (default) |
37+
| `//!generate-accessors:none` | no accessors |
38+
| `//!generate-accessors:readonly` | getter only |
39+
| `//!generate-accessors:writeonly` | setter only |
3340

3441
```c
35-
struct nvme_ctrl { /*!generate-accessors*/
42+
struct nvme_ctrl { /*!generate-accessors*/ /* both getter and setter */
3643
...
3744
};
38-
```
3945

40-
```c
41-
struct nvme_ctrl { //!generate-accessors
46+
struct nvme_ctrl { //!generate-accessors:readonly /* getter only by default */
4247
...
4348
};
4449
```
4550

4651
Only structs carrying this annotation will have accessors generated. All other structs in the header are ignored.
4752

53+
Individual members can always override the struct-level default using a per-member annotation (see below).
54+
4855
### Member exclusion — `accessors:none`
4956

5057
Place the annotation on a member's declaration line to suppress accessor generation for that member entirely (no setter, no getter):
@@ -59,7 +66,7 @@ struct nvme_ctrl { /*!generate-accessors*/
5966

6067
### Read-only members — `accessors:readonly`
6168

62-
Place the annotation on a member's declaration line to generate only a getter (no setter). This has the same effect as declaring the member `const`, but without changing the type in the struct:
69+
Place the annotation on a member's declaration line to generate only a getter (no setter). This has the same effect as declaring the member `const`, but without changing the type in the struct. Also useful to override a `generate-accessors:writeonly` struct default for individual members:
6370

6471
```c
6572
struct nvme_ctrl { /*!generate-accessors*/
@@ -71,17 +78,42 @@ struct nvme_ctrl { /*!generate-accessors*/
7178

7279
Members declared with the `const` qualifier are also automatically read-only.
7380

81+
### Write-only members — `accessors:writeonly`
82+
83+
Place the annotation on a member's declaration line to generate only a setter (no getter). Useful to override a `generate-accessors:readonly` struct default for individual members:
84+
85+
```c
86+
struct nvme_ctrl { /*!generate-accessors:readonly*/
87+
char *name; /* getter only (struct default) */
88+
char *token; //!accessors:writeonly /* setter only override */
89+
};
90+
```
91+
92+
### Read-write members — `accessors:readwrite`
93+
94+
Place the annotation on a member's declaration line to generate both a getter and a setter, overriding a restrictive struct-level default (`none`, `readonly`, or `writeonly`):
95+
96+
```c
97+
struct nvme_ctrl { /*!generate-accessors:none*/
98+
char *name; /* no accessors (struct default) */
99+
char *model; //!accessors:readwrite /* both getter and setter */
100+
char *firmware; //!accessors:readonly /* getter only */
101+
};
102+
```
103+
74104
### Annotation summary
75105

76-
| Annotation | Where | Effect |
77-
| --------------------------- | ------------ | ------------------------------- |
78-
| `/*!generate-accessors*/` | struct brace | Include this struct |
79-
| `//!generate-accessors` | struct brace | Include this struct |
80-
| `/*!accessors:none*/` | member line | Skip this member entirely |
81-
| `//!accessors:none` | member line | Skip this member entirely |
82-
| `/*!accessors:readonly*/` | member line | Generate getter only |
83-
| `//!accessors:readonly` | member line | Generate getter only |
84-
| `const` qualifier on member | member type | Generate getter only (built-in) |
106+
| Annotation | Where | Effect |
107+
| --------------------------------------- | ------------ | ------------------------------------------- |
108+
| `//!generate-accessors` | struct brace | Include struct, default: getter + setter |
109+
| `//!generate-accessors:none` | struct brace | Include struct, default: no accessors |
110+
| `//!generate-accessors:readonly` | struct brace | Include struct, default: getter only |
111+
| `//!generate-accessors:writeonly` | struct brace | Include struct, default: setter only |
112+
| `//!accessors:none` | member line | Skip this member entirely |
113+
| `//!accessors:readonly` | member line | Generate getter only |
114+
| `//!accessors:writeonly` | member line | Generate setter only |
115+
| `//!accessors:readwrite` | member line | Generate getter and setter |
116+
| `const` qualifier on member | member type | Suppress setter (built-in, always applies) |
85117

86118
------
87119

@@ -265,8 +297,11 @@ __public const char *person_get_role(const struct person *p)
265297
266298
1. **Dynamic strings** (`char *`) — setters store a `strdup()` copy; passing `NULL` clears the field.
267299
2. **Fixed char arrays** (`char foo[N]`) — setters use `snprintf`, always NUL-terminated.
268-
3. **`const` members** — only a getter is generated, no setter.
300+
3. **`const` members** — only a getter is generated, no setter (applies regardless of any annotation).
269301
4. **`//!accessors:readonly`** — same effect as `const`: getter only.
270-
5. **`//!accessors:none`** — member is completely ignored by the generator.
271-
6. **`--prefix`** — prepended to every function name (e.g. `--prefix nvme_` turns `ctrl_set_name` into `nvme_ctrl_set_name`).
272-
7. **Line length** — generated code is automatically wrapped to stay within the 80-column limit required by `checkpatch.pl`.
302+
5. **`//!accessors:writeonly`** — setter only; getter is suppressed.
303+
6. **`//!accessors:readwrite`** — both getter and setter; overrides a restrictive struct-level default.
304+
7. **`//!accessors:none`** — member is completely ignored by the generator.
305+
8. **Struct-level mode** — the qualifier on `generate-accessors` sets the default for every member in the struct; per-member annotations override the struct default.
306+
9. **`--prefix`** — prepended to every function name (e.g. `--prefix nvme_` turns `ctrl_set_name` into `nvme_ctrl_set_name`).
307+
10. **Line length** — generated code is automatically wrapped to stay within the 80-column limit required by `checkpatch.pl`.

libnvme/tools/generator/generate-accessors.py

Lines changed: 111 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,16 @@
1616
- Does not support typedef struct.
1717
- Does not support struct within struct.
1818
19-
Struct inclusion — annotate the opening brace line of the struct:
20-
struct nvme_ctrl { /*!generate-accessors*/
21-
struct nvme_ctrl { //!generate-accessors
19+
Struct inclusion — annotate the opening brace line of the struct.
20+
The optional mode qualifier sets the default for all members of the struct:
21+
struct nvme_ctrl { /*!generate-accessors*/ — default: both getter and setter
22+
struct nvme_ctrl { //!generate-accessors — default: both getter and setter
23+
struct nvme_ctrl { /*!generate-accessors:none*/ — default: no accessors
24+
struct nvme_ctrl { //!generate-accessors:none — default: no accessors
25+
struct nvme_ctrl { /*!generate-accessors:readonly*/ — default: getter only
26+
struct nvme_ctrl { //!generate-accessors:readonly — default: getter only
27+
struct nvme_ctrl { /*!generate-accessors:writeonly*/ — default: setter only
28+
struct nvme_ctrl { //!generate-accessors:writeonly — default: setter only
2229
2330
Member exclusion — annotate the member declaration line:
2431
char *model; /*!accessors:none*/
@@ -30,6 +37,16 @@
3037
char *state; /*!accessors:readonly*/
3138
char *state; //!accessors:readonly
3239
40+
Write-only members (setter only, getter suppressed):
41+
- Annotate the member declaration line:
42+
char *state; /*!accessors:writeonly*/
43+
char *state; //!accessors:writeonly
44+
45+
Both getter and setter (override a restrictive struct-level default):
46+
- Annotate the member declaration line:
47+
char *state; /*!accessors:readwrite*/
48+
char *state; //!accessors:readwrite
49+
3350
Example usage:
3451
./generate-accessors.py private.h
3552
./generate-accessors.py --prefix nvme_ private.h
@@ -185,12 +202,15 @@ def fits_80_ntabs(n, s):
185202
class Member:
186203
"""Represents one member of a parsed C struct."""
187204

188-
__slots__ = ('name', 'type', 'is_const', 'is_char_array', 'array_size')
205+
__slots__ = ('name', 'type', 'gen_getter', 'gen_setter',
206+
'is_char_array', 'array_size')
189207

190-
def __init__(self, name, type_str, is_const, is_char_array, array_size):
208+
def __init__(self, name, type_str, gen_getter, gen_setter,
209+
is_char_array, array_size):
191210
self.name = name
192-
self.type = type_str # e.g. "const char *", "int", "__u32"
193-
self.is_const = is_const # True → getter only (no setter generated)
211+
self.type = type_str # e.g. "const char *", "int", "__u32"
212+
self.gen_getter = gen_getter # True → emit getter
213+
self.gen_setter = gen_setter # True → emit setter
194214
self.is_char_array = is_char_array
195215
self.array_size = array_size # only valid when is_char_array is True
196216

@@ -199,9 +219,16 @@ def __init__(self, name, type_str, is_const, is_char_array, array_size):
199219
# Parsing
200220
# ---------------------------------------------------------------------------
201221

202-
def parse_members(struct_name, raw_body, verbose):
222+
def parse_members(struct_name, raw_body, struct_mode, verbose):
203223
"""Parse *raw_body* and return a list of Member objects.
204224
225+
*struct_mode* is the default access mode for all members of this struct,
226+
derived from the generate-accessors annotation qualifier:
227+
'both' — generate getter and setter (default when no qualifier)
228+
'readonly' — generate getter only
229+
'writeonly' — generate setter only
230+
'none' — generate nothing unless a per-member annotation overrides
231+
205232
Annotations are detected on the **raw** (un-stripped) line so that
206233
comment masking cannot hide them. Comments are stripped only afterwards,
207234
for regex matching.
@@ -214,7 +241,18 @@ def parse_members(struct_name, raw_body, verbose):
214241
# ----------------------------------------------------------------
215242
if has_annotation(raw_line, 'accessors:none'):
216243
continue
217-
readonly = has_annotation(raw_line, 'accessors:readonly')
244+
245+
if has_annotation(raw_line, 'accessors:readwrite'):
246+
member_mode = 'both'
247+
elif has_annotation(raw_line, 'accessors:readonly'):
248+
member_mode = 'readonly'
249+
elif has_annotation(raw_line, 'accessors:writeonly'):
250+
member_mode = 'writeonly'
251+
else:
252+
member_mode = struct_mode
253+
254+
gen_getter = member_mode in ('both', 'readonly')
255+
gen_setter = member_mode in ('both', 'writeonly')
218256

219257
# ----------------------------------------------------------------
220258
# Strip comments for member-declaration parsing.
@@ -229,10 +267,12 @@ def parse_members(struct_name, raw_body, verbose):
229267
# --- char array: [const] char name[size]; -----------------------
230268
m = CHAR_ARRAY_RE.match(clean)
231269
if m:
270+
is_const_qual = bool(m.group(1))
232271
members.append(Member(
233272
name=m.group(2),
234273
type_str='const char *',
235-
is_const=readonly or bool(m.group(1)),
274+
gen_getter=gen_getter,
275+
gen_setter=gen_setter and not is_const_qual,
236276
is_char_array=True,
237277
array_size=m.group(3),
238278
))
@@ -257,14 +297,57 @@ def parse_members(struct_name, raw_body, verbose):
257297
members.append(Member(
258298
name=name,
259299
type_str=type_str,
260-
is_const=readonly or is_const_qual,
300+
gen_getter=gen_getter,
301+
gen_setter=gen_setter and not is_const_qual,
261302
is_char_array=False,
262303
array_size=None,
263304
))
264305

265306
return members
266307

267308

309+
_VALID_MODES = frozenset(('both', 'none', 'readonly', 'writeonly'))
310+
311+
312+
def parse_struct_annotation(raw_body):
313+
"""Return the default mode for a struct from its generate-accessors annotation.
314+
315+
Recognises both comment styles with an optional mode qualifier:
316+
/*!generate-accessors*/ → 'both'
317+
/*!generate-accessors:none*/ → 'none'
318+
/*!generate-accessors:readonly*/ → 'readonly'
319+
/*!generate-accessors:writeonly*/ → 'writeonly'
320+
//!generate-accessors → 'both'
321+
//!generate-accessors:none → 'none'
322+
//!generate-accessors:readonly → 'readonly'
323+
//!generate-accessors:writeonly → 'writeonly'
324+
325+
Returns None when the annotation is absent.
326+
Prints a warning and falls back to 'both' for unrecognised qualifiers.
327+
"""
328+
first_token = raw_body.lstrip()
329+
330+
for pattern in (
331+
r'/\*!generate-accessors(?::([a-z]+))?\*/',
332+
r'//!generate-accessors(?::([a-z]+))?',
333+
):
334+
m = re.match(pattern, first_token)
335+
if m:
336+
qualifier = m.group(1) or 'both'
337+
if qualifier not in _VALID_MODES:
338+
print(
339+
f"warning: unknown generate-accessors qualifier "
340+
f"'{qualifier}'; valid values are: "
341+
f"{', '.join(sorted(_VALID_MODES))}. "
342+
f"Defaulting to 'both'.",
343+
file=sys.stderr,
344+
)
345+
qualifier = 'both'
346+
return qualifier
347+
348+
return None
349+
350+
268351
def parse_file(text, verbose):
269352
"""Return list of (struct_name, [Member]) tuples found in *text*.
270353
@@ -278,16 +361,15 @@ def parse_file(text, verbose):
278361
struct_name = match.group(1)
279362
raw_body = match.group(2)
280363

281-
# The annotation must be the first token after the opening '{'.
282-
first_token = raw_body.lstrip()
283-
if not (first_token.startswith('/*!generate-accessors*/') or
284-
first_token.startswith('//!generate-accessors')):
364+
struct_mode = parse_struct_annotation(raw_body)
365+
if struct_mode is None:
285366
continue
286367

287-
members = parse_members(struct_name, raw_body, verbose)
368+
members = parse_members(struct_name, raw_body, struct_mode, verbose)
288369

289370
if verbose and members:
290-
print(f"Found struct: {struct_name} ({len(members)} members)")
371+
print(f"Found struct: {struct_name} ({len(members)} members)"
372+
f" [mode: {struct_mode}]")
291373

292374
if members:
293375
result.append((struct_name, members))
@@ -387,15 +469,16 @@ def generate_hdr(f, prefix, struct_name, members):
387469
for member in members:
388470
is_dyn_str = (not member.is_char_array and
389471
member.type == 'const char *')
390-
if not member.is_const:
472+
if member.gen_setter:
391473
if member.is_char_array or is_dyn_str:
392474
emit_hdr_setter_str(f, prefix, struct_name,
393475
member.name, is_dyn_str)
394476
else:
395477
emit_hdr_setter_val(f, prefix, struct_name,
396478
member.name, member.type)
397-
emit_hdr_getter(f, prefix, struct_name,
398-
member.name, member.type, is_dyn_str)
479+
if member.gen_getter:
480+
emit_hdr_getter(f, prefix, struct_name,
481+
member.name, member.type, is_dyn_str)
399482

400483

401484
# ---------------------------------------------------------------------------
@@ -489,9 +572,9 @@ def emit_src_getter(f, prefix, sname, mname, mtype):
489572
def generate_src(f, prefix, struct_name, members):
490573
"""Write source implementations for all members of one struct."""
491574
for member in members:
492-
if not member.is_const:
493-
is_dyn_str = (not member.is_char_array and
494-
member.type == 'const char *')
575+
is_dyn_str = (not member.is_char_array and
576+
member.type == 'const char *')
577+
if member.gen_setter:
495578
if is_dyn_str:
496579
emit_src_setter_dynstr(f, prefix, struct_name, member.name)
497580
elif member.is_char_array:
@@ -500,7 +583,8 @@ def generate_src(f, prefix, struct_name, members):
500583
else:
501584
emit_src_setter_val(f, prefix, struct_name,
502585
member.name, member.type)
503-
emit_src_getter(f, prefix, struct_name, member.name, member.type)
586+
if member.gen_getter:
587+
emit_src_getter(f, prefix, struct_name, member.name, member.type)
504588

505589

506590
# ---------------------------------------------------------------------------
@@ -510,8 +594,9 @@ def generate_src(f, prefix, struct_name, members):
510594
def generate_ld(f, prefix, struct_name, members):
511595
"""Write linker version-script entries for all members of one struct."""
512596
for member in members:
513-
f.write(f'\t\t{_get_name(prefix, struct_name, member.name)};\n')
514-
if not member.is_const:
597+
if member.gen_getter:
598+
f.write(f'\t\t{_get_name(prefix, struct_name, member.name)};\n')
599+
if member.gen_setter:
515600
f.write(f'\t\t{_set_name(prefix, struct_name, member.name)};\n')
516601

517602

0 commit comments

Comments
 (0)