Subversion Repositories Kolibri OS

Rev

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