~corey.bryant/ubuntu/wily/python-pyscss/thedac

« back to all changes in this revision

Viewing changes to .pc/python-3.2-six.u.patch/scss/expression.py

  • Committer: Package Import Robot
  • Author(s): Thomas Goirand
  • Date: 2014-06-26 12:10:36 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20140626121036-3dv13zn5zptk9fpx
Tags: 1.2.0.post3-1
* Team upload.
* New upstream release (Closes: #738776).
* Added a debian/gbp.conf.
* Added missing ${python:Depends}
* Added Python 3 support.
* Removed duplicate debhelper build-depends.
* Cannonical VCS URLs.
* Standards-Version: is now 3.9.5.
* Added a watch file.
* override dh helpers which the package doesn't use.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from __future__ import absolute_import
 
2
from __future__ import print_function
 
3
 
 
4
from functools import partial
 
5
import logging
 
6
import operator
 
7
import re
 
8
 
 
9
import six
 
10
 
 
11
import scss.config as config
 
12
from scss.cssdefs import COLOR_NAMES, is_builtin_css_function, _expr_glob_re, _interpolate_re
 
13
from scss.errors import SassError, SassEvaluationError, SassParseError
 
14
from scss.rule import Namespace
 
15
from scss.types import Boolean, Color, List, Map, Null, Number, String, Undefined, Value
 
16
from scss.util import dequote, normalize_var
 
17
 
 
18
################################################################################
 
19
# Load C acceleration modules
 
20
Scanner = None
 
21
try:
 
22
    from scss._speedups import Scanner
 
23
except ImportError:
 
24
    from scss._native import Scanner
 
25
 
 
26
log = logging.getLogger(__name__)
 
27
 
 
28
 
 
29
class Calculator(object):
 
30
    """Expression evaluator."""
 
31
 
 
32
    ast_cache = {}
 
33
 
 
34
    def __init__(self, namespace=None):
 
35
        if namespace is None:
 
36
            self.namespace = Namespace()
 
37
        else:
 
38
            self.namespace = namespace
 
39
 
 
40
    def _pound_substitute(self, result):
 
41
        expr = result.group(1)
 
42
        value = self.evaluate_expression(expr)
 
43
 
 
44
        if value is None:
 
45
            return self.apply_vars(expr)
 
46
        elif value.is_null:
 
47
            return ""
 
48
        else:
 
49
            return dequote(value.render())
 
50
 
 
51
    def do_glob_math(self, cont):
 
52
        """Performs #{}-interpolation.  The result is always treated as a fixed
 
53
        syntactic unit and will not be re-evaluated.
 
54
        """
 
55
        # TODO that's a lie!  this should be in the parser for most cases.
 
56
        cont = str(cont)
 
57
        if '#{' not in cont:
 
58
            return cont
 
59
        cont = _expr_glob_re.sub(self._pound_substitute, cont)
 
60
        return cont
 
61
 
 
62
    def apply_vars(self, cont):
 
63
        # TODO this is very complicated.  it should go away once everything
 
64
        # valid is actually parseable.
 
65
        if isinstance(cont, six.string_types) and '$' in cont:
 
66
            try:
 
67
                # Optimization: the full cont is a variable in the context,
 
68
                cont = self.namespace.variable(cont)
 
69
            except KeyError:
 
70
                # Interpolate variables:
 
71
                def _av(m):
 
72
                    v = None
 
73
                    n = m.group(2)
 
74
                    try:
 
75
                        v = self.namespace.variable(n)
 
76
                    except KeyError:
 
77
                        if config.FATAL_UNDEFINED:
 
78
                            raise SyntaxError("Undefined variable: '%s'." % n)
 
79
                        else:
 
80
                            if config.VERBOSITY > 1:
 
81
                                log.error("Undefined variable '%s'", n, extra={'stack': True})
 
82
                            return n
 
83
                    else:
 
84
                        if v:
 
85
                            if not isinstance(v, six.string_types):
 
86
                                v = v.render()
 
87
                            # TODO this used to test for _dequote
 
88
                            if m.group(1):
 
89
                                v = dequote(v)
 
90
                        else:
 
91
                            v = m.group(0)
 
92
                        return v
 
93
 
 
94
                cont = _interpolate_re.sub(_av, cont)
 
95
        # TODO this is surprising and shouldn't be here
 
96
        cont = self.do_glob_math(cont)
 
97
        return cont
 
98
 
 
99
    def calculate(self, _base_str, divide=False):
 
100
        better_expr_str = _base_str
 
101
 
 
102
        better_expr_str = self.do_glob_math(better_expr_str)
 
103
 
 
104
        better_expr_str = self.evaluate_expression(better_expr_str, divide=divide)
 
105
 
 
106
        if better_expr_str is None:
 
107
            better_expr_str = String.unquoted(self.apply_vars(_base_str))
 
108
 
 
109
        return better_expr_str
 
110
 
 
111
    # TODO only used by magic-import...?
 
112
    def interpolate(self, var):
 
113
        value = self.namespace.variable(var)
 
114
        if var != value and isinstance(value, six.string_types):
 
115
            _vi = self.evaluate_expression(value)
 
116
            if _vi is not None:
 
117
                value = _vi
 
118
        return value
 
119
 
 
120
    def evaluate_expression(self, expr, divide=False):
 
121
        try:
 
122
            ast = self.parse_expression(expr)
 
123
        except SassError:
 
124
            if config.DEBUG:
 
125
                raise
 
126
            else:
 
127
                return None
 
128
 
 
129
        try:
 
130
            return ast.evaluate(self, divide=divide)
 
131
        except Exception as e:
 
132
            raise SassEvaluationError(e, expression=expr)
 
133
 
 
134
    def parse_expression(self, expr, target='goal'):
 
135
        if not isinstance(expr, six.string_types):
 
136
            raise TypeError("Expected string, got %r" % (expr,))
 
137
 
 
138
        key = (target, expr)
 
139
        if key in self.ast_cache:
 
140
            return self.ast_cache[key]
 
141
 
 
142
        try:
 
143
            parser = SassExpression(SassExpressionScanner(expr))
 
144
            ast = getattr(parser, target)()
 
145
        except SyntaxError as e:
 
146
            raise SassParseError(e, expression=expr, expression_pos=parser._char_pos)
 
147
 
 
148
        self.ast_cache[key] = ast
 
149
        return ast
 
150
 
 
151
 
 
152
# ------------------------------------------------------------------------------
 
153
# Expression classes -- the AST resulting from a parse
 
154
 
 
155
class Expression(object):
 
156
    def __repr__(self):
 
157
        return '<%s()>' % (self.__class__.__name__)
 
158
 
 
159
    def evaluate(self, calculator, divide=False):
 
160
        """Evaluate this AST node, and return a Sass value.
 
161
 
 
162
        `divide` indicates whether a descendant node representing a division
 
163
        should be forcibly treated as a division.  See the commentary in
 
164
        `BinaryOp`.
 
165
        """
 
166
        raise NotImplementedError
 
167
 
 
168
 
 
169
class Parentheses(Expression):
 
170
    """An expression of the form `(foo)`.
 
171
 
 
172
    Only exists to force a slash to be interpreted as division when contained
 
173
    within parentheses.
 
174
    """
 
175
    def __repr__(self):
 
176
        return '<%s(%s)>' % (self.__class__.__name__, repr(self.contents))
 
177
 
 
178
    def __init__(self, contents):
 
179
        self.contents = contents
 
180
 
 
181
    def evaluate(self, calculator, divide=False):
 
182
        return self.contents.evaluate(calculator, divide=True)
 
183
 
 
184
 
 
185
class UnaryOp(Expression):
 
186
    def __repr__(self):
 
187
        return '<%s(%s, %s)>' % (self.__class__.__name__, repr(self.op), repr(self.operand))
 
188
 
 
189
    def __init__(self, op, operand):
 
190
        self.op = op
 
191
        self.operand = operand
 
192
 
 
193
    def evaluate(self, calculator, divide=False):
 
194
        return self.op(self.operand.evaluate(calculator, divide=True))
 
195
 
 
196
 
 
197
class BinaryOp(Expression):
 
198
    def __repr__(self):
 
199
        return '<%s(%s, %s, %s)>' % (self.__class__.__name__, repr(self.op), repr(self.left), repr(self.right))
 
200
 
 
201
    def __init__(self, op, left, right):
 
202
        self.op = op
 
203
        self.left = left
 
204
        self.right = right
 
205
 
 
206
    def evaluate(self, calculator, divide=False):
 
207
        left = self.left.evaluate(calculator, divide=True)
 
208
        right = self.right.evaluate(calculator, divide=True)
 
209
 
 
210
        # Special handling of division: treat it as a literal slash if both
 
211
        # operands are literals, there are parentheses, or this is part of a
 
212
        # bigger expression.
 
213
        # The first condition is covered by the type check.  The other two are
 
214
        # covered by the `divide` argument: other nodes that perform arithmetic
 
215
        # will pass in True, indicating that this should always be a division.
 
216
        if (
 
217
            self.op is operator.truediv
 
218
            and not divide
 
219
            and isinstance(self.left, Literal)
 
220
            and isinstance(self.right, Literal)
 
221
        ):
 
222
            return String(left.render() + ' / ' + right.render(), quotes=None)
 
223
 
 
224
        return self.op(left, right)
 
225
 
 
226
 
 
227
class AnyOp(Expression):
 
228
    def __repr__(self):
 
229
        return '<%s(*%s)>' % (self.__class__.__name__, repr(self.op), repr(self.operands))
 
230
 
 
231
    def __init__(self, *operands):
 
232
        self.operands = operands
 
233
 
 
234
    def evaluate(self, calculator, divide=False):
 
235
        for operand in self.operands:
 
236
            value = operand.evaluate(calculator, divide=True)
 
237
            if value:
 
238
                return value
 
239
        return value
 
240
 
 
241
 
 
242
class AllOp(Expression):
 
243
    def __repr__(self):
 
244
        return '<%s(*%s)>' % (self.__class__.__name__, repr(self.operands))
 
245
 
 
246
    def __init__(self, *operands):
 
247
        self.operands = operands
 
248
 
 
249
    def evaluate(self, calculator, divide=False):
 
250
        for operand in self.operands:
 
251
            value = operand.evaluate(calculator, divide=True)
 
252
            if not value:
 
253
                return value
 
254
        return value
 
255
 
 
256
 
 
257
class NotOp(Expression):
 
258
    def __repr__(self):
 
259
        return '<%s(%s)>' % (self.__class__.__name__, repr(self.operand))
 
260
 
 
261
    def __init__(self, operand):
 
262
        self.operand = operand
 
263
 
 
264
    def evaluate(self, calculator, divide=False):
 
265
        operand = self.operand.evaluate(calculator, divide=True)
 
266
        return Boolean(not(operand))
 
267
 
 
268
 
 
269
class CallOp(Expression):
 
270
    def __repr__(self):
 
271
        return '<%s(%s, %s)>' % (self.__class__.__name__, repr(self.func_name), repr(self.argspec))
 
272
 
 
273
    def __init__(self, func_name, argspec):
 
274
        self.func_name = func_name
 
275
        self.argspec = argspec
 
276
 
 
277
    def evaluate(self, calculator, divide=False):
 
278
        # TODO bake this into the context and options "dicts", plus library
 
279
        func_name = normalize_var(self.func_name)
 
280
 
 
281
        argspec_node = self.argspec
 
282
 
 
283
        # Turn the pairs of arg tuples into *args and **kwargs
 
284
        # TODO unclear whether this is correct -- how does arg, kwarg, arg
 
285
        # work?
 
286
        args, kwargs = argspec_node.evaluate_call_args(calculator)
 
287
        argspec_len = len(args) + len(kwargs)
 
288
 
 
289
        # Translate variable names to Python identifiers
 
290
        # TODO what about duplicate kw names?  should this happen in argspec?
 
291
        # how does that affect mixins?
 
292
        kwargs = dict(
 
293
            (key.lstrip('$').replace('-', '_'), value)
 
294
            for key, value in kwargs.items())
 
295
 
 
296
        # TODO merge this with the library
 
297
        funct = None
 
298
        try:
 
299
            funct = calculator.namespace.function(func_name, argspec_len)
 
300
            # @functions take a ns as first arg.  TODO: Python functions possibly
 
301
            # should too
 
302
            if getattr(funct, '__name__', None) == '__call':
 
303
                funct = partial(funct, calculator.namespace)
 
304
        except KeyError:
 
305
            try:
 
306
                # DEVIATION: Fall back to single parameter
 
307
                funct = calculator.namespace.function(func_name, 1)
 
308
                args = [List(args, use_comma=True)]
 
309
            except KeyError:
 
310
                if not is_builtin_css_function(func_name):
 
311
                    log.error("Function not found: %s:%s", func_name, argspec_len, extra={'stack': True})
 
312
 
 
313
        if funct:
 
314
            ret = funct(*args, **kwargs)
 
315
            if not isinstance(ret, Value):
 
316
                raise TypeError("Expected Sass type as return value, got %r" % (ret,))
 
317
            return ret
 
318
 
 
319
        # No matching function found, so render the computed values as a CSS
 
320
        # function call.  Slurpy arguments are expanded and named arguments are
 
321
        # unsupported.
 
322
        if kwargs:
 
323
            raise TypeError("The CSS function %s doesn't support keyword arguments." % (func_name,))
 
324
 
 
325
        # TODO another candidate for a "function call" sass type
 
326
        rendered_args = [arg.render() for arg in args]
 
327
 
 
328
        return String(
 
329
            u"%s(%s)" % (func_name, u", ".join(rendered_args)),
 
330
            quotes=None)
 
331
 
 
332
 
 
333
class Literal(Expression):
 
334
    def __repr__(self):
 
335
        return '<%s(%s)>' % (self.__class__.__name__, repr(self.value))
 
336
 
 
337
    def __init__(self, value):
 
338
        if isinstance(value, Undefined) and config.FATAL_UNDEFINED:
 
339
            raise SyntaxError("Undefined literal.")
 
340
        else:
 
341
            self.value = value
 
342
 
 
343
    def evaluate(self, calculator, divide=False):
 
344
        return self.value
 
345
 
 
346
 
 
347
class Variable(Expression):
 
348
    def __repr__(self):
 
349
        return '<%s(%s)>' % (self.__class__.__name__, repr(self.name))
 
350
 
 
351
    def __init__(self, name):
 
352
        self.name = name
 
353
 
 
354
    def evaluate(self, calculator, divide=False):
 
355
        try:
 
356
            value = calculator.namespace.variable(self.name)
 
357
        except KeyError:
 
358
            if config.FATAL_UNDEFINED:
 
359
                raise SyntaxError("Undefined variable: '%s'." % self.name)
 
360
            else:
 
361
                if config.VERBOSITY > 1:
 
362
                    log.error("Undefined variable '%s'", self.name, extra={'stack': True})
 
363
                return Undefined()
 
364
        else:
 
365
            if isinstance(value, six.string_types):
 
366
                evald = calculator.evaluate_expression(value)
 
367
                if evald is not None:
 
368
                    return evald
 
369
            return value
 
370
 
 
371
 
 
372
class ListLiteral(Expression):
 
373
    def __repr__(self):
 
374
        return '<%s(%s, comma=%s)>' % (self.__class__.__name__, repr(self.items), repr(self.comma))
 
375
 
 
376
    def __init__(self, items, comma=True):
 
377
        self.items = items
 
378
        self.comma = comma
 
379
 
 
380
    def evaluate(self, calculator, divide=False):
 
381
        items = [item.evaluate(calculator, divide=divide) for item in self.items]
 
382
 
 
383
        # Whether this is a "plain" literal matters for null removal: nulls are
 
384
        # left alone if this is a completely vanilla CSS property
 
385
        is_literal = True
 
386
        if divide:
 
387
            # TODO sort of overloading "divide" here...  rename i think
 
388
            is_literal = False
 
389
        elif not all(isinstance(item, Literal) for item in self.items):
 
390
            is_literal = False
 
391
 
 
392
        return List(items, use_comma=self.comma, is_literal=is_literal)
 
393
 
 
394
 
 
395
class MapLiteral(Expression):
 
396
    def __repr__(self):
 
397
        return '<%s(%s)>' % (self.__class__.__name__, repr(self.pairs))
 
398
 
 
399
    def __init__(self, pairs):
 
400
        self.pairs = tuple((var, value) for var, value in pairs if value is not None)
 
401
 
 
402
    def evaluate(self, calculator, divide=False):
 
403
        scss_pairs = []
 
404
        for key, value in self.pairs:
 
405
            scss_pairs.append((
 
406
                key.evaluate(calculator),
 
407
                value.evaluate(calculator),
 
408
            ))
 
409
 
 
410
        return Map(scss_pairs)
 
411
 
 
412
 
 
413
class ArgspecLiteral(Expression):
 
414
    """Contains pairs of argument names and values, as parsed from a function
 
415
    definition or function call.
 
416
 
 
417
    Note that the semantics are somewhat ambiguous.  Consider parsing:
 
418
 
 
419
        $foo, $bar: 3
 
420
 
 
421
    If this appeared in a function call, $foo would refer to a value; if it
 
422
    appeared in a function definition, $foo would refer to an existing
 
423
    variable.  This it's up to the caller to use the right iteration function.
 
424
    """
 
425
    def __repr__(self):
 
426
        return '<%s(%s)>' % (self.__class__.__name__, repr(self.argpairs))
 
427
 
 
428
    def __init__(self, argpairs, slurp=None):
 
429
        # argpairs is a list of 2-tuples, parsed as though this were a function
 
430
        # call, so (variable name as string or None, default value as AST
 
431
        # node).
 
432
        # slurp is the name of a variable to receive slurpy arguments.
 
433
        self.argpairs = tuple(argpairs)
 
434
        if slurp is all:
 
435
            # DEVIATION: special syntax to allow injecting arbitrary arguments
 
436
            # from the caller to the callee
 
437
            self.inject = True
 
438
            self.slurp = None
 
439
        elif slurp:
 
440
            self.inject = False
 
441
            self.slurp = Variable(slurp)
 
442
        else:
 
443
            self.inject = False
 
444
            self.slurp = None
 
445
 
 
446
    def iter_list_argspec(self):
 
447
        yield None, ListLiteral(zip(*self.argpairs)[1])
 
448
 
 
449
    def iter_def_argspec(self):
 
450
        """Interpreting this literal as a function definition, yields pairs of
 
451
        (variable name as a string, default value as an AST node or None).
 
452
        """
 
453
        started_kwargs = False
 
454
        seen_vars = set()
 
455
 
 
456
        for var, value in self.argpairs:
 
457
            if var is None:
 
458
                # value is actually the name
 
459
                var = value
 
460
                value = None
 
461
 
 
462
                if started_kwargs:
 
463
                    raise SyntaxError(
 
464
                        "Required argument %r must precede optional arguments"
 
465
                        % (var.name,))
 
466
 
 
467
            else:
 
468
                started_kwargs = True
 
469
 
 
470
            if not isinstance(var, Variable):
 
471
                raise SyntaxError("Expected variable name, got %r" % (var,))
 
472
 
 
473
            if var.name in seen_vars:
 
474
                raise SyntaxError("Duplicate argument %r" % (var.name,))
 
475
            seen_vars.add(var.name)
 
476
 
 
477
            yield var.name, value
 
478
 
 
479
    def evaluate_call_args(self, calculator):
 
480
        """Interpreting this literal as a function call, return a 2-tuple of
 
481
        ``(args, kwargs)``.
 
482
        """
 
483
        args = []
 
484
        kwargs = {}
 
485
        for var_node, value_node in self.argpairs:
 
486
            value = value_node.evaluate(calculator, divide=True)
 
487
            if var_node is None:
 
488
                # Positional
 
489
                args.append(value)
 
490
            else:
 
491
                # Named
 
492
                if not isinstance(var_node, Variable):
 
493
                    raise SyntaxError("Expected variable name, got %r" % (var_node,))
 
494
                kwargs[var_node.name] = value
 
495
 
 
496
        # Slurpy arguments go on the end of the args
 
497
        if self.slurp:
 
498
            args.extend(self.slurp.evaluate(calculator, divide=True))
 
499
 
 
500
        return args, kwargs
 
501
 
 
502
 
 
503
def parse_bareword(word):
 
504
    if word in COLOR_NAMES:
 
505
        return Color.from_name(word)
 
506
    elif word == 'null':
 
507
        return Null()
 
508
    elif word == 'undefined':
 
509
        return Undefined()
 
510
    elif word == 'true':
 
511
        return Boolean(True)
 
512
    elif word == 'false':
 
513
        return Boolean(False)
 
514
    else:
 
515
        return String(word, quotes=None)
 
516
 
 
517
 
 
518
class Parser(object):
 
519
    def __init__(self, scanner):
 
520
        self._scanner = scanner
 
521
        self._pos = 0
 
522
        self._char_pos = 0
 
523
 
 
524
    def reset(self, input):
 
525
        self._scanner.reset(input)
 
526
        self._pos = 0
 
527
        self._char_pos = 0
 
528
 
 
529
    def _peek(self, types):
 
530
        """
 
531
        Returns the token type for lookahead; if there are any args
 
532
        then the list of args is the set of token types to allow
 
533
        """
 
534
        try:
 
535
            tok = self._scanner.token(self._pos, types)
 
536
            return tok[2]
 
537
        except SyntaxError:
 
538
            return None
 
539
 
 
540
    def _scan(self, type):
 
541
        """
 
542
        Returns the matched text, and moves to the next token
 
543
        """
 
544
        tok = self._scanner.token(self._pos, set([type]))
 
545
        self._char_pos = tok[0]
 
546
        if tok[2] != type:
 
547
            raise SyntaxError("SyntaxError[@ char %s: %s]" % (repr(tok[0]), "Trying to find " + type))
 
548
        self._pos += 1
 
549
        return tok[3]
 
550
 
 
551
 
 
552
################################################################################
 
553
## Grammar compiled using Yapps:
 
554
 
 
555
class SassExpressionScanner(Scanner):
 
556
    patterns = None
 
557
    _patterns = [
 
558
        ('":"', ':'),
 
559
        ('","', ','),
 
560
        ('[ \r\t\n]+', '[ \r\t\n]+'),
 
561
        ('LPAR', '\\(|\\['),
 
562
        ('RPAR', '\\)|\\]'),
 
563
        ('END', '$'),
 
564
        ('MUL', '[*]'),
 
565
        ('DIV', '/'),
 
566
        ('ADD', '[+]'),
 
567
        ('SUB', '-\\s'),
 
568
        ('SIGN', '-(?![a-zA-Z_])'),
 
569
        ('AND', '(?<![-\\w])and(?![-\\w])'),
 
570
        ('OR', '(?<![-\\w])or(?![-\\w])'),
 
571
        ('NOT', '(?<![-\\w])not(?![-\\w])'),
 
572
        ('NE', '!='),
 
573
        ('INV', '!'),
 
574
        ('EQ', '=='),
 
575
        ('LE', '<='),
 
576
        ('GE', '>='),
 
577
        ('LT', '<'),
 
578
        ('GT', '>'),
 
579
        ('DOTDOTDOT', '[.]{3}'),
 
580
        ('KWSTR', "'[^']*'(?=\\s*:)"),
 
581
        ('STR', "'[^']*'"),
 
582
        ('KWQSTR', '"[^"]*"(?=\\s*:)'),
 
583
        ('QSTR', '"[^"]*"'),
 
584
        ('UNITS', '(?<!\\s)(?:[a-zA-Z]+|%)(?![-\\w])'),
 
585
        ('KWNUM', '(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?=\\s*:)'),
 
586
        ('NUM', '(?:\\d+(?:\\.\\d*)?|\\.\\d+)'),
 
587
        ('KWCOLOR', '#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3})(?![a-fA-F0-9])(?=\\s*:)'),
 
588
        ('COLOR', '#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3})(?![a-fA-F0-9])'),
 
589
        ('KWVAR', '\\$[-a-zA-Z0-9_]+(?=\\s*:)'),
 
590
        ('SLURPYVAR', '\\$[-a-zA-Z0-9_]+(?=[.][.][.])'),
 
591
        ('VAR', '\\$[-a-zA-Z0-9_]+'),
 
592
        ('FNCT', '[-a-zA-Z_][-a-zA-Z0-9_]*(?=\\()'),
 
593
        ('KWID', '[-a-zA-Z_][-a-zA-Z0-9_]*(?=\\s*:)'),
 
594
        ('ID', '[-a-zA-Z_][-a-zA-Z0-9_]*'),
 
595
        ('BANG_IMPORTANT', '!important'),
 
596
    ]
 
597
 
 
598
    def __init__(self, input=None):
 
599
        if hasattr(self, 'setup_patterns'):
 
600
            self.setup_patterns(self._patterns)
 
601
        elif self.patterns is None:
 
602
            self.__class__.patterns = []
 
603
            for t, p in self._patterns:
 
604
                self.patterns.append((t, re.compile(p)))
 
605
        super(SassExpressionScanner, self).__init__(None, ['[ \r\t\n]+'], input)
 
606
 
 
607
 
 
608
class SassExpression(Parser):
 
609
    def goal(self):
 
610
        expr_lst = self.expr_lst()
 
611
        END = self._scan('END')
 
612
        return expr_lst
 
613
 
 
614
    def goal_argspec(self):
 
615
        argspec = self.argspec()
 
616
        END = self._scan('END')
 
617
        return argspec
 
618
 
 
619
    def argspec(self):
 
620
        _token_ = self._peek(self.argspec_rsts)
 
621
        if _token_ not in self.argspec_chks:
 
622
            if self._peek(self.argspec_rsts_) not in self.argspec_chks_:
 
623
                argspec_items = self.argspec_items()
 
624
                args, slurpy = argspec_items
 
625
                return ArgspecLiteral(args, slurp=slurpy)
 
626
            return ArgspecLiteral([])
 
627
        elif _token_ == 'SLURPYVAR':
 
628
            SLURPYVAR = self._scan('SLURPYVAR')
 
629
            DOTDOTDOT = self._scan('DOTDOTDOT')
 
630
            return ArgspecLiteral([], slurp=SLURPYVAR)
 
631
        else:  # == 'DOTDOTDOT'
 
632
            DOTDOTDOT = self._scan('DOTDOTDOT')
 
633
            return ArgspecLiteral([], slurp=all)
 
634
 
 
635
    def argspec_items(self):
 
636
        slurpy = None
 
637
        argspec_item = self.argspec_item()
 
638
        args = [argspec_item]
 
639
        if self._peek(self.argspec_items_rsts) == '","':
 
640
            self._scan('","')
 
641
            if self._peek(self.argspec_items_rsts_) not in self.argspec_chks_:
 
642
                _token_ = self._peek(self.argspec_items_rsts__)
 
643
                if _token_ == 'SLURPYVAR':
 
644
                    SLURPYVAR = self._scan('SLURPYVAR')
 
645
                    DOTDOTDOT = self._scan('DOTDOTDOT')
 
646
                    slurpy = SLURPYVAR
 
647
                elif _token_ == 'DOTDOTDOT':
 
648
                    DOTDOTDOT = self._scan('DOTDOTDOT')
 
649
                    slurpy = all
 
650
                else:  # in self.argspec_items_chks
 
651
                    argspec_items = self.argspec_items()
 
652
                    more_args, slurpy = argspec_items
 
653
                    args.extend(more_args)
 
654
        return args, slurpy
 
655
 
 
656
    def argspec_item(self):
 
657
        _token_ = self._peek(self.argspec_items_chks)
 
658
        if _token_ == 'KWVAR':
 
659
            KWVAR = self._scan('KWVAR')
 
660
            self._scan('":"')
 
661
            expr_slst = self.expr_slst()
 
662
            return (Variable(KWVAR), expr_slst)
 
663
        else:  # in self.argspec_item_chks
 
664
            expr_slst = self.expr_slst()
 
665
            return (None, expr_slst)
 
666
 
 
667
    def expr_map(self):
 
668
        map_item = self.map_item()
 
669
        pairs = [map_item]
 
670
        while self._peek(self.expr_map_rsts) == '","':
 
671
            self._scan('","')
 
672
            map_item = (None, None)
 
673
            if self._peek(self.expr_map_rsts_) not in self.expr_map_rsts:
 
674
                map_item = self.map_item()
 
675
            pairs.append(map_item)
 
676
        return MapLiteral(pairs)
 
677
 
 
678
    def map_item(self):
 
679
        kwatom = self.kwatom()
 
680
        self._scan('":"')
 
681
        expr_slst = self.expr_slst()
 
682
        return (kwatom, expr_slst)
 
683
 
 
684
    def expr_lst(self):
 
685
        expr_slst = self.expr_slst()
 
686
        v = [expr_slst]
 
687
        while self._peek(self.argspec_items_rsts) == '","':
 
688
            self._scan('","')
 
689
            expr_slst = self.expr_slst()
 
690
            v.append(expr_slst)
 
691
        return ListLiteral(v) if len(v) > 1 else v[0]
 
692
 
 
693
    def expr_slst(self):
 
694
        or_expr = self.or_expr()
 
695
        v = [or_expr]
 
696
        while self._peek(self.expr_slst_rsts) not in self.argspec_items_rsts:
 
697
            or_expr = self.or_expr()
 
698
            v.append(or_expr)
 
699
        return ListLiteral(v, comma=False) if len(v) > 1 else v[0]
 
700
 
 
701
    def or_expr(self):
 
702
        and_expr = self.and_expr()
 
703
        v = and_expr
 
704
        while self._peek(self.or_expr_rsts) == 'OR':
 
705
            OR = self._scan('OR')
 
706
            and_expr = self.and_expr()
 
707
            v = AnyOp(v, and_expr)
 
708
        return v
 
709
 
 
710
    def and_expr(self):
 
711
        not_expr = self.not_expr()
 
712
        v = not_expr
 
713
        while self._peek(self.and_expr_rsts) == 'AND':
 
714
            AND = self._scan('AND')
 
715
            not_expr = self.not_expr()
 
716
            v = AllOp(v, not_expr)
 
717
        return v
 
718
 
 
719
    def not_expr(self):
 
720
        _token_ = self._peek(self.argspec_item_chks)
 
721
        if _token_ != 'NOT':
 
722
            comparison = self.comparison()
 
723
            return comparison
 
724
        else:  # == 'NOT'
 
725
            NOT = self._scan('NOT')
 
726
            not_expr = self.not_expr()
 
727
            return NotOp(not_expr)
 
728
 
 
729
    def comparison(self):
 
730
        a_expr = self.a_expr()
 
731
        v = a_expr
 
732
        while self._peek(self.comparison_rsts) in self.comparison_chks:
 
733
            _token_ = self._peek(self.comparison_chks)
 
734
            if _token_ == 'LT':
 
735
                LT = self._scan('LT')
 
736
                a_expr = self.a_expr()
 
737
                v = BinaryOp(operator.lt, v, a_expr)
 
738
            elif _token_ == 'GT':
 
739
                GT = self._scan('GT')
 
740
                a_expr = self.a_expr()
 
741
                v = BinaryOp(operator.gt, v, a_expr)
 
742
            elif _token_ == 'LE':
 
743
                LE = self._scan('LE')
 
744
                a_expr = self.a_expr()
 
745
                v = BinaryOp(operator.le, v, a_expr)
 
746
            elif _token_ == 'GE':
 
747
                GE = self._scan('GE')
 
748
                a_expr = self.a_expr()
 
749
                v = BinaryOp(operator.ge, v, a_expr)
 
750
            elif _token_ == 'EQ':
 
751
                EQ = self._scan('EQ')
 
752
                a_expr = self.a_expr()
 
753
                v = BinaryOp(operator.eq, v, a_expr)
 
754
            else:  # == 'NE'
 
755
                NE = self._scan('NE')
 
756
                a_expr = self.a_expr()
 
757
                v = BinaryOp(operator.ne, v, a_expr)
 
758
        return v
 
759
 
 
760
    def a_expr(self):
 
761
        m_expr = self.m_expr()
 
762
        v = m_expr
 
763
        while self._peek(self.a_expr_rsts) in self.a_expr_chks:
 
764
            _token_ = self._peek(self.a_expr_chks)
 
765
            if _token_ == 'ADD':
 
766
                ADD = self._scan('ADD')
 
767
                m_expr = self.m_expr()
 
768
                v = BinaryOp(operator.add, v, m_expr)
 
769
            else:  # == 'SUB'
 
770
                SUB = self._scan('SUB')
 
771
                m_expr = self.m_expr()
 
772
                v = BinaryOp(operator.sub, v, m_expr)
 
773
        return v
 
774
 
 
775
    def m_expr(self):
 
776
        u_expr = self.u_expr()
 
777
        v = u_expr
 
778
        while self._peek(self.m_expr_rsts) in self.m_expr_chks:
 
779
            _token_ = self._peek(self.m_expr_chks)
 
780
            if _token_ == 'MUL':
 
781
                MUL = self._scan('MUL')
 
782
                u_expr = self.u_expr()
 
783
                v = BinaryOp(operator.mul, v, u_expr)
 
784
            else:  # == 'DIV'
 
785
                DIV = self._scan('DIV')
 
786
                u_expr = self.u_expr()
 
787
                v = BinaryOp(operator.truediv, v, u_expr)
 
788
        return v
 
789
 
 
790
    def u_expr(self):
 
791
        _token_ = self._peek(self.u_expr_rsts)
 
792
        if _token_ == 'SIGN':
 
793
            SIGN = self._scan('SIGN')
 
794
            u_expr = self.u_expr()
 
795
            return UnaryOp(operator.neg, u_expr)
 
796
        elif _token_ == 'ADD':
 
797
            ADD = self._scan('ADD')
 
798
            u_expr = self.u_expr()
 
799
            return UnaryOp(operator.pos, u_expr)
 
800
        else:  # in self.u_expr_chks
 
801
            atom = self.atom()
 
802
            return atom
 
803
 
 
804
    def atom(self):
 
805
        _token_ = self._peek(self.u_expr_chks)
 
806
        if _token_ == 'LPAR':
 
807
            LPAR = self._scan('LPAR')
 
808
            _token_ = self._peek(self.atom_rsts)
 
809
            if _token_ not in self.argspec_item_chks:
 
810
                expr_map = self.expr_map()
 
811
                v = expr_map
 
812
            else:  # in self.argspec_item_chks
 
813
                expr_lst = self.expr_lst()
 
814
                v = expr_lst
 
815
            RPAR = self._scan('RPAR')
 
816
            return Parentheses(v)
 
817
        elif _token_ == 'FNCT':
 
818
            FNCT = self._scan('FNCT')
 
819
            LPAR = self._scan('LPAR')
 
820
            argspec = self.argspec()
 
821
            RPAR = self._scan('RPAR')
 
822
            return CallOp(FNCT, argspec)
 
823
        elif _token_ == 'BANG_IMPORTANT':
 
824
            BANG_IMPORTANT = self._scan('BANG_IMPORTANT')
 
825
            return Literal(String(BANG_IMPORTANT, quotes=None))
 
826
        elif _token_ == 'ID':
 
827
            ID = self._scan('ID')
 
828
            return Literal(parse_bareword(ID))
 
829
        elif _token_ == 'NUM':
 
830
            NUM = self._scan('NUM')
 
831
            UNITS = None
 
832
            if self._peek(self.atom_rsts_) == 'UNITS':
 
833
                UNITS = self._scan('UNITS')
 
834
            return Literal(Number(float(NUM), unit=UNITS))
 
835
        elif _token_ == 'STR':
 
836
            STR = self._scan('STR')
 
837
            return Literal(String(STR[1:-1], quotes="'"))
 
838
        elif _token_ == 'QSTR':
 
839
            QSTR = self._scan('QSTR')
 
840
            return Literal(String(QSTR[1:-1], quotes='"'))
 
841
        elif _token_ == 'COLOR':
 
842
            COLOR = self._scan('COLOR')
 
843
            return Literal(Color.from_hex(COLOR, literal=True))
 
844
        else:  # == 'VAR'
 
845
            VAR = self._scan('VAR')
 
846
            return Variable(VAR)
 
847
 
 
848
    def kwatom(self):
 
849
        _token_ = self._peek(self.kwatom_rsts)
 
850
        if _token_ == '":"':
 
851
            pass
 
852
        elif _token_ == 'KWID':
 
853
            KWID = self._scan('KWID')
 
854
            return Literal(parse_bareword(KWID))
 
855
        elif _token_ == 'KWNUM':
 
856
            KWNUM = self._scan('KWNUM')
 
857
            UNITS = None
 
858
            if self._peek(self.kwatom_rsts_) == 'UNITS':
 
859
                UNITS = self._scan('UNITS')
 
860
            return Literal(Number(float(KWNUM), unit=UNITS))
 
861
        elif _token_ == 'KWSTR':
 
862
            KWSTR = self._scan('KWSTR')
 
863
            return Literal(String(KWSTR[1:-1], quotes="'"))
 
864
        elif _token_ == 'KWQSTR':
 
865
            KWQSTR = self._scan('KWQSTR')
 
866
            return Literal(String(KWQSTR[1:-1], quotes='"'))
 
867
        elif _token_ == 'KWCOLOR':
 
868
            KWCOLOR = self._scan('KWCOLOR')
 
869
            return Literal(Color.from_hex(COLOR, literal=True))
 
870
        else:  # == 'KWVAR'
 
871
            KWVAR = self._scan('KWVAR')
 
872
            return Variable(KWVAR)
 
873
 
 
874
    u_expr_chks = set(['LPAR', 'COLOR', 'QSTR', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID'])
 
875
    m_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","'])
 
876
    argspec_items_rsts = set(['RPAR', 'END', '","'])
 
877
    expr_map_rsts = set(['RPAR', '","'])
 
878
    argspec_items_rsts__ = set(['KWVAR', 'LPAR', 'QSTR', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID'])
 
879
    kwatom_rsts = set(['KWVAR', 'KWID', 'KWSTR', 'KWQSTR', 'KWCOLOR', '":"', 'KWNUM'])
 
880
    argspec_item_chks = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID'])
 
881
    a_expr_chks = set(['ADD', 'SUB'])
 
882
    expr_slst_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', '","'])
 
883
    or_expr_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","'])
 
884
    and_expr_rsts = set(['AND', 'LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","'])
 
885
    comparison_rsts = set(['LPAR', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'ADD', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'GE', 'NOT', 'OR', '","'])
 
886
    argspec_chks = set(['DOTDOTDOT', 'SLURPYVAR'])
 
887
    atom_rsts_ = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'VAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'UNITS', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","'])
 
888
    expr_map_rsts_ = set(['KWVAR', 'KWID', 'KWSTR', 'KWQSTR', 'RPAR', 'KWCOLOR', '":"', 'KWNUM', '","'])
 
889
    u_expr_rsts = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID'])
 
890
    comparison_chks = set(['GT', 'GE', 'NE', 'LT', 'LE', 'EQ'])
 
891
    argspec_items_rsts_ = set(['KWVAR', 'LPAR', 'QSTR', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID'])
 
892
    a_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","'])
 
893
    m_expr_chks = set(['MUL', 'DIV'])
 
894
    kwatom_rsts_ = set(['UNITS', '":"'])
 
895
    argspec_items_chks = set(['KWVAR', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID'])
 
896
    argspec_rsts = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'RPAR', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'QSTR', 'SIGN', 'ID'])
 
897
    atom_rsts = set(['KWVAR', 'KWID', 'KWSTR', 'BANG_IMPORTANT', 'LPAR', 'COLOR', 'KWQSTR', 'SIGN', 'KWCOLOR', 'VAR', 'ADD', 'NUM', '":"', 'STR', 'NOT', 'QSTR', 'KWNUM', 'ID', 'FNCT'])
 
898
    argspec_chks_ = set(['END', 'RPAR'])
 
899
    argspec_rsts_ = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'RPAR', 'ID'])
 
900
 
 
901
 
 
902
### Grammar ends.
 
903
################################################################################
 
904
 
 
905
__all__ = ('Calculator',)