diff options
author | Sebastian Koslowski <sebastian.koslowski@gmail.com> | 2016-05-03 17:13:08 +0200 |
---|---|---|
committer | Johnathan Corgan <johnathan@corganlabs.com> | 2017-06-29 09:16:49 -0700 |
commit | 7f7fa2f91467fdb2b11312be8562e7b51fdeb199 (patch) | |
tree | 24268bac15b9920d2a15ddbb45eaf3b9b03718a1 /grc/core/ports | |
parent | 44cae388881821942e691a4d69a923bbd8d347db (diff) |
grc: added yaml/mako support
Includes basic converter from XML/Cheetah to YAML/Mako based block format.
Diffstat (limited to 'grc/core/ports')
-rw-r--r-- | grc/core/ports/__init__.py | 23 | ||||
-rw-r--r-- | grc/core/ports/_virtual_connections.py | 126 | ||||
-rw-r--r-- | grc/core/ports/clone.py | 38 | ||||
-rw-r--r-- | grc/core/ports/port.py | 207 |
4 files changed, 394 insertions, 0 deletions
diff --git a/grc/core/ports/__init__.py b/grc/core/ports/__init__.py new file mode 100644 index 0000000000..375b5d63e3 --- /dev/null +++ b/grc/core/ports/__init__.py @@ -0,0 +1,23 @@ +""" +Copyright 2008-2015 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from __future__ import absolute_import + +from .port import Port +from .clone import PortClone diff --git a/grc/core/ports/_virtual_connections.py b/grc/core/ports/_virtual_connections.py new file mode 100644 index 0000000000..45f4a247fd --- /dev/null +++ b/grc/core/ports/_virtual_connections.py @@ -0,0 +1,126 @@ +# Copyright 2008-2016 Free Software Foundation, Inc. +# This file is part of GNU Radio +# +# GNU Radio Companion is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# GNU Radio Companion is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +from __future__ import absolute_import + +from itertools import chain + +from .. import blocks + + +class LoopError(Exception): + pass + + +def upstream_ports(port): + if port.is_sink: + return _sources_from_virtual_sink_port(port) + else: + return _sources_from_virtual_source_port(port) + + +def _sources_from_virtual_sink_port(sink_port, _traversed=None): + """ + Resolve the source port that is connected to the given virtual sink port. + Use the get source from virtual source to recursively resolve subsequent ports. + """ + source_ports_per_virtual_connection = ( + # there can be multiple ports per virtual connection + _sources_from_virtual_source_port(c.source_port, _traversed) # type: list + for c in sink_port.connections(enabled=True) + ) + return list(chain(*source_ports_per_virtual_connection)) # concatenate generated lists of ports + + +def _sources_from_virtual_source_port(source_port, _traversed=None): + """ + Recursively resolve source ports over the virtual connections. + Keep track of traversed sources to avoid recursive loops. + """ + _traversed = set(_traversed or []) # a new set! + if source_port in _traversed: + raise LoopError('Loop found when resolving port type') + _traversed.add(source_port) + + block = source_port.parent_block + flow_graph = source_port.parent_flowgraph + + if not isinstance(block, blocks.VirtualSource): + return [source_port] # nothing to resolve, we're done + + stream_id = block.params['stream_id'].value + + # currently the validation does not allow multiple virtual sinks and one virtual source + # but in the future it may... + connected_virtual_sink_blocks = ( + b for b in flow_graph.iter_enabled_blocks() + if isinstance(b, blocks.VirtualSink) and b.params['stream_id'].value == stream_id + ) + source_ports_per_virtual_connection = ( + _sources_from_virtual_sink_port(b.sinks[0], _traversed) # type: list + for b in connected_virtual_sink_blocks + ) + return list(chain(*source_ports_per_virtual_connection)) # concatenate generated lists of ports + + +def downstream_ports(port): + if port.is_source: + return _sinks_from_virtual_source_port(port) + else: + return _sinks_from_virtual_sink_port(port) + + +def _sinks_from_virtual_source_port(source_port, _traversed=None): + """ + Resolve the sink port that is connected to the given virtual source port. + Use the get sink from virtual sink to recursively resolve subsequent ports. + """ + sink_ports_per_virtual_connection = ( + # there can be multiple ports per virtual connection + _sinks_from_virtual_sink_port(c.sink_port, _traversed) # type: list + for c in source_port.connections(enabled=True) + ) + return list(chain(*sink_ports_per_virtual_connection)) # concatenate generated lists of ports + + +def _sinks_from_virtual_sink_port(sink_port, _traversed=None): + """ + Recursively resolve sink ports over the virtual connections. + Keep track of traversed sinks to avoid recursive loops. + """ + _traversed = set(_traversed or []) # a new set! + if sink_port in _traversed: + raise LoopError('Loop found when resolving port type') + _traversed.add(sink_port) + + block = sink_port.parent_block + flow_graph = sink_port.parent_flowgraph + + if not isinstance(block, blocks.VirtualSink): + return [sink_port] + + stream_id = block.params['stream_id'].value + + connected_virtual_source_blocks = ( + b for b in flow_graph.iter_enabled_blocks() + if isinstance(b, blocks.VirtualSource) and b.params['stream_id'].value == stream_id + ) + sink_ports_per_virtual_connection = ( + _sinks_from_virtual_source_port(b.sources[0], _traversed) # type: list + for b in connected_virtual_source_blocks + ) + return list(chain(*sink_ports_per_virtual_connection)) # concatenate generated lists of ports diff --git a/grc/core/ports/clone.py b/grc/core/ports/clone.py new file mode 100644 index 0000000000..4e1320f81d --- /dev/null +++ b/grc/core/ports/clone.py @@ -0,0 +1,38 @@ +# Copyright 2016 Free Software Foundation, Inc. +# This file is part of GNU Radio +# +# GNU Radio Companion is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# GNU Radio Companion is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +from .port import Port, Element + + +class PortClone(Port): + + def __init__(self, parent, direction, master, name, key): + Element.__init__(self, parent) + self.master_port = master + + self.name = name + self.key = key + self.multiplicity = 1 + + def __getattr__(self, item): + return getattr(self.master_port, item) + + def add_clone(self): + raise NotImplementedError() + + def remove_clone(self, port): + raise NotImplementedError() diff --git a/grc/core/ports/port.py b/grc/core/ports/port.py new file mode 100644 index 0000000000..139aae0ccc --- /dev/null +++ b/grc/core/ports/port.py @@ -0,0 +1,207 @@ +# Copyright 2008-2016 Free Software Foundation, Inc. +# This file is part of GNU Radio +# +# GNU Radio Companion is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# GNU Radio Companion is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +from __future__ import absolute_import + +from . import _virtual_connections + +from .. import Constants +from ..base import Element +from ..utils.descriptors import ( + EvaluatedFlag, EvaluatedEnum, EvaluatedPInt, + setup_names, lazy_property +) + + +@setup_names +class Port(Element): + + is_port = True + + dtype = EvaluatedEnum(list(Constants.TYPE_TO_SIZEOF.keys()), default='') + vlen = EvaluatedPInt() + multiplicity = EvaluatedPInt() + hidden = EvaluatedFlag() + optional = EvaluatedFlag() + + def __init__(self, parent, direction, id, label='', domain=Constants.DEFAULT_DOMAIN, dtype='', + vlen='', multiplicity=1, optional=False, hide='', **_): + """Make a new port from nested data.""" + Element.__init__(self, parent) + + self._dir = direction + self.key = id + if not label: + label = id if not id.isdigit() else {'sink': 'in', 'source': 'out'}[direction] + id + self.name = self._base_name = label + + self.domain = domain + self.dtype = dtype + self.vlen = vlen + + if domain == Constants.GR_MESSAGE_DOMAIN: # ToDo: message port class + self.key = self.name + self.dtype = 'message' + + self.multiplicity = multiplicity + self.optional = optional + self.hidden = hide + # end of args ######################################################## + self.clones = [] # References to cloned ports (for nports > 1) + + def __str__(self): + if self.is_source: + return 'Source - {}({})'.format(self.name, self.key) + if self.is_sink: + return 'Sink - {}({})'.format(self.name, self.key) + + def __repr__(self): + return '{!r}.{}[{}]'.format(self.parent, 'sinks' if self.is_sink else 'sources', self.key) + + @property + def item_size(self): + return Constants.TYPE_TO_SIZEOF[self.dtype] * self.vlen + + @lazy_property + def is_sink(self): + return self._dir == 'sink' + + @lazy_property + def is_source(self): + return self._dir == 'source' + + @property + def inherit_type(self): + """always empty for e.g. virtual blocks, may eval to empty for 'Wildcard'""" + return not self.dtype + + def validate(self): + Element.validate(self) + platform = self.parent_platform + + num_connections = len(list(self.connections(enabled=True))) + need_connection = not self.optional and not self.hidden + if need_connection and num_connections == 0: + self.add_error_message('Port is not connected.') + + if self.dtype not in Constants.TYPE_TO_SIZEOF.keys(): + self.add_error_message('Type "{}" is not a possible type.'.format(self.dtype)) + + try: + domain = platform.domains[self.domain] + if self.is_sink and not domain.multi_in and num_connections > 1: + self.add_error_message('Domain "{}" can have only one upstream block' + ''.format(self.domain)) + if self.is_source and not domain.multi_out and num_connections > 1: + self.add_error_message('Domain "{}" can have only one downstream block' + ''.format(self.domain)) + except KeyError: + self.add_error_message('Domain key "{}" is not registered.'.format(self.domain)) + + def rewrite(self): + del self.vlen + del self.multiplicity + del self.hidden + del self.optional + del self.dtype + + if self.inherit_type: + self.resolve_empty_type() + + Element.rewrite(self) + + # Update domain if was deduced from (dynamic) port type + if self.domain == Constants.GR_STREAM_DOMAIN and self.dtype == "message": + self.domain = Constants.GR_MESSAGE_DOMAIN + self.key = self.name + if self.domain == Constants.GR_MESSAGE_DOMAIN and self.dtype != "message": + self.domain = Constants.GR_STREAM_DOMAIN + self.key = '0' # Is rectified in rewrite() + + def resolve_virtual_source(self): + """Only used by Generator after validation is passed""" + return _virtual_connections.upstream_ports(self) + + def resolve_empty_type(self): + def find_port(finder): + try: + return next((p for p in finder(self) if not p.inherit_type), None) + except _virtual_connections.LoopError as error: + self.add_error_message(str(error)) + except (StopIteration, Exception): + pass + + try: + port = find_port(_virtual_connections.upstream_ports) or \ + find_port(_virtual_connections.downstream_ports) + self.set_evaluated('dtype', port.dtype) # we don't want to override the template + self.set_evaluated('vlen', port.vlen) # we don't want to override the template + self.domain = port.domain + except AttributeError: + self.domain = Constants.DEFAULT_DOMAIN + + def add_clone(self): + """ + Create a clone of this (master) port and store a reference in self._clones. + + The new port name (and key for message ports) will have index 1... appended. + If this is the first clone, this (master) port will get a 0 appended to its name (and key) + + Returns: + the cloned port + """ + # Add index to master port name if there are no clones yet + if not self.clones: + self.name = self._base_name + '0' + # Also update key for none stream ports + if not self.key.isdigit(): + self.key = self.name + + name = self._base_name + str(len(self.clones) + 1) + # Dummy value 99999 will be fixed later + key = '99999' if self.key.isdigit() else name + + # Clone + port_factory = self.parent_platform.make_port + port = port_factory(self.parent, direction=self._dir, + name=name, key=key, + master=self, cls_key='clone') + + self.clones.append(port) + return port + + def remove_clone(self, port): + """ + Remove a cloned port (from the list of clones only) + Remove the index 0 of the master port name (and key9 if there are no more clones left + """ + self.clones.remove(port) + # Remove index from master port name if there are no more clones + if not self.clones: + self.name = self._base_name + # Also update key for none stream ports + if not self.key.isdigit(): + self.key = self.name + + def connections(self, enabled=None): + """Iterator over all connections to/from this port + + enabled: None for all, True for enabled only, False for disabled only + """ + for con in self.parent_flowgraph.connections: + if self in con and (enabled is None or enabled == con.enabled): + yield con |