A semantic search engine for source code https://bitshift.benkurtovic.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

161 line
4.4 KiB

  1. import ast
  2. class _CachedWalker(ast.NodeVisitor):
  3. """
  4. Local node visitor for python abstract syntax trees.
  5. :ivar accum: (dict) Information on variables, functions, and classes
  6. accumulated from an abstract syntax tree.
  7. :ivar cache: (dict or None) Information stored about parent nodes. Added
  8. to accum when node reaches the lowest possible level.
  9. .. todo::
  10. Add visit funciton for ast.Name to record all uses of a variable.
  11. Use self.cache to store extra information about nodes.
  12. """
  13. def __init__(self):
  14. """
  15. Create a _TreeCutter instance.
  16. """
  17. self.accum = {'vars': {}, 'functions': {}, 'classes': {}}
  18. self.cache = []
  19. def block_position(self, node):
  20. """
  21. Helper function to get the start and end lines of an AST node.
  22. :param node: The node.
  23. :type node: ast.FunctionDef or ast.ClassDef or ast.Module
  24. """
  25. start_line, start_col = node.lineno, node.col_offset
  26. temp_node = node
  27. while 'body' in temp_node.__dict__:
  28. temp_node = temp_node.body[-1]
  29. end_line, end_col = temp_node.lineno, temp_node.col_offset
  30. return (start_line, start_col, end_line, end_col)
  31. def visit_Assign(self, node):
  32. """
  33. Visits Assign nodes in a tree. Adds relevant data about them to accum.
  34. :param node: The current node.
  35. :type node: ast.Assign
  36. .. todo::
  37. Add value and type metadata to accum.
  38. """
  39. line, col = node.lineno, node.col_offset
  40. pos = (line, col, -1, -1)
  41. self.cache.append({'nodes': []})
  42. self.generic_visit(node)
  43. last = self.cache.pop()
  44. for name in last['nodes']:
  45. if not self.accum['vars'].has_key(name):
  46. self.accum['vars'][name] = {'assignments': [], 'uses': []}
  47. self.accum['vars'][name]['assignments'].append(pos)
  48. def visit_FunctionDef(self, node):
  49. """
  50. Visits FunctionDef nodes in a tree. Adds relevant data about them to accum.
  51. :param node: The current node.
  52. :type node: ast.FunctionDef
  53. .. todo::
  54. Add arguments and decorators metadata to accum.
  55. """
  56. start_line, start_col, end_line, end_col = self.block_position(node)
  57. if not self.accum['functions'].has_key(node.name):
  58. self.accum['functions'][node.name] = {'assignments': [], 'uses': []}
  59. pos = (start_line, start_col, end_line, end_col)
  60. self.accum['functions'][node.name]['assignments'].append(pos)
  61. self.generic_visit(node)
  62. def visit_Call(self, node):
  63. """
  64. Visits Function Call nodes in a tree. Adds relevant data about them
  65. in the functions section for accum.
  66. :param node: The current node.
  67. :type node: ast.Call
  68. .. todo::
  69. Add arguments and decorators metadata to accum.
  70. """
  71. line, col = node.lineno, node.col_offset
  72. pos = (line, col, -1, -1)
  73. if isinstance(node.func, ast.Name):
  74. name = node.func.id
  75. else:
  76. name = node.func.attr
  77. if not self.accum['functions'].has_key(name):
  78. self.accum['functions'][name] = {'assignments': [], 'uses': []}
  79. self.accum['functions'][name]['uses'].append(pos)
  80. def visit_ClassDef(self, node):
  81. """
  82. Visits ClassDef nodes in a tree. Adds relevant data about them to accum.
  83. :param node: The current node.
  84. :type node: ast.ClassDef
  85. .. todo::
  86. Add arguments, inherits, and decorators metadata to accum.
  87. """
  88. start_line, start_col, end_line, end_col = self.block_position(node)
  89. pos = (start_line, start_col, end_line, end_col)
  90. self.accum['classes'][node.name] = pos
  91. self.generic_visit(node)
  92. def visit_Name(self, node):
  93. if self.cache:
  94. last = self.cache[-1]
  95. last['nodes'].append(node.id)
  96. def visit_Attribute(self, node):
  97. if self.cache:
  98. last = self.cache[-1]
  99. last['nodes'].append(node.attr)
  100. def parse_py(codelet):
  101. """
  102. Adds 'symbols' field to the codelet after parsing the python code.
  103. :param codelet: The codelet object to parsed.
  104. :type code: Codelet
  105. """
  106. tree = ast.parse(codelet.code)
  107. cutter = _CachedWalker()
  108. cutter.visit(tree)
  109. codelet.symbols = cutter.accum