Subversion Repositories Kolibri OS

Rev

Rev 8855 | Rev 8957 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. import re
  2. import os
  3. import argparse
  4.  
  5. # Parameters
  6. # Path to doxygen folder to make doxygen files in: -o <path>
  7. doxygen_src_path = 'docs/doxygen'
  8. # Remove generated doxygen files: --clean
  9. clean_generated_stuff = False
  10. # Dump all defined symbols: --dump
  11. dump_symbols = False
  12. # Print symbol stats: --stats
  13. print_stats = False
  14. # Do not write warnings file: --nowarn
  15. enable_warnings = True
  16.  
  17. # Constants
  18. link_root = "http://websvn.kolibrios.org/filedetails.php?repname=Kolibri+OS&path=/kernel/trunk"
  19.  
  20. # Warning list
  21. warnings = ""
  22.  
  23. # Parse arguments
  24. parser = argparse.ArgumentParser()
  25. parser.add_argument("-o", help="Doxygen output folder")
  26. parser.add_argument("--clean", help="Remove generated files", action="store_true")
  27. parser.add_argument("--dump", help="Dump all defined symbols", action="store_true")
  28. parser.add_argument("--stats", help="Print symbol stats", action="store_true")
  29. parser.add_argument("--nowarn", help="Do not write warnings file", action="store_true")
  30. args = parser.parse_args()
  31. doxygen_src_path = args.o if args.o else 'docs/doxygen'
  32. clean_generated_stuff = args.clean
  33. dump_symbols = args.dump
  34. print_stats = args.stats
  35. enable_warnings = not args.nowarn
  36.  
  37. # kernel_structure["filename"] = {
  38. #   [ [],    # [0] Variables - [ line, name ]
  39. #     [],    # [1] Macros - [ line, name ]
  40. #     [],    # [2] Procedures - [ line, name ]
  41. #     [],    # [3] Labels - [ line, name ]
  42. #     [] ] } # [4] Structures - [ line, name ]
  43. VARIABLES = 0
  44. MACROS = 1
  45. PROCEDURES = 2
  46. LABELS = 3
  47. STRUCTURES = 4
  48. kernel_structure = {}
  49.  
  50. class AsmVariable:
  51.         def __init__(self, line, name, type, init, comment, line_span):
  52.                 self.line = line
  53.                 self.name = name
  54.                 self.type = type
  55.                 self.init = init
  56.                 self.comment = comment # Comment after the definition (a dd 0 ; Comment)
  57.                 self.line_span = line_span # How much .asm lines its definition takes
  58.  
  59. class AsmFunction:
  60.         def __init__(self, line, name):
  61.                 self.line = line
  62.                 self.name = name
  63.  
  64. class AsmLabel:
  65.         def __init__(self, line, name):
  66.                 self.line = line
  67.                 self.name = name
  68.  
  69. class AsmMacro:
  70.         def __init__(self, asm_file_name, line, name, comment, args):
  71.                 self.file = asm_file_name
  72.                 self.line = line
  73.                 self.name = name
  74.                 self.comment = comment
  75.                 self.args = args
  76.  
  77. class AsmStruct:
  78.         def __init__(self, line, name):
  79.                 self.line = line
  80.                 self.name = name
  81.  
  82. def parse_variable(asm_file_name, lines, line_idx):
  83.         global warnings
  84.  
  85.         def curr():
  86.                 try: return line[i]
  87.                 except: return ''
  88.  
  89.         # Returns current and then increments current index
  90.         def step():
  91.                 nonlocal i
  92.                 c = curr()
  93.                 i += 1
  94.                 return c
  95.  
  96.         line = lines[line_idx]
  97.         i = 0
  98.         # Skip first spaces
  99.         while curr().isspace(): step()
  100.         # Get name
  101.         name = ""
  102.         while curr().isalnum() or curr() == '_' or curr() == '.': name += step()
  103.         # Skip spaces after variable name
  104.         while curr().isspace(): step()
  105.         # Get type specifier (db, dd, etc.)
  106.         type = ""
  107.         while curr().isalnum() or curr() == '_': type += step()
  108.         # Skip spaces after type specifier
  109.         while curr().isspace(): step()
  110.         # Get initial value (everything up to end of the line or comment)
  111.         init = ""
  112.         while curr() and curr() != ';': init += step()
  113.         # Get comment
  114.         comment = ""
  115.         if curr() == ';':
  116.                 step() # Skip ';'
  117.                 while curr(): comment += step()
  118.         # Process type
  119.         if type == "db": type = "byte"
  120.         elif type == "dw": type = "word"
  121.         elif type == "dd": type = "dword"
  122.         elif type == "dq": type = "qword"
  123.         else: raise Exception(f"Unexpected type: '{type}' (i = {i})")
  124.         # Process comment
  125.         if comment == "": comment = "Undocumented"
  126.         else:
  127.                 comment = comment.lstrip()
  128.                 if (len(comment) == 0):
  129.                         comment = "!!! EMPTY_COMMENT"
  130.                         warnings += f"{asm_file_name}:{line_idx + 1}: Empty comment in\n"
  131.                 if comment[0].islower():
  132.                         warnings += f"{asm_file_name}:{line_idx + 1}: Сomment sarting with lowercase\n"
  133.         # Build the result
  134.         result = AsmVariable(line_idx + 1, name, type, init, comment, 1)
  135.         return (1, result)
  136.  
  137. def is_id(c):
  138.         return c.isprintable() and c not in "+-/*=<>()[]{}:,|&~#`'\" \n\r\t\v"
  139.  
  140. def get_comment_begin(line):
  141.         result = len(line)
  142.         in_str = False
  143.         for i in range(len(line)):
  144.                 if in_str:
  145.                         if line[i] == in_str: in_str = False
  146.                         i += 1
  147.                 elif line[i] == '\'' or line[i] == '\"':
  148.                         in_str = line[i]
  149.                         i += 1
  150.                 elif line[i] == ';':
  151.                         result = i
  152.                         break
  153.                 else:
  154.                         i += 1
  155.         return result
  156.  
  157. def get_comment(line):
  158.         return line[get_comment_begin(line):]
  159.  
  160. def remove_comment(line):
  161.         return line[0:get_comment_begin(line)]
  162.  
  163. def insert_comment(line, comment):
  164.         comment_begin = get_comment_begin(line)
  165.         line_left = line[:get_comment_begin(line)]
  166.         line_right = line[get_comment_begin(line):]
  167.         return line_left + comment + line_right
  168.  
  169. def has_line_wrap(line):
  170.         if remove_comment(line).rstrip()[-1] == '\\':
  171.                 return True
  172.         return False
  173.  
  174. def remove_line_wrap(line):
  175.         if remove_comment(line).rstrip()[-1] == '\\':
  176.                 return remove_comment(line).rstrip()[:-1]
  177.         return line
  178.  
  179. def parse_macro(asm_file_name, lines, line_idx):
  180.         line_idx_orig = line_idx
  181.         global warnings
  182.  
  183.         def curr():
  184.                 try: return line[i]
  185.                 except: return ''
  186.  
  187.         # Returns current and then increments current index
  188.         def step():
  189.                 nonlocal i
  190.                 c = curr()
  191.                 i += 1
  192.                 return c
  193.  
  194.         line = lines[line_idx]
  195.         # Handle line wraps ('\' at the end)
  196.         while has_line_wrap(line):
  197.                 next_line = lines[line_idx + 1]
  198.                 prev_line_comment = get_comment(line)
  199.                 line = remove_line_wrap(line) + insert_comment(next_line, prev_line_comment)
  200.                 line_idx += 1
  201.  
  202.         i = 0
  203.         # Skip first spaces
  204.         while curr().isspace(): step()
  205.         # Read "macro" keyword
  206.         keyword = ""
  207.         while is_id(curr()): keyword += step()
  208.         if keyword != "macro": raise Exception(f"Not a macro: {line}")
  209.         # Skip spaces after "macro"
  210.         while curr().isspace(): step()
  211.         # Read macro name
  212.         name = ""
  213.         while curr() and not curr().isspace(): name += step()
  214.         # Skip spaces after macro name
  215.         while curr().isspace(): step()
  216.         # Find all arguments
  217.         args = []
  218.         arg = ''
  219.         while curr() and curr() != ';' and curr() != '{':
  220.                 # Collect identifier
  221.                 if is_id(curr()):
  222.                         arg += step()
  223.                 # Save the collected identifier
  224.                 elif curr() == ',':
  225.                         args.append(arg)
  226.                         arg = ''
  227.                         step()
  228.                 # Just push the '['
  229.                 elif curr() == '[':
  230.                         args.append(step())
  231.                 # Just push the identifier and get ']' ready to be pushed on next comma
  232.                 elif curr() == ']':
  233.                         args.append(arg)
  234.                         arg = step()
  235.                 # Just push the identifier and get '*' ready to be pushed on next comma
  236.                 elif curr() == '*':
  237.                         args.append(arg)
  238.                         arg = step()
  239.                 # Just skip whitespaces
  240.                 elif curr().isspace():
  241.                         step()
  242.                 # Something unexpected
  243.                 else:
  244.                         raise Exception(f"Unexpected symbol '{curr()}' at index #{i} " +
  245.                                         f"in the macro declaration:\n'{line}'")
  246.         if arg != '':
  247.                 args.append(arg)
  248.         # Find a comment if any
  249.         comment = ""
  250.         while curr() and curr() != ';': step()
  251.         if curr() == ';':
  252.                 step()
  253.                 while curr(): comment += step()
  254.         # Find end of the macro
  255.         end_of_macro = False
  256.         while not end_of_macro:
  257.                 line = lines[line_idx]
  258.                 rbraces = re.finditer('}', line)
  259.                 for rbrace_match in rbraces:
  260.                         rbrace_idx = rbrace_match.start()
  261.                         if line[rbrace_idx - 1] != '\\':
  262.                                 end_of_macro = True
  263.                 line_idx += 1
  264.         # Process comment
  265.         if comment != "":
  266.                 comment = comment.lstrip()
  267.                 if (len(comment) == 0):
  268.                         comment = "!!! EMPTY_COMMENT"
  269.                         warnings += f"{asm_file_name}:{line_idx + 1}: Empty comment in\n"
  270.                 if comment[0].islower():
  271.                         warnings += f"{asm_file_name}:{line_idx + 1}: Сomment sarting with lowercase\n"
  272.         # Build the output
  273.         line_span = line_idx - line_idx_orig + 1
  274.         result = AsmMacro(asm_file_name, line_idx_orig, name, comment, args)
  275.         return (line_span, result)
  276.  
  277. def get_declarations(asm_file_contents, asm_file_name):
  278.         asm_file_name = asm_file_name.replace("./", "")
  279.         kernel_structure[asm_file_name] = [ [], [], [], [], [] ]
  280.  
  281.         variable_pattern = re.compile(r'^\s*[\w\.]+\s+d[bwdq]\s+.*')
  282.         macro_pattern = re.compile(r'^\s*macro\s+([\w]+).*')
  283.         proc_pattern = re.compile(r'^\s*proc\s+([\w\.]+).*')
  284.         label_pattern = re.compile(r'^(?!;)\s*([\w\.]+):.*')
  285.         struct_pattern = re.compile(r'^\s*struct\s+([\w]+).*')
  286.  
  287.         line_idx = 0
  288.         lines = asm_file_contents.splitlines()
  289.         while line_idx < len(lines):
  290.                 line = lines[line_idx]
  291.  
  292.                 if variable_pattern.match(line):
  293.                         (skip_lines, var) = parse_variable(asm_file_name, lines, line_idx)
  294.                         kernel_structure[asm_file_name][VARIABLES].append(var)
  295.                         line_idx += skip_lines
  296.                         continue
  297.  
  298.                 match = macro_pattern.findall(line)
  299.                 if len(match) > 0:
  300.                         (skip_lines, macro) = parse_macro(asm_file_name, lines, line_idx)
  301.                         kernel_structure[asm_file_name][MACROS].append(macro)
  302.                         line_idx += skip_lines
  303.                         continue
  304.  
  305.                 match = proc_pattern.findall(line)
  306.                 if len(match) > 0:
  307.                         proc_name = match[0]
  308.                         kernel_structure[asm_file_name][PROCEDURES].append(AsmFunction(line_idx + 1, proc_name))
  309.                         line_idx += 1
  310.                         continue
  311.  
  312.                 match = label_pattern.findall(line)
  313.                 if len(match) > 0:
  314.                         label_name = match[0]
  315.                         # Don't count local labels
  316.                         if label_name[0] != '.':
  317.                                 kernel_structure[asm_file_name][LABELS].append(AsmLabel(line_idx + 1, label_name))
  318.                                 line_idx += 1
  319.                                 continue
  320.  
  321.                 match = struct_pattern.findall(line)
  322.                 if len(match) > 0:
  323.                         struct_name = match[0]
  324.                         kernel_structure[asm_file_name][STRUCTURES].append(AsmStruct(line_idx + 1, struct_name))
  325.                         end_of_struct = False
  326.                         while not end_of_struct:
  327.                                 line = lines[line_idx]
  328.                                 if re.match(r"^ends$", line) != None:
  329.                                         end_of_struct = True
  330.                                 line_idx += 1
  331.                         continue
  332.  
  333.                 line_idx += 1
  334.  
  335. def handle_file(handled_files, asm_file_name, subdir = "."):
  336.         if dump_symbols:
  337.                 print(f"Handling {asm_file_name}")
  338.         handled_files.append(asm_file_name)
  339.         try:
  340.                 asm_file_contents = open(asm_file_name, "r", encoding="utf-8").read()
  341.         except:
  342.                 return
  343.         get_declarations(asm_file_contents, asm_file_name)
  344.         include_directive_pattern_1 = re.compile(r'include "(.*)"')
  345.         include_directive_pattern_2 = re.compile(r'include \'(.*)\'')
  346.         includes = include_directive_pattern_1.findall(asm_file_contents)
  347.         includes += include_directive_pattern_2.findall(asm_file_contents)
  348.         for include in includes:
  349.                 include = include.replace('\\', '/');
  350.                 full_path = subdir + '/' + include;
  351.                 if full_path not in handled_files:
  352.                         new_subdir = full_path.rsplit('/', 1)[0]
  353.                         handle_file(handled_files, full_path, new_subdir)
  354.         return handled_files
  355.  
  356. kernel_files = []
  357.  
  358. handle_file(kernel_files, "./kernel.asm");
  359.  
  360. if dump_symbols:
  361.         for source in kernel_structure:
  362.                 print(f"File: {source}")
  363.                 if len(kernel_structure[source][VARIABLES]) > 0:
  364.                         print(" Variables:")
  365.                         for variable in kernel_structure[source][VARIABLES]:
  366.                                 print(f"  {variable.line}: {variable.name}")
  367.                 if len(kernel_structure[source][PROCEDURES]) > 0:
  368.                         print(" Procedures:")
  369.                         for procedure in kernel_structure[source][PROCEDURES]:
  370.                                 print(f"  {procedure.line}: {procedure.name}")
  371.                 if len(kernel_structure[source][LABELS]) > 0:
  372.                         print(" Global labels:")
  373.                         for label in kernel_structure[source][LABELS]:
  374.                                 print(f"  {label.line}: {label.name}")
  375.                 if len(kernel_structure[source][MACROS]) > 0:
  376.                         print(" Macroses:")
  377.                         for macro in kernel_structure[source][MACROS]:
  378.                                 print(f"  {macro.line}: {macro.name}")
  379.                 if len(kernel_structure[source][STRUCTURES]) > 0:
  380.                         print(" Structures:")
  381.                         for struct in kernel_structure[source][STRUCTURES]:
  382.                                 print(f"  {struct.line}: {struct.name}")
  383.  
  384. if print_stats:
  385.         # Collect stats
  386.         var_count = 0
  387.         proc_count = 0
  388.         label_count = 0
  389.         macro_count = 0
  390.         struct_count = 0
  391.  
  392.         for source in kernel_structure:
  393.                 var_count += len(kernel_structure[source][VARIABLES])
  394.                 proc_count += len(kernel_structure[source][PROCEDURES])
  395.                 label_count += len(kernel_structure[source][LABELS])
  396.                 macro_count += len(kernel_structure[source][MACROS])
  397.                 struct_count += len(kernel_structure[source][STRUCTURES])
  398.  
  399.         print(f"File count: {len(kernel_structure)}")
  400.         print(f"Variable count: {var_count}")
  401.         print(f"Procedures count: {proc_count}")
  402.         print(f"Global labels count: {label_count}")
  403.         print(f"Macroses count: {macro_count}")
  404.         print(f"Structures count: {struct_count}")
  405.  
  406. print(f"Writing doumented sources to {doxygen_src_path}")
  407.  
  408. created_files = []
  409.  
  410. def write_something(source, somehing):
  411.         full_path = doxygen_src_path + '/' + source
  412.         # Remove the file on first access if it was created by previous generation
  413.         if full_path not in created_files:
  414.                 if os.path.isfile(full_path):
  415.                         os.remove(full_path)
  416.                 created_files.append(full_path)
  417.         # Only remove the file on 'clean_generated_stuff' flag (removed above, just return)
  418.         if clean_generated_stuff: return
  419.         # Create directories need for the file
  420.         os.makedirs(os.path.dirname(full_path), exist_ok=True)
  421.         f = open(full_path, "a")
  422.         f.write(somehing)
  423.         f.close()
  424.  
  425. def write_variable(source, variable):
  426.         line = variable.line
  427.         type = variable.type
  428.         init = variable.init
  429.         brief = variable.comment
  430.         name = variable.name.replace(".", "_")
  431.         something = (f"/**\n" +
  432.                      f" * @brief {brief}\n" +
  433.                      f" * @par Initial value\n" +
  434.                      f" * {init}\n" +
  435.                      f" * @par Source\n" +
  436.                      f" * <a href='{link_root}/{source}#line-{line}'>{source}:{line}</a>\n" +
  437.                      f" */\n" +
  438.                      f"{type} {name};\n\n")
  439.         write_something(source, something)
  440.  
  441. def write_procedure(source, line, name, brief = "Undocumented"):
  442.         name = name.replace(".", "_")
  443.         something = (f"/**\n" +
  444.                      f" * @brief {brief}\n" +
  445.                      f" * @par Source\n" +
  446.                      f" * <a href='{link_root}/{source}#line-{line}'>{source}:{line}</a>\n" +
  447.                      f" */\n" +
  448.                      f"void {name}();\n\n")
  449.         write_something(source, something)
  450.  
  451. def write_label(source, line, name, brief = "Undocumented"):
  452.         name = name.replace(".", "_")
  453.         something = (f"/**\n" +
  454.                      f" * @brief {brief}\n" +
  455.                      f" * @par Source\n" +
  456.                      f" * <a href='{link_root}/{source}#line-{line}'>{source}:{line}</a>\n" +
  457.                      f" */\n" +
  458.                      f"void {name}();\n\n")
  459.         write_something(source, something)
  460.  
  461. def write_macro(source, macro):
  462.         if macro.comment == "": brief = "Undocumented"
  463.         else: brief = macro.comment
  464.         line = macro.line
  465.         name = macro.name.replace(".", "_").replace("@", "_")
  466.         # Construct arg list without '['s, ']'s and '*'s
  467.         args = [arg for arg in macro.args if arg not in "[]*"]
  468.         # Construct C-like arg list
  469.         arg_list = ""
  470.         if len(args) > 0:
  471.                 arg_list += '('
  472.                 argc = 0
  473.                 for arg in args:
  474.                         if argc != 0:
  475.                                 arg_list += ", "
  476.                         arg_list += arg
  477.                         argc += 1
  478.                 arg_list += ')'
  479.  
  480.         something = (f"/**\n" +
  481.                      f" * @def {name}\n" +
  482.                      f" * @brief {brief}\n" +
  483.                      f" * @par Source\n" +
  484.                      f" * <a href='{link_root}/{source}#line-{line}'>{source}:{line}</a>\n" +
  485.                      f" */\n#define {name}{arg_list}\n\n")
  486.         write_something(source, something)
  487.  
  488. def write_structure(source, line, name, brief = "Undocumented"):
  489.         name = name.replace(".", "_")
  490.         something = (f"/**\n" +
  491.                      f" * @struct {name}\n" +
  492.                      f" * @brief {brief}\n" +
  493.                      f" * @par Source\n" +
  494.                      f" * <a href='{link_root}/{source}#line-{line}'>{source}:{line}</a>\n" +
  495.                      f" */\nstruct {name}" + " {};\n\n")
  496.         write_something(source, something)
  497.  
  498. i = 1
  499. for source in kernel_structure:
  500.         # Print progress: current/total
  501.         print(f"{i}/{len(kernel_structure)} Writing {source}")
  502.         # Write variables doxygen of the source file
  503.         if len(kernel_structure[source][VARIABLES]) > 0:
  504.                 for variable in kernel_structure[source][VARIABLES]:
  505.                         write_variable(source, variable)
  506.         if len(kernel_structure[source][PROCEDURES]) > 0:
  507.                 for procedure in kernel_structure[source][PROCEDURES]:
  508.                         write_procedure(source, procedure.line, procedure.name)
  509.         if len(kernel_structure[source][LABELS]) > 0:
  510.                 for label in kernel_structure[source][LABELS]:
  511.                         write_label(source, label.line, label.name)
  512.         if len(kernel_structure[source][MACROS]) > 0:
  513.                 for macro in kernel_structure[source][MACROS]:
  514.                         write_macro(source, macro)
  515.         if len(kernel_structure[source][STRUCTURES]) > 0:
  516.                 for structure in kernel_structure[source][STRUCTURES]:
  517.                         write_structure(source, structure.line, structure.name)
  518.         i += 1
  519.  
  520. if enable_warnings:
  521.         open('asmxygen.txt', "w", encoding = "utf-8").write(warnings)
  522.