9 This module implements the core functionality of the UAVCAN DSDL compiler for libuavcan.
10 Supported Python versions: 3.2+, 2.7.
11 It accepts a list of root namespaces and produces the set of C++ header files for libuavcan.
12 It is based on the DSDL parsing package from pyuavcan.
15 from __future__
import division, absolute_import, print_function, unicode_literals
16 import sys, os, logging, errno, re
17 from .pyratemp
import Template
18 from pyuavcan_v0
import dsdl
26 OUTPUT_FILE_EXTENSION =
'hpp'
27 OUTPUT_FILE_PERMISSIONS = 0o444
28 TEMPLATE_FILENAME = os.path.join(os.path.dirname(__file__),
'data_type_template.tmpl')
30 __all__ = [
'run',
'logger',
'DsdlCompilerException']
35 logger = logging.getLogger(__name__)
37 def run(source_dirs, include_dirs, output_dir):
39 This function takes a list of root namespace directories (containing DSDL definition files to parse), a
40 possibly empty list of search directories (containing DSDL definition files that can be referenced from the types
41 that are going to be parsed), and the output directory path (possibly nonexistent) where the generated C++
42 header files will be stored.
44 Note that this module features lazy write, i.e. if an output file does already exist and its content is not going
45 to change, it will not be overwritten. This feature allows to avoid unnecessary recompilation of dependent object
49 source_dirs List of root namespace directories to parse.
50 include_dirs List of root namespace directories with referenced types (possibly empty). This list is
51 automaitcally extended with source_dirs.
52 output_dir Output directory path. Will be created if doesn't exist.
54 assert isinstance(source_dirs, list)
55 assert isinstance(include_dirs, list)
56 output_dir =
str(output_dir)
58 types =
run_parser(source_dirs, include_dirs + source_dirs)
60 die(
'No type definitions were found')
62 logger.info(
'%d types total', len(types))
69 a = os.path.abspath(filename)
70 r = os.path.relpath(filename)
71 return a
if '..' in r
else r
76 assert t.category == t.CATEGORY_COMPOUND
77 return t.full_name.replace(
'.', os.path.sep) +
'.' + OUTPUT_FILE_EXTENSION
82 os.makedirs(path, exist_ok=
True)
86 if ex.errno != errno.EEXIST:
94 types = dsdl.parse_namespaces(source_dirs, search_dirs)
95 except dsdl.DsdlException
as ex:
96 logger.info(
'Parser failure', exc_info=
True)
103 dest_dir = os.path.abspath(dest_dir)
106 logger.info(
'Generating type %s', t.full_name)
110 except Exception
as ex:
111 logger.info(
'Generator failure', exc_info=
True)
115 dirname = os.path.dirname(filename)
119 if os.path.exists(filename):
120 with open(filename)
as f:
121 existing_data = f.read()
122 if data == existing_data:
131 with open(filename,
'w')
as f:
134 os.chmod(filename, OUTPUT_FILE_PERMISSIONS)
135 except (OSError, IOError)
as ex:
136 logger.warning(
'Failed to set permissions for %s: %s',
pretty_filename(filename), ex)
139 if t.category == t.CATEGORY_PRIMITIVE:
141 t.CAST_MODE_SATURATED:
'::uavcan::CastModeSaturate',
142 t.CAST_MODE_TRUNCATED:
'::uavcan::CastModeTruncate',
144 if t.kind == t.KIND_FLOAT:
145 return '::uavcan::FloatSpec< %d, %s >' % (t.bitlen, cast_mode)
148 t.KIND_BOOLEAN:
'::uavcan::SignednessUnsigned',
149 t.KIND_UNSIGNED_INT:
'::uavcan::SignednessUnsigned',
150 t.KIND_SIGNED_INT:
'::uavcan::SignednessSigned',
152 return '::uavcan::IntegerSpec< %d, %s, %s >' % (t.bitlen, signedness, cast_mode)
153 elif t.category == t.CATEGORY_ARRAY:
156 t.MODE_STATIC:
'::uavcan::ArrayModeStatic',
157 t.MODE_DYNAMIC:
'::uavcan::ArrayModeDynamic',
159 return '::uavcan::Array< %s, %s, %d >' % (value_type, mode, t.max_size)
160 elif t.category == t.CATEGORY_COMPOUND:
161 return '::' + t.full_name.replace(
'.',
'::')
162 elif t.category == t.CATEGORY_VOID:
163 return '::uavcan::IntegerSpec< %d, ::uavcan::SignednessUnsigned, ::uavcan::CastModeSaturate >' % t.bitlen
168 t.short_name = t.full_name.split(
'.')[-1]
169 t.cpp_type_name = t.short_name +
'_'
170 t.cpp_full_type_name =
'::' + t.full_name.replace(
'.',
'::')
171 t.include_guard = t.full_name.replace(
'.',
'_').upper() +
'_HPP_INCLUDED'
174 def fields_includes(fields):
175 def detect_include(t):
176 if t.category == t.CATEGORY_COMPOUND:
178 if t.category == t.CATEGORY_ARRAY:
179 return detect_include(t.value_type)
180 return list(sorted(set(filter(
None, [detect_include(x.type)
for x
in fields]))))
182 if t.kind == t.KIND_MESSAGE:
183 t.cpp_includes = fields_includes(t.fields)
185 t.cpp_includes = fields_includes(t.request_fields + t.response_fields)
187 t.cpp_namespace_components = t.full_name.split(
'.')[:-1]
188 t.has_default_dtid = t.default_dtid
is not None
191 def inject_cpp_types(attributes):
195 a.void = a.type.category == a.type.CATEGORY_VOID
198 a.name =
'_void_%d' % void_index
201 if t.kind == t.KIND_MESSAGE:
202 inject_cpp_types(t.fields)
203 inject_cpp_types(t.constants)
204 t.all_attributes = t.fields + t.constants
205 t.union = t.union
and len(t.fields)
207 inject_cpp_types(t.request_fields)
208 inject_cpp_types(t.request_constants)
209 inject_cpp_types(t.response_fields)
210 inject_cpp_types(t.response_constants)
211 t.all_attributes = t.request_fields + t.request_constants + t.response_fields + t.response_constants
212 t.request_union = t.request_union
and len(t.request_fields)
213 t.response_union = t.response_union
and len(t.response_fields)
216 def inject_constant_info(constants):
218 if c.type.kind == c.type.KIND_FLOAT:
219 float(c.string_value)
220 c.cpp_value = c.string_value
223 c.cpp_value = c.string_value
224 if c.type.kind == c.type.KIND_UNSIGNED_INT:
227 if t.kind == t.KIND_MESSAGE:
228 inject_constant_info(t.constants)
230 inject_constant_info(t.request_constants)
231 inject_constant_info(t.response_constants)
235 t.KIND_MESSAGE:
'::uavcan::DataTypeKindMessage',
236 t.KIND_SERVICE:
'::uavcan::DataTypeKindService',
240 text = template_expander(t=t)
241 text =
'\n'.join(x.rstrip()
for x
in text.splitlines())
242 text = text.replace(
'\n\n\n\n\n',
'\n\n').replace(
'\n\n\n\n',
'\n\n').replace(
'\n\n\n',
'\n\n')
243 text = text.replace(
'{\n\n ',
'{\n ')
248 Templating is based on pyratemp (http://www.simple-is-better.org/template/pyratemp.html).
249 The pyratemp's syntax is rather verbose and not so human friendly, so we define some
250 custom extensions to make it easier to read and write.
251 The resulting syntax somewhat resembles Mako (which was used earlier instead of pyratemp):
254 Line joining through backslash (replaced with a single space):
255 ${foo(bar(very_long_arument=42, \
258 % for a in range(10):
263 The extended syntax is converted into pyratemp's through regexp substitution.
265 with open(filename)
as f:
266 template_text = f.read()
269 template_text = re.sub(
r'\\\r{0,1}\n\ *',
r' ', template_text)
272 template_text = re.sub(
r'([^\$]{0,1})\$\{([^\}]+)\}',
r'\1$!\2!$', template_text)
275 template_text = re.sub(
r'(?m)^(\ *)\%\ *(.+?):{0,1}$',
r'\1<!--(\2)-->', template_text)
278 template_text = re.sub(
r'<\!--\(end[a-z]+\)-->',
r'<!--(end)-->', template_text)
285 template_text = re.sub(
r'\ *\#\!.*',
'', template_text)
286 template_text = re.sub(
r'(<\!--\(macro\ [a-zA-Z0-9_]+\)-->.*?)',
r'\1\n', template_text)
296 args[
'indent'] =
lambda text, idnt =
' ': idnt + text.replace(
'\n',
'\n' + idnt)
298 def enum_last_value(iterable, start=0):
303 except StopIteration:
306 yield count,
False, last
309 yield count,
True, last
310 args[
'enum_last_value'] = enum_last_value
311 return template(**args)