10 from __future__
import division, absolute_import, print_function, unicode_literals
13 from logging
import getLogger
14 from io
import StringIO
15 from .signature
import Signature, compute_signature
16 from .common
import DsdlException, pretty_filename, bytes_from_crc64
17 from .type_limits
import get_unsigned_integer_range, get_signed_integer_range, get_float_range
31 MAX_FULL_TYPE_NAME_LEN = 80
33 SERVICE_DATA_TYPE_ID_MAX = 255
34 MESSAGE_DATA_TYPE_ID_MAX = 65535
37 logger = getLogger(__name__)
42 Common type description. The specialized type description classes inherit from this one.
44 full_name Full type name string, e.g. "pyuavcan_v0.protocol.NodeStatus"
45 category Any CATEGORY_*
47 CATEGORY_PRIMITIVE = 0
63 raise NotImplementedError(
'Pure virtual method')
66 raise NotImplementedError(
'Pure virtual method')
69 raise NotImplementedError(
'Pure virtual method')
76 Primitive type description, e.g. bool or float16.
79 bitlen Bit length, 1 to 64
80 cast_mode Any CAST_MODE_*
81 value_range Tuple containing min and max values: (min, max)
88 CAST_MODE_SATURATED = 0
89 CAST_MODE_TRUNCATED = 1
97 PrimitiveType.KIND_BOOLEAN: get_unsigned_integer_range,
98 PrimitiveType.KIND_UNSIGNED_INT: get_unsigned_integer_range,
99 PrimitiveType.KIND_SIGNED_INT: get_signed_integer_range,
100 PrimitiveType.KIND_FLOAT: get_float_range
104 """Please refer to the specification for details about normalized definitions."""
105 cast_mode =
'saturated' if self.
cast_mode == PrimitiveType.CAST_MODE_SATURATED
else 'truncated'
107 PrimitiveType.KIND_BOOLEAN:
'bool',
108 PrimitiveType.KIND_UNSIGNED_INT:
'uint' +
str(self.
bitlen),
109 PrimitiveType.KIND_SIGNED_INT:
'int' +
str(self.
bitlen),
110 PrimitiveType.KIND_FLOAT:
'float' +
str(self.
bitlen)
112 return cast_mode +
' ' + primary_type
117 value: Throws DsdlException if this value cannot be represented by this type.
120 if not low <= value <= high:
124 """Returns type bit length."""
128 """Returns type bit length."""
134 Array type description, e.g. float32[8], uint12[<34].
136 value_type Description of the array value type; the type of this field inherits Type, e.g. PrimitiveType
138 max_size Maximum number of elements in the array
150 """Please refer to the specification for details about normalized definitions."""
152 return (
'%s[<=%d]' if self.
mode == ArrayType.MODE_DYNAMIC
else '%s[%d]') % (typedef, self.
max_size)
155 """Returns total maximum bit length of the array, including length field if applicable."""
174 self.
value_type.category == Type.CATEGORY_PRIMITIVE
and \
181 Compound type description, e.g. pyuavcan_v0.protocol.NodeStatus.
183 source_file Path to the DSDL definition file for this type
184 default_dtid Default Data Type ID, if specified, None otherwise
185 version The version number of the dsdl definition as a tuple (e.g. (1,7))
187 source_text Raw DSDL definition text (as is, with comments and the original formatting)
189 Fields if kind == KIND_SERVICE:
190 request_fields Request struct field list, the type of each element is Field
191 response_fields Response struct field list
192 request_constants Request struct constant list, the type of each element is Constant
193 response_constants Response struct constant list
194 request_union Boolean indicating whether the request struct is a union
195 response_union Boolean indicating whether the response struct is a union
197 Fields if kind == KIND_MESSAGE:
198 fields Field list, the type of each element is Field
199 constants Constant list, the type of each element is Constant
200 union Boolean indicating whether the message struct is a union
202 Extra methods if kind == KIND_SERVICE:
203 get_max_bitlen_request() Returns maximum total bit length of the serialized request struct
204 get_max_bitlen_response() Same for the response struct
205 get_min_bitlen_request() Returns minimum total bit length of the serialized request struct
206 get_min_bitlen_response() Same for the response struct
208 Extra methods if kind == KIND_MESSAGE:
209 get_max_bitlen() Returns maximum total bit length of the serialized struct
210 get_min_bitlen() Returns minimum total bit length of the serialized struct
215 def __init__(self, full_name, kind, source_file, default_dtid, version, source_text):
216 Type.__init__(self, full_name, Type.CATEGORY_COMPOUND)
224 def compute_max_bitlen(flds, union):
227 lens = [x.type.get_max_bitlen()
for x
in flds]
229 return max(lens) +
max(len(flds) - 1, 1).bit_length()
233 def compute_min_bitlen(flds, union):
236 lens = [x.type.get_min_bitlen()
for x
in flds]
238 return min(lens) +
max(len(flds) - 1, 1).bit_length()
242 if kind == CompoundType.KIND_SERVICE:
253 elif kind == CompoundType.KIND_MESSAGE:
260 error(
'Compound type of unknown kind [%s]', kind)
267 return self._instantiate(*args, **kwargs)
271 Returns normalized DSDL definition text.
272 Please refer to the specification for details about normalized DSDL definitions.
278 return txt.write(
'\n'.join(x.get_normalized_definition()
for x
in attrs) +
'\n')
280 if self.
kind == CompoundType.KIND_SERVICE:
282 txt.write(
'\n@union\n')
286 txt.write(
'\n@union\n')
288 elif self.
kind == CompoundType.KIND_MESSAGE:
290 txt.write(
'\n@union\n')
293 error(
'Compound type of unknown kind [%s]', self.
kind)
294 return txt.getvalue().strip().replace(
'\n\n\n',
'\n').replace(
'\n\n',
'\n')
298 Computes DSDL signature of this type.
299 Please refer to the specification for details about signatures.
304 """Returns full type name string, e.g. 'pyuavcan_v0.protocol.NodeStatus'"""
309 Computes data type signature of this type. The data type signature is
310 guaranteed to match only if all nested data structures are compatible.
311 Please refer to the specification for details about signatures.
317 field_sig = field.type.get_data_type_signature()
318 if field_sig
is not None:
319 sig_value = sig.get_value()
328 Void type description, e.g. void2.
330 bitlen Bit length, 1 to 64
338 """Please refer to the specification for details about normalized definitions."""
342 """Returns type bit length."""
346 """Returns type bit length."""
352 Base class of an attribute description.
354 type Attribute type description, the type of this field inherits the class Type, e.g. PrimitiveType
355 name Attribute name string
367 raise NotImplementedError(
'Pure virtual method')
375 Does not add new fields to Attribute.
376 If type is void, the name will be None.
380 if self.
type.category == self.
type.CATEGORY_VOID:
388 Constant description.
390 init_expression Constant initialization expression string, e.g. "2+2" or "'\x66'"
391 value Computed result of the initialization expression in the final type (e.g. int, float)
392 string_value Computed result of the initialization expression as string
396 def __init__(self, type, name, init_expression, value):
397 Attribute.__init__(self, type, name)
401 if isinstance(value, long):
410 DSDL parser logic. Do not use this class directly; use the helper function instead.
417 search_dirs = sorted(map(os.path.abspath, self.
search_dirs))
418 filename = os.path.abspath(filename)
419 for dirname
in search_dirs:
420 root_ns = dirname.split(os.path.sep)[-1]
421 if filename.startswith(dirname):
422 dir_len = len(dirname)
423 basename_len = len(os.path.basename(filename))
424 ns = filename[dir_len:-basename_len]
425 ns = (root_ns +
'.' + ns.replace(os.path.sep,
'.').strip(
'.')).strip(
'.')
428 error(
'File [%s] was not found in search directories', filename)
431 basename = os.path.basename(filename)
432 items = basename.split(
'.')
434 if (len(items) != 2
and len(items) != 3
and len(items) != 4
and len(items) != 5)
or items[-1] !=
'uavcan':
435 error(
'Invalid file name [%s]; expected pattern: [<default-dtid>.]<short-type-name>.[<major-version>.<minor-version>.]uavcan', basename)
437 if len(items) == 2
or len(items) == 4:
438 default_dtid, name =
None, items[0]
440 default_dtid, name = items[0], items[1]
442 default_dtid =
int(default_dtid)
444 error(
'Invalid default data type ID [%s]', default_dtid)
446 if len(items) == 2
or len(items) == 3:
449 major_version, minor_version = items[-3], items[-2]
451 version = (
int(major_version),
int(minor_version))
453 error(
'Invalid version number [%s]', major_version, minor_version)
457 return full_name, version, default_dtid
460 def locate_namespace_directories(ns):
462 namespace_components = ns.split(
'.')
463 root_namespace, sub_namespace_components = namespace_components[0], namespace_components[1:]
465 if d.split(os.path.sep)[-1] == root_namespace:
466 namespace_dirs.append(os.path.join(d, *sub_namespace_components))
467 if len(namespace_dirs) == 0:
468 error(
'Unknown namespace [%s]', ns)
469 return namespace_dirs
471 if '.' not in typename:
473 full_typename = current_namespace +
'.' + typename
475 full_typename = typename
476 namespace =
'.'.join(full_typename.split(
'.')[:-1])
477 directories = locate_namespace_directories(namespace)
479 for directory
in directories:
480 logger.debug(
'Searching for [%s] in [%s]', full_typename, directory)
481 if not os.path.isdir(directory):
483 for fn
in os.listdir(directory):
484 fn = os.path.join(directory, fn)
485 if os.path.isfile(fn):
488 if full_typename == fn_full_typename:
490 except Exception
as ex:
491 logger.debug(
'Unknown file [%s], skipping... [%s]',
pretty_filename(fn), ex)
492 error(
'Type definition not found [%s]', typename)
497 enforce(1 <= bitlen <= 64,
'Invalid void bit length [%d]', bitlen)
501 logger.debug(
'Parsing the array value type [%s]...', value_typedef)
502 value_type = self.
_parse_type(filename, value_typedef, cast_mode)
503 enforce(value_type.category != value_type.CATEGORY_ARRAY,
504 'Multidimensional arrays are not allowed (protip: use nested types)')
506 if size_spec.startswith(
'<='):
507 max_size =
int(size_spec[2:], 0)
508 mode = ArrayType.MODE_DYNAMIC
509 elif size_spec.startswith(
'<'):
510 max_size =
int(size_spec[1:], 0) - 1
511 mode = ArrayType.MODE_DYNAMIC
513 max_size =
int(size_spec, 0)
514 mode = ArrayType.MODE_STATIC
516 error(
'Invalid array size specifier [%s] (valid patterns: [<=X], [<X], [X])', size_spec)
518 enforce(max_size > 0,
'Array size must be positive, not %d', max_size)
519 return ArrayType(value_type, mode, max_size)
524 if cast_mode
is None or cast_mode ==
'saturated':
525 cast_mode = PrimitiveType.CAST_MODE_SATURATED
526 elif cast_mode ==
'truncated':
527 cast_mode = PrimitiveType.CAST_MODE_TRUNCATED
529 error(
'Invalid cast mode [%s]', cast_mode)
531 if base_name ==
'bool':
532 return PrimitiveType(PrimitiveType.KIND_BOOLEAN, 1, cast_mode)
535 'uint': PrimitiveType.KIND_UNSIGNED_INT,
536 'int': PrimitiveType.KIND_SIGNED_INT,
537 'float': PrimitiveType.KIND_FLOAT,
540 error(
'Unknown primitive type (note: compound types should be in CamelCase)')
543 if kind == PrimitiveType.KIND_FLOAT:
544 enforce(bitlen
in (16, 32, 64),
'Invalid bit length for float type [%d]', bitlen)
546 enforce(2 <= bitlen <= 64,
'Invalid bit length [%d] (note: use bool instead of uint1)', bitlen)
551 logger.debug(
'Nested type [%s] is defined in [%s], parsing...', typedef,
pretty_filename(definition_filename))
552 t = self.
parse(definition_filename)
553 if t.kind == t.KIND_SERVICE:
554 error(
'A service type can not be nested into another compound type')
558 typedef = typedef.strip()
559 void_match = re.match(
r'void(\d{1,2})$', typedef)
560 array_match = re.match(
r'(.+?)\[([^\]]*)\]$', typedef)
561 primitive_match = re.match(
r'([a-z]+)(\d{1,2})$|(bool)$', typedef)
564 size_spec = void_match.group(1).strip()
567 assert not primitive_match
568 value_typedef = array_match.group(1).strip()
569 size_spec = array_match.group(2).strip()
571 elif primitive_match:
572 if primitive_match.group(0) ==
'bool':
575 base_name = primitive_match.group(1)
576 bitlen =
int(primitive_match.group(2))
579 enforce(cast_mode
is None,
'Cast mode specifier is not applicable for compound types [%s]', cast_mode)
584 enforce(attrtype.category == attrtype.CATEGORY_PRIMITIVE,
'Invalid type for constant [%d]', attrtype.category)
585 init_expression =
''.join(init_expression.split())
588 if isinstance(value, str)
and len(value) == 1:
590 elif isinstance(value, (float, int, bool, long)):
592 attrtype.KIND_UNSIGNED_INT: long,
593 attrtype.KIND_SIGNED_INT: long,
594 attrtype.KIND_BOOLEAN: int,
595 attrtype.KIND_FLOAT: float
596 }[attrtype.kind](value)
598 error(
'Invalid type of constant initialization expression [%s]', type(value).__name__)
600 logger.debug(
'Constant initialization expression evaluated as: [%s] --> %s', init_expression, repr(value))
601 attrtype.validate_value_range(value)
602 return Constant(attrtype, name, init_expression, value)
606 if tokens[0] ==
'saturated' or tokens[0] ==
'truncated':
607 cast_mode, tokens = tokens[0], tokens[1:]
609 if len(tokens) < 2
and not tokens[0].startswith(
'void'):
610 error(
'Invalid attribute definition')
613 typename, attrname, tokens = tokens[0],
None, []
615 typename, attrname, tokens = tokens[0], tokens[1], tokens[2:]
618 attrtype = self.
_parse_type(filename, typename, cast_mode)
621 if len(tokens) < 2
or tokens[0] !=
'=':
622 error(
'Constant assignment expected')
623 expression =
' '.join(tokens[1:])
626 return Field(attrtype, attrname)
630 for idx, line
in enumerate(text.splitlines()):
631 line = re.sub(
'#.*',
'', line).strip()
633 tokens = [tk
for tk
in line.split()
if tk]
634 yield idx + 1, tokens
639 numbered_lines = list(self.
_tokenize(source_text))
640 all_attributes_names = set()
641 fields, constants, resp_fields, resp_constants = [], [], [], []
642 union, resp_union =
False,
False
643 response_part =
False
644 for num, tokens
in numbered_lines:
646 if tokens == [
'---']:
647 enforce(
not response_part,
'Duplicate response mark')
649 all_attributes_names = set()
651 if tokens == [
'@union']:
653 enforce(
not resp_union,
'Response data structure has already been declared as union')
656 enforce(
not union,
'Data structure has already been declared as union')
660 if attr.name
and attr.name
in all_attributes_names:
661 error(
'Duplicated attribute name [%s]', attr.name)
662 all_attributes_names.add(attr.name)
663 if isinstance(attr, Constant):
664 (resp_constants
if response_part
else constants).append(attr)
665 elif isinstance(attr, Field):
666 (resp_fields
if response_part
else fields).append(attr)
668 error(
'Unknown attribute type - internal error')
669 except DsdlException
as ex:
673 except Exception
as ex:
674 logger.error(
'Internal error', exc_info=
True)
678 t =
CompoundType(full_typename, CompoundType.KIND_SERVICE, filename, default_dtid, version, source_text)
679 t.request_fields = fields
680 t.request_constants = constants
681 t.response_fields = resp_fields
682 t.response_constants = resp_constants
683 t.request_union = union
684 t.response_union = resp_union
685 max_bitlen = t.get_max_bitlen_request(), t.get_max_bitlen_response()
686 max_bytelen = tuple(map(bitlen_to_bytelen, max_bitlen))
688 t =
CompoundType(full_typename, CompoundType.KIND_MESSAGE, filename, default_dtid, version, source_text)
690 t.constants = constants
692 max_bitlen = t.get_max_bitlen()
698 logger.debug(
'Type [%s], default DTID: %s, signature: %08x, maxbits: %s, maxbytes: %s, DSSD:',
699 full_typename, default_dtid, t.get_dsdl_signature(), max_bitlen, max_bytelen)
700 for ln
in t.get_dsdl_signature_source_definition().splitlines():
701 logger.debug(
' %s', ln)
703 except DsdlException
as ex:
710 filename = os.path.abspath(filename)
711 with open(filename)
as f:
712 source_text = f.read()
715 except IOError
as ex:
717 except Exception
as ex:
718 logger.error(
'Internal error', exc_info=
True)
732 return int((x + 7) / 8)
740 '__builtins__':
None,
744 return eval(expression, env)
745 except Exception
as ex:
746 error(
'Cannot evaluate expression: %s',
str(ex))
750 dirnames = set(dirnames)
751 dirnames = list(map(os.path.abspath, dirnames))
756 enforce(
not d1.startswith(d2),
'Nested search directories are not allowed [%s] [%s]', d1, d2)
761 for component
in name.split(
'.'):
762 enforce(re.match(
r'[a-z][a-z0-9_]*$', component),
'Invalid namespace name [%s]', name)
763 enforce(len(name) <= MAX_FULL_TYPE_NAME_LEN,
'Namespace name is too long [%s]', name)
767 enforce(
'.' in name,
'Full type name must explicitly specify its namespace [%s]', name)
768 short_name = name.split(
'.')[-1]
769 namespace =
'.'.join(name.split(
'.')[:-1])
771 enforce(re.match(
r'[A-Z][A-Za-z0-9_]*$', short_name),
'Invalid type name [%s]', name)
772 enforce(len(name) <= MAX_FULL_TYPE_NAME_LEN,
'Type name is too long [%s]', name)
776 enforce(re.match(
r'[a-zA-Z][a-zA-Z0-9_]*$', name),
'Invalid attribute name [%s]', name)
780 if t.default_dtid
is None:
782 if t.kind == t.KIND_MESSAGE:
783 enforce(0 <= t.default_dtid <= MESSAGE_DATA_TYPE_ID_MAX,
784 'Invalid data type ID for message [%s]', t.default_dtid)
785 elif t.kind == t.KIND_SERVICE:
786 enforce(0 <= t.default_dtid <= SERVICE_DATA_TYPE_ID_MAX,
787 'Invalid data type ID for service [%s]', t.default_dtid)
789 error(
'Invalid kind: %s', t.kind)
793 def check_fields(fields):
794 enforce(len(fields) > 1,
'Union contains less than 2 fields')
795 enforce(
not any(_.type.category == _.type.CATEGORY_VOID
for _
in fields),
'Union must not contain void fields')
796 if t.kind == t.KIND_MESSAGE:
798 check_fields(t.fields)
799 elif t.kind == t.KIND_SERVICE:
801 check_fields(t.request_fields)
803 check_fields(t.response_fields)
805 error(
'Invalid kind: %s', t.kind)
810 Use only this function to parse DSDL definitions.
811 This function takes a list of root namespace directories (containing DSDL definition files to parse) and an
812 optional list of search directories (containing DSDL definition files that can be referenced from the types
813 that are going to be parsed).
815 Returns the list of parsed type definitions, where type of each element is CompoundType.
818 source_dirs: List of root namespace directories to parse.
819 search_dirs: List of root namespace directories with referenced types (optional). This list is
820 automatically extended with source_dirs.
822 >>> import pyuavcan_v0
823 >>> a = pyuavcan_v0.dsdl.parse_namespaces(['../dsdl/uavcan'])
827 pyuavcan_v0.Timestamp
829 [truncated uint48 husec]
831 [saturated uint48 UNKNOWN = 0, saturated uint48 USEC_PER_LSB = 100]
837 from functools
import partial
839 def on_walk_error(directory, ex):
841 for source_dir
in walk_dirs:
842 walker = os.walk(source_dir, onerror=partial(on_walk_error, source_dir), followlinks=
True)
843 for root, _dirnames, filenames
in walker:
844 for filename
in fnmatch.filter(filenames,
'*.uavcan'):
845 filename = os.path.join(root, filename)
848 all_default_dtid = {}
851 def ensure_unique_dtid(t, filename):
852 if t.default_dtid
is None:
854 key = t.kind, t.default_dtid
855 if key
in all_default_dtid:
856 value = all_default_dtid[key]
859 if t.get_dsdl_signature() != value[1].get_dsdl_signature():
860 error(
'Redefinition of data type ID: [%s] [%s]', first, second)
862 logger.debug(
'ignoring duplicate definition of %s', t.full_name)
865 all_default_dtid[key] = (filename, t)
867 parser =
Parser(source_dirs + (search_dirs
or []))
870 for filename
in walk(search_dirs):
871 t = parser.parse(filename)
872 ensure_unique_dtid(t, filename)
874 for filename
in walk(source_dirs):
875 t = parser.parse(filename)
876 ensure_unique_dtid(t, filename)
877 output_types.append(t)