tensor-sensor
332 строки · 10.1 Кб
1"""
2MIT License
3
4Copyright (c) 2021 Terence Parr
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22SOFTWARE.
23"""
24import tsensor25
26# Parse tree definitions
27# I found package ast in python3 lib after I built this. whoops. No biggie.
28# This tree structure is easier to visit for my purposes here. Also lets me
29# control the kinds of statements I process.
30
31class ParseTreeNode:32def __init__(self, parser):33self.parser = parser # which parser object created this node;34# useful for getting access to the code string from a token35self.value = None # used during evaluation36self.start = None # start token37self.stop = None # end token38def eval(self, frame):39"""40Evaluate the expression represented by this (sub)tree in context of frame.
41Try any exception found while evaluating and remember which operation that
42was in this tree
43"""
44try:45self.value = eval(str(self), frame.f_globals, frame.f_locals)46except BaseException as e:47raise IncrEvalTrap(self) from e48# print(self, "=>", self.value)49return self.value50@property51def optokens(self): # the associated token if atom or representative token if operation52return None53@property54def kids(self):55return []56def clarify(self):57return None58def __str__(self):59# Extract text from the original code string using token character indexes60return self.parser.code[self.start.cstart_idx:self.stop.cstop_idx]61def __repr__(self):62fields = self.__dict__.copy()63kill = ['start', 'stop', 'lbrack', 'lparen', 'parser']64for name in kill:65if name in fields: del fields[name]66args = [67v + '=' + fields[v].__repr__()68for v in fields69if v != 'value' or fields['value'] is not None70]71args = ','.join(args)72return f"{self.__class__.__name__}({args})"73
74class Assign(ParseTreeNode):75def __init__(self, parser, op, lhs, rhs, start, stop):76super().__init__(parser)77self.op, self.lhs, self.rhs = op, lhs, rhs78self.start, self.stop = start, stop79def eval(self, frame):80self.value = self.rhs.eval(frame)81# Don't eval this node as it causes side effect of making actual assignment to lhs82self.lhs.value = self.value83return self.value84@property85def optokens(self):86return [self.op]87@property88def kids(self):89return [self.lhs, self.rhs]90
91
92class Call(ParseTreeNode):93def __init__(self, parser, func, lparen, args, start, stop):94super().__init__(parser)95self.func = func96self.lparen = lparen97self.args = args98self.start, self.stop = start, stop99def eval(self, frame):100self.func.eval(frame)101for a in self.args:102a.eval(frame)103return super().eval(frame)104def clarify(self):105arg_msgs = []106for a in self.args:107ashape = tsensor.analysis._shape(a.value)108if ashape:109arg_msgs.append(f"arg {a} w/shape {ashape}")110if len(arg_msgs)==0:111return f"Cause: {self}"112return f"Cause: {self} tensor " + ', '.join(arg_msgs)113@property114def optokens(self):115f = None # assume complicated like a[i](args) with weird func expr116if isinstance(self.func, Member):117f = self.func.member118elif isinstance(self.func, Atom):119f = self.func120if f:121return [f.token,self.lparen,self.stop]122return [self.lparen,self.stop]123@property124def kids(self):125return [self.func]+self.args126
127
128class Return(ParseTreeNode):129def __init__(self, parser, result, start, stop):130super().__init__(parser)131self.result = result132self.start, self.stop = start, stop133def eval(self, frame):134self.value = [a.eval(frame) for a in self.result]135if len(self.value)==1:136self.value = self.value[0]137return self.value138@property139def optokens(self):140return [self.start]141@property142def kids(self):143return self.result144
145
146class Index(ParseTreeNode):147def __init__(self, parser, arr, lbrack, index, start, stop):148super().__init__(parser)149self.arr = arr150self.lbrack = lbrack151self.index = index152self.start, self.stop = start, stop153def eval(self, frame):154self.arr.eval(frame)155for i in self.index:156i.eval(frame)157return super().eval(frame)158@property159def optokens(self):160return [self.lbrack,self.stop]161@property162def kids(self):163return [self.arr] + self.index164
165
166class Member(ParseTreeNode):167def __init__(self, parser, op, obj, member, start, stop):168super().__init__(parser)169self.op = op # always DOT170self.obj = obj171self.member = member172self.start, self.stop = start, stop173def eval(self, frame):174self.obj.eval(frame)175# don't eval member as it's just a name to look up in obj176return super().eval(frame)177@property178def optokens(self): # the associated token if atom or representative token if operation179return [self.op]180@property181def kids(self):182return [self.obj, self.member]183
184
185class BinaryOp(ParseTreeNode):186def __init__(self, parser, op, lhs, rhs, start, stop):187super().__init__(parser)188self.op, self.lhs, self.rhs = op, lhs, rhs189self.start, self.stop = start, stop190def eval(self, frame):191self.lhs.eval(frame)192self.rhs.eval(frame)193return super().eval(frame)194def clarify(self):195opnd_msgs = []196lshape = tsensor.analysis._shape(self.lhs.value)197rshape = tsensor.analysis._shape(self.rhs.value)198if lshape:199opnd_msgs.append(f"operand {self.lhs} w/shape {lshape}")200if rshape:201opnd_msgs.append(f"operand {self.rhs} w/shape {rshape}")202return f"Cause: {self.op} on tensor " + ' and '.join(opnd_msgs)203@property204def optokens(self): # the associated token if atom or representative token if operation205return [self.op]206@property207def kids(self):208return [self.lhs, self.rhs]209
210
211class UnaryOp(ParseTreeNode):212def __init__(self, parser, op, opnd, start, stop):213super().__init__(parser)214self.op = op215self.opnd = opnd216self.start, self.stop = start, stop217def eval(self, frame):218self.opnd.eval(frame)219return super().eval(frame)220@property221def optokens(self):222return [self.op]223@property224def kids(self):225return [self.opnd]226
227
228class ListLiteral(ParseTreeNode):229def __init__(self, parser, elems, start, stop):230super().__init__(parser)231self.elems = elems232self.start, self.stop = start, stop233def eval(self, frame):234for i in self.elems:235i.eval(frame)236return super().eval(frame)237@property238def kids(self):239return self.elems240
241
242class TupleLiteral(ParseTreeNode):243def __init__(self, parser, elems, start, stop):244super().__init__(parser)245self.elems = elems246self.start, self.stop = start, stop247def eval(self, frame):248for i in self.elems:249i.eval(frame)250return super().eval(frame)251@property252def kids(self):253return self.elems254
255
256class SubExpr(ParseTreeNode):257# record parens for later display to keep precedence258def __init__(self, parser, e, start, stop):259super().__init__(parser)260self.e = e261self.start, self.stop = start, stop262def eval(self, frame):263self.value = self.e.eval(frame)264return self.value # don't re-evaluate265@property266def optokens(self):267return [self.start, self.stop]268@property269def kids(self):270return [self.e]271
272
273class Atom(ParseTreeNode):274def __init__(self, parser, token):275super().__init__(parser)276self.token = token277self.start, self.stop = token, token278def eval(self, frame):279if self.token.type == tsensor.parsing.COLON:280return ':' # fake a value here281return super().eval(frame)282def __repr__(self):283# v = f"{{{self.value}}}" if hasattr(self,'value') and self.value is not None else ""284return self.token.value285
286
287def postorder(t):288nodes = []289_postorder(t, nodes)290return nodes291
292
293def _postorder(t, nodes):294if t is None:295return296for sub in t.kids:297_postorder(sub, nodes)298nodes.append(t)299
300
301def leaves(t):302nodes = []303_leaves(t, nodes)304return nodes305
306
307def _leaves(t, nodes):308if t is None:309return310if len(t.kids) == 0:311nodes.append(t)312return313for sub in t.kids:314_leaves(sub, nodes)315
316
317def walk(t, pre=lambda x: None, post=lambda x: None):318if t is None:319return320pre(t)321for sub in t.kids:322walk(sub, pre, post)323post(t)324
325
326class IncrEvalTrap(BaseException):327"""328Used during re-evaluation of python line that threw exception to trap which
329subexpression caused the problem.
330"""
331def __init__(self, offending_expr):332self.offending_expr = offending_expr # where in tree did we get exception?333