Commit 157dd363 authored by Markus Armbruster's avatar Markus Armbruster
Browse files

qapi: Simplify how QAPIDoc implements its state machine



QAPIDoc uses a state machine to for processing of documentation lines.
Its state is encoded as an enum QAPIDoc._state (well, as enum-like
class actually, thanks to our infatuation with Python 2).

All we ever do with the state is calling the state's function to
process a line of documentation.  The enum values effectively serve as
handles for the functions.

Eliminate the rather wordy indirection: store the function to call in
QAPIDoc._append_line.  Update and improve comments.

Signed-off-by: default avatarMarkus Armbruster <armbru@redhat.com>
Message-Id: <20190606153803.5278-8-armbru@redhat.com>
Reviewed-by: default avatarKevin Wolf <kwolf@redhat.com>
[Commit message typo fixed]
parent c9d40709
Loading
Loading
Loading
Loading
+68 −57
Original line number Diff line number Diff line
@@ -102,6 +102,24 @@ class QAPISemError(QAPIError):


class QAPIDoc(object):
    """
    A documentation comment block, either expression or free-form

    Expression documentation blocks consist of

    * a body section: one line naming the expression, followed by an
      overview (any number of lines)

    * argument sections: a description of each argument (for commands
      and events) or member (for structs, unions and alternates)

    * features sections: a description of each feature flag

    * additional (non-argument) sections, possibly tagged

    Free-form documentation blocks consist only of a body section.
    """

    class Section(object):
        def __init__(self, name=None):
            # optional section name (argument/member or section name)
@@ -120,26 +138,6 @@ class QAPIDoc(object):
        def connect(self, member):
            self.member = member

    class DocPart:
        """
        Describes which part of the documentation we're parsing right now.

        Expression documentation blocks consist of
        * a BODY part: first line naming the expression, plus an
          optional overview
        * an ARGS part: description of each argument (for commands and
          events) or member (for structs, unions and alternates),
        * a FEATURES part: description of each feature,
        * a VARIOUS part: optional tagged sections.

        Free-form documentation blocks consist only of a BODY part.
        """
        # TODO Make it a subclass of Enum when Python 2 support is removed
        BODY = 1
        ARGS = 2
        FEATURES = 3
        VARIOUS = 4

    def __init__(self, parser, info):
        # self._parser is used to report errors with QAPIParseError.  The
        # resulting error position depends on the state of the parser.
@@ -156,7 +154,7 @@ class QAPIDoc(object):
        self.sections = []
        # the current section
        self._section = self.body
        self._part = QAPIDoc.DocPart.BODY
        self._append_line = self._append_body_line

    def has_section(self, name):
        """Return True if we have a section with this name."""
@@ -171,21 +169,10 @@ class QAPIDoc(object):

        The way that the line is dealt with depends on which part of
        the documentation we're parsing right now:

        BODY means that we're ready to process free-form text into
        self.body.  A symbol name is only allowed if no other text was
        parsed yet.  It is interpreted as the symbol name that
        describes the currently documented object.  On getting the
        second symbol name, we proceed to ARGS.

        ARGS means that we're parsing the arguments section.  Any
        symbol name is interpreted as an argument and an ArgSection is
        created for it.

        VARIOUS is the final part where free-form sections may appear.
        This includes named sections such as "Return:" as well as
        unnamed paragraphs.  Symbols are not allowed any more in this
        part.
        * The body section: ._append_line is ._append_body_line
        * An argument section: ._append_line is ._append_args_line
        * A features section: ._append_line is ._append_features_line
        * An additional section: ._append_line is ._append_various_line
        """
        line = line[1:]
        if not line:
@@ -195,17 +182,7 @@ class QAPIDoc(object):
        if line[0] != ' ':
            raise QAPIParseError(self._parser, "Missing space after #")
        line = line[1:]

        if self._part == QAPIDoc.DocPart.BODY:
            self._append_body_line(line)
        elif self._part == QAPIDoc.DocPart.ARGS:
            self._append_args_line(line)
        elif self._part == QAPIDoc.DocPart.FEATURES:
            self._append_features_line(line)
        elif self._part == QAPIDoc.DocPart.VARIOUS:
            self._append_various_line(line)
        else:
            assert False
        self._append_line(line)

    def end_comment(self):
        self._end_section()
@@ -219,6 +196,19 @@ class QAPIDoc(object):
                        'TODO:')

    def _append_body_line(self, line):
        """
        Process a line of documentation text in the body section.

        If this a symbol line and it is the section's first line, this
        is an expression documentation block for that symbol.

        If it's an expression documentation block, another symbol line
        begins the argument section for the argument named by it, and
        a section tag begins an additional section.  Start that
        section and append the line to it.

        Else, append the line to the current section.
        """
        name = line.split(' ', 1)[0]
        # FIXME not nice: things like '#  @foo:' and '# @foo: ' aren't
        # recognized, and get silently treated as ordinary text
@@ -230,38 +220,49 @@ class QAPIDoc(object):
            if not self.symbol:
                raise QAPIParseError(self._parser, "Invalid name")
        elif self.symbol:
            # We already know that we document some symbol
            # This is an expression documentation block
            if name.startswith('@') and name.endswith(':'):
                self._part = QAPIDoc.DocPart.ARGS
                self._append_line = self._append_args_line
                self._append_args_line(line)
            elif line == 'Features:':
                self._part = QAPIDoc.DocPart.FEATURES
                self._append_line = self._append_features_line
            elif self._is_section_tag(name):
                self._part = QAPIDoc.DocPart.VARIOUS
                self._append_line = self._append_various_line
                self._append_various_line(line)
            else:
                self._append_freeform(line.strip())
        else:
            # This is free-form documentation without a symbol
            # This is a free-form documentation block
            self._append_freeform(line.strip())

    def _append_args_line(self, line):
        """
        Process a line of documentation text in an argument section.

        A symbol line begins the next argument section, a section tag
        section or a non-indented line after a blank line begins an
        additional section.  Start that section and append the line to
        it.

        Else, append the line to the current section.

        """
        name = line.split(' ', 1)[0]

        if name.startswith('@') and name.endswith(':'):
            line = line[len(name)+1:]
            self._start_args_section(name[1:-1])
        elif self._is_section_tag(name):
            self._part = QAPIDoc.DocPart.VARIOUS
            self._append_line = self._append_various_line
            self._append_various_line(line)
            return
        elif (self._section.text.endswith('\n\n')
              and line and not line[0].isspace()):
            if line == 'Features:':
                self._part = QAPIDoc.DocPart.FEATURES
                self._append_line = self._append_features_line
            else:
                self._start_section()
                self._part = QAPIDoc.DocPart.VARIOUS
                self._append_line = self._append_various_line
                self._append_various_line(line)
            return

@@ -274,19 +275,29 @@ class QAPIDoc(object):
            line = line[len(name)+1:]
            self._start_features_section(name[1:-1])
        elif self._is_section_tag(name):
            self._part = QAPIDoc.DocPart.VARIOUS
            self._append_line = self._append_various_line
            self._append_various_line(line)
            return
        elif (self._section.text.endswith('\n\n')
              and line and not line[0].isspace()):
            self._start_section()
            self._part = QAPIDoc.DocPart.VARIOUS
            self._append_line = self._append_various_line
            self._append_various_line(line)
            return

        self._append_freeform(line.strip())

    def _append_various_line(self, line):
        """
        Process a line of documentation text in an additional section.

        A symbol line is an error.

        A section tag begins an additional section.  Start that
        section and append the line to it.

        Else, append the line to the current section.
        """
        name = line.split(' ', 1)[0]

        if name.startswith('@') and name.endswith(':'):