Fix issue #8 -- Cyclic references.
authorKim Grasman <kim.grasman@gmail.com>
Tue, 22 Jul 2014 20:38:44 +0000 (22:38 +0200)
committerKim Grasman <kim.grasman@gmail.com>
Tue, 22 Jul 2014 20:38:44 +0000 (22:38 +0200)
We can now have cyclic references between assignments and constructed type
members. This required extensive surgery to properly handle cycles in
dependency sorting and to generate assignment code in two passes.

asn1ate/parser.py
asn1ate/pyasn1gen.py
asn1ate/sema.py

index ff1d531..f20987b 100644 (file)
@@ -186,7 +186,8 @@ def _build_asn1_grammar():
     cstring_value = dblQuotedString
 
     builtin_value = boolean_value | bitstring_value | real_value | integer_value | null_value | cstring_value
-    defined_value = valuereference # todo: more options from 13.1
+    defined_value = Unique(valuereference) # todo: more options from 13.1
+    referenced_value = Unique(defined_value) # todo: more options from 16.11
 
     # object identifier value
     name_form = Unique(identifier)
@@ -198,7 +199,7 @@ def _build_asn1_grammar():
                               (objid_components_list | (defined_value + objid_components_list)) + \
                               Suppress('}')
 
-    value = builtin_value | defined_value | object_identifier_value
+    value = builtin_value | referenced_value | object_identifier_value
 
     # definitive identifier value
     definitive_number_form = Unique(number)
@@ -220,6 +221,8 @@ def _build_asn1_grammar():
     defined_type = Unique(typereference)  # todo: consider other defined types from 13.1
     referenced_type = Unique(defined_type)  # todo: consider other ref:d types from 16.3
 
+    # values
+
     # Forward-declare these, they can only be fully defined once
     # we have all types defined. There are some circular dependencies.
     named_type = Forward()
@@ -228,7 +231,7 @@ 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 | valuereference | MIN) + Suppress('..') + (signed_number | valuereference | MAX)
+    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(')')
 
@@ -321,7 +324,6 @@ def _build_asn1_grammar():
     set_type.setParseAction(annotate('SetType'))
     value_list_type.setParseAction(annotate('ValueListType'))
     bitstring_type.setParseAction(annotate('BitStringType'))
-    referenced_type.setParseAction(annotate('ReferencedType'))
     sequenceof_type.setParseAction(annotate('SequenceOfType'))
     setof_type.setParseAction(annotate('SetOfType'))
     named_number.setParseAction(annotate('NamedValue'))
@@ -336,7 +338,6 @@ def _build_asn1_grammar():
     named_type.setParseAction(annotate('NamedType'))
     type_assignment.setParseAction(annotate('TypeAssignment'))
     value_assignment.setParseAction(annotate('ValueAssignment'))
-    valuereference.setParseAction(annotate('ValueReference'))
     module_reference.setParseAction(annotate('ModuleReference'))
     module_body.setParseAction(annotate('ModuleBody'))
     module_definition.setParseAction(annotate('ModuleDefinition'))
@@ -353,6 +354,8 @@ def _build_asn1_grammar():
     assignment_list.setParseAction(annotate('AssignmentList'))
     bstring.setParseAction(annotate('BinaryStringValue'))
     hstring.setParseAction(annotate('HexStringValue'))
+    referenced_type.setParseAction(annotate('ReferencedType'))
+    referenced_value.setParseAction(annotate('ReferencedValue'))
 
     start = OneOrMore(module_definition)
     return start
index ae8a92a..6b1197f 100644 (file)
@@ -33,59 +33,86 @@ from asn1ate.sema import *
 
 class Pyasn1Backend(object):
     """ Backend to generate pyasn1 declarations from semantic tree.
-    Generators are divided into declarations and expressions,
-    because types in pyasn1 can be declared either as class
-    definitions or inline, e.g.
-
-    # Foo ::= INTEGER
-    # Foo is a decl
-    class Foo(univ.Integer):
-        pass
-
-    # Seq ::= SEQUENCE {
-    #     foo INTEGER
-    # }
-    # Seq is a decl,
-    # univ.Integer is an expr
-    class Seq(univ.Sequence):
-        componentType = namedtype.NamedTypes(
+
+    Pyasn1 represents type assignments as class derivation, e.g.
+
+        # Foo ::= INTEGER
+        class Foo(univ.Integer):
+            pass
+
+    For constructed types, the component types are instantiated inline, e.g.
+
+        # Seq ::= SEQUENCE {
+        #     foo INTEGER
+        # }
+        class Seq(univ.Sequence):
+             componentType = namedtype.NamedTypes(
+                namedtype.NamedType('foo', univ.Integer())
+             )
+
+    (univ.Integer is not a base class here, but a value.)
+
+    To cope with circular dependencies, we define types in two passes so we'll
+    generate the above as:
+
+        class Seq(univ.Sequence):
+            pass
+
+        Seq.componentType = namedtype.NamedTypes(
             namedtype.NamedType('foo', univ.Integer())
         )
 
-    Typically, declarations can contain other declarations
-    or expressions, expressions can only contain other expressions.
+    This is nice, because we separate the introduction of a name (``Seq``) from
+    the details of what it contains, so we can build recursive definitions
+    without getting into trouble with Python's name lookup.
+
+    We call the empty class a *declaration*, and the population of its members
+    *definition*. The instantiation of univ.Integer is called an
+    *inline definition*.
+
+    The translation from ASN.1 constructs to Pyasn1 come in different flavors,
+    depending on whether they're declarations, definitions or inline
+    definitions.
+
+    Only type and value assignments generate declarations. For type assignments
+    we generate a definition once all dependent declarations are created. If the
+    type assignment involves a constructed type, it is filled with inline
+    definitions.
     """
     def __init__(self, sema_module, out_stream):
         self.sema_module = sema_module
         self.writer = pygen.PythonWriter(out_stream)
 
         self.decl_generators = {
-            ChoiceType: self.decl_constructed_type,
-            SequenceType: self.decl_constructed_type,
-            SetType: self.decl_constructed_type,
-            TaggedType: self.decl_tagged_type,
-            SimpleType: self.decl_simple_type,
-            UserDefinedType: self.decl_userdefined_type,
-            ValueListType: self.decl_value_list_type,
-            BitStringType: self.decl_bitstring_type,
-            SequenceOfType: self.decl_sequenceof_type,
-            SetOfType: self.decl_setof_type,
             TypeAssignment: self.decl_type_assignment,
             ValueAssignment: self.decl_value_assignment
         }
 
-        self.expr_generators = {
-            TaggedType: self.expr_tagged_type,
-            SimpleType: self.expr_simple_type,
-            UserDefinedType: self.expr_userdefined_type,
-            ComponentType: self.expr_component_type,
-            NamedType: self.expr_named_type,
-            SequenceOfType: self.expr_sequenceof_type,
-            SetOfType: self.expr_setof_type,
-            ValueListType: self.expr_value_list_type,
-            ChoiceType: self.expr_constructed_type,
-            SequenceType: self.expr_constructed_type,
-            SetType: self.expr_constructed_type,
+        self.defn_generators = {
+            ChoiceType: self.defn_constructed_type,
+            SequenceType: self.defn_constructed_type,
+            SetType: self.defn_constructed_type,
+            TaggedType: self.defn_tagged_type,
+            SimpleType: self.defn_simple_type,
+            ReferencedType: self.defn_referenced_type,
+            ValueListType: self.defn_value_list_type,
+            BitStringType: self.defn_bitstring_type,
+            SequenceOfType: self.defn_sequenceof_type,
+            SetOfType: self.defn_setof_type,
+        }
+
+        self.inline_generators = {
+            TaggedType: self.inline_tagged_type,
+            SimpleType: self.inline_simple_type,
+            ReferencedType: self.inline_referenced_type,
+            ComponentType: self.inline_component_type,
+            NamedType: self.inline_named_type,
+            SequenceOfType: self.inline_sequenceof_type,
+            SetOfType: self.inline_setof_type,
+            ValueListType: self.inline_value_list_type,
+            ChoiceType: self.inline_constructed_type,
+            SequenceType: self.inline_constructed_type,
+            SetType: self.inline_constructed_type,
         }
 
     def generate_code(self):
@@ -94,22 +121,42 @@ class Pyasn1Backend(object):
 
         # TODO: Only generate _OID if sema_module
         # contains object identifier values.
-        self.generate_OID()
+        self.writer.write_block(self.generate_OID())
         self.writer.write_blanks(2)
 
-        assignments = topological_sort(self.sema_module.assignments)
-        for assignment in assignments:
-            self.writer.write_block(self.generate_decl(assignment))
-            self.writer.write_blanks(2)
+        assignment_components = dependency_sort(self.sema_module.assignments)
+        for component in assignment_components:
+            for assignment in component:
+                self.writer.write_block(self.generate_decl(assignment))
+                self.writer.write_blanks(2)
 
-    def generate_expr(self, t):
-        generator = self.expr_generators[type(t)]
-        return generator(t)
+            for assignment in component:
+                details = self.generate_definition(assignment)
+                if details:
+                    self.writer.write_block(details)
+                    self.writer.write_blanks(2)
+
+    def generate_definition(self, assignment):
+        assert isinstance(assignment, (ValueAssignment, TypeAssignment))
+
+        if isinstance(assignment, ValueAssignment):
+            return None  # Nothing to do here.
+
+        assigned_type, type_decl = assignment.type_name, assignment.type_decl
+        return self.generate_defn(assigned_type, type_decl)
 
     def generate_decl(self, t):
         generator = self.decl_generators[type(t)]
         return generator(t)
 
+    def generate_expr(self, t):
+        generator = self.inline_generators[type(t)]
+        return generator(t)
+
+    def generate_defn(self, class_name, t):
+        generator = self.defn_generators[type(t)]
+        return generator(class_name, t)
+
     def decl_type_assignment(self, assignment):
         fragment = self.writer.get_fragment()
 
@@ -117,46 +164,109 @@ class Pyasn1Backend(object):
 
         base_type = _translate_type(type_decl.type_name)
         fragment.write_line('class %s(%s):' % (assigned_type, base_type))
-
         fragment.push_indent()
-        fragment.write_block(self.generate_decl(type_decl))
+        fragment.write_line('pass')
         fragment.pop_indent()
 
         return str(fragment)
 
-    def expr_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)
+    def decl_value_assignment(self, assignment):
+        assigned_value, type_decl, value = assignment.value_name, assignment.type_decl, assignment.value
 
-        return type_expr
+        if isinstance(value, ObjectIdentifierValue):
+            value_constructor = self.build_object_identifier_value(value)
+        elif isinstance(value, BinaryStringValue):
+            value_type = _translate_type(type_decl.type_name)
+            value_constructor = '%s(binValue=\'%s\')' % (value_type, value.value)
+        elif isinstance(value, HexStringValue):
+            value_type = _translate_type(type_decl.type_name)
+            value_constructor = '%s(hexValue=\'%s\')' % (value_type, value.value)
+        else:
+            value_type = _translate_type(type_decl.type_name)
+            value_constructor = '%s(%s)' % (value_type, value)
 
-    def decl_simple_type(self, t):
+        return '%s = %s' % (assigned_value, value_constructor)
+
+    def defn_simple_type(self, class_name, t):
         if t.constraint:
-            return 'subtypeSpec = constraint.ValueRangeConstraint(%s, %s)' % (t.constraint.min_value, t.constraint.max_value)
-        else:
-            return 'pass'
+            return '%s.subtypeSpec = constraint.ValueRangeConstraint(%s, %s)' % (class_name, t.constraint.min_value, t.constraint.max_value)
 
-    def expr_userdefined_type(self, t):
-        return t.type_name + '()'
+        return None
 
-    def decl_userdefined_type(self, t):
-        return 'pass'
+    def defn_referenced_type(self, class_name, t):
+        return None
 
-    def decl_constructed_type(self, t):
+    def defn_constructed_type(self, class_name, t):
         fragment = self.writer.get_fragment()
 
-        fragment.write_line('componentType = namedtype.NamedTypes(')
-
+        fragment.write_line('%s.componentType = namedtype.NamedTypes(' % class_name)
         fragment.push_indent()
-        fragment.write_block(self.expr_component_types(t.components))
+        fragment.write_block(self.inline_component_types(t.components))
         fragment.pop_indent()
-
         fragment.write_line(')')
 
         return str(fragment)
 
-    def expr_constructed_type(self, t):
+    def defn_tagged_type(self, class_name, t):
+        fragment = self.writer.get_fragment()
+
+        tag_type = 'tagImplicitly' if t.implicit else 'tagExplicitly'
+        base_type = _translate_type(t.type_decl.type_name)
+
+        fragment.write_line('%s.tagSet = %s.tagSet.%s(%s)' % (class_name, base_type, tag_type, self.build_tag_expr(t)))
+        nested_dfn = self.generate_defn(class_name, t.type_decl)
+        if nested_dfn:
+            fragment.write_line(nested_dfn)
+
+        return str(fragment)
+
+    def defn_value_list_type(self, class_name, t):
+        if t.named_values:
+            fragment = self.writer.get_fragment()
+            fragment.write_line('%s.namedValues = namedval.NamedValues(' % class_name)
+            fragment.push_indent()
+
+            named_values = ['(\'%s\', %s)' % (v.identifier, v.value) for v in t.named_values if not isinstance(v, ExtensionMarker)]
+            fragment.write_enumeration(named_values)
+
+            fragment.pop_indent()
+            fragment.write_line(')')
+            return str(fragment)
+
+        return None
+
+    def defn_bitstring_type(self, class_name, t):
+        if t.named_bits:
+            fragment = self.writer.get_fragment()
+            fragment.write_line('%s.namedValues = namedval.NamedValues(' % class_name)
+            fragment.push_indent()
+
+            named_bit_list = list(map(lambda v: '(\'%s\', %s)' % (v.identifier, v.value), t.named_bits))
+            fragment.write_enumeration(named_bit_list)
+
+            fragment.pop_indent()
+            fragment.write_line(')')
+            return str(fragment)
+
+        return None
+
+    def defn_sequenceof_type(self, class_name, t):
+        return '%s.componentType = %s' % (class_name, self.generate_expr(t.type_decl))
+
+    def defn_setof_type(self, class_name, t):
+        return '%s.componentType = %s' % (class_name, self.generate_expr(t.type_decl))
+
+    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)
+
+        return type_expr
+
+    def inline_referenced_type(self, t):
+        return t.type_name + '()'
+
+    def inline_constructed_type(self, t):
         fragment = self.writer.get_fragment()
 
         class_name = _translate_type(t.type_name)
@@ -164,14 +274,14 @@ class Pyasn1Backend(object):
         fragment.write_line('%s(componentType=namedtype.NamedTypes(' % class_name)
 
         fragment.push_indent()
-        fragment.write_block(self.expr_component_types(t.components))
+        fragment.write_block(self.inline_component_types(t.components))
         fragment.pop_indent()
 
         fragment.write_line('))')
 
         return str(fragment)
 
-    def expr_component_types(self, components):
+    def inline_component_types(self, components):
         fragment = self.writer.get_fragment()
 
         component_exprs = []
@@ -183,23 +293,13 @@ class Pyasn1Backend(object):
 
         return str(fragment)
 
-    def expr_tagged_type(self, t):
+    def inline_tagged_type(self, t):
         tag_type = 'implicitTag' if t.implicit else 'explicitTag'
         type_expr = self.generate_expr(t.type_decl)
         type_expr += '.subtype(%s=%s)' % (tag_type, self.build_tag_expr(t))
 
         return type_expr
 
-    def decl_tagged_type(self, t):
-        fragment = self.writer.get_fragment()
-
-        tag_type = 'tagImplicitly' if t.implicit else 'tagExplicitly'
-        base_type = _translate_type(t.type_decl.type_name)
-        fragment.write_line('tagSet = %s.tagSet.%s(%s)' % (base_type, tag_type, self.build_tag_expr(t)))
-        fragment.write_line(self.generate_decl(t.type_decl))  # possibly 'pass'. but that's OK in a decl
-
-        return str(fragment)
-
     def build_tag_expr(self, tag_def):
         context = _translate_tag_class(tag_def.class_name)
 
@@ -211,14 +311,14 @@ class Pyasn1Backend(object):
 
         return 'tag.Tag(%s, %s, %s)' % (context, tag_format, tag_def.class_number)
 
-    def expr_component_type(self, t):
+    def inline_component_type(self, t):
         if t.components_of_type:
             # COMPONENTS OF works like a literal include, so just
             # expand all components of the referenced type.
             included_type_decl = self.sema_module.resolve_type_decl(t.components_of_type)
-            included_content = self.expr_component_types(included_type_decl.components)
+            included_content = self.inline_component_types(included_type_decl.components)
 
-            # Strip trailing newline from expr_component_types
+            # Strip trailing newline from inline_component_types
             # to make the list line up
             return included_content.strip()
 
@@ -232,27 +332,10 @@ class Pyasn1Backend(object):
         else:
             return "namedtype.NamedType('%s', %s)" % (t.identifier, self.generate_expr(t.type_decl))
 
-    def expr_named_type(self, t):
+    def inline_named_type(self, t):
         return "namedtype.NamedType('%s', %s)" % (t.identifier, self.generate_expr(t.type_decl))
 
-    def decl_value_list_type(self, t):
-        fragment = self.writer.get_fragment()
-
-        if t.named_values:
-            fragment.write_line('namedValues = namedval.NamedValues(')
-            fragment.push_indent()
-
-            named_values = ['(\'%s\', %s)' % (v.identifier, v.value) for v in t.named_values if not isinstance(v, ExtensionMarker)]
-            fragment.write_enumeration(named_values)
-
-            fragment.pop_indent()
-            fragment.write_line(')')
-        else:
-            fragment.write_line('pass')
-
-        return str(fragment)
-
-    def expr_value_list_type(self, t):
+    def inline_value_list_type(self, t):
         class_name = _translate_type(t.type_name)
         if t.named_values:
             named_values = ['(\'%s\', %s)' % (v.identifier, v.value) for v in t.named_values if not isinstance(v, ExtensionMarker)]
@@ -260,52 +343,12 @@ class Pyasn1Backend(object):
         else:
             return class_name + '()'
 
-    def decl_bitstring_type(self, t):
-        fragment = self.writer.get_fragment()
-
-        if t.named_bits:
-            fragment.write_line('namedValues = namedval.NamedValues(')
-            fragment.push_indent()
-
-            named_bit_list = list(map(lambda v: '(\'%s\', %s)' % (v.identifier, v.value), t.named_bits))
-            fragment.write_enumeration(named_bit_list)
-
-            fragment.pop_indent()
-            fragment.write_line(')')
-        else:
-            fragment.write_line('pass')
-
-        return str(fragment)
-
-    def expr_sequenceof_type(self, t):
+    def inline_sequenceof_type(self, t):
         return 'univ.SequenceOf(componentType=%s)' % self.generate_expr(t.type_decl)
 
-    def decl_sequenceof_type(self, t):
-        return 'componentType = %s' % self.generate_expr(t.type_decl)
-
-    def expr_setof_type(self, t):
+    def inline_setof_type(self, t):
         return 'univ.SetOf(componentType=%s)' % self.generate_expr(t.type_decl)
 
-    def decl_setof_type(self, t):
-        return 'componentType = %s' % self.generate_expr(t.type_decl)
-
-    def decl_value_assignment(self, assignment):
-        assigned_value, type_decl, value = assignment.value_name, assignment.type_decl, assignment.value
-
-        if isinstance(value, ObjectIdentifierValue):
-            value_constructor = self.build_object_identifier_value(value)
-        elif isinstance(value, BinaryStringValue):
-            value_type = _translate_type(type_decl.type_name)
-            value_constructor = '%s(binValue=\'%s\')' % (value_type, value.value)
-        elif isinstance(value, HexStringValue):
-            value_type = _translate_type(type_decl.type_name)
-            value_constructor = '%s(hexValue=\'%s\')' % (value_type, value.value)
-        else:
-            value_type = _translate_type(type_decl.type_name)
-            value_constructor = '%s(%s)' % (value_type, value)
-
-        return '%s = %s' % (assigned_value, value_constructor)
-
     def build_object_identifier_value(self, t):
         objid_components = []
 
@@ -325,25 +368,29 @@ class Pyasn1Backend(object):
         return '_OID(%s)' % ', '.join(objid_components)
 
     def generate_OID(self):
-        self.writer.write_line('def _OID(*components):')
-        self.writer.push_indent()
-        self.writer.write_line('output = []')
-        self.writer.write_line('for x in tuple(components):')
-        self.writer.push_indent()
-        self.writer.write_line('if isinstance(x, univ.ObjectIdentifier):')
-        self.writer.push_indent()
-        self.writer.write_line('output.extend(list(x))')
-        self.writer.pop_indent()
-        self.writer.write_line('else:')
-        self.writer.push_indent()
-        self.writer.write_line('output.append(int(x))')
-        self.writer.pop_indent()
-        self.writer.pop_indent()
-        self.writer.write_blanks(1)
-        self.writer.write_line('return univ.ObjectIdentifier(output)')
-        self.writer.pop_indent()
-
-        self.writer.pop_indent()
+        fragment = self.writer.get_fragment()
+
+        fragment.write_line('def _OID(*components):')
+        fragment.push_indent()
+        fragment.write_line('output = []')
+        fragment.write_line('for x in tuple(components):')
+        fragment.push_indent()
+        fragment.write_line('if isinstance(x, univ.ObjectIdentifier):')
+        fragment.push_indent()
+        fragment.write_line('output.extend(list(x))')
+        fragment.pop_indent()
+        fragment.write_line('else:')
+        fragment.push_indent()
+        fragment.write_line('output.append(int(x))')
+        fragment.pop_indent()
+        fragment.pop_indent()
+        fragment.write_blanks(1)
+        fragment.write_line('return univ.ObjectIdentifier(output)')
+        fragment.pop_indent()
+
+        fragment.pop_indent()
+
+        return str(fragment)
 
 
 def generate_pyasn1(sema_module, out_stream):
index f8e0340..79d7bc9 100644 (file)
@@ -42,23 +42,27 @@ def topological_sort(assignments):
     """ Algorithm adapted from:
     http://en.wikipedia.org/wiki/Topological_sorting.
 
+    Use this in code generators to sort assignments in dependency order, IFF
+    there are no circular dependencies.
+
     Assumes assignments is an iterable of items with two methods:
     - reference_name() -- returns the reference name of the assignment
     - references() -- returns an iterable of reference names
     upon which the assignment depends.
     """
-    graph = dict((a.reference_name(), set(a.references())) for a in assignments)
+    graph = dict((a.reference_name(), a.references()) for a in assignments)
 
     def has_predecessor(node):
-        for predecessor in graph.keys():
-            if node in graph[predecessor]:
+        for predecessors in graph.values():
+            if node in predecessors:
                 return True
 
         return False
 
     # Build a topological order of reference names
     topological_order = []
-    roots = [name for name in graph.keys() if not has_predecessor(name)]
+    roots = [name for name in graph.keys()
+             if not has_predecessor(name)]
 
     while roots:
         root = roots.pop()
@@ -67,7 +71,8 @@ def topological_sort(assignments):
         # and collect all new roots (the nodes that
         # were previously only referenced from n)
         successors = graph.pop(root, set())
-        roots.extend(successor for successor in successors if not has_predecessor(successor))
+        roots.extend(successor for successor in successors
+                     if not has_predecessor(successor))
 
         topological_order.insert(0, root)
 
@@ -75,7 +80,85 @@ def topological_sort(assignments):
         raise Exception('Can\'t sort cyclic references: %s' % graph)
 
     # Sort the actual assignments based on the topological order
-    return sorted(assignments, key=lambda a: topological_order.index(a.reference_name()))
+    return sorted(assignments,
+                  key=lambda a: topological_order.index(a.reference_name()))
+
+
+def dependency_sort(assignments):
+    """ We define a dependency sort as a Tarjan strongly-connected
+    components resolution. Tarjan's algorithm happens to topologically
+    sort as a by-product of finding strongly-connected components.
+
+    Use this in code generators to sort assignments in dependency order, if
+    there are circular dependencies. It is slower than ``topological_sort``.
+
+    In the sema model, each node depends on types mentioned in its
+    ``descendants``. The model is nominally a tree, except ``descendants``
+    can contain node references forming a cycle.
+
+    Returns a list of tuples, where each item represents a component
+    in the graph. Ideally they're one-tuples, but if the graph has cycles
+    items can have any number of elements constituting a cycle.
+
+    This is nice, because the output is in perfect dependency order,
+    except for the cycle components, where there is no order. They
+    can be detected on the basis of their plurality and handled
+    separately.
+    """
+    # Build reverse-lookup table from name -> node.
+    assignments_by_name = {a.reference_name(): a for a in assignments}
+
+    # Build the dependency graph.
+    graph = {}
+    for assignment in assignments:
+        references = assignment.references()
+        graph[assignment] = [assignments_by_name[r] for r in references
+                             if r in assignments_by_name]
+
+    # Now let Tarjan do its work! Adapted from here:
+    # http://www.logarithmic.net/pfh-files/blog/01208083168/tarjan.py
+    index_counter = [0]
+    stack = []
+    lowlinks = {}
+    index = {}
+    result = []
+
+    def strongconnect(node):
+        # Set the depth index for this node to the smallest unused index
+        index[node] = index_counter[0]
+        lowlinks[node] = index_counter[0]
+        index_counter[0] += 1
+        stack.append(node)
+
+        # Consider successors of `node`
+        successors = graph.get(node, [])
+        for successor in successors:
+            if successor not in lowlinks:
+                # Successor has not yet been visited; recurse on it
+                strongconnect(successor)
+                lowlinks[node] = min(lowlinks[node], lowlinks[successor])
+            elif successor in stack:
+                # the successor is in the stack and hence in the current
+                # strongly connected component (SCC)
+                lowlinks[node] = min(lowlinks[node], index[successor])
+
+        # If `node` is a root node, pop the stack and generate an SCC
+        if lowlinks[node] == index[node]:
+            connected_component = []
+
+            while True:
+                successor = stack.pop()
+                connected_component.append(successor)
+                if successor == node: break
+
+            component = tuple(connected_component)
+            result.append(component)
+
+    for node in graph:
+        if node not in lowlinks:
+            strongconnect(node)
+
+    return result
 
 
 # Registered object identifier names
@@ -109,23 +192,46 @@ some concepts captured which are not expressed in the spec.
 Most notably, we build a dependency graph of all types and values in a module,
 to allow code generators to build code in dependency order.
 
-All nodes that may be referenced (type and value assignments) must have a
-method called ``reference_name``.
-
-All nodes that may reference other types (e.g. assignments, component types)
-must have a method called ``references`` returning the names of all referenced
-nodes.
-
-Typically, if you have a ``reference_name``, you must also have a ``references``,
-but not the other way around.
+All nodes that somehow denote a referenced type or value, either definitions
+(type and value assignments) or references (referenced types, referenced values,
+etc) must have a method called ``reference_name``.
 """
 
 class SemaNode(object):
+    """ Base class for all sema nodes. """
+
     def children(self):
-        raise NotImplementedError()
+        """ Return a list of all contained sema nodes.
+
+        This implementation finds all member variables of type
+        SemaNode. It also expands list members, to transparently
+        handle the case where a node holds a list of other
+        sema nodes.
+        """
+        # Collect all SemaNode members.
+        members = list(vars(self).values())
+        children = [m for m in members if isinstance(m, SemaNode)]
+
+        # Expand SemaNodes out of list members, but do not recurse
+        # through lists of lists.
+        list_members = [m for m  in members if isinstance(m, list)]
+        for m in list_members:
+            children.extend(n for n in m if isinstance(n, SemaNode))
 
+        return children
 
-class Module(object):
+    def descendants(self):
+        """ Return a list of all recursively contained sema nodes.
+        """
+        descendants = []
+        for child in self.children():
+            descendants.append(child)
+            descendants.extend(child.descendants())
+
+        return descendants
+
+
+class Module(SemaNode):
     def __init__(self, elements):
         self._user_types = {}
 
@@ -150,7 +256,7 @@ class Module(object):
         """
         user_types = self.user_types()
 
-        if isinstance(type_decl, UserDefinedType):
+        if isinstance(type_decl, ReferencedType):
             return self.resolve_type_decl(user_types[type_decl.type_name])
         else:
             return type_decl
@@ -165,7 +271,19 @@ class Module(object):
     __repr__ = __str__
 
 
-class TypeAssignment(object):
+class Assignment(SemaNode):
+    def references(self):
+        """ Return a set of all reference names (both values and types) that
+        this assignment depends on.
+
+        This happens to coincide with all contained SemaNodes as exposed by
+        ``descendants`` with a ``reference_name`` method.
+        """
+        return set(d.reference_name() for d in self.descendants()
+                   if hasattr(d, 'reference_name'))
+
+
+class TypeAssignment(Assignment):
     def __init__(self, elements):
         assert(len(elements) == 3)
         type_name, _, type_decl = elements
@@ -175,48 +293,21 @@ class TypeAssignment(object):
     def reference_name(self):
         return self.type_name
 
-    def references(self):
-        refs = self.type_decl.references()
-
-        # Remove any self-references
-        refs = [ref for ref in refs if ref != self.type_name]
-
-        return refs
-
     def __str__(self):
         return '%s ::= %s' % (self.type_name, self.type_decl)
 
     __repr__ = __str__
 
 
-class ValueAssignment(object):
+class ValueAssignment(Assignment):
     def __init__(self, elements):
         value_name, type_name, _, value = elements
-        self.value_name = ValueReference(value_name.elements) # First token is always a valuereference
+        self.value_name = value_name
         self.type_decl = _create_sema_node(type_name)
-
-        if isinstance(value, parser.AnnotatedToken):
-            self.value = _create_sema_node(value)
-        else:
-            self.value = value
+        self.value = _maybe_create_sema_node(value)
 
     def reference_name(self):
-        return self.value_name.reference_name()
-
-    def references(self):
-        refs = [self.type_decl.reference_name()]
-        if isinstance(self.value, ValueReference):
-            refs.append(self.value.reference_name())
-        elif isinstance(self.value, ObjectIdentifierValue):
-            refs.extend(self.value.references())
-        else:
-            # It's a literal, and they don't play into declaration order.
-            pass
-
-        # Remove any self-references
-        refs = [ref for ref in refs if ref != self.value_name]
-
-        return refs
+        return self.value_name
 
     def __str__(self):
         return '%s %s ::= %s' % (self.value_name, self.type_decl, self.value)
@@ -224,35 +315,13 @@ class ValueAssignment(object):
     __repr__ = __str__
 
 
-class ValueReference(object):
-    def __init__(self, elements):
-        self.name = elements[0]
-
-    def reference_name(self):
-        return self.name
-
-    def references(self):
-        return []
-
-    def __str__(self):
-        return self.name
-
-    __repr__ = __str__
-
-
-class ConstructedType(object):
+class ConstructedType(SemaNode):
     """ Base type for SEQUENCE, SET and CHOICE. """
     def __init__(self, elements):
         type_name, component_tokens = elements
         self.type_name = type_name
         self.components = [_create_sema_node(token) for token in component_tokens]
 
-    def references(self):
-        references = []
-        for component in self.components:
-            references.extend(component.references())
-        return references
-
     def __str__(self):
         component_type_list = ', '.join(map(str, self.components))
         return '%s { %s }' % (self.type_name, component_type_list)
@@ -275,7 +344,7 @@ class SetType(ConstructedType):
         super(SetType, self).__init__(elements)
 
 
-class CollectionType(object):
+class CollectionType(SemaNode):
     """ Base type for SET OF and SEQUENCE OF. """
     def __init__(self, kind, elements):
         self.kind = kind
@@ -290,9 +359,6 @@ class CollectionType(object):
         else:
             assert False, 'Unknown form of %s OF declaration: %s' % (self.kind, elements)
 
-    def references(self):
-        return self.type_decl.references()
-
     def __str__(self):
         if self.size_constraint:
             return '%s %s OF %s' % (self.kind, self.size_constraint, self.type_decl)
@@ -312,7 +378,7 @@ class SetOfType(CollectionType):
         super(SetOfType, self).__init__('SET', elements)
 
 
-class TaggedType(object):
+class TaggedType(SemaNode):
     def __init__(self, elements):
         self.class_name = None
         self.class_number = None
@@ -339,12 +405,6 @@ class TaggedType(object):
     def type_name(self):
         return self.type_decl.type_name
 
-    def reference_name(self):
-        return self.type_decl.type_name
-
-    def references(self):
-        return self.type_decl.references()
-
     def __str__(self):
         class_spec = []
         if self.class_name:
@@ -362,23 +422,13 @@ class TaggedType(object):
     __repr__ = __str__
 
 
-class SimpleType(object):
+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)
 
-    def reference_name(self):
-        return self.type_name
-
-    def references(self):
-        refs = [self.type_name]
-        if self.constraint:
-            refs.extend(self.constraint.references())
-
-        return refs
-
     def __str__(self):
         if self.constraint is None:
             return self.type_name
@@ -388,38 +438,38 @@ class SimpleType(object):
     __repr__ = __str__
 
 
-class UserDefinedType(object):
+class ReferencedType(SemaNode):
     def __init__(self, elements):
         self.type_name = elements[0]
 
     def reference_name(self):
         return self.type_name
 
-    def references(self):
-        return [self.type_name]
-
     def __str__(self):
         return self.type_name
 
     __repr__ = __str__
 
 
-class Constraint(object):
+class ReferencedValue(SemaNode):
     def __init__(self, elements):
-        min_value, max_value = elements
+        self.name = elements[0]
 
-        self.min_value = _maybe_create_sema_node(min_value)
-        self.max_value = _maybe_create_sema_node(max_value)
+    def reference_name(self):
+        return self.name
+
+    def __str__(self):
+        return self.name
+
+    __repr__ = __str__
 
-    def references(self):
-        refs = []
-        if isinstance(self.min_value, ValueReference):
-            refs.append(self.min_value.reference_name())
 
-        if isinstance(self.max_value, ValueReference):
-            refs.append(self.max_value.reference_name())
+class Constraint(SemaNode):
+    def __init__(self, elements):
+        min_value, max_value = elements
 
-        return refs
+        self.min_value = _maybe_create_sema_node(min_value)
+        self.max_value = _maybe_create_sema_node(max_value)
 
     def __str__(self):
         return '(%s..%s)' % (self.min_value, self.max_value)
@@ -435,7 +485,7 @@ class SizeConstraint(Constraint):
     __repr__ = __str__
 
 
-class ComponentType(object):
+class ComponentType(SemaNode):
     def __init__(self, elements):
         self.identifier = None
         self.type_decl = None
@@ -462,18 +512,6 @@ class ComponentType(object):
         else:
             assert False, 'Unknown component type %s' % first_token
 
-    def references(self):
-        if self.components_of_type:
-            return [self.components_of_type.type_name]
-
-        refs = [self.type_decl.type_name]
-        refs.extend(self.type_decl.references())
-
-        if self.default_value is not None:
-            refs.append(str(self.default_value))
-
-        return refs
-
     def __str__(self):
         if self.components_of_type:
             return 'COMPONENTS OF %s' % self.components_of_type
@@ -489,7 +527,7 @@ class ComponentType(object):
     __repr__ = __str__
 
 
-class NamedType(object):
+class NamedType(SemaNode):
     def __init__(self, elements):
         first_token = elements[0]
         if first_token.ty == 'Type':
@@ -503,16 +541,13 @@ class NamedType(object):
 
         self.type_decl = _create_sema_node(type_token)
 
-    def references(self):
-        return self.type_decl.references()
-
     def __str__(self):
         return '%s %s' % (self.identifier, self.type_decl)
 
     __repr__ = __str__
 
 
-class ValueListType(object):
+class ValueListType(SemaNode):
     def __init__(self, elements):
         self.type_name = elements[0]
         if len(elements) > 1:
@@ -526,10 +561,6 @@ class ValueListType(object):
         else:
             self.named_values = None
 
-    def references(self):
-        # TODO: Value references
-        return []
-
     def __str__(self):
         if self.named_values:
             named_value_list = ', '.join(map(str, self.named_values))
@@ -540,7 +571,7 @@ class ValueListType(object):
     __repr__ = __str__
 
 
-class BitStringType(object):
+class BitStringType(SemaNode):
     def __init__(self, elements):
         self.type_name = elements[0]
         if len(elements) > 1:
@@ -548,10 +579,6 @@ class BitStringType(object):
         else:
             self.named_bits = None
 
-    def references(self):
-        # TODO: Value references
-        return []
-
     def __str__(self):
         if self.named_bits:
             named_bit_list = ', '.join(map(str, self.named_bits))
@@ -562,7 +589,7 @@ class BitStringType(object):
     __repr__ = __str__
 
 
-class NamedValue(object):
+class NamedValue(SemaNode):
     def __init__(self, elements):
         if len(elements) == 1:
             identifier_token = elements[0]
@@ -573,36 +600,28 @@ class NamedValue(object):
             self.identifier = identifier_token.elements[0]
             self.value = value_token.elements[0]
 
-    def references(self):
-        # TODO: This appears to never be called. Investigate.
-        return []
-
     def __str__(self):
         return '%s (%s)' % (self.identifier, self.value)
 
     __repr__ = __str__
 
 
-class ExtensionMarker(object):
+class ExtensionMarker(SemaNode):
     def __init__(self, elements):
         pass
 
-    def references(self):
-        # TODO: This appears to never be called. Investigate.
-        return []
-
     def __str__(self):
         return '...'
 
     __repr__ = __str__
 
 
-class NameForm(object):
+class NameForm(SemaNode):
     def __init__(self, elements):
         self.name = elements[0]
 
-    def references(self):
-        return [self.name]
+    def reference_name(self):
+        return self.name
 
     def __str__(self):
         return self.name
@@ -610,75 +629,51 @@ class NameForm(object):
     __repr__ = __str__
 
 
-class NumberForm(object):
+class NumberForm(SemaNode):
     def __init__(self, elements):
         self.value = elements[0]
 
-    def references(self):
-        return []
-
     def __str__(self):
         return str(self.value)
 
     __repr__ = __str__
 
 
-class NameAndNumberForm(object):
+class NameAndNumberForm(SemaNode):
     def __init__(self, elements):
-        # The first element is a NameForm containing only the
-        # name, so unpack it into a string.
-        self.name = elements[0].elements[0]
+        self.name = _create_sema_node(elements[0])
         self.number = _create_sema_node(elements[1])
 
-    def references(self):
-        return [str(self.name), str(self.number)]
-
     def __str__(self):
         return '%s(%s)' % (self.name, self.number)
 
     __repr__ = __str__
 
 
-class ObjectIdentifierValue(object):
+class ObjectIdentifierValue(SemaNode):
     def __init__(self, elements):
         self.components = [_create_sema_node(c) for c in elements]
 
-    def references(self):
-        refs = []
-        for component in self.components:
-            if isinstance(component, str):
-                refs.append(component)
-            else:
-                refs.extend(component.references())
-
-        return refs
-
     def __str__(self):
         return '{' + ' '.join(str(x) for x in self.components) + '}'
 
     __repr__ = __str__
 
 
-class BinaryStringValue(object):
+class BinaryStringValue(SemaNode):
     def __init__(self, elements):
         self.value = elements[0]
 
-    def references(self):
-        return []
-
     def __str__(self):
         return '\'%s\'B' % self.value
 
     __repr__ = __str__
 
 
-class HexStringValue(object):
+class HexStringValue(SemaNode):
     def __init__(self, elements):
         self.value = elements[0]
 
-    def references(self):
-        return []
-
     def __str__(self):
         return '\'%s\'H' % self.value
 
@@ -701,8 +696,6 @@ def _create_sema_node(token):
         return TypeAssignment(token.elements)
     elif token.ty == 'ValueAssignment':
         return ValueAssignment(token.elements)
-    elif token.ty == 'ValueReference':
-        return ValueReference(token.elements)
     elif token.ty == 'ComponentType':
         return ComponentType(token.elements)
     elif token.ty == 'NamedType':
@@ -720,7 +713,9 @@ def _create_sema_node(token):
     elif token.ty == 'SimpleType':
         return SimpleType(token.elements)
     elif token.ty == 'ReferencedType':
-        return UserDefinedType(token.elements)
+        return ReferencedType(token.elements)
+    elif token.ty == 'ReferencedValue':
+        return ReferencedValue(token.elements)
     elif token.ty == 'TaggedType':
         return TaggedType(token.elements)
     elif token.ty == 'SequenceType':