Support size and single-value constraints on some types.
authorKim Grasman <kim.grasman@gmail.com>
Fri, 25 Jul 2014 15:03:37 +0000 (17:03 +0200)
committerKim Grasman <kim.grasman@gmail.com>
Fri, 25 Jul 2014 15:03:37 +0000 (17:03 +0200)
.gitignore
asn1ate/parser.py
asn1ate/pyasn1gen.py
asn1ate/sema.py
testdata/sequenceof.asn
testdata/setof.asn
testdata/single_value_constraint.asn [new file with mode: 0644]
testdata/size_constraint.asn [new file with mode: 0644]
testdata/valuerange_constraint.asn [new file with mode: 0644]

index ca5ae9e..2353ea3 100644 (file)
@@ -15,6 +15,8 @@ dist/
 \r
 # Project files\r
 .ropeproject\r
+env/\r
+dbg/\r
 \r
 # TODO\r
 todo.txt
\ No newline at end of file
index f9ef0be..dac318b 100644 (file)
@@ -178,9 +178,9 @@ def _build_asn1_grammar():
     # values
     # BUG: These are badly specified and cause the grammar to break if used generally.
     # todo: consider more literals from 16.9
-    real_value = Regex(r'-?\d+(\.\d*)?') # todo: this doesn't really follow the spec
+    real_value = Regex(r'-?\d+(\.\d*)?')  # todo: this doesn't really follow the spec
     boolean_value = TRUE | FALSE
-    bitstring_value = bstring | hstring     # todo: consider more forms from 21.9
+    bitstring_value = bstring | hstring  # todo: consider more forms from 21.9
     integer_value = signed_number
     null_value = NULL
     cstring_value = dblQuotedString
@@ -233,10 +233,12 @@ def _build_asn1_grammar():
 
     # constraints
     # todo: consider the full subtype and general constraint syntax described in 45.*
-    # but for now, just implement a simple integer value range.
-    value_range_constraint = (signed_number | referenced_value | MIN) + Suppress('..') + (signed_number | referenced_value | MAX)
-    size_constraint = Optional(Suppress('(')) + Suppress(SIZE) + Suppress('(') + value_range_constraint + Suppress(')') + Optional(Suppress(')'))
-    constraint = Suppress('(') + value_range_constraint + Suppress(')')
+    lower_bound = (signed_number | referenced_value | MIN)
+    upper_bound = (signed_number | referenced_value | MAX)
+    single_value_constraint = Suppress('(') + value + Suppress(')')
+    value_range_constraint = Suppress('(') + lower_bound + Suppress('..') + upper_bound + Suppress(')')
+    # TODO: Include contained subtype constraint here if we ever implement it.
+    size_constraint = Optional(Suppress('(')) + Suppress(SIZE) + (single_value_constraint | value_range_constraint) + Optional(Suppress(')'))
 
     # TODO: consider exception syntax from 24.1
     extension_marker = Unique(ELLIPSIS)
@@ -259,26 +261,27 @@ def _build_asn1_grammar():
     setof_type = Suppress(SET) + Optional(size_constraint) + Suppress(OF) + (type_ | named_type)
     choice_type = CHOICE + braced_list(named_type | extension_marker)
     enumerated_type = ENUMERATED + braced_list(enumeration | extension_marker)
-    bitstring_type = BIT_STRING + braced_list(named_number)
-    plain_integer_type = INTEGER
-    restricted_integer_type = INTEGER + braced_list(named_number)
+    bitstring_type = BIT_STRING + braced_list(named_number) + Optional(single_value_constraint | size_constraint)
+    plain_integer_type = INTEGER + Optional(single_value_constraint)
+    restricted_integer_type = INTEGER + braced_list(named_number) + Optional(single_value_constraint)
     boolean_type = BOOLEAN
     real_type = REAL
     null_type = NULL
     object_identifier_type = OBJECT_IDENTIFIER
-    octetstring_type = OCTET_STRING
+    octetstring_type = OCTET_STRING + Optional(size_constraint)
     unrestricted_characterstring_type = CHARACTER_STRING
     restricted_characterstring_type = BMPString | GeneralString | \
                                       GraphicString | IA5String | \
                                       ISO646String | NumericString | \
                                       PrintableString | TeletexString | \
                                       T61String | UniversalString | \
-                                      UTF8String | VideotexString | VisibleString
-    characterstring_type = restricted_characterstring_type | unrestricted_characterstring_type
+                                      UTF8String | VideotexString | \
+                                      VisibleString
+    characterstring_type = (restricted_characterstring_type | unrestricted_characterstring_type) + Optional(size_constraint)
     useful_type = GeneralizedTime | UTCTime | ObjectDescriptor
 
     # todo: consider other builtins from 16.2
-    simple_type = (boolean_type | null_type | octetstring_type | characterstring_type | real_type | plain_integer_type | object_identifier_type | useful_type) + Optional(constraint)
+    simple_type = (boolean_type | null_type | octetstring_type | characterstring_type | real_type | plain_integer_type | object_identifier_type | useful_type) + Optional(value_range_constraint)
     constructed_type = choice_type | sequence_type | set_type
     value_list_type = restricted_integer_type | enumerated_type
     builtin_type = value_list_type | tagged_type | simple_type | constructed_type | sequenceof_type | setof_type | bitstring_type
@@ -331,8 +334,9 @@ def _build_asn1_grammar():
     setof_type.setParseAction(annotate('SetOfType'))
     named_number.setParseAction(annotate('NamedValue'))
     named_nonumber.setParseAction(annotate('NamedValue'))
-    constraint.setParseAction(annotate('Constraint'))
+    single_value_constraint.setParseAction(annotate('SingleValueConstraint'))
     size_constraint.setParseAction(annotate('SizeConstraint'))
+    value_range_constraint.setParseAction(annotate('ValueRangeConstraint'))
     component_type.setParseAction(annotate('ComponentType'))
     component_type_optional.setParseAction(annotate('ComponentTypeOptional'))
     component_type_default.setParseAction(annotate('ComponentTypeDefault'))
index 6b1197f..73579ac 100644 (file)
@@ -189,7 +189,7 @@ class Pyasn1Backend(object):
 
     def defn_simple_type(self, class_name, t):
         if t.constraint:
-            return '%s.subtypeSpec = constraint.ValueRangeConstraint(%s, %s)' % (class_name, t.constraint.min_value, t.constraint.max_value)
+            return '%s.subtypeSpec = %s' % (class_name, self.build_constraint_expr(t.constraint))
 
         return None
 
@@ -259,7 +259,7 @@ class Pyasn1Backend(object):
     def inline_simple_type(self, t):
         type_expr = _translate_type(t.type_name) + '()'
         if t.constraint:
-            type_expr += '.subtype(subtypeSpec=constraint.ValueRangeConstraint(%s, %s))' % (t.constraint.min_value, t.constraint.max_value)
+            type_expr += '.subtype(subtypeSpec=%s)' % self.build_constraint_expr(t.constraint)
 
         return type_expr
 
@@ -311,6 +311,26 @@ class Pyasn1Backend(object):
 
         return 'tag.Tag(%s, %s, %s)' % (context, tag_format, tag_def.class_number)
 
+    def build_constraint_expr(self, constraint):
+        def unpack_size_constraint(nested):
+            if isinstance(nested, SingleValueConstraint):
+                return nested.value, nested.value
+            elif isinstance(nested, ValueRangeConstraint):
+                return nested.min_value, nested.max_value
+            else:
+                assert False, "Unrecognized nested size constraint type: %s" % type(constraint.nested).__name__
+
+        if isinstance(constraint, SingleValueConstraint):
+            return 'constraint.SingleValueConstraint(%s)' % (constraint.value)
+        elif isinstance(constraint, SizeConstraint):
+            min_value, max_value = unpack_size_constraint(constraint.nested)
+            return 'constraint.ValueRangeConstraint(%s, %s)' % (min_value, max_value)
+        elif isinstance (constraint, ValueRangeConstraint):
+            return 'constraint.ValueRangeConstraint(%s, %s)' % (constraint.min_value,
+                                                                constraint.max_value)
+        else:
+            assert False, "Unrecognized constraint type: %s" % type(constraint).__name__
+
     def inline_component_type(self, t):
         if t.components_of_type:
             # COMPONENTS OF works like a literal include, so just
index e9999e7..98a2941 100644 (file)
@@ -426,8 +426,9 @@ class SimpleType(SemaNode):
     def __init__(self, elements):
         self.constraint = None
         self.type_name = elements[0]
-        if len(elements) > 1 and elements[1].ty == 'Constraint':
-            self.constraint = Constraint(elements[1].elements)
+        if len(elements) > 1:
+            _assert_annotated_token(elements[1])
+            self.constraint = _create_sema_node(elements[1])
 
     def __str__(self):
         if self.constraint is None:
@@ -478,12 +479,20 @@ class ReferencedValue(SemaNode):
     __repr__ = __str__
 
 
-class Constraint(SemaNode):
+class SingleValueConstraint(SemaNode):
     def __init__(self, elements):
-        min_value, max_value = elements
+        self.value = _maybe_create_sema_node(elements[0])
+
+    def __str__(self):
+        return '(%s)' % self.value
+
+    __repr__ = __str__
 
-        self.min_value = _maybe_create_sema_node(min_value)
-        self.max_value = _maybe_create_sema_node(max_value)
+
+class ValueRangeConstraint(SemaNode):
+    def __init__(self, elements):
+        self.min_value = _maybe_create_sema_node(elements[0])
+        self.max_value = _maybe_create_sema_node(elements[1])
 
     def __str__(self):
         return '(%s..%s)' % (self.min_value, self.max_value)
@@ -491,10 +500,14 @@ class Constraint(SemaNode):
     __repr__ = __str__
 
 
-class SizeConstraint(Constraint):
-    """ Size constraints have the same form as any value range constraints."""
+class SizeConstraint(SemaNode):
+    """ Size constraints nest single-value or range constraints to denote valid sizes. """
+    def __init__(self, elements):
+        self.nested = _create_sema_node(elements[0])
+        assert isinstance(self.nested, (ValueRangeConstraint, SingleValueConstraint))
+
     def __str__(self):
-        return 'SIZE(%s..%s)' % (self.min_value, self.max_value)
+        return 'SIZE%s' % (self.nested)
 
     __repr__ = __str__
 
@@ -564,6 +577,9 @@ class NamedType(SemaNode):
 class ValueListType(SemaNode):
     def __init__(self, elements):
         self.type_name = elements[0]
+        self.named_values = None
+        self.constraint = None
+
         if len(elements) > 1:
             self.named_values = [_create_sema_node(token) for token in elements[1]]
             for idx, n in enumerate(self.named_values):
@@ -572,15 +588,20 @@ class ValueListType(SemaNode):
                         n.value = str(0)
                     else:
                         n.value = str(int(self.named_values[idx-1].value) + 1)
-        else:
-            self.named_values = None
+
+        if len(elements) > 2:
+            self.constraint = _create_sema_node(elements[2])
 
     def __str__(self):
+        named_value_list = ''
+        constraint = ''
         if self.named_values:
-            named_value_list = ', '.join(map(str, self.named_values))
-            return '%s { %s }' % (self.type_name, named_value_list)
-        else:
-            return '%s' % self.type_name
+            named_value_list = ' { %s }' % ', '.join(map(str, self.named_values))
+
+        if self.constraint:
+            constraint = ' %s' % self.constraint
+
+        return '%s%s%s' % (self.type_name, named_value_list, constraint)
 
     __repr__ = __str__
 
@@ -744,8 +765,12 @@ def _create_sema_node(token):
         return SetOfType(token.elements)
     elif token.ty == 'ExtensionMarker':
         return ExtensionMarker(token.elements)
+    elif token.ty == 'SingleValueConstraint':
+        return SingleValueConstraint(token.elements)
     elif token.ty == 'SizeConstraint':
         return SizeConstraint(token.elements)
+    elif token.ty == 'ValueRangeConstraint':
+        return ValueRangeConstraint(token.elements)
     elif token.ty == 'ObjectIdentifierValue':
         return ObjectIdentifierValue(token.elements)
     elif token.ty == 'NameForm':
index f6e50c5..2aa64c5 100644 (file)
@@ -10,7 +10,5 @@ BEGIN
     quux SEQUENCE OF Foo      -- user-defined type
   }
 
-  -- With size constraint
-  Sized ::= SEQUENCE SIZE(1..100) OF INTEGER
-  SizedWithParens ::= SEQUENCE (SIZE(1..100)) OF INTEGER
+  -- Constraints handled in size_constraint.asn
 END
index e760c99..07f70bf 100644 (file)
@@ -10,7 +10,5 @@ BEGIN
     quux SET OF Foo     -- user-defined type
   }
 
-  -- With size constraint
-  Sized ::= SET SIZE(1..100) OF INTEGER
-  SizedWithParens ::= SET (SIZE(1..100)) OF INTEGER
+  -- Constraints handled in size_constraint.asn
 END
diff --git a/testdata/single_value_constraint.asn b/testdata/single_value_constraint.asn
new file mode 100644 (file)
index 0000000..ebad396
--- /dev/null
@@ -0,0 +1,13 @@
+TEST DEFINITIONS ::=\r
+BEGIN\r
+  value INTEGER ::= 128\r
+\r
+  -- Single-value constraints apply to all types.\r
+  ConstrainedInteger1 ::= INTEGER (50)\r
+  ConstrainedInteger2 ::= INTEGER (value)\r
+  ConstrainedInteger3 ::= INTEGER { one(10), two(20) } (10)\r
+\r
+  ConstrainedBitString1 ::= BIT STRING { one(1), two(2) } (1)\r
+\r
+  -- TODO: Add tests for the rest of the types as we need them.\r
+END\r
diff --git a/testdata/size_constraint.asn b/testdata/size_constraint.asn
new file mode 100644 (file)
index 0000000..7e4c84d
--- /dev/null
@@ -0,0 +1,25 @@
+TEST DEFINITIONS ::=\r
+BEGIN\r
+  min INTEGER ::= 128\r
+  max INTEGER ::= 256\r
+\r
+  -- Size constraints apply to the following types.\r
+  ConstrainedOctetStr1 ::= OCTET STRING (SIZE(32))\r
+  ConstrainedOctetStr2 ::= OCTET STRING (SIZE(16..32))\r
+  ConstrainedOctetStr3 ::= OCTET STRING (SIZE(min..max))\r
+\r
+  ConstrainedBitString1 ::= BIT STRING { one(1), two(2) } (SIZE(6))\r
+\r
+  -- TypeWithConstraint syntax for SET OF and SEQUENCE OF\r
+  SizedSetOf ::= SET SIZE(1..100) OF INTEGER\r
+  SizedSetOfWithParens ::= SET (SIZE(1..100)) OF INTEGER\r
+\r
+  SizedSequenceOf ::= SEQUENCE SIZE(1..100) OF INTEGER\r
+  SizedSequenceOfWithParens ::= SEQUENCE (SIZE(1..100)) OF INTEGER\r
+\r
+  \r
+  -- TODO: MIN/MAX is not yet translated in codegen.\r
+  -- ConstrainedOctetStr4 ::= OCTET STRING (SIZE(MIN..MAX))\r
+\r
+  -- TODO: Add tests for REAL types once we have them in place.\r
+END\r
diff --git a/testdata/valuerange_constraint.asn b/testdata/valuerange_constraint.asn
new file mode 100644 (file)
index 0000000..e63ac5e
--- /dev/null
@@ -0,0 +1,12 @@
+TEST DEFINITIONS ::=\r
+BEGIN\r
+  -- Value range constraints apply to the following types.\r
+  min INTEGER ::= 128\r
+  max INTEGER ::= 256\r
+  ConstraintedInteger1 ::= INTEGER(1..100)\r
+  ConstraintedInteger2 ::= INTEGER(min..max)\r
+  -- This is not yet translated in codegen\r
+  -- ConstraintedInteger3 ::= INTEGER(MIN..MAX)\r
+\r
+  -- TODO: Add tests for REAL types once we have them in place.\r
+END\r