#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
#
# pylint: disable=C0301,R0902,R0911,R0912,R0913,R0914,R0915,R0917

"""
Classes to implement output filters to print kernel-doc documentation.

The implementation uses a virtual base class ``OutputFormat``. It
contains dispatches to virtual methods, and some code to filter
out output messages.

The actual implementation is done on one separate class per each type
of output, e.g. ``RestFormat`` and ``ManFormat`` classes.

Currently, there are output classes for ReST and man/troff.
"""

import os
import re
from datetime import datetime

from kdoc.kdoc_parser import KernelDoc, type_param
from kdoc.kdoc_re import KernRe


function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False)

# match expressions used to find embedded type information
type_constant = KernRe(r"\b``([^\`]+)``\b", cache=False)
type_constant2 = KernRe(r"\%([-_*\w]+)", cache=False)
type_func = KernRe(r"(\w+)\(\)", cache=False)
type_param_ref = KernRe(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False)

# Special RST handling for func ptr params
type_fp_param = KernRe(r"\@(\w+)\(\)", cache=False)

# Special RST handling for structs with func ptr params
type_fp_param2 = KernRe(r"\@(\w+->\S+)\(\)", cache=False)

type_env = KernRe(r"(\$\w+)", cache=False)
type_enum = KernRe(r"\&(enum\s*([_\w]+))", cache=False)
type_struct = KernRe(r"\&(struct\s*([_\w]+))", cache=False)
type_typedef = KernRe(r"\&(typedef\s*([_\w]+))", cache=False)
type_union = KernRe(r"\&(union\s*([_\w]+))", cache=False)
type_member = KernRe(r"\&([_\w]+)(\.|->)([_\w]+)", cache=False)
type_fallback = KernRe(r"\&([_\w]+)", cache=False)
type_member_func = type_member + KernRe(r"\(\)", cache=False)


class OutputFormat:
    """
    Base class for OutputFormat. If used as-is, it means that only
    warnings will be displayed.
    """

    # output mode.
    OUTPUT_ALL          = 0 #: Output all symbols and doc sections.
    OUTPUT_INCLUDE      = 1 #: Output only specified symbols.
    OUTPUT_EXPORTED     = 2 #: Output exported symbols.
    OUTPUT_INTERNAL     = 3 #: Output non-exported symbols.

    #: Highlights to be used in ReST format.
    highlights = []

    #: Blank line character.
    blankline = ""

    def __init__(self):
        """Declare internal vars and set mode to ``OUTPUT_ALL``."""

        self.out_mode = self.OUTPUT_ALL
        self.enable_lineno = None
        self.nosymbol = {}
        self.symbol = None
        self.function_table = None
        self.config = None
        self.no_doc_sections = False

        self.data = ""

    def set_config(self, config):
        """
        Setup global config variables used by both parser and output.
        """

        self.config = config

    def set_filter(self, export, internal, symbol, nosymbol, function_table,
                   enable_lineno, no_doc_sections):
        """
        Initialize filter variables according to the requested mode.

        Only one choice is valid between export, internal and symbol.

        The nosymbol filter can be used on all modes.
        """

        self.enable_lineno = enable_lineno
        self.no_doc_sections = no_doc_sections
        self.function_table = function_table

        if symbol:
            self.out_mode = self.OUTPUT_INCLUDE
        elif export:
            self.out_mode = self.OUTPUT_EXPORTED
        elif internal:
            self.out_mode = self.OUTPUT_INTERNAL
        else:
            self.out_mode = self.OUTPUT_ALL

        if nosymbol:
            self.nosymbol = set(nosymbol)


    def highlight_block(self, block):
        """
        Apply the RST highlights to a sub-block of text.
        """

        for r, sub in self.highlights:
            block = r.sub(sub, block)

        return block

    def out_warnings(self, args):
        """
        Output warnings for identifiers that will be displayed.
        """

        for log_msg in args.warnings:
            self.config.warning(log_msg)

    def check_doc(self, name, args):
        """Check if DOC should be output."""

        if self.no_doc_sections:
            return False

        if name in self.nosymbol:
            return False

        if self.out_mode == self.OUTPUT_ALL:
            self.out_warnings(args)
            return True

        if self.out_mode == self.OUTPUT_INCLUDE:
            if name in self.function_table:
                self.out_warnings(args)
                return True

        return False

    def check_declaration(self, dtype, name, args):
        """
        Checks if a declaration should be output or not based on the
        filtering criteria.
        """

        if name in self.nosymbol:
            return False

        if self.out_mode == self.OUTPUT_ALL:
            self.out_warnings(args)
            return True

        if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]:
            if name in self.function_table:
                return True

        if self.out_mode == self.OUTPUT_INTERNAL:
            if dtype != "function":
                self.out_warnings(args)
                return True

            if name not in self.function_table:
                self.out_warnings(args)
                return True

        return False

    def msg(self, fname, name, args):
        """
        Handles a single entry from kernel-doc parser.
        """

        self.data = ""

        dtype = args.type

        if dtype == "doc":
            self.out_doc(fname, name, args)
            return self.data

        if not self.check_declaration(dtype, name, args):
            return self.data

        if dtype == "function":
            self.out_function(fname, name, args)
            return self.data

        if dtype == "enum":
            self.out_enum(fname, name, args)
            return self.data

        if dtype == "var":
            self.out_var(fname, name, args)
            return self.data

        if dtype == "typedef":
            self.out_typedef(fname, name, args)
            return self.data

        if dtype in ["struct", "union"]:
            self.out_struct(fname, name, args)
            return self.data

        # Warn if some type requires an output logic
        self.config.log.warning("doesn't know how to output '%s' block",
                                dtype)

        return None

    def output_symbols(self, fname, symbols):
        """
        Handles a set of KdocItem symbols.
        """
        self.set_symbols(symbols)

        msg = ""
        for arg in symbols:
            m = self.msg(fname, arg.name, arg)

            if m is None:
                ln = arg.get("ln", 0)
                dtype = arg.get('type', "")

                self.config.log.warning("%s:%d Can't handle %s",
                                        fname, ln, dtype)
            else:
                msg += m

        return msg

    # Virtual methods to be overridden by inherited classes
    # At the base class, those do nothing.
    def set_symbols(self, symbols):
        """Get a list of all symbols from kernel_doc."""

    def out_doc(self, fname, name, args):
        """Outputs a DOC block."""

    def out_function(self, fname, name, args):
        """Outputs a function."""

    def out_enum(self, fname, name, args):
        """Outputs an enum."""

    def out_var(self, fname, name, args):
        """Outputs a variable."""

    def out_typedef(self, fname, name, args):
        """Outputs a typedef."""

    def out_struct(self, fname, name, args):
        """Outputs a struct."""


class RestFormat(OutputFormat):
    """Consts and functions used by ReST output."""

    #: Highlights to be used in ReST format
    highlights = [
        (type_constant, r"``\1``"),
        (type_constant2, r"``\1``"),

        # Note: need to escape () to avoid func matching later
        (type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"),
        (type_member, r":c:type:`\1\2\3 <\1>`"),
        (type_fp_param, r"**\1\\(\\)**"),
        (type_fp_param2, r"**\1\\(\\)**"),
        (type_func, r"\1()"),
        (type_enum, r":c:type:`\1 <\2>`"),
        (type_struct, r":c:type:`\1 <\2>`"),
        (type_typedef, r":c:type:`\1 <\2>`"),
        (type_union, r":c:type:`\1 <\2>`"),

        # in rst this can refer to any type
        (type_fallback, r":c:type:`\1`"),
        (type_param_ref, r"**\1\2**")
    ]

    blankline = "\n"

    #: Sphinx literal block regex.
    sphinx_literal = KernRe(r'^[^.].*::$', cache=False)

    #: Sphinx code block regex.
    sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False)

    def __init__(self):
        """
        Creates class variables.

        Not really mandatory, but it is a good coding style and makes
        pylint happy.
        """

        super().__init__()
        self.lineprefix = ""

    def print_lineno(self, ln):
        """Outputs a line number."""

        if self.enable_lineno and ln is not None:
            ln += 1
            self.data += f".. LINENO {ln}\n"

    def output_highlight(self, args):
        """
        Outputs a C symbol that may require being converted to ReST using
        the self.highlights variable.
        """

        input_text = args
        output = ""
        in_literal = False
        litprefix = ""
        block = ""

        for line in input_text.strip("\n").split("\n"):

            # If we're in a literal block, see if we should drop out of it.
            # Otherwise, pass the line straight through unmunged.
            if in_literal:
                if line.strip():  # If the line is not blank
                    # If this is the first non-blank line in a literal block,
                    # figure out the proper indent.
                    if not litprefix:
                        r = KernRe(r'^(\s*)')
                        if r.match(line):
                            litprefix = '^' + r.group(1)
                        else:
                            litprefix = ""

                        output += line + "\n"
                    elif not KernRe(litprefix).match(line):
                        in_literal = False
                    else:
                        output += line + "\n"
                else:
                    output += line + "\n"

            # Not in a literal block (or just dropped out)
            if not in_literal:
                block += line + "\n"
                if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line):
                    in_literal = True
                    litprefix = ""
                    output += self.highlight_block(block)
                    block = ""

        # Handle any remaining block
        if block:
            output += self.highlight_block(block)

        # Print the output with the line prefix
        for line in output.strip("\n").split("\n"):
            self.data += self.lineprefix + line + "\n"

    def out_section(self, args, out_docblock=False):
        """
        Outputs a block section.

        This could use some work; it's used to output the DOC: sections, and
        starts by putting out the name of the doc section itself, but that
        tends to duplicate a header already in the template file.
        """
        for section, text in args.sections.items():
            # Skip sections that are in the nosymbol_table
            if section in self.nosymbol:
                continue

            if out_docblock:
                if not self.out_mode == self.OUTPUT_INCLUDE:
                    self.data += f".. _{section}:\n\n"
                    self.data += f'{self.lineprefix}**{section}**\n\n'
            else:
                self.data += f'{self.lineprefix}**{section}**\n\n'

            self.print_lineno(args.sections_start_lines.get(section, 0))
            self.output_highlight(text)
            self.data += "\n"
        self.data += "\n"

    def out_doc(self, fname, name, args):
        if not self.check_doc(name, args):
            return
        self.out_section(args, out_docblock=True)

    def out_function(self, fname, name, args):

        oldprefix = self.lineprefix
        signature = ""

        func_macro = args.get('func_macro', False)
        if func_macro:
            signature = name
        else:
            if args.get('functiontype'):
                signature = args['functiontype'] + " "
            signature += name + " ("

        ln = args.declaration_start_line
        count = 0
        for parameter in args.parameterlist:
            if count != 0:
                signature += ", "
            count += 1
            dtype = args.parametertypes.get(parameter, "")

            if function_pointer.search(dtype):
                signature += function_pointer.group(1) + parameter + function_pointer.group(3)
            else:
                signature += dtype

        if not func_macro:
            signature += ")"

        self.print_lineno(ln)
        if args.get('typedef') or not args.get('functiontype'):
            self.data += f".. c:macro:: {name}\n\n"

            if args.get('typedef'):
                self.data += "   **Typedef**: "
                self.lineprefix = ""
                self.output_highlight(args.get('purpose', ""))
                self.data += "\n\n**Syntax**\n\n"
                self.data += f"  ``{signature}``\n\n"
            else:
                self.data += f"``{signature}``\n\n"
        else:
            self.data += f".. c:function:: {signature}\n\n"

        if not args.get('typedef'):
            self.print_lineno(ln)
            self.lineprefix = "   "
            self.output_highlight(args.get('purpose', ""))
            self.data += "\n"

        # Put descriptive text into a container (HTML <div>) to help set
        # function prototypes apart
        self.lineprefix = "  "

        if args.parameterlist:
            self.data += ".. container:: kernelindent\n\n"
            self.data += f"{self.lineprefix}**Parameters**\n\n"

        for parameter in args.parameterlist:
            parameter_name = KernRe(r'\[.*').sub('', parameter)
            dtype = args.parametertypes.get(parameter, "")

            if dtype:
                self.data += f"{self.lineprefix}``{dtype}``\n"
            else:
                self.data += f"{self.lineprefix}``{parameter}``\n"

            self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))

            self.lineprefix = "    "
            if parameter_name in args.parameterdescs and \
               args.parameterdescs[parameter_name] != KernelDoc.undescribed:

                self.output_highlight(args.parameterdescs[parameter_name])
                self.data += "\n"
            else:
                self.data += f"{self.lineprefix}*undescribed*\n\n"
            self.lineprefix = "  "

        self.out_section(args)
        self.lineprefix = oldprefix

    def out_enum(self, fname, name, args):

        oldprefix = self.lineprefix
        ln = args.declaration_start_line

        self.data += f"\n\n.. c:enum:: {name}\n\n"

        self.print_lineno(ln)
        self.lineprefix = "  "
        self.output_highlight(args.get('purpose', ''))
        self.data += "\n"

        self.data += ".. container:: kernelindent\n\n"
        outer = self.lineprefix + "  "
        self.lineprefix = outer + "  "
        self.data += f"{outer}**Constants**\n\n"

        for parameter in args.parameterlist:
            self.data += f"{outer}``{parameter}``\n"

            if args.parameterdescs.get(parameter, '') != KernelDoc.undescribed:
                self.output_highlight(args.parameterdescs[parameter])
            else:
                self.data += f"{self.lineprefix}*undescribed*\n\n"
            self.data += "\n"

        self.lineprefix = oldprefix
        self.out_section(args)

    def out_var(self, fname, name, args):
        oldprefix = self.lineprefix
        ln = args.declaration_start_line
        full_proto = args.other_stuff.get("full_proto")
        if not full_proto:
            raise KeyError(f"Can't find full proto for {name} variable")

        self.lineprefix = "  "

        self.data += f"\n\n.. c:macro:: {name}\n\n{self.lineprefix}``{full_proto}``\n\n"

        self.print_lineno(ln)
        self.output_highlight(args.get('purpose', ''))
        self.data += "\n"

        if args.other_stuff["default_val"]:
            self.data += f'{self.lineprefix}**Initialization**\n\n'
            self.output_highlight(f'default: ``{args.other_stuff["default_val"]}``')

        self.out_section(args)

    def out_typedef(self, fname, name, args):

        oldprefix = self.lineprefix
        ln = args.declaration_start_line

        self.data += f"\n\n.. c:type:: {name}\n\n"

        self.print_lineno(ln)
        self.lineprefix = "   "

        self.output_highlight(args.get('purpose', ''))

        self.data += "\n"

        self.lineprefix = oldprefix
        self.out_section(args)

    def out_struct(self, fname, name, args):

        purpose = args.get('purpose', "")
        declaration = args.get('definition', "")
        dtype = args.type
        ln = args.declaration_start_line

        self.data += f"\n\n.. c:{dtype}:: {name}\n\n"

        self.print_lineno(ln)

        oldprefix = self.lineprefix
        self.lineprefix += "  "

        self.output_highlight(purpose)
        self.data += "\n"

        self.data += ".. container:: kernelindent\n\n"
        self.data += f"{self.lineprefix}**Definition**::\n\n"

        self.lineprefix = self.lineprefix + "  "

        declaration = declaration.replace("\t", self.lineprefix)

        self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n"
        self.data += f"{declaration}{self.lineprefix}" + "};\n\n"

        self.lineprefix = "  "
        self.data += f"{self.lineprefix}**Members**\n\n"
        for parameter in args.parameterlist:
            if not parameter or parameter.startswith("#"):
                continue

            parameter_name = parameter.split("[", maxsplit=1)[0]

            if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:
                continue

            self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))

            self.data += f"{self.lineprefix}``{parameter}``\n"

            self.lineprefix = "    "
            self.output_highlight(args.parameterdescs[parameter_name])
            self.lineprefix = "  "

            self.data += "\n"

        self.data += "\n"

        self.lineprefix = oldprefix
        self.out_section(args)


class ManFormat(OutputFormat):
    """
    Consts and functions used by man pages output.

    This class has one mandatory parameter and some optional ones, which
    are needed to define the title header contents:

    ``modulename``
        Defines the module name to be used at the troff ``.TH`` output.

        This argument is optional. If not specified, it will be filled
        with the directory which contains the documented file.

    ``section``
        Usually a numeric value from 0 to 9, but man pages also accept
        some strings like "p".

        Defauls to ``9``

    ``manual``
        Defaults to ``Kernel API Manual``.

    The above controls the output of teh corresponding fields on troff
    title headers, which will be filled like this::

        .TH "{name}" {section} "{date}" "{modulename}" "{manual}"

    where ``name``` will match the API symbol name, and ``date`` will be
    either the date where the Kernel was compiled or the current date
    """

    highlights = (
        (type_constant, r"\1"),
        (type_constant2, r"\1"),
        (type_func, r"\\fB\1\\fP"),
        (type_enum, r"\\fI\1\\fP"),
        (type_struct, r"\\fI\1\\fP"),
        (type_typedef, r"\\fI\1\\fP"),
        (type_union, r"\\fI\1\\fP"),
        (type_param, r"\\fI\1\\fP"),
        (type_param_ref, r"\\fI\1\2\\fP"),
        (type_member, r"\\fI\1\2\3\\fP"),
        (type_fallback, r"\\fI\1\\fP")
    )
    blankline = ""

    #: Allowed timestamp formats.
    date_formats = [
        "%a %b %d %H:%M:%S %Z %Y",
        "%a %b %d %H:%M:%S %Y",
        "%Y-%m-%d",
        "%b %d %Y",
        "%B %d %Y",
        "%m %d %Y",
    ]

    def modulename(self, args):
        if self._modulename:
            return self._modulename

        return os.path.dirname(args.fname)

    def emit_th(self, name, args):
        """Emit a title header line."""
        title = name.strip()
        module = self.modulename(args)

        self.data += f'.TH "{title}" {self.section} "{self.date}" '
        self.data += f'"{module}" "{self.manual}"\n'

    def __init__(self, modulename=None, section="9", manual="Kernel API Manual"):
        """
        Creates class variables.

        Not really mandatory, but it is a good coding style and makes
        pylint happy.
        """

        super().__init__()

        self._modulename = modulename
        self.section = section
        self.manual = manual

        self.symbols = []

        dt = None
        tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP")
        if tstamp:
            for fmt in self.date_formats:
                try:
                    dt = datetime.strptime(tstamp, fmt)
                    break
                except ValueError:
                    pass

        if not dt:
            dt = datetime.now()

        self.date = dt.strftime("%B %Y")

    def arg_name(self, args, name):
        """
        Return the name that will be used for the man page.

        As we may have the same name on different namespaces,
        prepend the data type for all types except functions and typedefs.

        The doc section is special: it uses the modulename.
        """

        dtype = args.type

        if dtype == "doc":
            return name
#            return os.path.basename(self.modulename(args))

        if dtype in ["function", "typedef"]:
            return name

        return f"{dtype} {name}"

    def set_symbols(self, symbols):
        """
        Get a list of all symbols from kernel_doc.

        Man pages will uses it to add a SEE ALSO section with other
        symbols at the same file.
        """
        self.symbols = symbols

    def out_tail(self, fname, name, args):
        """Adds a tail for all man pages."""

        # SEE ALSO section
        self.data += f'.SH "SEE ALSO"' + "\n.PP\n"
        self.data += (f"Kernel file \\fB{args.fname}\\fR\n")
        if len(self.symbols) >= 2:
            cur_name = self.arg_name(args, name)

            related = []
            for arg in self.symbols:
                out_name = self.arg_name(arg, arg.name)

                if cur_name == out_name:
                    continue

                related.append(f"\\fB{out_name}\\fR(9)")

            self.data += ",\n".join(related) + "\n"

        # TODO: does it make sense to add other sections? Maybe
        # REPORTING ISSUES? LICENSE?

    def msg(self, fname, name, args):
        """
        Handles a single entry from kernel-doc parser.

        Add a tail at the end of man pages output.
        """
        super().msg(fname, name, args)
        self.out_tail(fname, name, args)

        return self.data

    def emit_table(self, colspec_row, rows):

        if not rows:
            return ""

        out = ""
        colspec = "\t".join(["l"] * len(rows[0]))

        out += "\n.TS\n"
        out += "box;\n"
        out += f"{colspec}.\n"

        if colspec_row:
            out_row = []

            for text in colspec_row:
                out_row.append(f"\\fB{text}\\fP")

            out += "\t".join(out_row) + "\n_\n"

        for r in rows:
            out += "\t".join(r) + "\n"

        out += ".TE\n"

        return out

    def grid_table(self, lines, start):
        """
        Ancillary function to help handling a grid table inside the text.
        """

        i = start + 1
        rows = []
        colspec_row = None

        while i < len(lines):
            line = lines[i]

            if KernRe(r"^\s*\|.*\|\s*$").match(line):
                parts = []

                for p in line.strip('|').split('|'):
                    parts.append(p.strip())

                rows.append(parts)

            elif KernRe(r'^\+\=[\+\=]+\+\s*$').match(line):
                if rows and rows[0]:
                    if not colspec_row:
                        colspec_row = [""] * len(rows[0])

                    for j in range(0, len(rows[0])):
                        content = []
                        for row in rows:
                            content.append(row[j])

                        colspec_row[j] = " ".join(content)

                    rows = []

            elif KernRe(r"^\s*\+[-+]+\+.*$").match(line):
                pass

            else:
                break

            i += 1

        return i, self.emit_table(colspec_row, rows)

    def simple_table(self, lines, start):
        """
        Ancillary function to help handling a simple table inside the text.
        """

        i = start
        rows = []
        colspec_row = None

        pos = []
        for m in KernRe(r'\=+').finditer(lines[i]):
            pos.append((m.start(), m.end() - 1))

        i += 1
        while i < len(lines):
            line = lines[i]

            if KernRe(r"^\s*[\=]+[ \t\=]+$").match(line):
                i += 1
                break

            elif KernRe(r'^[\s=]+$').match(line):
                if rows and rows[0]:
                    if not colspec_row:
                        colspec_row = [""] * len(rows[0])

                    for j in range(0, len(rows[0])):
                        content = []
                        for row in rows:
                            content.append(row[j])

                        colspec_row[j] = " ".join(content)

                    rows = []

            else:
                row = [""] * len(pos)

                for j in range(0, len(pos)):
                    start, end = pos[j]

                    row[j] = line[start:end].strip()

                rows.append(row)

            i += 1

        return i, self.emit_table(colspec_row, rows)

    def code_block(self, lines, start):
        """
        Ensure that code blocks won't be messed up at the output.

        By default, troff join lines at the same paragraph. Disable it,
        on code blocks.
        """

        line = lines[start]

        if "code-block" in line:
            out = "\n.nf\n"
        elif line.startswith("..") and line.endswith("::"):
            #
            # Handle note, warning, error, ... markups
            #
            line = line[2:-1].strip().upper()
            out = f"\n.nf\n\\fB{line}\\fP\n"
        elif line.endswith("::"):
            out = line[:-1]
            out += "\n.nf\n"
        else:
            # Just in case. Should never happen in practice
            out = "\n.nf\n"

        i = start + 1
        ident = None

        while i < len(lines):
            line = lines[i]

            m = KernRe(r"\S").match(line)
            if not m:
                out += line + "\n"
                i += 1
                continue

            pos = m.start()
            if not ident:
                if pos > 0:
                    ident = pos
                else:
                    out += "\n.fi\n"
                    if i > start + 1:
                        return i - 1, out
                    else:
                        # Just in case. Should never happen in practice
                        return i, out

            if pos >= ident:
                out += line + "\n"
                i += 1
                continue

            break

        out += "\n.fi\n"
        return i, out

    def output_highlight(self, block):
        """
        Outputs a C symbol that may require being highlighted with
        self.highlights variable using troff syntax.
        """

        contents = self.highlight_block(block)

        if isinstance(contents, list):
            contents = "\n".join(contents)

        lines = contents.strip("\n").split("\n")
        i = 0

        while i < len(lines):
            org_line = lines[i]

            line = KernRe(r"^\s*").sub("", org_line)

            if line:
                if KernRe(r"^\+\-[-+]+\+.*$").match(line):
                    i, text = self.grid_table(lines, i)
                    self.data += text
                    continue

                if KernRe(r"^\=+[ \t]\=[ \t\=]+$").match(line):
                    i, text = self.simple_table(lines, i)
                    self.data += text
                    continue

                if line.endswith("::") or KernRe(r"\.\.\s+code-block.*::").match(line):
                    i, text = self.code_block(lines, i)
                    self.data += text
                    continue

                if line[0] == ".":
                    self.data += "\\&" + line + "\n"
                    i += 1
                    continue

                #
                # Handle lists
                #
                line = KernRe(r'^[-*]\s+').sub(r'.IP \[bu]\n', line)
                line = KernRe(r'^(\d+|a-z)[\.\)]\s+').sub(r'.IP \1\n', line)
            else:
                line = ".PP\n"

            i += 1

            self.data += line + "\n"

    def out_doc(self, fname, name, args):
        if not self.check_doc(name, args):
            return

        out_name = self.arg_name(args, name)

        self.emit_th(out_name, args)

        for section, text in args.sections.items():
            self.data += f'.SH "{section}"' + "\n"
            self.output_highlight(text)

    def out_function(self, fname, name, args):

        out_name = self.arg_name(args, name)

        self.emit_th(out_name, args)

        self.data += ".SH NAME\n"
        self.data += f"{name} \\- {args['purpose']}\n"

        self.data += ".SH SYNOPSIS\n"
        if args.get('functiontype', ''):
            self.data += f'.B "{args["functiontype"]}" {name}' + "\n"
        else:
            self.data += f'.B "{name}' + "\n"

        count = 0
        parenth = "("
        post = ","

        for parameter in args.parameterlist:
            if count == len(args.parameterlist) - 1:
                post = ");"

            dtype = args.parametertypes.get(parameter, "")
            if function_pointer.match(dtype):
                # Pointer-to-function
                self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n"
            else:
                dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype)

                self.data += f'.BI "{parenth}{dtype}"  "{post}"' + "\n"
            count += 1
            parenth = ""

        if args.parameterlist:
            self.data += ".SH ARGUMENTS\n"

        for parameter in args.parameterlist:
            parameter_name = re.sub(r'\[.*', '', parameter)

            self.data += f'.IP "{parameter}" 12' + "\n"
            self.output_highlight(args.parameterdescs.get(parameter_name, ""))

        for section, text in args.sections.items():
            self.data += f'.SH "{section.upper()}"' + "\n"
            self.output_highlight(text)

    def out_enum(self, fname, name, args):
        out_name = self.arg_name(args, name)

        self.emit_th(out_name, args)

        self.data += ".SH NAME\n"
        self.data += f"enum {name} \\- {args['purpose']}\n"

        self.data += ".SH SYNOPSIS\n"
        self.data += f"enum {name}" + " {\n"

        count = 0
        for parameter in args.parameterlist:
            self.data += f'.br\n.BI "    {parameter}"' + "\n"
            if count == len(args.parameterlist) - 1:
                self.data += "\n};\n"
            else:
                self.data += ", \n.br\n"

            count += 1

        self.data += ".SH Constants\n"

        for parameter in args.parameterlist:
            parameter_name = KernRe(r'\[.*').sub('', parameter)
            self.data += f'.IP "{parameter}" 12' + "\n"
            self.output_highlight(args.parameterdescs.get(parameter_name, ""))

        for section, text in args.sections.items():
            self.data += f'.SH "{section}"' + "\n"
            self.output_highlight(text)

    def out_var(self, fname, name, args):
        out_name = self.arg_name(args, name)
        full_proto = args.other_stuff["full_proto"]

        self.emit_th(out_name, args)

        self.data += ".SH NAME\n"
        self.data += f"{name} \\- {args['purpose']}\n"

        self.data += ".SH SYNOPSIS\n"
        self.data += f"{full_proto}\n"

        if args.other_stuff["default_val"]:
            self.data += f'.SH "Initialization"' + "\n"
            self.output_highlight(f'default: {args.other_stuff["default_val"]}')

        for section, text in args.sections.items():
            self.data += f'.SH "{section}"' + "\n"
            self.output_highlight(text)

    def out_typedef(self, fname, name, args):
        module = self.modulename(args)
        purpose = args.get('purpose')
        out_name = self.arg_name(args, name)

        self.emit_th(out_name, args)

        self.data += ".SH NAME\n"
        self.data += f"typedef {name} \\- {purpose}\n"

        for section, text in args.sections.items():
            self.data += f'.SH "{section}"' + "\n"
            self.output_highlight(text)

    def out_struct(self, fname, name, args):
        module = self.modulename(args)
        purpose = args.get('purpose')
        definition = args.get('definition')
        out_name = self.arg_name(args, name)

        self.emit_th(out_name, args)

        self.data += ".SH NAME\n"
        self.data += f"{args.type} {name} \\- {purpose}\n"

        # Replace tabs with two spaces and handle newlines
        declaration = definition.replace("\t", "  ")
        declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration)

        self.data += ".SH SYNOPSIS\n"
        self.data += f"{args.type} {name} " + "{" + "\n.br\n"
        self.data += f'.BI "{declaration}\n' + "};\n.br\n\n"

        self.data += ".SH Members\n"
        for parameter in args.parameterlist:
            if parameter.startswith("#"):
                continue

            parameter_name = re.sub(r"\[.*", "", parameter)

            if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:
                continue

            self.data += f'.IP "{parameter}" 12' + "\n"
            self.output_highlight(args.parameterdescs.get(parameter_name))

        for section, text in args.sections.items():
            self.data += f'.SH "{section}"' + "\n"
            self.output_highlight(text)
