1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4# 5# Copyright 2017, Data61 6# Commonwealth Scientific and Industrial Research Organisation (CSIRO) 7# ABN 41 687 119 230. 8# 9# This software may be distributed and modified according to the terms of 10# the BSD 2-Clause license. Note that NO WARRANTY is provided. 11# See "LICENSE_BSD2.txt" for details. 12# 13# @TAG(DATA61_BSD) 14# 15 16''' 17Objects that can appear in the derived AST. See accompanying docs for more 18information. 19''' 20 21from __future__ import absolute_import, division, print_function, \ 22 unicode_literals 23from camkes.internal.seven import cmp, filter, map, zip 24 25from .base import ASTObject, MapLike 26from .ckeywords import C_KEYWORDS 27from .exception import ASTError 28from .location import SourceLocation 29from camkes.internal.frozendict import frozendict 30from camkes.internal.isinstancefallback import isinstance_fallback 31import abc 32import collections 33import itertools 34import numbers 35import six 36import logging 37from types import LambdaType 38 39 40def types_compatible(value, attribute): 41 type = attribute.type 42 assert isinstance(type, (six.string_types, Struct)) 43 if attribute.array is True: 44 assert isinstance(value, (tuple, list)) 45 values = value 46 else: 47 values = (value,) 48 for value in values: 49 if isinstance(type, six.string_types): 50 if (isinstance(value, six.integer_types) and type not in ('int', 'uint64_t', 'int64_t')): 51 return (False, "For \"%s\": required type is \"int\", but type is \"%s\"" % (str(value), type)) 52 if (isinstance(value, float) and type not in ('double', 'float')): 53 return (False, "For \"%s\": required type is \"float\", but type is \"%s\"" % (str(value), type)) 54 if (isinstance(value, six.string_types) and type != 'string'): 55 return (False, "For \"%s\": required type is \"string\", but type is \"%s\"" % (str(value), type)) 56 if (isinstance(value, list) and type != 'list'): 57 return (False, "For \"%s\": required type is \"list\", but type is \"%s\"" % (str(value), type)) 58 if ((isinstance(value, dict) and type != 'dict')): 59 return (False, "For \"%s\": required type is \"dict\", but type is \"%s\"" % (str(value), type)) 60 61 elif isinstance(type, Struct): 62 attr_names = {x.name for x in type.attributes} 63 missing_attrs = list(attr_names - set(value.keys())) 64 if len(missing_attrs) != 0: 65 new_missing = [] 66 for attr in missing_attrs: 67 # Use default attribute if value not present in setting array 68 attribute = next(a for a in type.attributes if a.name == attr) 69 if attribute.default is not None: 70 value[attr] = attribute.default 71 else: 72 new_missing.append(attr) 73 if len(new_missing) != 0: 74 logging.warn("Attributes: \"%s\" are missing from assignment." % new_missing) 75 76 extra_attrs = list(set(value.keys()) - attr_names) 77 if len(extra_attrs) != 0: 78 logging.warn("Attributes: \"%s\" do not exist in \"%s\" definition." % 79 (extra_attrs, type.name)) 80 81 for x in type.attributes: 82 (compat, error_str) = types_compatible(value[x.name], x) 83 if not compat: 84 return (False, error_str) 85 return (True, "") 86 87 88def ast_property(name, types): 89 '''Creates custom getter and setter functions for an AST property. 90 Typechecks and raises exception if frozen 91 ''' 92 assert isinstance(name, six.string_types) 93 94 def prop_class_decorator(cls): 95 prop_name = "_%s" % name 96 (fget, fset, fdel) = (None, None, None) 97 old_prop = getattr(cls, name, None) 98 if old_prop is not None: 99 (fget, fset, fdel) = (old_prop.fget, old_prop.fset, old_prop.fdel) 100 101 def get_prop(self): 102 if fget: 103 # Call a getter function if it already exists. 104 return fget(self) 105 return getattr(self, prop_name) 106 107 def set_prop(self, value): 108 if isinstance(types, LambdaType): 109 assert types(value) 110 else: 111 assert isinstance(value, types) 112 if self.frozen: 113 raise TypeError('Tried to change {0} on object {1} to value {2} but object is frozen' % 114 (name, self.__class__.__name__, value)) 115 if fset: 116 # Call the setter if it already exists. 117 fset(self, value) 118 return 119 setattr(self, prop_name, value) 120 121 prop = property(get_prop, set_prop, fdel) 122 setattr(cls, name, prop) 123 return cls 124 return prop_class_decorator 125 126 127@ast_property("source", six.string_types) 128@ast_property("relative", bool) 129class Include(ASTObject): 130 def __init__(self, source, relative=True, location=None): 131 super(Include, self).__init__(location) 132 self.source = source 133 self.relative = relative 134 135 136class Reference(ASTObject): 137 '''This class encapsulates references to other entities that have been 138 parsed. 139 ''' 140 141 def __init__(self, symbol, symbol_type, location=None): 142 assert isinstance(symbol, list) and len(symbol) > 0 and \ 143 all([isinstance(x, six.string_types) for x in symbol]) 144 assert symbol_type is None or isinstance(symbol_type, type) 145 super(Reference, self).__init__(location) 146 self.name = symbol 147 self.type = symbol_type 148 149 def freeze(self): 150 raise ASTError('reference remaining in frozen AST tree', self) 151 152 153@ast_property("name", lambda x: x is None or isinstance(x, six.string_types)) 154@ast_property("composition", lambda x: isinstance(x, Reference) or isinstance_fallback(x, "Composition")) 155@ast_property("configuration", lambda x: isinstance(x, Reference) or isinstance_fallback(x, "Configuration")) 156class Assembly(ASTObject): 157 child_fields = ('composition', 'configuration') 158 159 def __init__(self, name=None, composition=None, configuration=None, location=None): 160 super(Assembly, self).__init__(location) 161 self.name = name 162 if composition is not None: 163 self.composition = composition 164 if configuration is None: 165 configuration = Configuration() 166 self.configuration = configuration 167 self.claim_children() 168 169 def claim_children(self): 170 self.adopt(self.composition) 171 self.adopt(self.configuration) 172 173 def freeze(self): 174 if self.frozen: 175 return 176 for s in self.configuration.settings: 177 for i in self.composition.instances: 178 if i.name == s.instance: 179 break 180 else: 181 continue 182 for a in i.type.attributes: 183 if a.name == s.attribute: 184 break 185 else: 186 continue 187 (result, error_str) = types_compatible(s.value, a) 188 if not result: 189 raise ASTError('mistyped assignment of attribute of type %s: %s' % 190 (str(a.type), error_str), s) 191 super(Assembly, self).freeze() 192 193 def get_attribute(self, instance_name, attribute_name): 194 ''' 195 Get an attribute from an instance from an assembly 196 ''' 197 instance_gen = (x for x in self.instances if x.name == instance_name) 198 try: 199 instance = next(instance_gen) 200 attribute_gen = (x for x in instance.type.attributes 201 if x.name == attribute_name) 202 return next(attribute_gen) 203 except StopIteration: 204 # couldn't find attribute 205 return None 206 207 # Shortcuts for accessing grandchildren. 208 @property 209 def instances(self): 210 return self.composition.instances 211 212 @property 213 def connections(self): 214 return self.composition.connections 215 216 @property 217 def settings(self): 218 return self.configuration.settings 219 220 221@ast_property("name", lambda x: x is None or isinstance(x, six.string_types)) 222@ast_property("instances", lambda i: isinstance(i, (list, tuple)) and 223 all(isinstance(x, (Reference)) or isinstance_fallback(x, "Instance") for x in i)) 224@ast_property("connections", lambda c: isinstance(c, (list, tuple)) and 225 all(isinstance(x, (Reference)) or isinstance_fallback(x, "Connection") for x in c)) 226@ast_property("groups", lambda g: isinstance(g, (list, tuple)) and 227 all(isinstance(x, (Reference)) or isinstance_fallback(x, "Group") for x in g)) 228@ast_property("exports", lambda e: isinstance(e, (list, tuple)) and 229 all(isinstance(x, (Reference)) or isinstance_fallback(x, "Export") for x in e)) 230class Composition(MapLike): 231 # Note that the ordering of these child fields is important as members of 232 # `connections` and `exports` may reference members of `instances` and/or 233 # `groups`, so both of the latter need to be seen by scoping resolution 234 # before the former. 235 child_fields = ('instances', 'groups', 'connections', 'exports') 236 237 def __init__(self, name=None, instances=None, connections=None, 238 groups=None, exports=None, location=None): 239 super(Composition, self).__init__(location) 240 self.name = name 241 self.instances = list(instances or []) 242 self.connections = list(connections or []) 243 self.groups = list(groups or []) 244 self.exports = list(exports or []) 245 self.claim_children() 246 247 def claim_children(self): 248 [self.adopt(i) for i in self.instances] 249 [self.adopt(c) for c in self.connections] 250 [self.adopt(g) for g in self.groups] 251 [self.adopt(e) for e in self.exports] 252 253 def freeze(self): 254 if isinstance(self.parent, Assembly): 255 # Check that the specification doesn't connect the same interface 256 # twice and all required interfaces are connected. Note that this 257 # only need to be true of the final, top-level composition. 258 unconnected = set() 259 connected = set() 260 for i in self.instances: 261 for inf in itertools.chain(i.type.consumes, i.type.dataports, 262 i.type.emits, i.type.provides, i.type.uses): 263 unconnected.add((i, inf)) 264 for conn in self.connections: 265 for end in itertools.chain(conn.from_ends, conn.to_ends): 266 if (end.instance, end.interface) in connected: 267 raise ASTError('duplicate use of interface %s.%s ' 268 '(deprecated form of N-way connections?)' % 269 (end.instance.name, end.interface.name), end) 270 assert (end.instance, end.interface) in unconnected, \ 271 'connection end %s.%s seems to refer to an interface ' \ 272 'that doesn\'t exist (bug in AST freezing?)' % \ 273 (end.instance.name, end.interface.name) 274 unconnected.remove((end.instance, end.interface)) 275 connected.add((end.instance, end.interface)) 276 for i, inf in unconnected: 277 if (not hasattr(inf, 'optional') or not inf.optional) and not \ 278 isinstance(inf, (Emits, Provides)): 279 # For a simple specification, this is immediately an error. 280 # However, in a hierarchical specification, this interface 281 # may actually be a phony "virtual" interface that is 282 # exporting a nested component's interface. Check that it 283 # is not before throwing an error. 284 if i.type.composition is not None and len([x for x in 285 i.type.composition.exports if x.destination == 286 inf]) > 0: 287 continue 288 raise ASTError('non-optional interface %s.%s is not ' 289 'connected' % (i.name, inf.name), i) 290 self.instances = tuple(self.instances) 291 self.connections = tuple(self.connections) 292 self.groups = tuple(self.groups) 293 self.exports = tuple(self.exports) 294 super(Composition, self).freeze() 295 296 297@ast_property("name", lambda x: x is None or isinstance(x, six.string_types)) 298@ast_property("settings", (list, tuple)) 299class Configuration(MapLike): 300 child_fields = ('settings',) 301 302 def __init__(self, name=None, settings=None, location=None): 303 super(Configuration, self).__init__(location) 304 self.name = name 305 self.settings = list(settings or []) 306 self.settings_dict = {} 307 self.claim_children() 308 309 def claim_children(self): 310 [self.adopt(s) for s in self.settings] 311 312 def freeze(self): 313 if self.frozen: 314 return 315 self.settings = tuple(self.settings) 316 # Jump MapLike 317 ASTObject.freeze(self) 318 self._mapping = collections.defaultdict(dict) 319 for s in self.settings: 320 if s.attribute in self._mapping[s.instance]: 321 raise ASTError('duplicate setting for attribute ' 322 '\'%s.%s\'' % (s.instance, s.attribute), s) 323 self._mapping[s.instance][s.attribute] = s.value 324 325 if s.instance in self.settings_dict: 326 self.settings_dict[s.instance][s.attribute] = s 327 else: 328 self.settings_dict[s.instance] = {s.attribute: s} 329 330 # Add any default values of attributes that were not set. 331 if isinstance(self.parent, Assembly): 332 for i in self.parent.composition.instances: 333 for a in i.type.attributes: 334 if (i.name not in self._mapping or 335 a.name not in self._mapping[i.name]) and \ 336 a.default is not None: 337 self._mapping[i.name][a.name] = a.default 338 339 340@ast_property("type", lambda x: isinstance(x, (Reference)) or isinstance_fallback(x, "Component")) 341@ast_property("name", six.string_types) 342@ast_property("address_space", six.string_types) 343class Instance(ASTObject): 344 child_fields = ('type',) 345 346 def __init__(self, type, name, location=None): 347 super(Instance, self).__init__(location) 348 self.type = type 349 self.name = name 350 self.address_space = name 351 352 def label(self): 353 return self.name 354 355 @property 356 def instance(self): 357 return self 358 359 def __str__(self): 360 return self.name 361 362 363@ast_property("type", lambda x: isinstance(x, (Reference)) or isinstance_fallback(x, "Connector")) 364@ast_property("name", six.string_types) 365@ast_property("from_ends", (list, tuple)) 366@ast_property("to_ends", (list, tuple)) 367class Connection(ASTObject): 368 child_fields = ('from_ends', 'to_ends', 'type') 369 370 def __init__(self, connection_type, name, from_ends, to_ends, location=None): 371 super(Connection, self).__init__(location) 372 self.type = connection_type 373 self.name = name 374 self.from_ends = from_ends 375 self.to_ends = to_ends 376 self.claim_children() 377 378 def claim_children(self): 379 [self.adopt(e) for e in self.from_ends] 380 [self.adopt(e) for e in self.to_ends] 381 382 def freeze(self): 383 if self.frozen: 384 return 385 self.from_ends = tuple(self.from_ends) 386 self.to_ends = tuple(self.to_ends) 387 super(Connection, self).freeze() 388 if len(self.from_ends) == 0: 389 raise ASTError('connection \'%s\' has no from ends' % self.name, 390 self) 391 if len(self.to_ends) == 0: 392 raise ASTError('connection \'%s\' has no to ends' % self.name, 393 self) 394 if len(self.from_ends) > 1 and not self.type.from_multiple: 395 raise ASTError('connection \'%s\' has multiple from ends ' 396 'but its connector type (%s) only permits one' 397 % (self.name, self.type.name), self) 398 if len(self.to_ends) > 1 and not self.type.to_multiple: 399 raise ASTError('connection \'%s\' has multiple to ends ' 400 'but its connector type (%s) only permits one' 401 % (self.name, self.type.name), self) 402 types = set([e.interface.type for e in self.from_ends] + 403 [e.interface.type for e in self.to_ends]) 404 interface_checking_attrib = self.type.get_attribute("disable_interface_type_checking") 405 if len(types) > 1 and (not interface_checking_attrib or not interface_checking_attrib.default): 406 raise ASTError('multiple conflicting types for the ' 407 'interfaces of connection \'%s\': %s' % (self.name, 408 ', '.join([(t.name if hasattr(t, 'name') else type(t).__name__) for t in types])), self) 409 for f in self.from_ends: 410 if not isinstance(f.interface, Emits) and not \ 411 isinstance(f.interface, Uses) and not \ 412 isinstance(f.interface, Dataport): 413 if f.instance is None: 414 name = f.interface.name 415 else: 416 name = '%s.%s' % (f.instance.name, f.interface.name) 417 raise ASTError('connection \'%s\' has an end %s that cannot be ' 418 'used as \'from\'' % (self.name, name), self) 419 if self.type.from_hardware and not f.get_end_type().hardware: 420 raise ASTError('connection \'%s\' has a type \'%s\' that is ' 421 'intended for connecting from hardware devices, but end ' 422 '%s.%s refers to a software component' % (self.name, 423 self.type.name, f.instance.name, f.interface.name), f) 424 if not self.type.from_hardware and f.get_end_type().hardware: 425 raise ASTError('connection \'%s\' has a type \'%s\' that is ' 426 'intended for connecting from software components, but ' 427 'end %s.%s refers to a hardware device' % (self.name, 428 self.type.name, f.instance.name, f.interface.name), f) 429 if self.type.from_type == 'Event' and not \ 430 isinstance(f.interface, Emits): 431 raise ASTError('connection \'%s\' has a type \'%s\' that is ' 432 'intended for connecting from events, but end %s.%s is ' 433 'not an emitted event' % (self.name, self.type.name, 434 f.instance.name, f.interface.name), f) 435 if self.type.from_type == 'Procedure' and not \ 436 isinstance(f.interface, Uses): 437 raise ASTError('connection \'%s\' has a type \'%s\' that is ' 438 'intended for connecting from procedures, but end %s.%s ' 439 'is not a used procedure' % (self.name, self.type.name, 440 f.instance.name, f.interface.name), f) 441 if self.type.from_type == 'Dataport' and not \ 442 isinstance(f.interface, Dataport): 443 raise ASTError('connection \'%s\' has a type \'%s\' that is ' 444 'intended for connecting from dataports, but end %s.%s ' 445 'is not a dataport' % (self.name, self.type.name, 446 f.instance.name, f.interface.name), f) 447 for t in self.to_ends: 448 if not isinstance(t.interface, Consumes) and not \ 449 isinstance(t.interface, Provides) and not \ 450 isinstance(t.interface, Dataport): 451 if t.instance is None: 452 name = t.interface.name 453 else: 454 name = '%s.%s' % (t.instance.name, t.interface.name) 455 raise ASTError('connection \'%s\' has an end %s that cannot be ' 456 'used as \'to\'' % (self.name, name), self) 457 if self.type.to_hardware and not t.get_end_type().hardware: 458 raise ASTError('connection \'%s\' has a type \'%s\' that is ' 459 'intended for connecting to hardware devices, but end ' 460 '%s.%s refers to a software component' % (self.name, 461 self.type.name, t.instance.name, t.interface.name), t) 462 if not self.type.to_hardware and t.get_end_type().hardware: 463 raise ASTError('connection \'%s\' has a type \'%s\' that is ' 464 'intended for connecting to software components, but end ' 465 '%s.%s refers to a hardware device' % (self.name, 466 self.type.name, t.instance.name, t.interface.name), t) 467 if self.type.to_type == 'Event' and not \ 468 isinstance(t.interface, Consumes): 469 raise ASTError('connection \'%s\' has a type \'%s\' that is ' 470 'intended for connecting to events, but end %s.%s is ' 471 'not a consumed event' % (self.name, self.type.name, 472 t.instance.name, t.interface.name), t) 473 if self.type.to_type == 'Procedure' and not \ 474 isinstance(t.interface, Provides): 475 raise ASTError('connection \'%s\' has a type \'%s\' that is ' 476 'intended for connecting to procedures, but end %s.%s ' 477 'is not a provided procedure' % (self.name, self.type.name, 478 t.instance.name, t.interface.name), t) 479 if self.type.to_type == 'Dataport' and not \ 480 isinstance(t.interface, Dataport): 481 raise ASTError('connection \'%s\' has a type \'%s\' that is ' 482 'intended for connecting to dataports, but end %s.%s ' 483 'is not a dataport' % (self.name, self.type.name, 484 t.instance.name, t.interface.name), t) 485 486 # Convenience members for dealing with connections that we know are 487 # one-to-one. 488 @property 489 def from_end(self): 490 assert len(self.from_ends) == 1, 'accessing a connection with ' \ 491 'multiple from ends as if it only has one' 492 return self.from_ends[0] 493 494 @property 495 def to_end(self): 496 assert len(self.to_ends) == 1, 'accessing a connection with ' \ 497 'multiple to ends as if it only has one' 498 return self.to_ends[0] 499 500 @property 501 def from_instance(self): 502 return self.from_end.instance 503 504 @property 505 def from_interface(self): 506 return self.from_end.interface 507 508 @property 509 def to_instance(self): 510 return self.to_end.instance 511 512 @property 513 def to_interface(self): 514 return self.to_end.interface 515 516 def label(self): 517 return self.name 518 519 520@ast_property("instance", six.string_types) 521@ast_property("attribute", six.string_types) 522@ast_property("value", lambda x: isinstance(x, (numbers.Number, list, tuple, frozendict, dict, six.string_types)) or isinstance_fallback(x, "AttributeReference") 523 or isinstance_fallback(x, "QueryObject")) 524class Setting(ASTObject): 525 def __init__(self, instance, attribute, value, location=None): 526 super(Setting, self).__init__(location) 527 self.instance = instance 528 self.attribute = attribute 529 self.value = value 530 if isinstance(value, Reference): 531 self.child_fields = ('value',) 532 533 # This property is wrapped by the ast_property decorator which adds on type checking and the frozen check 534 @property 535 def value(self): 536 return self._value 537 538 @value.setter 539 def value(self, value): 540 self._value = value 541 if isinstance(value, (Attribute, Reference)): 542 self.child_fields = ('value',) 543 else: 544 self.child_fields = () 545 546 def freeze(self): 547 if self.frozen: 548 return 549 if isinstance(self.value, Reference): 550 raise ASTError('setting %s.%s is a reference with no resolved ' 551 'value' % (self.instance, self.attribute), self) 552 if self.attribute in C_KEYWORDS: 553 raise ASTError('setting name \'%s\' clashes with a C keyword' % 554 self.attribute, self) 555 if isinstance(self.value, list): 556 self.value = tuple(self.value) 557 elif isinstance(self.value, dict): 558 self.value = frozendict(self.value) 559 super(Setting, self).freeze() 560 561 562@ast_property("name", six.string_types) 563@ast_property("attributes", lambda a: isinstance(a, (list, tuple)) and 564 all(isinstance_fallback(x, "Attribute") for x in a)) 565class Struct(ASTObject): 566 child_fields = ('attributes',) 567 anon_struct_count = 0 568 569 def __init__(self, name=None, attributes=None, location=None): 570 super(Struct, self).__init__(location) 571 572 if name is None: 573 # Generate a struct name as none was provided. 574 name = "camkes_anon_struct_%d" % Struct.anon_struct_count 575 Struct.anon_struct_count = Struct.anon_struct_count + 1 576 self.name = name 577 self.attributes = list(attributes or []) 578 579 def __str__(self): 580 return self.name 581 582 def freeze(self): 583 if self.frozen: 584 return 585 self.attributes = tuple(self.attributes) 586 super(Struct, self).freeze() 587 588 589@ast_property("name", lambda x: x is None or isinstance(x, six.string_types)) 590@ast_property("control", bool) 591@ast_property("hardware", bool) 592@ast_property("includes", lambda i: isinstance(i, (list, tuple)) and 593 all(isinstance_fallback(x, "Include") for x in i)) 594@ast_property("provides", lambda p: isinstance(p, (list, tuple)) and 595 all(isinstance_fallback(x, "Provides") for x in p)) 596@ast_property("uses", lambda u: isinstance(u, (list, tuple)) and 597 all(isinstance_fallback(x, "Uses") for x in u)) 598@ast_property("emits", lambda e: isinstance(e, (list, tuple)) and 599 all(isinstance_fallback(x, "Emits") for x in e)) 600@ast_property("consumes", lambda c: isinstance(c, (list, tuple)) and 601 all(isinstance_fallback(x, "Consumes") for x in c)) 602@ast_property("dataports", lambda d: isinstance(d, (list, tuple)) and 603 all(isinstance_fallback(x, "Dataport") for x in d)) 604@ast_property("attributes", lambda a: isinstance(a, (list, tuple)) and 605 all(isinstance_fallback(x, "Attribute") for x in a)) 606@ast_property("mutexes", lambda m: isinstance(m, (list, tuple)) and 607 all(isinstance_fallback(x, "Mutex") for x in m)) 608@ast_property("semaphores", lambda s: isinstance(s, (list, tuple)) and 609 all(isinstance_fallback(x, "Semaphore") for x in s)) 610@ast_property("binary_semaphores", lambda b: isinstance(b, (list, tuple)) and 611 all(isinstance_fallback(x, "BinarySemaphore") for x in b)) 612@ast_property("composition", Composition) 613@ast_property("configuration", Configuration) 614class Component(MapLike): 615 child_fields = ('attributes', 'includes', 'provides', 'uses', 'emits', 616 'consumes', 'dataports', 'mutexes', 'semaphores', 'binary_semaphores', 'composition', 617 'configuration') 618 619 def __init__(self, name=None, includes=None, control=False, hardware=False, 620 provides=None, uses=None, emits=None, consumes=None, dataports=None, 621 attributes=None, mutexes=None, semaphores=None, binary_semaphores=None, composition=None, 622 configuration=None, location=None): 623 super(Component, self).__init__(location) 624 self.name = name 625 self.includes = list(includes or []) 626 self.control = control 627 self.hardware = hardware 628 self.provides = list(provides or []) 629 self.uses = list(uses or []) 630 self.emits = list(emits or []) 631 self.consumes = list(consumes or []) 632 self.dataports = list(dataports or []) 633 self.attributes = list(attributes or []) 634 self.mutexes = list(mutexes or []) 635 self.semaphores = list(semaphores or []) 636 self.binary_semaphores = list(binary_semaphores or []) 637 if composition is not None: 638 self.composition = composition 639 else: 640 self._composition = None 641 if configuration is not None: 642 self.configuration = configuration 643 else: 644 self._configuration = None 645 self.claim_children() 646 647 def claim_children(self): 648 [self.adopt(i) for i in self.includes] 649 [self.adopt(p) for p in self.provides] 650 [self.adopt(u) for u in self.uses] 651 [self.adopt(e) for e in self.emits] 652 [self.adopt(c) for c in self.consumes] 653 [self.adopt(d) for d in self.dataports] 654 [self.adopt(a) for a in self.attributes] 655 [self.adopt(m) for m in self.mutexes] 656 [self.adopt(s) for s in self.semaphores] 657 [self.adopt(b) for b in self.binary_semaphores] 658 if self.composition is not None: 659 self.adopt(self.composition) 660 if self.configuration is not None: 661 self.adopt(self.configuration) 662 663 def freeze(self): 664 if self.frozen: 665 return 666 if self.control and self.hardware: 667 raise ASTError('component %s is illegally defined as being both a ' 668 'control component and a hardware device' % self.name, self) 669 if self.hardware: 670 if len(self.mutexes) > 0: 671 raise ASTError('component %s is illegally defined as a ' 672 'hardware device that also has mutexes' % self.name, self) 673 if len(self.semaphores) > 0: 674 raise ASTError('component %s is illegally defined as a ' 675 'hardware device that also has semaphores' % self.name, 676 self) 677 self.includes = tuple(self.includes) 678 self.provides = tuple(self.provides) 679 self.uses = tuple(self.uses) 680 self.emits = tuple(self.emits) 681 self.consumes = tuple(self.consumes) 682 self.dataports = tuple(self.dataports) 683 self.attributes = tuple(self.attributes) 684 self.mutexes = tuple(self.mutexes) 685 self.semaphores = tuple(self.semaphores) 686 super(Component, self).freeze() 687 688 def interface_is_exported(self, interface): 689 '''Check whether the given interface is an `export` endpoint. 690 This is used to remove virtual interfaces in the generated Isabelle model.''' 691 return (self.composition is not None and 692 any(interface == ex.destination.name for ex in self.composition.exports)) 693 694 695class Interface(six.with_metaclass(abc.ABCMeta, ASTObject)): 696 697 def __init__(self, location=None): 698 super(Interface, self).__init__(location) 699 700 def __str__(self): 701 return self.name 702 703 704@ast_property("name", six.string_types) 705@ast_property("type", lambda x: isinstance(x, (Reference)) or isinstance_fallback(x, "Procedure")) 706class Provides(Interface): 707 child_fields = ('type',) 708 709 def __init__(self, type, name, location=None): 710 super(Provides, self).__init__(location) 711 self.type = type 712 self.name = name 713 714 715@ast_property("type", lambda x: isinstance(x, (Reference)) or isinstance_fallback(x, "Procedure")) 716@ast_property("name", six.string_types) 717@ast_property("optional", bool) 718class Uses(Interface): 719 child_fields = ('type',) 720 721 def __init__(self, type, name, optional=False, location=None): 722 super(Uses, self).__init__(location) 723 self.type = type 724 self.name = name 725 self.optional = optional 726 727 728@ast_property("type", six.string_types) 729@ast_property("name", six.string_types) 730class Emits(Interface): 731 def __init__(self, type, name, location=None): 732 super(Emits, self).__init__(location) 733 self.type = type 734 self.name = name 735 736 737@ast_property("type", six.string_types) 738@ast_property("name", six.string_types) 739@ast_property("optional", bool) 740class Consumes(Interface): 741 def __init__(self, type, name, optional=False, location=None): 742 super(Consumes, self).__init__(location) 743 self.type = type 744 self.name = name 745 self.optional = optional 746 747 748@ast_property("type", six.string_types) 749@ast_property("name", six.string_types) 750@ast_property("optional", bool) 751class Dataport(Interface): 752 def __init__(self, type, name, optional=False, location=None): 753 super(Dataport, self).__init__(location) 754 self.type = type 755 self.name = name 756 self.optional = optional 757 758 759@ast_property("name", six.string_types) 760class Mutex(ASTObject): 761 def __init__(self, name, location=None): 762 super(Mutex, self).__init__(location) 763 self.name = name 764 765 766@ast_property("name", six.string_types) 767class Semaphore(ASTObject): 768 def __init__(self, name, location=None): 769 super(Semaphore, self).__init__(location) 770 self.name = name 771 772 773@ast_property("name", six.string_types) 774class BinarySemaphore(ASTObject): 775 def __init__(self, name, location=None): 776 super(BinarySemaphore, self).__init__(location) 777 self.name = name 778 779 780@ast_property("name", lambda x: x is None or isinstance(x, six.string_types)) 781@ast_property("from_type", lambda x: isinstance(x, six.string_types) and 782 x in ('Dataport', 'Event', 'Procedure')) 783@ast_property("to_type", lambda x: isinstance(x, six.string_types) and 784 x in ('Dataport', 'Event', 'Procedure')) 785@ast_property("from_multiple", bool) 786@ast_property("to_multiple", bool) 787@ast_property("from_threads", lambda x: isinstance(x, six.integer_types) and x >= 0) 788@ast_property("to_threads", lambda x: isinstance(x, six.integer_types) and x >= 0) 789@ast_property("from_hardware", bool) 790@ast_property("to_hardware", bool) 791@ast_property("attributes", lambda x: isinstance(x, (list, tuple)) and 792 all(isinstance_fallback(y, "Attribute") for y in value)) 793class Connector(ASTObject): 794 def __init__(self, name=None, from_type=None, to_type=None, 795 from_threads=1, to_threads=1, 796 from_hardware=False, to_hardware=False, attributes=None, location=None): 797 super(Connector, self).__init__(location) 798 TRANSLATION = { 799 'Event': 'Event', 800 'Events': 'Event', 801 'Procedure': 'Procedure', 802 'Procedures': 'Procedure', 803 'Dataport': 'Dataport', 804 'Dataports': 'Dataport', 805 } 806 self.name = name 807 if from_type: 808 self.from_type = TRANSLATION.get(from_type) 809 else: 810 self._from_type = None 811 if to_type: 812 self.to_type = TRANSLATION.get(to_type) 813 else: 814 self._to_type = None 815 self.from_multiple = from_type in ('Events', 'Procedures', 'Dataports') 816 self.to_multiple = to_type in ('Events', 'Procedures', 'Dataports') 817 self.from_threads = from_threads 818 self.to_threads = to_threads 819 self.from_hardware = from_hardware 820 self.to_hardware = to_hardware 821 self._attributes = attributes 822 823 def get_attribute(self, attribute_name): 824 for attrib in self.attributes: 825 if attrib.name == attribute_name: 826 return attrib 827 return None 828 829 830@ast_property("name", lambda x: x is None or isinstance(x, six.string_types)) 831@ast_property("instances", lambda i: isinstance(i, (list, tuple)) and 832 all(isinstance(x, Instance) for x in i)) 833class Group(MapLike): 834 child_fields = ('instances',) 835 836 def __init__(self, name=None, instances=None, location=None): 837 super(Group, self).__init__(location) 838 self.name = name 839 self.instances = list(instances or []) 840 self.claim_children() 841 842 def claim_children(self): 843 [self.adopt(i) for i in self.instances] 844 845 846@ast_property("name", lambda x: x is None or (isinstance(x, six.string_types))) 847@ast_property("includes", lambda i: isinstance(i, (list, tuple)) and 848 all(isinstance(x, Include) for x in i)) 849@ast_property("methods", lambda m: isinstance(m, (list, tuple)) and 850 all(isinstance(x, Method) for x in m)) 851@ast_property("attributes", lambda a: isinstance(a, (list, tuple)) and 852 all(isinstance(x, Attribute) for x in a)) 853class Procedure(MapLike): 854 child_fields = ('includes', 'methods', 'attributes') 855 856 def __init__(self, name=None, includes=None, methods=None, attributes=None, location=None): 857 super(Procedure, self).__init__(location) 858 self.name = name 859 self.includes = list(includes or []) 860 self.methods = list(methods or []) 861 self.attributes = list(attributes or []) 862 self.claim_children() 863 864 def claim_children(self): 865 [self.adopt(i) for i in self.includes] 866 [self.adopt(m) for m in self.methods] 867 [self.adopt(a) for a in self.attributes] 868 869 870@ast_property("name", six.string_types) 871@ast_property("return_type", lambda x: x is None or isinstance(x, six.string_types)) 872@ast_property("parameters", lambda p: isinstance(p, (list, tuple)) and 873 all(isinstance(x, Parameter) for x in p)) 874class Method(ASTObject): 875 child_fields = ('parameters',) 876 877 def __init__(self, name, return_type, parameters, location=None): 878 super(Method, self).__init__(location) 879 self.name = name 880 self.return_type = return_type 881 self.parameters = list(parameters) 882 self.claim_children() 883 884 def claim_children(self): 885 [self.adopt(p) for p in self.parameters] 886 887 def freeze(self): 888 if self.frozen: 889 return 890 if self.name in C_KEYWORDS: 891 raise ASTError('method name \'%s\' clashes with a C keyword' % 892 self.name, self) 893 params = set() 894 for p in self.parameters: 895 if p.name in params: 896 raise ASTError('duplicate parameter name \'%s\' in method ' 897 '\'%s\'' % (p.name, self.name), self) 898 params.add(p.name) 899 self.parameters = tuple(self.parameters) 900 super(Method, self).freeze() 901 902 903@ast_property("type", (six.string_types, Reference, Struct)) 904@ast_property("name", six.string_types) 905@ast_property("array", bool) 906@ast_property("default", lambda x: x is None or isinstance(x, (numbers.Number, list, dict, six.string_types))) 907class Attribute(ASTObject): 908 def __init__(self, type, name, array=False, default=None, location=None): 909 super(Attribute, self).__init__(location) 910 self.name = name 911 self.type = type 912 self.default = default 913 self.array = array 914 if isinstance(type, (Reference, Struct)): 915 self.child_fields = ('type',) 916 917 # Type checking and frozen checks are done by ast_property delegate 918 @property 919 def type(self): 920 return self._type 921 922 @type.setter 923 def type(self, value): 924 self._type = value 925 if isinstance(value, (Reference, Struct)): 926 self.child_fields = ('type',) 927 else: 928 self.child_fields = () 929 930 def freeze(self): 931 if self.frozen: 932 return 933 if self.name in C_KEYWORDS: 934 raise ASTError('attribute name \'%s\' clashes with a C keyword' % 935 self.name, self) 936 super(Attribute, self).freeze() 937 938 939@ast_property("name", six.string_types) 940@ast_property("direction", lambda x: isinstance(x, six.string_types) and x in ('in', 'inout', 'out', 'refin')) 941@ast_property("type", six.string_types) 942@ast_property("array", bool) 943class Parameter(ASTObject): 944 def __init__(self, name, direction, type, array=False, location=None): 945 super(Parameter, self).__init__(location) 946 self.name = name 947 self.direction = direction 948 self.type = type 949 self.array = array 950 951 def freeze(self): 952 if self.frozen: 953 return 954 if self.name in C_KEYWORDS: 955 raise ASTError('parameter name \'%s\' clashes with a C keyword' % 956 self.name, self) 957 super(Parameter, self).freeze() 958 959 960@ast_property("end", lambda x: isinstance(x, six.string_types) and x in ('from', 'to')) 961@ast_property("instance", lambda x: x is None or isinstance(x, (Instance, Reference))) 962@ast_property("interface", (Interface, Reference)) 963class ConnectionEnd(ASTObject): 964 child_fields = ('instance', 'interface') 965 966 def __init__(self, end, instance, interface, location=None): 967 super(ConnectionEnd, self).__init__(location) 968 self.end = end 969 self.instance = instance 970 self.interface = interface 971 972 # Shorthand that can replace the use of a very commonly repeated 973 # condition in the templates. 974 # 975 # This tests to see if the entity might block. 976 def might_block(self): 977 return len(self.instance.type.provides + self.instance.type.uses 978 + self.instance.type.consumes 979 + self.instance.type.mutexes + self.instance.type.semaphores) > 1 980 981 def label(self): 982 return self.parent.label() 983 984 def get_end_type(self): 985 ''' 986 If the instance is none but the composition that this connection 987 is in is inside a component, then the type of that end is the top 988 component. 989 ''' 990 if self.instance is not None: 991 return self.instance.type 992 # self.parent is a connection 993 # self.parent.parent is a composition 994 # self.parent.parent.parent is either an assembly or Component 995 # The parser shouldn't get to this point if the parent.parent.parent 996 # isn't a Component 997 assert isinstance(self.parent.parent.parent, Component) 998 return self.parent.parent.parent 999 1000 def __str__(self): 1001 return "%s.%s" % (str(self.instance), str(self.interface)) 1002 1003 1004@ast_property("source_instance", (Instance, Reference)) 1005@ast_property("source_interface", (Interface, Reference)) 1006@ast_property("destination", (Interface, Reference)) 1007class Export(ASTObject): 1008 child_fields = ('source_instance', 'source_interface', 'destination') 1009 1010 def __init__(self, source_instance, source_interface, destination, 1011 location=None): 1012 super(Export, self).__init__(location) 1013 self.source_instance = source_instance 1014 self.source_interface = source_interface 1015 self.destination = destination 1016 1017 1018@ast_property("reference", six.string_types) 1019@ast_property("dict_lookup", lambda x: x is None or isinstance_fallback(x, "DictLookup")) 1020class AttributeReference(ASTObject): 1021 def __init__(self, reference, dict_lookup, location=None): 1022 super(AttributeReference, self).__init__(location) 1023 self.reference = reference 1024 self.dict_lookup = dict_lookup 1025 1026 1027@ast_property("lookup", list) 1028class DictLookup(ASTObject): 1029 def __init__(self, lookup, location): 1030 super(DictLookup, self).__init__(location) 1031 self.lookup = lookup 1032 1033 1034@ast_property("type", six.string_types) 1035@ast_property("args", lambda i: isinstance(i, list) and 1036 all(isinstance(x, dict) for x in i)) 1037@ast_property("dict_lookup", lambda x: x is None or isinstance(x, DictLookup)) 1038class QueryObject(ASTObject): 1039 def __init__(self, query_name, query_args, dict_lookup, location): 1040 super(QueryObject, self).__init__(location) 1041 self.type = query_name # the name of the query 1042 self.args = query_args # the arguments passed to the query 1043 self.dict_lookup = dict_lookup 1044