owl-basic 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. owl_basic/__init__.py +3 -0
  2. owl_basic/algorithms.py +29 -0
  3. owl_basic/ast_utils.py +204 -0
  4. owl_basic/basic_visitor.py +55 -0
  5. owl_basic/cfg_vertex.py +65 -0
  6. owl_basic/codegen/__init__.py +0 -0
  7. owl_basic/codegen/clr/__init__.py +0 -0
  8. owl_basic/codegen/clr/cil_visitor.py +1296 -0
  9. owl_basic/codegen/clr/cts.py +56 -0
  10. owl_basic/codegen/clr/emitters.py +94 -0
  11. owl_basic/codegen/clr/generate.py +539 -0
  12. owl_basic/correlation_visitor.py +119 -0
  13. owl_basic/data_visitor.py +62 -0
  14. owl_basic/decoder.py +339 -0
  15. owl_basic/errors.py +22 -0
  16. owl_basic/flow/__init__.py +17 -0
  17. owl_basic/flow/basic_block.py +34 -0
  18. owl_basic/flow/basic_block_identifier.py +66 -0
  19. owl_basic/flow/basic_block_orderer.py +29 -0
  20. owl_basic/flow/connectors.py +19 -0
  21. owl_basic/flow/convert_sub_visitor.py +28 -0
  22. owl_basic/flow/entry_point_locator.py +55 -0
  23. owl_basic/flow/entry_point_visitor.py +48 -0
  24. owl_basic/flow/flow_analysis.py +56 -0
  25. owl_basic/flow/flow_graph_creator.py +14 -0
  26. owl_basic/flow/flowgraph_visitor.py +178 -0
  27. owl_basic/flow/longjump_converter.py +20 -0
  28. owl_basic/flow/longjump_visitor.py +53 -0
  29. owl_basic/flow/subroutine_converter.py +38 -0
  30. owl_basic/flow/traversal.py +110 -0
  31. owl_basic/gml_visitor.py +151 -0
  32. owl_basic/line_mapper.py +43 -0
  33. owl_basic/line_number_visitor.py +65 -0
  34. owl_basic/main.py +381 -0
  35. owl_basic/node.py +21 -0
  36. owl_basic/options.py +22 -0
  37. owl_basic/owltyping/__init__.py +1 -0
  38. owl_basic/owltyping/function_type_inferer.py +50 -0
  39. owl_basic/owltyping/hindley_milner.py +524 -0
  40. owl_basic/owltyping/set_function_type_visitor.py +25 -0
  41. owl_basic/owltyping/type_system.py +220 -0
  42. owl_basic/owltyping/typecheck.py +60 -0
  43. owl_basic/owltyping/typecheck_visitor.py +471 -0
  44. owl_basic/parent_visitor.py +37 -0
  45. owl_basic/process.py +36 -0
  46. owl_basic/separation_visitor.py +98 -0
  47. owl_basic/sigil.py +30 -0
  48. owl_basic/simplify_visitor.py +204 -0
  49. owl_basic/singleton.py +127 -0
  50. owl_basic/source_debugging.py +124 -0
  51. owl_basic/symbol_table_visitor.py +220 -0
  52. owl_basic/symbol_tables.py +195 -0
  53. owl_basic/syntax/__init__.py +0 -0
  54. owl_basic/syntax/ast.py +1081 -0
  55. owl_basic/syntax/ast_meta.py +228 -0
  56. owl_basic/syntax/grammar.py +1972 -0
  57. owl_basic/syntax/lexer.py +943 -0
  58. owl_basic/syntax/parser.py +77 -0
  59. owl_basic/utility.py +26 -0
  60. owl_basic/visitor.py +43 -0
  61. owl_basic/xml_blocks.py +137 -0
  62. owl_basic/xml_visitor.py +101 -0
  63. owl_basic-0.6.0.dist-info/METADATA +37 -0
  64. owl_basic-0.6.0.dist-info/RECORD +69 -0
  65. owl_basic-0.6.0.dist-info/WHEEL +5 -0
  66. owl_basic-0.6.0.dist-info/entry_points.txt +2 -0
  67. owl_basic-0.6.0.dist-info/licenses/LICENSE +21 -0
  68. owl_basic-0.6.0.dist-info/licenses/THIRD-PARTY-NOTICES.md +57 -0
  69. owl_basic-0.6.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,29 @@
1
+ '''
2
+ Created on 9 Feb 2010
3
+
4
+ @author: rjs
5
+ '''
6
+ import logging
7
+ logger = logging.getLogger('flow.basic_block_orderer')
8
+
9
+ from .traversal import approximateTopologicalOrder
10
+
11
+ def orderBasicBlocks(basic_blocks, options):
12
+ '''
13
+ :param basic_blocks: A dictionary of entry blocks - BasicBlock instances through which control
14
+ flow enters the graph of each program, function or procedure. The keys are
15
+ the entry point names.
16
+ :param options: Command line options
17
+ :returns: A dictionary of lists of basic blocks in approximate topological order. Keys are entry_point names
18
+ '''
19
+ ordered_blocks = {}
20
+ for name, basic_block in basic_blocks.items():
21
+ logger.debug(name)
22
+ order = approximateTopologicalOrder(basic_block)
23
+ print("order = ", order)
24
+ for i, block in enumerate(order):
25
+ block.topological_order = i
26
+ ordered_blocks[name] = order
27
+ return ordered_blocks
28
+
29
+
@@ -0,0 +1,19 @@
1
+ '''
2
+ Functions for manipulating the control flow graph
3
+ '''
4
+
5
+ from owl_basic.ast_utils import findFollowingStatement
6
+
7
+ def connectToFollowing(statement):
8
+ following = findFollowingStatement(statement)
9
+ if following is not None:
10
+ connect(statement, following)
11
+
12
+ def connect(from_statement, to_statement):
13
+ from_statement.addOutEdge(to_statement)
14
+ to_statement.addInEdge(from_statement)
15
+
16
+ def connectLoop(from_statement, to_statement):
17
+ from_statement.addLoopBackEdge(to_statement)
18
+ to_statement.addLoopFromEdge(from_statement)
19
+
@@ -0,0 +1,28 @@
1
+ from owl_basic.visitor import Visitor
2
+ from owl_basic.ast_utils import replaceStatement
3
+ from owl_basic.syntax.ast import LiteralInteger, CallProcedure, ReturnFromProcedure
4
+ from owl_basic import errors
5
+
6
+ class ConvertSubVisitor(Visitor):
7
+ """
8
+ Replaces RETURN with ENDPROC
9
+ """
10
+
11
+ def __init__(self):
12
+ pass
13
+
14
+ def visitAstNode(self, node):
15
+ node.forEachChild(self.visit)
16
+
17
+ def visitGosub(self, gosub):
18
+ if isinstance(gosub.targetLogicalLine, LiteralInteger):
19
+ # Convert to a procedure call
20
+ proc = CallProcedure(name="PROCSub" + str(gosub.targetLogicalLine.value))
21
+ replaceStatement(gosub, proc)
22
+ else:
23
+ errors.fatalError("Cannot compile computed GOSUB target at line %s" % gosub.lineNum)
24
+
25
+ def visitReturn(self, ret):
26
+ # Convert each RETURN to an ENDPROC by changing the class
27
+ ret.__class__ = ReturnFromProcedure
28
+
@@ -0,0 +1,55 @@
1
+ import sys
2
+ import logging
3
+
4
+ from owl_basic.syntax import ast
5
+ from owl_basic import ast_utils
6
+ from owl_basic import errors
7
+ from . import flow_analysis
8
+
9
+ from .entry_point_visitor import EntryPointVisitor
10
+
11
+
12
+ def locateEntryPoints(parse_tree, line_mapper, options):
13
+ '''
14
+ Locate all the program, procedure, function and subroutine entry points in the program
15
+ represented by parse_tree.
16
+ :param parse_tree: The root AstNode of an abstract syntax tree representing the program.
17
+ :param line_mapper: A LineMapper for the program.
18
+ :param options: Command line options.
19
+ :returns: A dictionary of entry point names to entry point AstStatement nodes. The name
20
+ of the program entry point, if there is one, will be '__owl__main'
21
+ '''
22
+ logging.debug("locateEntryPoints")
23
+ epv = EntryPointVisitor(line_mapper)
24
+ parse_tree.accept(epv)
25
+ first_statement = line_mapper.firstStatement()
26
+ epv.mainEntryPoint(first_statement)
27
+ entry_points = epv.entryPoints
28
+ # Tag each statement with its predecessor entry point
29
+ logging.debug("Tagging statements with entry point\n")
30
+ for entry_point in entry_points.values():
31
+ flow_analysis.tagSuccessors(entry_point, line_mapper)
32
+
33
+ guardExecutableDefinitions(entry_points)
34
+ return entry_points
35
+
36
+ def guardExecutableDefinitions(entry_points):
37
+ logging.debug("Checking for direct execution of function or procedure bodies...")
38
+
39
+ for entry_point in entry_points.values():
40
+ if isinstance(entry_point, ast.DefinitionStatement):
41
+ if len(entry_point.inEdges) != 0:
42
+
43
+ reachable_predecessors = (len(predecessor.entryPoints) > 0 for predecessor in entry_point.inEdges)
44
+ if any(reachable_predecessors):
45
+ errors.warning("Execution of procedure/function at line %s" % entry_point.lineNum)
46
+ # TODO: Could use ERROR statement here
47
+ # TODO: Could also have an option to allow procedures to be directly executable
48
+ # which may involve inserting a call to the procedure at this point ... probably
49
+ # more appropriate for GOSUBroutines.
50
+ raise_stmt = ast.Raise(type="ExecutedDefinitionException")
51
+ raise_stmt.lineNum = entry_point.lineNum
52
+ ast_utils.insertStatementBefore(entry_point, raise_stmt)
53
+ raise_stmt.clearOutEdges()
54
+ entry_point.clearInEdges()
55
+ return entry_point, entry_points
@@ -0,0 +1,48 @@
1
+ from owl_basic.visitor import Visitor
2
+ from owl_basic.ast_utils import *
3
+ from owl_basic import errors
4
+
5
+ class EntryPointVisitor(Visitor):
6
+ """
7
+ This enumerates all of the entry-points in the supplied AST
8
+ """
9
+
10
+ def __init__(self, line_mapper):
11
+ # TODO: This should locate its own root, finding it on demand
12
+ self.line_mapper = line_mapper
13
+ self.__entry_points = {}
14
+
15
+ entryPoints = property(lambda self: self.__entry_points)
16
+
17
+ def mainEntryPoint(self, statement):
18
+ '''
19
+ Set the main() entrypoint of the program (i.e. the first line)
20
+ '''
21
+ if not hasattr(statement, "entryPoint"):
22
+ statement.entryPoint = "__owl__main"
23
+ self.__entry_points['__owl__main'] = statement
24
+
25
+ def visitAstNode(self, node):
26
+ "Visit all children in order"
27
+ node.forEachChild(self.visit)
28
+
29
+ def visitDefineProcedure(self, defproc):
30
+ self.__entry_points[defproc.name] = defproc
31
+ defproc.entryPoint = "public"
32
+
33
+ def visitDefineFunction(self, deffn):
34
+ self.__entry_points[deffn.name] = deffn
35
+ deffn.entryPoint = "public"
36
+
37
+ def visitGosub(self, gosub):
38
+ gosub_target = self.line_mapper.statementOnLine(gosub.targetLogicalLine)
39
+ if gosub_target:
40
+ self.__entry_points["gosub%d" % int(gosub.targetLogicalLine.value)] = gosub_target
41
+ gosub_target.entryPoint = "private"
42
+ gosub_target.addComeFromGosubEdge(gosub)
43
+ else:
44
+ print("Line not found")
45
+ # TODO: Error!
46
+ pass
47
+
48
+
@@ -0,0 +1,56 @@
1
+ # Functions for analysing the CFG graph
2
+
3
+ from owl_basic.syntax import ast
4
+
5
+
6
+ def tagNode(tag, node):
7
+ if tag not in node.entryPoints:
8
+ node.addEntryPoint(tag)
9
+ tagFollowingStatements(node, tag)
10
+
11
+ def tagSuccessors(entry_point, line_mapper):
12
+ """
13
+ Given an entry point node, tag all successors of that entry point
14
+ with the routine name
15
+ :param entry_point: A node which is the entry point into a routine
16
+ :param line_mapper: An object which supports a physicalToLogical method call
17
+ to convert line numbers.
18
+ """
19
+ tag = None
20
+ if isinstance(entry_point, ast.DefineProcedure):
21
+ # TODO: There is a bug here, whereby SUBXYZ can get relabelled PROCSUBXYZ
22
+ tag = "PROC" + entry_point.name
23
+ elif isinstance(entry_point, ast.DefineFunction):
24
+ tag = "FN" + entry_point.name
25
+ elif len(entry_point.comeFromGosubEdges) != 0:
26
+ logical_line_number = line_mapper.physicalToLogical(entry_point.lineNum)
27
+ tag = "SUB%d" % logical_line_number
28
+ else:
29
+ # TODO: Find a better way to do this rather than defaulting here
30
+ tag = "MAIN"
31
+
32
+ if tag is not None:
33
+ tagNode(tag, entry_point)
34
+ tagFollowingStatements(entry_point, tag)
35
+
36
+ def tagFollowingStatements(node, tag):
37
+ for successor in node.outEdges:
38
+ tagNode(tag, successor)
39
+
40
+ def deTagSuccessors(node):
41
+ """
42
+ Given a node and its entry point tags, remove all those tags from following
43
+ the nodes.
44
+ """
45
+ tags = node.entryPoints
46
+ if tags is not None:
47
+ deTagFollowingStatements(node, tags)
48
+
49
+ def deTagFollowingStatements(node, tags):
50
+ for successor in node.outEdges:
51
+ deTagNode(tags, successor)
52
+
53
+ def deTagNode(tags, node):
54
+ if tags.issubset(node.entryPoints):
55
+ node.entryPoints.difference_update(tags)
56
+ deTagFollowingStatements(node, tags)
@@ -0,0 +1,14 @@
1
+ '''
2
+ Creation of the forward control flow graph
3
+ '''
4
+
5
+ import logging
6
+
7
+ from .flowgraph_visitor import FlowgraphForwardVisitor
8
+
9
+ logger = logging.getLogger('flow.flow_graph_creator')
10
+
11
+ def createForwardControlFlowGraph(parse_tree, line_mapper, options):
12
+ logger.debug("flowgraph")
13
+ logger.info("Creating Control Flow Graph...")
14
+ parse_tree.accept(FlowgraphForwardVisitor(line_mapper))
@@ -0,0 +1,178 @@
1
+ import logging
2
+
3
+ from owl_basic.visitor import Visitor
4
+ from owl_basic.ast_utils import findFollowingStatement
5
+ from .connectors import connect, connectToFollowing
6
+ from owl_basic import errors
7
+
8
+ logger = logging.getLogger('flow.flowgraph_visitor')
9
+
10
+ class FlowgraphForwardVisitor(Visitor):
11
+ """
12
+ This visitor connects each statement node to its following statement node.
13
+ For composite statement nodes (e.g. Program, If or Case nodes) the visitor connects
14
+ the child nodes appropriately.
15
+ """
16
+
17
+ def __init__(self, line_mapper):
18
+ self.line_mapper = line_mapper
19
+
20
+ def visitAstNode(self, node):
21
+ "Visit all children in order"
22
+ node.forEachChild(self.visit)
23
+
24
+ def visitAstStatement(self, statement):
25
+ """
26
+ Default behaviour for statements is to insert into the graph
27
+ and connect to the following statement. We override this behaviour
28
+ for conditional statements with more specific visitors.
29
+ """
30
+ connectToFollowing(statement)
31
+
32
+ def visitIf(self, iff):
33
+ """
34
+ Connect the If to the initial statements of the true and false
35
+ clauses. Process each statement in both clauses.
36
+ """
37
+ following = findFollowingStatement(iff)
38
+ if not following:
39
+ errors.internal("Following statement to IF not found at line %d" % iff.lineNum)
40
+ if iff.trueClause is not None:
41
+ if isinstance(iff.trueClause, list):
42
+ if len(iff.trueClause) > 0:
43
+ # Connect to the beginning of the true clause
44
+ first_true_statement = iff.trueClause[0]
45
+ connect(iff, first_true_statement)
46
+
47
+ for statement in iff.trueClause:
48
+ self.visit(statement)
49
+ else:
50
+ connect(iff.following) # TODO: Error!
51
+ else:
52
+ connect(iff, iff.trueClause)
53
+ self.visit(iff.trueClause)
54
+ else:
55
+ connect(iff, following)
56
+
57
+ if iff.falseClause is not None:
58
+ if isinstance(iff.falseClause, list):
59
+ if len(iff.falseClause) > 0:
60
+ # Connect to the beginning of the false clause
61
+ first_false_statement = iff.falseClause[0]
62
+ connect(iff, first_false_statement)
63
+
64
+ for statement in iff.falseClause:
65
+ self.visit(statement)
66
+ else:
67
+ connect(iff.following) # TODO: Error!
68
+ else:
69
+ connect(iff, iff.falseClause)
70
+ self.visit(iff.falseClause)
71
+ else:
72
+ connect(iff, following)
73
+
74
+ def visitGoto(self, goto):
75
+ """
76
+ Connect the Goto to the first statement on the target line if
77
+ it exists. Error if it does not.
78
+ """
79
+ # TODO: targetLogicalLine needs to be a constant for this
80
+ # to work
81
+ #print "CFG goto"
82
+ logger.debug("visitGoto")
83
+ #print "goto.targetLogicalLine = %s" % goto.targetLogicalLine.value
84
+ goto_target = self.line_mapper.statementOnLine(goto.targetLogicalLine)
85
+ #print "goto_target = %s" % goto_target
86
+ if goto_target:
87
+ connect(goto, goto_target)
88
+ else:
89
+ errors.error("No such line %s at line %s" % (goto.targetLogicalLine.value, goto.lineNum))
90
+
91
+ def visitOnGoto(self, ongoto):
92
+ """
93
+ Connect the OnGoto to the first statement on each of the target lines
94
+ if they exist. Error if they do not. Also connect to the first line of
95
+ the out-of-range clause if present, and process each statement within
96
+ that clause.
97
+ """
98
+ logger.debug("visitOnGoto")
99
+ ongoto.targetStatements = [] # TODO: Fix this so it isn't a monkey patch!
100
+ for targetLogicalLine in ongoto.targetLogicalLines:
101
+ ongoto_target = self.line_mapper.statementOnLine(targetLogicalLine)
102
+ if ongoto_target:
103
+ connect(ongoto, ongoto_target)
104
+ ongoto.targetStatements.append(ongoto_target)
105
+ else:
106
+ errors.error("No such line %s at line %s" % (targetLogicalLine.value, ongoto.lineNum))
107
+
108
+ first_else_statement = None
109
+ if ongoto.outOfRangeClause is not None:
110
+ if isinstance(ongoto.outOfRangeClause, list):
111
+ if len(ongoto.outOfRangeClause) > 0:
112
+ # Connect to the beginning of the else clause
113
+ first_else_statement = ongoto.outOfRangeClause[0]
114
+ connect(ongoto, first_else_statement)
115
+
116
+ for statement in ongoto.outOfRangeClause:
117
+ self.visit(statement)
118
+ else:
119
+ first_else_statement = ongoto.outOfRangeClause
120
+ connect(ongoto, ongoto.outOfRangeClause)
121
+ self.visit(ongoto.outOfRangeClause)
122
+ ongoto.outOfRangeStatement = first_else_statement # TODO: Fix this so it isn't a monkey patch!
123
+
124
+ assert hasattr(ongoto, "targetStatements")
125
+ assert hasattr(ongoto, "outOfRangeStatement")
126
+
127
+ def visitCase(self, case):
128
+ """
129
+ Connect the Case statement to the first statement of each when clause and
130
+ the otherwise clause. Process the statements within each clause.
131
+ """
132
+ logger.debug("visitCase")
133
+ for when_clause in case.whenClauses:
134
+ if when_clause.statements is not None and len(when_clause.statements) > 0:
135
+ # Connect to the beginning of the when clause
136
+ first_when_statement = when_clause.statements[0]
137
+ connect(case,first_when_statement)
138
+
139
+ for statement in when_clause.statements:
140
+ self.visit(statement)
141
+
142
+ # The following are statements which do not pass control to the
143
+ # succeeding statement in the linear source code order of the program
144
+
145
+ def visitReturnFromFunction(self, return_from_function):
146
+ # Do not connect to following statement
147
+ logger.debug("visitReturnFromFunction")
148
+
149
+ def visitReturnFromProcedure(self, return_frin_procedure):
150
+ # Do not connect to following statement
151
+ logger.debug("visitReturnFromProcedure")
152
+
153
+ def visitGenerateError(self, generator_error):
154
+ # Do not connect to following statement
155
+ logger.debug("visitGenerateError")
156
+
157
+ def visitReturnError(self, return_error):
158
+ # Do not connect to following statement
159
+ logger.debug("visitReturnError")
160
+
161
+ def visitQuit(self, quit):
162
+ # Do not connect to following statement
163
+ logger.debug("visitQuit")
164
+
165
+ def visitStop(self, stop):
166
+ # Do not connect to following statement
167
+ logger.debug("visitStop")
168
+
169
+ def visitEnd(self, end):
170
+ # Do not connect to following statement
171
+ logger.debug("visitEnd")
172
+
173
+ def visitReturn(self, end):
174
+ # Do not connect to following statement
175
+ # TODO: Rheolism!
176
+ logger.debug("visitReturn")
177
+
178
+
@@ -0,0 +1,20 @@
1
+ '''
2
+ Convert longjumps (GOTO out of a function or procedure)
3
+ to exceptions with appropriate handlers.
4
+ '''
5
+
6
+ import logging
7
+
8
+ from .longjump_visitor import LongjumpVisitor
9
+
10
+ logger = logging.getLogger('flow.longjump_converter')
11
+
12
+ def convertLongjumpsToExceptions(parse_tree, line_mapper, options):
13
+ logger.debug("convertLongjumpsToExceptions")
14
+ # Insert longjumps where flow control jumps out of a procedure
15
+ logging.info("Finding longjump locations")
16
+ ljv = LongjumpVisitor(line_mapper)
17
+ parse_tree.accept(ljv)
18
+ logging.info("Creating long jumps")
19
+
20
+ ljv.createLongjumps()
@@ -0,0 +1,53 @@
1
+ # A visitor for locating longjumps
2
+
3
+ from owl_basic.visitor import Visitor
4
+ from owl_basic import errors
5
+ from owl_basic.utility import underscoresToCamelCase
6
+ from owl_basic.syntax.ast import Goto, LongJump
7
+ from owl_basic.ast_utils import elideNode
8
+ from . import flow_analysis
9
+
10
+ class LongjumpVisitor(Visitor):
11
+ """
12
+ AST visitor for locating long jumps (jumps out of
13
+ procedures or functions) and replacing GOTOs with LONGJUMPs
14
+ """
15
+ def __init__(self, line_mapper):
16
+ self.line_mapper = line_mapper
17
+ self.longjumps = []
18
+
19
+ def visitAstNode(self, node):
20
+ node.forEachChild(self.visit)
21
+
22
+ def visitCfgVertex(self, vertex):
23
+ "Generic visitor for simple statements"
24
+
25
+ # Check the entry points
26
+ for following in vertex.outEdges:
27
+ if len(vertex.entryPoints) > 0:
28
+ # Not unreachable code
29
+ if following.entryPoints != vertex.entryPoints:
30
+ if isinstance(vertex, Goto):
31
+ self.longjumps.append(vertex)
32
+ else:
33
+ # TODO: Improve error message
34
+ errors.warning("Unreachable statement at line %s" % vertex.lineNum)
35
+
36
+ vertex.forEachChild(self.visit)
37
+
38
+ def createLongjumps(self):
39
+ """
40
+ Iterate through the discovered long jump locations, modify entry point
41
+ tags in the following statements, replace Goto nodes with Longjump nodes
42
+ and modify the control flow graph.
43
+ """
44
+ #print "createLongjumps"
45
+ for node in self.longjumps:
46
+ flow_analysis.deTagSuccessors(node)
47
+
48
+ for node in self.longjumps:
49
+ node.__class__ = LongJump
50
+ for target in node.outEdges:
51
+ target.inEdges.remove(node)
52
+ node.clearOutEdges()
53
+
@@ -0,0 +1,38 @@
1
+ '''
2
+ Convert subroutines with named PROCedures
3
+ '''
4
+
5
+ import logging
6
+
7
+ from owl_basic.syntax.ast import DefineProcedure
8
+ from owl_basic.ast_utils import insertStatementBefore
9
+ from .convert_sub_visitor import ConvertSubVisitor
10
+ from .flow_analysis import tagSuccessors, deTagSuccessors
11
+
12
+ logger = logging.getLogger('flow.subroutine_converter')
13
+
14
+ def convertSubroutinesToProcedures(parse_tree, entry_points, line_mapper, options):
15
+ logger.info("Convert subroutines to procedures")
16
+ entry_point_names_to_remove = []
17
+ entry_points_to_add = {}
18
+ for name, entry_point in entry_points.items():
19
+ # TODO: This will only work with simple (i.e. single entry) subroutines
20
+ print("name = %s, entry_point = %s" % (name, entry_point))
21
+ subname = iter(entry_point.entryPoints).next()
22
+ if subname.startswith('SUB'):
23
+ procname = 'PROCSub' + subname[3:]
24
+ assert len(entry_point.inEdges) == 0
25
+ defproc = DefineProcedure(name=procname, formalParameters=None)
26
+ insertStatementBefore(entry_point, defproc)
27
+ deTagSuccessors(entry_point)
28
+ entry_point.clearEntryPoints()
29
+ entry_point_names_to_remove.append(name)
30
+ entry_points_to_add[procname] = defproc
31
+ entry_point.clearComeFromGosubEdges()
32
+ tagSuccessors(defproc, line_mapper)
33
+ for name in entry_point_names_to_remove:
34
+ del entry_points[name]
35
+ entry_points.update(entry_points_to_add)
36
+
37
+ csv = ConvertSubVisitor()
38
+ parse_tree.accept(csv)
@@ -0,0 +1,110 @@
1
+ '''
2
+ Algorithms for traversal of the control flow graph
3
+ '''
4
+
5
+ import logging
6
+ from itertools import chain
7
+
8
+ logger = logging.getLogger('flow.traversal')
9
+
10
+ def depthFirstSearch(vertex, visited = None):
11
+ '''
12
+ A generator which performs depth first search from the supplied vertex through
13
+ the control flow graph.
14
+ :param vertex: A CFG Vertex from which depth first search will be performed
15
+ :param visited: A, optional set of vertices which need not be visited
16
+ :yields: Successive CfgVertices in a depth first traversal of the graph
17
+ '''
18
+ to_visit = []
19
+ if visited is None:
20
+ visited = set()
21
+ to_visit.append(vertex)
22
+ while len(to_visit) != 0:
23
+ v = to_visit.pop()
24
+ if v not in visited:
25
+ visited.add(v)
26
+ yield v
27
+ to_visit.extend(v.outEdges)
28
+
29
+ class ApproximateToplogicalOrderer(object):
30
+
31
+ def __init__(self, vertex, vertices_to_consider):
32
+ self.order = []
33
+ self.stack = []
34
+ self.cur_dfsnum = 0
35
+ self.index = {}
36
+ self.low = {}
37
+ self.vertices_to_consider = vertices_to_consider
38
+
39
+ for v in self.vertices_to_consider:
40
+ self.index[v] = "To be done"
41
+
42
+ # Special case the start vertex to prevent infinite recursion
43
+ self.index[vertex] = "Done"
44
+ for successor in chain(vertex.outEdges, vertex.loopBackEdges):
45
+ if successor in self.vertices_to_consider:
46
+ if self.index[successor] == "To be done":
47
+ self.visit(successor)
48
+ self.order.insert(0, vertex)
49
+
50
+ def visit(self, cur_vertex):
51
+ self.index[cur_vertex] = self.cur_dfsnum
52
+ self.low[cur_vertex] = self.cur_dfsnum
53
+ self.cur_dfsnum += 1
54
+ self.stack.append(cur_vertex)
55
+
56
+ for successor in chain(cur_vertex.outEdges, cur_vertex.loopBackEdges):
57
+ if successor in self.vertices_to_consider:
58
+ if self.index[successor] == "To be done":
59
+ self.visit(successor)
60
+ self.low[cur_vertex] = min(self.low[cur_vertex], self.low[successor])
61
+ elif self.index[successor] == "Done":
62
+ pass
63
+
64
+ #else:
65
+ elif successor in self.stack:
66
+ self.low[cur_vertex] = min(self.low[cur_vertex], self.index[successor])
67
+
68
+ if self.low[cur_vertex] == self.index[cur_vertex]:
69
+ # We found a strongly connected component
70
+ scc = []
71
+ while True:
72
+ popped = self.stack.pop()
73
+ scc.append(popped)
74
+ self.index[popped] = "Done"
75
+ if popped == cur_vertex:
76
+ break
77
+
78
+ if len(scc) == 1:
79
+ self.order.insert(0, cur_vertex)
80
+ else:
81
+ self.order = approximateTopologicalOrder(self.chooseFirst(scc), scc) + self.order
82
+
83
+ def chooseFirst(self, scc):
84
+ '''
85
+ Returns the vertex with the most in edges which are not in scc. This function
86
+ is used for computing a likely starting point for the SCC.
87
+ :param scc: A list of vertices comprising a strong-connected-component.
88
+ '''
89
+ sizes = [v.inDegree for v in scc]
90
+ index = sizes.index(max(sizes))
91
+ return scc[index]
92
+
93
+ def approximateTopologicalOrder(vertex, vertices_to_consider=None):
94
+ '''
95
+ A function which performs approximate topological ordering of the vertices
96
+ reachable from the supplied vertices, taking into account strongly
97
+ connected components in the graph.
98
+ :param vertex: The starting CfgVertex
99
+ :param vertices_to_consider: A set of vertices to which the search should be limited. If None, all vertices
100
+ reachable from vertex are used.
101
+ :returns A sequence of basic blocks in approximate toplogical order
102
+ '''
103
+ logger.debug("approximateTopologicalOrder(%s, %s)", str(vertex), str(vertices_to_consider))
104
+ vertices_under_consideration = set(depthFirstSearch(vertex)) if vertices_to_consider is None else vertices_to_consider
105
+ ato = ApproximateToplogicalOrderer(vertex, vertices_under_consideration)
106
+ assert set(ato.order) == set(vertices_under_consideration)
107
+ return ato.order
108
+
109
+
110
+