diff options
Diffstat (limited to 'scripts/qapi/parser.py')
-rw-r--r-- | scripts/qapi/parser.py | 141 |
1 files changed, 51 insertions, 90 deletions
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py index 878f90b458..4923a59d60 100644 --- a/scripts/qapi/parser.py +++ b/scripts/qapi/parser.py @@ -346,7 +346,7 @@ class QAPISchemaParser: elif not self.tok.isspace(): # Show up to next structural, whitespace or quote # character - match = must_match('[^[\\]{}:,\\s\'"]+', + match = must_match('[^[\\]{}:,\\s\']+', self.src[self.cursor-1:]) raise QAPIParseError(self, "stray '%s'" % match.group(0)) @@ -468,34 +468,39 @@ class QAPIDoc: class Section: # pylint: disable=too-few-public-methods def __init__(self, parser: QAPISchemaParser, - name: Optional[str] = None, indent: int = 0): - + name: Optional[str] = None): # parser, for error messages about indentation self._parser = parser # optional section name (argument/member or section name) self.name = name + # section text without section name self.text = '' - # the expected indent level of the text of this section - self._indent = indent + # indentation to strip (None means indeterminate) + self._indent = None if self.name else 0 def append(self, line: str) -> None: - # Strip leading spaces corresponding to the expected indent level - # Blank lines are always OK. + line = line.rstrip() + if line: indent = must_match(r'\s*', line).end() - if indent < self._indent: + if self._indent is None: + # indeterminate indentation + if self.text != '': + # non-blank, non-first line determines indentation + self._indent = indent + elif indent < self._indent: raise QAPIParseError( self._parser, "unexpected de-indent (expected at least %d spaces)" % self._indent) line = line[self._indent:] - self.text += line.rstrip() + '\n' + self.text += line + '\n' class ArgSection(Section): def __init__(self, parser: QAPISchemaParser, - name: str, indent: int = 0): - super().__init__(parser, name, indent) + name: str): + super().__init__(parser, name) self.member: Optional['QAPISchemaMember'] = None def connect(self, member: 'QAPISchemaMember') -> None: @@ -558,12 +563,12 @@ class QAPIDoc: self._switch_section(QAPIDoc.NullSection(self._parser)) @staticmethod - def _is_section_tag(name: str) -> bool: - return name in ('Returns:', 'Since:', - # those are often singular or plural - 'Note:', 'Notes:', - 'Example:', 'Examples:', - 'TODO:') + def _match_at_name_colon(string: str) -> re.Match: + return re.match(r'@([^:]*): *', string) + + @staticmethod + def _match_section_tag(string: str) -> re.Match: + return re.match(r'(Returns|Since|Notes?|Examples?|TODO): *', string) def _append_body_line(self, line: str) -> None: """ @@ -579,7 +584,6 @@ class QAPIDoc: 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 if not self.symbol and not self.body.text and line.startswith('@'): @@ -593,12 +597,12 @@ class QAPIDoc: self._parser, "name required after '@'") elif self.symbol: # This is a definition documentation block - if name.startswith('@') and name.endswith(':'): + if self._match_at_name_colon(line): self._append_line = self._append_args_line self._append_args_line(line) elif line == 'Features:': self._append_line = self._append_features_line - elif self._is_section_tag(name): + elif self._match_section_tag(line): self._append_line = self._append_various_line self._append_various_line(line) else: @@ -619,25 +623,11 @@ class QAPIDoc: Else, append the line to the current section. """ - name = line.split(' ', 1)[0] - - if name.startswith('@') and name.endswith(':'): - # If line is "@arg: first line of description", find - # the index of 'f', which is the indent we expect for any - # following lines. We then remove the leading "@arg:" - # from line and replace it with spaces so that 'f' has the - # same index as it did in the original line and can be - # handled the same way we will handle following lines. - indent = must_match(r'@\S*:\s*', line).end() - line = line[indent:] - if not line: - # Line was just the "@arg:" header; following lines - # are not indented - indent = 0 - else: - line = ' ' * indent + line - self._start_args_section(name[1:-1], indent) - elif self._is_section_tag(name): + match = self._match_at_name_colon(line) + if match: + line = line[match.end():] + self._start_args_section(match.group(1)) + elif self._match_section_tag(line): self._append_line = self._append_various_line self._append_various_line(line) return @@ -654,25 +644,11 @@ class QAPIDoc: self._append_freeform(line) def _append_features_line(self, line: str) -> None: - name = line.split(' ', 1)[0] - - if name.startswith('@') and name.endswith(':'): - # If line is "@arg: first line of description", find - # the index of 'f', which is the indent we expect for any - # following lines. We then remove the leading "@arg:" - # from line and replace it with spaces so that 'f' has the - # same index as it did in the original line and can be - # handled the same way we will handle following lines. - indent = must_match(r'@\S*:\s*', line).end() - line = line[indent:] - if not line: - # Line was just the "@arg:" header; following lines - # are not indented - indent = 0 - else: - line = ' ' * indent + line - self._start_features_section(name[1:-1], indent) - elif self._is_section_tag(name): + match = self._match_at_name_colon(line) + if match: + line = line[match.end():] + self._start_features_section(match.group(1)) + elif self._match_section_tag(line): self._append_line = self._append_various_line self._append_various_line(line) return @@ -696,36 +672,22 @@ class QAPIDoc: Else, append the line to the current section. """ - name = line.split(' ', 1)[0] - - if name.startswith('@') and name.endswith(':'): + match = self._match_at_name_colon(line) + if match: raise QAPIParseError(self._parser, - "'%s' can't follow '%s' section" - % (name, self.sections[0].name)) - if self._is_section_tag(name): - # If line is "Section: first line of description", find - # the index of 'f', which is the indent we expect for any - # following lines. We then remove the leading "Section:" - # from line and replace it with spaces so that 'f' has the - # same index as it did in the original line and can be - # handled the same way we will handle following lines. - indent = must_match(r'\S*:\s*', line).end() - line = line[indent:] - if not line: - # Line was just the "Section:" header; following lines - # are not indented - indent = 0 - else: - line = ' ' * indent + line - self._start_section(name[:-1], indent) + "'@%s:' can't follow '%s' section" + % (match.group(1), self.sections[0].name)) + match = self._match_section_tag(line) + if match: + line = line[match.end():] + self._start_section(match.group(1)) self._append_freeform(line) def _start_symbol_section( self, symbols_dict: Dict[str, 'QAPIDoc.ArgSection'], - name: str, - indent: int) -> None: + name: str) -> None: # FIXME invalid names other than the empty string aren't flagged if not name: raise QAPIParseError(self._parser, "invalid parameter name") @@ -733,27 +695,26 @@ class QAPIDoc: raise QAPIParseError(self._parser, "'%s' parameter name duplicated" % name) assert not self.sections - new_section = QAPIDoc.ArgSection(self._parser, name, indent) + new_section = QAPIDoc.ArgSection(self._parser, name) self._switch_section(new_section) symbols_dict[name] = new_section - def _start_args_section(self, name: str, indent: int) -> None: - self._start_symbol_section(self.args, name, indent) + def _start_args_section(self, name: str) -> None: + self._start_symbol_section(self.args, name) - def _start_features_section(self, name: str, indent: int) -> None: - self._start_symbol_section(self.features, name, indent) + def _start_features_section(self, name: str) -> None: + self._start_symbol_section(self.features, name) - def _start_section(self, name: Optional[str] = None, - indent: int = 0) -> None: + def _start_section(self, name: Optional[str] = None) -> None: if name in ('Returns', 'Since') and self.has_section(name): raise QAPIParseError(self._parser, "duplicated '%s' section" % name) - new_section = QAPIDoc.Section(self._parser, name, indent) + new_section = QAPIDoc.Section(self._parser, name) self._switch_section(new_section) self.sections.append(new_section) def _switch_section(self, new_section: 'QAPIDoc.Section') -> None: - text = self._section.text = self._section.text.strip() + text = self._section.text = self._section.text.strip('\n') # Only the 'body' section is allowed to have an empty body. # All other sections, including anonymous ones, must have text. |