xref: /linux/scripts/lib/kdoc/kdoc_output.py (revision 3e443d167327b10966166c1953631936547b03d0)
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        sectionlist = args.get('sectionlist', [])
343        sections = args.get('sections', {})
344        section_start_lines = args.get('section_start_lines', {})
345
346        for section in sectionlist:
347            # Skip sections that are in the nosymbol_table
348            if section in self.nosymbol:
349                continue
350
351            if out_docblock:
352                if not self.out_mode == self.OUTPUT_INCLUDE:
353                    self.data += f".. _{section}:\n\n"
354                    self.data += f'{self.lineprefix}**{section}**\n\n'
355            else:
356                self.data += f'{self.lineprefix}**{section}**\n\n'
357
358            self.print_lineno(section_start_lines.get(section, 0))
359            self.output_highlight(sections[section])
360            self.data += "\n"
361        self.data += "\n"
362
363    def out_doc(self, fname, name, args):
364        if not self.check_doc(name, args):
365            return
366        self.out_section(args, out_docblock=True)
367
368    def out_function(self, fname, name, args):
369
370        oldprefix = self.lineprefix
371        signature = ""
372
373        func_macro = args.get('func_macro', False)
374        if func_macro:
375            signature = args['function']
376        else:
377            if args.get('functiontype'):
378                signature = args['functiontype'] + " "
379            signature += args['function'] + " ("
380
381        parameterlist = args.get('parameterlist', [])
382        parameterdescs = args.get('parameterdescs', {})
383        parameterdesc_start_lines = args.get('parameterdesc_start_lines', {})
384
385        ln = args.get('declaration_start_line', 0)
386
387        count = 0
388        for parameter in parameterlist:
389            if count != 0:
390                signature += ", "
391            count += 1
392            dtype = args['parametertypes'].get(parameter, "")
393
394            if function_pointer.search(dtype):
395                signature += function_pointer.group(1) + parameter + function_pointer.group(3)
396            else:
397                signature += dtype
398
399        if not func_macro:
400            signature += ")"
401
402        self.print_lineno(ln)
403        if args.get('typedef') or not args.get('functiontype'):
404            self.data += f".. c:macro:: {args['function']}\n\n"
405
406            if args.get('typedef'):
407                self.data += "   **Typedef**: "
408                self.lineprefix = ""
409                self.output_highlight(args.get('purpose', ""))
410                self.data += "\n\n**Syntax**\n\n"
411                self.data += f"  ``{signature}``\n\n"
412            else:
413                self.data += f"``{signature}``\n\n"
414        else:
415            self.data += f".. c:function:: {signature}\n\n"
416
417        if not args.get('typedef'):
418            self.print_lineno(ln)
419            self.lineprefix = "   "
420            self.output_highlight(args.get('purpose', ""))
421            self.data += "\n"
422
423        # Put descriptive text into a container (HTML <div>) to help set
424        # function prototypes apart
425        self.lineprefix = "  "
426
427        if parameterlist:
428            self.data += ".. container:: kernelindent\n\n"
429            self.data += f"{self.lineprefix}**Parameters**\n\n"
430
431        for parameter in parameterlist:
432            parameter_name = KernRe(r'\[.*').sub('', parameter)
433            dtype = args['parametertypes'].get(parameter, "")
434
435            if dtype:
436                self.data += f"{self.lineprefix}``{dtype}``\n"
437            else:
438                self.data += f"{self.lineprefix}``{parameter}``\n"
439
440            self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0))
441
442            self.lineprefix = "    "
443            if parameter_name in parameterdescs and \
444               parameterdescs[parameter_name] != KernelDoc.undescribed:
445
446                self.output_highlight(parameterdescs[parameter_name])
447                self.data += "\n"
448            else:
449                self.data += f"{self.lineprefix}*undescribed*\n\n"
450            self.lineprefix = "  "
451
452        self.out_section(args)
453        self.lineprefix = oldprefix
454
455    def out_enum(self, fname, name, args):
456
457        oldprefix = self.lineprefix
458        name = args.get('enum', '')
459        parameterlist = args.get('parameterlist', [])
460        parameterdescs = args.get('parameterdescs', {})
461        ln = args.get('declaration_start_line', 0)
462
463        self.data += f"\n\n.. c:enum:: {name}\n\n"
464
465        self.print_lineno(ln)
466        self.lineprefix = "  "
467        self.output_highlight(args.get('purpose', ''))
468        self.data += "\n"
469
470        self.data += ".. container:: kernelindent\n\n"
471        outer = self.lineprefix + "  "
472        self.lineprefix = outer + "  "
473        self.data += f"{outer}**Constants**\n\n"
474
475        for parameter in parameterlist:
476            self.data += f"{outer}``{parameter}``\n"
477
478            if parameterdescs.get(parameter, '') != KernelDoc.undescribed:
479                self.output_highlight(parameterdescs[parameter])
480            else:
481                self.data += f"{self.lineprefix}*undescribed*\n\n"
482            self.data += "\n"
483
484        self.lineprefix = oldprefix
485        self.out_section(args)
486
487    def out_typedef(self, fname, name, args):
488
489        oldprefix = self.lineprefix
490        name = args.get('typedef', '')
491        ln = args.get('declaration_start_line', 0)
492
493        self.data += f"\n\n.. c:type:: {name}\n\n"
494
495        self.print_lineno(ln)
496        self.lineprefix = "   "
497
498        self.output_highlight(args.get('purpose', ''))
499
500        self.data += "\n"
501
502        self.lineprefix = oldprefix
503        self.out_section(args)
504
505    def out_struct(self, fname, name, args):
506
507        name = args.get('struct', "")
508        purpose = args.get('purpose', "")
509        declaration = args.get('definition', "")
510        dtype = args.get('type', "struct")
511        ln = args.get('declaration_start_line', 0)
512
513        parameterlist = args.get('parameterlist', [])
514        parameterdescs = args.get('parameterdescs', {})
515        parameterdesc_start_lines = args.get('parameterdesc_start_lines', {})
516
517        self.data += f"\n\n.. c:{dtype}:: {name}\n\n"
518
519        self.print_lineno(ln)
520
521        oldprefix = self.lineprefix
522        self.lineprefix += "  "
523
524        self.output_highlight(purpose)
525        self.data += "\n"
526
527        self.data += ".. container:: kernelindent\n\n"
528        self.data += f"{self.lineprefix}**Definition**::\n\n"
529
530        self.lineprefix = self.lineprefix + "  "
531
532        declaration = declaration.replace("\t", self.lineprefix)
533
534        self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n"
535        self.data += f"{declaration}{self.lineprefix}" + "};\n\n"
536
537        self.lineprefix = "  "
538        self.data += f"{self.lineprefix}**Members**\n\n"
539        for parameter in parameterlist:
540            if not parameter or parameter.startswith("#"):
541                continue
542
543            parameter_name = parameter.split("[", maxsplit=1)[0]
544
545            if parameterdescs.get(parameter_name) == KernelDoc.undescribed:
546                continue
547
548            self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0))
549
550            self.data += f"{self.lineprefix}``{parameter}``\n"
551
552            self.lineprefix = "    "
553            self.output_highlight(parameterdescs[parameter_name])
554            self.lineprefix = "  "
555
556            self.data += "\n"
557
558        self.data += "\n"
559
560        self.lineprefix = oldprefix
561        self.out_section(args)
562
563
564class ManFormat(OutputFormat):
565    """Consts and functions used by man pages output"""
566
567    highlights = (
568        (type_constant, r"\1"),
569        (type_constant2, r"\1"),
570        (type_func, r"\\fB\1\\fP"),
571        (type_enum, r"\\fI\1\\fP"),
572        (type_struct, r"\\fI\1\\fP"),
573        (type_typedef, r"\\fI\1\\fP"),
574        (type_union, r"\\fI\1\\fP"),
575        (type_param, r"\\fI\1\\fP"),
576        (type_param_ref, r"\\fI\1\2\\fP"),
577        (type_member, r"\\fI\1\2\3\\fP"),
578        (type_fallback, r"\\fI\1\\fP")
579    )
580    blankline = ""
581
582    date_formats = [
583        "%a %b %d %H:%M:%S %Z %Y",
584        "%a %b %d %H:%M:%S %Y",
585        "%Y-%m-%d",
586        "%b %d %Y",
587        "%B %d %Y",
588        "%m %d %Y",
589    ]
590
591    def __init__(self, modulename):
592        """
593        Creates class variables.
594
595        Not really mandatory, but it is a good coding style and makes
596        pylint happy.
597        """
598
599        super().__init__()
600        self.modulename = modulename
601
602        dt = None
603        tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP")
604        if tstamp:
605            for fmt in self.date_formats:
606                try:
607                    dt = datetime.strptime(tstamp, fmt)
608                    break
609                except ValueError:
610                    pass
611
612        if not dt:
613            dt = datetime.now()
614
615        self.man_date = dt.strftime("%B %Y")
616
617    def output_highlight(self, block):
618        """
619        Outputs a C symbol that may require being highlighted with
620        self.highlights variable using troff syntax
621        """
622
623        contents = self.highlight_block(block)
624
625        if isinstance(contents, list):
626            contents = "\n".join(contents)
627
628        for line in contents.strip("\n").split("\n"):
629            line = KernRe(r"^\s*").sub("", line)
630            if not line:
631                continue
632
633            if line[0] == ".":
634                self.data += "\\&" + line + "\n"
635            else:
636                self.data += line + "\n"
637
638    def out_doc(self, fname, name, args):
639        sectionlist = args.get('sectionlist', [])
640        sections = args.get('sections', {})
641
642        if not self.check_doc(name, args):
643            return
644
645        self.data += f'.TH "{self.modulename}" 9 "{self.modulename}" "{self.man_date}" "API Manual" LINUX' + "\n"
646
647        for section in sectionlist:
648            self.data += f'.SH "{section}"' + "\n"
649            self.output_highlight(sections.get(section))
650
651    def out_function(self, fname, name, args):
652        """output function in man"""
653
654        parameterlist = args.get('parameterlist', [])
655        parameterdescs = args.get('parameterdescs', {})
656        sectionlist = args.get('sectionlist', [])
657        sections = args.get('sections', {})
658
659        self.data += f'.TH "{args["function"]}" 9 "{args["function"]}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n"
660
661        self.data += ".SH NAME\n"
662        self.data += f"{args['function']} \\- {args['purpose']}\n"
663
664        self.data += ".SH SYNOPSIS\n"
665        if args.get('functiontype', ''):
666            self.data += f'.B "{args["functiontype"]}" {args["function"]}' + "\n"
667        else:
668            self.data += f'.B "{args["function"]}' + "\n"
669
670        count = 0
671        parenth = "("
672        post = ","
673
674        for parameter in parameterlist:
675            if count == len(parameterlist) - 1:
676                post = ");"
677
678            dtype = args['parametertypes'].get(parameter, "")
679            if function_pointer.match(dtype):
680                # Pointer-to-function
681                self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n"
682            else:
683                dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype)
684
685                self.data += f'.BI "{parenth}{dtype}"  "{post}"' + "\n"
686            count += 1
687            parenth = ""
688
689        if parameterlist:
690            self.data += ".SH ARGUMENTS\n"
691
692        for parameter in parameterlist:
693            parameter_name = re.sub(r'\[.*', '', parameter)
694
695            self.data += f'.IP "{parameter}" 12' + "\n"
696            self.output_highlight(parameterdescs.get(parameter_name, ""))
697
698        for section in sectionlist:
699            self.data += f'.SH "{section.upper()}"' + "\n"
700            self.output_highlight(sections[section])
701
702    def out_enum(self, fname, name, args):
703
704        name = args.get('enum', '')
705        parameterlist = args.get('parameterlist', [])
706        sectionlist = args.get('sectionlist', [])
707        sections = args.get('sections', {})
708
709        self.data += f'.TH "{self.modulename}" 9 "enum {args["enum"]}" "{self.man_date}" "API Manual" LINUX' + "\n"
710
711        self.data += ".SH NAME\n"
712        self.data += f"enum {args['enum']} \\- {args['purpose']}\n"
713
714        self.data += ".SH SYNOPSIS\n"
715        self.data += f"enum {args['enum']}" + " {\n"
716
717        count = 0
718        for parameter in parameterlist:
719            self.data += f'.br\n.BI "    {parameter}"' + "\n"
720            if count == len(parameterlist) - 1:
721                self.data += "\n};\n"
722            else:
723                self.data += ", \n.br\n"
724
725            count += 1
726
727        self.data += ".SH Constants\n"
728
729        for parameter in parameterlist:
730            parameter_name = KernRe(r'\[.*').sub('', parameter)
731            self.data += f'.IP "{parameter}" 12' + "\n"
732            self.output_highlight(args['parameterdescs'].get(parameter_name, ""))
733
734        for section in sectionlist:
735            self.data += f'.SH "{section}"' + "\n"
736            self.output_highlight(sections[section])
737
738    def out_typedef(self, fname, name, args):
739        module = self.modulename
740        typedef = args.get('typedef')
741        purpose = args.get('purpose')
742        sectionlist = args.get('sectionlist', [])
743        sections = args.get('sections', {})
744
745        self.data += f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual" LINUX' + "\n"
746
747        self.data += ".SH NAME\n"
748        self.data += f"typedef {typedef} \\- {purpose}\n"
749
750        for section in sectionlist:
751            self.data += f'.SH "{section}"' + "\n"
752            self.output_highlight(sections.get(section))
753
754    def out_struct(self, fname, name, args):
755        module = self.modulename
756        struct_type = args.get('type')
757        struct_name = args.get('struct')
758        purpose = args.get('purpose')
759        definition = args.get('definition')
760        sectionlist = args.get('sectionlist', [])
761        parameterlist = args.get('parameterlist', [])
762        sections = args.get('sections', {})
763        parameterdescs = args.get('parameterdescs', {})
764
765        self.data += f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_date}" "API Manual" LINUX' + "\n"
766
767        self.data += ".SH NAME\n"
768        self.data += f"{struct_type} {struct_name} \\- {purpose}\n"
769
770        # Replace tabs with two spaces and handle newlines
771        declaration = definition.replace("\t", "  ")
772        declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration)
773
774        self.data += ".SH SYNOPSIS\n"
775        self.data += f"{struct_type} {struct_name} " + "{" + "\n.br\n"
776        self.data += f'.BI "{declaration}\n' + "};\n.br\n\n"
777
778        self.data += ".SH Members\n"
779        for parameter in parameterlist:
780            if parameter.startswith("#"):
781                continue
782
783            parameter_name = re.sub(r"\[.*", "", parameter)
784
785            if parameterdescs.get(parameter_name) == KernelDoc.undescribed:
786                continue
787
788            self.data += f'.IP "{parameter}" 12' + "\n"
789            self.output_highlight(parameterdescs.get(parameter_name))
790
791        for section in sectionlist:
792            self.data += f'.SH "{section}"' + "\n"
793            self.output_highlight(sections.get(section))
794