DSDL compiler for libuavcan - dsdlc

This commit is contained in:
Pavel Kirienko
2014-03-03 13:39:27 +04:00
parent 5568e5751e
commit c035dd4436
5 changed files with 368 additions and 8 deletions
+3
View File
@@ -12,3 +12,6 @@ lib*.so.*
.project
.cproject
.pydevproject
# libuavcan DSDL compiler default output directory
*dsdlc_output
+182 -1
View File
@@ -1 +1,182 @@
/*
* UAVCAN data structure definition for libuavcan.
*
* Autogenerated, do not edit.
*
* Source file: ${t.filename}
*/
#pragma once
#include <limits>
#include <uavcan/data_type.hpp>
#include <uavcan/global_data_type_registry.hpp>
#include <uavcan/internal/util.hpp>
% 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
};
</%def>
${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
};
</%def>
${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<ConstantTypes::${a.name}>::Type
${a.name} = ${a.cpp_value}; // ${a.init_expression}
%endif
% endfor
// Fields
% for a in fields:
typename ::uavcan::StorageType<FieldTypes::${a.name}>::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;
}
</%def>
${generate_codec_calls_per_field('encode', 'ParameterType')}
${generate_codec_calls_per_field('decode', 'ReferenceType')}
</%def>
% 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
</%def>
% 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
+172 -2
View File
@@ -5,7 +5,8 @@
# Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
#
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<typename ::uavcan::StorageType<typename ConstantTypes::%s>::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)
@@ -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
+6 -5
View File
@@ -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()