class Calculator(object): """Expression evaluator.""" ast_cache = {} 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 that's a lie! this should be in the parser for most cases. if not isinstance(cont, six.string_types): warn(FutureWarning( "do_glob_math was passed a non-string {0!r} " "-- this will no longer be supported in pyScss 2.0" .format(cont) )) cont = six.text_type(cont) if '#{' not in cont: return cont cont = _expr_glob_re.sub(self._pound_substitute, cont) return cont def apply_vars(self, cont): # TODO this is very complicated. it should go away once everything # valid is actually parseable. 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 config.FATAL_UNDEFINED: raise SyntaxError("Undefined variable: '%s'." % n) else: if config.VERBOSITY > 1: 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) # TODO this is surprising and shouldn't be here 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): try: ast = self.parse_expression(expr) except SassError as e: if config.DEBUG: raise else: return None try: return ast.evaluate(self, divide=divide) except Exception as e: six.reraise(SassEvaluationError, SassEvaluationError(e, expression=expr), sys.exc_info()[2]) def parse_expression(self, expr, target='goal'): if isinstance(expr, six.text_type): # OK pass elif isinstance(expr, six.binary_type): # Dubious warn(FutureWarning( "parse_expression was passed binary data {0!r} " "-- this will no longer be supported in pyScss 2.0" .format(expr) )) # Don't guess an encoding; you reap what you sow expr = six.text_type(expr) else: raise TypeError("Expected string, got %r" % (expr,)) key = (target, expr) if key in self.ast_cache: return self.ast_cache[key] try: parser = SassExpression(SassExpressionScanner(expr)) ast = getattr(parser, target)() except SyntaxError as e: raise SassParseError(e, expression=expr, expression_pos=parser._char_pos) self.ast_cache[key] = ast return ast
class Calculator(object): """Expression evaluator.""" ast_cache = {} def __init__( self, namespace=None, ignore_parse_errors=False, undefined_variables_fatal=True, ): if namespace is None: self.namespace = Namespace() else: self.namespace = namespace self.ignore_parse_errors = ignore_parse_errors self.undefined_variables_fatal = undefined_variables_fatal 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 that's a lie! this should be in the parser for most cases. if not isinstance(cont, six.string_types): warn( FutureWarning( "do_glob_math was passed a non-string {0!r} " "-- this will no longer be supported in pyScss 2.0".format( cont))) cont = six.text_type(cont) if '#{' not in cont: return cont cont = _expr_glob_re.sub(self._pound_substitute, cont) return cont def apply_vars(self, cont): # TODO this is very complicated. it should go away once everything # valid is actually parseable. 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 self.undefined_variables_fatal: raise SyntaxError("Undefined variable: '%s'." % n) else: log.error("Undefined variable '%s'", n, extra={'stack': True}) return n else: if v: if not isinstance(v, Value): raise TypeError( "Somehow got a variable {0!r} " "with a non-Sass value: {1!r}".format( n, v)) 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) else: # Variable succeeded, so we need to render it cont = cont.render() # TODO this is surprising and shouldn't be here cont = self.do_glob_math(cont) return cont def calculate(self, expression, divide=False): result = self.evaluate_expression(expression, divide=divide) if result is None: return String.unquoted(self.apply_vars(expression)) return result # 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): try: ast = self.parse_expression(expr) except SassError as e: if self.ignore_parse_errors: return None raise try: return ast.evaluate(self, divide=divide) except Exception as e: six.reraise(SassEvaluationError, SassEvaluationError(e, expression=expr), sys.exc_info()[2]) def parse_expression(self, expr, target='goal'): if isinstance(expr, six.text_type): # OK pass elif isinstance(expr, six.binary_type): # Dubious warn( FutureWarning( "parse_expression was passed binary data {0!r} " "-- this will no longer be supported in pyScss 2.0".format( expr))) # Don't guess an encoding; you reap what you sow expr = six.text_type(expr) else: raise TypeError("Expected string, got %r" % (expr, )) key = (target, expr) if key in self.ast_cache: return self.ast_cache[key] try: parser = SassExpression(SassExpressionScanner(expr)) ast = getattr(parser, target)() except SyntaxError as e: raise SassParseError(e, expression=expr, expression_pos=parser._char_pos) self.ast_cache[key] = ast return ast def parse_interpolations(self, string): """Parse a string for interpolations, but don't treat anything else as Sass syntax. Returns an AST node. """ # Shortcut: if there are no #s in the string in the first place, it # must not have any interpolations, right? if '#' not in string: return Literal(String.unquoted(string)) return self.parse_expression(string, 'goal_interpolated_literal') def parse_vars_and_interpolations(self, string): """Parse a string for variables and interpolations, but don't treat anything else as Sass syntax. Returns an AST node. """ # Shortcut: if there are no #s or $s in the string in the first place, # it must not have anything of interest. if '#' not in string and '$' not in string: return Literal(String.unquoted(string)) return self.parse_expression(string, 'goal_interpolated_literal_with_vars')
class Calculator(object): """Expression evaluator.""" ast_cache = {} def __init__( self, namespace=None, ignore_parse_errors=False, undefined_variables_fatal=True, ): if namespace is None: self.namespace = Namespace() else: self.namespace = namespace self.ignore_parse_errors = ignore_parse_errors self.undefined_variables_fatal = undefined_variables_fatal 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 that's a lie! this should be in the parser for most cases. if not isinstance(cont, six.string_types): warn(FutureWarning( "do_glob_math was passed a non-string {0!r} " "-- this will no longer be supported in pyScss 2.0" .format(cont) )) cont = six.text_type(cont) if '#{' not in cont: return cont cont = _expr_glob_re.sub(self._pound_substitute, cont) return cont def apply_vars(self, cont): # TODO this is very complicated. it should go away once everything # valid is actually parseable. 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 self.undefined_variables_fatal: raise SyntaxError("Undefined variable: '%s'." % n) else: log.error("Undefined variable '%s'", n, extra={'stack': True}) return n else: if v: if not isinstance(v, Value): raise TypeError( "Somehow got a variable {0!r} " "with a non-Sass value: {1!r}" .format(n, v) ) 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) else: # Variable succeeded, so we need to render it cont = cont.render() # TODO this is surprising and shouldn't be here cont = self.do_glob_math(cont) return cont def calculate(self, expression, divide=False): expression = self.evaluate_expression(expression, divide=divide) if expression is None: return String.unquoted(self.apply_vars(expression)) return expression # 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): try: ast = self.parse_expression(expr) except SassError as e: if self.ignore_parse_errors: return None raise try: return ast.evaluate(self, divide=divide) except Exception as e: six.reraise(SassEvaluationError, SassEvaluationError(e, expression=expr), sys.exc_info()[2]) def parse_expression(self, expr, target='goal'): if isinstance(expr, six.text_type): # OK pass elif isinstance(expr, six.binary_type): # Dubious warn(FutureWarning( "parse_expression was passed binary data {0!r} " "-- this will no longer be supported in pyScss 2.0" .format(expr) )) # Don't guess an encoding; you reap what you sow expr = six.text_type(expr) else: raise TypeError("Expected string, got %r" % (expr,)) key = (target, expr) if key in self.ast_cache: return self.ast_cache[key] try: parser = SassExpression(SassExpressionScanner(expr)) ast = getattr(parser, target)() except SyntaxError as e: raise SassParseError(e, expression=expr, expression_pos=parser._char_pos) self.ast_cache[key] = ast return ast def parse_interpolations(self, string): """Parse a string for interpolations, but don't treat anything else as Sass syntax. Returns an AST node. """ # Shortcut: if there are no #s in the string in the first place, it # must not have any interpolations, right? if '#' not in string: return Literal(String.unquoted(string)) return self.parse_expression(string, 'goal_interpolated_anything')
class Calculator(object): """Expression evaluator.""" ast_cache = {} 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 that's a lie! this should be in the parser for most cases. if not isinstance(cont, six.string_types): warn( FutureWarning( "do_glob_math was passed a non-string {0!r} " "-- this will no longer be supported in pyScss 2.0".format( cont))) cont = six.text_type(cont) if '#{' not in cont: return cont cont = _expr_glob_re.sub(self._pound_substitute, cont) return cont def apply_vars(self, cont): # TODO this is very complicated. it should go away once everything # valid is actually parseable. 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 config.FATAL_UNDEFINED: raise SyntaxError("Undefined variable: '%s'." % n) else: if config.VERBOSITY > 1: 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) # TODO this is surprising and shouldn't be here 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): try: ast = self.parse_expression(expr) except SassError as e: if config.DEBUG: raise else: return None try: return ast.evaluate(self, divide=divide) except Exception as e: raise SassEvaluationError(e, expression=expr) def parse_expression(self, expr, target='goal'): if isinstance(expr, six.text_type): # OK pass elif isinstance(expr, six.binary_type): # Dubious warn( FutureWarning( "parse_expression was passed binary data {0!r} " "-- this will no longer be supported in pyScss 2.0".format( expr))) # Don't guess an encoding; you reap what you sow expr = six.text_type(expr) else: raise TypeError("Expected string, got %r" % (expr, )) key = (target, expr) if key in self.ast_cache: return self.ast_cache[key] try: parser = SassExpression(SassExpressionScanner(expr)) ast = getattr(parser, target)() except SyntaxError as e: raise SassParseError(e, expression=expr, expression_pos=parser._char_pos) self.ast_cache[key] = ast return ast
class Calculator(object): """Expression evaluator.""" def __init__(self, namespace=None): if namespace is None: self.namespace = Namespace() else: self.namespace = namespace def _calculate_expr(self, result): _group0 = result.group(1) _base_str = _group0 better_expr_str = self.evaluate_expression(_base_str) if better_expr_str is None: better_expr_str = self.apply_vars(_base_str) else: better_expr_str = dequote(better_expr_str.render()) return better_expr_str 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._calculate_expr, 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 = self.namespace.variable(m.group(2)) 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 = 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: P = SassExpression(SassExpressionScanner()) P.reset(expr) ast = P.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()) parser.reset(expr) ast = getattr(parser, target)() if target == 'goal': ast_cache[expr] = ast return ast
class Calculator(object): """Expression evaluator.""" ast_cache = {} 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 config.FATAL_UNDEFINED: raise else: if config.VERBOSITY > 1: 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): try: ast = self.parse_expression(expr) except SassError: if config.DEBUG: raise else: return None try: return ast.evaluate(self, divide=divide) except Exception as e: raise SassEvaluationError(e, expression=expr) def parse_expression(self, expr, target='goal'): if not isinstance(expr, six.string_types): raise TypeError("Expected string, got %r" % (expr,)) key = (target, expr) if key in self.ast_cache: return self.ast_cache[key] try: parser = SassExpression(SassExpressionScanner(expr)) ast = getattr(parser, target)() except SyntaxError as e: raise SassParseError(e, expression=expr, expression_pos=parser._char_pos) self.ast_cache[key] = ast return ast