1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# 4# Copyright 2017, Data61 5# Commonwealth Scientific and Industrial Research Organisation (CSIRO) 6# ABN 41 687 119 230. 7# 8# This software may be distributed and modified according to the terms of 9# the BSD 2-Clause license. Note that NO WARRANTY is provided. 10# See "LICENSE_BSD2.txt" for details. 11# 12# @TAG(DATA61_BSD) 13# 14 15from __future__ import absolute_import, division, print_function, \ 16 unicode_literals 17from camkes.internal.seven import cmp, filter, map, zip 18 19from camkes.internal.hash import camkes_hash 20from .exception import ASTError 21from .location import SourceLocation 22from .traversal import NullContext, TraversalAction, TraversalContext 23import abc, collections, six 24 25class ASTObject(six.with_metaclass(abc.ABCMeta, object)): 26 27 child_fields = () 28 29 # Fields that should be ignored when calculating an object hash or 30 # performing comparisons. Inheriting classes should extend this if 31 # necessary. 32 no_hash = ('child_fields', '_frozen', '_location', 'no_hash', '_parent') 33 34 def __init__(self, location=None): 35 assert location is None or isinstance(location, SourceLocation) 36 self._frozen = False 37 self._location = location 38 self._parent = None 39 40 @property 41 def frozen(self): 42 return self._frozen 43 @frozen.setter 44 def frozen(self, value): 45 assert isinstance(value, bool) 46 if self._frozen and not value: 47 raise TypeError('you cannot unfreeze an AST object') 48 self._frozen = value 49 50 @property 51 def location(self): 52 return self._location 53 @location.setter 54 def location(self, value): 55 assert value is None or isinstance(value, SourceLocation) 56 if self.frozen: 57 raise TypeError('you cannot change the location of a frozen AST ' 58 'object') 59 self._location = value 60 61 @property 62 def parent(self): 63 return self._parent 64 @parent.setter 65 def parent(self, value): 66 assert value is None or isinstance(value, ASTObject) 67 if self.frozen: 68 raise TypeError('you cannot change the parent of a frozen AST ' 69 'object') 70 self._parent = value 71 72 def freeze(self): 73 if self.frozen: 74 return 75 for f in self.child_fields: 76 assert hasattr(self, f) 77 item = getattr(self, f) 78 if isinstance(item, (list, tuple)): 79 for i in item: 80 i.freeze() 81 elif item is not None: 82 item.freeze() 83 self.frozen = True 84 85 @property 86 def filename(self): 87 if self.location is None: 88 return None 89 return self.location.filename 90 91 @property 92 def lineno(self): 93 if self.location is None: 94 return None 95 return self.location.lineno 96 97 @property 98 def children(self): 99 '''Returns the contained descendents of this object.''' 100 assert isinstance(self.child_fields, tuple), 'child_fields is not a ' \ 101 'tuple; accidentally declared as a string?' 102 kids = [] 103 for f in self.child_fields: 104 assert hasattr(self, f) 105 item = getattr(self, f) 106 if isinstance(item, (list, tuple)): 107 kids.extend(item) 108 else: 109 kids.append(item) 110 return kids 111 112 def adopt(self, child): 113 child.parent = self 114 115 def claim_children(self): 116 pass 117 118 def __cmp__(self, other): 119 if type(self) != type(other): 120 return cmp(str(type(self)), str(type(other))) 121 122 for f in (k for k in self.__dict__.keys() if k not in self.no_hash): 123 if not hasattr(other, f): 124 return 1 125 elif getattr(self, f) is getattr(other, f): 126 # PERF: Field `f` in both items references the exact same 127 # object. Skip the remainder of this iteration of the loop to 128 # avoid unnecessarily comparing an object with itself. 129 continue 130 elif getattr(self, f) != getattr(other, f): 131 if type(getattr(self, f)) != type(getattr(other, f)): 132 return cmp(str(type(getattr(self, f))), 133 str(type(getattr(other, f)))) 134 elif isinstance(getattr(self, f), type): 135 assert isinstance(getattr(other, f), type), 'incorrect ' \ 136 'control flow in __cmp__ (bug in AST base?)' 137 return cmp(str(getattr(self, f)), str(getattr(other, f))) 138 return cmp(getattr(self, f), getattr(other, f)) 139 140 return 0 141 142 def __hash__(self): 143 return camkes_hash((k, v) for k, v in self.__dict__.items() 144 if k not in self.no_hash) 145 146 # When comparing `ASTObject`s, we always want to invoke 147 # `ASTObject.__cmp__`, but unfortunately we inherit rich comparison methods 148 # from `object`. Override these here, to force `ASTObject.__cmp__`. Note 149 # that you cannot call `cmp` in any of the following functions or they will 150 # infinitely recurse. 151 def __lt__(self, other): 152 return self.__cmp__(other) < 0 153 def __le__(self, other): 154 return self.__cmp__(other) <= 0 155 def __eq__(self, other): 156 return self.__cmp__(other) == 0 157 def __ne__(self, other): 158 return self.__cmp__(other) != 0 159 def __gt__(self, other): 160 return self.__cmp__(other) > 0 161 def __ge__(self, other): 162 return self.__cmp__(other) >= 0 163 164 def preorder(self, f, context=None): 165 ''' 166 Pre-order traversal. Note that, unlike the post-order traversal below, 167 this does *not* recurse into the children of nodes you replace. The 168 rationale for this is that there is no other way to indicate to the 169 traversal algorithm that you do not wish to recurse into the children 170 and, if you *do* want to recurse, you can accomplish this manually 171 yourself before returning the replacement. 172 ''' 173 assert isinstance(f, TraversalAction) 174 assert context is None or isinstance(context, TraversalContext) 175 assert isinstance(self.child_fields, tuple), 'child_fields is not a ' \ 176 'tuple; accidentally declared as a string?' 177 if context is None: 178 context = NullContext() 179 for field in self.child_fields: 180 assert hasattr(self, field) 181 item = getattr(self, field) 182 if isinstance(item, (list, tuple)): 183 for i in six.moves.range(len(item)): 184 replacement = f(item[i]) 185 if replacement is item[i]: 186 with context(f): 187 replacement.preorder(f, context) 188 else: 189 getattr(self, field)[i] = replacement 190 else: 191 replacement = f(item) 192 if replacement is item and replacement is not None: 193 with context(f): 194 replacement.preorder(f, context) 195 elif replacement is not item: 196 setattr(self, field, replacement) 197 198 def postorder(self, f, context=None): 199 assert isinstance(f, TraversalAction) 200 assert context is None or isinstance(context, TraversalContext) 201 assert isinstance(self.child_fields, tuple), 'child_fields is not a ' \ 202 'tuple; accidentally declared as a string?' 203 if context is None: 204 context = NullContext() 205 for field in self.child_fields: 206 assert hasattr(self, field) 207 item = getattr(self, field) 208 if isinstance(item, (list, tuple)): 209 for i in six.moves.range(len(item)): 210 with context(f): 211 item[i].postorder(f, context) 212 replacement = f(item[i]) 213 if replacement is not item[i]: 214 getattr(self, field)[i] = replacement 215 else: 216 if item is not None: 217 with context(f): 218 item.postorder(f, context) 219 replacement = f(item) 220 if replacement is not item: 221 setattr(self, field, replacement) 222 223 def label(self): 224 return None 225 226class MapLike(six.with_metaclass(abc.ABCMeta, ASTObject, collections.Mapping)): 227 228 no_hash = ASTObject.no_hash + ('_mapping',) 229 230 def __init__(self, location=None): 231 super(MapLike, self).__init__(location) 232 self._mapping = None 233 234 def freeze(self): 235 if self.frozen: 236 return 237 super(MapLike, self).freeze() 238 self._mapping = {} 239 def add(d, i): 240 duplicate = d.get(i.name) 241 if duplicate is not None: 242 raise ASTError('duplicate entity \'%s\' defined, ' 243 'collides with %s at %s:%s' % (i.name, 244 type(duplicate).__name__, duplicate.filename, 245 duplicate.lineno), i) 246 d[i.name] = i 247 for field in self.child_fields: 248 assert hasattr(self, field) 249 item = getattr(self, field) 250 if isinstance(item, (list, tuple)): 251 [add(self._mapping, x) for x in item 252 if hasattr(x, 'name') and x.name is not None] 253 elif item is not None and hasattr(item, 'name') and \ 254 item.name is not None: 255 add(self._mapping, item) 256 257 def __getitem__(self, key): 258 assert self.frozen, 'dict access on non-frozen object' 259 return self._mapping[key] 260 def __iter__(self): 261 assert self.frozen, 'dict access on non-frozen object' 262 return iter(self._mapping) 263 def __len__(self): 264 assert self.frozen, 'dict access on non-frozen object' 265 return len(self._mapping) 266