diff options
Diffstat (limited to 'scripts/lib/kdoc/kdoc_parser.py')
-rw-r--r--[-rwxr-xr-x] | scripts/lib/kdoc/kdoc_parser.py | 392 |
1 files changed, 211 insertions, 181 deletions
diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 4f036c720b36..062453eefc7a 100755..100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -12,7 +12,6 @@ Read a C language source or header FILE and extract embedded documentation comments """ -import argparse import re from pprint import pformat @@ -61,24 +60,22 @@ export_symbol_ns = KernRe(r'^\s*EXPORT_SYMBOL_NS(_GPL)?\s*\(\s*(\w+)\s*,\s*"\S+" type_param = KernRe(r"\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False) - -class KernelDoc: +class state: """ - Read a C language source or header FILE and extract embedded - documentation comments. + State machine enums """ # Parser states - STATE_NORMAL = 0 # normal code - STATE_NAME = 1 # looking for function name - STATE_BODY_MAYBE = 2 # body - or maybe more description - STATE_BODY = 3 # the body of the comment - STATE_BODY_WITH_BLANK_LINE = 4 # the body which has a blank line - STATE_PROTO = 5 # scanning prototype - STATE_DOCBLOCK = 6 # documentation block - STATE_INLINE = 7 # gathering doc outside main block - - st_name = [ + NORMAL = 0 # normal code + NAME = 1 # looking for function name + BODY_MAYBE = 2 # body - or maybe more description + BODY = 3 # the body of the comment + BODY_WITH_BLANK_LINE = 4 # the body which has a blank line + PROTO = 5 # scanning prototype + DOCBLOCK = 6 # documentation block + INLINE = 7 # gathering doc outside main block + + name = [ "NORMAL", "NAME", "BODY_MAYBE", @@ -90,15 +87,15 @@ class KernelDoc: ] # Inline documentation state - STATE_INLINE_NA = 0 # not applicable ($state != STATE_INLINE) - STATE_INLINE_NAME = 1 # looking for member name (@foo:) - STATE_INLINE_TEXT = 2 # looking for member documentation - STATE_INLINE_END = 3 # done - STATE_INLINE_ERROR = 4 # error - Comment without header was found. - # Spit a warning as it's not - # proper kernel-doc and ignore the rest. - - st_inline_name = [ + INLINE_NA = 0 # not applicable ($state != INLINE) + INLINE_NAME = 1 # looking for member name (@foo:) + INLINE_TEXT = 2 # looking for member documentation + INLINE_END = 3 # done + INLINE_ERROR = 4 # error - Comment without header was found. + # Spit a warning as it's not + # proper kernel-doc and ignore the rest. + + inline_name = [ "", "_NAME", "_TEXT", @@ -106,92 +103,149 @@ class KernelDoc: "_ERROR", ] - # Section names +SECTION_DEFAULT = "Description" # default section - section_default = "Description" # default section - section_intro = "Introduction" - section_context = "Context" - section_return = "Return" +class KernelEntry: - undescribed = "-- undescribed --" + def __init__(self, config, ln): + self.config = config - def __init__(self, config, fname): - """Initialize internal variables""" + self.contents = "" + self.function = "" + self.sectcheck = "" + self.struct_actual = "" + self.prototype = "" - self.fname = fname - self.config = config + self.warnings = [] - # Initial state for the state machines - self.state = self.STATE_NORMAL - self.inline_doc_state = self.STATE_INLINE_NA + self.parameterlist = [] + self.parameterdescs = {} + self.parametertypes = {} + self.parameterdesc_start_lines = {} - # Store entry currently being processed - self.entry = None + self.section_start_lines = {} + self.sectionlist = [] + self.sections = {} - # Place all potential outputs into an array - self.entries = [] + self.anon_struct_union = False + + self.leading_space = None + + # State flags + self.brcount = 0 + + self.in_doc_sect = False + self.declaration_start_line = ln + 1 # TODO: rename to emit_message after removal of kernel-doc.pl - def emit_warning(self, ln, msg, warning=True): + def emit_msg(self, log_msg, warning=True): """Emit a message""" - log_msg = f"{self.fname}:{ln} {msg}" - if not warning: self.config.log.info(log_msg) return - if self.entry: - # Delegate warning output to output logic, as this way it - # will report warnings/info only for symbols that are output + # Delegate warning output to output logic, as this way it + # will report warnings/info only for symbols that are output - self.entry.warnings.append(log_msg) - return - - self.config.log.warning(log_msg) + self.warnings.append(log_msg) + return def dump_section(self, start_new=True): """ Dumps section contents to arrays/hashes intended for that purpose. """ - name = self.entry.section - contents = self.entry.contents + name = self.section + contents = self.contents if type_param.match(name): name = type_param.group(1) - self.entry.parameterdescs[name] = contents - self.entry.parameterdesc_start_lines[name] = self.entry.new_start_line + self.parameterdescs[name] = contents + self.parameterdesc_start_lines[name] = self.new_start_line - self.entry.sectcheck += name + " " - self.entry.new_start_line = 0 + self.sectcheck += name + " " + self.new_start_line = 0 elif name == "@...": name = "..." - self.entry.parameterdescs[name] = contents - self.entry.sectcheck += name + " " - self.entry.parameterdesc_start_lines[name] = self.entry.new_start_line - self.entry.new_start_line = 0 + self.parameterdescs[name] = contents + self.sectcheck += name + " " + self.parameterdesc_start_lines[name] = self.new_start_line + self.new_start_line = 0 else: - if name in self.entry.sections and self.entry.sections[name] != "": + if name in self.sections and self.sections[name] != "": # Only warn on user-specified duplicate section names - if name != self.section_default: - self.emit_warning(self.entry.new_start_line, - f"duplicate section name '{name}'\n") - self.entry.sections[name] += contents + if name != SECTION_DEFAULT: + self.emit_msg(self.new_start_line, + f"duplicate section name '{name}'\n") + self.sections[name] += contents else: - self.entry.sections[name] = contents - self.entry.sectionlist.append(name) - self.entry.section_start_lines[name] = self.entry.new_start_line - self.entry.new_start_line = 0 + self.sections[name] = contents + self.sectionlist.append(name) + self.section_start_lines[name] = self.new_start_line + self.new_start_line = 0 -# self.config.log.debug("Section: %s : %s", name, pformat(vars(self.entry))) +# self.config.log.debug("Section: %s : %s", name, pformat(vars(self))) if start_new: - self.entry.section = self.section_default - self.entry.contents = "" + self.section = SECTION_DEFAULT + self.contents = "" + + +class KernelDoc: + """ + Read a C language source or header FILE and extract embedded + documentation comments. + """ + + # Section names + + section_intro = "Introduction" + section_context = "Context" + section_return = "Return" + + undescribed = "-- undescribed --" + + def __init__(self, config, fname): + """Initialize internal variables""" + + self.fname = fname + self.config = config + + # Initial state for the state machines + self.state = state.NORMAL + self.inline_doc_state = state.INLINE_NA + + # Store entry currently being processed + self.entry = None + + # Place all potential outputs into an array + self.entries = [] + + def emit_msg(self, ln, msg, warning=True): + """Emit a message""" + + log_msg = f"{self.fname}:{ln} {msg}" + + if self.entry: + self.entry.emit_msg(log_msg, warning) + return + + if warning: + self.config.log.warning(log_msg) + else: + self.config.log.info(log_msg) + + def dump_section(self, start_new=True): + """ + Dumps section contents to arrays/hashes intended for that purpose. + """ + + if self.entry: + self.entry.dump_section(start_new) # TODO: rename it to store_declaration after removal of kernel-doc.pl def output_declaration(self, dtype, name, **args): @@ -236,36 +290,11 @@ class KernelDoc: variables used by the state machine. """ - self.entry = argparse.Namespace - - self.entry.contents = "" - self.entry.function = "" - self.entry.sectcheck = "" - self.entry.struct_actual = "" - self.entry.prototype = "" - - self.entry.warnings = [] - - self.entry.parameterlist = [] - self.entry.parameterdescs = {} - self.entry.parametertypes = {} - self.entry.parameterdesc_start_lines = {} - - self.entry.section_start_lines = {} - self.entry.sectionlist = [] - self.entry.sections = {} - - self.entry.anon_struct_union = False - - self.entry.leading_space = None + self.entry = KernelEntry(self.config, ln) # State flags - self.state = self.STATE_NORMAL - self.inline_doc_state = self.STATE_INLINE_NA - self.entry.brcount = 0 - - self.entry.in_doc_sect = False - self.entry.declaration_start_line = ln + 1 + self.state = state.NORMAL + self.inline_doc_state = state.INLINE_NA def push_parameter(self, ln, decl_type, param, dtype, org_arg, declaration_name): @@ -323,8 +352,8 @@ class KernelDoc: else: dname = f"{decl_type} member" - self.emit_warning(ln, - f"{dname} '{param}' not described in '{declaration_name}'") + self.emit_msg(ln, + f"{dname} '{param}' not described in '{declaration_name}'") # Strip spaces from param so that it is one continuous string on # parameterlist. This fixes a problem where check_sections() @@ -388,7 +417,7 @@ class KernelDoc: if r.match(arg): param = r.group(1) else: - self.emit_warning(ln, f"Invalid param: {arg}") + self.emit_msg(ln, f"Invalid param: {arg}") param = arg dtype = KernRe(r'([^\(]+\(\*?)\s*' + re.escape(param)).sub(r'\1', arg) @@ -404,7 +433,7 @@ class KernelDoc: if r.match(arg): param = r.group(1) else: - self.emit_warning(ln, f"Invalid param: {arg}") + self.emit_msg(ln, f"Invalid param: {arg}") param = arg dtype = KernRe(r'([^\(]+\(\*?)\s*' + re.escape(param)).sub(r'\1', arg) @@ -437,7 +466,7 @@ class KernelDoc: if KernRe(r'^(\*+)\s*(.*)').match(param): r = KernRe(r'^(\*+)\s*(.*)') if not r.match(param): - self.emit_warning(ln, f"Invalid param: {param}") + self.emit_msg(ln, f"Invalid param: {param}") continue param = r.group(1) @@ -450,7 +479,7 @@ class KernelDoc: elif KernRe(r'(.*?):(\w+)').search(param): r = KernRe(r'(.*?):(\w+)') if not r.match(param): - self.emit_warning(ln, f"Invalid param: {param}") + self.emit_msg(ln, f"Invalid param: {param}") continue if dtype != "": # Skip unnamed bit-fields @@ -498,8 +527,8 @@ class KernelDoc: else: dname = f"{decl_type} member" - self.emit_warning(ln, - f"Excess {dname} '{sects[sx]}' description in '{decl_name}'") + self.emit_msg(ln, + f"Excess {dname} '{sects[sx]}' description in '{decl_name}'") def check_return_section(self, ln, declaration_name, return_type): """ @@ -516,8 +545,8 @@ class KernelDoc: return if not self.entry.sections.get("Return", None): - self.emit_warning(ln, - f"No description found for return value of '{declaration_name}'") + self.emit_msg(ln, + f"No description found for return value of '{declaration_name}'") def dump_struct(self, ln, proto): """ @@ -556,12 +585,12 @@ class KernelDoc: members = r.group(2) if not members: - self.emit_warning(ln, f"{proto} error: Cannot parse struct or union!") + self.emit_msg(ln, f"{proto} error: Cannot parse struct or union!") return if self.entry.identifier != declaration_name: - self.emit_warning(ln, - f"expecting prototype for {decl_type} {self.entry.identifier}. Prototype was for {decl_type} {declaration_name} instead\n") + self.emit_msg(ln, + f"expecting prototype for {decl_type} {self.entry.identifier}. Prototype was for {decl_type} {declaration_name} instead\n") return args_pattern = r'([^,)]+)' @@ -830,16 +859,16 @@ class KernelDoc: members = r.group(2).rstrip() if not members: - self.emit_warning(ln, f"{proto}: error: Cannot parse enum!") + self.emit_msg(ln, f"{proto}: error: Cannot parse enum!") return if self.entry.identifier != declaration_name: if self.entry.identifier == "": - self.emit_warning(ln, - f"{proto}: wrong kernel-doc identifier on prototype") + self.emit_msg(ln, + f"{proto}: wrong kernel-doc identifier on prototype") else: - self.emit_warning(ln, - f"expecting prototype for enum {self.entry.identifier}. Prototype was for enum {declaration_name} instead") + self.emit_msg(ln, + f"expecting prototype for enum {self.entry.identifier}. Prototype was for enum {declaration_name} instead") return if not declaration_name: @@ -856,14 +885,14 @@ class KernelDoc: self.entry.parameterlist.append(arg) if arg not in self.entry.parameterdescs: self.entry.parameterdescs[arg] = self.undescribed - self.emit_warning(ln, - f"Enum value '{arg}' not described in enum '{declaration_name}'") + self.emit_msg(ln, + f"Enum value '{arg}' not described in enum '{declaration_name}'") member_set.add(arg) for k in self.entry.parameterdescs: if k not in member_set: - self.emit_warning(ln, - f"Excess enum value '%{k}' description in '{declaration_name}'") + self.emit_msg(ln, + f"Excess enum value '%{k}' description in '{declaration_name}'") self.output_declaration('enum', declaration_name, enum=declaration_name, @@ -1018,13 +1047,13 @@ class KernelDoc: found = True break if not found: - self.emit_warning(ln, - f"cannot understand function prototype: '{prototype}'") + self.emit_msg(ln, + f"cannot understand function prototype: '{prototype}'") return if self.entry.identifier != declaration_name: - self.emit_warning(ln, - f"expecting prototype for {self.entry.identifier}(). Prototype was for {declaration_name}() instead") + self.emit_msg(ln, + f"expecting prototype for {self.entry.identifier}(). Prototype was for {declaration_name}() instead") return prms = " ".join(self.entry.parameterlist) @@ -1087,8 +1116,8 @@ class KernelDoc: args = r.group(3) if self.entry.identifier != declaration_name: - self.emit_warning(ln, - f"expecting prototype for typedef {self.entry.identifier}. Prototype was for typedef {declaration_name} instead\n") + self.emit_msg(ln, + f"expecting prototype for typedef {self.entry.identifier}. Prototype was for typedef {declaration_name} instead\n") return decl_type = 'function' @@ -1119,7 +1148,8 @@ class KernelDoc: declaration_name = r.group(1) if self.entry.identifier != declaration_name: - self.emit_warning(ln, f"expecting prototype for typedef {self.entry.identifier}. Prototype was for typedef {declaration_name} instead\n") + self.emit_msg(ln, + f"expecting prototype for typedef {self.entry.identifier}. Prototype was for typedef {declaration_name} instead\n") return self.output_declaration('typedef', declaration_name, @@ -1130,7 +1160,7 @@ class KernelDoc: purpose=self.entry.declaration_purpose) return - self.emit_warning(ln, "error: Cannot parse typedef!") + self.emit_msg(ln, "error: Cannot parse typedef!") @staticmethod def process_export(function_set, line): @@ -1166,7 +1196,7 @@ class KernelDoc: self.entry.in_doc_sect = False # next line is always the function name - self.state = self.STATE_NAME + self.state = state.NAME def process_name(self, ln, line): """ @@ -1182,7 +1212,7 @@ class KernelDoc: self.entry.section = doc_block.group(1) self.entry.identifier = self.entry.section - self.state = self.STATE_DOCBLOCK + self.state = state.DOCBLOCK return if doc_decl.search(line): @@ -1224,10 +1254,10 @@ class KernelDoc: self.entry.identifier = self.entry.identifier.strip(" ") - self.state = self.STATE_BODY + self.state = state.BODY # if there's no @param blocks need to set up default section here - self.entry.section = self.section_default + self.entry.section = SECTION_DEFAULT self.entry.new_start_line = ln + 1 r = KernRe("[-:](.*)") @@ -1238,44 +1268,44 @@ class KernelDoc: r = KernRe(r"\s+") self.entry.descr = r.sub(" ", self.entry.descr) self.entry.declaration_purpose = self.entry.descr - self.state = self.STATE_BODY_MAYBE + self.state = state.BODY_MAYBE else: self.entry.declaration_purpose = "" if not self.entry.is_kernel_comment: - self.emit_warning(ln, - f"This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst\n{line}") - self.state = self.STATE_NORMAL + self.emit_msg(ln, + f"This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst\n{line}") + self.state = state.NORMAL if not self.entry.declaration_purpose and self.config.wshort_desc: - self.emit_warning(ln, - f"missing initial short description on line:\n{line}") + self.emit_msg(ln, + f"missing initial short description on line:\n{line}") if not self.entry.identifier and self.entry.decl_type != "enum": - self.emit_warning(ln, - f"wrong kernel-doc identifier on line:\n{line}") - self.state = self.STATE_NORMAL + self.emit_msg(ln, + f"wrong kernel-doc identifier on line:\n{line}") + self.state = state.NORMAL if self.config.verbose: - self.emit_warning(ln, - f"Scanning doc for {self.entry.decl_type} {self.entry.identifier}", + self.emit_msg(ln, + f"Scanning doc for {self.entry.decl_type} {self.entry.identifier}", warning=False) return # Failed to find an identifier. Emit a warning - self.emit_warning(ln, f"Cannot find identifier on line:\n{line}") + self.emit_msg(ln, f"Cannot find identifier on line:\n{line}") def process_body(self, ln, line): """ STATE_BODY and STATE_BODY_MAYBE: the bulk of a kerneldoc comment. """ - if self.state == self.STATE_BODY_WITH_BLANK_LINE: + if self.state == state.BODY_WITH_BLANK_LINE: r = KernRe(r"\s*\*\s?\S") if r.match(line): self.dump_section() - self.entry.section = self.section_default + self.entry.section = SECTION_DEFAULT self.entry.new_start_line = ln self.entry.contents = "" @@ -1311,7 +1341,7 @@ class KernelDoc: if self.entry.contents: self.entry.contents += "\n" - self.state = self.STATE_BODY + self.state = state.BODY return if doc_end.search(line): @@ -1320,12 +1350,12 @@ class KernelDoc: # Look for doc_com + <text> + doc_end: r = KernRe(r'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') if r.match(line): - self.emit_warning(ln, f"suspicious ending line: {line}") + self.emit_msg(ln, f"suspicious ending line: {line}") self.entry.prototype = "" self.entry.new_start_line = ln + 1 - self.state = self.STATE_PROTO + self.state = state.PROTO return if doc_content.search(line): @@ -1336,16 +1366,16 @@ class KernelDoc: self.dump_section() self.entry.new_start_line = ln - self.state = self.STATE_BODY + self.state = state.BODY else: - if self.entry.section != self.section_default: - self.state = self.STATE_BODY_WITH_BLANK_LINE + if self.entry.section != SECTION_DEFAULT: + self.state = state.BODY_WITH_BLANK_LINE else: - self.state = self.STATE_BODY + self.state = state.BODY self.entry.contents += "\n" - elif self.state == self.STATE_BODY_MAYBE: + elif self.state == state.BODY_MAYBE: # Continued declaration purpose self.entry.declaration_purpose = self.entry.declaration_purpose.rstrip() @@ -1383,12 +1413,12 @@ class KernelDoc: return # Unknown line, ignore - self.emit_warning(ln, f"bad line: {line}") + self.emit_msg(ln, f"bad line: {line}") def process_inline(self, ln, line): """STATE_INLINE: docbook comments within a prototype.""" - if self.inline_doc_state == self.STATE_INLINE_NAME and \ + if self.inline_doc_state == state.INLINE_NAME and \ doc_inline_sect.search(line): self.entry.section = doc_inline_sect.group(1) self.entry.new_start_line = ln @@ -1397,7 +1427,7 @@ class KernelDoc: if self.entry.contents != "": self.entry.contents += "\n" - self.inline_doc_state = self.STATE_INLINE_TEXT + self.inline_doc_state = state.INLINE_TEXT # Documentation block end */ return @@ -1405,21 +1435,21 @@ class KernelDoc: if self.entry.contents not in ["", "\n"]: self.dump_section() - self.state = self.STATE_PROTO - self.inline_doc_state = self.STATE_INLINE_NA + self.state = state.PROTO + self.inline_doc_state = state.INLINE_NA return if doc_content.search(line): - if self.inline_doc_state == self.STATE_INLINE_TEXT: + if self.inline_doc_state == state.INLINE_TEXT: self.entry.contents += doc_content.group(1) + "\n" if not self.entry.contents.strip(" ").rstrip("\n"): self.entry.contents = "" - elif self.inline_doc_state == self.STATE_INLINE_NAME: - self.emit_warning(ln, - f"Incorrect use of kernel-doc format: {line}") + elif self.inline_doc_state == state.INLINE_NAME: + self.emit_msg(ln, + f"Incorrect use of kernel-doc format: {line}") - self.inline_doc_state = self.STATE_INLINE_ERROR + self.inline_doc_state = state.INLINE_ERROR def syscall_munge(self, ln, proto): # pylint: disable=W0613 """ @@ -1489,8 +1519,8 @@ class KernelDoc: tracepointargs = r.group(1) if not tracepointname or not tracepointargs: - self.emit_warning(ln, - f"Unrecognized tracepoint format:\n{proto}\n") + self.emit_msg(ln, + f"Unrecognized tracepoint format:\n{proto}\n") else: proto = f"static inline void trace_{tracepointname}({tracepointargs})" self.entry.identifier = f"trace_{self.entry.identifier}" @@ -1598,8 +1628,8 @@ class KernelDoc: self.dump_section(start_new=False) elif doc_inline_start.search(line): - self.state = self.STATE_INLINE - self.inline_doc_state = self.STATE_INLINE_NAME + self.state = state.INLINE + self.inline_doc_state = state.INLINE_NAME elif self.entry.decl_type == 'function': self.process_proto_function(ln, line) @@ -1663,7 +1693,7 @@ class KernelDoc: line = line.expandtabs().strip("\n") # Group continuation lines on prototypes - if self.state == self.STATE_PROTO: + if self.state == state.PROTO: if line.endswith("\\"): prev += line.rstrip("\\") cont = True @@ -1681,8 +1711,8 @@ class KernelDoc: prev_ln = None self.config.log.debug("%d %s%s: %s", - ln, self.st_name[self.state], - self.st_inline_name[self.inline_doc_state], + ln, state.name[self.state], + state.inline_name[self.inline_doc_state], line) # This is an optimization over the original script. @@ -1696,18 +1726,18 @@ class KernelDoc: self.process_export(export_table, line) # Hand this line to the appropriate state handler - if self.state == self.STATE_NORMAL: + if self.state == state.NORMAL: self.process_normal(ln, line) - elif self.state == self.STATE_NAME: + elif self.state == state.NAME: self.process_name(ln, line) - elif self.state in [self.STATE_BODY, self.STATE_BODY_MAYBE, - self.STATE_BODY_WITH_BLANK_LINE]: + elif self.state in [state.BODY, state.BODY_MAYBE, + state.BODY_WITH_BLANK_LINE]: self.process_body(ln, line) - elif self.state == self.STATE_INLINE: # scanning for inline parameters + elif self.state == state.INLINE: # scanning for inline parameters self.process_inline(ln, line) - elif self.state == self.STATE_PROTO: + elif self.state == state.PROTO: self.process_proto(ln, line) - elif self.state == self.STATE_DOCBLOCK: + elif self.state == state.DOCBLOCK: self.process_docblock(ln, line) except OSError: self.config.log.error(f"Error: Cannot open file {self.fname}") |