mirror of
https://github.com/gnuradio/gnuradio-companion.git
synced 2025-12-10 00:42:30 -06:00
354 lines
12 KiB
Python
354 lines
12 KiB
Python
import codecs
|
|
import operator
|
|
import os
|
|
import tempfile
|
|
import textwrap
|
|
import time
|
|
import re
|
|
|
|
from mako.template import Template
|
|
|
|
from .. import Messages, blocks
|
|
from ..Constants import TOP_BLOCK_FILE_MODE
|
|
from .FlowGraphProxy import FlowGraphProxy
|
|
from ..utils import expr_utils
|
|
from .top_block import TopBlockGenerator
|
|
|
|
DATA_DIR = os.path.dirname(__file__)
|
|
|
|
HEADER_TEMPLATE = os.path.join(DATA_DIR, 'cpp_templates/flow_graph.hpp.mako')
|
|
SOURCE_TEMPLATE = os.path.join(DATA_DIR, 'cpp_templates/flow_graph.cpp.mako')
|
|
CMAKE_TEMPLATE = os.path.join(DATA_DIR, 'cpp_templates/CMakeLists.txt.mako')
|
|
|
|
header_template = Template(filename=HEADER_TEMPLATE)
|
|
source_template = Template(filename=SOURCE_TEMPLATE)
|
|
cmake_template = Template(filename=CMAKE_TEMPLATE)
|
|
|
|
|
|
class CppTopBlockGenerator(TopBlockGenerator):
|
|
|
|
def __init__(self, flow_graph, file_path):
|
|
"""
|
|
Initialize the top block generator object.
|
|
|
|
Args:
|
|
flow_graph: the flow graph object
|
|
file_path: the path to write the file to
|
|
"""
|
|
|
|
self._flow_graph = FlowGraphProxy(flow_graph)
|
|
self._generate_options = self._flow_graph.get_option('generate_options')
|
|
|
|
self._mode = TOP_BLOCK_FILE_MODE
|
|
dirname = os.path.dirname(file_path)
|
|
# Handle the case where the directory is read-only
|
|
# In this case, use the system's temp directory
|
|
if not os.access(dirname, os.W_OK):
|
|
dirname = tempfile.gettempdir()
|
|
|
|
filename = self._flow_graph.get_option('id')
|
|
self.file_path = os.path.join(dirname, filename)
|
|
self._dirname = dirname
|
|
|
|
def write(self):
|
|
"""generate output and write it to files"""
|
|
self._warnings()
|
|
|
|
fg = self._flow_graph
|
|
self.title = fg.get_option('title') or fg.get_option('id').replace('_', ' ').title()
|
|
variables = fg.get_cpp_variables()
|
|
parameters = fg.get_parameters()
|
|
monitors = fg.get_monitors()
|
|
self._variable_types()
|
|
self._parameter_types()
|
|
|
|
self.namespace = {
|
|
'flow_graph': fg,
|
|
'variables': variables,
|
|
'parameters': parameters,
|
|
'monitors': monitors,
|
|
'generate_options': self._generate_options,
|
|
'generated_time': time.ctime(),
|
|
}
|
|
|
|
if not os.path.exists(os.path.join(self.file_path, 'build')):
|
|
os.makedirs(os.path.join(self.file_path, 'build'))
|
|
|
|
for filename, data in self._build_cpp_header_code_from_template():
|
|
with codecs.open(filename, 'w', encoding='utf-8') as fp:
|
|
fp.write(data)
|
|
|
|
for filename, data in self._build_cpp_source_code_from_template():
|
|
with codecs.open(filename, 'w', encoding='utf-8') as fp:
|
|
fp.write(data)
|
|
|
|
if fg.get_option('gen_cmake') == 'On':
|
|
for filename, data in self._build_cmake_code_from_template():
|
|
with codecs.open(filename, 'w', encoding='utf-8') as fp:
|
|
fp.write(data)
|
|
|
|
def _build_cpp_source_code_from_template(self):
|
|
"""
|
|
Convert the flow graph to a C++ source file.
|
|
|
|
Returns:
|
|
a string of C++ code
|
|
"""
|
|
file_path = self.file_path + '/' + self._flow_graph.get_option('id') + '.cpp'
|
|
|
|
output = []
|
|
|
|
flow_graph_code = source_template.render(
|
|
title=self.title,
|
|
includes=self._includes(),
|
|
blocks=self._blocks(),
|
|
callbacks=self._callbacks(),
|
|
connections=self._connections(),
|
|
**self.namespace
|
|
)
|
|
# strip trailing white-space
|
|
flow_graph_code = "\n".join(line.rstrip() for line in flow_graph_code.split("\n"))
|
|
output.append((file_path, flow_graph_code))
|
|
|
|
return output
|
|
|
|
|
|
def _build_cpp_header_code_from_template(self):
|
|
"""
|
|
Convert the flow graph to a C++ header file.
|
|
|
|
Returns:
|
|
a string of C++ code
|
|
"""
|
|
file_path = self.file_path + '/' + self._flow_graph.get_option('id') + '.hpp'
|
|
|
|
output = []
|
|
|
|
flow_graph_code = header_template.render(
|
|
title=self.title,
|
|
includes=self._includes(),
|
|
blocks=self._blocks(),
|
|
callbacks=self._callbacks(),
|
|
connections=self._connections(),
|
|
**self.namespace
|
|
)
|
|
# strip trailing white-space
|
|
flow_graph_code = "\n".join(line.rstrip() for line in flow_graph_code.split("\n"))
|
|
output.append((file_path, flow_graph_code))
|
|
|
|
return output
|
|
|
|
def _build_cmake_code_from_template(self):
|
|
"""
|
|
Convert the flow graph to a CMakeLists.txt file.
|
|
|
|
Returns:
|
|
a string of CMake code
|
|
"""
|
|
filename = 'CMakeLists.txt'
|
|
file_path = os.path.join(self.file_path, filename)
|
|
|
|
output = []
|
|
|
|
flow_graph_code = cmake_template.render(
|
|
title=self.title,
|
|
includes=self._includes(),
|
|
blocks=self._blocks(),
|
|
callbacks=self._callbacks(),
|
|
connections=self._connections(),
|
|
links=self._links(),
|
|
**self.namespace
|
|
)
|
|
# strip trailing white-space
|
|
flow_graph_code = "\n".join(line.rstrip() for line in flow_graph_code.split("\n"))
|
|
output.append((file_path, flow_graph_code))
|
|
|
|
return output
|
|
|
|
def _links(self):
|
|
fg = self._flow_graph
|
|
links = fg.links()
|
|
seen = set()
|
|
output = []
|
|
|
|
for link_list in links:
|
|
if link_list:
|
|
for link in link_list:
|
|
seen.add(link)
|
|
|
|
return list(seen)
|
|
|
|
def _includes(self):
|
|
fg = self._flow_graph
|
|
includes = fg.includes()
|
|
seen = set()
|
|
output = []
|
|
|
|
def is_duplicate(l):
|
|
if l.startswith('#include') and l in seen:
|
|
return True
|
|
seen.add(line)
|
|
return False
|
|
|
|
for block_ in includes:
|
|
for include_ in block_:
|
|
if not include_:
|
|
continue
|
|
line = include_.rstrip()
|
|
if not is_duplicate(line):
|
|
output.append(line)
|
|
|
|
return output
|
|
|
|
def _blocks(self):
|
|
fg = self._flow_graph
|
|
parameters = fg.get_parameters()
|
|
|
|
# List of blocks not including variables and imports and parameters and disabled
|
|
def _get_block_sort_text(block):
|
|
code = block.cpp_templates.render('make').replace(block.name, ' ')
|
|
try:
|
|
code += block.params['gui_hint'].get_value() # Newer gui markup w/ qtgui
|
|
except:
|
|
pass
|
|
return code
|
|
|
|
blocks = [
|
|
b for b in fg.blocks
|
|
if b.enabled and not (b.get_bypassed() or b.is_import or b in parameters or b.key == 'options')
|
|
]
|
|
|
|
blocks = expr_utils.sort_objects(blocks, operator.attrgetter('name'), _get_block_sort_text)
|
|
blocks_make = []
|
|
for block in blocks:
|
|
translations = block.cpp_templates.render('translations')
|
|
make = block.cpp_templates.render('make')
|
|
declarations = block.cpp_templates.render('declarations')
|
|
|
|
if translations:
|
|
# TODO: Would be nice to avoid using eval() here
|
|
translations_dict = eval(translations)
|
|
for key in translations_dict:
|
|
make = make.replace(key, translations_dict[key])
|
|
declarations = declarations.replace(key, translations_dict[key])
|
|
if make:
|
|
blocks_make.append((block, make, declarations))
|
|
elif 'qt' in block.key:
|
|
# The QT Widget blocks are technically variables,
|
|
# but they contain some code we don't want to miss
|
|
blocks_make.append(('', make, declarations))
|
|
return blocks_make
|
|
|
|
def _variable_types(self):
|
|
fg = self._flow_graph
|
|
variables = fg.get_cpp_variables()
|
|
|
|
for var in variables:
|
|
var.decide_type()
|
|
|
|
def _parameter_types(self):
|
|
fg = self._flow_graph
|
|
parameters = fg.get_parameters()
|
|
|
|
for param in parameters:
|
|
type_translation = {'eng_float' : 'double', 'intx' : 'int', 'std' : 'std::string'};
|
|
param.vtype = type_translation[param.params['type'].value]
|
|
|
|
def _callbacks(self):
|
|
fg = self._flow_graph
|
|
variables = fg.get_cpp_variables()
|
|
parameters = fg.get_parameters()
|
|
|
|
# List of variable names
|
|
var_ids = [var.name for var in parameters + variables]
|
|
|
|
replace_dict = dict((var_id, 'this->' + var_id) for var_id in var_ids)
|
|
|
|
callbacks_all = []
|
|
for block in fg.iter_enabled_blocks():
|
|
callbacks_all.extend(expr_utils.expr_replace(cb, replace_dict) for cb in block.get_cpp_callbacks())
|
|
|
|
# Map var id to callbacks
|
|
def uses_var_id(callback):
|
|
used = expr_utils.get_variable_dependencies(callback, [var_id])
|
|
return used and ('this->' + var_id in callback) # callback might contain var_id itself
|
|
|
|
callbacks = {}
|
|
for var_id in var_ids:
|
|
callbacks[var_id] = [callback for callback in callbacks_all if uses_var_id(callback)]
|
|
|
|
return callbacks
|
|
|
|
def _connections(self):
|
|
fg = self._flow_graph
|
|
templates = {key: Template(text)
|
|
for key, text in fg.parent_platform.cpp_connection_templates.items()}
|
|
|
|
def make_port_sig(port):
|
|
# TODO: make sense of this
|
|
if port.parent.key in ('pad_source', 'pad_sink'):
|
|
block = 'self'
|
|
key = fg.get_pad_port_global_key(port)
|
|
else:
|
|
block = 'this->' + port.parent_block.name
|
|
key = port.key
|
|
|
|
if not key.isdigit():
|
|
key = re.findall(r'\d+', key)[0]
|
|
|
|
return '{block}, {key}'.format(block=block, key=key)
|
|
|
|
connections = fg.get_enabled_connections()
|
|
|
|
# Get the virtual blocks and resolve their connections
|
|
connection_factory = fg.parent_platform.Connection
|
|
virtual = [c for c in connections if isinstance(c.source_block, blocks.VirtualSource)]
|
|
for connection in virtual:
|
|
sink = connection.sink_port
|
|
for source in connection.source_port.resolve_virtual_source():
|
|
resolved = connection_factory(fg.orignal_flowgraph, source, sink)
|
|
connections.append(resolved)
|
|
# Remove the virtual connection
|
|
connections.remove(connection)
|
|
|
|
# Bypassing blocks: Need to find all the enabled connections for the block using
|
|
# the *connections* object rather than get_connections(). Create new connections
|
|
# that bypass the selected block and remove the existing ones. This allows adjacent
|
|
# bypassed blocks to see the newly created connections to downstream blocks,
|
|
# allowing them to correctly construct bypass connections.
|
|
bypassed_blocks = fg.get_bypassed_blocks()
|
|
for block in bypassed_blocks:
|
|
# Get the upstream connection (off of the sink ports)
|
|
# Use *connections* not get_connections()
|
|
source_connection = [c for c in connections if c.sink_port == block.sinks[0]]
|
|
# The source connection should never have more than one element.
|
|
assert (len(source_connection) == 1)
|
|
|
|
# Get the source of the connection.
|
|
source_port = source_connection[0].source_port
|
|
|
|
# Loop through all the downstream connections
|
|
for sink in (c for c in connections if c.source_port == block.sources[0]):
|
|
if not sink.enabled:
|
|
# Ignore disabled connections
|
|
continue
|
|
connection = connection_factory(fg.orignal_flowgraph, source_port, sink.sink_port)
|
|
connections.append(connection)
|
|
# Remove this sink connection
|
|
connections.remove(sink)
|
|
# Remove the source connection
|
|
connections.remove(source_connection[0])
|
|
|
|
# List of connections where each endpoint is enabled (sorted by domains, block names)
|
|
def by_domain_and_blocks(c):
|
|
return c.type, c.source_block.name, c.sink_block.name
|
|
|
|
rendered = []
|
|
for con in sorted(connections, key=by_domain_and_blocks):
|
|
template = templates[con.type]
|
|
code = template.render(make_port_sig=make_port_sig, source=con.source_port, sink=con.sink_port)
|
|
code = 'this->tb->' + code
|
|
rendered.append(code)
|
|
|
|
return rendered
|