parser.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846
  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. from __future__ import division, absolute_import, print_function, unicode_literals
  10. import os
  11. import re
  12. from logging import getLogger
  13. from io import StringIO
  14. from .signature import Signature, compute_signature
  15. from .common import DsdlException, pretty_filename, bytes_from_crc64
  16. from .type_limits import get_unsigned_integer_range, get_signed_integer_range, get_float_range
  17. # Python 2.7 compatibility
  18. try:
  19. # noinspection PyUnresolvedReferences,PyShadowingBuiltins
  20. str = unicode # @ReservedAssignment @UndefinedVariable
  21. except NameError:
  22. pass
  23. try:
  24. # noinspection PyUnresolvedReferences,PyUnboundLocalVariable
  25. long(1) # @UndefinedVariable
  26. except NameError:
  27. long = int # @ReservedAssignment
  28. MAX_FULL_TYPE_NAME_LEN = 80
  29. SERVICE_DATA_TYPE_ID_MAX = 255
  30. MESSAGE_DATA_TYPE_ID_MAX = 65535
  31. logger = getLogger(__name__)
  32. class Type:
  33. """
  34. Common type description. The specialized type description classes inherit from this one.
  35. Fields:
  36. full_name Full type name string, e.g. "uavcan.protocol.NodeStatus"
  37. category Any CATEGORY_*
  38. """
  39. CATEGORY_PRIMITIVE = 0
  40. CATEGORY_ARRAY = 1
  41. CATEGORY_COMPOUND = 2
  42. CATEGORY_VOID = 3
  43. def __init__(self, full_name, category):
  44. self.full_name = str(full_name)
  45. self.category = category
  46. def __str__(self):
  47. return self.get_normalized_definition()
  48. def get_data_type_signature(self):
  49. return None
  50. def get_normalized_definition(self):
  51. raise NotImplementedError('Pure virtual method')
  52. def get_max_bitlen(self):
  53. raise NotImplementedError('Pure virtual method')
  54. def get_min_bitlen(self):
  55. raise NotImplementedError('Pure virtual method')
  56. __repr__ = __str__
  57. class PrimitiveType(Type):
  58. """
  59. Primitive type description, e.g. bool or float16.
  60. Fields:
  61. kind Any KIND_*
  62. bitlen Bit length, 1 to 64
  63. cast_mode Any CAST_MODE_*
  64. value_range Tuple containing min and max values: (min, max)
  65. """
  66. KIND_BOOLEAN = 0
  67. KIND_UNSIGNED_INT = 1
  68. KIND_SIGNED_INT = 2
  69. KIND_FLOAT = 3
  70. CAST_MODE_SATURATED = 0
  71. CAST_MODE_TRUNCATED = 1
  72. def __init__(self, kind, bitlen, cast_mode):
  73. self.kind = kind
  74. self.bitlen = bitlen
  75. self.cast_mode = cast_mode
  76. Type.__init__(self, self.get_normalized_definition(), Type.CATEGORY_PRIMITIVE)
  77. self.value_range = {
  78. PrimitiveType.KIND_BOOLEAN: get_unsigned_integer_range,
  79. PrimitiveType.KIND_UNSIGNED_INT: get_unsigned_integer_range,
  80. PrimitiveType.KIND_SIGNED_INT: get_signed_integer_range,
  81. PrimitiveType.KIND_FLOAT: get_float_range
  82. }[self.kind](bitlen)
  83. def get_normalized_definition(self):
  84. """Please refer to the specification for details about normalized definitions."""
  85. cast_mode = 'saturated' if self.cast_mode == PrimitiveType.CAST_MODE_SATURATED else 'truncated'
  86. primary_type = {
  87. PrimitiveType.KIND_BOOLEAN: 'bool',
  88. PrimitiveType.KIND_UNSIGNED_INT: 'uint' + str(self.bitlen),
  89. PrimitiveType.KIND_SIGNED_INT: 'int' + str(self.bitlen),
  90. PrimitiveType.KIND_FLOAT: 'float' + str(self.bitlen)
  91. }[self.kind]
  92. return cast_mode + ' ' + primary_type
  93. def validate_value_range(self, value):
  94. """
  95. Args:
  96. value: Throws DsdlException if this value cannot be represented by this type.
  97. """
  98. low, high = self.value_range
  99. if not low <= value <= high:
  100. error('Value [%s] is out of range %s', value, self.value_range)
  101. def get_max_bitlen(self):
  102. """Returns type bit length."""
  103. return self.bitlen
  104. def get_min_bitlen(self):
  105. """Returns type bit length."""
  106. return self.bitlen
  107. class ArrayType(Type):
  108. """
  109. Array type description, e.g. float32[8], uint12[<34].
  110. Fields:
  111. value_type Description of the array value type; the type of this field inherits Type, e.g. PrimitiveType
  112. mode Any MODE_*
  113. max_size Maximum number of elements in the array
  114. """
  115. MODE_STATIC = 0
  116. MODE_DYNAMIC = 1
  117. def __init__(self, value_type, mode, max_size):
  118. self.value_type = value_type
  119. self.mode = mode
  120. self.max_size = max_size
  121. Type.__init__(self, self.get_normalized_definition(), Type.CATEGORY_ARRAY)
  122. def get_normalized_definition(self):
  123. """Please refer to the specification for details about normalized definitions."""
  124. typedef = self.value_type.get_normalized_definition()
  125. return ('%s[<=%d]' if self.mode == ArrayType.MODE_DYNAMIC else '%s[%d]') % (typedef, self.max_size)
  126. def get_max_bitlen(self):
  127. """Returns total maximum bit length of the array, including length field if applicable."""
  128. payload_max_bitlen = self.max_size * self.value_type.get_max_bitlen()
  129. return {
  130. self.MODE_DYNAMIC: payload_max_bitlen + self.max_size.bit_length(),
  131. self.MODE_STATIC: payload_max_bitlen
  132. }[self.mode]
  133. def get_min_bitlen(self):
  134. if self.mode == self.MODE_STATIC:
  135. return self.value_type.get_min_bitlen() * self.max_size
  136. else:
  137. return 0 # Considering TAO
  138. def get_data_type_signature(self):
  139. return self.value_type.get_data_type_signature()
  140. @property
  141. def is_string_like(self):
  142. return self.mode == self.MODE_DYNAMIC and \
  143. self.value_type.category == Type.CATEGORY_PRIMITIVE and \
  144. self.value_type.bitlen == 8
  145. # noinspection PyAbstractClass
  146. class CompoundType(Type):
  147. """
  148. Compound type description, e.g. uavcan.protocol.NodeStatus.
  149. Fields:
  150. source_file Path to the DSDL definition file for this type
  151. default_dtid Default Data Type ID, if specified, None otherwise
  152. kind Any KIND_*
  153. source_text Raw DSDL definition text (as is, with comments and the original formatting)
  154. Fields if kind == KIND_SERVICE:
  155. request_fields Request struct field list, the type of each element is Field
  156. response_fields Response struct field list
  157. request_constants Request struct constant list, the type of each element is Constant
  158. response_constants Response struct constant list
  159. request_union Boolean indicating whether the request struct is a union
  160. response_union Boolean indicating whether the response struct is a union
  161. Fields if kind == KIND_MESSAGE:
  162. fields Field list, the type of each element is Field
  163. constants Constant list, the type of each element is Constant
  164. union Boolean indicating whether the message struct is a union
  165. Extra methods if kind == KIND_SERVICE:
  166. get_max_bitlen_request() Returns maximum total bit length of the serialized request struct
  167. get_max_bitlen_response() Same for the response struct
  168. get_min_bitlen_request() Returns minimum total bit length of the serialized request struct
  169. get_min_bitlen_response() Same for the response struct
  170. Extra methods if kind == KIND_MESSAGE:
  171. get_max_bitlen() Returns maximum total bit length of the serialized struct
  172. get_min_bitlen() Returns minimum total bit length of the serialized struct
  173. """
  174. KIND_SERVICE = 0
  175. KIND_MESSAGE = 1
  176. def __init__(self, full_name, kind, source_file, default_dtid, source_text):
  177. Type.__init__(self, full_name, Type.CATEGORY_COMPOUND)
  178. self.source_file = source_file
  179. self.default_dtid = default_dtid
  180. self.kind = kind
  181. self.source_text = source_text
  182. def compute_max_bitlen(flds, union):
  183. if len(flds) == 0:
  184. return 0
  185. lens = [x.type.get_max_bitlen() for x in flds]
  186. if union:
  187. return max(lens) + max(len(flds) - 1, 1).bit_length()
  188. else:
  189. return sum(lens)
  190. def compute_min_bitlen(flds, union):
  191. if len(flds) == 0:
  192. return 0
  193. lens = [x.type.get_min_bitlen() for x in flds]
  194. if union:
  195. return min(lens) + max(len(flds) - 1, 1).bit_length()
  196. else:
  197. return sum(lens)
  198. if kind == CompoundType.KIND_SERVICE:
  199. self.request_fields = []
  200. self.response_fields = []
  201. self.request_constants = []
  202. self.response_constants = []
  203. self.get_max_bitlen_request = lambda: compute_max_bitlen(self.request_fields, self.request_union)
  204. self.get_max_bitlen_response = lambda: compute_max_bitlen(self.response_fields, self.response_union)
  205. self.get_min_bitlen_request = lambda: compute_min_bitlen(self.request_fields, self.request_union)
  206. self.get_min_bitlen_response = lambda: compute_min_bitlen(self.response_fields, self.response_union)
  207. self.request_union = False
  208. self.response_union = False
  209. elif kind == CompoundType.KIND_MESSAGE:
  210. self.fields = []
  211. self.constants = []
  212. self.get_max_bitlen = lambda: compute_max_bitlen(self.fields, self.union)
  213. self.get_min_bitlen = lambda: compute_min_bitlen(self.fields, self.union)
  214. self.union = False
  215. else:
  216. error('Compound type of unknown kind [%s]', kind)
  217. def _instantiate(self, *args, **kwargs):
  218. # This is a stub
  219. pass
  220. def __call__(self, *args, **kwargs):
  221. return self._instantiate(*args, **kwargs)
  222. def get_dsdl_signature_source_definition(self):
  223. """
  224. Returns normalized DSDL definition text.
  225. Please refer to the specification for details about normalized DSDL definitions.
  226. """
  227. txt = StringIO()
  228. txt.write(self.full_name + '\n')
  229. def adjoin(attrs):
  230. return txt.write('\n'.join(x.get_normalized_definition() for x in attrs) + '\n')
  231. if self.kind == CompoundType.KIND_SERVICE:
  232. if self.request_union:
  233. txt.write('\n@union\n')
  234. adjoin(self.request_fields)
  235. txt.write('\n---\n')
  236. if self.response_union:
  237. txt.write('\n@union\n')
  238. adjoin(self.response_fields)
  239. elif self.kind == CompoundType.KIND_MESSAGE:
  240. if self.union:
  241. txt.write('\n@union\n')
  242. adjoin(self.fields)
  243. else:
  244. error('Compound type of unknown kind [%s]', self.kind)
  245. return txt.getvalue().strip().replace('\n\n\n', '\n').replace('\n\n', '\n')
  246. def get_dsdl_signature(self):
  247. """
  248. Computes DSDL signature of this type.
  249. Please refer to the specification for details about signatures.
  250. """
  251. return compute_signature(self.get_dsdl_signature_source_definition())
  252. def get_normalized_definition(self):
  253. """Returns full type name string, e.g. 'uavcan.protocol.NodeStatus'"""
  254. return self.full_name
  255. def get_data_type_signature(self):
  256. """
  257. Computes data type signature of this type. The data type signature is
  258. guaranteed to match only if all nested data structures are compatible.
  259. Please refer to the specification for details about signatures.
  260. """
  261. sig = Signature(self.get_dsdl_signature())
  262. fields = self.request_fields + self.response_fields if self.kind == CompoundType.KIND_SERVICE else self.fields
  263. for field in fields:
  264. field_sig = field.type.get_data_type_signature()
  265. if field_sig is not None:
  266. sig_value = sig.get_value()
  267. sig.add(bytes_from_crc64(field_sig))
  268. sig.add(bytes_from_crc64(sig_value))
  269. return sig.get_value()
  270. class VoidType(Type):
  271. """
  272. Void type description, e.g. void2.
  273. Fields:
  274. bitlen Bit length, 1 to 64
  275. """
  276. def __init__(self, bitlen):
  277. self.bitlen = bitlen
  278. Type.__init__(self, self.get_normalized_definition(), Type.CATEGORY_VOID)
  279. def get_normalized_definition(self):
  280. """Please refer to the specification for details about normalized definitions."""
  281. return 'void' + str(self.bitlen)
  282. def get_max_bitlen(self):
  283. """Returns type bit length."""
  284. return self.bitlen
  285. def get_min_bitlen(self):
  286. """Returns type bit length."""
  287. return self.bitlen
  288. class Attribute:
  289. """
  290. Base class of an attribute description.
  291. Fields:
  292. type Attribute type description, the type of this field inherits the class Type, e.g. PrimitiveType
  293. name Attribute name string
  294. """
  295. # noinspection PyShadowingBuiltins
  296. def __init__(self, type, name): # @ReservedAssignment
  297. self.type = type
  298. self.name = name
  299. def __str__(self):
  300. return self.get_normalized_definition()
  301. def get_normalized_definition(self):
  302. raise NotImplementedError('Pure virtual method')
  303. __repr__ = __str__
  304. class Field(Attribute):
  305. """
  306. Field description.
  307. Does not add new fields to Attribute.
  308. If type is void, the name will be None.
  309. """
  310. def get_normalized_definition(self):
  311. if self.type.category == self.type.CATEGORY_VOID:
  312. return self.type.get_normalized_definition()
  313. else:
  314. return '%s %s' % (self.type.get_normalized_definition(), self.name)
  315. class Constant(Attribute):
  316. """
  317. Constant description.
  318. Fields:
  319. init_expression Constant initialization expression string, e.g. "2+2" or "'\x66'"
  320. value Computed result of the initialization expression in the final type (e.g. int, float)
  321. string_value Computed result of the initialization expression as string
  322. """
  323. # noinspection PyShadowingBuiltins
  324. def __init__(self, type, name, init_expression, value): # @ReservedAssignment
  325. Attribute.__init__(self, type, name)
  326. self.init_expression = init_expression
  327. self.value = value
  328. self.string_value = repr(value)
  329. if isinstance(value, long):
  330. self.string_value = self.string_value.replace('L', '')
  331. def get_normalized_definition(self):
  332. return '%s %s = %s' % (self.type.get_normalized_definition(), self.name, self.init_expression)
  333. class Parser:
  334. """
  335. DSDL parser logic. Do not use this class directly; use the helper function instead.
  336. """
  337. def __init__(self, search_dirs):
  338. self.search_dirs = validate_search_directories(search_dirs)
  339. def _namespace_from_filename(self, filename):
  340. search_dirs = sorted(map(os.path.abspath, self.search_dirs)) # Nested last
  341. filename = os.path.abspath(filename)
  342. for dirname in search_dirs:
  343. root_ns = dirname.split(os.path.sep)[-1]
  344. if filename.startswith(dirname):
  345. dir_len = len(dirname)
  346. basename_len = len(os.path.basename(filename))
  347. ns = filename[dir_len:-basename_len]
  348. ns = (root_ns + '.' + ns.replace(os.path.sep, '.').strip('.')).strip('.')
  349. validate_namespace_name(ns)
  350. return ns
  351. error('File [%s] was not found in search directories', filename)
  352. def _full_typename_and_dtid_from_filename(self, filename):
  353. basename = os.path.basename(filename)
  354. items = basename.split('.')
  355. if (len(items) != 2 and len(items) != 3) or items[-1] != 'uavcan':
  356. error('Invalid file name [%s]; expected pattern: [<default-dtid>.]<short-type-name>.uavcan', basename)
  357. if len(items) == 2:
  358. default_dtid, name = None, items[0]
  359. else:
  360. default_dtid, name = items[0], items[1]
  361. try:
  362. default_dtid = int(default_dtid)
  363. except ValueError:
  364. error('Invalid default data type ID [%s]', default_dtid)
  365. full_name = self._namespace_from_filename(filename) + '.' + name
  366. validate_compound_type_full_name(full_name)
  367. return full_name, default_dtid
  368. def _locate_compound_type_definition(self, referencing_filename, typename):
  369. def locate_namespace_directory(ns):
  370. namespace_components = ns.split('.')
  371. root_namespace, sub_namespace_components = namespace_components[0], namespace_components[1:]
  372. for d in self.search_dirs:
  373. if d.split(os.path.sep)[-1] == root_namespace:
  374. return os.path.join(d, *sub_namespace_components)
  375. error('Unknown namespace [%s]', ns)
  376. if '.' not in typename:
  377. current_namespace = self._namespace_from_filename(referencing_filename)
  378. full_typename = current_namespace + '.' + typename
  379. else:
  380. full_typename = typename
  381. namespace = '.'.join(full_typename.split('.')[:-1])
  382. directory = locate_namespace_directory(namespace)
  383. logger.debug('Searching for [%s] in [%s]', full_typename, directory)
  384. for fn in os.listdir(directory):
  385. fn = os.path.join(directory, fn)
  386. if os.path.isfile(fn):
  387. try:
  388. fn_full_typename, _dtid = self._full_typename_and_dtid_from_filename(fn)
  389. if full_typename == fn_full_typename:
  390. return fn
  391. except Exception as ex:
  392. logger.debug('Unknown file [%s], skipping... [%s]', pretty_filename(fn), ex)
  393. error('Type definition not found [%s]', typename)
  394. # noinspection PyUnusedLocal
  395. @staticmethod
  396. def _parse_void_type(filename, bitlen):
  397. enforce(1 <= bitlen <= 64, 'Invalid void bit length [%d]', bitlen)
  398. return VoidType(bitlen)
  399. def _parse_array_type(self, filename, value_typedef, size_spec, cast_mode):
  400. logger.debug('Parsing the array value type [%s]...', value_typedef)
  401. value_type = self._parse_type(filename, value_typedef, cast_mode)
  402. enforce(value_type.category != value_type.CATEGORY_ARRAY,
  403. 'Multidimensional arrays are not allowed (protip: use nested types)')
  404. try:
  405. if size_spec.startswith('<='):
  406. max_size = int(size_spec[2:], 0)
  407. mode = ArrayType.MODE_DYNAMIC
  408. elif size_spec.startswith('<'):
  409. max_size = int(size_spec[1:], 0) - 1
  410. mode = ArrayType.MODE_DYNAMIC
  411. else:
  412. max_size = int(size_spec, 0)
  413. mode = ArrayType.MODE_STATIC
  414. except ValueError:
  415. error('Invalid array size specifier [%s] (valid patterns: [<=X], [<X], [X])', size_spec)
  416. else:
  417. enforce(max_size > 0, 'Array size must be positive, not %d', max_size)
  418. return ArrayType(value_type, mode, max_size)
  419. # noinspection PyUnusedLocal
  420. @staticmethod
  421. def _parse_primitive_type(filename, base_name, bitlen, cast_mode):
  422. if cast_mode is None or cast_mode == 'saturated':
  423. cast_mode = PrimitiveType.CAST_MODE_SATURATED
  424. elif cast_mode == 'truncated':
  425. cast_mode = PrimitiveType.CAST_MODE_TRUNCATED
  426. else:
  427. error('Invalid cast mode [%s]', cast_mode)
  428. if base_name == 'bool':
  429. return PrimitiveType(PrimitiveType.KIND_BOOLEAN, 1, cast_mode)
  430. try:
  431. kind = {
  432. 'uint': PrimitiveType.KIND_UNSIGNED_INT,
  433. 'int': PrimitiveType.KIND_SIGNED_INT,
  434. 'float': PrimitiveType.KIND_FLOAT,
  435. }[base_name]
  436. except KeyError:
  437. error('Unknown primitive type (note: compound types should be in CamelCase)')
  438. # noinspection PyUnboundLocalVariable
  439. if kind == PrimitiveType.KIND_FLOAT:
  440. enforce(bitlen in (16, 32, 64), 'Invalid bit length for float type [%d]', bitlen)
  441. else:
  442. enforce(2 <= bitlen <= 64, 'Invalid bit length [%d] (note: use bool instead of uint1)', bitlen)
  443. return PrimitiveType(kind, bitlen, cast_mode)
  444. def _parse_compound_type(self, filename, typedef):
  445. definition_filename = self._locate_compound_type_definition(filename, typedef)
  446. logger.debug('Nested type [%s] is defined in [%s], parsing...', typedef, pretty_filename(definition_filename))
  447. t = self.parse(definition_filename)
  448. if t.kind == t.KIND_SERVICE:
  449. error('A service type can not be nested into another compound type')
  450. return t
  451. def _parse_type(self, filename, typedef, cast_mode):
  452. typedef = typedef.strip()
  453. void_match = re.match(r'void(\d{1,2})$', typedef)
  454. array_match = re.match(r'(.+?)\[([^\]]*)\]$', typedef)
  455. primitive_match = re.match(r'([a-z]+)(\d{1,2})$|(bool)$', typedef)
  456. if void_match:
  457. size_spec = void_match.group(1).strip()
  458. return self._parse_void_type(filename, int(size_spec))
  459. elif array_match:
  460. assert not primitive_match
  461. value_typedef = array_match.group(1).strip()
  462. size_spec = array_match.group(2).strip()
  463. return self._parse_array_type(filename, value_typedef, size_spec, cast_mode)
  464. elif primitive_match:
  465. if primitive_match.group(0) == 'bool':
  466. return self._parse_primitive_type(filename, 'bool', 1, cast_mode)
  467. else:
  468. base_name = primitive_match.group(1)
  469. bitlen = int(primitive_match.group(2))
  470. return self._parse_primitive_type(filename, base_name, bitlen, cast_mode)
  471. else:
  472. enforce(cast_mode is None, 'Cast mode specifier is not applicable for compound types [%s]', cast_mode)
  473. return self._parse_compound_type(filename, typedef)
  474. @staticmethod
  475. def _make_constant(attrtype, name, init_expression):
  476. enforce(attrtype.category == attrtype.CATEGORY_PRIMITIVE, 'Invalid type for constant [%d]', attrtype.category)
  477. init_expression = ''.join(init_expression.split()) # Remove spaces
  478. value = evaluate_expression(init_expression)
  479. if isinstance(value, str) and len(value) == 1: # ASCII character
  480. value = ord(value)
  481. elif isinstance(value, (float, int, bool, long)): # Numeric literal
  482. value = {
  483. attrtype.KIND_UNSIGNED_INT: long,
  484. attrtype.KIND_SIGNED_INT: long,
  485. attrtype.KIND_BOOLEAN: int, # Not bool because we need to check range
  486. attrtype.KIND_FLOAT: float
  487. }[attrtype.kind](value)
  488. else:
  489. error('Invalid type of constant initialization expression [%s]', type(value).__name__)
  490. logger.debug('Constant initialization expression evaluated as: [%s] --> %s', init_expression, repr(value))
  491. attrtype.validate_value_range(value)
  492. return Constant(attrtype, name, init_expression, value)
  493. def _parse_line(self, filename, tokens):
  494. cast_mode = None
  495. if tokens[0] == 'saturated' or tokens[0] == 'truncated':
  496. cast_mode, tokens = tokens[0], tokens[1:]
  497. if len(tokens) < 2 and not tokens[0].startswith('void'):
  498. error('Invalid attribute definition')
  499. if len(tokens) == 1:
  500. typename, attrname, tokens = tokens[0], None, []
  501. else:
  502. typename, attrname, tokens = tokens[0], tokens[1], tokens[2:]
  503. validate_attribute_name(attrname)
  504. attrtype = self._parse_type(filename, typename, cast_mode)
  505. if len(tokens) > 0:
  506. if len(tokens) < 2 or tokens[0] != '=':
  507. error('Constant assignment expected')
  508. expression = ' '.join(tokens[1:])
  509. return self._make_constant(attrtype, attrname, expression)
  510. else:
  511. return Field(attrtype, attrname)
  512. @staticmethod
  513. def _tokenize(text):
  514. for idx, line in enumerate(text.splitlines()):
  515. line = re.sub('#.*', '', line).strip() # Remove comments and leading/trailing whitespaces
  516. if line:
  517. tokens = [tk for tk in line.split() if tk]
  518. yield idx + 1, tokens
  519. def parse_source(self, filename, source_text):
  520. try:
  521. full_typename, default_dtid = self._full_typename_and_dtid_from_filename(filename)
  522. numbered_lines = list(self._tokenize(source_text))
  523. all_attributes_names = set()
  524. fields, constants, resp_fields, resp_constants = [], [], [], []
  525. union, resp_union = False, False
  526. response_part = False
  527. for num, tokens in numbered_lines:
  528. try:
  529. if tokens == ['---']:
  530. enforce(not response_part, 'Duplicate response mark')
  531. response_part = True
  532. all_attributes_names = set()
  533. continue
  534. if tokens == ['@union']:
  535. if response_part:
  536. enforce(not resp_union, 'Response data structure has already been declared as union')
  537. resp_union = True
  538. else:
  539. enforce(not union, 'Data structure has already been declared as union')
  540. union = True
  541. continue
  542. attr = self._parse_line(filename, tokens)
  543. if attr.name and attr.name in all_attributes_names:
  544. error('Duplicated attribute name [%s]', attr.name)
  545. all_attributes_names.add(attr.name)
  546. if isinstance(attr, Constant):
  547. (resp_constants if response_part else constants).append(attr)
  548. elif isinstance(attr, Field):
  549. (resp_fields if response_part else fields).append(attr)
  550. else:
  551. error('Unknown attribute type - internal error')
  552. except DsdlException as ex:
  553. if not ex.line:
  554. ex.line = num
  555. raise ex
  556. except Exception as ex:
  557. logger.error('Internal error', exc_info=True)
  558. raise DsdlException('Internal error: %s' % str(ex), line=num)
  559. if response_part:
  560. t = CompoundType(full_typename, CompoundType.KIND_SERVICE, filename, default_dtid, source_text)
  561. t.request_fields = fields
  562. t.request_constants = constants
  563. t.response_fields = resp_fields
  564. t.response_constants = resp_constants
  565. t.request_union = union
  566. t.response_union = resp_union
  567. max_bitlen = t.get_max_bitlen_request(), t.get_max_bitlen_response()
  568. max_bytelen = tuple(map(bitlen_to_bytelen, max_bitlen))
  569. else:
  570. t = CompoundType(full_typename, CompoundType.KIND_MESSAGE, filename, default_dtid, source_text)
  571. t.fields = fields
  572. t.constants = constants
  573. t.union = union
  574. max_bitlen = t.get_max_bitlen()
  575. max_bytelen = bitlen_to_bytelen(max_bitlen)
  576. validate_union(t)
  577. validate_data_type_id(t)
  578. logger.debug('Type [%s], default DTID: %s, signature: %08x, maxbits: %s, maxbytes: %s, DSSD:',
  579. full_typename, default_dtid, t.get_dsdl_signature(), max_bitlen, max_bytelen)
  580. for ln in t.get_dsdl_signature_source_definition().splitlines():
  581. logger.debug(' %s', ln)
  582. return t
  583. except DsdlException as ex:
  584. if not ex.file:
  585. ex.file = filename
  586. raise ex
  587. def parse(self, filename):
  588. try:
  589. filename = os.path.abspath(filename)
  590. with open(filename) as f:
  591. source_text = f.read()
  592. return self.parse_source(filename, source_text)
  593. except IOError as ex:
  594. raise DsdlException('IO error: %s' % str(ex), file=filename)
  595. except Exception as ex:
  596. logger.error('Internal error', exc_info=True)
  597. raise DsdlException('Internal error: %s' % str(ex), file=filename)
  598. def error(fmt, *args):
  599. raise DsdlException(fmt % args)
  600. def enforce(cond, fmt, *args):
  601. if not cond:
  602. error(fmt, *args)
  603. def bitlen_to_bytelen(x):
  604. return int((x + 7) / 8)
  605. def evaluate_expression(expression):
  606. try:
  607. env = {
  608. 'locals': None,
  609. 'globals': None,
  610. '__builtins__': None,
  611. 'true': 1,
  612. 'false': 0
  613. }
  614. return eval(expression, env)
  615. except Exception as ex:
  616. error('Cannot evaluate expression: %s', str(ex))
  617. def validate_search_directories(dirnames):
  618. dirnames = set(dirnames)
  619. dirnames = list(map(os.path.abspath, dirnames))
  620. for d1 in dirnames:
  621. for d2 in dirnames:
  622. if d1 == d2:
  623. continue
  624. enforce(not d1.startswith(d2), 'Nested search directories are not allowed [%s] [%s]', d1, d2)
  625. enforce(d1.split(os.path.sep)[-1] != d2.split(os.path.sep)[-1],
  626. 'Namespace roots must be unique [%s] [%s]', d1, d2)
  627. return dirnames
  628. def validate_namespace_name(name):
  629. for component in name.split('.'):
  630. enforce(re.match(r'[a-z][a-z0-9_]*$', component), 'Invalid namespace name [%s]', name)
  631. enforce(len(name) <= MAX_FULL_TYPE_NAME_LEN, 'Namespace name is too long [%s]', name)
  632. def validate_compound_type_full_name(name):
  633. enforce('.' in name, 'Full type name must explicitly specify its namespace [%s]', name)
  634. short_name = name.split('.')[-1]
  635. namespace = '.'.join(name.split('.')[:-1])
  636. validate_namespace_name(namespace)
  637. enforce(re.match(r'[A-Z][A-Za-z0-9_]*$', short_name), 'Invalid type name [%s]', name)
  638. enforce(len(name) <= MAX_FULL_TYPE_NAME_LEN, 'Type name is too long [%s]', name)
  639. def validate_attribute_name(name):
  640. enforce(re.match(r'[a-zA-Z][a-zA-Z0-9_]*$', name), 'Invalid attribute name [%s]', name)
  641. def validate_data_type_id(t):
  642. if t.default_dtid is None:
  643. return
  644. if t.kind == t.KIND_MESSAGE:
  645. enforce(0 <= t.default_dtid <= MESSAGE_DATA_TYPE_ID_MAX,
  646. 'Invalid data type ID for message [%s]', t.default_dtid)
  647. elif t.kind == t.KIND_SERVICE:
  648. enforce(0 <= t.default_dtid <= SERVICE_DATA_TYPE_ID_MAX,
  649. 'Invalid data type ID for service [%s]', t.default_dtid)
  650. else:
  651. error('Invalid kind: %s', t.kind)
  652. def validate_union(t):
  653. def check_fields(fields):
  654. enforce(len(fields) > 1, 'Union contains less than 2 fields')
  655. enforce(not any(_.type.category == _.type.CATEGORY_VOID for _ in fields), 'Union must not contain void fields')
  656. if t.kind == t.KIND_MESSAGE:
  657. if t.union:
  658. check_fields(t.fields)
  659. elif t.kind == t.KIND_SERVICE:
  660. if t.request_union:
  661. check_fields(t.request_fields)
  662. if t.response_union:
  663. check_fields(t.response_fields)
  664. else:
  665. error('Invalid kind: %s', t.kind)
  666. def parse_namespaces(source_dirs, search_dirs=None):
  667. """
  668. Use only this function to parse DSDL definitions.
  669. This function takes a list of root namespace directories (containing DSDL definition files to parse) and an
  670. optional list of search directories (containing DSDL definition files that can be referenced from the types
  671. that are going to be parsed).
  672. Returns the list of parsed type definitions, where type of each element is CompoundType.
  673. Args:
  674. source_dirs: List of root namespace directories to parse.
  675. search_dirs: List of root namespace directories with referenced types (optional). This list is
  676. automatically extended with source_dirs.
  677. Example:
  678. >>> import uavcan
  679. >>> a = uavcan.dsdl.parse_namespaces(['../dsdl/uavcan'])
  680. >>> len(a)
  681. 77
  682. >>> a[0]
  683. uavcan.Timestamp
  684. >>> a[0].fields
  685. [truncated uint48 husec]
  686. >>> a[0].constants
  687. [saturated uint48 UNKNOWN = 0, saturated uint48 USEC_PER_LSB = 100]
  688. """
  689. # noinspection PyShadowingNames
  690. def walk():
  691. import fnmatch
  692. from functools import partial
  693. def on_walk_error(directory, ex):
  694. raise DsdlException('OS error in [%s]: %s' % (directory, str(ex)))
  695. for source_dir in source_dirs:
  696. walker = os.walk(source_dir, onerror=partial(on_walk_error, source_dir), followlinks=True)
  697. for root, _dirnames, filenames in walker:
  698. for filename in fnmatch.filter(filenames, '*.uavcan'):
  699. filename = os.path.join(root, filename)
  700. yield filename
  701. all_default_dtid = {} # (kind, dtid) : filename
  702. # noinspection PyShadowingNames
  703. def ensure_unique_dtid(t, filename):
  704. if t.default_dtid is None:
  705. return
  706. key = t.kind, t.default_dtid
  707. if key in all_default_dtid:
  708. first = pretty_filename(all_default_dtid[key])
  709. second = pretty_filename(filename)
  710. error('Default data type ID collision: [%s] [%s]', first, second)
  711. all_default_dtid[key] = filename
  712. parser = Parser(source_dirs + (search_dirs or []))
  713. output_types = []
  714. for filename in walk():
  715. t = parser.parse(filename)
  716. ensure_unique_dtid(t, filename)
  717. output_types.append(t)
  718. return output_types