-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtree.py
302 lines (205 loc) · 7.35 KB
/
tree.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
"""Note our method to go from parsing output to AST expects to find a class in this file for each grammar expression."""
from typing import Union, List
class AstNode:
# A set of strings that we can discard after parsing.
# Ex: in 'var a = 2', we don't need 'var' when building our Assignment node.
SYNTAX_STRINGS = {'=', ';', ',', '', '"', "'", '(', ')', '()', '{', '}', '{}', 'return', 'if', 'else', 'for',
# I'm not sure anymore why I need whitespace characters here. Removing does break tests though ;).
' ', '\n'}
@property
def children(self):
return []
def __str__(self):
"""A string for the node alone (without its subtree)"""
return f'{self.__class__.__name__}'
def walk(self):
"""Traverse the ast depth-first and yield the nodes."""
yield self
for child in self.children:
yield from child.walk()
class Wrap(AstNode):
"""I could have named this `Program` as it just wraps our program (we need a top-level node!)."""
def __init__(self, statements: List['Statement']):
self.statements = statements
@property
def children(self):
return self.statements
class Identifier(AstNode):
def __init__(self, name):
self.name = name
def __str__(self):
return f'{self.__class__.__name__}(name={self.name})'
class Statement(AstNode):
pass
class Expr(Statement):
# TODO: the *value* of this class is debatable as well (like Statement)
# I think it should be an abstract class and Integer, String, etc should inherit from it.
@property
def children(self):
return []
class Integer(Expr):
def __init__(self, value):
self.value = int(value)
def __str__(self):
return f'{self.__class__.__name__}({self.value})'
class String(Expr):
def __init__(self, value: str):
self.value = value
def __str__(self):
return f'{self.__class__.__name__}({self.value})'
class Char(Expr):
def __init__(self, value: str):
self.value = value
def __str__(self):
return f'{self.__class__.__name__}({self.value})'
class Assignment(Expr):
def __init__(self, identifier: Identifier, value: Expr):
self.identifier = identifier
self.value = value
@property
def children(self):
return [self.identifier, self.value]
def __str__(self):
return f'{self.__class__.__name__}'
class Declaration(Statement):
def __init__(self, type: str, identifier: Identifier, value: Union[Expr, None] = None):
self.type = type
self.identifier = identifier
self.value = value
@property
def children(self):
return [self.identifier] if self.value is None else [self.identifier, self.value]
def __str__(self):
return f'{self.__class__.__name__}'
class UnOp(AstNode):
NOT = '!'
PLUS = '+'
COMPLEMENT = '~'
MINUS = '-'
def __init__(self, operation, operand):
self.operation = operation
self.operand = operand
@property
def children(self):
return [self.operand]
class BinOp(AstNode):
MULTIPLY = '*'
ADD = '+'
SUBSTRACT = '-'
DIVIDE = '/'
MODULO = '%'
GTE = '>='
LTE = '<='
GT = '>'
LT = '<'
EQ = '=='
NEQ = '!='
OPERATORS = [MULTIPLY, ADD, SUBSTRACT, DIVIDE, MODULO, GT, LT, GTE, LTE, EQ, NEQ]
def __init__(self, left, operation, right):
self.operation = operation
self.left = left
self.right = right
@property
def children(self):
return [self.left, self.right]
def __str__(self):
return f'{self.__class__.__name__}({self.operation})'
class Function(Statement):
def __init__(self, return_type: str, name: Identifier, body: List[Statement],
args: Union['FunctionArgs', None] = None):
self.return_type = return_type
self.name = name
self.args = args if args is not None else FunctionArgs()
self.body = BodyBlock(body)
@property
def children(self):
return [self.name, self.body, self.args]
class BodyBlock(AstNode):
# Name is lame, to avoid conflict with Block defined in the grammar...
def __init__(self, statements: List[Statement]):
self.statements = statements
@property
def children(self):
return self.statements
class FunctionCall(Expr):
def __init__(self, function_id: Identifier, args: Union['FunctionCallArgs', None] = None):
self.function_id = function_id
self.args = args
@property
def children(self):
return [self.function_id, self.args]
class Return(Statement):
def __init__(self, value: Expr):
self.value = value
def __str__(self):
return f'{self.__class__.__name__}({self.value})'
@property
def children(self):
return [self.value]
class If(Statement):
def __init__(self, condition, if_block, else_block=None):
self.condition = condition
self.if_block = if_block
self.else_block = else_block
@property
def children(self):
return [self.condition, self.if_block] + ([self.else_block] if self.else_block is not None else [])
class FunctionArgs(AstNode):
def __init__(self, *args: Declaration):
self.args = args
@property
def children(self):
return self.args
class FunctionCallArgs(AstNode):
def __init__(self, *args):
self.args = args
@property
def children(self):
return self.args
class ForLoop(Statement):
def __init__(self, for_init: Union[Declaration, Assignment], for_condition: Expr, for_increment: Assignment,
for_body: BodyBlock):
self.for_init = for_init
self.for_condition = for_condition
self.for_increment = for_increment
self.for_body = for_body
@property
def children(self):
return [self.for_init, self.for_condition, self.for_increment, self.for_body]
def ControlFlowBody(statement_or_block=None):
if statement_or_block is None:
# This is an empty if block with braces.
return BodyBlock([])
if isinstance(statement_or_block, Statement):
return BodyBlock([statement_or_block])
else:
return BodyBlock(statement_or_block)
def reduce_to_list(item, items=None):
"""Typically we will have three statements (lines of code basically) in a row, giving us:
Block line1 [Block line2 [Block line3]]
And we want a list of statements at the end of the day
"""
if items is None:
items = []
if isinstance(item, list):
return item + items
else:
return [item] + items
def Noop(ast_or_terminal_token):
return ast_or_terminal_token
# Noop because we dont want to create a node for these. They were just here to make the grammar clearer.
SimpleExpr = Noop
Operator = Noop
UnaryOperator = Noop
Type = Noop
ForInit = Noop
Block = reduce_to_list
# This was created during the Great Battle of Left Recursion and Left Associativity.
Expr2 = Expr
def ast_to_str(ast: AstNode, depth=0):
"""Return a pretty-print representation of an AST"""
indent = ' ' * 2
# Each node class is responsible for providing a __str__ function.
extra_break = '\n' if ast.children else ''
return indent * depth + str(ast) + extra_break + '\n'.join(
[ast_to_str(child, depth=depth + 1) for child in ast.children])