From e51ab4d3c8d52604b5f2fbc83d2c1f235941fa94 Mon Sep 17 00:00:00 2001 From: "Eevee (Alex Munroe)" Date: Sun, 31 Aug 2014 18:52:45 -0700 Subject: Ported @mixin and @include to the new block AST. --- scss/ast.py | 42 +++++++-- scss/blockast.py | 199 +++++++++++++++++++++++++++++++++++++++ scss/compiler.py | 226 ++++++--------------------------------------- scss/grammar/expression.g | 20 +++- scss/grammar/expression.py | 34 +++++-- 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. @@ -778,162 +783,6 @@ class Compilation(object): pristine_callee_namespace.use_import(import_key) 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): """ @@ -1241,46 +1090,6 @@ class Compilation(object): inner_rule.namespace.set_variable(var, Number(i)) 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): """ @@ -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']) -- cgit v1.2.1