diff --git a/pyuavcan/dsdl_test_data/root_a/1023.ServiceInRootA.uavcan b/pyuavcan/dsdl_test_data/root_a/1023.ServiceInRootA.uavcan new file mode 100644 index 0000000000..7aaebb36bb --- /dev/null +++ b/pyuavcan/dsdl_test_data/root_a/1023.ServiceInRootA.uavcan @@ -0,0 +1,7 @@ + # +# Test file +# + +root_b.TypeInRootB foo +--- +root_a.ns2.TypeInNs2[<256] foo diff --git a/pyuavcan/dsdl_test_data/root_a/TypeInRootA.uavcan b/pyuavcan/dsdl_test_data/root_a/425.TypeInRootA.uavcan similarity index 100% rename from pyuavcan/dsdl_test_data/root_a/TypeInRootA.uavcan rename to pyuavcan/dsdl_test_data/root_a/425.TypeInRootA.uavcan diff --git a/pyuavcan/pyuavcan/dsdl/__init__.py b/pyuavcan/pyuavcan/dsdl/__init__.py index 2e16a3a053..f364dd0624 100644 --- a/pyuavcan/pyuavcan/dsdl/__init__.py +++ b/pyuavcan/pyuavcan/dsdl/__init__.py @@ -2,4 +2,8 @@ # Copyright (C) 2014 Pavel Kirienko # -from .parser import Parser +from .parser import Parser, parse_namespaces, \ + Type, PrimitiveType, ArrayType, CompoundType, \ + Attribute, Field, Constant + +from .common import DsdlException diff --git a/pyuavcan/pyuavcan/dsdl/parser.py b/pyuavcan/pyuavcan/dsdl/parser.py index 3f82aa9e63..4ac78873e8 100644 --- a/pyuavcan/pyuavcan/dsdl/parser.py +++ b/pyuavcan/pyuavcan/dsdl/parser.py @@ -4,12 +4,13 @@ # Copyright (C) 2014 Pavel Kirienko # -import os, re, logging +import os, re, logging, math from .signature import compute_signature from .common import DsdlException, pretty_filename from .type_limits import get_unsigned_integer_range, get_signed_integer_range, get_float_range MAX_FULL_TYPE_NAME_LEN = 80 +DATA_TYPE_ID_MAX = 1023 class Type: CATEGORY_PRIMITIVE = 0 @@ -57,9 +58,10 @@ class PrimitiveType(Type): return cast_mode + ' ' + primary_type def validate_value_range(self, value): - low, high = self.value_range - if not low <= value <= high: - _error('Value [%s] is out of range %s', value, self.value_range) + if math.isfinite(value): + low, high = self.value_range + if not low <= value <= high: + _error('Value [%s] is out of range %s', value, self.value_range) class ArrayType(Type): MODE_STATIC = 0 @@ -149,7 +151,9 @@ def evaluate_expression(expression): 'globals': None, '__builtins__': None, 'true': 1, - 'false': 0 + 'false': 0, + 'inf': float('+inf'), + 'nan': float('nan') } return eval(expression, env) except Exception as ex: @@ -183,6 +187,9 @@ def validate_compound_type_full_name(name): def validate_attribute_name(name): _enforce(re.match(r'[a-zA-Z][a-zA-Z0-9_]*$', name), 'Invalid attribute name [%s]', name) +def validate_data_type_id(dtid): + _enforce(0 <= dtid <= DATA_TYPE_ID_MAX, 'Invalid data type ID [%s]', dtid) + def tokenize_dsdl_definition(text): for idx, line in enumerate(text.splitlines()): line = re.sub('#.*', '', line).strip() # Remove comments and leading/trailing whitespaces @@ -224,6 +231,7 @@ class Parser: default_dtid = int(default_dtid) except ValueError: _error('Invalid default data type ID [%s]', default_dtid) + validate_data_type_id(default_dtid) full_name = self._namespace_from_filename(filename) + '.' + name validate_compound_type_full_name(full_name) return full_name, default_dtid @@ -264,10 +272,10 @@ class Parser: if os.path.isfile(fn): try: fn_full_typename, _dtid = self._full_typename_and_dtid_from_filename(fn) + if full_typename == fn_full_typename: + return fn except Exception as ex: self.log.info('Unknown file [%s], skipping... [%s]', pretty_filename(fn), ex) - if full_typename == fn_full_typename: - return fn _error('Type definition not found [%s]', typename) def _parse_array_type(self, filename, value_typedef, size_spec, cast_mode): @@ -380,10 +388,11 @@ class Parser: return Field(attrtype, attrname) def parse(self, filename): - filename = os.path.abspath(filename) - with open(filename) as f: - text = f.read() try: + filename = os.path.abspath(filename) + with open(filename) as f: + text = f.read() + full_typename, default_dtid = self._full_typename_and_dtid_from_filename(filename) numbered_lines = list(tokenize_dsdl_definition(text)) all_attributes_names = set() @@ -434,10 +443,44 @@ class Parser: if not ex.file: ex.file = filename raise ex + except IOError as ex: + raise DsdlException('IO error: %s' % str(ex), file=filename) from ex except Exception as ex: raise DsdlException('Internal error: %s' % str(ex), file=filename) from ex +def parse_namespaces(directory_list): + def walk(): + import fnmatch + from functools import partial + def on_walk_error(directory, ex): + raise DsdlException('OS error in [%s]: %s' % (directory, str(ex))) from ex + for directory in directory_list: + walker = os.walk(directory, onerror=partial(on_walk_error, directory), followlinks=True) + for root, _dirnames, filenames in walker: + for filename in fnmatch.filter(filenames, '*.uavcan'): + filename = os.path.join(root, filename) + yield filename + + all_default_dtid = {} # (kind, dtid) : filename + def ensure_unique_dtid(t, filename): + if t.default_dtid is None: + return + key = t.kind, t.default_dtid + if key in all_default_dtid: + first = pretty_filename(all_default_dtid[key]) + second = pretty_filename(filename) + _error('Default data type ID collision: [%s] [%s]', first, second) + all_default_dtid[key] = filename + + parser = Parser(directory_list) + output_types = [] + for filename in walk(): + t = parser.parse(filename) + ensure_unique_dtid(t, filename) + output_types.append(t) + return output_types + if __name__ == '__main__': import sys logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(levelname)s: %(message)s') @@ -446,12 +489,14 @@ if __name__ == '__main__': self_directory = os.path.dirname(__file__) test_dir = os.path.join(self_directory, '..', '..', 'dsdl_test_data') test_dir = os.path.normpath(test_dir) - parser = Parser([os.path.join(test_dir, 'root_a'), os.path.join(test_dir, 'root_b')]) - parser.log.setLevel(logging.DEBUG) - t = parser.parse(os.path.join(test_dir, 'root_a', 'ns1', 'ns9', '425.BeginFirmwareUpdate.uavcan')) +# parser = Parser([os.path.join(test_dir, 'root_a'), os.path.join(test_dir, 'root_b')]) +# t = parser.parse(os.path.join(test_dir, 'root_a', 'ns1', 'ns9', '425.BeginFirmwareUpdate.uavcan')) + t = parse_namespaces([os.path.join(test_dir, 'root_a'), os.path.join(test_dir, 'root_b')]) + print(len(t)) else: - search_dirs = sys.argv[1:-1] - filename = sys.argv[-1] - parser = Parser(search_dirs) - parser.log.setLevel(logging.DEBUG) - t = parser.parse(filename) + t = parse_namespaces(sys.argv[1:]) + print(len(t)) +# search_dirs = sys.argv[1:-1] +# filename = sys.argv[-1] +# parser = Parser(search_dirs) +# t = parser.parse(filename)