xref: /linux/tools/lib/python/kdoc/kdoc_output.py (revision 9c3911812b4a719623ea7502b419929eb01b2fc2)
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"""
8Classes to implement output filters to print kernel-doc documentation.
9
10The implementation uses a virtual base class ``OutputFormat``. It
11contains 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, e.g. ``RestFormat`` and ``ManFormat`` classes.
16
17Currently, there are output classes for ReST and man/troff.
18"""
19
20import os
21import re
22from datetime import datetime
23
24from kdoc.kdoc_parser import KernelDoc, type_param
25from kdoc.kdoc_re import KernRe
26
27
28function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False)
29
30# match expressions used to find embedded type information
31type_constant = KernRe(r"\b``([^\`]+)``\b", cache=False)
32type_constant2 = KernRe(r"\%([-_*\w]+)", cache=False)
33type_func = KernRe(r"(\w+)\(\)", cache=False)
34type_param_ref = KernRe(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False)
35
36# Special RST handling for func ptr params
37type_fp_param = KernRe(r"\@(\w+)\(\)", cache=False)
38
39# Special RST handling for structs with func ptr params
40type_fp_param2 = KernRe(r"\@(\w+->\S+)\(\)", cache=False)
41
42type_env = KernRe(r"(\$\w+)", cache=False)
43type_enum = KernRe(r"\&(enum\s*([_\w]+))", cache=False)
44type_struct = KernRe(r"\&(struct\s*([_\w]+))", cache=False)
45type_typedef = KernRe(r"\&(typedef\s*([_\w]+))", cache=False)
46type_union = KernRe(r"\&(union\s*([_\w]+))", cache=False)
47type_member = KernRe(r"\&([_\w]+)(\.|->)([_\w]+)", cache=False)
48type_fallback = KernRe(r"\&([_\w]+)", cache=False)
49type_member_func = type_member + KernRe(r"\(\)", cache=False)
50
51
52class OutputFormat:
53    """
54    Base class for OutputFormat. If used as-is, it means that only
55    warnings will be displayed.
56    """
57
58    # output mode.
59    OUTPUT_ALL          = 0 #: Output all symbols and doc sections.
60    OUTPUT_INCLUDE      = 1 #: Output only specified symbols.
61    OUTPUT_EXPORTED     = 2 #: Output exported symbols.
62    OUTPUT_INTERNAL     = 3 #: Output non-exported symbols.
63
64    #: Highlights to be used in ReST format.
65    highlights = []
66
67    #: Blank line character.
68    blankline = ""
69
70    def __init__(self):
71        """Declare internal vars and set mode to ``OUTPUT_ALL``."""
72
73        self.out_mode = self.OUTPUT_ALL
74        self.enable_lineno = None
75        self.nosymbol = {}
76        self.symbol = None
77        self.function_table = None
78        self.config = None
79        self.no_doc_sections = False
80
81        self.data = ""
82
83    def set_config(self, config):
84        """
85        Setup global config variables used by both parser and output.
86        """
87
88        self.config = config
89
90    def set_filter(self, export, internal, symbol, nosymbol, function_table,
91                   enable_lineno, no_doc_sections):
92        """
93        Initialize filter variables according to the requested mode.
94
95        Only one choice is valid between export, internal and symbol.
96
97        The nosymbol filter can be used on all modes.
98        """
99
100        self.enable_lineno = enable_lineno
101        self.no_doc_sections = no_doc_sections
102        self.function_table = function_table
103
104        if symbol:
105            self.out_mode = self.OUTPUT_INCLUDE
106        elif export:
107            self.out_mode = self.OUTPUT_EXPORTED
108        elif internal:
109            self.out_mode = self.OUTPUT_INTERNAL
110        else:
111            self.out_mode = self.OUTPUT_ALL
112
113        if nosymbol:
114            self.nosymbol = set(nosymbol)
115
116
117    def highlight_block(self, block):
118        """
119        Apply the RST highlights to a sub-block of text.
120        """
121
122        for r, sub in self.highlights:
123            block = r.sub(sub, block)
124
125        return block
126
127    def out_warnings(self, args):
128        """
129        Output warnings for identifiers that will be displayed.
130        """
131
132        for log_msg in args.warnings:
133            self.config.warning(log_msg)
134
135    def check_doc(self, name, args):
136        """Check if DOC should be output."""
137
138        if self.no_doc_sections:
139            return False
140
141        if name in self.nosymbol:
142            return False
143
144        if self.out_mode == self.OUTPUT_ALL:
145            self.out_warnings(args)
146            return True
147
148        if self.out_mode == self.OUTPUT_INCLUDE:
149            if name in self.function_table:
150                self.out_warnings(args)
151                return True
152
153        return False
154
155    def check_declaration(self, dtype, name, args):
156        """
157        Checks if a declaration should be output or not based on the
158        filtering criteria.
159        """
160
161        if name in self.nosymbol:
162            return False
163
164        if self.out_mode == self.OUTPUT_ALL:
165            self.out_warnings(args)
166            return True
167
168        if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]:
169            if name in self.function_table:
170                return True
171
172        if self.out_mode == self.OUTPUT_INTERNAL:
173            if dtype != "function":
174                self.out_warnings(args)
175                return True
176
177            if name not in self.function_table:
178                self.out_warnings(args)
179                return True
180
181        return False
182
183    def msg(self, fname, name, args):
184        """
185        Handles a single entry from kernel-doc parser.
186        """
187
188        self.data = ""
189
190        dtype = args.type
191
192        if dtype == "doc":
193            self.out_doc(fname, name, args)
194            return self.data
195
196        if not self.check_declaration(dtype, name, args):
197            return self.data
198
199        if dtype == "function":
200            self.out_function(fname, name, args)
201            return self.data
202
203        if dtype == "enum":
204            self.out_enum(fname, name, args)
205            return self.data
206
207        if dtype == "var":
208            self.out_var(fname, name, args)
209            return self.data
210
211        if dtype == "typedef":
212            self.out_typedef(fname, name, args)
213            return self.data
214
215        if dtype in ["struct", "union"]:
216            self.out_struct(fname, name, args)
217            return self.data
218
219        # Warn if some type requires an output logic
220        self.config.log.warning("doesn't know how to output '%s' block",
221                                dtype)
222
223        return None
224
225    def output_symbols(self, fname, symbols):
226        """
227        Handles a set of KdocItem symbols.
228        """
229        self.set_symbols(symbols)
230
231        msg = ""
232        for arg in symbols:
233            m = self.msg(fname, arg.name, arg)
234
235            if m is None:
236                ln = arg.get("ln", 0)
237                dtype = arg.get('type', "")
238
239                self.config.log.warning("%s:%d Can't handle %s",
240                                        fname, ln, dtype)
241            else:
242                msg += m
243
244        return msg
245
246    # Virtual methods to be overridden by inherited classes
247    # At the base class, those do nothing.
248    def set_symbols(self, symbols):
249        """Get a list of all symbols from kernel_doc."""
250
251    def out_doc(self, fname, name, args):
252        """Outputs a DOC block."""
253
254    def out_function(self, fname, name, args):
255        """Outputs a function."""
256
257    def out_enum(self, fname, name, args):
258        """Outputs an enum."""
259
260    def out_var(self, fname, name, args):
261        """Outputs a variable."""
262
263    def out_typedef(self, fname, name, args):
264        """Outputs a typedef."""
265
266    def out_struct(self, fname, name, args):
267        """Outputs a struct."""
268
269
270class RestFormat(OutputFormat):
271    """Consts and functions used by ReST output."""
272
273    #: Highlights to be used in ReST format
274    highlights = [
275        (type_constant, r"``\1``"),
276        (type_constant2, r"``\1``"),
277
278        # Note: need to escape () to avoid func matching later
279        (type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"),
280        (type_member, r":c:type:`\1\2\3 <\1>`"),
281        (type_fp_param, r"**\1\\(\\)**"),
282        (type_fp_param2, r"**\1\\(\\)**"),
283        (type_func, r"\1()"),
284        (type_enum, r":c:type:`\1 <\2>`"),
285        (type_struct, r":c:type:`\1 <\2>`"),
286        (type_typedef, r":c:type:`\1 <\2>`"),
287        (type_union, r":c:type:`\1 <\2>`"),
288
289        # in rst this can refer to any type
290        (type_fallback, r":c:type:`\1`"),
291        (type_param_ref, r"**\1\2**")
292    ]
293
294    blankline = "\n"
295
296    #: Sphinx literal block regex.
297    sphinx_literal = KernRe(r'^[^.].*::$', cache=False)
298
299    #: Sphinx code block regex.
300    sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False)
301
302    def __init__(self):
303        """
304        Creates class variables.
305
306        Not really mandatory, but it is a good coding style and makes
307        pylint happy.
308        """
309
310        super().__init__()
311        self.lineprefix = ""
312
313    def print_lineno(self, ln):
314        """Outputs a line number."""
315
316        if self.enable_lineno and ln is not None:
317            ln += 1
318            self.data += f".. LINENO {ln}\n"
319
320    def output_highlight(self, args):
321        """
322        Outputs a C symbol that may require being converted to ReST using
323        the self.highlights variable.
324        """
325
326        input_text = args
327        output = ""
328        in_literal = False
329        litprefix = ""
330        block = ""
331
332        for line in input_text.strip("\n").split("\n"):
333
334            # If we're in a literal block, see if we should drop out of it.
335            # Otherwise, pass the line straight through unmunged.
336            if in_literal:
337                if line.strip():  # If the line is not blank
338                    # If this is the first non-blank line in a literal block,
339                    # figure out the proper indent.
340                    if not litprefix:
341                        r = KernRe(r'^(\s*)')
342                        if r.match(line):
343                            litprefix = '^' + r.group(1)
344                        else:
345                            litprefix = ""
346
347                        output += line + "\n"
348                    elif not KernRe(litprefix).match(line):
349                        in_literal = False
350                    else:
351                        output += line + "\n"
352                else:
353                    output += line + "\n"
354
355            # Not in a literal block (or just dropped out)
356            if not in_literal:
357                block += line + "\n"
358                if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line):
359                    in_literal = True
360                    litprefix = ""
361                    output += self.highlight_block(block)
362                    block = ""
363
364        # Handle any remaining block
365        if block:
366            output += self.highlight_block(block)
367
368        # Print the output with the line prefix
369        for line in output.strip("\n").split("\n"):
370            self.data += self.lineprefix + line + "\n"
371
372    def out_section(self, args, out_docblock=False):
373        """
374        Outputs a block section.
375
376        This could use some work; it's used to output the DOC: sections, and
377        starts by putting out the name of the doc section itself, but that
378        tends to duplicate a header already in the template file.
379        """
380        for section, text in args.sections.items():
381            # Skip sections that are in the nosymbol_table
382            if section in self.nosymbol:
383                continue
384
385            if out_docblock:
386                if not self.out_mode == self.OUTPUT_INCLUDE:
387                    self.data += f".. _{section}:\n\n"
388                    self.data += f'{self.lineprefix}**{section}**\n\n'
389            else:
390                self.data += f'{self.lineprefix}**{section}**\n\n'
391
392            self.print_lineno(args.sections_start_lines.get(section, 0))
393            self.output_highlight(text)
394            self.data += "\n"
395        self.data += "\n"
396
397    def out_doc(self, fname, name, args):
398        if not self.check_doc(name, args):
399            return
400        self.out_section(args, out_docblock=True)
401
402    def out_function(self, fname, name, args):
403
404        oldprefix = self.lineprefix
405        signature = ""
406
407        func_macro = args.get('func_macro', False)
408        if func_macro:
409            signature = name
410        else:
411            if args.get('functiontype'):
412                signature = args['functiontype'] + " "
413            signature += name + " ("
414
415        ln = args.declaration_start_line
416        count = 0
417        for parameter in args.parameterlist:
418            if count != 0:
419                signature += ", "
420            count += 1
421            dtype = args.parametertypes.get(parameter, "")
422
423            if function_pointer.search(dtype):
424                signature += function_pointer.group(1) + parameter + function_pointer.group(3)
425            else:
426                signature += dtype
427
428        if not func_macro:
429            signature += ")"
430
431        self.print_lineno(ln)
432        if args.get('typedef') or not args.get('functiontype'):
433            self.data += f".. c:macro:: {name}\n\n"
434
435            if args.get('typedef'):
436                self.data += "   **Typedef**: "
437                self.lineprefix = ""
438                self.output_highlight(args.get('purpose', ""))
439                self.data += "\n\n**Syntax**\n\n"
440                self.data += f"  ``{signature}``\n\n"
441            else:
442                self.data += f"``{signature}``\n\n"
443        else:
444            self.data += f".. c:function:: {signature}\n\n"
445
446        if not args.get('typedef'):
447            self.print_lineno(ln)
448            self.lineprefix = "   "
449            self.output_highlight(args.get('purpose', ""))
450            self.data += "\n"
451
452        # Put descriptive text into a container (HTML <div>) to help set
453        # function prototypes apart
454        self.lineprefix = "  "
455
456        if args.parameterlist:
457            self.data += ".. container:: kernelindent\n\n"
458            self.data += f"{self.lineprefix}**Parameters**\n\n"
459
460        for parameter in args.parameterlist:
461            parameter_name = KernRe(r'\[.*').sub('', parameter)
462            dtype = args.parametertypes.get(parameter, "")
463
464            if dtype:
465                self.data += f"{self.lineprefix}``{dtype}``\n"
466            else:
467                self.data += f"{self.lineprefix}``{parameter}``\n"
468
469            self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))
470
471            self.lineprefix = "    "
472            if parameter_name in args.parameterdescs and \
473               args.parameterdescs[parameter_name] != KernelDoc.undescribed:
474
475                self.output_highlight(args.parameterdescs[parameter_name])
476                self.data += "\n"
477            else:
478                self.data += f"{self.lineprefix}*undescribed*\n\n"
479            self.lineprefix = "  "
480
481        self.out_section(args)
482        self.lineprefix = oldprefix
483
484    def out_enum(self, fname, name, args):
485
486        oldprefix = self.lineprefix
487        ln = args.declaration_start_line
488
489        self.data += f"\n\n.. c:enum:: {name}\n\n"
490
491        self.print_lineno(ln)
492        self.lineprefix = "  "
493        self.output_highlight(args.get('purpose', ''))
494        self.data += "\n"
495
496        self.data += ".. container:: kernelindent\n\n"
497        outer = self.lineprefix + "  "
498        self.lineprefix = outer + "  "
499        self.data += f"{outer}**Constants**\n\n"
500
501        for parameter in args.parameterlist:
502            self.data += f"{outer}``{parameter}``\n"
503
504            if args.parameterdescs.get(parameter, '') != KernelDoc.undescribed:
505                self.output_highlight(args.parameterdescs[parameter])
506            else:
507                self.data += f"{self.lineprefix}*undescribed*\n\n"
508            self.data += "\n"
509
510        self.lineprefix = oldprefix
511        self.out_section(args)
512
513    def out_var(self, fname, name, args):
514        oldprefix = self.lineprefix
515        ln = args.declaration_start_line
516        full_proto = args.other_stuff.get("full_proto")
517        if not full_proto:
518            raise KeyError(f"Can't find full proto for {name} variable")
519
520        self.lineprefix = "  "
521
522        self.data += f"\n\n.. c:macro:: {name}\n\n{self.lineprefix}``{full_proto}``\n\n"
523
524        self.print_lineno(ln)
525        self.output_highlight(args.get('purpose', ''))
526        self.data += "\n"
527
528        if args.other_stuff["default_val"]:
529            self.data += f'{self.lineprefix}**Initialization**\n\n'
530            self.output_highlight(f'default: ``{args.other_stuff["default_val"]}``')
531
532        self.out_section(args)
533
534    def out_typedef(self, fname, name, args):
535
536        oldprefix = self.lineprefix
537        ln = args.declaration_start_line
538
539        self.data += f"\n\n.. c:type:: {name}\n\n"
540
541        self.print_lineno(ln)
542        self.lineprefix = "   "
543
544        self.output_highlight(args.get('purpose', ''))
545
546        self.data += "\n"
547
548        self.lineprefix = oldprefix
549        self.out_section(args)
550
551    def out_struct(self, fname, name, args):
552
553        purpose = args.get('purpose', "")
554        declaration = args.get('definition', "")
555        dtype = args.type
556        ln = args.declaration_start_line
557
558        self.data += f"\n\n.. c:{dtype}:: {name}\n\n"
559
560        self.print_lineno(ln)
561
562        oldprefix = self.lineprefix
563        self.lineprefix += "  "
564
565        self.output_highlight(purpose)
566        self.data += "\n"
567
568        self.data += ".. container:: kernelindent\n\n"
569        self.data += f"{self.lineprefix}**Definition**::\n\n"
570
571        self.lineprefix = self.lineprefix + "  "
572
573        declaration = declaration.replace("\t", self.lineprefix)
574
575        self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n"
576        self.data += f"{declaration}{self.lineprefix}" + "};\n\n"
577
578        self.lineprefix = "  "
579        self.data += f"{self.lineprefix}**Members**\n\n"
580        for parameter in args.parameterlist:
581            if not parameter or parameter.startswith("#"):
582                continue
583
584            parameter_name = parameter.split("[", maxsplit=1)[0]
585
586            if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:
587                continue
588
589            self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))
590
591            self.data += f"{self.lineprefix}``{parameter}``\n"
592
593            self.lineprefix = "    "
594            self.output_highlight(args.parameterdescs[parameter_name])
595            self.lineprefix = "  "
596
597            self.data += "\n"
598
599        self.data += "\n"
600
601        self.lineprefix = oldprefix
602        self.out_section(args)
603
604
605class ManFormat(OutputFormat):
606    """
607    Consts and functions used by man pages output.
608
609    This class has one mandatory parameter and some optional ones, which
610    are needed to define the title header contents:
611
612    ``modulename``
613        Defines the module name to be used at the troff ``.TH`` output.
614
615        This argument is optional. If not specified, it will be filled
616        with the directory which contains the documented file.
617
618    ``section``
619        Usually a numeric value from 0 to 9, but man pages also accept
620        some strings like "p".
621
622        Defauls to ``9``
623
624    ``manual``
625        Defaults to ``Kernel API Manual``.
626
627    The above controls the output of teh corresponding fields on troff
628    title headers, which will be filled like this::
629
630        .TH "{name}" {section} "{date}" "{modulename}" "{manual}"
631
632    where ``name``` will match the API symbol name, and ``date`` will be
633    either the date where the Kernel was compiled or the current date
634    """
635
636    highlights = (
637        (type_constant, r"\1"),
638        (type_constant2, r"\1"),
639        (type_func, r"\\fB\1\\fP"),
640        (type_enum, r"\\fI\1\\fP"),
641        (type_struct, r"\\fI\1\\fP"),
642        (type_typedef, r"\\fI\1\\fP"),
643        (type_union, r"\\fI\1\\fP"),
644        (type_param, r"\\fI\1\\fP"),
645        (type_param_ref, r"\\fI\1\2\\fP"),
646        (type_member, r"\\fI\1\2\3\\fP"),
647        (type_fallback, r"\\fI\1\\fP")
648    )
649    blankline = ""
650
651    #: Allowed timestamp formats.
652    date_formats = [
653        "%a %b %d %H:%M:%S %Z %Y",
654        "%a %b %d %H:%M:%S %Y",
655        "%Y-%m-%d",
656        "%b %d %Y",
657        "%B %d %Y",
658        "%m %d %Y",
659    ]
660
661    def modulename(self, args):
662        if self._modulename:
663            return self._modulename
664
665        return os.path.dirname(args.fname)
666
667    def emit_th(self, name, args):
668        """Emit a title header line."""
669        title = name.strip()
670        module = self.modulename(args)
671
672        self.data += f'.TH "{title}" {self.section} "{self.date}" '
673        self.data += f'"{module}" "{self.manual}"\n'
674
675    def __init__(self, modulename=None, section="9", manual="Kernel API Manual"):
676        """
677        Creates class variables.
678
679        Not really mandatory, but it is a good coding style and makes
680        pylint happy.
681        """
682
683        super().__init__()
684
685        self._modulename = modulename
686        self.section = section
687        self.manual = manual
688
689        self.symbols = []
690
691        dt = None
692        tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP")
693        if tstamp:
694            for fmt in self.date_formats:
695                try:
696                    dt = datetime.strptime(tstamp, fmt)
697                    break
698                except ValueError:
699                    pass
700
701        if not dt:
702            dt = datetime.now()
703
704        self.date = dt.strftime("%B %Y")
705
706    def arg_name(self, args, name):
707        """
708        Return the name that will be used for the man page.
709
710        As we may have the same name on different namespaces,
711        prepend the data type for all types except functions and typedefs.
712
713        The doc section is special: it uses the modulename.
714        """
715
716        dtype = args.type
717
718        if dtype == "doc":
719            return name
720#            return os.path.basename(self.modulename(args))
721
722        if dtype in ["function", "typedef"]:
723            return name
724
725        return f"{dtype} {name}"
726
727    def set_symbols(self, symbols):
728        """
729        Get a list of all symbols from kernel_doc.
730
731        Man pages will uses it to add a SEE ALSO section with other
732        symbols at the same file.
733        """
734        self.symbols = symbols
735
736    def out_tail(self, fname, name, args):
737        """Adds a tail for all man pages."""
738
739        # SEE ALSO section
740        self.data += f'.SH "SEE ALSO"' + "\n.PP\n"
741        self.data += (f"Kernel file \\fB{args.fname}\\fR\n")
742        if len(self.symbols) >= 2:
743            cur_name = self.arg_name(args, name)
744
745            related = []
746            for arg in self.symbols:
747                out_name = self.arg_name(arg, arg.name)
748
749                if cur_name == out_name:
750                    continue
751
752                related.append(f"\\fB{out_name}\\fR(9)")
753
754            self.data += ",\n".join(related) + "\n"
755
756        # TODO: does it make sense to add other sections? Maybe
757        # REPORTING ISSUES? LICENSE?
758
759    def msg(self, fname, name, args):
760        """
761        Handles a single entry from kernel-doc parser.
762
763        Add a tail at the end of man pages output.
764        """
765        super().msg(fname, name, args)
766        self.out_tail(fname, name, args)
767
768        return self.data
769
770    def emit_table(self, colspec_row, rows):
771
772        if not rows:
773            return ""
774
775        out = ""
776        colspec = "\t".join(["l"] * len(rows[0]))
777
778        out += "\n.TS\n"
779        out += "box;\n"
780        out += f"{colspec}.\n"
781
782        if colspec_row:
783            out_row = []
784
785            for text in colspec_row:
786                out_row.append(f"\\fB{text}\\fP")
787
788            out += "\t".join(out_row) + "\n_\n"
789
790        for r in rows:
791            out += "\t".join(r) + "\n"
792
793        out += ".TE\n"
794
795        return out
796
797    def grid_table(self, lines, start):
798        """
799        Ancillary function to help handling a grid table inside the text.
800        """
801
802        i = start + 1
803        rows = []
804        colspec_row = None
805
806        while i < len(lines):
807            line = lines[i]
808
809            if KernRe(r"^\s*\|.*\|\s*$").match(line):
810                parts = []
811
812                for p in line.strip('|').split('|'):
813                    parts.append(p.strip())
814
815                rows.append(parts)
816
817            elif KernRe(r'^\+\=[\+\=]+\+\s*$').match(line):
818                if rows and rows[0]:
819                    if not colspec_row:
820                        colspec_row = [""] * len(rows[0])
821
822                    for j in range(0, len(rows[0])):
823                        content = []
824                        for row in rows:
825                            content.append(row[j])
826
827                        colspec_row[j] = " ".join(content)
828
829                    rows = []
830
831            elif KernRe(r"^\s*\+[-+]+\+.*$").match(line):
832                pass
833
834            else:
835                break
836
837            i += 1
838
839        return i, self.emit_table(colspec_row, rows)
840
841    def simple_table(self, lines, start):
842        """
843        Ancillary function to help handling a simple table inside the text.
844        """
845
846        i = start
847        rows = []
848        colspec_row = None
849
850        pos = []
851        for m in KernRe(r'\=+').finditer(lines[i]):
852            pos.append((m.start(), m.end() - 1))
853
854        i += 1
855        while i < len(lines):
856            line = lines[i]
857
858            if KernRe(r"^\s*[\=]+[ \t\=]+$").match(line):
859                i += 1
860                break
861
862            elif KernRe(r'^[\s=]+$').match(line):
863                if rows and rows[0]:
864                    if not colspec_row:
865                        colspec_row = [""] * len(rows[0])
866
867                    for j in range(0, len(rows[0])):
868                        content = []
869                        for row in rows:
870                            content.append(row[j])
871
872                        colspec_row[j] = " ".join(content)
873
874                    rows = []
875
876            else:
877                row = [""] * len(pos)
878
879                for j in range(0, len(pos)):
880                    start, end = pos[j]
881
882                    row[j] = line[start:end].strip()
883
884                rows.append(row)
885
886            i += 1
887
888        return i, self.emit_table(colspec_row, rows)
889
890    def code_block(self, lines, start):
891        """
892        Ensure that code blocks won't be messed up at the output.
893
894        By default, troff join lines at the same paragraph. Disable it,
895        on code blocks.
896        """
897
898        line = lines[start]
899
900        if "code-block" in line:
901            out = "\n.nf\n"
902        elif line.startswith("..") and line.endswith("::"):
903            #
904            # Handle note, warning, error, ... markups
905            #
906            line = line[2:-1].strip().upper()
907            out = f"\n.nf\n\\fB{line}\\fP\n"
908        elif line.endswith("::"):
909            out = line[:-1]
910            out += "\n.nf\n"
911        else:
912            # Just in case. Should never happen in practice
913            out = "\n.nf\n"
914
915        i = start + 1
916        ident = None
917
918        while i < len(lines):
919            line = lines[i]
920
921            m = KernRe(r"\S").match(line)
922            if not m:
923                out += line + "\n"
924                i += 1
925                continue
926
927            pos = m.start()
928            if not ident:
929                if pos > 0:
930                    ident = pos
931                else:
932                    out += "\n.fi\n"
933                    if i > start + 1:
934                        return i - 1, out
935                    else:
936                        # Just in case. Should never happen in practice
937                        return i, out
938
939            if pos >= ident:
940                out += line + "\n"
941                i += 1
942                continue
943
944            break
945
946        out += "\n.fi\n"
947        return i, out
948
949    def output_highlight(self, block):
950        """
951        Outputs a C symbol that may require being highlighted with
952        self.highlights variable using troff syntax.
953        """
954
955        contents = self.highlight_block(block)
956
957        if isinstance(contents, list):
958            contents = "\n".join(contents)
959
960        lines = contents.strip("\n").split("\n")
961        i = 0
962
963        while i < len(lines):
964            org_line = lines[i]
965
966            line = KernRe(r"^\s*").sub("", org_line)
967
968            if line:
969                if KernRe(r"^\+\-[-+]+\+.*$").match(line):
970                    i, text = self.grid_table(lines, i)
971                    self.data += text
972                    continue
973
974                if KernRe(r"^\=+[ \t]\=[ \t\=]+$").match(line):
975                    i, text = self.simple_table(lines, i)
976                    self.data += text
977                    continue
978
979                if line.endswith("::") or KernRe(r"\.\.\s+code-block.*::").match(line):
980                    i, text = self.code_block(lines, i)
981                    self.data += text
982                    continue
983
984                if line[0] == ".":
985                    self.data += "\\&" + line + "\n"
986                    i += 1
987                    continue
988
989                #
990                # Handle lists
991                #
992                line = KernRe(r'^[-*]\s+').sub(r'.IP \[bu]\n', line)
993                line = KernRe(r'^(\d+|a-z)[\.\)]\s+').sub(r'.IP \1\n', line)
994            else:
995                line = ".PP\n"
996
997            i += 1
998
999            self.data += line + "\n"
1000
1001    def out_doc(self, fname, name, args):
1002        if not self.check_doc(name, args):
1003            return
1004
1005        out_name = self.arg_name(args, name)
1006
1007        self.emit_th(out_name, args)
1008
1009        for section, text in args.sections.items():
1010            self.data += f'.SH "{section}"' + "\n"
1011            self.output_highlight(text)
1012
1013    def out_function(self, fname, name, args):
1014
1015        out_name = self.arg_name(args, name)
1016
1017        self.emit_th(out_name, args)
1018
1019        self.data += ".SH NAME\n"
1020        self.data += f"{name} \\- {args['purpose']}\n"
1021
1022        self.data += ".SH SYNOPSIS\n"
1023        if args.get('functiontype', ''):
1024            self.data += f'.B "{args["functiontype"]}" {name}' + "\n"
1025        else:
1026            self.data += f'.B "{name}' + "\n"
1027
1028        count = 0
1029        parenth = "("
1030        post = ","
1031
1032        for parameter in args.parameterlist:
1033            if count == len(args.parameterlist) - 1:
1034                post = ");"
1035
1036            dtype = args.parametertypes.get(parameter, "")
1037            if function_pointer.match(dtype):
1038                # Pointer-to-function
1039                self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n"
1040            else:
1041                dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype)
1042
1043                self.data += f'.BI "{parenth}{dtype}"  "{post}"' + "\n"
1044            count += 1
1045            parenth = ""
1046
1047        if args.parameterlist:
1048            self.data += ".SH ARGUMENTS\n"
1049
1050        for parameter in args.parameterlist:
1051            parameter_name = re.sub(r'\[.*', '', parameter)
1052
1053            self.data += f'.IP "{parameter}" 12' + "\n"
1054            self.output_highlight(args.parameterdescs.get(parameter_name, ""))
1055
1056        for section, text in args.sections.items():
1057            self.data += f'.SH "{section.upper()}"' + "\n"
1058            self.output_highlight(text)
1059
1060    def out_enum(self, fname, name, args):
1061        out_name = self.arg_name(args, name)
1062
1063        self.emit_th(out_name, args)
1064
1065        self.data += ".SH NAME\n"
1066        self.data += f"enum {name} \\- {args['purpose']}\n"
1067
1068        self.data += ".SH SYNOPSIS\n"
1069        self.data += f"enum {name}" + " {\n"
1070
1071        count = 0
1072        for parameter in args.parameterlist:
1073            self.data += f'.br\n.BI "    {parameter}"' + "\n"
1074            if count == len(args.parameterlist) - 1:
1075                self.data += "\n};\n"
1076            else:
1077                self.data += ", \n.br\n"
1078
1079            count += 1
1080
1081        self.data += ".SH Constants\n"
1082
1083        for parameter in args.parameterlist:
1084            parameter_name = KernRe(r'\[.*').sub('', parameter)
1085            self.data += f'.IP "{parameter}" 12' + "\n"
1086            self.output_highlight(args.parameterdescs.get(parameter_name, ""))
1087
1088        for section, text in args.sections.items():
1089            self.data += f'.SH "{section}"' + "\n"
1090            self.output_highlight(text)
1091
1092    def out_var(self, fname, name, args):
1093        out_name = self.arg_name(args, name)
1094        full_proto = args.other_stuff["full_proto"]
1095
1096        self.emit_th(out_name, args)
1097
1098        self.data += ".SH NAME\n"
1099        self.data += f"{name} \\- {args['purpose']}\n"
1100
1101        self.data += ".SH SYNOPSIS\n"
1102        self.data += f"{full_proto}\n"
1103
1104        if args.other_stuff["default_val"]:
1105            self.data += f'.SH "Initialization"' + "\n"
1106            self.output_highlight(f'default: {args.other_stuff["default_val"]}')
1107
1108        for section, text in args.sections.items():
1109            self.data += f'.SH "{section}"' + "\n"
1110            self.output_highlight(text)
1111
1112    def out_typedef(self, fname, name, args):
1113        module = self.modulename(args)
1114        purpose = args.get('purpose')
1115        out_name = self.arg_name(args, name)
1116
1117        self.emit_th(out_name, args)
1118
1119        self.data += ".SH NAME\n"
1120        self.data += f"typedef {name} \\- {purpose}\n"
1121
1122        for section, text in args.sections.items():
1123            self.data += f'.SH "{section}"' + "\n"
1124            self.output_highlight(text)
1125
1126    def out_struct(self, fname, name, args):
1127        module = self.modulename(args)
1128        purpose = args.get('purpose')
1129        definition = args.get('definition')
1130        out_name = self.arg_name(args, name)
1131
1132        self.emit_th(out_name, args)
1133
1134        self.data += ".SH NAME\n"
1135        self.data += f"{args.type} {name} \\- {purpose}\n"
1136
1137        # Replace tabs with two spaces and handle newlines
1138        declaration = definition.replace("\t", "  ")
1139        declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration)
1140
1141        self.data += ".SH SYNOPSIS\n"
1142        self.data += f"{args.type} {name} " + "{" + "\n.br\n"
1143        self.data += f'.BI "{declaration}\n' + "};\n.br\n\n"
1144
1145        self.data += ".SH Members\n"
1146        for parameter in args.parameterlist:
1147            if parameter.startswith("#"):
1148                continue
1149
1150            parameter_name = re.sub(r"\[.*", "", parameter)
1151
1152            if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:
1153                continue
1154
1155            self.data += f'.IP "{parameter}" 12' + "\n"
1156            self.output_highlight(args.parameterdescs.get(parameter_name))
1157
1158        for section, text in args.sections.items():
1159            self.data += f'.SH "{section}"' + "\n"
1160            self.output_highlight(text)
1161