<gh_stars>0
# Copyright (c) 2013-2015, Schneider Electric Buildings AB
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of Schneider Electric Buildings AB nor the
#       names of contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import re
from copy import copy
from pyparsing import Keyword, Literal, Word, OneOrMore, ZeroOrMore, Combine, Regex, Forward, Optional, Group, Suppress, delimitedList, cStyleComment, nums, srange, dblQuotedString, Or, CaselessLiteral


__all__ = ['parse_asn1', 'AnnotatedToken']


def parse_asn1(asn1_definition):
    """ Parse a string containing one or more ASN.1 module definitions.
    Returns a list of module syntax trees represented as nested lists of
    AnnotatedToken objects.
    """
    grammar = _build_asn1_grammar()
    parse_result = grammar.parseString(asn1_definition)
    parse_tree = parse_result.asList()
    return parse_tree


def print_parse_tree(node, indent=1):
    """ Debugging aid. Dumps a parse tree as returned
    from parse_asn1 to stdout in indented tree form.
    """
    def indented_print(msg):
        print(' ' * indent + msg)

    if type(node) is AnnotatedToken:
        # tagged token
        tag, values = node.ty, node.elements
        indented_print('%s:' % tag)
        print_parse_tree(values, indent + 1)
    elif type(node) is list:
        # token list
        for token in node:
            print_parse_tree(token, indent + 1)
    else:
        # token
        indented_print(str(node))


class AnnotatedToken(object):
    """ A simple data structure to keep track of a token's
    type, identified by a string, and its children.
    Children may be other annotated tokens, lists or simple
    strings.
    """
    def __init__(self, token_type, elements):
        self.ty = token_type
        self.elements = elements

    def __str__(self):
        return 'T(%s)%s' % (self.ty, self.elements)

    __repr__ = __str__


def _build_asn1_grammar():
    def build_identifier(prefix_pattern):
        identifier_suffix = Optional(Word(srange('[-0-9a-zA-Z]')))
        identifier = Combine(Word(srange(prefix_pattern), exact=1) + identifier_suffix)  # todo: more rigorous? trailing hyphens and -- forbidden
        return identifier

    def braced_list(element_rule):
        return Suppress('{') + Group(delimitedList(element_rule)) + Suppress('}')

    def annotate(name):
        def annotation(t):
            return AnnotatedToken(name, t.asList())

        return annotation

    # Reserved words
    ANY = Keyword('ANY')
    DEFINED_BY = Keyword('DEFINED BY')
    DEFINITIONS = Keyword('DEFINITIONS')
    BEGIN = Keyword('BEGIN')
    END = Keyword('END')
    OPTIONAL = Keyword('OPTIONAL')
    DEFAULT = Keyword('DEFAULT')
    TRUE = Keyword('TRUE')
    FALSE = Keyword('FALSE')
    UNIVERSAL = Keyword('UNIVERSAL')
    APPLICATION = Keyword('APPLICATION')
    PRIVATE = Keyword('PRIVATE')
    MIN = Keyword('MIN')
    MAX = Keyword('MAX')
    IMPLICIT = Keyword('IMPLICIT')
    EXPLICIT = Keyword('EXPLICIT')
    EXPLICIT_TAGS = Keyword('EXPLICIT TAGS')
    IMPLICIT_TAGS = Keyword('IMPLICIT TAGS')
    AUTOMATIC_TAGS = Keyword('AUTOMATIC TAGS')
    EXTENSIBILITY_IMPLIED = Keyword('EXTENSIBILITY IMPLIED')
    COMPONENTS_OF = Keyword('COMPONENTS OF')
    ELLIPSIS = Keyword('...')
    SIZE = Keyword('SIZE')
    OF = Keyword('OF')
    IMPORTS = Keyword('IMPORTS')
    EXPORTS = Keyword('EXPORTS')
    FROM = Keyword('FROM')

    # Built-in types
    SEQUENCE = Keyword('SEQUENCE')
    SET = Keyword('SET')
    CHOICE = Keyword('CHOICE')
    ENUMERATED = Keyword('ENUMERATED')
    BIT_STRING = Keyword('BIT STRING')
    BOOLEAN = Keyword('BOOLEAN')
    REAL = Keyword('REAL')
    OCTET_STRING = Keyword('OCTET STRING')
    CHARACTER_STRING = Keyword('CHARACTER STRING')
    NULL = Keyword('NULL')
    INTEGER = Keyword('INTEGER')
    OBJECT_IDENTIFIER = Keyword('OBJECT IDENTIFIER')

    # Restricted string types
    BMPString = Keyword('BMPString')
    GeneralString = Keyword('GeneralString')
    GraphicString = Keyword('GraphicString')
    IA5String =  Keyword('IA5String')
    ISO646String = Keyword('ISO646String')
    NumericString = Keyword('NumericString')
    PrintableString = Keyword('PrintableString')
    TeletexString = Keyword('TeletexString')
    T61String = Keyword('T61String')
    UniversalString = Keyword('UniversalString')
    UTF8String = Keyword('UTF8String')
    VideotexString = Keyword('VideotexString')
    VisibleString = Keyword('VisibleString')

    # Useful types
    GeneralizedTime = Keyword('GeneralizedTime')
    UTCTime = Keyword('UTCTime')
    ObjectDescriptor = Keyword('ObjectDescriptor')

    # Literals
    number = Word(nums)
    signed_number = Combine(Optional('-') + number)  # todo: consider defined values from 18.1
    bstring = Suppress('\'') + StringOf('01') + Suppress('\'B')
    hstring = Suppress('\'') + StringOf('0123456789ABCDEF') + Suppress('\'H')

    # Comments
    hyphen_comment = Regex(r"--[\s\S]*?(--|$)", flags=re.MULTILINE)
    comment = hyphen_comment | cStyleComment

    # identifier
    identifier = build_identifier('[a-z]')

    # references
    # these are duplicated to force unique token annotations
    valuereference = build_identifier('[a-z]')
    typereference = build_identifier('[A-Z]')
    module_reference = build_identifier('[A-Z]')
    reference = valuereference | typereference  # TODO: consider object references from 12.1

    # values
    # todo: consider more literals from 16.9
    boolean_value = TRUE | FALSE
    bitstring_value = bstring | hstring  # todo: consider more forms from 21.9
    integer_value = signed_number
    null_value = NULL
    cstring_value = dblQuotedString

    exponent = CaselessLiteral('e') + signed_number
    real_value = Combine(signed_number + Optional(Literal('.') + Optional(number)) + Optional(exponent))

    # In value range constraints, decimal points must be followed by number, or
    # the grammar becomes ambiguous: ([1.].100) vs ([1]..[100])
    constraint_real_value = Combine(signed_number + Optional(Literal('.') + number) + Optional(exponent))

    builtin_value = boolean_value | bitstring_value | real_value | integer_value | null_value | cstring_value
    external_value_reference = module_reference + Suppress('.') + valuereference
    defined_value = external_value_reference | 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)
    number_form = Unique(number)
    name_and_number_form = name_form + Suppress('(') + number_form + Suppress(')')
    objid_components = name_and_number_form | name_form | number_form | defined_value
    objid_components_list = OneOrMore(objid_components)
    object_identifier_value = Suppress('{') + \
                              (objid_components_list | (defined_value + objid_components_list)) + \
                              Suppress('}')

    value = builtin_value | referenced_value | object_identifier_value

    # definitive identifier value
    definitive_number_form = Unique(number)
    definitive_name_and_number_form = name_form + Suppress('(') + definitive_number_form + Suppress(')')
    definitive_objid_component = definitive_name_and_number_form | name_form | definitive_number_form
    definitive_objid_component_list = OneOrMore(definitive_objid_component)
    definitive_identifier = Optional(Suppress('{') + definitive_objid_component_list + Suppress('}'))

    # tags
    class_ = UNIVERSAL | APPLICATION | PRIVATE
    class_number = Unique(number) # todo: consider defined values from 30.1
    tag = Suppress('[') + Optional(class_) + class_number + Suppress(']')
    tag_default = EXPLICIT_TAGS | IMPLICIT_TAGS | AUTOMATIC_TAGS

    # extensions
    extension_default = Unique(EXTENSIBILITY_IMPLIED)

    # values

    # Forward-declare these, they can only be fully defined once
    # we have all types defined. There are some circular dependencies.
    named_type = Forward()
    type_ = Forward()

    # constraints
    # todo: consider the full subtype and general constraint syntax described in 45.*
    lower_bound = (constraint_real_value | signed_number | referenced_value | MIN)
    upper_bound = (constraint_real_value | signed_number | referenced_value | MAX)
    single_value_constraint = Suppress('(') + value + Suppress(')')
    value_range_constraint = Suppress('(') + lower_bound + Suppress('..') + upper_bound + Suppress(')')
    # TODO: Include contained subtype constraint here if we ever implement it.
    size_constraint = Optional(Suppress('(')) + Suppress(SIZE) + (single_value_constraint | value_range_constraint) + Optional(Suppress(')'))

    # types
    # todo: consider other defined types from 13.1
    defined_type = Optional(module_reference + Suppress('.'), default=None) + typereference + Optional(size_constraint, default=None)

    # TODO: consider exception syntax from 24.1
    extension_marker = Unique(ELLIPSIS)

    component_type_optional = named_type + Suppress(OPTIONAL)
    component_type_default = named_type + Suppress(DEFAULT) + value
    component_type_components_of = Suppress(COMPONENTS_OF) + type_
    component_type = component_type_components_of | component_type_optional | component_type_default | named_type

    tagged_type = tag + Optional(IMPLICIT | EXPLICIT, default=None) + type_

    named_number_value = Suppress('(') + signed_number + Suppress(')')
    named_number = identifier + named_number_value
    named_nonumber = Unique(identifier)
    enumeration = named_number | named_nonumber

    set_type = SET + braced_list(component_type | extension_marker)
    sequence_type = SEQUENCE + braced_list(component_type | extension_marker)
    sequenceof_type = Suppress(SEQUENCE) + Optional(size_constraint, default=None) + Suppress(OF) + (type_ | named_type)
    setof_type = Suppress(SET) + Optional(size_constraint, default=None) + Suppress(OF) + (type_ | named_type)
    choice_type = CHOICE + braced_list(named_type | extension_marker)
    selection_type = identifier + Suppress('<') + type_
    enumerated_type = ENUMERATED + braced_list(enumeration | extension_marker)
    bitstring_type = BIT_STRING + Optional(braced_list(named_number), default=[]) + Optional(single_value_constraint | size_constraint, default=None)
    plain_integer_type = INTEGER
    restricted_integer_type = INTEGER + braced_list(named_number) + Optional(single_value_constraint, default=None)
    boolean_type = BOOLEAN
    real_type = REAL
    null_type = NULL
    object_identifier_type = OBJECT_IDENTIFIER
    octetstring_type = OCTET_STRING + Optional(size_constraint)
    unrestricted_characterstring_type = CHARACTER_STRING
    restricted_characterstring_type = BMPString | GeneralString | \
                                      GraphicString | IA5String | \
                                      ISO646String | NumericString | \
                                      PrintableString | TeletexString | \
                                      T61String | UniversalString | \
                                      UTF8String | VideotexString | \
                                      VisibleString
    characterstring_type = (restricted_characterstring_type | unrestricted_characterstring_type) + Optional(size_constraint)
    useful_type = GeneralizedTime | UTCTime | ObjectDescriptor

    # ANY type
    any_type = ANY + Optional(Suppress(DEFINED_BY + identifier))

    # todo: consider other builtins from 16.2
    simple_type = (any_type | boolean_type | null_type | octetstring_type | characterstring_type | real_type | plain_integer_type | object_identifier_type | useful_type) + Optional(value_range_constraint | single_value_constraint)
    constructed_type = choice_type | sequence_type | set_type
    value_list_type = restricted_integer_type | enumerated_type
    builtin_type = value_list_type | tagged_type | simple_type | constructed_type | sequenceof_type | setof_type | bitstring_type

    referenced_type = defined_type | selection_type  # todo: consider other ref:d types from 16.3

    type_ << (builtin_type | referenced_type)

    # EXT: identifier should not be Optional here, but
    # our other ASN.1 code generator supports unnamed members,
    # and we use them.
    named_type << (Optional(identifier) + type_)

    type_assignment = typereference + '::=' + type_
    value_assignment = valuereference + type_ + '::=' + value

    assignment = type_assignment | value_assignment
    assignment_list = ZeroOrMore(assignment)

    assigned_identifier = Optional(object_identifier_value | defined_value)
    global_module_reference = module_reference + assigned_identifier

    symbol = Unique(reference)  # TODO: parameterized reference?
    symbol_list = Group(delimitedList(symbol))
    symbols_from_module = symbol_list + Suppress(FROM) + global_module_reference
    symbols_from_module_list = OneOrMore(symbols_from_module)
    symbols_imported = Optional(symbols_from_module_list)
    exports = Optional(Suppress(EXPORTS) + symbol_list + Suppress(';'))
    imports = Optional(Suppress(IMPORTS) + symbols_imported + Suppress(';'))

    module_body = (exports + imports + assignment_list)
    module_identifier = module_reference + definitive_identifier
    module_definition = module_identifier + Suppress(DEFINITIONS) + Optional(tag_default, default=None) + \
                        Optional(extension_default, default=None) + Suppress('::=') + Suppress(BEGIN) + module_body + Suppress(END)

    module_definition.ignore(comment)

    # Mark up the parse results with token tags
    identifier.setParseAction(annotate('Identifier'))
    named_number_value.setParseAction(annotate('Value'))
    tag.setParseAction(annotate('Tag'))
    class_.setParseAction(annotate('TagClass'))
    class_number.setParseAction(annotate('TagClassNumber'))
    type_.setParseAction(annotate('Type'))
    simple_type.setParseAction(annotate('SimpleType'))
    choice_type.setParseAction(annotate('ChoiceType'))
    sequence_type.setParseAction(annotate('SequenceType'))
    set_type.setParseAction(annotate('SetType'))
    value_list_type.setParseAction(annotate('ValueListType'))
    bitstring_type.setParseAction(annotate('BitStringType'))
    sequenceof_type.setParseAction(annotate('SequenceOfType'))
    setof_type.setParseAction(annotate('SetOfType'))
    named_number.setParseAction(annotate('NamedValue'))
    named_nonumber.setParseAction(annotate('NamedValue'))
    single_value_constraint.setParseAction(annotate('SingleValueConstraint'))
    size_constraint.setParseAction(annotate('SizeConstraint'))
    value_range_constraint.setParseAction(annotate('ValueRangeConstraint'))
    component_type.setParseAction(annotate('ComponentType'))
    component_type_optional.setParseAction(annotate('ComponentTypeOptional'))
    component_type_default.setParseAction(annotate('ComponentTypeDefault'))
    component_type_components_of.setParseAction(annotate('ComponentTypeComponentsOf'))
    tagged_type.setParseAction(annotate('TaggedType'))
    named_type.setParseAction(annotate('NamedType'))
    type_assignment.setParseAction(annotate('TypeAssignment'))
    value_assignment.setParseAction(annotate('ValueAssignment'))
    module_reference.setParseAction(annotate('ModuleReference'))
    module_body.setParseAction(annotate('ModuleBody'))
    module_definition.setParseAction(annotate('ModuleDefinition'))
    extension_marker.setParseAction(annotate('ExtensionMarker'))
    name_form.setParseAction(annotate('NameForm'))
    number_form.setParseAction(annotate('NumberForm'))
    name_and_number_form.setParseAction(annotate('NameAndNumberForm'))
    object_identifier_value.setParseAction(annotate('ObjectIdentifierValue'))
    definitive_identifier.setParseAction(annotate('DefinitiveIdentifier'))
    definitive_number_form.setParseAction(annotate('DefinitiveNumberForm'))
    definitive_name_and_number_form.setParseAction(annotate('DefinitiveNameAndNumberForm'))
    imports.setParseAction(annotate('Imports'))
    exports.setParseAction(annotate('Exports'))
    assignment_list.setParseAction(annotate('AssignmentList'))
    bstring.setParseAction(annotate('BinaryStringValue'))
    hstring.setParseAction(annotate('HexStringValue'))
    defined_type.setParseAction(annotate('DefinedType'))
    selection_type.setParseAction(annotate('SelectionType'))
    referenced_value.setParseAction(annotate('ReferencedValue'))

    start = OneOrMore(module_definition)
    return start


def Unique(token):
    """ Use to create a distinct name of a production
    with the same form as another, e.g.
      identifier = build_identifier('[a-z]')
      valuereference = build_identifier('[a-z]')
    We prefer:
      identifier = build_identifier('[a-z]')
      valuereference = Unique(identifier)
    to avoid duplicating the details of the grammar.
    This allows unique parse actions for productions
    with the same underlying rules.
    """
    return copy(token)


def StringOf(elements):
    """ Create a rule to parse a string of any of the chars in elements.
    Skips any whitespace.
    This is useful for the ASN.1 hstring and bstring productions.
    """
    element = CharSet(elements)
    return Combine(OneOrMore(element), adjacent=False)  # Use adjacent=False to skip whitespace


def CharSet(elements):
    """ Create a set of valid characters as a single rule.
    elements is a string containing all the desired chars, e.g.
      CharSet('01234567890')        # all numbers
      CharSet('01234567890ABCDEF')  # all hex numbers
    """
    unpacked_chars = [Literal(c) for c in elements]
    return Or(unpacked_chars)
