# -*- coding: utf-8 -*-
#
# Copyright (c) 2009 by Gustavo Narea <http://gustavonarea.net/>.
#
# This file is part of Booleano <http://code.gustavonarea.net/booleano/>.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, distribute with
# modifications, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
# IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name(s) of the above copyright
# holders shall not be used in advertising or otherwise to promote the sale,
# use or other dealings in this Software without prior written authorization.
"""
Adaptive grammar definition and handling.

"""

from booleano.exc import GrammarError


__all__ = ("Grammar", )


class Grammar(object):
    """
    Adaptive grammar.
    
    Instances of this class contain the properties of the grammar, but are not
    able to generate a parser based on the represented grammar -- that is
    a :class:`Parser`'s job.
    
    """
    
    default_tokens = {
        # Some logical connectives:
        'not': "~",
        'and': "&",
        'or': "|",
        'xor': "^",
        # Relational operators:
        'eq': "==",
        'ne': "!=",
        'lt': "<",
        'gt': ">",
        'le': "<=",
        'ge': ">=",
        # Set operators:
        'belongs_to': u"∈",
        'is_subset': u"⊂",
        'set_start': "{",
        'set_end': "}",
        'element_separator': ",",
        # Grouping marks:
        'string_start': '"',
        'string_end': '"',
        'group_start': "(",
        'group_end': ")",
        # Marks related to function arguments:
        'arguments_start': "(",
        'arguments_end': ")",
        'arguments_separator': ",",
        # Numeric-related tokens:
        'positive_sign': "+",
        'negative_sign': "-",
        'decimal_separator': ".",
        'thousands_separator': ",",
        # Miscellaneous tokens:
        'identifier_spacing': "_",
        'namespace_separator': ":",
    }
    
    default_settings = {
        'superset_right_in_is_subset': True,
        'set_right_in_contains': True,
        'optional_positive_sign': True,
    }
    
    known_generators = set([
        "operation",
        "string",
        "number",
    ])
    
    def __init__(self, settings=None, generators=None, **tokens):
        """
        Set up a grammar, possibly customizing its properties.
        
        :param custom_settings: The grammar settings to be overridden, if any.
        :type custom_settings: dict
        :param custom_generators: The custom generators for the parser to be
            generated, if any.
        :type custom_generators: dict
        
        Keyword arguments represent the tokens to be overridden.
        
        """
        self._custom_settings = {}
        self._custom_generators = {}
        self._custom_tokens = {}
        # Setting the custom properties:
        settings = settings or {}
        generators = generators or {}
        for (setting_name, setting) in settings.items():
            self.set_setting(setting_name, setting)
        for (generator_name, generator) in generators.items():
            self.set_custom_generator(generator_name, generator)
        for (token_name, token) in tokens.items():
            self.set_token(token_name, token)
    
    #{ Token handling
    
    def get_token(self, token_name):
        """
        Return the token called ``token_name``.
        
        :param token_name: The name/key of the token to be returned.
        :type token_name: basestring
        :return: The requested token.
        :rtype: basestring
        :raises GrammarError: If the ``token_name`` is unknown.
        
        If the token doesn't have a custom value, the default value will be
        returned instead.
        
        """
        self._check_token_existence(token_name)
        return self._custom_tokens.get(token_name,
                                       self.default_tokens[token_name])
    
    def set_token(self, token_name, token):
        """
        Set the token called ``token_name`` to the custom value ``token``.
        
        :param token_name: The name of the token to be customized.
        :type token_name: basestring
        :param token: The new value of the token.
        :type token: basestring
        :raises GrammarError: If the ``token_name`` is unknown.
        
        """
        self._check_token_existence(token_name)
        self._custom_tokens[token_name] = token
    
    def _check_token_existence(self, token_name):
        """
        Check that ``token_name`` is a known token.
        
        :param token_name: The token's name.
        :type token_name: basestring
        :raises GrammarError: If the ``token_name`` is unknown.
        
        """
        if token_name not in self.default_tokens:
            raise GrammarError('Unknown token "%s"' % token_name)
    
    #{ Settings handling
    
    def get_setting(self, setting_name):
        """
        Return the value of setting identified by ``setting_name``.
        
        :param setting_name: The name of the setting to be retrieved.
        :type setting_name: basestring
        :return: The setting value.
        :raises GrammarError: If the ``setting_name`` is unknown.
        
        """
        self._check_setting_existence(setting_name)
        return self._custom_settings.get(setting_name,
                                         self.default_settings[setting_name])
    
    def set_setting(self, setting_name, setting):
        """
        Set the setting called ``setting_name`` to the custom value ``setting``.
        
        :param setting_name: The name of the setting to be customized.
        :type setting_name: basestring
        :param setting: The new value of the setting.
        :type setting: basestring
        :raises GrammarError: If the ``setting_name`` is unknown or ``setting``
            is an invalid value for ``setting_name``.
        
        Settings whose expected value is a Python boolean are not validated.
        
        """
        self._check_setting_existence(setting_name)
        self._custom_settings[setting_name] = setting
    
    def _check_setting_existence(self, setting_name):
        """
        Check that ``setting_name`` is a known setting.
        
        :param setting_name: The setting's name.
        :type setting_name: basestring
        :raises GrammarError: If the ``setting_name`` is unknown.
        
        """
        if setting_name not in self.default_settings:
            raise GrammarError('Unknown setting "%s"' % setting_name)
    
    # Custom generator handling
    
    def get_custom_generator(self, generator_name):
        """
        Return the generator identified by ``generator_name``.
        
        :param generator_name: The name of the generator to be retrieved.
        :type generator_name: basestring
        :return: The generator value or ``None`` if it's not been set.
        :raises GrammarError: If the ``generator_name`` is unknown.
        
        """
        self._check_generator_existence(generator_name)
        return self._custom_generators.get(generator_name)
    
    def set_custom_generator(self, generator_name, generator):
        """
        Set the generator called ``generator_name`` to the custom callable
        ``generator``.
        
        :param generator_name: The name of the generator to be overridden.
        :type generator_name: basestring
        :param generator: The custom generator (a Python callable).
        :raises GrammarError: If the ``generator_name`` is unknown.
        
        """
        self._check_generator_existence(generator_name)
        self._custom_generators[generator_name] = generator
    
    def _check_generator_existence(self, generator_name):
        """
        Check that ``generator_name`` is a known generator.
        
        :param generator_name: The generator's name.
        :type generator_name: basestring
        :raises GrammarError: If the ``generator_name`` is unknown.
        
        """
        if generator_name not in self.known_generators:
            raise GrammarError('Unknown generator "%s"' % generator_name)
    
    #}
