Ejemplo n.º 1
0
def main():

    long_description = """
    Provide documentation for modules, actors, and components.
    """

    default_format = 'compact' if sys.argv[0].endswith('csdocs') else 'detailed'

    argparser = argparse.ArgumentParser(description=long_description)
    group = argparser.add_mutually_exclusive_group()
    group.add_argument('what', metavar='<actor or module>', type=str, nargs='?', default='',
                       help='What to look up documentation for, if empty show top level documentation')
    group.add_argument('--all', action='store_const', const=True, default=False,
                       help='Generate complete actor documentation in Markdown format')
    argparser.add_argument('--format', default=default_format, choices=['detailed', 'compact', 'raw'],
                           help='Options "detailed" and "compact" returns Markdown-formatted text,'
                                ' while "raw" returns a JSON-formatted representation that can be'
                                ' used to generated the documentation in other formats.')

    args = argparser.parse_args()
    store = DocumentationStore()

    if args.all:
        all_docs()
    else:
        if args.format == 'raw':
            print json.dumps(store.help_raw(args.what))
        else:
            print store.help(args.what, args.format == 'compact')
Ejemplo n.º 2
0
 def handle_get_actor_doc(self, handle, connection, match, data, hdr):
     """ Query ActorStore for documentation
     """
     path = match.group(1)
     what = '.'.join(path.strip('/').split('/'))
     ds = DocumentationStore()
     data = ds.help_raw(what)
     self.send_response(handle, connection, json.dumps(data))
Ejemplo n.º 3
0
 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()
Ejemplo n.º 4
0
 def __init__(self, cs_info):
     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.check()
Ejemplo n.º 5
0
def handle_get_actor_doc(self, handle, connection, match, data, hdr):
    """
    GET /actor_doc {path}
    Get documentation in 'raw' format for actor or module at {path}
    Path is formatted as '/{module}/{submodule}/ ... /{actor}'.
    If {path} is empty return top-level documentation.
    See DocumentStore help_raw() for details on data format.
    Response status code: OK
    Response: dictionary with documentation
    """
    path = match.group(1)
    what = '.'.join(path.strip('/').split('/'))
    ds = DocumentationStore()
    what = None if not what else what
    data = ds.help_raw(what)
    self.send_response(handle, connection, data)
Ejemplo n.º 6
0
def _lookup(node, issue_tracker):
    if _is_local_component(node.actor_type):
        comps = query(_root(node), kind=ast.Component, attributes={'name':node.actor_type})
        if not comps:
            reason = "Missing local component definition: '{}'".format(node.actor_type)
            issue_tracker.add_error(reason, node)
            return {'is_known': False}
        comp = comps[0]
        metadata = {
            'is_known': True,
            'name': comp.name,
            'type': 'component',
            'inputs': comp.inports,
            'outputs': comp.outports,
            'args':{
                'mandatory':comp.arg_names,
                'optional':{}
            },
            'definition': comp.children[0]
        }
    else:
        metadata = DocumentationStore().metadata(node.actor_type)
        if not metadata['is_known']:
            reason = "Not validating actor type: '{}'".format(node.actor_type)
            issue_tracker.add_warning(reason, node)

    return metadata
def handle_get_actor_doc(self, handle, connection, match, data, hdr):
    """
    GET /actor_doc/{path}
    Get documentation in 'raw' format for actor or module at {path}
    Path is formatted as '/{module}/{submodule}/ ... /{actor}'.
    If {path} is empty return top-level documentation.
    See DocumentStore help_raw() for details on data format.
    Response status code: OK
    Response: dictionary with documentation
    """
    path = match.group(1)
    what = '.'.join(path.strip('/').split('/'))
    ds = DocumentationStore()
    what = None if not what else what
    data = ds.help_raw(what)
    self.send_response(handle, connection, data)
Ejemplo n.º 8
0
class ActorViz(Viz):
    """docstring for ActorViz"""

    docstore = DocumentationStore()

    def __init__(self, name, actor_type, args, **dummy):
        super(ActorViz, self).__init__()
        self.type_color = 'lightblue'
        self.name = name
        self.args = args
        self.actor_type = actor_type
        doc = self.docstore.help_raw(actor_type)
        self.set_ports(doc)

    def set_ports(self, doc):
        inports = [p for p, _ in doc['inputs']]
        outports = [p for p, _ in doc['outputs']]
        inlen = len(inports)
        outlen = len(outports)
        self.portrows = max(inlen, outlen)
        self.inports = inports + [''] * (self.portrows - inlen)
        self.outports = outports + [''] * (self.portrows - outlen)

    def __str__(self):
        lines = []
        lines.append('{0} [label=<'.format(_refname(self.name)))
        lines.append(
            '<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="0" CELLPADDING="1">'
        )
        # Name
        lines.append('<TR><TD bgcolor="{1}" COLSPAN="3">{0}</TD></TR>'.format(
            self.name, self.type_color))
        # Class
        lines.append('<TR><TD COLSPAN="3">{0}</TD></TR>'.format(
            self.actor_type))
        is_first = True
        for inport, outport in zip(self.inports, self.outports):
            inref = ' bgcolor="lightgrey" PORT="{0}_in"'.format(
                inport) if inport else ''
            outref = ' bgcolor="lightgrey" PORT="{0}_out"'.format(
                outport) if outport else ''
            if is_first:
                is_first = False
                middle = '<TD ROWSPAN="{0}">    </TD>'.format(self.portrows)
            else:
                middle = ''
            lines.append(
                '<TR><TD{0} align="left">{1}</TD>{4}<TD{2} align="right">{3}</TD></TR>'
                .format(inref, inport, outref, outport, middle))
        lines.append('</TABLE>>];')

        return '\n'.join(lines)
Ejemplo n.º 9
0
def all_docs(what=None):
    ds = DocumentationStore()

    raw = ds.help_raw(what)
    print ds.help(what)

    for actor in raw.get('actors', []):
        print ds.help(what + '.' + actor)

    for module in raw.get('modules', []):
        all_docs(module)
Ejemplo n.º 10
0
    def _init_metadata(self):
        def gather_actors(module):
            # Depth first
            l = []
            for m in d.modules(module):
                l = l + gather_actors(m)
            actors = d.actors(module)
            if module:
                # Add namespace
                actors = ['.'.join([module, a]) for a in actors]
            return l + actors

        d = DocumentationStore()
        metadata = {}
        actors = gather_actors('')

        for actor in actors:
            parts = actor.split('.')
            x = metadata
            for p in parts[:-1]:
                x = x.setdefault(p, {})
            x[parts[-1]] = d.metadata(actor)

        self.metadata = metadata
Ejemplo n.º 11
0
    def _init_metadata(self):
        def gather_actors(module):
            # Depth first
            l = []
            for m in d.modules(module):
                l = l + gather_actors(m)
            actors = d.actors(module)
            if module:
                # Add namespace
                actors = ['.'.join([module, a]) for a in actors]
            return l + actors

        d = DocumentationStore()
        metadata = {}
        actors = gather_actors('')

        for actor in actors:
            parts = actor.split('.')
            x = metadata
            for p in parts[:-1]:
                x = x.setdefault(p, {})
            x[parts[-1]] = d.metadata(actor)

        self.metadata = metadata
Ejemplo n.º 12
0
import visitor
import astnode as ast
from codegen import calvin_astgen, query
from parser import calvin_parse
from calvin.actorstore.store import DocumentationStore

_docstore = DocumentationStore()

def _refname(name):
   return name.replace(':', '_')

def _lookup_definition(actor_type, root):
    if '.' in actor_type:
        doc = _docstore.help_raw(actor_type)
        if not doc['name']:
            return ([], [], '')
        t = doc['type']

        inports = [p for p,_ in doc['inputs']]
        outports = [p for p,_ in doc['outputs']]
    else:
        t = 'component'
        comps = query(root, kind=ast.Component, attributes={'name':actor_type}, maxdepth=2)
        if not comps:
            return ([], [], '')

        inports, outports = comps[0].inports, comps[0].outports

    return (inports, outports, t)

#
Ejemplo n.º 13
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])
Ejemplo n.º 14
0
 def setUp(self):
     self.ds = DocumentationStore()
Ejemplo n.º 15
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)
def document(what):
    store = DocumentationStore()
    print store.help(what or None, compact=False, formatting='md', links=False)
Ejemplo n.º 17
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)