pyuavcan docstrings

This commit is contained in:
Pavel Kirienko 2014-07-11 15:28:36 +04:00
parent dc347f658b
commit 4fd4e217e1
5 changed files with 131 additions and 0 deletions

View File

@ -2,4 +2,9 @@
# Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
#
'''
Python UAVCAN package.
Currently it implements only a DSDL parser (refer to the submodule 'dsdl').
'''
from . import dsdl

View File

@ -2,6 +2,10 @@
# Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
#
'''
This module implements a fully compliant UAVCAN DSDL parser.
'''
from .parser import Parser, parse_namespaces, \
Type, PrimitiveType, ArrayType, CompoundType, \
Attribute, Field, Constant

View File

@ -6,12 +6,19 @@ from __future__ import division, absolute_import, print_function, unicode_litera
import os
class DsdlException(Exception):
'''
This exception is raised in case of a parser failure.
Fields:
file Source file path where the error has occurred. Optional, will be None if unknown.
line Source file line number where the error has occurred. Optional, will be None if unknown.
'''
def __init__(self, text, file=None, line=None):
Exception.__init__(self, text)
self.file = file
self.line = line
def __str__(self):
'''Returns nicely formatted error string in GCC-like format (can be parsed by e.g. Eclipse error parser)'''
if self.file and self.line:
return '%s:%d: %s' % (pretty_filename(self.file), self.line, Exception.__str__(self))
if self.file:
@ -20,6 +27,7 @@ class DsdlException(Exception):
def pretty_filename(filename):
'''Returns a nice human readable path to 'filename'.'''
a = os.path.abspath(filename)
r = os.path.relpath(filename)
return a if len(a) < len(r) else r

View File

@ -26,6 +26,12 @@ DATA_TYPE_ID_MAX = 1023
MAX_DATA_STRUCT_LEN_BYTES = 439
class Type:
'''
Common type description. The specialized type description classes inherit from this one.
Fields:
full_name Full type name string, e.g. "uavcan.protocol.NodeStatus"
category Any CATEGORY_*
'''
CATEGORY_PRIMITIVE = 0
CATEGORY_ARRAY = 1
CATEGORY_COMPOUND = 2
@ -40,6 +46,14 @@ class Type:
__repr__ = __str__
class PrimitiveType(Type):
'''
Primitive type description, e.g. bool or float16.
Fields:
kind Any KIND_*
bitlen Bit length, 1 to 64
cast_mode Any CAST_MODE_*
value_range Tuple containing min and max values: (min, max)
'''
KIND_BOOLEAN = 0
KIND_UNSIGNED_INT = 1
KIND_SIGNED_INT = 2
@ -61,6 +75,7 @@ class PrimitiveType(Type):
}[self.kind](bitlen)
def get_normalized_definition(self):
'''Please refer to the specification for details about normalized definitions.'''
cast_mode = 'saturated' if self.cast_mode == PrimitiveType.CAST_MODE_SATURATED else 'truncated'
primary_type = {
PrimitiveType.KIND_BOOLEAN: 'bool',
@ -71,14 +86,23 @@ class PrimitiveType(Type):
return cast_mode + ' ' + primary_type
def validate_value_range(self, value):
'''Checks value range, throws DsdlException if the value cannot be represented by this type.'''
low, high = self.value_range
if not low <= value <= high:
error('Value [%s] is out of range %s', value, self.value_range)
def get_max_bitlen(self):
'''Returns type bit length.'''
return self.bitlen
class ArrayType(Type):
'''
Array type description, e.g. float32[8], uint12[<34].
Fields:
value_type Description of the array value type; the type of this field inherits Type, e.g. PrimitiveType
mode Any MODE_*
max_size Maximum number of elements in the array
'''
MODE_STATIC = 0
MODE_DYNAMIC = 1
@ -89,10 +113,12 @@ class ArrayType(Type):
Type.__init__(self, self.get_normalized_definition(), Type.CATEGORY_ARRAY)
def get_normalized_definition(self):
'''Please refer to the specification for details about normalized definitions.'''
typedef = self.value_type.get_normalized_definition()
return ('%s[<=%d]' if self.mode == ArrayType.MODE_DYNAMIC else '%s[%d]') % (typedef, self.max_size)
def get_max_bitlen(self):
'''Returns total maximum bit length of the array, including length field if applicable.'''
payload_max_bitlen = self.max_size * self.value_type.get_max_bitlen()
return {
self.MODE_DYNAMIC: payload_max_bitlen + self.max_size.bit_length(),
@ -100,6 +126,31 @@ class ArrayType(Type):
}[self.mode]
class CompoundType(Type):
'''
Compound type description, e.g. uavcan.protocol.NodeStatus.
Fields:
source_file Path to the DSDL definition file for this type
default_dtid Default Data Type ID, if specified, None otherwise
kind Any KIND_*
source_text Raw DSDL definition text (as is, with comments and the original formatting)
Fields if kind == KIND_SERVICE:
request_fields Request struct field list, the type of each element is Field
response_fields Response struct field list
request_constants Request struct constant list, the type of each element is Constant
response_constants Response struct constant list
Fields if kind == KIND_MESSAGE:
fields Field list, the type of each element is Field
constants Constant list, the type of each element is Constant
Extra methods if kind == KIND_SERVICE:
get_max_bitlen_request() Returns maximum total bit length for the serialized request struct
get_max_bitlen_response() Same for the response struct
Extra methods if kind == KIND_MESSAGE:
get_max_bitlen() Returns maximum total bit length for the serialized struct
'''
KIND_SERVICE = 0
KIND_MESSAGE = 1
@ -125,6 +176,10 @@ class CompoundType(Type):
error('Compound type of unknown kind [%s]', kind)
def get_dsdl_signature_source_definition(self):
'''
Returns normalized DSDL definition text.
Please refer to the specification for details about normalized DSDL definitions.
'''
txt = StringIO()
txt.write(self.full_name + '\n')
adjoin = lambda attrs: txt.write('\n'.join(x.get_normalized_definition() for x in attrs) + '\n')
@ -143,13 +198,24 @@ class CompoundType(Type):
return txt.getvalue().strip().replace('\n\n\n', '\n').replace('\n\n', '\n')
def get_dsdl_signature(self):
'''
Computes DSDL signature of this type.
Please refer to the specification for details about signatures.
'''
return compute_signature(self.get_dsdl_signature_source_definition())
def get_normalized_definition(self):
'''Returns full type name string, e.g. "uavcan.protocol.NodeStatus"'''
return self.full_name
class Attribute:
'''
Base class of an attribute description.
Fields:
type Attribute type description, the type of this field inherits the class Type, e.g. PrimitiveType
name Attribute name string
'''
def __init__(self, type, name): # @ReservedAssignment
self.type = type
self.name = name
@ -160,10 +226,21 @@ class Attribute:
__repr__ = __str__
class Field(Attribute):
'''
Field description.
Does not add new fields to Attribute.
'''
def get_normalized_definition(self):
return '%s %s' % (self.type.get_normalized_definition(), self.name)
class Constant(Attribute):
'''
Constant description.
Fields:
init_expression Constant initialization expression string, e.g. "2+2" or "'\x66'"
value Computed result of the initialization expression in the final type (e.g. int, float)
string_value Computed result of the initialization expression as string
'''
def __init__(self, type, name, init_expression, value): # @ReservedAssignment
Attribute.__init__(self, type, name)
self.init_expression = init_expression
@ -177,6 +254,9 @@ class Constant(Attribute):
class Parser:
'''
DSDL parser logic. Do not use this class directly; use the helper function instead.
'''
LOGGER_NAME = 'dsdl_parser'
def __init__(self, search_dirs):
@ -500,6 +580,28 @@ def validate_data_struct_len(t):
def parse_namespaces(source_dirs, search_dirs=None):
'''
Use only this function to parse DSDL definitions.
This function takes a list of root namespace directories (containing DSDL definition files to parse) and an
optional list of search directories (containing DSDL definition files that can be referenced from the types
that are going to be parsed).
Returns the list of parsed type definitions, where type of each element is CompoundType.
Args:
source_dirs List of root namespace directories to parse.
search_dirs List of root namespace directories with referenced types (optional). This list is
automaitcally extended with source_dirs.
Example:
>>> import pyuavcan
>>> a = pyuavcan.dsdl.parse_namespaces(['../dsdl/uavcan'])
>>> len(a)
77
>>> a[0]
uavcan.Timestamp
>>> a[0].fields
[truncated uint48 husec]
>>> a[0].constants
[saturated uint48 UNKNOWN = 0, saturated uint48 USEC_PER_LSB = 100]
'''
def walk():
import fnmatch
from functools import partial

View File

@ -16,16 +16,23 @@ from __future__ import division, absolute_import, print_function, unicode_litera
# Check: 0x62EC59E3F1A4F00A
#
class Signature:
'''
This class implements the UAVCAN DSDL signature hash function. Please refer to the specification for details.
'''
MASK64 = 0xFFFFFFFFFFFFFFFF
POLY = 0x42F0E1EBA9EA3693
def __init__(self, extend_from=None):
'''
extend_from Initial value (optional)
'''
if extend_from is not None:
self._crc = (int(extend_from) & Signature.MASK64) ^ Signature.MASK64
else:
self._crc = Signature.MASK64
def add(self, data_bytes):
'''Feed ASCII string or bytes to the signature function'''
try:
if isinstance(data_bytes, basestring): # Python 2.7 compatibility
data_bytes = map(ord, data_bytes)
@ -42,10 +49,15 @@ class Signature:
self._crc <<= 1
def get_value(self):
'''Returns integer signature value'''
return (self._crc & Signature.MASK64) ^ Signature.MASK64
def compute_signature(data):
'''
One-shot signature computation for ASCII string or bytes.
Returns integer signture value.
'''
s = Signature()
s.add(data)
return s.get_value()