99Command = collections .namedtuple ("Command" , ["command" , "target" ])
1010
1111# Line of code. Not a namedtuple because we need mutability.
12+
13+
1214class CodeLine :
1315 def __init__ (self , number , text , terminating = False , noop = False ):
1416 self .number = number
@@ -24,15 +26,18 @@ def AddCommand(self, command):
2426
2527 def __repr__ (self ):
2628 return "[{0} (terminating: {1}, noop: {2}, commands: {3})] {4}" .format (self .number , self .terminating , self .noop , self .commands , self .text )
27-
29+
2830 def __eq__ (self , other ):
2931 return other is not None and self .number == other .number and self .text == other .text and self .terminating == other .terminating
3032
3133# Connection between two nodes.
32- # dst is the name of the target node, kind is the type of connection, line_number is the line where the call/goto happens.
34+ # dst is the name of the target node, kind is the type of connection,
35+ # line_number is the line where the call/goto happens.
3336Connection = collections .namedtuple ("Connection" , ["dst" , "kind" , "line_number" ])
3437
3538# Node in the call graph.
39+
40+
3641class Node :
3742 def __init__ (self , name ):
3843 self .name = name
@@ -46,38 +51,38 @@ def __init__(self, name):
4651
4752 def AddConnection (self , dst , kind , line_number = NO_LINE_NUMBER ):
4853 self .connections .add (Connection (dst , kind , line_number ))
49-
54+
5055 def AddCodeLine (self , line_number , code ):
5156 self .code .append (CodeLine (line_number , code .strip ().lower (), False ))
5257 self .loc += 1
53-
58+
5459 def GetCommandCount (self ):
5560 node_counter = collections .Counter ()
5661
5762 for line in self .code :
5863 for command , count in line .commands_counter .items ():
5964 node_counter [command ] += count
60-
6165 return node_counter
62-
66+
6367 def __repr__ (self ):
6468 return "{0}. {1}, {2}" .format (self .name , self .code , self .connections )
6569
6670 def __lt__ (self , other ):
67- if other == None :
71+ if other is None :
6872 return False
6973 return self .name < other .name
7074
75+
7176class CallGraph :
7277 def __init__ (self , log_file = sys .stderr ):
7378 self .nodes = {}
7479 self .log_file = log_file
7580 self .first_node = None
76-
81+
7782 def GetOrCreateNode (self , name ):
7883 if name in self .nodes :
7984 return self .nodes [name ]
80-
85+
8186 node = Node (name )
8287 self .nodes [name ] = node
8388 return node
@@ -86,7 +91,8 @@ def _MarkExitNodes(self):
8691 # A node is an exit node if:
8792 # 1. it contains an "exit" command with no target
8893 # or
89- # 2. it's reached from the starting node via "goto" or "nested" connections
94+ # 2. it's reached from the starting node via "goto" or
95+ # "nested" connections
9096 # and it contains an exit command or a "goto eof" command.
9197
9298 # Identify all nodes with an exit command with no targets.
@@ -98,7 +104,7 @@ def _MarkExitNodes(self):
98104
99105 # Visit the call graph to find nodes satisfying condition #2.
100106 q = [self .first_node ]
101- visited = set () # Used to avoid loops, since the call graph is not acyclic.
107+ visited = set () # Used to avoid loops, since the call graph is not acyclic.
102108
103109 while q :
104110 cur = q .pop ()
@@ -119,19 +125,22 @@ def _MarkExitNodes(self):
119125 continue
120126 if connection .kind == "nested" or connection .kind == "goto" :
121127 q .append (self .nodes [connection .dst ])
122-
123128
124- # Adds to each node information depending on the contents of the code, such as connections
125- # deriving from goto/call commands and whether the node is terminating or not.
129+ # Adds to each node information depending on the
130+ # contents of the code, such as connections
131+ # deriving from goto/call commands and
132+ # whether the node is terminating or not.
126133 def _AnnotateNode (self , node ):
127134 print (u"Annotating node {0} (line {1})" .format (node .original_name , node .line_number ), file = self .log_file )
128135 for i in range (len (node .code )):
129136 line = node .code [i ]
130137 line_number = line .number
131138 text = line .text
132139
133- # Tokenize the line of code, and store all elements that warrant augmenting the
134- # node in a list (interesting_commands), which will then be processed later.
140+ # Tokenize the line of code, and store all elements that
141+ # warrant augmenting the
142+ # node in a list (interesting_commands), which will then
143+ # be processed later.
135144 tokens = text .strip ().lower ().split ()
136145 if not tokens :
137146 line .noop = True
@@ -164,31 +173,32 @@ def _AnnotateNode(self, node):
164173 continue
165174 line .AddCommand (Command ("call" , block_name ))
166175 continue
167-
176+
168177 if token == "exit" or token == "@exit" :
169178 target = ""
170179 if i + 1 < len (tokens ):
171180 target = tokens [i + 1 ]
172181 line .AddCommand (Command ("exit" , target ))
173-
182+
174183 for command , target in line .commands :
175184 if command == "call" or command == "goto" :
176185 node .AddConnection (target , command , line_number )
177186 print (u"Line {} has a goto towards: <{}>. Current block: {}" .format (line_number , target , node .name ), file = self .log_file )
178-
187+
179188 if (command == "goto" and target == "eof" ) or command == "exit" :
180189 line .terminating = True
181190
182191 if command == "exit" and target == "" :
183192 line .terminating = True
184-
193+
185194 @staticmethod
186195 def Build (input_file , log_file = sys .stderr ):
187196 call_graph = CallGraph ._ParseSource (input_file , log_file )
188197 for node in call_graph .nodes .values ():
189198 call_graph ._AnnotateNode (node )
190-
191- # Prune away EOF if it is a virtual node (no line number) and there are no call/nested connections to it.
199+
200+ # Prune away EOF if it is a virtual node (no line number) and
201+ # there are no call/nested connections to it.
192202 eof = call_graph .GetOrCreateNode ("eof" )
193203 all_connections = itertools .chain .from_iterable (n .connections for n in call_graph .nodes .values ())
194204 destinations = set ((c .dst , c .kind ) for c in all_connections )
@@ -200,8 +210,9 @@ def Build(input_file, log_file=sys.stderr):
200210 print (u"Removing {} eof connections in node {}" .format (len (eof_connections ), node .name ), file = log_file )
201211 for c in eof_connections :
202212 node .connections .remove (c )
203-
204- # Warn the user if there are goto connections to eof, which will not be executed by CMD.
213+
214+ # Warn the user if there are goto connections to eof
215+ # which will not be executed by CMD.
205216 if eof .line_number != NO_LINE_NUMBER and ("eof" , "goto" ) in destinations :
206217 print (u"WARNING: there are goto connections to eof, but CMD will not execute that code via goto." , file = log_file )
207218
@@ -212,7 +223,8 @@ def Build(input_file, log_file=sys.stderr):
212223 cur_node = nodes_by_line_number [i ]
213224 prev_node = nodes_by_line_number [i - 1 ]
214225
215- # Special case: the previous node has no code or all lines are comments / empty lines.
226+ # Special case: the previous node has no code or all lines are
227+ # comments / empty lines.
216228 all_noop = all (line .noop for line in prev_node .code )
217229 if not prev_node .code or all_noop :
218230 print (u"Adding nested connection between {0} and {1} because all_noop ({2}) or empty code ({3})" .format (
@@ -221,15 +233,16 @@ def Build(input_file, log_file=sys.stderr):
221233 break
222234
223235 # Heuristic for "nested" connections:
224- # iterate the previous node's commands, and create a nested connection
225- # only if the command that logically precedes the current node does not
226- # contain a goto or an exit (which would mean that the current node is not reached
227- # by "flowing" from the previous node to the current node.)
236+ # iterate the previous node's commands, and create a nested
237+ # connection only if the command that logically precedes the
238+ # current node does not contain a goto or an exit (which would mean
239+ # that the current node is not reached by "flowing" from the
240+ # previous node to the current node.)
228241 for line in reversed (prev_node .code ):
229242 # Skip comments and empty lines.
230243 if line .noop :
231244 continue
232-
245+
233246 commands = set (c .command for c in line .commands )
234247 if "exit" not in commands and "goto" not in commands :
235248 print (u"Adding nested connection between {0} and {1} because there is a non-exit or non-goto command." .format (
@@ -246,10 +259,10 @@ def Build(input_file, log_file=sys.stderr):
246259
247260 return call_graph
248261
249- # Creates a call graph from an input file, parsing the file in blocks and creating
250- # one node for each block. Note that the nodes don't contain any information that
251- # depend on the contents of the node, as this is just the starting point for the
252- # processing.
262+ # Creates a call graph from an input file, parsing the file in blocks and
263+ # creating one node for each block. Note that the nodes don't contain any
264+ # information that depend on the contents of the node, as this is just the
265+ # starting point for the processing.
253266 @staticmethod
254267 def _ParseSource (input_file , log_file = sys .stderr ):
255268 call_graph = CallGraph (log_file )
@@ -267,10 +280,12 @@ def _ParseSource(input_file, log_file=sys.stderr):
267280
268281 # Start of new block.
269282 if line .startswith (":" ) and not line .startswith ("::" ):
270- # In the off chance that there are multiple words, cmd considers the first word the label name.
283+ # In the off chance that there are multiple words,
284+ # cmd considers the first word the label name.
271285 original_block_name = line [1 :].split ()[0 ].strip ()
272286
273- # Since cmd is case-insensitive, let's convert block names to lowercase.
287+ # Since cmd is case-insensitive, let's convert block names to
288+ # lowercase.
274289 block_name = original_block_name .lower ()
275290
276291 print (u"Line {} defines a new block: <{}>" .format (line_number , block_name ), file = log_file )
@@ -279,14 +294,15 @@ def _ParseSource(input_file, log_file=sys.stderr):
279294 next_node .line_number = line_number
280295 next_node .original_name = original_block_name
281296
282- # If this node is defined on line one, remove __begin__, so we avoid having two
297+ # If this node is defined on line one, remove __begin__,
298+ # so we avoid having two
283299 # nodes with the same line number.
284300 if line_number == 1 :
285301 del call_graph .nodes ["__begin__" ]
286302 call_graph .first_node = next_node
287303
288304 cur_node = next_node
289-
305+
290306 cur_node .AddCodeLine (line_number , line )
291307
292308 return call_graph
0 commit comments