From c035dd443616722b614e3deb6cacb4e21bd63a95 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Mon, 3 Mar 2014 13:39:27 +0400 Subject: [PATCH] DSDL compiler for libuavcan - dsdlc --- .gitignore | 3 + .../dsdl_compiler/data_type_template.hpp | 183 +++++++++++++++++- libuavcan/dsdl_compiler/dsdlc.py | 174 ++++++++++++++++- .../root_a/425.TypeInRootA.uavcan | 5 + pyuavcan/pyuavcan/dsdl/parser.py | 11 +- 5 files changed, 368 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 15eddb26cd..7578e9d0b7 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ lib*.so.* .project .cproject .pydevproject + +# libuavcan DSDL compiler default output directory +*dsdlc_output diff --git a/libuavcan/dsdl_compiler/data_type_template.hpp b/libuavcan/dsdl_compiler/data_type_template.hpp index 8d1c8b69c3..102e257d1c 100644 --- a/libuavcan/dsdl_compiler/data_type_template.hpp +++ b/libuavcan/dsdl_compiler/data_type_template.hpp @@ -1 +1,182 @@ - +/* + * UAVCAN data structure definition for libuavcan. + * + * Autogenerated, do not edit. + * + * Source file: ${t.filename} + */ + +#pragma once + +#include +#include +#include +#include + +% for inc in t.cpp_includes: +#include <${inc}> +% endfor + +#if !defined(UAVCAN_CONSTEXPR) +# if __cplusplus < 201100L +# define UAVCAN_CONSTEXPR const +# else +# define UAVCAN_CONSTEXPR constexpr +# endif +#endif + +<%! +indent = lambda text, idnt=' ': idnt + text.replace('\n', '\n' + idnt) +%> + +/****************************************************************************** +% for line in t.source_text.strip().splitlines(): +${line} +% endfor +******************************************************************************/ + +% for nsc in t.cpp_namespace_components: +namespace ${nsc} +{ +% endfor + +struct ${t.short_name} +{ +<%def name="generate_primary_body(type_name, max_bitlen, fields, constants)" buffered="True"> + typedef const ${type_name}& ParameterType; + typedef ${type_name}& ReferenceType; + +<%def name="expand_attr_types(group_name, attrs)"> + struct ${group_name} + { +% for a in attrs: + typedef ${a.cpp_type} ${a.name}; +% endfor + }; + + ${expand_attr_types('ConstantTypes', constants)} + ${expand_attr_types('FieldTypes', fields)} + +<%def name="expand_enum_per_field(enum_name)"> + enum + { + ${enum_name} +% for idx,a in enumerate(fields): + ${'=' if idx == 0 else '+'} FieldTypes::${a.name}::${enum_name} +% endfor + }; + + ${expand_enum_per_field('MinBitLen')} + ${expand_enum_per_field('MaxBitLen')} + + // Constants +% for a in constants: + % if a.cpp_use_enum: + enum { ${a.name} = ${a.cpp_value} }; // ${a.init_expression} + % else: + static UAVCAN_CONSTEXPR typename ::uavcan::StorageType::Type + ${a.name} = ${a.cpp_value}; // ${a.init_expression} + %endif +% endfor + + // Fields +% for a in fields: + typename ::uavcan::StorageType::Type ${a.name}; +% endfor + + ${type_name}() +% for idx,a in enumerate(fields): + ${':' if idx == 0 else ','} ${a.name}() +% endfor + { +#if UAVCAN_DEBUG + /* + * Cross-checking MaxBitLen provided by the DSDL compiler. + * This check shall never be performed in user code because MaxBitLen value + * actually depends on the nested types, thus it is not invariant. + */ + ::uavcan::StaticAssert<${max_bitlen} == MaxBitLen>::check(); +#endif + } + +<%def name="generate_codec_calls_per_field(call_name, self_parameter_type)"> + static int ${call_name}(${self_parameter_type} self, ::uavcan::ScalarCodec& codec, + ::uavcan::TailArrayOptimizationMode tao_mode = ::uavcan::TailArrayOptEnabled) + { + int res = 1; +% for idx,a in enumerate(fields): + res = FieldTypes::${a.name}::${call_name}(self.${a.name}, codec, \ +${'::uavcan::TailArrayOptDisabled' if (idx + 1) < len(fields) else 'tao_mode'}); + if (res <= 0) + return res; +% endfor + return res; + } + + ${generate_codec_calls_per_field('encode', 'ParameterType')} + ${generate_codec_calls_per_field('decode', 'ReferenceType')} + +% if t.kind == t.KIND_SERVICE: + struct Request + { + ${generate_primary_body(t.short_name, t.get_max_bitlen_request(), t.request_fields, t.request_constants) | indent} + }; + + struct Response + { + ${generate_primary_body(t.short_name, t.get_max_bitlen_response(), t.response_fields, t.response_constants) | indent} + }; +% else: + ${generate_primary_body(t.short_name, t.get_max_bitlen(), t.fields, t.constants)} +% endif + + /* + * Static type info + */ + enum { DataTypeKind = ${t.cpp_kind} }; +% if t.has_default_dtid: + enum { DefaultDataTypeID = ${t.default_dtid} }; +% else: + // This type has no default data type ID +% endif + + static const char* getDataTypeFullName() + { + return "${t.full_name}"; + } + + static void extendDataTypeSignature(::uavcan::DataTypeSignature& signature) + { + signature.extend(getDataTypeSignature()); + } + + static ::uavcan::DataTypeSignature getDataTypeSignature() + { + ::uavcan::DataTypeSignature signature(${hex(t.dsdl_signature)}); +<%def name="extend_signature_per_field(scope_prefix, fields)"> + % for a in fields: + ${scope_prefix}FieldTypes::${a.name}::extendDataTypeSignature(signature); + % endfor + +% if t.kind == t.KIND_SERVICE: + ${extend_signature_per_field('Request::', t.request_fields)} + ${extend_signature_per_field('Response::', t.response_fields)} +% else: + ${extend_signature_per_field('', t.fields)} +% endif + return signature; + } +}; + +// TODO Stream operator + +% if t.has_default_dtid: +namespace +{ +::uavcan::DefaultDataTypeRegistrator<${t.short_name}> _uavcan_gdtr_registrator_${t.short_name}; +} +% endif + +% for nsc in t.cpp_namespace_components: +} +% endfor diff --git a/libuavcan/dsdl_compiler/dsdlc.py b/libuavcan/dsdl_compiler/dsdlc.py index 1675d85fb3..99cdba1b9d 100755 --- a/libuavcan/dsdl_compiler/dsdlc.py +++ b/libuavcan/dsdl_compiler/dsdlc.py @@ -5,7 +5,8 @@ # Copyright (C) 2014 Pavel Kirienko # -import sys, os +import sys, os, argparse, logging +from mako.template import Template RUNNING_FROM_SRC_DIR = os.path.abspath(__file__).endswith(os.path.join('libuavcan', 'dsdl_compiler', 'dsdlc.py')) if RUNNING_FROM_SRC_DIR: @@ -15,4 +16,173 @@ if RUNNING_FROM_SRC_DIR: from pyuavcan import dsdl -print('Hello') +MAX_BITLEN_FOR_ENUM = 31 +CPP_HEADER_EXTENSION = 'hpp' +TEMPLATE_FILENAME = os.path.join(os.path.dirname(__file__), 'data_type_template.hpp') + +# ----------------- + +class DsdlCompilerException(Exception): + pass + +def pretty_filename(filename): + a = os.path.abspath(filename) + r = os.path.relpath(filename) + return a if len(a) < len(r) else r + +def type_output_filename(t): + assert t.category == t.CATEGORY_COMPOUND + return t.full_name.replace('.', os.path.sep) + '.' + CPP_HEADER_EXTENSION + +def die(text): + print(text, file=sys.stderr) + exit(1) + +def configure_logging(verbosity): + fmt = '%(message)s' + level = { 0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG }.get(verbosity or 0, logging.DEBUG) + logging.basicConfig(stream=sys.stderr, level=level, format=fmt) + +def run_parser(source_dir, search_dirs): + try: + types = dsdl.parse_namespace(source_dir, search_dirs) + except dsdl.DsdlException as ex: + errtext = str(ex) # TODO: gcc-style formatting + die(errtext) + logging.info('%d types from [%s] parsed successfully', len(types), source_dir) + return types + +def run_generator(types, dest_dir): + try: + dest_dir = os.path.abspath(dest_dir) # Removing '..' + os.makedirs(dest_dir, exist_ok=True) + for t in types: + logging.info('Generating type %s', t.full_name) + file_path = os.path.join(dest_dir, type_output_filename(t)) + dir_path = os.path.dirname(file_path) + os.makedirs(dir_path, exist_ok=True) + text = generate_one_type(t) + with open(file_path, 'w') as f: + f.write(text) + except Exception as ex: + logging.info('Generator error', exc_info=True) + die(str(ex)) + +def type_to_cpp_type(t): + if t.category == t.CATEGORY_PRIMITIVE: + cast_mode = { + t.CAST_MODE_SATURATED: '::uavcan::CastModeSaturate', + t.CAST_MODE_TRUNCATED: '::uavcan::CastModeTruncate', + }[t.cast_mode] + if t.kind == t.KIND_FLOAT: + return '::uavcan::FloatSpec<%d, %s>' % (t.bitlen, cast_mode) + else: + signedness = { + t.KIND_BOOLEAN: '::uavcan::SignednessUnsigned', + t.KIND_UNSIGNED_INT: '::uavcan::SignednessUnsigned', + t.KIND_SIGNED_INT: '::uavcan::SignednessSigned', + }[t.kind] + return '::uavcan::IntegerSpec<%d, %s, %s>' % (t.bitlen, signedness, cast_mode) + elif t.category == t.CATEGORY_ARRAY: + value_type = type_to_cpp_type(t.value_type) + mode = { + t.MODE_STATIC: '::uavcan::ArrayModeStatic', + t.MODE_DYNAMIC: '::uavcan::ArrayModeDynamic', + }[t.mode] + return '::uavcan::Array<%s, %s, %d>' % (value_type, mode, t.max_size) + elif t.category == t.CATEGORY_COMPOUND: + return '::' + t.full_name.replace('.', '::') + else: + raise DsdlCompilerException('Unknown type category: %s' % t.category) + +def generate_one_type(t): + t.short_name = t.full_name.split('.')[-1] + + # Dependencies (no duplicates) + def fields_includes(fields): + return set(type_output_filename(x.type) for x in fields if x.type.category == x.type.CATEGORY_COMPOUND) + + if t.kind == t.KIND_MESSAGE: + t.cpp_includes = fields_includes(t.fields) + else: + t.cpp_includes = fields_includes(t.request_fields + t.response_fields) + + t.cpp_namespace_components = t.full_name.split('.')[:-1] + t.has_default_dtid = t.default_dtid is not None + + # Attribute types + def inject_cpp_types(attributes): + for a in attributes: + a.cpp_type = type_to_cpp_type(a.type) + + if t.kind == t.KIND_MESSAGE: + inject_cpp_types(t.fields) + inject_cpp_types(t.constants) + else: + inject_cpp_types(t.request_fields) + inject_cpp_types(t.request_constants) + inject_cpp_types(t.response_fields) + inject_cpp_types(t.response_constants) + + # Constant properties + def inject_constant_info(constants): + for c in constants: + if c.type.kind == c.type.KIND_FLOAT: + c.cpp_use_enum = False + numeric_limits = '::std::numeric_limits::Type>' % c.name + numeric_limits_inf = numeric_limits + '::infinity()' + special_values = { + 'inf': numeric_limits_inf, + '+inf': numeric_limits_inf, + '-inf': '-' + numeric_limits_inf, + 'nan': numeric_limits + '::quiet_NaN()', + } + if c.string_value in special_values: + c.cpp_value = special_values[c.string_value] + else: + float(c.string_value) # making sure that this is a valid float literal + c.cpp_value = c.string_value + else: + c.cpp_use_enum = c.value >= 0 and c.type.bitlen <= MAX_BITLEN_FOR_ENUM + c.cpp_value = c.string_value + if t.kind == t.KIND_MESSAGE: + inject_constant_info(t.constants) + else: + inject_constant_info(t.request_constants) + inject_constant_info(t.response_constants) + + # Data type kind + t.cpp_kind = { + t.KIND_MESSAGE: '::uavcan::DataTypeKindMessage', + t.KIND_SERVICE: '::uavcan::DataTypeKindService', + }[t.kind] + + # Generation + template = Template(filename=TEMPLATE_FILENAME) + text = template.render(t=t) + text = '\n'.join(x.rstrip() for x in text.splitlines()) + text = text.replace('\n\n\n\n', '\n\n').replace('\n\n\n', '\n\n') + text = text.replace('{\n\n ', '{\n ') + return text + +# ----------------- + +DESCRIPTION = '''UAVCAN DSDL compiler. Takes an input directory that contains an hierarchy of DSDL +definitions and converts it into compatible hierarchy of C++ types for libuavcan.''' + +DEFAULT_OUTDIR = './dsdlc_output' + +argparser = argparse.ArgumentParser(description=DESCRIPTION) +argparser.add_argument('source_dir', help='source directory with DSDL definitions') +argparser.add_argument('--verbose', '-v', action='count', help='verbosity level (-v, -vv)') +argparser.add_argument('--outdir', '-O', default=DEFAULT_OUTDIR, help='output directory, default %s' % DEFAULT_OUTDIR) +argparser.add_argument('--incdir', '-I', default=[], action='append', help='nested type namespaces') +args = argparser.parse_args() + +configure_logging(args.verbose) + +types = run_parser(args.source_dir, args.incdir) +if not types: + die('No type definitions were found') + +run_generator(types, args.outdir) diff --git a/pyuavcan/dsdl_test_data/root_a/425.TypeInRootA.uavcan b/pyuavcan/dsdl_test_data/root_a/425.TypeInRootA.uavcan index 616b723f56..6e2b11e780 100644 --- a/pyuavcan/dsdl_test_data/root_a/425.TypeInRootA.uavcan +++ b/pyuavcan/dsdl_test_data/root_a/425.TypeInRootA.uavcan @@ -2,5 +2,10 @@ # Test file # +int2 SMALL_CONST = -2 +int61 CONST = -123456789 +float16 FLOAT_CONST = 1.23 +float16 FLOAT_CONST2 = nan +float16 FLOAT_CONST3 = -inf root_b.TypeInRootB type_in_root_b root_a.ns2.TypeInNs2[<5] type_in_ns_2 diff --git a/pyuavcan/pyuavcan/dsdl/parser.py b/pyuavcan/pyuavcan/dsdl/parser.py index 40201c2d1b..cabc769ff8 100644 --- a/pyuavcan/pyuavcan/dsdl/parser.py +++ b/pyuavcan/pyuavcan/dsdl/parser.py @@ -92,13 +92,14 @@ class CompoundType(Type): KIND_SERVICE = 0 KIND_MESSAGE = 1 - def __init__(self, full_name, kind, dsdl_signature, dsdl_path, default_dtid, filename): + def __init__(self, full_name, kind, dsdl_signature, dsdl_path, default_dtid, filename, source_text): super().__init__(full_name, Type.CATEGORY_COMPOUND) self.dsdl_signature = dsdl_signature self.dsdl_path = dsdl_path self.default_dtid = default_dtid self.kind = kind self.filename = filename + self.source_text = source_text max_bitlen_sum = lambda fields: sum([x.type.get_max_bitlen() for x in fields]) if kind == CompoundType.KIND_SERVICE: self.request_fields = [] @@ -362,10 +363,10 @@ class Parser: try: filename = os.path.abspath(filename) with open(filename) as f: - text = f.read() + source_text = f.read() full_typename, default_dtid = self._full_typename_and_dtid_from_filename(filename) - numbered_lines = list(self._tokenize(text)) + numbered_lines = list(self._tokenize(source_text)) all_attributes_names = set() fields, constants, resp_fields, resp_constants = [], [], [], [] response_part = False @@ -396,7 +397,7 @@ class Parser: dsdl_signature = self._compute_dsdl_signature(full_typename, fields, constants, resp_fields, resp_constants) typedef = CompoundType(full_typename, CompoundType.KIND_SERVICE, dsdl_signature, filename, - default_dtid, filename) + default_dtid, filename, source_text) typedef.request_fields = fields typedef.request_constants = constants typedef.response_fields = resp_fields @@ -406,7 +407,7 @@ class Parser: else: dsdl_signature = self._compute_dsdl_signature(full_typename, fields, constants) typedef = CompoundType(full_typename, CompoundType.KIND_MESSAGE, dsdl_signature, filename, - default_dtid, filename) + default_dtid, filename, source_text) typedef.fields = fields typedef.constants = constants max_bitlen = typedef.get_max_bitlen()