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