<commit_before>from __future__ import absolute_import

from functools import partial
import logging
import operator
import re

import six

import scss.config as config
from scss.cssdefs import COLOR_NAMES, is_builtin_css_function, _expr_glob_re, _interpolate_re, _variable_re
from scss.rule import Namespace
from scss.types import Boolean, Color, List, Map, Null, Number, ParserValue, String, Undefined
from scss.util import dequote, normalize_var

################################################################################
# Load C acceleration modules
Scanner = None
try:
    from scss._speedups import NoMoreTokens, Scanner
except ImportError:
    from scss._native import NoMoreTokens, Scanner

log = logging.getLogger(__name__)

FATAL_UNDEFINED = True
ast_cache = {}


class Calculator(object):
    """Expression evaluator."""

    def __init__(self, namespace=None):
        if namespace is None:
            self.namespace = Namespace()
        else:
            self.namespace = namespace

    def _pound_substitute(self, result):
        expr = result.group(1)
        value = self.evaluate_expression(expr)

        if value is None:
            return self.apply_vars(expr)
        elif value.is_null:
            return ""
        else:
            return dequote(value.render())

    def do_glob_math(self, cont):
        """Performs #{}-interpolation.  The result is always treated as a fixed
        syntactic unit and will not be re-evaluated.
        """
        # TODO this should really accept and/or parse an *expression* and
        # return a type  :|
        cont = str(cont)
        if '#{' not in cont:
            return cont
        cont = _expr_glob_re.sub(self._pound_substitute, cont)
        return cont

    def apply_vars(self, cont):
        if isinstance(cont, six.string_types) and '$' in cont:
            try:
                # Optimization: the full cont is a variable in the context,
                cont = self.namespace.variable(cont)
            except KeyError:
                # Interpolate variables:
                def _av(m):
                    v = None
                    n = m.group(2)
                    try:
                        v = self.namespace.variable(n)
                    except KeyError:
                        if FATAL_UNDEFINED:
                            raise
                        else:
                            log.error("Undefined variable '%s'", n, extra={'stack': True})
                            return n
                    else:
                        if v:
                            if not isinstance(v, six.string_types):
                                v = v.render()
                            # TODO this used to test for _dequote
                            if m.group(1):
                                v = dequote(v)
                        else:
                            v = m.group(0)
                        return v

                cont = _interpolate_re.sub(_av, cont)
        # XXX what?: if options is not None:
        # ...apply math:
        cont = self.do_glob_math(cont)
        return cont

    def calculate(self, _base_str, divide=False):
        better_expr_str = _base_str

        better_expr_str = self.do_glob_math(better_expr_str)

        better_expr_str = self.evaluate_expression(better_expr_str, divide=divide)

        if better_expr_str is None:
            better_expr_str = String.unquoted(self.apply_vars(_base_str))

        return better_expr_str

    # TODO only used by magic-import...?
    def interpolate(self, var):
        value = self.namespace.variable(var)
        if var != value and isinstance(value, six.string_types):
            _vi = self.evaluate_expression(value)
            if _vi is not None:
                value = _vi
        return value

    def evaluate_expression(self, expr, divide=False):
        if not isinstance(expr, six.string_types):
            raise TypeError("Expected string, got %r" % (expr,))

        if expr in ast_cache:
            ast = ast_cache[expr]

        elif _variable_re.match(expr):
            # Short-circuit for variable names
            ast = Variable(expr)

        else:
            try:
                parser = SassExpression(SassExpressionScanner(expr))
                ast = parser.goal()
            except SyntaxError:
                if config.DEBUG:
                    raise
                return None
            except Exception:
                # TODO hoist me up since the rule is gone
                #log.exception("Exception raised: %s in `%s' (%s)", e, expr, rule.file_and_line)
                if config.DEBUG:
                    raise
                return None
            else:
                ast_cache[expr] = ast

        return ast.evaluate(self, divide=divide)

    def parse_expression(self, expr, target='goal'):
        if expr in ast_cache:
            return ast_cache[expr]

        parser = SassExpression(SassExpressionScanner(expr))
        ast = getattr(parser, target)()

        if target == 'goal':
            ast_cache[expr] = ast

        return ast
<commit_msg>Exceptions during evaluation are ignored unless DEBUG is on (otherwise things get broken, like 'filter: grayscale(1) brightness(.1);' for example)<commit_after>from __future__ import absolute_import

from functools import partial
import logging
import operator
import re

import six

import scss.config as config
from scss.cssdefs import COLOR_NAMES, is_builtin_css_function, _expr_glob_re, _interpolate_re, _variable_re
from scss.rule import Namespace
from scss.types import Boolean, Color, List, Map, Null, Number, ParserValue, String, Undefined
from scss.util import dequote, normalize_var

################################################################################
# Load C acceleration modules
Scanner = None
try:
    from scss._speedups import NoMoreTokens, Scanner
except ImportError:
    from scss._native import NoMoreTokens, Scanner

log = logging.getLogger(__name__)

FATAL_UNDEFINED = True
ast_cache = {}


class Calculator(object):
    """Expression evaluator."""

    def __init__(self, namespace=None):
        if namespace is None:
            self.namespace = Namespace()
        else:
            self.namespace = namespace

    def _pound_substitute(self, result):
        expr = result.group(1)
        value = self.evaluate_expression(expr)

        if value is None:
            return self.apply_vars(expr)
        elif value.is_null:
            return ""
        else:
            return dequote(value.render())

    def do_glob_math(self, cont):
        """Performs #{}-interpolation.  The result is always treated as a fixed
        syntactic unit and will not be re-evaluated.
        """
        # TODO this should really accept and/or parse an *expression* and
        # return a type  :|
        cont = str(cont)
        if '#{' not in cont:
            return cont
        cont = _expr_glob_re.sub(self._pound_substitute, cont)
        return cont

    def apply_vars(self, cont):
        if isinstance(cont, six.string_types) and '$' in cont:
            try:
                # Optimization: the full cont is a variable in the context,
                cont = self.namespace.variable(cont)
            except KeyError:
                # Interpolate variables:
                def _av(m):
                    v = None
                    n = m.group(2)
                    try:
                        v = self.namespace.variable(n)
                    except KeyError:
                        if FATAL_UNDEFINED:
                            raise
                        else:
                            log.error("Undefined variable '%s'", n, extra={'stack': True})
                            return n
                    else:
                        if v:
                            if not isinstance(v, six.string_types):
                                v = v.render()
                            # TODO this used to test for _dequote
                            if m.group(1):
                                v = dequote(v)
                        else:
                            v = m.group(0)
                        return v

                cont = _interpolate_re.sub(_av, cont)
        # XXX what?: if options is not None:
        # ...apply math:
        cont = self.do_glob_math(cont)
        return cont

    def calculate(self, _base_str, divide=False):
        better_expr_str = _base_str

        better_expr_str = self.do_glob_math(better_expr_str)

        better_expr_str = self.evaluate_expression(better_expr_str, divide=divide)

        if better_expr_str is None:
            better_expr_str = String.unquoted(self.apply_vars(_base_str))

        return better_expr_str

    # TODO only used by magic-import...?
    def interpolate(self, var):
        value = self.namespace.variable(var)
        if var != value and isinstance(value, six.string_types):
            _vi = self.evaluate_expression(value)
            if _vi is not None:
                value = _vi
        return value

    def evaluate_expression(self, expr, divide=False):
        if not isinstance(expr, six.string_types):
            raise TypeError("Expected string, got %r" % (expr,))

        if expr in ast_cache:
            ast = ast_cache[expr]

        elif _variable_re.match(expr):
            # Short-circuit for variable names
            ast = Variable(expr)

        else:
            try:
                parser = SassExpression(SassExpressionScanner(expr))
                ast = parser.goal()
            except SyntaxError:
                if config.DEBUG:
                    raise
                return None
            else:
                ast_cache[expr] = ast

        try:
            return ast.evaluate(self, divide=divide)
        except Exception:
            # TODO hoist me up since the rule is gone
            #log.exception("Exception raised: %s in `%s' (%s)", e, expr, rule.file_and_line)
            if config.DEBUG:
                raise
            return None

    def parse_expression(self, expr, target='goal'):
        if expr in ast_cache:
            return ast_cache[expr]

        parser = SassExpression(SassExpressionScanner(expr))
        ast = getattr(parser, target)()

        if target == 'goal':
            ast_cache[expr] = ast

        return ast

{'fraction_non_alphanumeric': 0.09107591269074754, 'fraction_numerical': 0.0009658103148541626, 'mean_word_length': 1.681947681947682, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2085079', 'n_tokens_mistral': 2796, 'n_tokens_neox': 2654, 'n_words': 787}