summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEevee (Alex Munroe) <eevee.git@veekun.com>2014-08-31 18:52:45 -0700
committerEevee (Alex Munroe) <eevee.git@veekun.com>2014-09-01 21:34:59 -0700
commite51ab4d3c8d52604b5f2fbc83d2c1f235941fa94 (patch)
treee4d9445bfd42db5e2cb809ea85453df13820e08d
parented27be468c3104620d5a880e7aee8b4deb4ca5f4 (diff)
downloadpyscss-e51ab4d3c8d52604b5f2fbc83d2c1f235941fa94.tar.gz
Ported @mixin and @include to the new block AST.
-rw-r--r--scss/ast.py42
-rw-r--r--scss/blockast.py199
-rw-r--r--scss/compiler.py226
-rw-r--r--scss/grammar/expression.g20
-rw-r--r--scss/grammar/expression.py34
5 files changed, 301 insertions, 220 deletions
diff --git a/scss/ast.py b/scss/ast.py
index 06de23a..9bebbb4 100644
--- a/scss/ast.py
+++ b/scss/ast.py
@@ -151,15 +151,15 @@ class NotOp(Expression):
class CallOp(Expression):
def __repr__(self):
- return '<%s(%s, %s)>' % (self.__class__.__name__, repr(self.func_name), repr(self.argspec))
+ return '<%s(%s, %s)>' % (self.__class__.__name__, repr(self.function_name), repr(self.argspec))
- def __init__(self, func_name, argspec):
- self.func_name = func_name
+ def __init__(self, function_name, argspec):
+ self.function_name = function_name
self.argspec = argspec
def evaluate(self, calculator, divide=False):
# TODO bake this into the context and options "dicts", plus library
- func_name = normalize_var(self.func_name)
+ function_name = normalize_var(self.function_name)
argspec_node = self.argspec
@@ -179,7 +179,7 @@ class CallOp(Expression):
# TODO merge this with the library
funct = None
try:
- funct = calculator.namespace.function(func_name, argspec_len)
+ funct = calculator.namespace.function(function_name, argspec_len)
# @functions take a ns as first arg. TODO: Python functions possibly
# should too
if getattr(funct, '__name__', None) == '__call':
@@ -187,11 +187,11 @@ class CallOp(Expression):
except KeyError:
try:
# DEVIATION: Fall back to single parameter
- funct = calculator.namespace.function(func_name, 1)
+ funct = calculator.namespace.function(function_name, 1)
args = [List(args, use_comma=True)]
except KeyError:
- if not is_builtin_css_function(func_name):
- log.error("Function not found: %s:%s", func_name, argspec_len, extra={'stack': True})
+ if not is_builtin_css_function(function_name):
+ log.error("Function not found: %s:%s", function_name, argspec_len, extra={'stack': True})
if funct:
ret = funct(*args, **kwargs)
@@ -203,13 +203,13 @@ class CallOp(Expression):
# function call. Slurpy arguments are expanded and named arguments are
# unsupported.
if kwargs:
- raise TypeError("The CSS function %s doesn't support keyword arguments." % (func_name,))
+ raise TypeError("The CSS function %s doesn't support keyword arguments." % (function_name,))
# TODO another candidate for a "function call" sass type
rendered_args = [arg.render() for arg in args]
return String(
- "%s(%s)" % (func_name, ", ".join(rendered_args)),
+ "%s(%s)" % (function_name, ", ".join(rendered_args)),
quotes=None)
@@ -424,6 +424,28 @@ class ArgspecLiteral(Expression):
yield var.name, value
+ def iter_def_arities(self):
+ """Yield every possible arity this argspec (treated as a function or
+ mixin definition) might accept.
+ """
+ if self.slurp or self.inject:
+ # Accepting slurpy args means anything goes
+ yield None
+
+ # Iterate over the given arguments, counting from 1
+ required_args = 0
+ for arity, (name, default) in enumerate(self.iter_def_argspec(), start=1):
+ if default is None:
+ # This argument is required, so the number of required args is
+ # at least this arity (but might be more, if the next arg is
+ # also required)
+ required_args = arity
+ else:
+ # Every optional argument is an acceptable arity
+ yield arity
+
+ yield required_args
+
def evaluate_call_args(self, calculator):
"""Interpreting this literal as a function call, return a 2-tuple of
``(args, kwargs)``.
diff --git a/scss/blockast.py b/scss/blockast.py
index 812f98c..bc8eea1 100644
--- a/scss/blockast.py
+++ b/scss/blockast.py
@@ -5,9 +5,20 @@ from __future__ import division
import logging
+try:
+ from collections import OrderedDict
+except ImportError:
+ # Backport
+ from ordereddict import OrderedDict
+
+from scss.errors import SassParseError
from scss.expression import Calculator
+from scss.namespace import Namespace
from scss.types import List
from scss.types import Null
+from scss.types import Arglist
+from scss.types import String
+from scss.types import Undefined
from scss.util import normalize_var # TODO put in... namespace maybe?
@@ -36,6 +47,7 @@ class Node(object):
class Declaration(Node):
pass
+
class Assignment(Declaration):
def __init__(self, name, value_expression):
self.name = name
@@ -106,6 +118,7 @@ class _AtRuleMixin(object):
def evaluate(*args):
log.warn("Not yet implemented")
+
class AtRule(_AtRuleMixin, Node):
"""An at-rule with no children, e.g. ``@import``."""
@@ -247,3 +260,189 @@ class AtEachBlock(AtRuleBlock):
for child in self.children:
child.evaluate(compilation)
+
+# TODO make @-rules support both with and without blocks with the same class,
+# so this can be gotten rid of
+class AtIncludeBlock(AtRule):
+ def __init__(self, mixin_name, argspec):
+ self.mixin_name = mixin_name
+ self.argspec = argspec
+
+ @classmethod
+ def parse(cls, argument):
+ # TODO basically copy/pasted from @mixin
+ try:
+ callop = Calculator().parse_expression(
+ argument, 'goal_function_call_opt_parens')
+ except SassParseError:
+ # TODO exc.when("trying to parse a mixin inclusion")
+ raise
+
+ # Unpack the CallOp
+ mixin_name = callop.function_name
+ argspec = callop.argspec
+
+ return cls(mixin_name, argspec)
+
+ def evaluate(self, compilation):
+ caller_namespace = compilation.current_namespace
+ caller_calculator = compilation._make_calculator(caller_namespace)
+
+ # Render the passed arguments, using the caller's namespace
+ args, kwargs = self.argspec.evaluate_call_args(caller_calculator)
+
+ argc = len(args) + len(kwargs)
+ try:
+ mixin = caller_namespace.mixin(self.mixin_name, argc)
+ except KeyError:
+ try:
+ # TODO should really get rid of this; ... works now
+ # Fallback to single parameter:
+ mixin = caller_namespace.mixin(self.mixin_name, 1)
+ except KeyError:
+ # TODO track source line/file
+ # TODO hang on this should be fatal
+ log.error("Mixin not found: %s:%d (%s)", self.mixin_name, argc, "lol idk", extra={'stack': True})
+ return
+ else:
+ args = [List(args, use_comma=True)]
+ # TODO what happens to kwargs?
+
+ # TODO source_file = mixin[0]
+ # TODO lineno = mixin[1]
+
+ # Put the arguments in their own namespace, and let the mixin derive
+ # from it
+ local_namespace = Namespace()
+ self._populate_namespace_from_call(
+ compilation, local_namespace, mixin.argspec, args, kwargs)
+
+ namespaces = [local_namespace]
+ if self.argspec.inject and mixin.argspec.inject:
+ # DEVIATION: Pass the ENTIRE local namespace to the mixin (yikes)
+ namespaces.append(compilation.current_namespace)
+
+ mixin.evaluate(compilation, namespaces)
+
+ # TODO the old SassRule defined from_source_etc here
+ # TODO _rule.options['@content'] = block.unparsed_contents
+
+ def _populate_namespace_from_call(self, compilation, namespace, argspec, args, kwargs):
+ """Populate a temporary @mixin namespace with the arguments passed to
+ an @include.
+ """
+ # Mutation protection
+ args = list(args)
+ kwargs = OrderedDict(kwargs)
+
+ calculator = compilation._make_calculator(namespace)
+
+ # Populate the mixin/function's namespace with its arguments
+ for var_name, node in argspec.iter_def_argspec():
+ if args:
+ # If there are positional arguments left, use the first
+ value = args.pop(0)
+ elif var_name in kwargs:
+ # Try keyword arguments
+ value = kwargs.pop(var_name)
+ elif node is not None:
+ # OK, there's a default argument; try that
+ # DEVIATION: this allows argument defaults to refer to earlier
+ # argument values
+ value = node.evaluate(calculator, divide=True)
+ else:
+ # TODO this should raise
+ # TODO in the meantime, warn_undefined(...)
+ value = Undefined()
+
+ namespace.set_variable(var_name, value, local_only=True)
+
+ if argspec.slurp:
+ # Slurpy var gets whatever is left
+ # TODO should preserve the order of extra kwargs
+ sass_kwargs = []
+ for key, value in kwargs.items():
+ sass_kwargs.append((String(key[1:]), value))
+ namespace.set_variable(
+ argspec.slurp.name,
+ Arglist(args, sass_kwargs))
+ args = []
+ kwargs = {}
+ elif argspec.inject:
+ # Callee namespace gets all the extra kwargs whether declared or
+ # not
+ for var_name, value in kwargs.items():
+ namespace.set_variable(var_name, value, local_only=True)
+ kwargs = {}
+
+ # TODO would be nice to say where the mixin/function came from
+ # TODO generally need more testing of error cases
+ # TODO
+ name = "This mixin"
+ if kwargs:
+ raise NameError("%s has no such argument %s" % (name, kwargs.keys()[0]))
+
+ if args:
+ raise NameError("%s received extra arguments: %r" % (name, args))
+
+ # TODO import_key = mixin[5]
+ # TODO pristine_callee_namespace = mixin[3]
+ # TODO pristine_callee_namespace.use_import(import_key)
+ return namespace
+
+
+class Mixin(object):
+ """Binds a parsed @mixin block to the runtime namespace it was defined in.
+ """
+ def __init__(self, mixin, namespace):
+ self.mixin = mixin
+ self.namespace = namespace
+
+ @property
+ def argspec(self):
+ return self.mixin.argspec
+
+ def evaluate(self, compilation, namespaces):
+ local_namespace = Namespace.derive_from(
+ self.namespace, *namespaces)
+
+ with compilation.push_namespace(local_namespace):
+ for child in self.mixin.children:
+ child.evaluate(compilation)
+
+
+class AtMixinBlock(AtRuleBlock):
+ directive = '@mixin'
+
+ def __init__(self, mixin_name, argspec):
+ # TODO fix parent to not assign to directive/argument and use super
+ # here
+ Block.__init__(self)
+
+ self.mixin_name = mixin_name
+ self.argspec = argspec
+
+ @classmethod
+ def parse(cls, argument):
+ # TODO the original _get_funct_def applied interpolations here before
+ # parsing anything; not sure that's right, but kronuz might rely on it?
+ try:
+ callop = Calculator().parse_expression(
+ argument, 'goal_function_call_opt_parens')
+ except SassParseError:
+ # TODO exc.when("trying to parse a mixin definition")
+ raise
+
+ # Unpack the CallOp
+ mixin_name = callop.function_name
+ argspec = callop.argspec
+
+ return cls(mixin_name, argspec)
+
+ def evaluate(self, compilation):
+ # Evaluating a @mixin just means making it exist; we already have its
+ # AST!
+ namespace = compilation.current_namespace
+ mixin = Mixin(self, namespace)
+ for arity in self.argspec.iter_def_arities():
+ namespace.set_mixin(self.mixin_name, arity, mixin)
diff --git a/scss/compiler.py b/scss/compiler.py
index c8de900..277cb94 100644
--- a/scss/compiler.py
+++ b/scss/compiler.py
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
from __future__ import division
from collections import defaultdict
+from collections import deque
from contextlib import contextmanager
from enum import Enum
import glob
@@ -347,9 +348,8 @@ class Compilation(object):
# the compiler then?
# TODO in which case maybe evaluation should go here and Calculator
# should vanish? (but then it's awkward to use programmatically, hmm)
- self._ancestry_stack = [RuleAncestry()]
- self.current_namespace = root_namespace
- self.current_calculator = self._make_calculator(self.current_namespace)
+ self._ancestry_stack = deque([RuleAncestry()])
+ self._namespace_stack = deque([root_namespace])
self.declarations = []
file_block.evaluate(self)
@@ -460,10 +460,15 @@ class Compilation(object):
# TODO need some real registration+dispatch here
if directive == '@each':
return AtEachBlock.parse(argument)
+ elif directive == '@mixin':
+ return AtMixinBlock.parse(argument)
else:
return AtRuleBlock(directive, argument)
else:
- return AtRule(directive, argument)
+ if directive == '@include':
+ return AtIncludeBlock.parse(argument)
+ else:
+ return AtRule(directive, argument)
# Try splitting this into "name : value"
# TODO strictly speaking this isn't right -- consider foo#{":"}bar.
@@ -779,162 +784,6 @@ class Compilation(object):
return callee_namespace
# @print_timing(10)
- def _at_function(self, calculator, rule, scope, block):
- """
- Implements @mixin and @function
- """
- if not block.argument:
- raise SyntaxError("%s requires a function name (%s)" % (block.directive, rule.file_and_line))
-
- funct, argspec_node = self._get_funct_def(rule, calculator, block.argument)
-
- defaults = {}
- new_params = []
-
- for var_name, default in argspec_node.iter_def_argspec():
- new_params.append(var_name)
- if default is not None:
- defaults[var_name] = default
-
- # TODO a function or mixin is re-parsed every time it's called; there's
- # no AST for anything but expressions :(
- mixin = [rule.source_file, block.lineno, block.unparsed_contents, rule.namespace, argspec_node, rule.source_file]
- if block.directive == '@function':
- def _call(mixin):
- def __call(namespace, *args, **kwargs):
- source_file = mixin[0]
- lineno = mixin[1]
- m_codestr = mixin[2]
- pristine_callee_namespace = mixin[3]
- callee_namespace = pristine_callee_namespace.derive()
-
- # TODO CallOp converts Sass names to Python names, so we
- # have to convert them back to Sass names. would be nice
- # to avoid this back-and-forth somehow
- kwargs = OrderedDict(
- (normalize_var('$' + key), value)
- for (key, value) in kwargs.items())
-
- self._populate_namespace_from_call(
- "Function {0}".format(funct),
- callee_namespace, mixin, args, kwargs)
-
- _rule = SassRule(
- source_file=source_file,
- lineno=lineno,
- unparsed_contents=m_codestr,
- namespace=callee_namespace,
-
- # rule
- import_key=rule.import_key,
- legacy_compiler_options=rule.legacy_compiler_options,
- options=rule.options,
- properties=rule.properties,
- extends_selectors=rule.extends_selectors,
- ancestry=rule.ancestry,
- nested=rule.nested,
- )
- # TODO supposed to throw an error if there's a slurpy arg
- # but keywords() is never called on it
- try:
- self.manage_children(_rule, scope)
- except SassReturn as e:
- return e.retval
- else:
- return Null()
- return __call
- _mixin = _call(mixin)
- _mixin.mixin = mixin
- mixin = _mixin
-
- if block.directive == '@mixin':
- add = rule.namespace.set_mixin
- elif block.directive == '@function':
- add = rule.namespace.set_function
-
- # Register the mixin for every possible arity it takes
- if argspec_node.slurp or argspec_node.inject:
- add(funct, None, mixin)
- else:
- while len(new_params):
- add(funct, len(new_params), mixin)
- param = new_params.pop()
- if param not in defaults:
- break
- if not new_params:
- add(funct, 0, mixin)
- _at_mixin = _at_function
-
- # @print_timing(10)
- def _at_include(self, calculator, rule, scope, block):
- """
- Implements @include, for @mixins
- """
- caller_namespace = rule.namespace
- caller_calculator = self._make_calculator(caller_namespace)
- funct, caller_argspec = self._get_funct_def(rule, caller_calculator, block.argument)
-
- # Render the passed arguments, using the caller's namespace
- args, kwargs = caller_argspec.evaluate_call_args(caller_calculator)
-
- argc = len(args) + len(kwargs)
- try:
- mixin = caller_namespace.mixin(funct, argc)
- except KeyError:
- try:
- # TODO maybe? don't do this, once '...' works
- # Fallback to single parameter:
- mixin = caller_namespace.mixin(funct, 1)
- except KeyError:
- log.error("Mixin not found: %s:%d (%s)", funct, argc, rule.file_and_line, extra={'stack': True})
- return
- else:
- args = [List(args, use_comma=True)]
- # TODO what happens to kwargs?
-
- source_file = mixin[0]
- lineno = mixin[1]
- m_codestr = mixin[2]
- pristine_callee_namespace = mixin[3]
- callee_argspec = mixin[4]
- if caller_argspec.inject and callee_argspec.inject:
- # DEVIATION: Pass the ENTIRE local namespace to the mixin (yikes)
- callee_namespace = Namespace.derive_from(
- caller_namespace,
- pristine_callee_namespace)
- else:
- callee_namespace = pristine_callee_namespace.derive()
-
- self._populate_namespace_from_call(
- "Mixin {0}".format(funct),
- callee_namespace, mixin, args, kwargs)
-
- _rule = SassRule(
- # These must be file and line in which the @include occurs
- source_file=rule.source_file,
- lineno=rule.lineno,
-
- # These must be file and line in which the @mixin was defined
- from_source_file=source_file,
- from_lineno=lineno,
-
- unparsed_contents=m_codestr,
- namespace=callee_namespace,
-
- # rule
- import_key=rule.import_key,
- legacy_compiler_options=rule.legacy_compiler_options,
- options=rule.options,
- properties=rule.properties,
- extends_selectors=rule.extends_selectors,
- ancestry=rule.ancestry,
- nested=rule.nested,
- )
-
- _rule.options['@content'] = block.unparsed_contents
- self.manage_children(_rule, scope)
-
- # @print_timing(10)
def _at_content(self, calculator, rule, scope, block):
"""
Implements @content
@@ -1242,46 +1091,6 @@ class Compilation(object):
self.manage_children(inner_rule, scope)
# @print_timing(10)
- def _at_each(self, calculator, rule, scope, block):
- """
- Implements @each
- """
- varstring, _, valuestring = block.argument.partition(' in ')
- values = calculator.calculate(valuestring)
- if not values:
- return
-
- varlist = [
- normalize_var(calculator.do_glob_math(var.strip()))
- # TODO use list parsing here
- for var in varstring.split(",")
- ]
-
- # `@each $foo, in $bar` unpacks, but `@each $foo in $bar` does not!
- unpack = len(varlist) > 1
- if not varlist[-1]:
- varlist.pop()
-
- inner_rule = rule.copy()
- inner_rule.unparsed_contents = block.unparsed_contents
- if not self.should_scope_loop_in_rule(inner_rule):
- # DEVIATION: Allow not creating a new namespace
- inner_rule.namespace = rule.namespace
-
- for v in List.from_maybe(values):
- if unpack:
- v = List.from_maybe(v)
- for i, var in enumerate(varlist):
- if i >= len(v):
- value = Null()
- else:
- value = v[i]
- inner_rule.namespace.set_variable(var, value)
- else:
- inner_rule.namespace.set_variable(varlist[0], v)
- self.manage_children(inner_rule, scope)
-
- # @print_timing(10)
def _at_while(self, calculator, rule, scope, block):
"""
Implements @while
@@ -1595,6 +1404,23 @@ class Compilation(object):
finally:
self._ancestry_stack.pop()
+ @property
+ def current_namespace(self):
+ return self._namespace_stack[-1]
+
+ @property
+ def current_calculator(self):
+ return self._make_calculator(self.current_namespace)
+
+ @contextmanager
+ def push_namespace(self, namespace):
+ """Manually create a new scope with the given namespace."""
+ self._namespace_stack.append(namespace)
+ try:
+ yield namespace
+ finally:
+ self._namespace_stack.pop()
+
def add_declaration(self, prop, value):
self.declarations.append((self.current_ancestry, [(prop, value)]))
return
diff --git a/scss/grammar/expression.g b/scss/grammar/expression.g
index df4d7e0..22443d4 100644
--- a/scss/grammar/expression.g
+++ b/scss/grammar/expression.g
@@ -96,13 +96,27 @@ parser SassExpression:
# -------------------------------------------------------------------------
# Goals:
- rule goal: expr_lst END {{ return expr_lst }}
+ rule goal:
+ expr_lst END {{ return expr_lst }}
- rule goal_argspec: argspec END {{ return argspec }}
+ rule goal_argspec:
+ argspec END {{ return argspec }}
+
+ rule goal_function_call:
+ function_call END {{ return function_call }}
+
+ rule goal_function_call_opt_parens:
+ # This is used for @mixin and @include, where the parens are optional
+ BAREWORD {{ argspec = ArgspecLiteral([]) }}
+ [ LPAR argspec RPAR ]
+ END {{ return CallOp(BAREWORD, argspec) }}
# Arguments:
# TODO should support multiple slurpies, and enforce (probably not in the
# parser) that positional args come first
+ rule function_call:
+ FNCT LPAR argspec RPAR {{ return CallOp(FNCT, argspec) }}
+
rule argspec:
[
argspec_items {{ args, slurpy = argspec_items }}
@@ -225,7 +239,7 @@ parser SassExpression:
{{ return interpolated_url }}
| LITERAL_FUNCTION LPAR interpolated_function RPAR
{{ return Interpolation.maybe(interpolated_function, type=Function, function_name=LITERAL_FUNCTION) }}
- | FNCT LPAR argspec RPAR {{ return CallOp(FNCT, argspec) }}
+ | function_call {{ return function_call }}
| BANG_IMPORTANT {{ return Literal(String(BANG_IMPORTANT, quotes=None)) }}
| interpolated_bareword {{ return Interpolation.maybe(interpolated_bareword) }}
| NUM {{ UNITS = None }}
diff --git a/scss/grammar/expression.py b/scss/grammar/expression.py
index cf3c9e7..6eed2c8 100644
--- a/scss/grammar/expression.py
+++ b/scss/grammar/expression.py
@@ -105,6 +105,28 @@ class SassExpression(Parser):
END = self._scan('END')
return argspec
+ def goal_function_call(self):
+ function_call = self.function_call()
+ END = self._scan('END')
+ return function_call
+
+ def goal_function_call_opt_parens(self):
+ BAREWORD = self._scan('BAREWORD')
+ argspec = ArgspecLiteral([])
+ if self._peek(self.goal_function_call_opt_parens_rsts) == 'LPAR':
+ LPAR = self._scan('LPAR')
+ argspec = self.argspec()
+ RPAR = self._scan('RPAR')
+ END = self._scan('END')
+ return CallOp(BAREWORD, argspec)
+
+ def function_call(self):
+ FNCT = self._scan('FNCT')
+ LPAR = self._scan('LPAR')
+ argspec = self.argspec()
+ RPAR = self._scan('RPAR')
+ return CallOp(FNCT, argspec)
+
def argspec(self):
_token_ = self._peek(self.argspec_rsts)
if _token_ not in self.argspec_chks:
@@ -328,11 +350,8 @@ class SassExpression(Parser):
RPAR = self._scan('RPAR')
return Interpolation.maybe(interpolated_function, type=Function, function_name=LITERAL_FUNCTION)
elif _token_ == 'FNCT':
- FNCT = self._scan('FNCT')
- LPAR = self._scan('LPAR')
- argspec = self.argspec()
- RPAR = self._scan('RPAR')
- return CallOp(FNCT, argspec)
+ function_call = self.function_call()
+ return function_call
elif _token_ == 'BANG_IMPORTANT':
BANG_IMPORTANT = self._scan('BANG_IMPORTANT')
return Literal(String(BANG_IMPORTANT, quotes=None))
@@ -475,7 +494,7 @@ class SassExpression(Parser):
u_expr_chks = set(['LPAR', 'DOUBLE_QUOTE', 'BAREWORD', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'VAR', 'NUM', 'FNCT', 'LITERAL_FUNCTION', 'BANG_IMPORTANT', 'SINGLE_QUOTE'])
m_expr_rsts = set(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'OR', '","'])
argspec_items_rsts = set(['RPAR', 'END', '","'])
- expr_slst_chks = set(['INTERP_END', 'RPAR', 'END', '":"', '","'])
+ argspec_items_chks = set(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'VAR', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'BAREWORD', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'FNCT', 'NOT', 'BANG_IMPORTANT', 'SINGLE_QUOTE'])
expr_lst_rsts = set(['INTERP_END', 'END', '","'])
expr_map_or_list_rsts = set(['RPAR', '":"', '","'])
argspec_item_chks = set(['LPAR', 'DOUBLE_QUOTE', 'VAR', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'BAREWORD', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'FNCT', 'NOT', 'BANG_IMPORTANT', 'SINGLE_QUOTE'])
@@ -503,9 +522,10 @@ class SassExpression(Parser):
m_expr_chks = set(['MUL', 'DIV'])
goal_interpolated_anything_rsts = set(['END', 'INTERP_START'])
interpolated_bare_url_rsts = set(['RPAR', 'INTERP_START'])
- argspec_items_chks = set(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'VAR', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'BAREWORD', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'FNCT', 'NOT', 'BANG_IMPORTANT', 'SINGLE_QUOTE'])
+ expr_slst_chks = set(['INTERP_END', 'RPAR', 'END', '":"', '","'])
argspec_rsts = set(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'END', 'SLURPYVAR', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'BAREWORD', 'DOTDOTDOT', 'RPAR', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'SIGN', 'SINGLE_QUOTE'])
atom_rsts = set(['LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'BAREWORD', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'RPAR', 'SINGLE_QUOTE'])
+ goal_function_call_opt_parens_rsts = set(['LPAR', 'END'])
argspec_items_rsts__ = set(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'VAR', 'SLURPYVAR', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'BAREWORD', 'DOTDOTDOT', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'FNCT', 'NOT', 'BANG_IMPORTANT', 'SINGLE_QUOTE'])
argspec_rsts_ = set(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'END', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'BAREWORD', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'RPAR', 'SINGLE_QUOTE'])