Exemplo n.º 1
0
class Checker(object):
    # FIXME: Provide additional checks making use of
    #        - actor_def.inport_names and actor_def.outport_names
    #        - make use of arg_type (STRING, NUMBER, etc.)
    #        - analyze the actions wrt port use and token consumption:
    #          for f in actor_def.action_priority:
    #              print f.__name__, [x.cell_contents for x in f.__closure__]
    #
    def __init__(self, cs_info, verify=True):
        super(Checker, self).__init__()
        self.ds = DocumentationStore()
        self.cs_info = cs_info
        self.constants = self.cs_info['constants']
        self.comp_defs = self.cs_info['components']
        self.errors = []
        self.warnings = []
        self.verify = verify
        self.check()

    def issue(self, fmt, **info):
        return {
            'reason': fmt.format(**info),
            'line': info.get('line', 0),
            'col': info.get('col', 0)
        }

    def append_error(self, fmt, **info):
        issue = self.issue(fmt, **info)
        self.errors.append(issue)

    def append_warning(self, fmt, **info):
        issue = self.issue(fmt, **info)
        self.warnings.append(issue)

    def check(self):
        """
        Check constants, local components, and program, in that order.
        Generate error and warning issues as they are encountered.
        """
        self.check_constants()
        for comp in self.comp_defs.values():
            self.check_component(comp)
        self.check_structure(self.cs_info['structure'])

    def check_component(self, comp_def):
        """
        Check connections, structure, and argument for local component
        """
        defs = self.get_definition(comp_def['name'])
        self.check_component_connections(defs, comp_def['structure']['connections'])
        self.check_structure(comp_def['structure'], comp_def['arg_identifiers'])
        implicits = self._find_implicits(comp_def['structure']['connections'])
        self.check_component_arguments(comp_def, implicits)

    def _find_implicits(self, connections):
        implicit = [c['src_port'] for c in connections if not c['src']]
        arg_names = [value for kind, value in implicit if kind == 'IDENTIFIER']
        return arg_names

    def get_definition(self, actor_type):
        """
        Get the actor/component definition from the docstore.
        For local components, let the docstore generate the definition.
        """
        if actor_type in self.comp_defs:
            return self.ds.component_docs("local." + actor_type, self.comp_defs[actor_type])
        return self.ds.actor_docs(actor_type)

    def dbg_lines(self, s):
        """Return the debug line numbers in a construct. Default to 0."""
        try:
            return [x['dbg_line'] for x in s] or [0]
        except:
            return [0]

    def twiddle_portrefs(self, is_component, port_dir):
        if port_dir not in ['in', 'out']:
            raise Exception("Invalid port direction: {}".format(port_dir))
        if is_component:
            target, target_port = ('dst', 'dst_port') if port_dir == 'out' else ('src', 'src_port')
        else:
            target, target_port = ('dst', 'dst_port') if port_dir == 'in' else ('src', 'src_port')
        return target, target_port

    def generate_ports(self, definition, actor=None):
        """
        This generator takes care of remapping src and dst with respect to programs and component definitions.
        Given definition, connections, and optionally actor it will generate a list of tuples:
        (port, target, target_port, port_dir, actor) according to the following scheme:

        component inports  : (port, 'src', 'src_port', 'in', '.')
        component outports : (port, 'dst', 'dst_port', 'out', '.')
        actor inports      : (port, 'dst', 'dst_port', 'in', actor)
        actor outports     : (port, 'src', 'src_port', 'out', actor)

        that help sorting out connections.
        """
        is_component = actor is None
        actor = '.' if is_component else actor
        for port_dir in ['in', 'out']:
            target, target_port = self.twiddle_portrefs(is_component, port_dir)
            def_dir = 'inputs' if port_dir == 'in' else 'outputs'
            for p, _ in definition[def_dir]:
                yield((p, target, target_port, port_dir, actor))

    def _verify_port_names(self, definition, connections, actor=None):
        """Look for misspelled port names."""
        # A little transformation is required depending on actor vs. component and port direction
        retval = []
        is_component = actor is None
        actor = '.' if is_component else actor
        for port_dir in ['in', 'out']:
            target, target_port = self.twiddle_portrefs(is_component, port_dir)
            def_dir = 'inputs' if port_dir == 'in' else 'outputs'
            ports = [p for p, _ in definition[def_dir]]
            invalid_ports = [(c[target_port], port_dir, c['dbg_line']) for c in connections if c[target] == actor and c[target_port] not in ports]
            retval.extend(invalid_ports)
        return retval

    def check_atleast_one_connection(self, definition, connections, actor=None):
        """Check that all ports have at least one connection"""
        retval = []
        for port, target, target_port, port_dir, actor in self.generate_ports(definition, actor):
            pc = [c for c in connections if c[target] == actor and c[target_port] == port]
            if len(pc) < 1:
                retval.append((port, port_dir, max(self.dbg_lines(connections))))
        return retval

    def check_atmost_one_connection(self, definition, connections, actor=None):
        """Check that component destination port have at most one connection"""
        retval = []
        for port, target, target_port, port_dir, actor in self.generate_ports(definition, actor):
            if target == 'src':
                # Skip output (src) ports since they can have multiple connections
                continue
            pc = [c for c in connections if c[target] == actor and c[target_port] == port]
            if len(pc) > 1:
                retval.extend([(port, port_dir, c['dbg_line']) for c in pc])
        return retval

    def report_port_errors(self, fmt, portspecs, definition, actor=None):
        for port, port_dir, line in portspecs:
            self.append_error(fmt, line=line, port=port, port_dir=port_dir, actor=actor, **definition)

    def check_component_connections(self, definition, connections):
        # Check for bogus ports
        invalid_ports = self._verify_port_names(definition, connections)
        fmt = "Component {name} has no {port_dir}port '{port}'"
        self.report_port_errors(fmt, invalid_ports, definition)

        # All ports should have at least one connection...
        bad_ports = self.check_atleast_one_connection(definition, connections)
        fmt = "Component {name} is missing connection to {port_dir}port '{port}'"
        self.report_port_errors(fmt, bad_ports, definition)

        # ... but outports should have exactly one connection
        bad_ports = self.check_atmost_one_connection(definition, connections)
        fmt = "Component {name} has multiple connections to {port_dir}port '{port}'"
        self.report_port_errors(fmt, bad_ports, definition)

        # Check for illegal passthrough (.in > .out) connections
        fmt = "Component {name} passes port '{src_port}' directly to port '{dst_port}'"
        for pc in [c for c in connections if c['src'] == c['dst'] == '.']:
            self.append_error(fmt, line=c['dbg_line'], src_port=c['src_port'], dst_port=c['dst_port'], **definition)

    def check_actor_connections(self, actor, definition, connections):
        invalid_ports = self._verify_port_names(definition, connections, actor)
        fmt = "Actor {actor} ({ns}.{name}) has no {port_dir}port '{port}'"
        self.report_port_errors(fmt, invalid_ports, definition, actor)

        # All ports should have at least one connection...
        bad_ports = self.check_atleast_one_connection(definition, connections, actor)
        fmt = "Actor {actor} ({ns}.{name}) is missing connection to {port_dir}port '{port}'"
        self.report_port_errors(fmt, bad_ports, definition, actor)

    def check_constants(self):
        """Verify that all constant definitions evaluate to a value."""
        for constant in self.constants:
            try:
                self.lookup_constant(constant)
            except KeyError as e:
                fmt = "Constant '{name}' is undefined"
                self.append_error(fmt, name=e.args[0])
            except:
                fmt = "Constant '{name}' has a circular reference"
                self.append_error(fmt, name=constant)

    def lookup_constant(self, identifier, seen=None):
        """
        Return value for constant 'identifier' by recursively looking for a value.
        Raise an exception if not found
        """
        seen = seen or []
        kind, value = self.constants[identifier]
        if kind != "IDENTIFIER":
            return value
        if value in seen:
            raise Exception("Circular reference in constant definition")
        seen.append(value)
        return self.lookup_constant(value, seen)

    def check_component_arguments(self, comp_def, implicits):
        """
        Warn if component declares parameters that are not used by the actors in the component.
        """
        declared_args = set(comp_def['arg_identifiers'])
        used_args = set(implicits)
        for actor_def in comp_def['structure']['actors'].values():
            used_args.update({value for kind, value in actor_def['args'].values() if kind == 'IDENTIFIER'})

        unused_args = declared_args - used_args
        for u in unused_args:
            fmt = "Unused argument: '{param}'"
            self.append_error(fmt, line=comp_def['dbg_line'], param=u)

    def check_arguments(self, definition, declaration, arguments):
        """
        Verify that all arguments are present and valid when instantiating actors.
        'arguments' is a list of the arguments whose value is supplied by a component
        to it's constituent actors.
        """
        mandatory = set(definition['args']['mandatory'])
        optional = set(definition['args']['optional'])
        defined = set(declaration['args'].keys())

        # Case 1: Missing arguments
        missing = mandatory - defined
        for m in missing:
            fmt = "Missing argument: '{param}'"
            self.append_error(fmt, line=declaration['dbg_line'], param=m)

        # Case 2: Extra (unused) arguments
        unused = defined - (mandatory | optional)
        for m in unused:
            fmt = "Unused argument: '{param}'"
            self.append_error(fmt, line=declaration['dbg_line'], param=m)

        # Case 3: value for arg is IDENTIFIER rather than VALUE, and IDENTIFIER is not in constants
        for param, (kind, value) in declaration['args'].iteritems():
            # If kind is not IDENTIFIER we have an actual value => continue to next argument
            if kind != 'IDENTIFIER':
                continue
            # First check if the identifier (value) is provided by a wrapping component...
            if value in arguments:
                continue
            # ... and if it is not it have to be a constant or there was an error in the script.
            if value not in self.constants:
                fmt = "Undefined identifier: '{param}'"
                self.append_error(fmt, line=declaration['dbg_line'], param=value)

    def undeclared_actors(self, connections, declared_actors):
        """
        Scan connections for actors and compare to the set of declared actors in order
        to find actors that are referenced in the script but not declared.
        """
        # Look for undeclared actors
        src_actors = {c['src'] for c in connections if c['src'] != "."}
        dst_actors = {c['dst'] for c in connections if c['dst'] != "."}
        # Implicit src actors are defined by a constant on the inport
        implicit_src_actors = {c['src'] for c in connections if c['src'] is None}
        all_actors = src_actors | dst_actors
        undefined_actors = all_actors - (set(declared_actors) | implicit_src_actors)
        return undefined_actors

    def unknown_actors(self, actor_decls):
        """
        Find unknown actors, i.e. actors that are declared in the script,
        but whose definition is missing from the actorstore.
        """
        unknown_actors = [a for a, decl in actor_decls.iteritems() if not self.get_definition(decl['actor_type'])]
        return unknown_actors

    def check_structure(self, structure, arguments=None):
        """
        Check structure of program or component definition.
        'arguments' is a list of the parameters provided by the component to its constituent actors,
        and is only present if this method is called from 'check_component'.
        """
        connections = structure['connections']
        actor_declarations = structure['actors']
        declared_actors = actor_declarations.keys()
        arguments = arguments or []

        # Look for missing actors
        for actor in self.undeclared_actors(connections, declared_actors):
            fmt = "Undefined actor: '{actor}'"
            lines = [c['dbg_line'] for c in connections if c['src'] == actor or c['dst'] == actor]
            for line in lines:
                self.append_error(fmt, line=line, actor=actor)

        # FIXME: Add check for actors declared more than once
        # Note: Unused actors will be caught when checking connections

        # Check if actor definition exists
        unknown_actors = self.unknown_actors(actor_declarations)
        if self.verify:
            for actor in unknown_actors:
                fmt = "Unknown actor type: '{type}'"
                self.append_error(fmt, type=actor_declarations[actor]['actor_type'], line=actor_declarations[actor]['dbg_line'])

        # Check the validity of the known actors
        known_actors = set(declared_actors) - set(unknown_actors)
        for actor in known_actors:
            definition = self.get_definition(actor_declarations[actor]['actor_type'])
            self.check_actor_connections(actor, definition, connections)
            self.check_arguments(definition, actor_declarations[actor], arguments)
Exemplo n.º 2
0
class Checker(object):
    # FIXME: Provide additional checks making use of
    #        - actor_def.inport_names and actor_def.outport_names
    #        - make use of arg_type (STRING, NUMBER, etc.)
    #        - analyze the actions wrt port use and token consumption:
    #          for f in actor_def.action_priority:
    #              print f.__name__, [x.cell_contents for x in f.__closure__]
    #
    def __init__(self, cs_info):
        super(Checker, self).__init__()
        self.ds = DocumentationStore()
        self.cs_info = cs_info
        self.local_actors = {}
        self.errors = []
        self.warnings = []
        self.check()

    def issue(self, fmt, **info):
        return {'reason':fmt.format(**info), 'line':info.get('line', 0), 'col':info.get('col', 0)}

    def append_error(self, fmt, **info):
        issue = self.issue(fmt, **info)
        self.errors.append(issue)

    def append_warning(self, fmt, **info):
        issue = self.issue(fmt, **info)
        self.warnings.append(issue)

    def check(self):
        components = self.cs_info['components']
        for comp in components:
            self.local_actors[comp['name']] = comp
            self.check_component(comp)
        self.check_structure(self.cs_info['structure'])

    def check_component(self, comp_def):
        # print comp_name, json.dumps(comp_def, indent=4), json.dumps(self.get_definition(comp_name), indent=4)
        # fake definition for component test inputs/outputs have reversed meaning
        # defs = {'inputs':[(p, '') for p in comp_def['outports']], 'outputs':[(p, '') for p in comp_def['inports']]}
        defs = self.get_definition(comp_def['name'])
        self.check_connections(None, defs, comp_def['structure']['connections'])
        self.check_structure(comp_def['structure'])
        # Check for unused arguments and issue warning, not error
        args = set(comp_def['arg_identifiers'])
        used_args = set([])
        for a in comp_def['structure']['actors'].values():
            used_args.update(a['args'].keys())
        unused_args = args - used_args
        for u in unused_args:
            fmt = "Unused argument: '{param}'"
            self.append_warning(fmt, line=comp_def['dbg_line'], param=u)

    def get_definition(self, actor_type):
        if actor_type in self.local_actors:
            return self.ds.component_docs("local."+actor_type, self.local_actors[actor_type])
        return self.ds.actor_docs(actor_type)

    def dbg_lines(self, s):
        try:
            return [x['dbg_line'] for x in s] + [0]
        except:
            return [0]

    def check_component_connections(self, definition, connections):
        inport_names = [p for p, _ in definition['inputs']]
        outport_names = [p for p, _ in definition['outputs']]
        # Verify names of ports
        invalid_inports = [(c['src_port'], 'in', c['dbg_line']) for c in connections if not c['src'] and c['src_port'] not in inport_names]
        invalid_outports = [(c['dst_port'], 'out', c['dbg_line']) for c in connections if not c['dst'] and c['dst_port'] not in outport_names]
        invalid_ports = invalid_inports + invalid_outports
        for port, port_dir, line in invalid_ports:
            fmt = "Component {name} has no {port_dir}port '{port}'"
            self.append_error(fmt, line=line, port=port, port_dir=port_dir, **definition)
        # outports should have exactly one connection
        for port in outport_names:
            incoming = [c for c in connections if not c['dst'] and c['dst_port'] == port]
            if len(incoming) == 0:
                fmt = "Component {name} is missing connection to outport '{port}'"
                self.append_error(fmt, line=max(self.dbg_lines(connections)), port=port, **definition)
            if len(incoming) > 1:
                fmt = "Component {name} has multiple connections to outport '{port}'"
                for c in incoming:
                    self.append_error(fmt, line=c['dbg_line'], port=port, **definition)
        # inports should have at least one connection
        for port in inport_names:
            outgoing = [c for c in connections if not c['src'] and c['src_port'] == port]
            if len(outgoing) < 1:
                fmt = "Component {name} is missing connection to inport '{port}'"
                self.append_error(fmt, line=max(self.dbg_lines(connections)), port=port, **definition)

    def check_actor_connections(self, actor, definition, connections):
        inport_names = [p for p, _ in definition['inputs']]
        outport_names = [p for p, _ in definition['outputs']]
        # Verify names of ports
        invalid_inports = [(c['dst_port'], 'in', c['dbg_line']) for c in connections if c['dst'] == actor and c['dst_port'] not in inport_names]
        invalid_outports = [(c['src_port'], 'out', c['dbg_line']) for c in connections if c['src'] == actor and c['src_port'] not in outport_names]
        invalid_ports = invalid_inports + invalid_outports
        for port, port_dir, line in invalid_ports:
            fmt = "Actor {actor} ({ns}.{name}) has no {port_dir}port '{port}'"
            self.append_error(fmt, line=line, port=port, port_dir=port_dir, actor=actor, **definition)
        # inports should have exactly one connection
        for port in inport_names:
            incoming = [c for c in connections if c['dst'] == actor and c['dst_port'] == port]
            if len(incoming) == 0:
                fmt = "Actor {actor} ({ns}.{name}) is missing connection to inport '{port}'"
                self.append_error(fmt, line=max(self.dbg_lines(connections)), port=port, actor=actor, **definition)
            if len(incoming) > 1:
                fmt = "Actor {actor} ({ns}.{name}) has multiple connections to inport '{port}'"
                for c in incoming:
                    self.append_error(fmt, line=c['dbg_line'], port=port, actor=actor, **definition)
        # outports should have at least one connection
        for port in outport_names:
            outgoing = [c for c in connections if c['src'] == actor and c['src_port'] == port]
            if not outgoing:
                fmt = "Actor {actor} ({ns}.{name}) is missing connection to outport '{port}'"
                self.append_error(fmt, line=max(self.dbg_lines(connections)), port=port, actor=actor, **definition)

    def check_connections(self, actor, definition, connections):
        if actor:
            self.check_actor_connections(actor, definition, connections)
        else:
            self.check_component_connections(definition, connections)

    def check_arguments(self, definition, declaration):
        mandatory = set(definition['args']['mandatory'])
        defined = set(declaration['args'].keys())
        undefined = mandatory - defined
        for u in undefined:
            fmt = "Missing argument: '{param}'"
            self.append_error(fmt, line=declaration['dbg_line'], param=u)
            print self.errors[-1], definition

    def check_structure(self, structure):
        actors = structure['actors'].keys()

        # Look for undefined actors
        src_actors = {c['src'] for c in structure['connections'] if c['src']}
        dst_actors = {c['dst'] for c in structure['connections'] if c['dst']}
        all_actors = src_actors | dst_actors
        undefined_actors = all_actors - set(actors)
        for actor in undefined_actors:
            fmt = "Undefined actor: '{actor}'"
            lines = [c['dbg_line'] for c in structure['connections'] if c['src'] == actor or c['dst'] == actor]
            for line in lines:
                self.append_error(fmt, line=line, actor=actor)

        # Note: Unused actors will be caught when checking connections

        # Check if actor exists
        for actor in actors:
            actor_type = structure['actors'][actor]['actor_type']
            definition = self.get_definition(actor_type)
            if not definition:
                fmt = "Unknown actor type: '{type}'"
                self.append_error(fmt, type=actor_type, line=structure['actors'][actor]['dbg_line'])
                continue
            # We have actor definition, check that it is fully connected
            self.check_connections(actor, definition, structure['connections'])
            self.check_arguments(definition, structure['actors'][actor])
Exemplo n.º 3
0
class Checker(object):
    # FIXME: Provide additional checks making use of
    #        - actor_def.inport_names and actor_def.outport_names
    #        - make use of arg_type (STRING, NUMBER, etc.)
    #        - analyze the actions wrt port use and token consumption:
    #          for f in actor_def.action_priority:
    #              print f.__name__, [x.cell_contents for x in f.__closure__]
    #
    def __init__(self, cs_info, verify=True):
        super(Checker, self).__init__()
        self.ds = DocumentationStore()
        self.cs_info = cs_info
        self.constants = self.cs_info['constants']
        self.comp_defs = self.cs_info['components']
        self.errors = []
        self.warnings = []
        self.verify = verify
        self.check()

    def issue(self, fmt, **info):
        return {
            'reason': fmt.format(**info),
            'line': info.get('line', 0),
            'col': info.get('col', 0)
        }

    def append_error(self, fmt, **info):
        issue = self.issue(fmt, **info)
        self.errors.append(issue)

    def append_warning(self, fmt, **info):
        issue = self.issue(fmt, **info)
        self.warnings.append(issue)

    def check(self):
        """
        Check constants, local components, and program, in that order.
        Generate error and warning issues as they are encountered.
        """
        self.check_constants()
        for comp in self.comp_defs.values():
            self.check_component(comp)
        self.check_structure(self.cs_info['structure'])

    def check_component(self, comp_def):
        """
        Check connections, structure, and argument for local component
        """
        defs = self.get_definition(comp_def['name'])
        self.check_component_connections(defs,
                                         comp_def['structure']['connections'])
        self.check_structure(comp_def['structure'],
                             comp_def['arg_identifiers'])
        implicits = self._find_implicits(comp_def['structure']['connections'])
        self.check_component_arguments(comp_def, implicits)

    def _find_implicits(self, connections):
        implicit = [c['src_port'] for c in connections if not c['src']]
        arg_names = [value for kind, value in implicit if kind == 'IDENTIFIER']
        return arg_names

    def get_definition(self, actor_type):
        """
        Get the actor/component definition from the docstore.
        For local components, let the docstore generate the definition.
        """
        if actor_type in self.comp_defs:
            return self.ds.component_docs("local." + actor_type,
                                          self.comp_defs[actor_type])
        return self.ds.actor_docs(actor_type)

    def dbg_lines(self, s):
        """Return the debug line numbers in a construct. Default to 0."""
        try:
            return [x['dbg_line'] for x in s] or [0]
        except:
            return [0]

    def twiddle_portrefs(self, is_component, port_dir):
        if port_dir not in ['in', 'out']:
            raise Exception("Invalid port direction: {}".format(port_dir))
        if is_component:
            target, target_port = (
                'dst', 'dst_port') if port_dir == 'out' else ('src',
                                                              'src_port')
        else:
            target, target_port = (
                'dst', 'dst_port') if port_dir == 'in' else ('src', 'src_port')
        return target, target_port

    def generate_ports(self, definition, actor=None):
        """
        This generator takes care of remapping src and dst with respect to programs and component definitions.
        Given definition, connections, and optionally actor it will generate a list of tuples:
        (port, target, target_port, port_dir, actor) according to the following scheme:

        component inports  : (port, 'src', 'src_port', 'in', '.')
        component outports : (port, 'dst', 'dst_port', 'out', '.')
        actor inports      : (port, 'dst', 'dst_port', 'in', actor)
        actor outports     : (port, 'src', 'src_port', 'out', actor)

        that help sorting out connections.
        """
        is_component = actor is None
        actor = '.' if is_component else actor
        for port_dir in ['in', 'out']:
            target, target_port = self.twiddle_portrefs(is_component, port_dir)
            def_dir = 'inputs' if port_dir == 'in' else 'outputs'
            for p, _ in definition[def_dir]:
                yield ((p, target, target_port, port_dir, actor))

    def _verify_port_names(self, definition, connections, actor=None):
        """Look for misspelled port names."""
        # A little transformation is required depending on actor vs. component and port direction
        retval = []
        is_component = actor is None
        actor = '.' if is_component else actor
        for port_dir in ['in', 'out']:
            target, target_port = self.twiddle_portrefs(is_component, port_dir)
            def_dir = 'inputs' if port_dir == 'in' else 'outputs'
            ports = [p for p, _ in definition[def_dir]]
            invalid_ports = [
                (c[target_port], port_dir, c['dbg_line']) for c in connections
                if c[target] == actor and c[target_port] not in ports
            ]
            retval.extend(invalid_ports)
        return retval

    def check_atleast_one_connection(self,
                                     definition,
                                     connections,
                                     actor=None):
        """Check that all ports have at least one connection"""
        retval = []
        for port, target, target_port, port_dir, actor in self.generate_ports(
                definition, actor):
            pc = [
                c for c in connections
                if c[target] == actor and c[target_port] == port
            ]
            if len(pc) < 1:
                retval.append(
                    (port, port_dir, max(self.dbg_lines(connections))))
        return retval

    def check_atmost_one_connection(self, definition, connections, actor=None):
        """Check that input ports have at most one connection"""
        retval = []
        for port, target, target_port, port_dir, actor in self.generate_ports(
                definition, actor):
            if target == 'src':
                # Skip output (src) ports since they can have multiple connections
                continue
            pc = [
                c for c in connections
                if c[target] == actor and c[target_port] == port
            ]
            if len(pc) > 1:
                retval.extend([(port, port_dir, c['dbg_line']) for c in pc])
        return retval

    def report_port_errors(self, fmt, portspecs, definition, actor=None):
        for port, port_dir, line in portspecs:
            self.append_error(fmt,
                              line=line,
                              port=port,
                              port_dir=port_dir,
                              actor=actor,
                              **definition)

    def check_component_connections(self, definition, connections):
        # Check for bogus ports
        invalid_ports = self._verify_port_names(definition, connections)
        fmt = "Component {name} has no {port_dir}port '{port}'"
        self.report_port_errors(fmt, invalid_ports, definition)

        # All ports should have at least one connection...
        bad_ports = self.check_atleast_one_connection(definition, connections)
        fmt = "Component {name} is missing connection to {port_dir}port '{port}'"
        self.report_port_errors(fmt, bad_ports, definition)

        # ... but outports should have exactly one connection
        bad_ports = self.check_atmost_one_connection(definition, connections)
        fmt = "Component {name} has multiple connections to {port_dir}port '{port}'"
        self.report_port_errors(fmt, bad_ports, definition)

        # Check for illegal passthrough (.in > .out) connections
        fmt = "Component {name} passes port '{src_port}' directly to port '{dst_port}'"
        for pc in [c for c in connections if c['src'] == c['dst'] == '.']:
            self.append_error(fmt,
                              line=c['dbg_line'],
                              src_port=c['src_port'],
                              dst_port=c['dst_port'],
                              **definition)

    def check_actor_connections(self, actor, definition, connections):
        invalid_ports = self._verify_port_names(definition, connections, actor)
        fmt = "Actor {actor} ({ns}.{name}) has no {port_dir}port '{port}'"
        self.report_port_errors(fmt, invalid_ports, definition, actor)

        # All ports should have at least one connection...
        bad_ports = self.check_atleast_one_connection(definition, connections,
                                                      actor)
        fmt = "Actor {actor} ({ns}.{name}) is missing connection to {port_dir}port '{port}'"
        self.report_port_errors(fmt, bad_ports, definition, actor)

        # ... but inports should have exactly one connection
        bad_ports = self.check_atmost_one_connection(definition, connections,
                                                     actor)
        fmt = "Actor {actor} ({ns}.{name}) has multiple connections to {port_dir}port '{port}'"
        self.report_port_errors(fmt, bad_ports, definition, actor)

    def check_constants(self):
        """Verify that all constant definitions evaluate to a value."""
        for constant in self.constants:
            try:
                self.lookup_constant(constant)
            except KeyError as e:
                fmt = "Constant '{name}' is undefined"
                self.append_error(fmt, name=e.args[0])
            except:
                fmt = "Constant '{name}' has a circular reference"
                self.append_error(fmt, name=constant)

    def lookup_constant(self, identifier, seen=None):
        """
        Return value for constant 'identifier' by recursively looking for a value.
        Raise an exception if not found
        """
        seen = seen or []
        kind, value = self.constants[identifier]
        if kind != "IDENTIFIER":
            return value
        if value in seen:
            raise Exception("Circular reference in constant definition")
        seen.append(value)
        return self.lookup_constant(value, seen)

    def check_component_arguments(self, comp_def, implicits):
        """
        Warn if component declares parameters that are not used by the actors in the component.
        """
        declared_args = set(comp_def['arg_identifiers'])
        used_args = set(implicits)
        for actor_def in comp_def['structure']['actors'].values():
            used_args.update({
                value
                for kind, value in actor_def['args'].values()
                if kind == 'IDENTIFIER'
            })

        unused_args = declared_args - used_args
        for u in unused_args:
            fmt = "Unused argument: '{param}'"
            self.append_error(fmt, line=comp_def['dbg_line'], param=u)

    def check_arguments(self, definition, declaration, arguments):
        """
        Verify that all arguments are present and valid when instantiating actors.
        'arguments' is a list of the arguments whose value is supplied by a component
        to it's constituent actors.
        """
        mandatory = set(definition['args']['mandatory'])
        optional = set(definition['args']['optional'])
        defined = set(declaration['args'].keys())

        # Case 1: Missing arguments
        missing = mandatory - defined
        for m in missing:
            fmt = "Missing argument: '{param}'"
            self.append_error(fmt, line=declaration['dbg_line'], param=m)

        # Case 2: Extra (unused) arguments
        unused = defined - (mandatory | optional)
        for m in unused:
            fmt = "Unused argument: '{param}'"
            self.append_error(fmt, line=declaration['dbg_line'], param=m)

        # Case 3: value for arg is IDENTIFIER rather than VALUE, and IDENTIFIER is not in constants
        for param, (kind, value) in declaration['args'].iteritems():
            # If kind is not IDENTIFIER we have an actual value => continue to next argument
            if kind != 'IDENTIFIER':
                continue
            # First check if the identifier (value) is provided by a wrapping component...
            if value in arguments:
                continue
            # ... and if it is not it have to be a constant or there was an error in the script.
            if value not in self.constants:
                fmt = "Undefined identifier: '{param}'"
                self.append_error(fmt,
                                  line=declaration['dbg_line'],
                                  param=value)

    def undeclared_actors(self, connections, declared_actors):
        """
        Scan connections for actors and compare to the set of declared actors in order
        to find actors that are referenced in the script but not declared.
        """
        # Look for undeclared actors
        src_actors = {c['src'] for c in connections if c['src'] != "."}
        dst_actors = {c['dst'] for c in connections if c['dst'] != "."}
        # Implicit src actors are defined by a constant on the inport
        implicit_src_actors = {
            c['src']
            for c in connections if c['src'] is None
        }
        all_actors = src_actors | dst_actors
        undefined_actors = all_actors - (set(declared_actors)
                                         | implicit_src_actors)
        return undefined_actors

    def unknown_actors(self, actor_decls):
        """
        Find unknown actors, i.e. actors that are declared in the script,
        but whose definition is missing from the actorstore.
        """
        unknown_actors = [
            a for a, decl in actor_decls.iteritems()
            if not self.get_definition(decl['actor_type'])
        ]
        return unknown_actors

    def check_structure(self, structure, arguments=None):
        """
        Check structure of program or component definition.
        'arguments' is a list of the parameters provided by the component to its constituent actors,
        and is only present if this method is called from 'check_component'.
        """
        connections = structure['connections']
        actor_declarations = structure['actors']
        declared_actors = actor_declarations.keys()
        arguments = arguments or []

        # Look for missing actors
        for actor in self.undeclared_actors(connections, declared_actors):
            fmt = "Undefined actor: '{actor}'"
            lines = [
                c['dbg_line'] for c in connections
                if c['src'] == actor or c['dst'] == actor
            ]
            for line in lines:
                self.append_error(fmt, line=line, actor=actor)

        # FIXME: Add check for actors declared more than once
        # Note: Unused actors will be caught when checking connections

        # Check if actor definition exists
        unknown_actors = self.unknown_actors(actor_declarations)
        if self.verify:
            for actor in unknown_actors:
                fmt = "Unknown actor type: '{type}'"
                self.append_error(fmt,
                                  type=actor_declarations[actor]['actor_type'],
                                  line=actor_declarations[actor]['dbg_line'])

        # Check the validity of the known actors
        known_actors = set(declared_actors) - set(unknown_actors)
        for actor in known_actors:
            definition = self.get_definition(
                actor_declarations[actor]['actor_type'])
            self.check_actor_connections(actor, definition, connections)
            self.check_arguments(definition, actor_declarations[actor],
                                 arguments)