parser.py
Go to the documentation of this file.
1 #
2 # Copyright (C) 2014-2015 UAVCAN Development Team <uavcan.org>
3 #
4 # This software is distributed under the terms of the MIT License.
5 #
6 # Author: Pavel Kirienko <pavel.kirienko@zubax.com>
7 # Ben Dyer <ben_dyer@mac.com>
8 #
9 
10 from __future__ import division, absolute_import, print_function, unicode_literals
11 import os
12 import re
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
18 
19 # Python 2.7 compatibility
20 try:
21  # noinspection PyUnresolvedReferences,PyShadowingBuiltins
22  str = unicode # @ReservedAssignment @UndefinedVariable
23 except NameError:
24  pass
25 try:
26  # noinspection PyUnresolvedReferences,PyUnboundLocalVariable
27  long(1) # @UndefinedVariable
28 except NameError:
29  long = int # @ReservedAssignment
30 
31 MAX_FULL_TYPE_NAME_LEN = 80
32 
33 SERVICE_DATA_TYPE_ID_MAX = 255
34 MESSAGE_DATA_TYPE_ID_MAX = 65535
35 
36 
37 logger = getLogger(__name__)
38 
39 
40 class Type:
41  """
42  Common type description. The specialized type description classes inherit from this one.
43  Fields:
44  full_name Full type name string, e.g. "pyuavcan_v0.protocol.NodeStatus"
45  category Any CATEGORY_*
46  """
47  CATEGORY_PRIMITIVE = 0
48  CATEGORY_ARRAY = 1
49  CATEGORY_COMPOUND = 2
50  CATEGORY_VOID = 3
51 
52  def __init__(self, full_name, category):
53  self.full_name = str(full_name)
54  self.category = category
55 
56  def __str__(self):
57  return self.get_normalized_definition()
58 
60  return None
61 
63  raise NotImplementedError('Pure virtual method')
64 
65  def get_max_bitlen(self):
66  raise NotImplementedError('Pure virtual method')
67 
68  def get_min_bitlen(self):
69  raise NotImplementedError('Pure virtual method')
70 
71  __repr__ = __str__
72 
73 
75  """
76  Primitive type description, e.g. bool or float16.
77  Fields:
78  kind Any KIND_*
79  bitlen Bit length, 1 to 64
80  cast_mode Any CAST_MODE_*
81  value_range Tuple containing min and max values: (min, max)
82  """
83  KIND_BOOLEAN = 0
84  KIND_UNSIGNED_INT = 1
85  KIND_SIGNED_INT = 2
86  KIND_FLOAT = 3
87 
88  CAST_MODE_SATURATED = 0
89  CAST_MODE_TRUNCATED = 1
90 
91  def __init__(self, kind, bitlen, cast_mode):
92  self.kind = kind
93  self.bitlen = bitlen
94  self.cast_mode = cast_mode
95  Type.__init__(self, self.get_normalized_definition(), Type.CATEGORY_PRIMITIVE)
96  self.value_range = {
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
101  }[self.kind](bitlen)
102 
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'
106  primary_type = {
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)
111  }[self.kind]
112  return cast_mode + ' ' + primary_type
113 
114  def validate_value_range(self, value):
115  """
116  Args:
117  value: Throws DsdlException if this value cannot be represented by this type.
118  """
119  low, high = self.value_range
120  if not low <= value <= high:
121  error('Value [%s] is out of range %s', value, self.value_range)
122 
123  def get_max_bitlen(self):
124  """Returns type bit length."""
125  return self.bitlen
126 
127  def get_min_bitlen(self):
128  """Returns type bit length."""
129  return self.bitlen
130 
131 
133  """
134  Array type description, e.g. float32[8], uint12[<34].
135  Fields:
136  value_type Description of the array value type; the type of this field inherits Type, e.g. PrimitiveType
137  mode Any MODE_*
138  max_size Maximum number of elements in the array
139  """
140  MODE_STATIC = 0
141  MODE_DYNAMIC = 1
142 
143  def __init__(self, value_type, mode, max_size):
144  self.value_type = value_type
145  self.mode = mode
146  self.max_size = max_size
147  Type.__init__(self, self.get_normalized_definition(), Type.CATEGORY_ARRAY)
148 
150  """Please refer to the specification for details about normalized definitions."""
151  typedef = self.value_type.get_normalized_definition()
152  return ('%s[<=%d]' if self.mode == ArrayType.MODE_DYNAMIC else '%s[%d]') % (typedef, self.max_size)
153 
154  def get_max_bitlen(self):
155  """Returns total maximum bit length of the array, including length field if applicable."""
156  payload_max_bitlen = self.max_size * self.value_type.get_max_bitlen()
157  return {
158  self.MODE_DYNAMIC: payload_max_bitlen + self.max_size.bit_length(),
159  self.MODE_STATIC: payload_max_bitlen
160  }[self.mode]
161 
162  def get_min_bitlen(self):
163  if self.mode == self.MODE_STATIC:
164  return self.value_type.get_min_bitlen() * self.max_size
165  else:
166  return 0 # Considering TAO
167 
169  return self.value_type.get_data_type_signature()
170 
171  @property
172  def is_string_like(self):
173  return self.mode == self.MODE_DYNAMIC and \
174  self.value_type.category == Type.CATEGORY_PRIMITIVE and \
175  self.value_type.bitlen == 8
176 
177 
178 # noinspection PyAbstractClass
180  """
181  Compound type description, e.g. pyuavcan_v0.protocol.NodeStatus.
182  Fields:
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))
186  kind Any KIND_*
187  source_text Raw DSDL definition text (as is, with comments and the original formatting)
188 
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
196 
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
201 
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
207 
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
211  """
212  KIND_SERVICE = 0
213  KIND_MESSAGE = 1
214 
215  def __init__(self, full_name, kind, source_file, default_dtid, version, source_text):
216  Type.__init__(self, full_name, Type.CATEGORY_COMPOUND)
217  self.source_file = source_file
218  self.default_dtid = default_dtid
219  self.version = version
220  self.kind = kind
221  self.source_text = source_text
223 
224  def compute_max_bitlen(flds, union):
225  if len(flds) == 0:
226  return 0
227  lens = [x.type.get_max_bitlen() for x in flds]
228  if union:
229  return max(lens) + max(len(flds) - 1, 1).bit_length()
230  else:
231  return sum(lens)
232 
233  def compute_min_bitlen(flds, union):
234  if len(flds) == 0:
235  return 0
236  lens = [x.type.get_min_bitlen() for x in flds]
237  if union:
238  return min(lens) + max(len(flds) - 1, 1).bit_length()
239  else:
240  return sum(lens)
241 
242  if kind == CompoundType.KIND_SERVICE:
243  self.request_fields = []
244  self.response_fields = []
247  self.get_max_bitlen_request = lambda: compute_max_bitlen(self.request_fields, self.request_union)
248  self.get_max_bitlen_response = lambda: compute_max_bitlen(self.response_fields, self.response_union)
249  self.get_min_bitlen_request = lambda: compute_min_bitlen(self.request_fields, self.request_union)
250  self.get_min_bitlen_response = lambda: compute_min_bitlen(self.response_fields, self.response_union)
251  self.request_union = False
252  self.response_union = False
253  elif kind == CompoundType.KIND_MESSAGE:
254  self.fields = []
255  self.constants = []
256  self.get_max_bitlen = lambda: compute_max_bitlen(self.fields, self.union)
257  self.get_min_bitlen = lambda: compute_min_bitlen(self.fields, self.union)
258  self.union = False
259  else:
260  error('Compound type of unknown kind [%s]', kind)
261 
262  def _instantiate(self, *args, **kwargs):
263  # This is a stub
264  pass
265 
266  def __call__(self, *args, **kwargs):
267  return self._instantiate(*args, **kwargs)
268 
270  """
271  Returns normalized DSDL definition text.
272  Please refer to the specification for details about normalized DSDL definitions.
273  """
274  txt = StringIO()
275  txt.write(self.full_name + '\n')
276 
277  def adjoin(attrs):
278  return txt.write('\n'.join(x.get_normalized_definition() for x in attrs) + '\n')
279 
280  if self.kind == CompoundType.KIND_SERVICE:
281  if self.request_union:
282  txt.write('\n@union\n')
283  adjoin(self.request_fields)
284  txt.write('\n---\n')
285  if self.response_union:
286  txt.write('\n@union\n')
287  adjoin(self.response_fields)
288  elif self.kind == CompoundType.KIND_MESSAGE:
289  if self.union:
290  txt.write('\n@union\n')
291  adjoin(self.fields)
292  else:
293  error('Compound type of unknown kind [%s]', self.kind)
294  return txt.getvalue().strip().replace('\n\n\n', '\n').replace('\n\n', '\n')
295 
297  """
298  Computes DSDL signature of this type.
299  Please refer to the specification for details about signatures.
300  """
302 
304  """Returns full type name string, e.g. 'pyuavcan_v0.protocol.NodeStatus'"""
305  return self.full_name
306 
308  """
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.
312  """
313  if self._data_type_signature is None:
314  sig = Signature(self.get_dsdl_signature())
315  fields = self.request_fields + self.response_fields if self.kind == CompoundType.KIND_SERVICE else self.fields
316  for field in fields:
317  field_sig = field.type.get_data_type_signature()
318  if field_sig is not None:
319  sig_value = sig.get_value()
320  sig.add(bytes_from_crc64(field_sig))
321  sig.add(bytes_from_crc64(sig_value))
322  self._data_type_signature = sig.get_value()
323  return self._data_type_signature
324 
325 
326 class VoidType(Type):
327  """
328  Void type description, e.g. void2.
329  Fields:
330  bitlen Bit length, 1 to 64
331  """
332 
333  def __init__(self, bitlen):
334  self.bitlen = bitlen
335  Type.__init__(self, self.get_normalized_definition(), Type.CATEGORY_VOID)
336 
338  """Please refer to the specification for details about normalized definitions."""
339  return 'void' + str(self.bitlen)
340 
341  def get_max_bitlen(self):
342  """Returns type bit length."""
343  return self.bitlen
344 
345  def get_min_bitlen(self):
346  """Returns type bit length."""
347  return self.bitlen
348 
349 
350 class Attribute:
351  """
352  Base class of an attribute description.
353  Fields:
354  type Attribute type description, the type of this field inherits the class Type, e.g. PrimitiveType
355  name Attribute name string
356  """
357 
358  # noinspection PyShadowingBuiltins
359  def __init__(self, type, name): # @ReservedAssignment
360  self.type = type
361  self.name = name
362 
363  def __str__(self):
364  return self.get_normalized_definition()
365 
367  raise NotImplementedError('Pure virtual method')
368 
369  __repr__ = __str__
370 
371 
373  """
374  Field description.
375  Does not add new fields to Attribute.
376  If type is void, the name will be None.
377  """
378 
380  if self.type.category == self.type.CATEGORY_VOID:
381  return self.type.get_normalized_definition()
382  else:
383  return '%s %s' % (self.type.get_normalized_definition(), self.name)
384 
385 
387  """
388  Constant description.
389  Fields:
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
393  """
394 
395  # noinspection PyShadowingBuiltins
396  def __init__(self, type, name, init_expression, value): # @ReservedAssignment
397  Attribute.__init__(self, type, name)
398  self.init_expression = init_expression
399  self.value = value
400  self.string_value = repr(value)
401  if isinstance(value, long):
402  self.string_value = self.string_value.replace('L', '')
403 
405  return '%s %s = %s' % (self.type.get_normalized_definition(), self.name, self.init_expression)
406 
407 
408 class Parser:
409  """
410  DSDL parser logic. Do not use this class directly; use the helper function instead.
411  """
412 
413  def __init__(self, search_dirs):
415 
416  def _namespace_from_filename(self, filename):
417  search_dirs = sorted(map(os.path.abspath, self.search_dirs)) # Nested last
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('.')
427  return ns
428  error('File [%s] was not found in search directories', filename)
429 
431  basename = os.path.basename(filename)
432  items = basename.split('.')
433 
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)
436 
437  if len(items) == 2 or len(items) == 4:
438  default_dtid, name = None, items[0]
439  else:
440  default_dtid, name = items[0], items[1]
441  try:
442  default_dtid = int(default_dtid)
443  except ValueError:
444  error('Invalid default data type ID [%s]', default_dtid)
445 
446  if len(items) == 2 or len(items) == 3:
447  version = None
448  else:
449  major_version, minor_version = items[-3], items[-2]
450  try:
451  version = (int(major_version), int(minor_version))
452  except ValueError:
453  error('Invalid version number [%s]', major_version, minor_version)
454 
455  full_name = self._namespace_from_filename(filename) + '.' + name
457  return full_name, version, default_dtid
458 
459  def _locate_compound_type_definition(self, referencing_filename, typename):
460  def locate_namespace_directories(ns):
461  namespace_dirs = []
462  namespace_components = ns.split('.')
463  root_namespace, sub_namespace_components = namespace_components[0], namespace_components[1:]
464  for d in self.search_dirs:
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
470 
471  if '.' not in typename:
472  current_namespace = self._namespace_from_filename(referencing_filename)
473  full_typename = current_namespace + '.' + typename
474  else:
475  full_typename = typename
476  namespace = '.'.join(full_typename.split('.')[:-1])
477  directories = locate_namespace_directories(namespace)
478 
479  for directory in directories:
480  logger.debug('Searching for [%s] in [%s]', full_typename, directory)
481  if not os.path.isdir(directory):
482  continue
483  for fn in os.listdir(directory):
484  fn = os.path.join(directory, fn)
485  if os.path.isfile(fn):
486  try:
487  fn_full_typename, _version, _dtid = self._full_typename_version_and_dtid_from_filename(fn)
488  if full_typename == fn_full_typename:
489  return fn
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)
493 
494  # noinspection PyUnusedLocal
495  @staticmethod
496  def _parse_void_type(filename, bitlen):
497  enforce(1 <= bitlen <= 64, 'Invalid void bit length [%d]', bitlen)
498  return VoidType(bitlen)
499 
500  def _parse_array_type(self, filename, value_typedef, size_spec, cast_mode):
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)')
505  try:
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
512  else:
513  max_size = int(size_spec, 0)
514  mode = ArrayType.MODE_STATIC
515  except ValueError:
516  error('Invalid array size specifier [%s] (valid patterns: [<=X], [<X], [X])', size_spec)
517  else:
518  enforce(max_size > 0, 'Array size must be positive, not %d', max_size)
519  return ArrayType(value_type, mode, max_size)
520 
521  # noinspection PyUnusedLocal
522  @staticmethod
523  def _parse_primitive_type(filename, base_name, bitlen, cast_mode):
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
528  else:
529  error('Invalid cast mode [%s]', cast_mode)
530 
531  if base_name == 'bool':
532  return PrimitiveType(PrimitiveType.KIND_BOOLEAN, 1, cast_mode)
533  try:
534  kind = {
535  'uint': PrimitiveType.KIND_UNSIGNED_INT,
536  'int': PrimitiveType.KIND_SIGNED_INT,
537  'float': PrimitiveType.KIND_FLOAT,
538  }[base_name]
539  except KeyError:
540  error('Unknown primitive type (note: compound types should be in CamelCase)')
541 
542  # noinspection PyUnboundLocalVariable
543  if kind == PrimitiveType.KIND_FLOAT:
544  enforce(bitlen in (16, 32, 64), 'Invalid bit length for float type [%d]', bitlen)
545  else:
546  enforce(2 <= bitlen <= 64, 'Invalid bit length [%d] (note: use bool instead of uint1)', bitlen)
547  return PrimitiveType(kind, bitlen, cast_mode)
548 
549  def _parse_compound_type(self, filename, typedef):
550  definition_filename = self._locate_compound_type_definition(filename, typedef)
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')
555  return t
556 
557  def _parse_type(self, filename, typedef, cast_mode):
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)
562 
563  if void_match:
564  size_spec = void_match.group(1).strip()
565  return self._parse_void_type(filename, int(size_spec))
566  elif array_match:
567  assert not primitive_match
568  value_typedef = array_match.group(1).strip()
569  size_spec = array_match.group(2).strip()
570  return self._parse_array_type(filename, value_typedef, size_spec, cast_mode)
571  elif primitive_match:
572  if primitive_match.group(0) == 'bool':
573  return self._parse_primitive_type(filename, 'bool', 1, cast_mode)
574  else:
575  base_name = primitive_match.group(1)
576  bitlen = int(primitive_match.group(2))
577  return self._parse_primitive_type(filename, base_name, bitlen, cast_mode)
578  else:
579  enforce(cast_mode is None, 'Cast mode specifier is not applicable for compound types [%s]', cast_mode)
580  return self._parse_compound_type(filename, typedef)
581 
582  @staticmethod
583  def _make_constant(attrtype, name, init_expression):
584  enforce(attrtype.category == attrtype.CATEGORY_PRIMITIVE, 'Invalid type for constant [%d]', attrtype.category)
585  init_expression = ''.join(init_expression.split()) # Remove spaces
586  value = evaluate_expression(init_expression)
587 
588  if isinstance(value, str) and len(value) == 1: # ASCII character
589  value = ord(value)
590  elif isinstance(value, (float, int, bool, long)): # Numeric literal
591  value = {
592  attrtype.KIND_UNSIGNED_INT: long,
593  attrtype.KIND_SIGNED_INT: long,
594  attrtype.KIND_BOOLEAN: int, # Not bool because we need to check range
595  attrtype.KIND_FLOAT: float
596  }[attrtype.kind](value)
597  else:
598  error('Invalid type of constant initialization expression [%s]', type(value).__name__)
599 
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)
603 
604  def _parse_line(self, filename, tokens):
605  cast_mode = None
606  if tokens[0] == 'saturated' or tokens[0] == 'truncated':
607  cast_mode, tokens = tokens[0], tokens[1:]
608 
609  if len(tokens) < 2 and not tokens[0].startswith('void'):
610  error('Invalid attribute definition')
611 
612  if len(tokens) == 1:
613  typename, attrname, tokens = tokens[0], None, []
614  else:
615  typename, attrname, tokens = tokens[0], tokens[1], tokens[2:]
616  validate_attribute_name(attrname)
617 
618  attrtype = self._parse_type(filename, typename, cast_mode)
619 
620  if len(tokens) > 0:
621  if len(tokens) < 2 or tokens[0] != '=':
622  error('Constant assignment expected')
623  expression = ' '.join(tokens[1:])
624  return self._make_constant(attrtype, attrname, expression)
625  else:
626  return Field(attrtype, attrname)
627 
628  @staticmethod
629  def _tokenize(text):
630  for idx, line in enumerate(text.splitlines()):
631  line = re.sub('#.*', '', line).strip() # Remove comments and leading/trailing whitespaces
632  if line:
633  tokens = [tk for tk in line.split() if tk]
634  yield idx + 1, tokens
635 
636  def parse_source(self, filename, source_text):
637  try:
638  full_typename, version, default_dtid = self._full_typename_version_and_dtid_from_filename(filename)
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:
645  try:
646  if tokens == ['---']:
647  enforce(not response_part, 'Duplicate response mark')
648  response_part = True
649  all_attributes_names = set()
650  continue
651  if tokens == ['@union']:
652  if response_part:
653  enforce(not resp_union, 'Response data structure has already been declared as union')
654  resp_union = True
655  else:
656  enforce(not union, 'Data structure has already been declared as union')
657  union = True
658  continue
659  attr = self._parse_line(filename, tokens)
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)
667  else:
668  error('Unknown attribute type - internal error')
669  except DsdlException as ex:
670  if not ex.line:
671  ex.line = num
672  raise ex
673  except Exception as ex:
674  logger.error('Internal error', exc_info=True)
675  raise DsdlException('Internal error: %s' % str(ex), line=num)
676 
677  if response_part:
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))
687  else:
688  t = CompoundType(full_typename, CompoundType.KIND_MESSAGE, filename, default_dtid, version, source_text)
689  t.fields = fields
690  t.constants = constants
691  t.union = union
692  max_bitlen = t.get_max_bitlen()
693  max_bytelen = bitlen_to_bytelen(max_bitlen)
694 
695  validate_union(t)
696 
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)
702  return t
703  except DsdlException as ex:
704  if not ex.file:
705  ex.file = filename
706  raise ex
707 
708  def parse(self, filename):
709  try:
710  filename = os.path.abspath(filename)
711  with open(filename) as f:
712  source_text = f.read()
713 
714  return self.parse_source(filename, source_text)
715  except IOError as ex:
716  raise DsdlException('IO error: %s' % str(ex), file=filename)
717  except Exception as ex:
718  logger.error('Internal error', exc_info=True)
719  raise DsdlException('Internal error: %s' % str(ex), file=filename)
720 
721 
722 def error(fmt, *args):
723  raise DsdlException(fmt % args)
724 
725 
726 def enforce(cond, fmt, *args):
727  if not cond:
728  error(fmt, *args)
729 
730 
732  return int((x + 7) / 8)
733 
734 
735 def evaluate_expression(expression):
736  try:
737  env = {
738  'locals': None,
739  'globals': None,
740  '__builtins__': None,
741  'true': 1,
742  'false': 0
743  }
744  return eval(expression, env)
745  except Exception as ex:
746  error('Cannot evaluate expression: %s', str(ex))
747 
748 
750  dirnames = set(dirnames)
751  dirnames = list(map(os.path.abspath, dirnames))
752  for d1 in dirnames:
753  for d2 in dirnames:
754  if d1 == d2:
755  continue
756  enforce(not d1.startswith(d2), 'Nested search directories are not allowed [%s] [%s]', d1, d2)
757  return dirnames
758 
759 
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)
764 
765 
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])
770  validate_namespace_name(namespace)
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)
773 
774 
776  enforce(re.match(r'[a-zA-Z][a-zA-Z0-9_]*$', name), 'Invalid attribute name [%s]', name)
777 
778 
780  if t.default_dtid is None:
781  return
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)
788  else:
789  error('Invalid kind: %s', t.kind)
790 
791 
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:
797  if t.union:
798  check_fields(t.fields)
799  elif t.kind == t.KIND_SERVICE:
800  if t.request_union:
801  check_fields(t.request_fields)
802  if t.response_union:
803  check_fields(t.response_fields)
804  else:
805  error('Invalid kind: %s', t.kind)
806 
807 
808 def parse_namespaces(source_dirs, search_dirs=None):
809  """
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).
814 
815  Returns the list of parsed type definitions, where type of each element is CompoundType.
816 
817  Args:
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.
821  Example:
822  >>> import pyuavcan_v0
823  >>> a = pyuavcan_v0.dsdl.parse_namespaces(['../dsdl/uavcan'])
824  >>> len(a)
825  77
826  >>> a[0]
827  pyuavcan_v0.Timestamp
828  >>> a[0].fields
829  [truncated uint48 husec]
830  >>> a[0].constants
831  [saturated uint48 UNKNOWN = 0, saturated uint48 USEC_PER_LSB = 100]
832  """
833 
834  # noinspection PyShadowingNames
835  def walk(walk_dirs):
836  import fnmatch
837  from functools import partial
838 
839  def on_walk_error(directory, ex):
840  raise DsdlException('OS error in [%s]: %s' % (directory, str(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)
846  yield filename
847 
848  all_default_dtid = {} # (kind, dtid) : filename
849 
850  # noinspection PyShadowingNames
851  def ensure_unique_dtid(t, filename):
852  if t.default_dtid is None:
853  return
854  key = t.kind, t.default_dtid
855  if key in all_default_dtid:
856  value = all_default_dtid[key]
857  first = pretty_filename(value[0])
858  second = pretty_filename(filename)
859  if t.get_dsdl_signature() != value[1].get_dsdl_signature():
860  error('Redefinition of data type ID: [%s] [%s]', first, second)
861  else:
862  logger.debug('ignoring duplicate definition of %s', t.full_name)
863  return
864 
865  all_default_dtid[key] = (filename, t)
866 
867  parser = Parser(source_dirs + (search_dirs or []))
868  output_types = []
869  if search_dirs:
870  for filename in walk(search_dirs):
871  t = parser.parse(filename)
872  ensure_unique_dtid(t, filename)
873 
874  for filename in walk(source_dirs):
875  t = parser.parse(filename)
876  ensure_unique_dtid(t, filename)
877  output_types.append(t)
878  return output_types
pyuavcan_v0.dsdl.common.DsdlException
Definition: dsdl/common.py:16
pyuavcan_v0.dsdl.parser.ArrayType.get_data_type_signature
def get_data_type_signature(self)
Definition: parser.py:168
pyuavcan_v0.dsdl.parser.PrimitiveType
Definition: parser.py:74
pyuavcan_v0.dsdl.parser.Parser._locate_compound_type_definition
def _locate_compound_type_definition(self, referencing_filename, typename)
Definition: parser.py:459
pyuavcan_v0.dsdl.parser.Type.get_data_type_signature
def get_data_type_signature(self)
Definition: parser.py:59
pyuavcan_v0.dsdl.parser.Parser._parse_type
def _parse_type(self, filename, typedef, cast_mode)
Definition: parser.py:557
pyuavcan_v0.dsdl.parser.Attribute.type
type
Definition: parser.py:360
pyuavcan_v0.dsdl.parser.Parser._make_constant
def _make_constant(attrtype, name, init_expression)
Definition: parser.py:583
pyuavcan_v0.dsdl.parser.evaluate_expression
def evaluate_expression(expression)
Definition: parser.py:735
pyuavcan_v0.dsdl.parser.Constant.string_value
string_value
Definition: parser.py:400
pyuavcan_v0.dsdl.parser.CompoundType.get_dsdl_signature
def get_dsdl_signature(self)
Definition: parser.py:296
pyuavcan_v0.dsdl.parser.Attribute.get_normalized_definition
def get_normalized_definition(self)
Definition: parser.py:366
pyuavcan_v0.dsdl.parser.CompoundType.source_file
source_file
Definition: parser.py:217
pyuavcan_v0.dsdl.parser.ArrayType.value_type
value_type
Definition: parser.py:144
pyuavcan_v0.dsdl.parser.Type.get_min_bitlen
def get_min_bitlen(self)
Definition: parser.py:68
pyuavcan_v0.dsdl.parser.CompoundType.get_min_bitlen_request
get_min_bitlen_request
Definition: parser.py:249
pyuavcan_v0.dsdl.parser.Attribute.name
name
Definition: parser.py:361
pyuavcan_v0.dsdl.parser.CompoundType.request_constants
request_constants
Definition: parser.py:245
pyuavcan_v0.dsdl.parser.validate_union
def validate_union(t)
Definition: parser.py:792
pyuavcan_v0.dsdl.parser.validate_data_type_id
def validate_data_type_id(t)
Definition: parser.py:779
pyuavcan_v0.dsdl.parser.error
def error(fmt, *args)
Definition: parser.py:722
pyuavcan_v0.dsdl.parser.CompoundType.response_fields
response_fields
Definition: parser.py:244
pyuavcan_v0.dsdl.parser.CompoundType.default_dtid
default_dtid
Definition: parser.py:218
pyuavcan_v0.dsdl.parser.CompoundType.constants
constants
Definition: parser.py:255
pyuavcan_v0.dsdl.parser.Attribute
Definition: parser.py:350
pyuavcan_v0.dsdl.parser.PrimitiveType.bitlen
bitlen
Definition: parser.py:93
pyuavcan_v0.dsdl.parser.Parser._parse_array_type
def _parse_array_type(self, filename, value_typedef, size_spec, cast_mode)
Definition: parser.py:500
pyuavcan_v0.dsdl.parser.CompoundType.version
version
Definition: parser.py:219
pyuavcan_v0.dsdl.parser.Parser._tokenize
def _tokenize(text)
Definition: parser.py:629
pyuavcan_v0.dsdl.parser.bitlen_to_bytelen
def bitlen_to_bytelen(x)
Definition: parser.py:731
pyuavcan_v0.dsdl.parser.ArrayType.max_size
max_size
Definition: parser.py:146
pyuavcan_v0.dsdl.parser.VoidType
Definition: parser.py:326
pyuavcan_v0.dsdl.parser.Parser._parse_primitive_type
def _parse_primitive_type(filename, base_name, bitlen, cast_mode)
Definition: parser.py:523
pyuavcan_v0.dsdl.parser.parse_namespaces
def parse_namespaces(source_dirs, search_dirs=None)
Definition: parser.py:808
pyuavcan_v0.dsdl.parser.validate_attribute_name
def validate_attribute_name(name)
Definition: parser.py:775
pyuavcan_v0.dsdl.parser.Type.full_name
full_name
Definition: parser.py:53
pyuavcan_v0.dsdl.parser.Parser._parse_compound_type
def _parse_compound_type(self, filename, typedef)
Definition: parser.py:549
pyuavcan_v0.dsdl.parser.PrimitiveType.validate_value_range
def validate_value_range(self, value)
Definition: parser.py:114
pyuavcan_v0.dsdl.parser.ArrayType.get_min_bitlen
def get_min_bitlen(self)
Definition: parser.py:162
pyuavcan_v0.dsdl.parser.ArrayType
Definition: parser.py:132
pyuavcan_v0.dsdl.parser.CompoundType.__init__
def __init__(self, full_name, kind, source_file, default_dtid, version, source_text)
Definition: parser.py:215
pyuavcan_v0.dsdl.parser.Constant.init_expression
init_expression
Definition: parser.py:398
pyuavcan_v0.dsdl.parser.VoidType.get_normalized_definition
def get_normalized_definition(self)
Definition: parser.py:337
pyuavcan_v0.dsdl.parser.Type.category
category
Definition: parser.py:54
pyuavcan_v0.dsdl.parser.Parser._parse_line
def _parse_line(self, filename, tokens)
Definition: parser.py:604
pyuavcan_v0.dsdl.parser.CompoundType.fields
fields
Definition: parser.py:254
pyuavcan_v0.dsdl.parser.Type
Definition: parser.py:40
pyuavcan_v0.dsdl.parser.CompoundType.get_min_bitlen
get_min_bitlen
Definition: parser.py:257
pyuavcan_v0.dsdl.parser.validate_compound_type_full_name
def validate_compound_type_full_name(name)
Definition: parser.py:766
pyuavcan_v0.dsdl.common.bytes_from_crc64
def bytes_from_crc64(crc64)
Definition: dsdl/common.py:77
pyuavcan_v0.dsdl.parser.Parser._parse_void_type
def _parse_void_type(filename, bitlen)
Definition: parser.py:496
pyuavcan_v0.dsdl.parser.CompoundType.get_min_bitlen_response
get_min_bitlen_response
Definition: parser.py:250
pyuavcan_v0.dsdl.parser.Field.get_normalized_definition
def get_normalized_definition(self)
Definition: parser.py:379
pyuavcan_v0.dsdl.signature.compute_signature
def compute_signature(data)
Definition: signature.py:61
pyuavcan_v0.dsdl.parser.CompoundType.get_max_bitlen
get_max_bitlen
Definition: parser.py:256
pyuavcan_v0.dsdl.parser.enforce
def enforce(cond, fmt, *args)
Definition: parser.py:726
uavcan::max
const UAVCAN_EXPORT T & max(const T &a, const T &b)
Definition: templates.hpp:291
pyuavcan_v0.dsdl.parser.Parser._namespace_from_filename
def _namespace_from_filename(self, filename)
Definition: parser.py:416
pyuavcan_v0.dsdl.parser.Attribute.__init__
def __init__(self, type, name)
Definition: parser.py:359
pyuavcan_v0.dsdl.parser.ArrayType.is_string_like
def is_string_like(self)
Definition: parser.py:172
pyuavcan_v0.dsdl.parser.PrimitiveType.__init__
def __init__(self, kind, bitlen, cast_mode)
Definition: parser.py:91
pyuavcan_v0.dsdl.parser.Attribute.__str__
def __str__(self)
Definition: parser.py:363
pyuavcan_v0.dsdl.parser.CompoundType.get_max_bitlen_response
get_max_bitlen_response
Definition: parser.py:248
pyuavcan_v0.dsdl.parser.Parser.__init__
def __init__(self, search_dirs)
Definition: parser.py:413
pyuavcan_v0.dsdl.parser.long
long
Definition: parser.py:29
pyuavcan_v0.dsdl.parser.CompoundType.__call__
def __call__(self, *args, **kwargs)
Definition: parser.py:266
pyuavcan_v0.dsdl.parser.CompoundType.response_constants
response_constants
Definition: parser.py:246
pyuavcan_v0.dsdl.parser.Type.__init__
def __init__(self, full_name, category)
Definition: parser.py:52
pyuavcan_v0.dsdl.parser.Constant.__init__
def __init__(self, type, name, init_expression, value)
Definition: parser.py:396
uavcan::min
const UAVCAN_EXPORT T & min(const T &a, const T &b)
Definition: templates.hpp:281
pyuavcan_v0.dsdl.parser.CompoundType.get_normalized_definition
def get_normalized_definition(self)
Definition: parser.py:303
pyuavcan_v0.dsdl.parser.CompoundType.get_dsdl_signature_source_definition
def get_dsdl_signature_source_definition(self)
Definition: parser.py:269
pyuavcan_v0.dsdl.parser.Type.__str__
def __str__(self)
Definition: parser.py:56
pyuavcan_v0.dsdl.parser.PrimitiveType.get_normalized_definition
def get_normalized_definition(self)
Definition: parser.py:103
pyuavcan_v0.dsdl.parser.Parser._full_typename_version_and_dtid_from_filename
def _full_typename_version_and_dtid_from_filename(self, filename)
Definition: parser.py:430
pyuavcan_v0.dsdl.parser.ArrayType.get_normalized_definition
def get_normalized_definition(self)
Definition: parser.py:149
pyuavcan_v0.dsdl.parser.ArrayType.get_max_bitlen
def get_max_bitlen(self)
Definition: parser.py:154
pyuavcan_v0.dsdl.parser.CompoundType.response_union
response_union
Definition: parser.py:252
pyuavcan_v0.dsdl.parser.ArrayType.__init__
def __init__(self, value_type, mode, max_size)
Definition: parser.py:143
pyuavcan_v0.dsdl.parser.validate_namespace_name
def validate_namespace_name(name)
Definition: parser.py:760
int
int
Definition: libstubs.cpp:120
pyuavcan_v0.dsdl.parser.PrimitiveType.kind
kind
Definition: parser.py:92
pyuavcan_v0.dsdl.parser.PrimitiveType.value_range
value_range
Definition: parser.py:96
pyuavcan_v0.dsdl.parser.PrimitiveType.get_min_bitlen
def get_min_bitlen(self)
Definition: parser.py:127
pyuavcan_v0.dsdl.parser.CompoundType.source_text
source_text
Definition: parser.py:221
pyuavcan_v0.dsdl.parser.CompoundType.kind
kind
Definition: parser.py:220
pyuavcan_v0.dsdl.parser.CompoundType
Definition: parser.py:179
pyuavcan_v0.dsdl.parser.CompoundType.request_union
request_union
Definition: parser.py:251
libuavcan_dsdl_compiler.pretty_filename
def pretty_filename(filename)
Definition: libuavcan_dsdl_compiler/__init__.py:67
pyuavcan_v0.dsdl.parser.PrimitiveType.cast_mode
cast_mode
Definition: parser.py:94
pyuavcan_v0.dsdl.parser.VoidType.bitlen
bitlen
Definition: parser.py:334
pyuavcan_v0.dsdl.parser.CompoundType.union
union
Definition: parser.py:258
pyuavcan_v0.dsdl.parser.validate_search_directories
def validate_search_directories(dirnames)
Definition: parser.py:749
pyuavcan_v0.dsdl.parser.Constant.get_normalized_definition
def get_normalized_definition(self)
Definition: parser.py:404
pyuavcan_v0.dsdl.signature.Signature
Definition: signature.py:23
pyuavcan_v0.dsdl.parser.CompoundType.request_fields
request_fields
Definition: parser.py:243
pyuavcan_v0.dsdl.parser.ArrayType.MODE_DYNAMIC
int MODE_DYNAMIC
Definition: parser.py:141
pyuavcan_v0.dsdl.parser.CompoundType._data_type_signature
_data_type_signature
Definition: parser.py:222
pyuavcan_v0.dsdl.parser.CompoundType.get_max_bitlen_request
get_max_bitlen_request
Definition: parser.py:247
pyuavcan_v0.dsdl.parser.Type.get_normalized_definition
def get_normalized_definition(self)
Definition: parser.py:62
pyuavcan_v0.dsdl.parser.Field
Definition: parser.py:372
pyuavcan_v0.dsdl.parser.str
str
Definition: parser.py:22
pyuavcan_v0.dsdl.parser.ArrayType.MODE_STATIC
int MODE_STATIC
Definition: parser.py:140
pyuavcan_v0.dsdl.parser.Parser.parse_source
def parse_source(self, filename, source_text)
Definition: parser.py:636
pyuavcan_v0.dsdl.parser.Parser.parse
def parse(self, filename)
Definition: parser.py:708
pyuavcan_v0.dsdl.parser.Parser
Definition: parser.py:408
pyuavcan_v0.dsdl.parser.ArrayType.mode
mode
Definition: parser.py:145
pyuavcan_v0.dsdl.parser.Parser.search_dirs
search_dirs
Definition: parser.py:414
pyuavcan_v0.dsdl.parser.CompoundType._instantiate
def _instantiate(self, *args, **kwargs)
Definition: parser.py:262
pyuavcan_v0.dsdl.parser.VoidType.__init__
def __init__(self, bitlen)
Definition: parser.py:333
pyuavcan_v0.dsdl.parser.Constant.value
value
Definition: parser.py:399
pyuavcan_v0.dsdl.parser.CompoundType.get_data_type_signature
def get_data_type_signature(self)
Definition: parser.py:307
pyuavcan_v0.dsdl.parser.VoidType.get_max_bitlen
def get_max_bitlen(self)
Definition: parser.py:341
pyuavcan_v0.dsdl.parser.Constant
Definition: parser.py:386
pyuavcan_v0.dsdl.parser.VoidType.get_min_bitlen
def get_min_bitlen(self)
Definition: parser.py:345
pyuavcan_v0.dsdl.parser.Type.get_max_bitlen
def get_max_bitlen(self)
Definition: parser.py:65
pyuavcan_v0.dsdl.parser.PrimitiveType.get_max_bitlen
def get_max_bitlen(self)
Definition: parser.py:123


uavcan_communicator
Author(s):
autogenerated on Fri Dec 13 2024 03:10:02