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.
- owl_basic/__init__.py +3 -0
- owl_basic/algorithms.py +29 -0
- owl_basic/ast_utils.py +204 -0
- owl_basic/basic_visitor.py +55 -0
- owl_basic/cfg_vertex.py +65 -0
- owl_basic/codegen/__init__.py +0 -0
- owl_basic/codegen/clr/__init__.py +0 -0
- owl_basic/codegen/clr/cil_visitor.py +1296 -0
- owl_basic/codegen/clr/cts.py +56 -0
- owl_basic/codegen/clr/emitters.py +94 -0
- owl_basic/codegen/clr/generate.py +539 -0
- owl_basic/correlation_visitor.py +119 -0
- owl_basic/data_visitor.py +62 -0
- owl_basic/decoder.py +339 -0
- owl_basic/errors.py +22 -0
- owl_basic/flow/__init__.py +17 -0
- owl_basic/flow/basic_block.py +34 -0
- owl_basic/flow/basic_block_identifier.py +66 -0
- owl_basic/flow/basic_block_orderer.py +29 -0
- owl_basic/flow/connectors.py +19 -0
- owl_basic/flow/convert_sub_visitor.py +28 -0
- owl_basic/flow/entry_point_locator.py +55 -0
- owl_basic/flow/entry_point_visitor.py +48 -0
- owl_basic/flow/flow_analysis.py +56 -0
- owl_basic/flow/flow_graph_creator.py +14 -0
- owl_basic/flow/flowgraph_visitor.py +178 -0
- owl_basic/flow/longjump_converter.py +20 -0
- owl_basic/flow/longjump_visitor.py +53 -0
- owl_basic/flow/subroutine_converter.py +38 -0
- owl_basic/flow/traversal.py +110 -0
- owl_basic/gml_visitor.py +151 -0
- owl_basic/line_mapper.py +43 -0
- owl_basic/line_number_visitor.py +65 -0
- owl_basic/main.py +381 -0
- owl_basic/node.py +21 -0
- owl_basic/options.py +22 -0
- owl_basic/owltyping/__init__.py +1 -0
- owl_basic/owltyping/function_type_inferer.py +50 -0
- owl_basic/owltyping/hindley_milner.py +524 -0
- owl_basic/owltyping/set_function_type_visitor.py +25 -0
- owl_basic/owltyping/type_system.py +220 -0
- owl_basic/owltyping/typecheck.py +60 -0
- owl_basic/owltyping/typecheck_visitor.py +471 -0
- owl_basic/parent_visitor.py +37 -0
- owl_basic/process.py +36 -0
- owl_basic/separation_visitor.py +98 -0
- owl_basic/sigil.py +30 -0
- owl_basic/simplify_visitor.py +204 -0
- owl_basic/singleton.py +127 -0
- owl_basic/source_debugging.py +124 -0
- owl_basic/symbol_table_visitor.py +220 -0
- owl_basic/symbol_tables.py +195 -0
- owl_basic/syntax/__init__.py +0 -0
- owl_basic/syntax/ast.py +1081 -0
- owl_basic/syntax/ast_meta.py +228 -0
- owl_basic/syntax/grammar.py +1972 -0
- owl_basic/syntax/lexer.py +943 -0
- owl_basic/syntax/parser.py +77 -0
- owl_basic/utility.py +26 -0
- owl_basic/visitor.py +43 -0
- owl_basic/xml_blocks.py +137 -0
- owl_basic/xml_visitor.py +101 -0
- owl_basic-0.6.0.dist-info/METADATA +37 -0
- owl_basic-0.6.0.dist-info/RECORD +69 -0
- owl_basic-0.6.0.dist-info/WHEEL +5 -0
- owl_basic-0.6.0.dist-info/entry_points.txt +2 -0
- owl_basic-0.6.0.dist-info/licenses/LICENSE +21 -0
- owl_basic-0.6.0.dist-info/licenses/THIRD-PARTY-NOTICES.md +57 -0
- 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
|
+
|