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,204 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from owl_basic.utility import camelCaseToUnderscores
|
|
4
|
+
from owl_basic.visitor import Visitor
|
|
5
|
+
from owl_basic.node import *
|
|
6
|
+
from owl_basic.options import *
|
|
7
|
+
from owl_basic.ast_utils import elideNode, insertStatementAfter
|
|
8
|
+
from owl_basic.syntax.ast import StatementList, Next
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger('simplify_visitor')
|
|
11
|
+
|
|
12
|
+
class SimplifyStatementListVisitor(Visitor):
|
|
13
|
+
"""
|
|
14
|
+
Visitor for simplifying nested StatementList nodes by flattening the
|
|
15
|
+
list of statements.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self._accumulated_statements = []
|
|
19
|
+
|
|
20
|
+
def visitAstNode(self, node):
|
|
21
|
+
if node is not None:
|
|
22
|
+
self._accumulated_statements.append(node)
|
|
23
|
+
|
|
24
|
+
def visitStatementList(self, statement_list):
|
|
25
|
+
statement_list.forEachChild(self.visit)
|
|
26
|
+
|
|
27
|
+
def visitAstStatement(self, statement):
|
|
28
|
+
self._accumulated_statements.append(statement)
|
|
29
|
+
|
|
30
|
+
def _accumulatedStatements(self):
|
|
31
|
+
return self._accumulated_statements;
|
|
32
|
+
|
|
33
|
+
accumulatedStatements = property(_accumulatedStatements)
|
|
34
|
+
|
|
35
|
+
class SimplificationVisitor(Visitor):
|
|
36
|
+
"""
|
|
37
|
+
AST visitor for simplifying the AST, by removing redundant nodes.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def visitAstNode(self, node):
|
|
41
|
+
node.forEachChild(self.visit)
|
|
42
|
+
|
|
43
|
+
def visitStatementList(self, statement_list):
|
|
44
|
+
"Flatten nested StatementLists and remove Statement nodes."
|
|
45
|
+
if not hasattr(statement_list, "parent"):
|
|
46
|
+
print(statement_list.statements)
|
|
47
|
+
assert 0
|
|
48
|
+
sslv = SimplifyStatementListVisitor()
|
|
49
|
+
sslv.visit(statement_list)
|
|
50
|
+
statement_list.statements = sslv.accumulatedStatements
|
|
51
|
+
for index, statement in enumerate(statement_list.statements):
|
|
52
|
+
statement.parent = statement_list
|
|
53
|
+
statement.parent_property = "statements"
|
|
54
|
+
statement.parent_index = index
|
|
55
|
+
self.visit(statement)
|
|
56
|
+
|
|
57
|
+
statement_list.parent.child_infos["statements"] = statement_list.child_infos["statements"]
|
|
58
|
+
assert hasattr(statement_list, "statements")
|
|
59
|
+
statement_list.parent.statements = statement_list.statements
|
|
60
|
+
|
|
61
|
+
def visitIf(self, iff):
|
|
62
|
+
if isinstance(iff.trueClause, StatementList):
|
|
63
|
+
sslv = SimplifyStatementListVisitor()
|
|
64
|
+
sslv.visit(iff.trueClause)
|
|
65
|
+
iff.child_infos['true_clause'] = iff.trueClause.child_infos['statements']
|
|
66
|
+
iff.trueClause = sslv.accumulatedStatements
|
|
67
|
+
if len(iff.trueClause) == 0:
|
|
68
|
+
iff.trueClause = None
|
|
69
|
+
else:
|
|
70
|
+
for index, statement in enumerate(iff.trueClause):
|
|
71
|
+
statement.parent = iff
|
|
72
|
+
statement.parent_property = 'trueClause'
|
|
73
|
+
statement.parent_index = index
|
|
74
|
+
self.visit(statement)
|
|
75
|
+
else:
|
|
76
|
+
self.visit(iff.trueClause)
|
|
77
|
+
|
|
78
|
+
if isinstance(iff.falseClause, StatementList):
|
|
79
|
+
sslv = SimplifyStatementListVisitor()
|
|
80
|
+
sslv.visit(iff.falseClause)
|
|
81
|
+
iff.child_infos['false_clause'] = iff.falseClause.child_infos['statements']
|
|
82
|
+
iff.falseClause = sslv.accumulatedStatements
|
|
83
|
+
if len(iff.falseClause) == 0:
|
|
84
|
+
iff.falseClause = None
|
|
85
|
+
else:
|
|
86
|
+
for index, statement in enumerate(iff.falseClause):
|
|
87
|
+
statement.parent = iff
|
|
88
|
+
statement.parent_property = 'falseClause'
|
|
89
|
+
statement.parent_index = index
|
|
90
|
+
self.visit(statement)
|
|
91
|
+
else:
|
|
92
|
+
self.visit(iff.falseClause)
|
|
93
|
+
|
|
94
|
+
self.visit(iff.condition)
|
|
95
|
+
|
|
96
|
+
def visitOnGoto(self, ongoto):
|
|
97
|
+
if ongoto.outOfRangeClause is not None:
|
|
98
|
+
if isinstance(ongoto.outOfRangeClause, StatementList):
|
|
99
|
+
sslv = SimplifyStatementListVisitor()
|
|
100
|
+
sslv.visit(ongoto.outOfRangeClause)
|
|
101
|
+
ongoto.child_infos['out_of_range_clause'] = ongoto.outOfRangeClause.child_infos['statements']
|
|
102
|
+
ongoto.outOfRangeClause = sslv.accumulatedStatements
|
|
103
|
+
if len(ongoto.outOfRangeClause) == 0:
|
|
104
|
+
ongoto.outOfRangeClause = None
|
|
105
|
+
else:
|
|
106
|
+
for index, statement in enumerate(ongoto.outOfRangeClause):
|
|
107
|
+
statement.parent = ongoto
|
|
108
|
+
statement.parent_property = 'outOfRangeClause'
|
|
109
|
+
statement.parent_index = index
|
|
110
|
+
self.visit(statement)
|
|
111
|
+
else:
|
|
112
|
+
self.visit(ongoto.outOfRangeClause)
|
|
113
|
+
|
|
114
|
+
self.visit(ongoto.switch)
|
|
115
|
+
self.visit(ongoto.targetLogicalLines)
|
|
116
|
+
|
|
117
|
+
def visitCase(self, case):
|
|
118
|
+
"Remove the WhenClauseList level from the AST"
|
|
119
|
+
case.child_infos["when_clauses"] = case.whenClauses.child_infos["clauses"]
|
|
120
|
+
case.whenClauses = case.whenClauses.clauses
|
|
121
|
+
for clause in case.whenClauses:
|
|
122
|
+
clause.parent = case
|
|
123
|
+
self.visit(clause)
|
|
124
|
+
|
|
125
|
+
def visitMarkerStatement(self, marker):
|
|
126
|
+
"""
|
|
127
|
+
Remove the followingStatement from the Repeat, DefineProcedure, etc, moving it to immediately
|
|
128
|
+
after the statement in the parent StatementList
|
|
129
|
+
"""
|
|
130
|
+
logger.debug("visitMarkerStatement %s at line number %s", marker, marker.lineNum)
|
|
131
|
+
if marker.followingStatement is not None:
|
|
132
|
+
following = marker.followingStatement
|
|
133
|
+
marker.followingStatement = None
|
|
134
|
+
insertStatementAfter(marker, following)
|
|
135
|
+
# TODO: Does the moved statement node ever get visited? Are we inserting into a sequence during iteration?
|
|
136
|
+
#self.visit(following)
|
|
137
|
+
|
|
138
|
+
def visitExpressionList(self, expr_list):
|
|
139
|
+
"""
|
|
140
|
+
Remove ExpressionList level from the AST by replacing the contents of
|
|
141
|
+
the owning attribute of its parents with the ExpressionList's own list of expressions
|
|
142
|
+
"""
|
|
143
|
+
expr_list.forEachChild(self.visit)
|
|
144
|
+
elideNode(expr_list, liftFormalTypes=True)
|
|
145
|
+
|
|
146
|
+
def visitVduList(self, vdu_list):
|
|
147
|
+
"""
|
|
148
|
+
Remove VduList level from the AST by replacing the contents of
|
|
149
|
+
the owning attribute of its parent with the VduList's own list of items
|
|
150
|
+
"""
|
|
151
|
+
vdu_list.forEachChild(self.visit)
|
|
152
|
+
elideNode(vdu_list, liftFormalTypes=True)
|
|
153
|
+
|
|
154
|
+
def visitActualArgList(self, actual_arg_list):
|
|
155
|
+
"""
|
|
156
|
+
Remove the ActualArgList level from the AST by replacing the contents of
|
|
157
|
+
the owning attribute of its parent with the ActualArgList's own list of arguments
|
|
158
|
+
"""
|
|
159
|
+
actual_arg_list.forEachChild(self.visit)
|
|
160
|
+
elideNode(actual_arg_list, liftFormalTypes=True)
|
|
161
|
+
|
|
162
|
+
def visitFormalArgList(self, formal_arg_list):
|
|
163
|
+
"""
|
|
164
|
+
Remove the FormalArgList level from the AST by replacing the contents of
|
|
165
|
+
the owning attribute of its parent with the FormalArgList's own list of arguments
|
|
166
|
+
"""
|
|
167
|
+
formal_arg_list.forEachChild(self.visit)
|
|
168
|
+
elideNode(formal_arg_list, liftFormalTypes=True)
|
|
169
|
+
|
|
170
|
+
def visitPrintList(self, print_list):
|
|
171
|
+
"""
|
|
172
|
+
Remove the PrintList level from the AST by replacing the contents of the
|
|
173
|
+
owning attribute of its parent.
|
|
174
|
+
"""
|
|
175
|
+
print_list.forEachChild(self.visit)
|
|
176
|
+
elideNode(print_list, liftFormalTypes=True)
|
|
177
|
+
|
|
178
|
+
def visitInputList(self, input_list):
|
|
179
|
+
"""
|
|
180
|
+
Remove the InputList level from the AST by replacing the contents of the
|
|
181
|
+
owning attribute of its parent.
|
|
182
|
+
"""
|
|
183
|
+
input_list.forEachChild(self.visit)
|
|
184
|
+
elideNode(input_list, liftFormalTypes=True)
|
|
185
|
+
|
|
186
|
+
def visitVariableList(self, variable_list):
|
|
187
|
+
"""
|
|
188
|
+
Remove the VariableList level from the AST by replacing the contents of the
|
|
189
|
+
owning attribute of its parent.
|
|
190
|
+
"""
|
|
191
|
+
variable_list.forEachChild(self.visit)
|
|
192
|
+
elideNode(variable_list, liftFormalTypes=True)
|
|
193
|
+
|
|
194
|
+
def visitExpressionList(self, expression_list):
|
|
195
|
+
"""
|
|
196
|
+
Remove the ExpresionList level from the AST by replacing the contents of the
|
|
197
|
+
owning attribute of its parent.
|
|
198
|
+
"""
|
|
199
|
+
expression_list.forEachChild(self.visit)
|
|
200
|
+
elideNode(expression_list, liftFormalTypes=True)
|
|
201
|
+
|
|
202
|
+
# TODO: visitInputList
|
|
203
|
+
|
|
204
|
+
|
owl_basic/singleton.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A Python Singleton mixin class that makes use of some of the ideas
|
|
3
|
+
found at http://c2.com/cgi/wiki?PythonSingleton. Just inherit
|
|
4
|
+
from it and you have a singleton. No code is required in
|
|
5
|
+
subclasses to create singleton behavior -- inheritance from
|
|
6
|
+
Singleton is all that is needed.
|
|
7
|
+
|
|
8
|
+
Assume S is a class that inherits from Singleton. Useful behaviors
|
|
9
|
+
are:
|
|
10
|
+
|
|
11
|
+
1) Getting the singleton:
|
|
12
|
+
|
|
13
|
+
S.getInstance()
|
|
14
|
+
|
|
15
|
+
returns the instance of S. If none exists, it is created.
|
|
16
|
+
|
|
17
|
+
2) The usual idiom to construct an instance by calling the class, i.e.
|
|
18
|
+
|
|
19
|
+
S()
|
|
20
|
+
|
|
21
|
+
is disabled for the sake of clarity. If it were allowed, a programmer
|
|
22
|
+
who didn't happen notice the inheritance from Singleton might think he
|
|
23
|
+
was creating a new instance. So it is felt that it is better to
|
|
24
|
+
make that clearer by requiring the call of a class method that is defined in
|
|
25
|
+
Singleton. An attempt to instantiate via S() will restult in an SingletonException
|
|
26
|
+
being raised.
|
|
27
|
+
|
|
28
|
+
3) If S.__init__(.) requires parameters, include them in the
|
|
29
|
+
first call to S.getInstance(.). If subsequent calls have parameters,
|
|
30
|
+
a SingletonException is raised.
|
|
31
|
+
|
|
32
|
+
4) As an implementation detail, classes that inherit
|
|
33
|
+
from Singleton may not have their own __new__
|
|
34
|
+
methods. To make sure this requirement is followed,
|
|
35
|
+
an exception is raised if a Singleton subclass includ
|
|
36
|
+
es __new__. This happens at subclass instantiation
|
|
37
|
+
time (by means of the MetaSingleton metaclass.
|
|
38
|
+
|
|
39
|
+
By Gary Robinson, grobinson@transpose.com. No rights reserved --
|
|
40
|
+
placed in the public domain -- which is only reasonable considering
|
|
41
|
+
how much it owes to other people's version which are in the
|
|
42
|
+
public domain. The idea of using a metaclass came from
|
|
43
|
+
a comment on Gary's blog (see
|
|
44
|
+
http://www.garyrobinson.net/2004/03/python_singleto.html#comments).
|
|
45
|
+
Other improvements came from comments and email from other
|
|
46
|
+
people who saw it online. (See the blog post and comments
|
|
47
|
+
for further credits.)
|
|
48
|
+
|
|
49
|
+
Not guaranteed to be fit for any particular purpose. Use at your
|
|
50
|
+
own risk.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SingletonException(Exception):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class MetaSingleton(type):
|
|
62
|
+
def __new__(metaclass, strName, tupBases, dict):
|
|
63
|
+
if '__new__' in dict:
|
|
64
|
+
raise SingletonException('Can not override __new__ in a Singleton')
|
|
65
|
+
return super(MetaSingleton,metaclass).__new__(metaclass, strName, tupBases, dict)
|
|
66
|
+
|
|
67
|
+
def __call__(cls, *lstArgs, **dictArgs):
|
|
68
|
+
raise SingletonException('Singletons may only be instantiated through getInstance()')
|
|
69
|
+
|
|
70
|
+
class Singleton(object, metaclass=MetaSingleton):
|
|
71
|
+
def __init__(self, *args, **kwargs):
|
|
72
|
+
"""
|
|
73
|
+
Provided for compatibility with subclasses which call super() in their __init__
|
|
74
|
+
"""
|
|
75
|
+
super(Singleton, self).__init__(*args, **kwargs)
|
|
76
|
+
|
|
77
|
+
# @TODO: This method NEEDS to be thread-safe
|
|
78
|
+
def getInstance(cls, *lstArgs, **kwargs):
|
|
79
|
+
"""
|
|
80
|
+
Call this to instantiate an instance or retrieve the existing instance.
|
|
81
|
+
If the singleton requires args to be instantiated, include them the first
|
|
82
|
+
time you call getInstance.
|
|
83
|
+
"""
|
|
84
|
+
if cls._isInstantiated():
|
|
85
|
+
if len(lstArgs) + len(kwargs) != 0:
|
|
86
|
+
raise SingletonException('If no supplied args, singleton must already be instantiated, or __init__ must require no args')
|
|
87
|
+
else:
|
|
88
|
+
instance = cls.__new__(cls)
|
|
89
|
+
instance.__init__(*lstArgs, **kwargs)
|
|
90
|
+
cls.cInstance = instance
|
|
91
|
+
return cls.cInstance
|
|
92
|
+
getInstance = classmethod(getInstance)
|
|
93
|
+
|
|
94
|
+
def _isInstantiated(cls):
|
|
95
|
+
return hasattr(cls, 'cInstance')
|
|
96
|
+
_isInstantiated = classmethod(_isInstantiated)
|
|
97
|
+
|
|
98
|
+
def _getConstructionArgCountNotCountingSelf(cls):
|
|
99
|
+
return cls.__init__.im_func.func_code.co_argcount - 1
|
|
100
|
+
_getConstructionArgCountNotCountingSelf = classmethod(_getConstructionArgCountNotCountingSelf)
|
|
101
|
+
|
|
102
|
+
def _forgetClassInstanceReferenceForTesting(cls):
|
|
103
|
+
"""
|
|
104
|
+
This is designed for convenience in testing -- sometimes you
|
|
105
|
+
want to get rid of a singleton during test code to see what
|
|
106
|
+
happens when you call getInstance() under a new situation.
|
|
107
|
+
|
|
108
|
+
To really delete the object, all external references to it
|
|
109
|
+
also need to be deleted.
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
delattr(cls,'cInstance')
|
|
113
|
+
except AttributeError:
|
|
114
|
+
# run up the chain of base classes until we find the one that has the instance
|
|
115
|
+
# and then delete it there
|
|
116
|
+
for baseClass in cls.__bases__:
|
|
117
|
+
if issubclass(baseClass, Singleton):
|
|
118
|
+
baseClass._forgetClassInstanceReferenceForTesting()
|
|
119
|
+
_forgetClassInstanceReferenceForTesting = classmethod(_forgetClassInstanceReferenceForTesting)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == '__main__':
|
|
124
|
+
|
|
125
|
+
unittest.main()
|
|
126
|
+
|
|
127
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from bisect import bisect_right
|
|
4
|
+
|
|
5
|
+
from owl_basic.visitor import Visitor
|
|
6
|
+
|
|
7
|
+
class SourceDebuggingVisitor(Visitor):
|
|
8
|
+
'''
|
|
9
|
+
A visitor for computing start and end character columns based upon
|
|
10
|
+
file offsets attached to each AstStatement node and offsets to the beginning
|
|
11
|
+
of each line. The operation of this visitor assumes that a depth-first traversal
|
|
12
|
+
of the AST will visit all statements in source program order.
|
|
13
|
+
'''
|
|
14
|
+
def __init__(self, data, line_offsets, line_number_prefixes):
|
|
15
|
+
'''
|
|
16
|
+
:param data: The source file as a string
|
|
17
|
+
:param line_offsets: an array of line offsets
|
|
18
|
+
:param line_number_prexies: an array of the lengths of the line-number and
|
|
19
|
+
whitespace prefix to each source line
|
|
20
|
+
'''
|
|
21
|
+
self.__data = data
|
|
22
|
+
self.__line_offsets = line_offsets
|
|
23
|
+
self.__line_number_prefixes = line_number_prefixes
|
|
24
|
+
self.__previous_statement = None
|
|
25
|
+
self.__separator_regex = re.compile(r'\s*([\n:]|ELSE)')
|
|
26
|
+
|
|
27
|
+
def visitAstNode(self, node):
|
|
28
|
+
node.forEachChild(self.visit)
|
|
29
|
+
|
|
30
|
+
def visitAstStatement(self, statement):
|
|
31
|
+
'''
|
|
32
|
+
Get the startPos of the visited statement. Use this value, together with the
|
|
33
|
+
probably innaccurate endPos of the *previous* statement to locate the end of
|
|
34
|
+
the previous statement, and update its endPos value.
|
|
35
|
+
'''
|
|
36
|
+
#print statement
|
|
37
|
+
self.setPreviousStatementColumns(statement)
|
|
38
|
+
self.__previous_statement = statement
|
|
39
|
+
# There is no need to visit the children of most statements
|
|
40
|
+
|
|
41
|
+
def visitIf(self, statement):
|
|
42
|
+
#print statement
|
|
43
|
+
self.setPreviousStatementColumns(statement)
|
|
44
|
+
# If statements, don't need adjusting, so we unset self.__previous_statement
|
|
45
|
+
self.__previous_statement = None
|
|
46
|
+
# We must visit the children of IF statements
|
|
47
|
+
if statement.trueClause is not None:
|
|
48
|
+
#print "visiting trueClause"
|
|
49
|
+
self.visit(statement.trueClause)
|
|
50
|
+
|
|
51
|
+
if statement.falseClause is not None:
|
|
52
|
+
#print "visiting falseClause"
|
|
53
|
+
self.visit(statement.falseClause)
|
|
54
|
+
|
|
55
|
+
# Set up the columns for this IF statement itself
|
|
56
|
+
self.setStartAndEndColumns(statement)
|
|
57
|
+
|
|
58
|
+
def setPreviousStatementColumns(self, statement):
|
|
59
|
+
'''
|
|
60
|
+
Update the endPos attribute on self.__previous_statement based upon the startPos attribute
|
|
61
|
+
of the statement parameters. A search between the two statements for a statement separator
|
|
62
|
+
is performed, and computations of the start and end character columns for __previous_statement
|
|
63
|
+
are performed and these attributes set.
|
|
64
|
+
Note: Assumes that self.__previous statement is the prior statement in the program
|
|
65
|
+
order to statement
|
|
66
|
+
:param statement: The statement being visited, the start of which marks the latest position
|
|
67
|
+
for the end of the previous statement.
|
|
68
|
+
'''
|
|
69
|
+
if self.__previous_statement is not None:
|
|
70
|
+
self.adjustEndPos(self.__previous_statement, statement.startPos)
|
|
71
|
+
self.setStartAndEndColumns(self.__previous_statement)
|
|
72
|
+
|
|
73
|
+
def adjustEndPos(self, statement, limit_pos):
|
|
74
|
+
'''
|
|
75
|
+
Adjust the endPos attribute of statement forwards by searching from its existing
|
|
76
|
+
location forwards up to limit_pos for a statement separator (colon or newline)
|
|
77
|
+
'''
|
|
78
|
+
# Define a half-open range [search_start_pos, search_end_pos) within which we expect
|
|
79
|
+
# to locate a statement boundary.
|
|
80
|
+
#print statement
|
|
81
|
+
#print statement.startPos
|
|
82
|
+
#print statement.endPos
|
|
83
|
+
search_start_pos = statement.endPos
|
|
84
|
+
search_end_pos = limit_pos
|
|
85
|
+
assert search_start_pos is not None
|
|
86
|
+
assert search_end_pos is not None
|
|
87
|
+
if search_start_pos < search_end_pos - 1:
|
|
88
|
+
# TODO: Some possible issue here with line end markers (because of normalised line endings), ignoring the contents of REMs, DATA and LiteralStrings
|
|
89
|
+
# and line continuation
|
|
90
|
+
# Search for new-lines or COLONs within the search string
|
|
91
|
+
m = self.__separator_regex.search(self.__data, search_start_pos, search_end_pos)
|
|
92
|
+
if m is not None:
|
|
93
|
+
statement.endPos = m.start()
|
|
94
|
+
else:
|
|
95
|
+
print("Error: Could not locate statement separator in >>>%s<<<" % self.__data[search_start_pos:search_end_pos])
|
|
96
|
+
#print ">>>%s<<<" % self.__data[statement.startPos:statement.endPos]
|
|
97
|
+
|
|
98
|
+
def setStartAndEndColumns(self, statement):
|
|
99
|
+
'''
|
|
100
|
+
Sets the start and end columns on statement from start and end lexing positions.
|
|
101
|
+
:param statement: The statement node to be modified.
|
|
102
|
+
'''
|
|
103
|
+
statement.startColumn = self.columnFromPos(statement.startPos)
|
|
104
|
+
statement.endColumn = self.columnFromPos(statement.endPos)
|
|
105
|
+
|
|
106
|
+
def columnFromPos(self, pos):
|
|
107
|
+
'''
|
|
108
|
+
:param pos: An zero-based offset from the beginning of the source file
|
|
109
|
+
:returns: A one-based character column from the beginning of the line
|
|
110
|
+
'''
|
|
111
|
+
# TODO: Could make this faster by passing in a range on lines to check
|
|
112
|
+
#print "pos =", pos
|
|
113
|
+
index = bisect_right(self.__line_offsets, pos) - 1
|
|
114
|
+
#print "index =", index
|
|
115
|
+
line_start_pos = self.__line_offsets[index]
|
|
116
|
+
#print "line_start_pos =", line_start_pos
|
|
117
|
+
prefix_length = self.__line_number_prefixes[index]
|
|
118
|
+
#print "prefix_length =", prefix_length
|
|
119
|
+
column = pos - line_start_pos + prefix_length + 1 # One based
|
|
120
|
+
#print "column =", column
|
|
121
|
+
#print
|
|
122
|
+
return column
|
|
123
|
+
|
|
124
|
+
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# A visitor for performing type-checking over the Abstract Syntax Tree
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from functools import partial
|
|
5
|
+
|
|
6
|
+
from owl_basic.visitor import Visitor
|
|
7
|
+
from owl_basic.errors import *
|
|
8
|
+
from owl_basic.symbol_tables import *
|
|
9
|
+
from owl_basic.syntax.ast import FormalArgument, FormalReferenceArgument, Variable, AstStatement
|
|
10
|
+
from owl_basic.ast_utils import findNode
|
|
11
|
+
from owl_basic import sigil
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger('symbol_table_visitor')
|
|
14
|
+
|
|
15
|
+
class SymbolTableVisitor(Visitor):
|
|
16
|
+
"""
|
|
17
|
+
CFG visitor for annotating statement nodes with
|
|
18
|
+
references to a symbol table.
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.__global_symbols = SymbolTable("global symbol table",
|
|
22
|
+
protection=SymbolTable.writable,
|
|
23
|
+
parent=SystemSymbolTable.getInstance())
|
|
24
|
+
|
|
25
|
+
def _getGlobalSymbols(self):
|
|
26
|
+
return self.__global_symbols
|
|
27
|
+
|
|
28
|
+
globalSymbols = property(_getGlobalSymbols)
|
|
29
|
+
|
|
30
|
+
def followSuccessors(self, statement):
|
|
31
|
+
# Visit successors - depth first through CFG
|
|
32
|
+
for out_edge in statement.outEdges:
|
|
33
|
+
if out_edge.symbolTable is None:
|
|
34
|
+
self.visit(out_edge)
|
|
35
|
+
|
|
36
|
+
def checkPredecessorsAndRefer(self, statement):
|
|
37
|
+
"""
|
|
38
|
+
Given a statement, return the symbol table of the
|
|
39
|
+
preceding statement. If a statement has multiple predecessors,
|
|
40
|
+
check that all predecessors refer to the same
|
|
41
|
+
symbol table - raise an error if not.
|
|
42
|
+
"""
|
|
43
|
+
if statement.symbolTable is None:
|
|
44
|
+
symbol_table = None
|
|
45
|
+
for in_edge in statement.inEdges:
|
|
46
|
+
if in_edge.symbolTable is not None:
|
|
47
|
+
if symbol_table is None:
|
|
48
|
+
symbol_table = in_edge.symbolTable
|
|
49
|
+
else:
|
|
50
|
+
if in_edge.symbolTable is not symbol_table:
|
|
51
|
+
errors.fatalError("Inconsistent variable scopes for %s at line %s" % statement, statement.lineNum)
|
|
52
|
+
assert symbol_table is not None
|
|
53
|
+
return symbol_table
|
|
54
|
+
return statement.symbolTable
|
|
55
|
+
|
|
56
|
+
def tryAddVariable(self, symbol_table, variable):
|
|
57
|
+
if (isinstance(variable, Variable)):
|
|
58
|
+
symbol_info = SymbolInfo(variable.identifier, variable.actualType)
|
|
59
|
+
symbol_table.tryAdd(symbol_info)
|
|
60
|
+
else:
|
|
61
|
+
assert 0, "%s is not a variable" % variable
|
|
62
|
+
# TODO: What?
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def visitAstStatement(self, statement):
|
|
66
|
+
"""
|
|
67
|
+
Attaches the same symbol table as the predecessor statement to this
|
|
68
|
+
statement Depth first search visit of successors statements
|
|
69
|
+
"""
|
|
70
|
+
#logger.debug("SymbolTableVisitor.visitAstStatement %s at %s", statement, statement.lineNum)
|
|
71
|
+
statement.symbolTable = self.checkPredecessorsAndRefer(statement)
|
|
72
|
+
assert statement.symbolTable is not None
|
|
73
|
+
# TODO: Check that all other variable references within this statement can
|
|
74
|
+
# be successfully looked up.
|
|
75
|
+
self.followSuccessors(statement)
|
|
76
|
+
|
|
77
|
+
def visitDefinitionStatement(self, defproc):
|
|
78
|
+
"""
|
|
79
|
+
Visit DEFPROC DEFFN. Create a new symbol table containing the formal
|
|
80
|
+
parameters of the procedure or function, which also refers to the global symbol
|
|
81
|
+
table.
|
|
82
|
+
"""
|
|
83
|
+
#logger.debug("SymbolTableVisitor.visitDefinitionStatement")
|
|
84
|
+
if defproc.symbolTable is None:
|
|
85
|
+
symbol_table = None
|
|
86
|
+
if defproc.formalParameters is None or len(defproc.formalParameters.arguments) == 0:
|
|
87
|
+
# If there are no parameters, we just use the
|
|
88
|
+
# global symbol table
|
|
89
|
+
symbol_table = self.__global_symbols
|
|
90
|
+
else:
|
|
91
|
+
symbol_infos = []
|
|
92
|
+
|
|
93
|
+
for formal_argument in defproc.formalParameters.arguments:
|
|
94
|
+
name = formal_argument.argument.identifier
|
|
95
|
+
type = formal_argument.argument.actualType
|
|
96
|
+
if isinstance(formal_argument.argument, FormalArgument):
|
|
97
|
+
modifier = SymbolInfo.modifier_arg
|
|
98
|
+
elif isinstance(formal_argument.argument, FormalReferenceArgument):
|
|
99
|
+
modifier = SymbolInfo.modifier_ref_arg
|
|
100
|
+
symbol_info = SymbolInfo(name, type, SymbolInfo.modifier_arg)
|
|
101
|
+
symbol_infos.append(symbol_info)
|
|
102
|
+
symbol_table = FormalParameterSymbolTable(symbol_infos, defproc.name, self.__global_symbols)
|
|
103
|
+
assert symbol_table is not None
|
|
104
|
+
defproc.symbolTable = symbol_table
|
|
105
|
+
self.followSuccessors(defproc)
|
|
106
|
+
|
|
107
|
+
def visitLocal(self, local):
|
|
108
|
+
#logger.debug("SymbolTableVisitor.visitLocal")
|
|
109
|
+
# TODO: We should have a warning if LOCAL and PRIVATE are not the first
|
|
110
|
+
# statements in a definition
|
|
111
|
+
# TODO: REFACTOR This is almost identical to visitPrivate
|
|
112
|
+
if 'MAIN' in local.entryPoints:
|
|
113
|
+
errors.fatalError("Items can only be made local in a function or procedure at line %s" % local.lineNum)
|
|
114
|
+
if local.symbolTable is None:
|
|
115
|
+
symbol_infos = []
|
|
116
|
+
for variable in local.variables:
|
|
117
|
+
name = variable.identifier
|
|
118
|
+
type = variable.actualType
|
|
119
|
+
symbol_info = SymbolInfo(name, type, SymbolInfo.modifier_local)
|
|
120
|
+
symbol_infos.append(symbol_info)
|
|
121
|
+
assert len(local.entryPoints) == 1
|
|
122
|
+
procedure = iter(local.entryPoints).next()
|
|
123
|
+
symbol_table = LocalSymbolTable(symbol_infos, procedure, self.checkPredecessorsAndRefer(local))
|
|
124
|
+
assert symbol_table is not None
|
|
125
|
+
local.symbolTable = symbol_table
|
|
126
|
+
self.followSuccessors(local)
|
|
127
|
+
|
|
128
|
+
def visitPrivate(self, private):
|
|
129
|
+
#logger.debug("SymbolTableVisitor.visitPrivate")
|
|
130
|
+
# TODO: We should have a warning if LOCAL and PRIVATE are not the first
|
|
131
|
+
# statements in a definition
|
|
132
|
+
# TODO: REFACTOR This is almost identical to visitLocal
|
|
133
|
+
if 'MAIN' in private.entryPoints:
|
|
134
|
+
errors.fatalError("Items can only be made local in a function or procedure at line %s" % local.lineNum)
|
|
135
|
+
if private.symbolTable is None:
|
|
136
|
+
symbol_infos = []
|
|
137
|
+
for variable in private.variables:
|
|
138
|
+
name = variable.identifier
|
|
139
|
+
type = variable.actualType
|
|
140
|
+
symbol_info = SymbolInfo(name, type, SymbolInfo.modifier_private)
|
|
141
|
+
symbol_infos.append(symbol_info)
|
|
142
|
+
assert len(private.entryPoints) == 1
|
|
143
|
+
procedure = iter(private.entryPoints).next()
|
|
144
|
+
symbol_table = PrivateSymbolTable(symbol_infos, procedure, self.checkPredecessorsAndRefer(private))
|
|
145
|
+
assert symbol_table is not None
|
|
146
|
+
private.symbolTable = symbol_table
|
|
147
|
+
self.followSuccessors(private)
|
|
148
|
+
|
|
149
|
+
def visitAssignment(self, statement):
|
|
150
|
+
logger.debug("SymbolTableVisitor.visitAssignment")
|
|
151
|
+
print(statement)
|
|
152
|
+
#assert statement.symbolTable is not None
|
|
153
|
+
statement.symbolTable = self.checkPredecessorsAndRefer(statement)
|
|
154
|
+
#self.tryAddVariable(statement.symbolTable, statement.lValue)
|
|
155
|
+
statement.lValue.accept(self)
|
|
156
|
+
self.followSuccessors(statement)
|
|
157
|
+
|
|
158
|
+
def visitVariable(self, variable):
|
|
159
|
+
logger.debug("SymbolTableVisitor.visitVariable")
|
|
160
|
+
statement_node = findNode(variable, lambda node: isinstance(node, AstStatement))
|
|
161
|
+
symbol_table = statement_node.symbolTable
|
|
162
|
+
self.tryAddVariable(symbol_table, variable)
|
|
163
|
+
|
|
164
|
+
def visitAllocateArray(self, statement):
|
|
165
|
+
logger.debug("SymbolTableVisitor.allocateArray")
|
|
166
|
+
statement.symbolTable = self.checkPredecessorsAndRefer(statement)
|
|
167
|
+
symbol_info = SymbolInfo(statement.identifier, sigil.identifierToType(statement.identifier),
|
|
168
|
+
rank=len(statement.dimensions))
|
|
169
|
+
statement.symbolTable.tryAdd(symbol_info)
|
|
170
|
+
self.followSuccessors(statement)
|
|
171
|
+
|
|
172
|
+
def visitAllocateBlock(self, statement):
|
|
173
|
+
#logger.debug("SymbolTableVisitor.allocateBlock")
|
|
174
|
+
statement.symbolTable = self.checkPredecessorsAndRefer(statement)
|
|
175
|
+
assert statement.symbolTable is not None
|
|
176
|
+
symbol_info = SymbolInfo(statement.identifier, PtrType)
|
|
177
|
+
self.followSuccessors(statement)
|
|
178
|
+
|
|
179
|
+
def visitForToStep(self, statement):
|
|
180
|
+
#logger.debug("SymbolTableVisitor.visitForToStep")
|
|
181
|
+
statement.symbolTable = self.checkPredecessorsAndRefer(statement)
|
|
182
|
+
assert statement.symbolTable is not None
|
|
183
|
+
self.tryAddVariable(statement.symbolTable, statement.identifier)
|
|
184
|
+
self.followSuccessors(statement)
|
|
185
|
+
|
|
186
|
+
def visitInput(self, statement):
|
|
187
|
+
#logger.debug("SymbolTableVisitor.visitInput")
|
|
188
|
+
statement.symbolTable = self.checkPredecessorsAndRefer(statement)
|
|
189
|
+
assert statement.symbolTable is not None
|
|
190
|
+
variables = (item for item in statement.inputList if isinstance(item, Variable))
|
|
191
|
+
for variable in variables:
|
|
192
|
+
self.tryAddVariable(statement.symbolTable, variable)
|
|
193
|
+
self.followSuccessors(statement)
|
|
194
|
+
|
|
195
|
+
def visitInputFile(self, input):
|
|
196
|
+
#logger.debug("SymbolTableVisitor.visitInputFile")
|
|
197
|
+
statement.symbolTable = self.checkPredecessorsAndRefer(statement)
|
|
198
|
+
assert statement.symbolTable is not None
|
|
199
|
+
for item in statement.inputList:
|
|
200
|
+
self.tryAddVariable(statement.symbolTable, item)
|
|
201
|
+
self.followSuccessors(input)
|
|
202
|
+
|
|
203
|
+
def visitMouse(self, mouse):
|
|
204
|
+
#logger.debug("SymbolTableVisitor.visitInput")
|
|
205
|
+
statement.symbolTable = self.checkPredecessorsAndRefer(statement)
|
|
206
|
+
assert statement.symbolTable is not None
|
|
207
|
+
self.tryAddVariable(statement.symbolTable, statement.xCoord)
|
|
208
|
+
self.tryAddVariable(statement.symbolTable, statement.yCoord)
|
|
209
|
+
self.tryAddVariable(statement.symbolTable, statement.buttons)
|
|
210
|
+
self.tryAddVariable(statement.symbolTable, statement.time)
|
|
211
|
+
self.followSuccessors(statement)
|
|
212
|
+
|
|
213
|
+
def visitRead(self, statement):
|
|
214
|
+
#logger.debug("SymbolTableVisitior.visitRead")
|
|
215
|
+
statement.symbolTable = self.checkPredecessorsAndRefer(statement)
|
|
216
|
+
assert statement.symbolTable is not None
|
|
217
|
+
for writable in statement.writables.writables:
|
|
218
|
+
self.tryAddVariable(statement.symbolTable, writable)
|
|
219
|
+
|
|
220
|
+
|