xref: /linux/scripts/lib/kdoc/kdoc_output.py (revision 8d9d122915492ea6984f32e5df30cef5c582f062)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
4#
5# pylint: disable=C0301,R0902,R0911,R0912,R0913,R0914,R0915,R0917
6
7"""
8Implement output filters to print kernel-doc documentation.
9
10The implementation uses a virtual base class (OutputFormat) which
11contains a dispatches to virtual methods, and some code to filter
12out output messages.
13
14The actual implementation is done on one separate class per each type
15of output. Currently, there are output classes for ReST and man/troff.
16"""
17
18import os
19import re
20from datetime import datetime
21
22from kdoc_parser import KernelDoc, type_param
23from kdoc_re import KernRe
24
25
26function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False)
27
28# match expressions used to find embedded type information
29type_constant = KernRe(r"\b``([^\`]+)``\b", cache=False)
30type_constant2 = KernRe(r"\%([-_*\w]+)", cache=False)
31type_func = KernRe(r"(\w+)\(\)", cache=False)
32type_param_ref = KernRe(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False)
33
34# Special RST handling for func ptr params
35type_fp_param = KernRe(r"\@(\w+)\(\)", cache=False)
36
37# Special RST handling for structs with func ptr params
38type_fp_param2 = KernRe(r"\@(\w+->\S+)\(\)", cache=False)
39
40type_env = KernRe(r"(\$\w+)", cache=False)
41type_enum = KernRe(r"\&(enum\s*([_\w]+))", cache=False)
42type_struct = KernRe(r"\&(struct\s*([_\w]+))", cache=False)
43type_typedef = KernRe(r"\&(typedef\s*([_\w]+))", cache=False)
44type_union = KernRe(r"\&(union\s*([_\w]+))", cache=False)
45type_member = KernRe(r"\&([_\w]+)(\.|->)([_\w]+)", cache=False)
46type_fallback = KernRe(r"\&([_\w]+)", cache=False)
47type_member_func = type_member + KernRe(r"\(\)", cache=False)
48
49
50class OutputFormat:
51    """
52    Base class for OutputFormat. If used as-is, it means that only
53    warnings will be displayed.
54    """
55
56    # output mode.
57    OUTPUT_ALL          = 0 # output all symbols and doc sections
58    OUTPUT_INCLUDE      = 1 # output only specified symbols
59    OUTPUT_EXPORTED     = 2 # output exported symbols
60    OUTPUT_INTERNAL     = 3 # output non-exported symbols
61
62    # Virtual member to be overriden at the  inherited classes
63    highlights = []
64
65    def __init__(self):
66        """Declare internal vars and set mode to OUTPUT_ALL"""
67
68        self.out_mode = self.OUTPUT_ALL
69        self.enable_lineno = None
70        self.nosymbol = {}
71        self.symbol = None
72        self.function_table = None
73        self.config = None
74        self.no_doc_sections = False
75
76        self.data = ""
77
78    def set_config(self, config):
79        """
80        Setup global config variables used by both parser and output.
81        """
82
83        self.config = config
84
85    def set_filter(self, export, internal, symbol, nosymbol, function_table,
86                   enable_lineno, no_doc_sections):
87        """
88        Initialize filter variables according with the requested mode.
89
90        Only one choice is valid between export, internal and symbol.
91
92        The nosymbol filter can be used on all modes.
93        """
94
95        self.enable_lineno = enable_lineno
96        self.no_doc_sections = no_doc_sections
97        self.function_table = function_table
98
99        if symbol:
100            self.out_mode = self.OUTPUT_INCLUDE
101        elif export:
102            self.out_mode = self.OUTPUT_EXPORTED
103        elif internal:
104            self.out_mode = self.OUTPUT_INTERNAL
105        else:
106            self.out_mode = self.OUTPUT_ALL
107
108        if nosymbol:
109            self.nosymbol = set(nosymbol)
110
111
112    def highlight_block(self, block):
113        """
114        Apply the RST highlights to a sub-block of text.
115        """
116
117        for r, sub in self.highlights:
118            block = r.sub(sub, block)
119
120        return block
121
122    def out_warnings(self, args):
123        """
124        Output warnings for identifiers that will be displayed.
125        """
126
127        warnings = args.get('warnings', [])
128
129        for log_msg in warnings:
130            self.config.warning(log_msg)
131
132    def check_doc(self, name, args):
133        """Check if DOC should be output"""
134
135        if self.no_doc_sections:
136            return False
137
138        if name in self.nosymbol:
139            return False
140
141        if self.out_mode == self.OUTPUT_ALL:
142            self.out_warnings(args)
143            return True
144
145        if self.out_mode == self.OUTPUT_INCLUDE:
146            if name in self.function_table:
147                self.out_warnings(args)
148                return True
149
150        return False
151
152    def check_declaration(self, dtype, name, args):
153        """
154        Checks if a declaration should be output or not based on the
155        filtering criteria.
156        """
157
158        if name in self.nosymbol:
159            return False
160
161        if self.out_mode == self.OUTPUT_ALL:
162            self.out_warnings(args)
163            return True
164
165        if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]:
166            if name in self.function_table:
167                return True
168
169        if self.out_mode == self.OUTPUT_INTERNAL:
170            if dtype != "function":
171                self.out_warnings(args)
172                return True
173
174            if name not in self.function_table:
175                self.out_warnings(args)
176                return True
177
178        return False
179
180    def msg(self, fname, name, args):
181        """
182        Handles a single entry from kernel-doc parser
183        """
184
185        self.data = ""
186
187        dtype = args.get('type', "")
188
189        if dtype == "doc":
190            self.out_doc(fname, name, args)
191            return self.data
192
193        if not self.check_declaration(dtype, name, args):
194            return self.data
195
196        if dtype == "function":
197            self.out_function(fname, name, args)
198            return self.data
199
200        if dtype == "enum":
201            self.out_enum(fname, name, args)
202            return self.data
203
204        if dtype == "typedef":
205            self.out_typedef(fname, name, args)
206            return self.data
207
208        if dtype in ["struct", "union"]:
209            self.out_struct(fname, name, args)
210            return self.data
211
212        # Warn if some type requires an output logic
213        self.config.log.warning("doesn't now how to output '%s' block",
214                                dtype)
215
216        return None
217
218    # Virtual methods to be overridden by inherited classes
219    # At the base class, those do nothing.
220    def out_doc(self, fname, name, args):
221        """Outputs a DOC block"""
222
223    def out_function(self, fname, name, args):
224        """Outputs a function"""
225
226    def out_enum(self, fname, name, args):
227        """Outputs an enum"""
228
229    def out_typedef(self, fname, name, args):
230        """Outputs a typedef"""
231
232    def out_struct(self, fname, name, args):
233        """Outputs a struct"""
234
235
236class RestFormat(OutputFormat):
237    """Consts and functions used by ReST output"""
238
239    highlights = [
240        (type_constant, r"``\1``"),
241        (type_constant2, r"``\1``"),
242
243        # Note: need to escape () to avoid func matching later
244        (type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"),
245        (type_member, r":c:type:`\1\2\3 <\1>`"),
246        (type_fp_param, r"**\1\\(\\)**"),
247        (type_fp_param2, r"**\1\\(\\)**"),
248        (type_func, r"\1()"),
249        (type_enum, r":c:type:`\1 <\2>`"),
250        (type_struct, r":c:type:`\1 <\2>`"),
251        (type_typedef, r":c:type:`\1 <\2>`"),
252        (type_union, r":c:type:`\1 <\2>`"),
253
254        # in rst this can refer to any type
255        (type_fallback, r":c:type:`\1`"),
256        (type_param_ref, r"**\1\2**")
257    ]
258    blankline = "\n"
259
260    sphinx_literal = KernRe(r'^[^.].*::$', cache=False)
261    sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False)
262
263    def __init__(self):
264        """
265        Creates class variables.
266
267        Not really mandatory, but it is a good coding style and makes
268        pylint happy.
269        """
270
271        super().__init__()
272        self.lineprefix = ""
273
274    def print_lineno(self, ln):
275        """Outputs a line number"""
276
277        if self.enable_lineno and ln is not None:
278            ln += 1
279            self.data += f".. LINENO {ln}\n"
280
281    def output_highlight(self, args):
282        """
283        Outputs a C symbol that may require being converted to ReST using
284        the self.highlights variable
285        """
286
287        input_text = args
288        output = ""
289        in_literal = False
290        litprefix = ""
291        block = ""
292
293        for line in input_text.strip("\n").split("\n"):
294
295            # If we're in a literal block, see if we should drop out of it.
296            # Otherwise, pass the line straight through unmunged.
297            if in_literal:
298                if line.strip():  # If the line is not blank
299                    # If this is the first non-blank line in a literal block,
300                    # figure out the proper indent.
301                    if not litprefix:
302                        r = KernRe(r'^(\s*)')
303                        if r.match(line):
304                            litprefix = '^' + r.group(1)
305                        else:
306                            litprefix = ""
307
308                        output += line + "\n"
309                    elif not KernRe(litprefix).match(line):
310                        in_literal = False
311                    else:
312                        output += line + "\n"
313                else:
314                    output += line + "\n"
315
316            # Not in a literal block (or just dropped out)
317            if not in_literal:
318                block += line + "\n"
319                if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line):
320                    in_literal = True
321                    litprefix = ""
322                    output += self.highlight_block(block)
323                    block = ""
324
325        # Handle any remaining block
326        if block:
327            output += self.highlight_block(block)
328
329        # Print the output with the line prefix
330        for line in output.strip("\n").split("\n"):
331            self.data += self.lineprefix + line + "\n"
332
333    def out_section(self, args, out_docblock=False):
334        """
335        Outputs a block section.
336
337        This could use some work; it's used to output the DOC: sections, and
338        starts by putting out the name of the doc section itself, but that
339        tends to duplicate a header already in the template file.
340        """
341
342        sections = args.get('sections', {})
343        section_start_lines = args.get('section_start_lines', {})
344
345        for section in sections:
346            # Skip sections that are in the nosymbol_table
347            if section in self.nosymbol:
348                continue
349
350            if out_docblock:
351                if not self.out_mode == self.OUTPUT_INCLUDE:
352                    self.data += f".. _{section}:\n\n"
353                    self.data += f'{self.lineprefix}**{section}**\n\n'
354            else:
355                self.data += f'{self.lineprefix}**{section}**\n\n'
356
357            self.print_lineno(section_start_lines.get(section, 0))
358            self.output_highlight(sections[section])
359            self.data += "\n"
360        self.data += "\n"
361
362    def out_doc(self, fname, name, args):
363        if not self.check_doc(name, args):
364            return
365        self.out_section(args, out_docblock=True)
366
367    def out_function(self, fname, name, args):
368
369        oldprefix = self.lineprefix
370        signature = ""
371
372        func_macro = args.get('func_macro', False)
373        if func_macro:
374            signature = args['function']
375        else:
376            if args.get('functiontype'):
377                signature = args['functiontype'] + " "
378            signature += args['function'] + " ("
379
380        parameterlist = args.get('parameterlist', [])
381        parameterdescs = args.get('parameterdescs', {})
382        parameterdesc_start_lines = args.get('parameterdesc_start_lines', {})
383
384        ln = args.get('declaration_start_line', 0)
385
386        count = 0
387        for parameter in parameterlist:
388            if count != 0:
389                signature += ", "
390            count += 1
391            dtype = args['parametertypes'].get(parameter, "")
392
393            if function_pointer.search(dtype):
394                signature += function_pointer.group(1) + parameter + function_pointer.group(3)
395            else:
396                signature += dtype
397
398        if not func_macro:
399            signature += ")"
400
401        self.print_lineno(ln)
402        if args.get('typedef') or not args.get('functiontype'):
403            self.data += f".. c:macro:: {args['function']}\n\n"
404
405            if args.get('typedef'):
406                self.data += "   **Typedef**: "
407                self.lineprefix = ""
408                self.output_highlight(args.get('purpose', ""))
409                self.data += "\n\n**Syntax**\n\n"
410                self.data += f"  ``{signature}``\n\n"
411            else:
412                self.data += f"``{signature}``\n\n"
413        else:
414            self.data += f".. c:function:: {signature}\n\n"
415
416        if not args.get('typedef'):
417            self.print_lineno(ln)
418            self.lineprefix = "   "
419            self.output_highlight(args.get('purpose', ""))
420            self.data += "\n"
421
422        # Put descriptive text into a container (HTML <div>) to help set
423        # function prototypes apart
424        self.lineprefix = "  "
425
426        if parameterlist:
427            self.data += ".. container:: kernelindent\n\n"
428            self.data += f"{self.lineprefix}**Parameters**\n\n"
429
430        for parameter in parameterlist:
431            parameter_name = KernRe(r'\[.*').sub('', parameter)
432            dtype = args['parametertypes'].get(parameter, "")
433
434            if dtype:
435                self.data += f"{self.lineprefix}``{dtype}``\n"
436            else:
437                self.data += f"{self.lineprefix}``{parameter}``\n"
438
439            self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0))
440
441            self.lineprefix = "    "
442            if parameter_name in parameterdescs and \
443               parameterdescs[parameter_name] != KernelDoc.undescribed:
444
445                self.output_highlight(parameterdescs[parameter_name])
446                self.data += "\n"
447            else:
448                self.data += f"{self.lineprefix}*undescribed*\n\n"
449            self.lineprefix = "  "
450
451        self.out_section(args)
452        self.lineprefix = oldprefix
453
454    def out_enum(self, fname, name, args):
455
456        oldprefix = self.lineprefix
457        name = args.get('enum', '')
458        parameterlist = args.get('parameterlist', [])
459        parameterdescs = args.get('parameterdescs', {})
460        ln = args.get('declaration_start_line', 0)
461
462        self.data += f"\n\n.. c:enum:: {name}\n\n"
463
464        self.print_lineno(ln)
465        self.lineprefix = "  "
466        self.output_highlight(args.get('purpose', ''))
467        self.data += "\n"
468
469        self.data += ".. container:: kernelindent\n\n"
470        outer = self.lineprefix + "  "
471        self.lineprefix = outer + "  "
472        self.data += f"{outer}**Constants**\n\n"
473
474        for parameter in parameterlist:
475            self.data += f"{outer}``{parameter}``\n"
476
477            if parameterdescs.get(parameter, '') != KernelDoc.undescribed:
478                self.output_highlight(parameterdescs[parameter])
479            else:
480                self.data += f"{self.lineprefix}*undescribed*\n\n"
481            self.data += "\n"
482
483        self.lineprefix = oldprefix
484        self.out_section(args)
485
486    def out_typedef(self, fname, name, args):
487
488        oldprefix = self.lineprefix
489        name = args.get('typedef', '')
490        ln = args.get('declaration_start_line', 0)
491
492        self.data += f"\n\n.. c:type:: {name}\n\n"
493
494        self.print_lineno(ln)
495        self.lineprefix = "   "
496
497        self.output_highlight(args.get('purpose', ''))
498
499        self.data += "\n"
500
501        self.lineprefix = oldprefix
502        self.out_section(args)
503
504    def out_struct(self, fname, name, args):
505
506        name = args.get('struct', "")
507        purpose = args.get('purpose', "")
508        declaration = args.get('definition', "")
509        dtype = args.get('type', "struct")
510        ln = args.get('declaration_start_line', 0)
511
512        parameterlist = args.get('parameterlist', [])
513        parameterdescs = args.get('parameterdescs', {})
514        parameterdesc_start_lines = args.get('parameterdesc_start_lines', {})
515
516        self.data += f"\n\n.. c:{dtype}:: {name}\n\n"
517
518        self.print_lineno(ln)
519
520        oldprefix = self.lineprefix
521        self.lineprefix += "  "
522
523        self.output_highlight(purpose)
524        self.data += "\n"
525
526        self.data += ".. container:: kernelindent\n\n"
527        self.data += f"{self.lineprefix}**Definition**::\n\n"
528
529        self.lineprefix = self.lineprefix + "  "
530
531        declaration = declaration.replace("\t", self.lineprefix)
532
533        self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n"
534        self.data += f"{declaration}{self.lineprefix}" + "};\n\n"
535
536        self.lineprefix = "  "
537        self.data += f"{self.lineprefix}**Members**\n\n"
538        for parameter in parameterlist:
539            if not parameter or parameter.startswith("#"):
540                continue
541
542            parameter_name = parameter.split("[", maxsplit=1)[0]
543
544            if parameterdescs.get(parameter_name) == KernelDoc.undescribed:
545                continue
546
547            self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0))
548
549            self.data += f"{self.lineprefix}``{parameter}``\n"
550
551            self.lineprefix = "    "
552            self.output_highlight(parameterdescs[parameter_name])
553            self.lineprefix = "  "
554
555            self.data += "\n"
556
557        self.data += "\n"
558
559        self.lineprefix = oldprefix
560        self.out_section(args)
561
562
563class ManFormat(OutputFormat):
564    """Consts and functions used by man pages output"""
565
566    highlights = (
567        (type_constant, r"\1"),
568        (type_constant2, r"\1"),
569        (type_func, r"\\fB\1\\fP"),
570        (type_enum, r"\\fI\1\\fP"),
571        (type_struct, r"\\fI\1\\fP"),
572        (type_typedef, r"\\fI\1\\fP"),
573        (type_union, r"\\fI\1\\fP"),
574        (type_param, r"\\fI\1\\fP"),
575        (type_param_ref, r"\\fI\1\2\\fP"),
576        (type_member, r"\\fI\1\2\3\\fP"),
577        (type_fallback, r"\\fI\1\\fP")
578    )
579    blankline = ""
580
581    date_formats = [
582        "%a %b %d %H:%M:%S %Z %Y",
583        "%a %b %d %H:%M:%S %Y",
584        "%Y-%m-%d",
585        "%b %d %Y",
586        "%B %d %Y",
587        "%m %d %Y",
588    ]
589
590    def __init__(self, modulename):
591        """
592        Creates class variables.
593
594        Not really mandatory, but it is a good coding style and makes
595        pylint happy.
596        """
597
598        super().__init__()
599        self.modulename = modulename
600
601        dt = None
602        tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP")
603        if tstamp:
604            for fmt in self.date_formats:
605                try:
606                    dt = datetime.strptime(tstamp, fmt)
607                    break
608                except ValueError:
609                    pass
610
611        if not dt:
612            dt = datetime.now()
613
614        self.man_date = dt.strftime("%B %Y")
615
616    def output_highlight(self, block):
617        """
618        Outputs a C symbol that may require being highlighted with
619        self.highlights variable using troff syntax
620        """
621
622        contents = self.highlight_block(block)
623
624        if isinstance(contents, list):
625            contents = "\n".join(contents)
626
627        for line in contents.strip("\n").split("\n"):
628            line = KernRe(r"^\s*").sub("", line)
629            if not line:
630                continue
631
632            if line[0] == ".":
633                self.data += "\\&" + line + "\n"
634            else:
635                self.data += line + "\n"
636
637    def out_doc(self, fname, name, args):
638        sections = args.get('sections', {})
639
640        if not self.check_doc(name, args):
641            return
642
643        self.data += f'.TH "{self.modulename}" 9 "{self.modulename}" "{self.man_date}" "API Manual" LINUX' + "\n"
644
645        for section in sections:
646            self.data += f'.SH "{section}"' + "\n"
647            self.output_highlight(sections.get(section))
648
649    def out_function(self, fname, name, args):
650        """output function in man"""
651
652        parameterlist = args.get('parameterlist', [])
653        parameterdescs = args.get('parameterdescs', {})
654        sections = args.get('sections', {})
655
656        self.data += f'.TH "{args["function"]}" 9 "{args["function"]}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n"
657
658        self.data += ".SH NAME\n"
659        self.data += f"{args['function']} \\- {args['purpose']}\n"
660
661        self.data += ".SH SYNOPSIS\n"
662        if args.get('functiontype', ''):
663            self.data += f'.B "{args["functiontype"]}" {args["function"]}' + "\n"
664        else:
665            self.data += f'.B "{args["function"]}' + "\n"
666
667        count = 0
668        parenth = "("
669        post = ","
670
671        for parameter in parameterlist:
672            if count == len(parameterlist) - 1:
673                post = ");"
674
675            dtype = args['parametertypes'].get(parameter, "")
676            if function_pointer.match(dtype):
677                # Pointer-to-function
678                self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n"
679            else:
680                dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype)
681
682                self.data += f'.BI "{parenth}{dtype}"  "{post}"' + "\n"
683            count += 1
684            parenth = ""
685
686        if parameterlist:
687            self.data += ".SH ARGUMENTS\n"
688
689        for parameter in parameterlist:
690            parameter_name = re.sub(r'\[.*', '', parameter)
691
692            self.data += f'.IP "{parameter}" 12' + "\n"
693            self.output_highlight(parameterdescs.get(parameter_name, ""))
694
695        for section in sections:
696            self.data += f'.SH "{section.upper()}"' + "\n"
697            self.output_highlight(sections[section])
698
699    def out_enum(self, fname, name, args):
700
701        name = args.get('enum', '')
702        parameterlist = args.get('parameterlist', [])
703        sections = args.get('sections', {})
704
705        self.data += f'.TH "{self.modulename}" 9 "enum {args["enum"]}" "{self.man_date}" "API Manual" LINUX' + "\n"
706
707        self.data += ".SH NAME\n"
708        self.data += f"enum {args['enum']} \\- {args['purpose']}\n"
709
710        self.data += ".SH SYNOPSIS\n"
711        self.data += f"enum {args['enum']}" + " {\n"
712
713        count = 0
714        for parameter in parameterlist:
715            self.data += f'.br\n.BI "    {parameter}"' + "\n"
716            if count == len(parameterlist) - 1:
717                self.data += "\n};\n"
718            else:
719                self.data += ", \n.br\n"
720
721            count += 1
722
723        self.data += ".SH Constants\n"
724
725        for parameter in parameterlist:
726            parameter_name = KernRe(r'\[.*').sub('', parameter)
727            self.data += f'.IP "{parameter}" 12' + "\n"
728            self.output_highlight(args['parameterdescs'].get(parameter_name, ""))
729
730        for section in sections:
731            self.data += f'.SH "{section}"' + "\n"
732            self.output_highlight(sections[section])
733
734    def out_typedef(self, fname, name, args):
735        module = self.modulename
736        typedef = args.get('typedef')
737        purpose = args.get('purpose')
738        sections = args.get('sections', {})
739
740        self.data += f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual" LINUX' + "\n"
741
742        self.data += ".SH NAME\n"
743        self.data += f"typedef {typedef} \\- {purpose}\n"
744
745        for section in sections:
746            self.data += f'.SH "{section}"' + "\n"
747            self.output_highlight(sections.get(section))
748
749    def out_struct(self, fname, name, args):
750        module = self.modulename
751        struct_type = args.get('type')
752        struct_name = args.get('struct')
753        purpose = args.get('purpose')
754        definition = args.get('definition')
755        parameterlist = args.get('parameterlist', [])
756        sections = args.get('sections', {})
757        parameterdescs = args.get('parameterdescs', {})
758
759        self.data += f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_date}" "API Manual" LINUX' + "\n"
760
761        self.data += ".SH NAME\n"
762        self.data += f"{struct_type} {struct_name} \\- {purpose}\n"
763
764        # Replace tabs with two spaces and handle newlines
765        declaration = definition.replace("\t", "  ")
766        declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration)
767
768        self.data += ".SH SYNOPSIS\n"
769        self.data += f"{struct_type} {struct_name} " + "{" + "\n.br\n"
770        self.data += f'.BI "{declaration}\n' + "};\n.br\n\n"
771
772        self.data += ".SH Members\n"
773        for parameter in parameterlist:
774            if parameter.startswith("#"):
775                continue
776
777            parameter_name = re.sub(r"\[.*", "", parameter)
778
779            if parameterdescs.get(parameter_name) == KernelDoc.undescribed:
780                continue
781
782            self.data += f'.IP "{parameter}" 12' + "\n"
783            self.output_highlight(parameterdescs.get(parameter_name))
784
785        for section in sections:
786            self.data += f'.SH "{section}"' + "\n"
787            self.output_highlight(sections.get(section))
788