"""Convert ASN.1 specifications to Python data structures.

"""

import logging
import re
import sys

from pyparsing import Literal
from pyparsing import Keyword
from pyparsing import Word
from pyparsing import ZeroOrMore
from pyparsing import Regex
from pyparsing import printables
from pyparsing import delimitedList
from pyparsing import Group
from pyparsing import Optional
from pyparsing import Forward
from pyparsing import StringEnd
from pyparsing import OneOrMore
from pyparsing import nums
from pyparsing import Suppress
from pyparsing import ParseException
from pyparsing import ParseSyntaxException
from pyparsing import NotAny
from pyparsing import NoMatch
from pyparsing import QuotedString
from pyparsing import Combine
from pyparsing import ParseResults
from pyparsing import lineno

from .errors import Error


LOGGER = logging.getLogger(__name__)

EXTENSION_MARKER = None


class ParseError(Error):
    pass


class InternalParserError(Error):
    pass


class Tokens(object):

    def __init__(self, tag, tokens):
        self.tag = tag
        self.tokens = tokens

    def __getitem__(self, index):
        return self.tokens[index]

    def __len__(self):
        return len(self.tokens)

    def __iter__(self):
        for token in self.tokens:
            yield token

    def __bool__(self):
        return len(self.tokens) > 0

    def __eq__(self, other):
        return other == self.tag

    def __repr__(self):
        return "Tokens(tag='{}', tokens='{}')".format(self.tag,
                                                      self.tokens)


class Tag(Group):

    def __init__(self, tag, expr):
        super(Tag, self).__init__(expr)
        self.tag = tag

    def postParse(self, instring, loc, tokenlist):
        return Tokens(self.tag, tokenlist.asList())


def merge_dicts(dicts):
    return {k: v for d in dicts for k, v in d.items()}


def convert_integer(_s, _l, tokens):
    try:
        return int(tokens[0])
    except (IndexError, ValueError):
        return tokens


def convert_real_number(_s, _l, tokens):
    if '.' not in tokens[0]:
        tokens = int(tokens[0])

    return tokens


def convert_number(token):
    if isinstance(token, list):
        token = token[0]

    try:
        return int(token)
    except (ValueError, TypeError):
        return token


def convert_size(tokens):
    if len(tokens) == 0:
        return None

    tokens = tokens[0]

    if tokens[0] == 'SIZE':
        values = []

        for item_tokens in tokens[1].asList():
            if '..' in item_tokens:
                value = (convert_number(item_tokens[0]),
                         convert_number(item_tokens[2]))
            else:
                value = convert_number(item_tokens[0])

            values.append(value)

        return values
    elif isinstance(tokens[0], dict):
        if 'size' in tokens[0]:
            return tokens[0]['size']


def convert_table(tokens):
    tokens = tokens[0]

    try:
        if isinstance(tokens[1][0][0], list):
            defined_object_set = tokens[1][0][0][0]
        else:
            defined_object_set = tokens[1][0][0]
    except IndexError:
        return None

    try:
        component_ids = tokens[4]
    except IndexError:
        return defined_object_set

    return [defined_object_set, component_ids]


def convert_enum_values(string, location, tokens):
    number = 0
    values = []
    used_numbers = []
    root, extension = tokens
    root = root.asList()
    extension = extension.asList()

    def add_used_numbers(items):
        for item in items:
            if not isinstance(item, list):
                continue

            item_number = item[2]

            if item_number in used_numbers:
                raise ParseError(
                    'Duplicated ENUMERATED number {} at line {}.'.format(
                        item_number,
                        lineno(location, string)))

            used_numbers.append(item_number)

    # Root enumeration.
    add_used_numbers(root)

    for token in root:
        if isinstance(token, list):
            values.append((token[0], token[2]))
        else:
            while number in used_numbers:
                number += 1

            used_numbers.append(number)
            values.append((token, number))
            number += 1

    # Optional additional enumeration.
    if extension:
        values.append(EXTENSION_MARKER)
        additional = extension[1:]
        add_used_numbers(additional)

        for token in additional:
            if isinstance(token, list):
                number = token[2]
                values.append((token[0], number))
            else:
                if number in used_numbers:
                    raise ParseError(
                        'Duplicated ENUMERATED number {} at line {}.'.format(
                            number,
                            lineno(location, string)))

                values.append((token, number))
                used_numbers.append(number)

            number += 1

    return values


def convert_tag(tokens):
    if len(tokens) > 0:
        if len(tokens[0]) == 1:
            tag = {
                'number': int(tokens[0][0])
            }
        else:
            tag = {
                'number': convert_number(tokens[0][1]),
                'class': tokens[0][0]
            }

        if tokens[1]:
            tag['kind'] = tokens[1][0] if tokens[1] else None

        return tag


def convert_value_range(_s, _l, tokens):
    tokens = tokens.asList()
    minimum = tokens[0]

    if isinstance(minimum, list):
        minimum = minimum[0]

    maximum = tokens[1]

    if isinstance(maximum, list):
        maximum = maximum[0]

    return (minimum, maximum)


def convert_inner_type_constraints(_s, _l, tokens):
    tokens = tokens.asList()
    components = []

    if tokens[0] == 'WITH COMPONENTS':
        for item_tokens in tokens[2]:
            if item_tokens == '...':
                value = EXTENSION_MARKER
            elif len(item_tokens) == 2:
                if isinstance(item_tokens[1], list):
                    value = item_tokens[1][0]

                    if isinstance(value, list):
                        value = value[0]

                    value = (item_tokens[0], value)
                else:
                    value = (item_tokens[0], item_tokens[1])
            else:
                value = item_tokens

            components.append(value)

        return {'with-components': components}
    else:
        return {}


def convert_size_constraint(_s, _l, tokens):
    tokens = tokens.asList()[1]
    values = []

    for item_tokens in tokens:
        if item_tokens == '...':
            value = EXTENSION_MARKER
        elif '..' in item_tokens:
            value = (convert_number(item_tokens[0]),
                     convert_number(item_tokens[2]))
        else:
            value = convert_number(item_tokens[0])

        values.append(value)

    return {'size': values}


def convert_permitted_alphabet(_s, _l, tokens):
    tokens = tokens.asList()
    values = []

    for token in tokens[1:]:
        if isinstance(token[0], list):
            for char in token[0][0]:
                values.append((char, char))
        else:
            values.append(token[0])

    return {'from': values}


def convert_constraint(_s, _l, tokens):
    tokens = tokens.asList()
    # print('constraint:', tokens)


def convert_members(tokens):
    members = []

    for member_tokens in tokens:
        if member_tokens in [['...'], '...']:
            members.append(EXTENSION_MARKER)
            continue

        if member_tokens[0] == 'COMPONENTS OF':
            members.append({
                'components-of': member_tokens[1][0]['type']
            })
            continue

        if member_tokens[0] == '[[':
            members.append(convert_members(member_tokens[1]))
            continue

        if len(member_tokens) == 2:
            member_tokens, qualifiers = member_tokens
            qualifiers = qualifiers.asList()
        else:
            qualifiers = []

        member = convert_type(member_tokens[2], [])
        member['name'] = member_tokens[0]

        if 'OPTIONAL' in qualifiers:
            member['optional'] = True

        if 'DEFAULT' in qualifiers:
            if len(qualifiers[1]) == 0:
                value = []
            else:
                value = convert_value(qualifiers[1], member['type'])

            member['default'] = value

        tag = convert_tag(member_tokens[1])

        if tag:
            member['tag'] = tag

        members.append(member)

    return members


def convert_sequence_type(_s, _l, tokens):
    return {
        'type': 'SEQUENCE',
        'members': convert_members(tokens[2])
    }


def convert_sequence_of_type(_s, _l, tokens):
    is_named_element = len(tokens) == 6

    converted_type = {
        'type': 'SEQUENCE OF',
        'element': convert_type(tokens[5 if is_named_element else 4], []),
    }

    if (is_named_element):
        converted_type['element_name'] = tokens[3]

    if len(tokens[1]) > 0:
        converted_type['size'] = tokens[1][0][0]['size']

        if len(tokens[1]) > 1 and tokens[1][1] == '...':
            converted_type['size'].append(None)

    tag = convert_tag(tokens[4 if is_named_element else 3])

    if tag:
        converted_type['element']['tag'] = tag

    return converted_type


def convert_set_type(_s, _l, tokens):
    return {
        'type': 'SET',
        'members': convert_members(tokens[2])
    }


def convert_set_of_type(_s, _l, tokens):
    is_named_element = len(tokens) == 6

    converted_type = {
        'type': 'SET OF',
        'element': convert_type(tokens[5 if is_named_element else 4], [])
    }

    if (is_named_element):
        converted_type['element_name'] = tokens[3]

    if len(tokens[1]) > 0:
        converted_type['size'] = tokens[1][0][0]['size']

        if len(tokens[1]) > 1 and tokens[1][1] == '...':
            converted_type['size'].append(None)

    tag = convert_tag(tokens[4 if is_named_element else 3])

    if tag:
        converted_type['element']['tag'] = tag

    return converted_type


def convert_choice_type(_s, _l, tokens):
    return {
        'type': 'CHOICE',
        'members': convert_members(tokens[2])
    }


def convert_defined_type(_s, _l, tokens):
    converted = {
        'type': tokens[0]
    }

    # actual-parameters
    if len(tokens) == 2:
        converted.update(tokens[1])

    return converted


def convert_integer_type(_s, _l, tokens):
    tokens = tokens.asList()
    compiled = {'type': 'INTEGER'}

    if tokens[1]:
        compiled['named-numbers'] = {
            item[0]: item[2] for item in tokens[1]
        }

    return compiled


def convert_real_type(_s, _l, _tokens):
    return {'type': 'REAL'}


def convert_enumerated_type(string, location, tokens):
    return {
        'type': 'ENUMERATED',
        'values': convert_enum_values(string, location, tokens[2])
    }


def convert_keyword_type(_s, _l, tokens):
    return {
        'type': tokens[0]
    }


def convert_print(_s, _l, tokens):
    print('convert_print', tokens)


def convert_object_identifier_type(_s, _l, _tokens):
    return {
        'type': 'OBJECT IDENTIFIER'
    }


def convert_bit_string_type(_s, _l, tokens):
    converted_type = {
        'type': 'BIT STRING'
    }

    named_bit_list = tokens.asList()[1]

    if named_bit_list:
        converted_type['named-bits'] = [
            tuple(named_bit) for named_bit in named_bit_list
        ]

    return converted_type


def convert_octet_string_type(_s, _l, _tokens):
    return {
        'type': 'OCTET STRING'
    }


def convert_ia5_string_type(_s, _l, _tokens):
    return {
        'type': 'IA5String'
    }


def convert_any_defined_by_type(_s, _l, tokens):
    return {
        'type': 'ANY DEFINED BY',
        'value': tokens[1],
        'choices': {}
    }


def convert_actual_parameter_list(_s, _l, tokens):
    tokens = tokens.asList()[0]
    converted = []

    for parameter in tokens:
        converted.append(parameter[0][0])

    if converted:
        converted = {'actual-parameters': converted}
    else:
        converted = None

    return converted


def convert_parameter_list(_s, _l, tokens):
    tokens = tokens.asList()

    if tokens[0]:
        converted = {'parameters': tokens[0]}
    else:
        converted = None

    return converted


def convert_null_type(_s, _l, _tokens):
    return {
        'type': 'NULL'
    }


def convert_boolean_type(_s, _l, _tokens):
    return {
        'type': 'BOOLEAN'
    }


def convert_type(tokens, parameters):
    converted_type, constraints = tokens

    restricted_to = []

    for constraint_tokens in constraints:
        if isinstance(constraint_tokens, ParseResults):
            constraint_tokens = constraint_tokens.asList()

        if constraint_tokens == '...':
            if restricted_to:
                restricted_to.append(EXTENSION_MARKER)

            if 'size' in converted_type:
                converted_type['size'].append(None)
        elif len(constraint_tokens) == 1:
            if not isinstance(constraint_tokens[0], dict):
                restricted_to.append(convert_number(constraint_tokens[0]))
            elif 'size' in constraint_tokens[0]:
                converted_type.update(constraint_tokens[0])
            elif 'from' in constraint_tokens[0]:
                converted_type.update(constraint_tokens[0])
            elif 'with-components' in constraint_tokens[0]:
                converted_type.update(constraint_tokens[0])

    if isinstance(parameters, dict):
        converted_type.update(parameters)

    if '{' in restricted_to:
        restricted_to = []

    if restricted_to:
        converted_type['restricted-to'] = restricted_to

    types = [
        'BIT STRING',
        'OCTET STRING',
        'IA5String',
        'BMPString',
        'VisibleString',
        'UTF8String',
        'NumericString',
        'PrintableString'
    ]

    if converted_type['type'] in types:
        size = convert_size(constraints)

        if size:
            converted_type['size'] = size

    if '&' in converted_type['type']:
        converted_type['table'] = convert_table(tokens.asList()[1:])

    return converted_type


def convert_bstring(_s, _l, tokens):
    return '0b' + re.sub(r"[\sB']", '', tokens[0])


def convert_hstring(_s, _l, tokens):
    return '0x' + re.sub(r"[\sH']", '', tokens[0]).lower()


def convert_bit_string_value(tokens):
    value = tokens[0]

    if value == 'IdentifierList':
        value = value[:]
    elif isinstance(value, str):
        value = value
    else:
        value = None

    return value


def convert_value(tokens, type_=None):
    if type_ == 'INTEGER':
        value = int(tokens[0])
    elif type_ == 'OBJECT IDENTIFIER':
        value = []

        for value_tokens in tokens:
            if len(value_tokens) == 2:
                value.append((value_tokens[0], int(value_tokens[1])))
            else:
                value.append(convert_number(value_tokens[0]))
    elif type_ == 'BOOLEAN':
        value = (tokens[0] == 'TRUE')
    elif tokens[0] == 'BitStringValue':
        value = convert_bit_string_value(tokens[0])
    elif isinstance(tokens[0], str):
        value = convert_number(tokens[0])
    elif isinstance(tokens[0], int):
        value = tokens[0]
    else:
        value = None

    return value


def convert_parameterized_object_set_assignment(_s, _l, tokens):
    members = []

    try:
        for member_tokens in tokens[4].asList():
            if len(member_tokens[0]) == 1:
                member = member_tokens[0][0]
            else:
                member = {}

                for item_tokens in member_tokens[0]:
                    name = item_tokens[0]
                    value = item_tokens[1][0]

                    if isinstance(value, Tokens):
                        value = value[0]

                    member[name] = convert_number(value)

            members.append(member)
    except (IndexError, KeyError):
        pass

    converted_type = {
        'class': tokens[1],
        'members': members
    }

    return ('parameterized-object-set-assignment',
            tokens[0],
            converted_type)


def convert_parameterized_object_assignment(_s, _l, tokens):
    type_ = tokens[1]

    converted_type = {
        'type': type_,
        'value': None
    }

    return ('parameterized-object-assignment',
            tokens[0],
            converted_type)


def convert_parameterized_object_class_assignment(_s, _l, tokens):
    members = []

    for member in tokens[3]:
        if member[0][1].islower():
            converted_member = member[1][0]

            if isinstance(converted_member, Tokens):
                converted_member = converted_member[0]
        else:
            converted_member = {'type': 'OpenType'}

        converted_member['name'] = member[0]

        members.append(converted_member)

    converted_type = {
        'members': members
    }

    return ('parameterized-object-class-assignment',
            tokens[0],
            converted_type)


def convert_parameterized_type_assignment(_s, _l, tokens):
    tokens = tokens.asList()
    converted_type = convert_type(tokens[4], tokens[1])

    try:
        tag = convert_tag(tokens[3])
    except ValueError:
        tag = None

    if tag:
        converted_type['tag'] = tag

    return ('parameterized-type-assignment',
            tokens[0],
            converted_type)


def convert_parameterized_value_assignment(_s, _l, tokens):
    type_ = tokens[1][0][0]

    if isinstance(type_, Tokens):
        type_ = type_[0]
    elif isinstance(type_, dict):
        type_ = type_['type']

    converted_type = {
        'type': type_,
        'value': convert_value(tokens[2], type_)
    }

    return ('parameterized-value-assignment',
            tokens[0],
            converted_type)


def convert_imports(_s, _l, tokens):
    tokens = tokens.asList()
    imports = {}

    if tokens:
        for from_tokens in tokens:
            from_name = from_tokens[2]

            LOGGER.debug("Converting imports from '%s'.", from_name)

            imported = from_tokens[0]

            if from_name in imports:
                imported = list(set(imports[from_name] + imported))

            imports[from_name] = sorted(imported)

    return {'imports': imports}


def convert_assignment_list(_s, _l, tokens):
    types = {}
    values = {}
    object_classes = {}
    object_sets = {}

    for kind, name, value in tokens:
        if kind == 'parameterized-object-set-assignment':
            if name in object_sets:
                LOGGER.warning("Object set '%s' already defined.", name)

            object_sets[name] = value
        elif kind == 'parameterized-object-assignment':
            if name in values:
                LOGGER.warning("Object '%s' already defined.", name)

            values[name] = value
        elif kind == 'parameterized-object-class-assignment':
            if name in object_classes:
                LOGGER.warning("Object class '%s' already defined.", name)

            object_classes[name] = value
        elif kind == 'parameterized-type-assignment':
            if name in types:
                LOGGER.warning("Type '%s' already defined.", name)

            types[name] = value
        elif kind == 'parameterized-value-assignment':
            if name in values:
                LOGGER.warning("Value '%s' already defined.", name)

            values[name] = value
        else:
            raise InternalParserError(
                'Unrecognized assignment kind {}.'.format(kind))

    return {
        'types': types,
        'values': values,
        'object-classes': object_classes,
        'object-sets': object_sets
    }


def convert_module_body(_s, _l, tokens):
    return merge_dicts(tokens)


def convert_module_definition(_s, _l, tokens):
    tokens = tokens.asList()
    module = tokens[1][0]
    module['extensibility-implied'] = (tokens[0][3] != [])

    if tokens[0][2]:
        module['tags'] = tokens[0][2][0]

    return {tokens[0][0]: module}


def convert_specification(_s, _l, tokens):
    return merge_dicts(tokens)


def create_grammar():
    """Return the ASN.1 grammar as Pyparsing objects.

    """

    # Keywords.
    SEQUENCE = Keyword('SEQUENCE').setName('SEQUENCE')
    CHOICE = Keyword('CHOICE').setName('CHOICE')
    ENUMERATED = Keyword('ENUMERATED').setName('ENUMERATED')
    DEFINITIONS = Keyword('DEFINITIONS').setName('DEFINITIONS')
    BEGIN = Keyword('BEGIN').setName('BEGIN')
    END = Keyword('END').setName('END')
    AUTOMATIC = Keyword('AUTOMATIC').setName('AUTOMATIC')
    TAGS = Keyword('TAGS').setName('TAGS')
    OPTIONAL = Keyword('OPTIONAL').setName('OPTIONAL')
    OF = Keyword('OF').setName('OF')
    SIZE = Keyword('SIZE').setName('SIZE')
    INTEGER = Keyword('INTEGER').setName('INTEGER')
    REAL = Keyword('REAL').setName('REAL')
    BIT_STRING = Keyword('BIT STRING').setName('BIT STRING')
    OCTET_STRING = Keyword('OCTET STRING').setName('OCTET STRING')
    DEFAULT = Keyword('DEFAULT').setName('DEFAULT')
    IMPORTS = Keyword('IMPORTS').setName('IMPORTS')
    EXPORTS = Keyword('EXPORTS').setName('EXPORTS')
    FROM = Keyword('FROM').setName('FROM')
    CONTAINING = Keyword('CONTAINING').setName('CONTAINING')
    ENCODED_BY = Keyword('ENCODED_BY').setName('ENCODED_BY')
    IMPLICIT = Keyword('IMPLICIT').setName('IMPLICIT')
    EXPLICIT = Keyword('EXPLICIT').setName('EXPLICIT')
    OBJECT_IDENTIFIER = Keyword('OBJECT IDENTIFIER').setName('OBJECT IDENTIFIER')
    UNIVERSAL = Keyword('UNIVERSAL').setName('UNIVERSAL')
    APPLICATION = Keyword('APPLICATION').setName('APPLICATION')
    PRIVATE = Keyword('PRIVATE').setName('PRIVATE')
    SET = Keyword('SET').setName('SET')
    ANY_DEFINED_BY = Keyword('ANY DEFINED BY').setName('ANY DEFINED BY')
    EXTENSIBILITY_IMPLIED = Keyword('EXTENSIBILITY IMPLIED').setName(
        'EXTENSIBILITY IMPLIED')
    BOOLEAN = Keyword('BOOLEAN').setName('BOOLEAN')
    TRUE = Keyword('TRUE').setName('TRUE')
    FALSE = Keyword('FALSE').setName('FALSE')
    CLASS = Keyword('CLASS').setName('CLASS')
    WITH_SYNTAX = Keyword('WITH SYNTAX').setName('WITH SYNTAX')
    UNIQUE = Keyword('UNIQUE').setName('UNIQUE')
    NULL = Keyword('NULL').setName('NULL')
    WITH_COMPONENT = Keyword('WITH COMPONENT').setName('WITH COMPONENT')
    WITH_COMPONENTS = Keyword('WITH COMPONENTS').setName('WITH COMPONENTS')
    WITH_SUCCESSORS = Keyword('WITH SUCCESSORS').setName('WITH SUCCESSORS')
    WITH_DESCENDANTS = Keyword('WITH DESCENDANTS').setName('WITH DESCENDANTS')
    COMPONENTS_OF = Keyword('COMPONENTS OF').setName('COMPONENTS OF')
    PRESENT = Keyword('PRESENT').setName('PRESENT')
    ABSENT = Keyword('ABSENT').setName('ABSENT')
    ALL = Keyword('ALL').setName('ALL')
    EXCEPT = Keyword('EXCEPT').setName('EXCEPT')
    MIN = Keyword('MIN').setName('MIN')
    MAX = Keyword('MAX').setName('MAX')
    INCLUDES = Keyword('INCLUDES').setName('INCLUDES')
    PATTERN = Keyword('PATTERN').setName('PATTERN')
    CONSTRAINED_BY = Keyword('CONSTRAINED BY').setName('CONSTRAINED BY')
    UNION = Keyword('UNION').setName('UNION')
    INTERSECTION = Keyword('INTERSECTION').setName('INTERSECTION')
    PLUS_INFINITY = Keyword('PLUS-INFINITY').setName('PLUS-INFINITY')
    MINUS_INFINITY = Keyword('MINUS-INFINITY').setName('MINUS-INFINITY')
    BMPString = Keyword('BMPString').setName('BMPString')
    GeneralString = Keyword('GeneralString').setName('GeneralString')
    GraphicString = Keyword('GraphicString').setName('GraphicString')
    IA5String = Keyword('IA5String').setName('IA5String')
    ISO646String = Keyword('ISO646String').setName('ISO646String')
    NumericString = Keyword('NumericString').setName('NumericString')
    PrintableString = Keyword('PrintableString').setName('PrintableString')
    TeletexString = Keyword('TeletexString').setName('TeletexString')
    UTCTime = Keyword('UTCTime').setName('UTCTime')
    GeneralizedTime = Keyword('GeneralizedTime').setName('GeneralizedTime')
    T61String = Keyword('T61String').setName('T61String')
    UniversalString = Keyword('UniversalString').setName('UniversalString')
    UTF8String = Keyword('UTF8String').setName('UTF8String')
    VideotexString = Keyword('VideotexString').setName('VideotexString')
    VisibleString = Keyword('VisibleString').setName('VisibleString')
    CHARACTER_STRING = Keyword('CHARACTER STRING').setName('CHARACTER STRING')

    # Various literals.
    word = Word(printables, excludeChars=',(){}[].:=;"|').setName('word')
    identifier = Regex(r'[a-z][a-zA-Z0-9-]*').setName('identifier')
    assign = Literal('::=').setName('::=')
    left_parenthesis = Literal('(')
    right_parenthesis = Literal(')')
    left_brace = Literal('{')
    right_brace = Literal('}')
    left_bracket = Literal('[')
    right_bracket = Literal(']')
    left_version_brackets = Literal('[[')
    right_version_brackets = Literal(']]')
    colon = Literal(':')
    semi_colon = Literal(';')
    dot = Literal('.')
    range_separator = Literal('..')
    ellipsis = Literal('...')
    pipe = Literal('|')
    caret = Literal('^')
    comma = Literal(',')
    at = Literal('@')
    exclamation_mark = Literal('!')
    integer = Word(nums + '-')
    real_number = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?')
    bstring = Regex(r"'[01\s]*'B")
    hstring = Regex(r"'[0-9A-F\s]*'H")
    cstring = QuotedString('"')
    number = (Word(nums).setName('number') + ~dot)
    ampersand = Literal('&')
    less_than = Literal('<')

    reserved_words = Regex(r'(END|SEQUENCE|ENUMERATED|WITH)(\s|$)')

    # Forward declarations.
    value = Forward()
    type_ = Forward()
    object_ = Forward()
    object_set = Forward()
    primitive_field_name = Forward()
    constraint = Forward()
    element_set_spec = Forward()
    token_or_group_spec = Forward()
    value_reference = Forward().setName('valuereference')
    type_reference = Forward().setName('typereference')
    value_set = Forward().setName('"valueSet" not implemented')
    named_type = Forward()
    root_element_set_spec = Forward()
    defined_object_set = Forward()
    syntax_list = Forward()
    object_from_object = Forward()
    object_set_from_objects = Forward()
    defined_value = Forward().setName('DefinedValue')
    component_type_lists = Forward()
    extension_and_exception = Forward()
    optional_extension_marker = Forward()
    additional_element_set_spec = Forward()
    reference = Forward()
    defined_object_class = Forward()
    defined_type = Forward()
    module_reference = Forward()
    external_type_reference = Forward()
    external_value_reference = Forward()
    simple_defined_type = Forward()
    defined_object = Forward()
    referenced_value = Forward()
    builtin_value = Forward()
    named_value = Forward()
    sequence_value = Forward()
    signed_number = Forward()
    name_and_number_form = Forward()
    number_form = Forward().setName('numberForm')
    definitive_number_form = Forward().setName('definitiveNumberForm')
    version_number = Forward()
    union_mark = Forward()
    named_number = Forward()
    size_constraint = Forward()

    value_field_reference = Combine(ampersand + value_reference)
    type_field_reference = Combine(ampersand + type_reference)

    # ToDo: Remove size_paren as they are a workaround for
    #       SEQUENCE/SET OF.
    size_paren = (Suppress(Optional(left_parenthesis))
                  + size_constraint
                  + Suppress(Optional(right_parenthesis)))

    class_number = (number | defined_value).setName('ClassNumber')
    tag = Group(Optional(Suppress(left_bracket)
                         - Group(Optional(UNIVERSAL
                                          | APPLICATION
                                          | PRIVATE)
                                 + class_number)
                         - Suppress(right_bracket)
                         + Group(Optional(IMPLICIT | EXPLICIT))))

    any_defined_by_type = (ANY_DEFINED_BY + word)
    any_defined_by_type.setName('ANY DEFINED BY')

    identifier_list = delimitedList(identifier)

    # X.683: 8. Parameterized assignments
    dummy_reference = reference
    dummy_governor = dummy_reference
    governor = (type_ | defined_object_class)
    param_governor = (governor | dummy_governor)
    parameter = (Suppress(Optional(param_governor + colon)) + dummy_reference)
    parameter_list = Group(Optional(Suppress(left_brace)
                                    + delimitedList(parameter)
                                    + Suppress(right_brace)))

    # X.683: 9. Referencing parameterized definitions
    actual_parameter = Group(type_
                             | value
                             | value_set
                             | defined_object_class
                             | object_
                             | object_set)
    actual_parameter_list = Group(Suppress(left_brace)
                                  + delimitedList(actual_parameter)
                                  + Suppress(right_brace))
    parameterized_object = (defined_object + actual_parameter_list)
    parameterized_object_set = (defined_object_set + actual_parameter_list)
    parameterized_object_class = (defined_object_class + actual_parameter_list)
    parameterized_value_set_type = (simple_defined_type
                                    + actual_parameter_list)
    simple_defined_value = (external_value_reference
                            | value_reference)
    parameterized_value = (simple_defined_value
                           + actual_parameter_list)
    simple_defined_type <<= (external_type_reference
                             | type_reference)
    parameterized_type = (simple_defined_type
                          + actual_parameter_list)
    parameterized_reference = (reference + Optional(left_brace + right_brace))

    # X.682: 11. Contents constraints
    contents_constraint = ((CONTAINING + type_)
                           | (ENCODED_BY + value)
                           | (CONTAINING + type_ + ENCODED_BY + value))

    # X.682: 10. Table constraints, including component relation constraints
    level = OneOrMore(dot)
    component_id_list = identifier
    at_notation = (Suppress(at)
                   - (component_id_list
                      | Combine(level + component_id_list)))
    component_relation_constraint = (left_brace
                                     + Group(Group(defined_object_set))
                                     + right_brace
                                     + left_brace
                                     - Group(delimitedList(at_notation))
                                     - right_brace)
    component_relation_constraint.setName('"{"')
    simple_table_constraint = object_set
    table_constraint = (component_relation_constraint
                        | simple_table_constraint)

    # X.682: 9. User-defined constants
    user_defined_constraint_parameter = ((governor
                                          + colon
                                          + (value
                                             | value_set
                                             | object_
                                             | object_set))
                                         | type_
                                         | defined_object_class)
    user_defined_constraint = (CONSTRAINED_BY
                               - left_brace
                               - Optional(delimitedList(
                                   user_defined_constraint_parameter))
                               - right_brace)
    user_defined_constraint.setName('CONSTRAINED_BY')

    # X.682: 8. General constraint specification
    general_constraint = (user_defined_constraint
                          | table_constraint
                          | contents_constraint)

    # X.681: 7. ASN.1 lexical items
    object_set_reference = type_reference
    value_set_field_reference = NoMatch().setName(
        '"valueSetFieldReference" not implemented')
    object_field_reference = NoMatch().setName(
        '"objectFieldReference" not implemented')
    object_set_field_reference = NoMatch().setName(
        '"objectSetFieldReference" not implemented')
    object_class_reference = (NotAny(reserved_words)
                              + Regex(r'[A-Z][A-Z0-9-]*'))
    object_reference = value_reference

    # X.681: 8. Referencing definitions
    external_object_set_reference = NoMatch().setName(
        '"externalObjectSetReference" not implemented')
    defined_object_set <<= (external_object_set_reference
                            | object_set_reference)
    defined_object <<= NoMatch().setName('"definedObject" not implemented')
    defined_object_class <<= object_class_reference

    # X.681: 9. Information object class definition and assignment
    field_name = primitive_field_name
    primitive_field_name <<= (type_field_reference
                              | value_field_reference
                              | value_set_field_reference
                              | object_field_reference
                              | object_set_field_reference)
    object_set_field_spec = NoMatch().setName('"objectSetFieldSpec" not implemented')
    object_field_spec = NoMatch().setName('"objectFieldSpec" not implemented')
    variable_type_value_set_field_spec = NoMatch().setName(
        '"variableTypeValueSetFieldSpec" not implemented')
    fixed_type_value_set_field_spec = NoMatch().setName(
        '"fixedTypeValueSetFieldSpec" not implemented')
    variable_type_value_field_spec = NoMatch().setName(
        '"variableTypeValueFieldSpec" not implemented')
    fixed_type_value_field_spec = (value_field_reference
                                   + type_
                                   + Optional(UNIQUE)
                                   + Optional(OPTIONAL
                                              | (DEFAULT - value)))
    type_field_spec = (type_field_reference
                       + Optional(OPTIONAL
                                  | (DEFAULT - type_)))
    field_spec = Group(type_field_spec
                       | fixed_type_value_field_spec
                       | variable_type_value_field_spec
                       | fixed_type_value_set_field_spec
                       | variable_type_value_set_field_spec
                       | object_field_spec
                       | object_set_field_spec)
    with_syntax_spec = (WITH_SYNTAX - syntax_list)
    object_class_defn = (CLASS
                         - Suppress(left_brace)
                         - Group(delimitedList(field_spec))
                         - Suppress(right_brace)
                         - Optional(with_syntax_spec))
    object_class = (object_class_defn
                    # | defined_object_class
                    | parameterized_object_class)
    parameterized_object_class_assignment = (object_class_reference
                                             + Suppress(parameter_list)
                                             + assign
                                             + object_class)

    # X.681: 10. Syntax list
    literal = (word | comma)
    required_token = (literal | primitive_field_name)
    optional_group = (left_bracket
                      + OneOrMore(token_or_group_spec)
                      + right_bracket)
    token_or_group_spec <<= (required_token | optional_group)
    syntax_list <<= (left_brace
                     + OneOrMore(token_or_group_spec)
                     + right_brace)

    # X.681: 11. Information object definition and assignment
    setting = (type_ | value | value_set | object_ | object_set | QuotedString('"'))
    field_setting = Group(primitive_field_name + setting)
    default_syntax = (Suppress(left_brace)
                      + delimitedList(field_setting)
                      + Suppress(right_brace))
    defined_syntax_token = (literal | setting)
    defined_syntax = (left_brace + ZeroOrMore(defined_syntax_token) + right_brace)
    object_defn = Group(default_syntax | defined_syntax)
    object_ <<= (defined_object
                 | object_defn
                 | object_from_object
                 | parameterized_object)
    parameterized_object_assignment = (object_reference
                                       + Suppress(parameter_list)
                                       + defined_object_class
                                       + Suppress(assign)
                                       + object_)

    # X.681: 12. Information object set definition and assignment
    object_set_elements = (object_
                           | defined_object_set
                           | object_set_from_objects
                           | parameterized_object_set)
    object_set_spec = ((root_element_set_spec
                        + Optional(comma
                                   + ellipsis
                                   + Optional(comma
                                              + additional_element_set_spec)))
                       | (ellipsis + Optional(comma + additional_element_set_spec)))
    object_set <<= (left_brace + Group(object_set_spec) + right_brace)
    object_set.setName('"{"')
    parameterized_object_set_assignment = (object_set_reference
                                           + Suppress(parameter_list)
                                           + defined_object_class
                                           - assign
                                           - object_set)

    # X.681: 13. Associated tables

    # X.681: 14. Notation for the object class field type
    fixed_type_field_val = (builtin_value | referenced_value)
    open_type_field_val = (type_ + colon + value)
    object_class_field_value = (open_type_field_val
                                | fixed_type_field_val)
    object_class_field_type = Combine(defined_object_class
                                      + dot
                                      + field_name)
    object_class_field_type.setName('ObjectClassFieldType')

    # X.681: 15. Information from objects
    object_set_from_objects <<= NoMatch().setName(
        '"objectSetFromObjects" not implemented')
    object_from_object <<= NoMatch().setName('"objectFromObject" not implemented')

    # X.680: 49. The exception identifier
    exception_spec = Optional(
        exclamation_mark
        + NoMatch().setName('"exceptionSpec" not implemented'))

    # X.680: 47. Subtype elements
    pattern_constraint = (PATTERN + value)
    value_constraint = constraint
    presence_constraint = (PRESENT | ABSENT | OPTIONAL)
    component_constraint = (Optional(value_constraint)
                            + Optional(presence_constraint))
    named_constraint = Group(identifier + component_constraint)
    type_constraints = delimitedList(named_constraint)
    full_specification = (left_brace + Group(type_constraints) + right_brace)
    partial_specification = (left_brace
                             + Group(ellipsis
                                     + Suppress(comma)
                                     + type_constraints)
                             + right_brace)
    single_type_constraint = constraint
    multiple_type_constraints = (full_specification | partial_specification)
    with_component = WITH_COMPONENT - single_type_constraint
    with_components = WITH_COMPONENTS - multiple_type_constraints
    inner_type_constraints = with_component | with_components
    permitted_alphabet = (FROM - constraint)
    type_constraint = type_
    size_constraint <<= (SIZE - Group(constraint))
    upper_end_value = (value | MAX)
    lower_end_value = (value | MIN)
    upper_endpoint = (Optional(less_than) + upper_end_value)
    lower_endpoint = (lower_end_value + Optional(less_than))
    value_range = (((Combine(integer + dot) + Suppress(range_separator))
                    | (integer + Suppress(range_separator))
                    | (lower_endpoint + Suppress(range_separator)))
                   - upper_endpoint)
    contained_subtype = (Optional(INCLUDES) + type_)
    single_value = value
    subtype_elements = (size_constraint
                        | permitted_alphabet
                        | value_range
                        | inner_type_constraints
                        | single_value
                        | pattern_constraint
                        | contained_subtype
                        | type_constraint)
    # X.680: 46. Element set specification
    union_mark <<= (pipe | UNION)
    intersection_mark = (caret | INTERSECTION)
    elements = Group(subtype_elements
                     | object_set_elements
                     | (left_parenthesis + element_set_spec + right_parenthesis))
    unions = delimitedList(elements, delim=(union_mark | intersection_mark))
    exclusions = (EXCEPT + elements)
    element_set_spec <<= (Suppress(ALL + exclusions) | unions)
    root_element_set_spec <<= element_set_spec
    additional_element_set_spec <<= element_set_spec
    element_set_specs = (root_element_set_spec
                         + Optional(Suppress(comma) - ellipsis
                                    + Optional(Suppress(comma)
                                               - additional_element_set_spec)))

    # X.680: 45. Constrained types
    subtype_constraint = element_set_specs
    constraint_spec = (general_constraint
                       | subtype_constraint)
    constraint_spec.setName('one or more constraints')
    constraint <<= (Suppress(left_parenthesis)
                    - constraint_spec
                    - Suppress(right_parenthesis))

    # X.680: 40. Definition of unrestricted character string types
    unrestricted_character_string_type = CHARACTER_STRING
    unrestricted_character_string_value = NoMatch().setName(
        '"unrestrictedCharacterStringValue" not implemented')

    # X.680: 39. Canonical order of characters

    # X.680: 38. Specification of the ASN.1 module "ASN.1-CHARACTER-MODULE"

    # X.680: 37. Definition of restricted character string types
    group = number
    plane = number
    row = number
    cell = number
    quadruple = (left_brace
                 + group + comma
                 + plane + comma
                 + row + comma
                 + cell
                 + right_brace)
    table_column = number
    table_row = number
    tuple_ = (left_brace + table_column + comma + table_row + right_brace)
    chars_defn = (cstring | quadruple | tuple_ | defined_value)
    charsyms = delimitedList(chars_defn)
    character_string_list = (left_brace + charsyms + right_brace)
    restricted_character_string_value = (cstring
                                         | character_string_list
                                         | quadruple
                                         | tuple_)
    restricted_character_string_type = (BMPString
                                        | GeneralString
                                        | GraphicString
                                        | IA5String
                                        | ISO646String
                                        | NumericString
                                        | PrintableString
                                        | TeletexString
                                        | UTCTime
                                        | GeneralizedTime
                                        | T61String
                                        | UniversalString
                                        | UTF8String
                                        | VideotexString
                                        | VisibleString)

    # X.680: 36. Notation for character string types
    character_string_value = (restricted_character_string_value
                              | unrestricted_character_string_value)
    character_string_type = (restricted_character_string_type
                             | unrestricted_character_string_type)

    # X.680: 35. The character string types

    # X.680: 34. Notation for the external type
    # external_value = sequence_value

    # X.680: 33. Notation for embedded-pdv type
    # embedded_pdv_value = sequence_value

    # X.680: 32. Notation for relative object identifier type
    relative_oid_components = Group(number_form
                                    | name_and_number_form
                                    | defined_value)
    relative_oid_component_list = OneOrMore(relative_oid_components)
    relative_oid_value = (Suppress(left_brace)
                          + relative_oid_component_list
                          + Suppress(right_brace))

    # X.680: 31. Notation for object identifier type
    name_and_number_form <<= (identifier
                              + Suppress(left_parenthesis)
                              - number_form
                              - Suppress(right_parenthesis))
    number_form <<= (number | defined_value)
    name_form = identifier
    obj_id_components = Group(name_and_number_form
                              | defined_value
                              | number_form
                              | name_form)
    obj_id_components_list = OneOrMore(obj_id_components)
    object_identifier_value = ((Suppress(left_brace)
                                + obj_id_components_list
                                + Suppress(right_brace))
                               | (Suppress(left_brace)
                                  + defined_value
                                  + obj_id_components_list
                                  + Suppress(right_brace)))

    object_identifier_type = (OBJECT_IDENTIFIER
                              + Optional(left_parenthesis
                                         + delimitedList(word, delim='|')
                                         + right_parenthesis))
    object_identifier_type.setName('OBJECT IDENTIFIER')

    # X.680: 30. Notation for tagged types
    tagged_value = NoMatch()

    # X.680: 29. Notation for selection types

    # X.680: 28. Notation for the choice types
    alternative_type_list = delimitedList(named_type)
    extension_addition_alternatives_group = Group(left_version_brackets
                                                  + Suppress(version_number)
                                                  - Group(alternative_type_list)
                                                  - right_version_brackets)
    extension_addition_alternative = (extension_addition_alternatives_group
                                      | named_type)
    extension_addition_alternatives_list = delimitedList(extension_addition_alternative)
    extension_addition_alternatives = Optional(Suppress(comma)
                                               + extension_addition_alternatives_list)
    root_alternative_type_list = alternative_type_list
    alternative_type_lists = (root_alternative_type_list
                              + Optional(Suppress(comma)
                                         + extension_and_exception
                                         + extension_addition_alternatives
                                         + optional_extension_marker))
    choice_type = (CHOICE
                   - left_brace
                   + Group(alternative_type_lists)
                   - right_brace)
    choice_type.setName('CHOICE')
    choice_value = (identifier + colon + value)

    # X.680: 27. Notation for the set-of types
    # set_of_value = NoMatch()
    set_of_type = (SET
                   + Group(Optional(Group(size_constraint) | constraint))
                   + OF
                   + Optional(identifier)
                   - tag
                   - type_)
    set_of_type.setName('SET OF')

    # X.680: 26. Notation for the set types
    # set_value = NoMatch()
    set_type = (SET
                + left_brace
                + Group(Optional(component_type_lists
                                 | (extension_and_exception
                                    + optional_extension_marker)))
                - right_brace)
    set_type.setName('SET')

    # X.680: 25. Notation for the sequence-of types
    sequence_of_value = NoMatch()
    sequence_of_type = (SEQUENCE
                        + Group(Optional(Group(size_constraint) | constraint))
                        + OF
                        + Optional(identifier)
                        - tag
                        - type_)
    sequence_of_type.setName('SEQUENCE OF')

    # X.680: 24. Notation for the sequence types
    component_value_list = delimitedList(named_value)
    sequence_value <<= (left_brace
                        + Optional(component_value_list)
                        + right_brace)
    component_type = Group(named_type
                           + Group(Optional(OPTIONAL
                                            | (DEFAULT + value)))
                           | (COMPONENTS_OF - type_))
    version_number <<= Optional(number + Suppress(colon))
    extension_addition_group = Group(left_version_brackets
                                     + Suppress(version_number)
                                     + Group(delimitedList(component_type))
                                     + right_version_brackets)
    extension_and_exception <<= (ellipsis + Optional(exception_spec))
    extension_addition = (component_type | extension_addition_group)
    extension_addition_list = delimitedList(extension_addition)
    extension_additions = Optional(Suppress(comma) + extension_addition_list)
    extension_end_marker = (Suppress(comma) + ellipsis)
    optional_extension_marker <<= Optional(Suppress(comma) + ellipsis)
    component_type_list = delimitedList(component_type)
    root_component_type_list = component_type_list
    component_type_lists <<= ((root_component_type_list
                               + Optional(Suppress(comma)
                                          + extension_and_exception
                                          + extension_additions
                                          + ((extension_end_marker
                                              + Suppress(comma)
                                              + root_component_type_list)
                                             | optional_extension_marker)))
                              | (extension_and_exception
                                 + extension_additions
                                 + ((extension_end_marker
                                     + Suppress(comma)
                                     + root_component_type_list)
                                    | optional_extension_marker)))
    sequence_type = (SEQUENCE
                     - left_brace
                     + Group(Optional(component_type_lists
                                      | (extension_and_exception
                                         + optional_extension_marker)))
                     - right_brace)
    sequence_type.setName('SEQUENCE')

    # X.680: 23. Notation for the null type
    null_value = NULL
    null_type = NULL

    # X.680: 22. Notation for the octetstring type
    # octet_string_value = (bstring
    #                       | hstring
    #                       | (CONTAINING + value))
    octet_string_type = OCTET_STRING
    octet_string_type.setName('OCTET STRING')

    # X.680: 21. Notation for the bitstring type
    bit_string_type = (BIT_STRING
                       + Group(Optional(
                           Suppress(left_brace)
                           + delimitedList(Group(word
                                                 + Suppress(left_parenthesis)
                                                 + word
                                                 + Suppress(right_parenthesis)))
                           + Suppress(right_brace))))
    bit_string_type.setName('BIT STRING')
    bit_string_value = Tag('BitStringValue',
                           bstring
                           | hstring
                           | Tag('IdentifierList',
                                 Suppress(left_brace)
                                 + Optional(identifier_list)
                                 + Suppress(right_brace))
                           | (CONTAINING - value))

    # X.680: 20. Notation for the real type
    special_real_value = (PLUS_INFINITY
                          | MINUS_INFINITY)
    numeric_real_value = (real_number
                          | sequence_value)
    real_value = (numeric_real_value
                  | special_real_value)
    real_type = REAL
    real_type.setName('REAL')

    # X.680: 19. Notation for the enumerated type
    enumerated_value = identifier
    enumeration_item = (named_number | identifier)
    enumeration = delimitedList(enumeration_item)
    root_enumeration = enumeration
    additional_enumeration = enumeration
    enumerations = Group(Group(root_enumeration)
                         + Group(Optional(Group(Suppress(comma
                                                         - ellipsis
                                                         + exception_spec))
                                          + Optional(Suppress(comma)
                                                     - additional_enumeration))))
    enumerated_type = (ENUMERATED
                       - left_brace
                       + enumerations
                       - right_brace)
    enumerated_type.setName('ENUMERATED')

    # X.680: 18. Notation for the integer type
    integer_value = (signed_number | identifier)
    signed_number <<= Combine(Optional('-') + number)
    named_number <<= Group(identifier
                           + left_parenthesis
                           + (signed_number | defined_value)
                           + right_parenthesis)
    named_number_list = delimitedList(named_number)
    integer_type = (INTEGER
                    + Group(Optional(Suppress(left_brace)
                                     + named_number_list
                                     + Suppress(right_brace))))
    integer_type.setName('INTEGER')

    # X.680: 17. Notation for boolean type
    boolean_type = BOOLEAN
    boolean_value = (TRUE | FALSE)

    # X.680: 16. Definition of types and values
    named_value <<= (identifier + value)
    referenced_value <<= NoMatch().setName('"referencedValue" not implemented')
    builtin_value <<= (bit_string_value
                       | boolean_value
                       | character_string_value
                       | choice_value
                       | relative_oid_value
                       | sequence_value
                       # | embedded_pdv_value
                       | enumerated_value
                       # | external_value
                       # | instance_of_value
                       | real_value
                       | integer_value
                       | null_value
                       | object_identifier_value
                       # | octet_string_value
                       | sequence_of_value
                       # | set_value
                       # | set_of_value
                       | tagged_value)
    value <<= Group(object_class_field_value)
    # | referenced_value
    # | builtin_value)
    named_type <<= Group(identifier
                         - tag
                         - type_)
    referenced_type = defined_type
    referenced_type.setName('ReferencedType')
    builtin_type = (choice_type
                    | integer_type
                    | null_type
                    | real_type
                    | bit_string_type
                    | octet_string_type
                    | enumerated_type
                    | sequence_of_type
                    | sequence_type
                    | object_class_field_type
                    | set_of_type
                    | set_type
                    | object_identifier_type
                    | boolean_type
                    | character_string_type)
    type_ <<= Group((builtin_type
                     | any_defined_by_type
                     | referenced_type).setName('Type')
                    + Group(ZeroOrMore(constraint)))

    # X.680: 15. Assigning types and values
    type_reference <<= (NotAny(reserved_words)
                        + Regex(r'[A-Z][a-zA-Z0-9-]*'))
    value_reference <<= Regex(r'[a-z][a-zA-Z0-9-]*')
    value_set <<= NoMatch().setName('"valueSet" not implemented')
    parameterized_type_assignment = (type_reference
                                     + parameter_list
                                     - assign
                                     - tag
                                     - type_)
    parameterized_value_assignment = (value_reference
                                      + Suppress(parameter_list)
                                      - Group(type_)
                                      - Suppress(assign)
                                      - value)

    # X.680: 14. Notation to support references to ASN.1 components

    # X.680: 13. Referencing type and value definitions
    external_value_reference <<= (module_reference
                                  + dot
                                  + value_reference)
    external_type_reference <<= (module_reference
                                 + dot
                                 + type_reference)
    defined_type <<= (external_type_reference
                      | parameterized_type
                      | parameterized_value_set_type
                      | type_reference)
    defined_value <<= (external_value_reference
                       | parameterized_value
                       | value_reference)

    # X.680: 12. Module definition
    module_reference <<= (NotAny(reserved_words)
                          + Regex(r'[A-Z][a-zA-Z0-9-]*').setName('modulereference'))
    assigned_identifier = Suppress(Optional(object_identifier_value
                                            | (defined_value + ~(comma | FROM))))
    global_module_reference = (module_reference + assigned_identifier)
    reference <<= (type_reference
                   | value_reference
                   | object_class_reference
                   | object_reference
                   | object_set_reference)
    symbol = (parameterized_reference
              | reference)
    symbol_list = Group(delimitedList(symbol))
    symbols_from_module = (symbol_list
                           + FROM
                           + global_module_reference
                           + Suppress(Optional(WITH_SUCCESSORS
                                                | WITH_DESCENDANTS)))
    symbols_imported = ZeroOrMore(Group(symbols_from_module))
    imports = Optional(Suppress(IMPORTS)
                       - symbols_imported
                       - Suppress(semi_colon))
    symbols_exported = OneOrMore(symbol_list)
    exports = Suppress(Optional(EXPORTS
                                - (ALL | symbols_exported) + semi_colon))
    assignment = (parameterized_object_set_assignment
                  | parameterized_object_assignment
                  | parameterized_object_class_assignment
                  | parameterized_type_assignment
                  | parameterized_value_assignment)
    assignment_list = ZeroOrMore(assignment)
    module_body = (exports + imports + assignment_list)
    definitive_name_and_number_form = (identifier
                                       + Suppress(left_parenthesis)
                                       - definitive_number_form
                                       - Suppress(right_parenthesis))
    definitive_number_form <<= number
    definitive_obj_id_component = Group(definitive_name_and_number_form
                                        | name_form
                                        | definitive_number_form)
    definitive_obj_id_components_list = OneOrMore(definitive_obj_id_component)
    definitive_identifier = Group(Optional(Suppress(left_brace)
                                           - definitive_obj_id_components_list
                                           - Suppress(right_brace)))
    module_identifier = (module_reference
                         + definitive_identifier)
    tag_default = Group(Optional((AUTOMATIC | EXPLICIT | IMPLICIT) + TAGS))
    extension_default = Group(Optional(EXTENSIBILITY_IMPLIED))
    module_definition = (Group(module_identifier
                               - Suppress(DEFINITIONS)
                               + tag_default
                               + extension_default
                               - Suppress(assign)
                               - Suppress(BEGIN))
                         + Group(module_body)
                         - Suppress(END))

    # The whole specification.
    specification = OneOrMore(module_definition) + StringEnd()

    # Parse actions converting tokens to asn1tools representation.
    integer.setParseAction(convert_integer)
    signed_number.setParseAction(convert_integer)
    real_number.setParseAction(convert_real_number)
    bstring.setParseAction(convert_bstring)
    hstring.setParseAction(convert_hstring)
    value_range.setParseAction(convert_value_range)
    with_component.setParseAction(convert_inner_type_constraints)
    with_components.setParseAction(convert_inner_type_constraints)
    size_constraint.setParseAction(convert_size_constraint)
    permitted_alphabet.setParseAction(convert_permitted_alphabet)
    constraint.setParseAction(convert_constraint)
    module_body.setParseAction(convert_module_body)
    specification.setParseAction(convert_specification)
    module_definition.setParseAction(convert_module_definition)
    assignment_list.setParseAction(convert_assignment_list)
    imports.setParseAction(convert_imports)
    parameterized_object_set_assignment.setParseAction(
        convert_parameterized_object_set_assignment)
    parameterized_object_assignment.setParseAction(
        convert_parameterized_object_assignment)
    parameterized_object_class_assignment.setParseAction(
        convert_parameterized_object_class_assignment)
    parameterized_type_assignment.setParseAction(
        convert_parameterized_type_assignment)
    parameterized_value_assignment.setParseAction(
        convert_parameterized_value_assignment)
    sequence_type.setParseAction(convert_sequence_type)
    sequence_of_type.setParseAction(convert_sequence_of_type)
    set_type.setParseAction(convert_set_type)
    set_of_type.setParseAction(convert_set_of_type)
    integer_type.setParseAction(convert_integer_type)
    real_type.setParseAction(convert_real_type)
    boolean_type.setParseAction(convert_boolean_type)
    bit_string_type.setParseAction(convert_bit_string_type)
    octet_string_type.setParseAction(convert_octet_string_type)
    null_type.setParseAction(convert_null_type)
    object_identifier_type.setParseAction(convert_object_identifier_type)
    enumerated_type.setParseAction(convert_enumerated_type)
    choice_type.setParseAction(convert_choice_type)
    defined_type.setParseAction(convert_defined_type)
    character_string_type.setParseAction(convert_keyword_type)
    object_class_field_type.setParseAction(convert_keyword_type)
    any_defined_by_type.setParseAction(convert_any_defined_by_type)
    actual_parameter_list.setParseAction(convert_actual_parameter_list)
    parameter_list.setParseAction(convert_parameter_list)

    return specification


def ignore_comments(string):
    """Ignore comments in given string by replacing them with spaces. This
    reduces the parsing time by roughly a factor of two.

    """

    comments = [
        (mo.start(), mo.group(0))
        for mo in re.finditer(r'(/\*|\*/|--|\n)', string)
    ]

    comments.sort()

    in_single_line_comment = False
    multi_line_comment_depth = 0
    start_offset = 0
    non_comment_offset = 0
    chunks = []

    for offset, kind in comments:
        if in_single_line_comment:
            if kind in ['--', '\n']:
                in_single_line_comment = False

                if kind == '--':
                    offset += 2

                chunks.append(' ' * (offset - start_offset))
                non_comment_offset = offset
        elif multi_line_comment_depth > 0:
            if kind == '/*':
                multi_line_comment_depth += 1
            elif kind == '*/':
                multi_line_comment_depth -= 1

                if multi_line_comment_depth == 0:
                    offset += 2
                    chunks.append(' ' * (offset - start_offset))
                    non_comment_offset = offset
        elif kind == '--':
            in_single_line_comment = True
            start_offset = offset
            chunks.append(string[non_comment_offset:start_offset])
        elif kind == '/*':
            multi_line_comment_depth = 1
            start_offset = offset
            chunks.append(string[non_comment_offset:start_offset])

    if in_single_line_comment:
        raise ParseSyntaxException(
            string,
            start_offset,
            'Missing newline or -- for single line comment')

    if multi_line_comment_depth > 0:
        raise ParseSyntaxException(
            string,
            start_offset,
            'Missing */ for multi line comment')

    chunks.append(string[non_comment_offset:])

    return ''.join(chunks)


def parse_string(string):
    """Parse given ASN.1 specification string and return a dictionary of
    its contents.

    The dictionary can later be compiled with
    :func:`~asn1tools.compile_dict()`.

    >>> with open('foo.asn') as fin:
    ...     foo = asn1tools.parse_string(fin.read())

    """

    grammar = create_grammar()

    try:
        string = ignore_comments(string)
        tokens = grammar.parseString(string).asList()
    except (ParseException, ParseSyntaxException) as e:
        raise ParseError("Invalid ASN.1 syntax at line {}, column {}: '{}': {}.".format(
            e.lineno,
            e.column,
            e.markInputline(),
            e.msg))

    return tokens[0]


def parse_files(filenames, encoding='utf-8'):
    """Parse given ASN.1 specification file(s) and return a dictionary of
    its/their contents.

    The dictionary can later be compiled with
    :func:`~asn1tools.compile_dict()`.

    `encoding` is the text encoding. This argument is passed to the
    built-in function `open()`.

    >>> foo = asn1tools.parse_files('foo.asn')

    """

    if isinstance(filenames, str):
        filenames = [filenames]

    string = ''

    for filename in filenames:
        if sys.version_info[0] < 3:
            with open(filename, 'r') as fin:
                string += fin.read()
                string += '\n'
        else:
            with open(filename, 'r', encoding=encoding, errors='replace') as fin:
                string += fin.read()
                string += '\n'

    return parse_string(string)
