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: Markus Armbruster <armbru@redhat.com>
Message-Id: <20190606153803.5278-8-armbru@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
[Commit message typo fixed]
This commit is contained in:
Markus Armbruster 2019-06-06 17:38:03 +02:00
parent c9d4070991
commit 157dd36395
1 changed files with 68 additions and 57 deletions

View File

@ -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(':'):