class Sass::Script::Tree::Funcall
returns a string representation of the function call.
{Sass::Script::Functions}, or if no function with the given name exists it
A function call either calls one of the functions in
A SassScript parse node representing a function call.
def _perform(environment)
-
(Sass::SyntaxError)
- if the function call raises an ArgumentError
Returns:
-
(Sass::Script::Value)
- The SassScript object that is the value of the function call
Parameters:
-
environment
(Sass::Environment
) -- The environment in which to evaluate the SassScript
def _perform(environment) args = @args.each_with_index. map {|a, i| perform_arg(a, environment, signature && signature.args[i])} keywords = Sass::Util.map_hash(@keywords) do |k, v| [k, perform_arg(v, environment, k.tr('-', '_'))] end splat = Sass::Tree::Visitors::Perform.perform_splat( @splat, keywords, @kwarg_splat, environment) fn = @callable || environment.function(@name) if fn && fn.origin == :stylesheet environment.stack.with_function(filename, line, name) do return without_original(perform_sass_fn(fn, args, splat, environment)) end end args = construct_ruby_args(ruby_name, args, splat, environment) if Sass::Script::Functions.callable?(ruby_name) && (!fn || fn.origin == :builtin) local_environment = Sass::Environment.new(environment.global_env, environment.options) local_environment.caller = Sass::ReadOnlyEnvironment.new(environment, environment.options) result = local_environment.stack.with_function(filename, line, name) do opts(Sass::Script::Functions::EvaluationContext.new( local_environment).send(ruby_name, *args)) end without_original(result) else opts(to_literal(args)) end rescue ArgumentError => e reformat_argument_error(e) end
def children
- See: Node#children -
Returns:
-
(Array
-)
def children res = @args + @keywords.values res << @splat if @splat res << @kwarg_splat if @kwarg_splat res end
def construct_ruby_args(name, args, splat, environment)
def construct_ruby_args(name, args, splat, environment) args += splat.to_a if splat # All keywords are contained in splat.keywords for consistency, # even if there were no splats passed in. old_keywords_accessed = splat.keywords_accessed keywords = splat.keywords splat.keywords_accessed = old_keywords_accessed unless (signature = Sass::Script::Functions.signature(name.to_sym, args.size, keywords.size)) return args if keywords.empty? raise Sass::SyntaxError.new("Function #{name} doesn't support keyword arguments") end # If the user passes more non-keyword args than the function expects, # but it does expect keyword args, Ruby's arg handling won't raise an error. # Since we don't want to make functions think about this, # we'll handle it for them here. if signature.var_kwargs && !signature.var_args && args.size > signature.args.size raise Sass::SyntaxError.new( "#{args[signature.args.size].inspect} is not a keyword argument for `#{name}'") elsif keywords.empty? args << {} if signature.var_kwargs return args end argnames = signature.args[args.size..-1] || [] deprecated_argnames = (signature.deprecated && signature.deprecated[args.size..-1]) || [] args += argnames.zip(deprecated_argnames).map do |(argname, deprecated_argname)| if keywords.has_key?(argname) keywords.delete(argname) elsif deprecated_argname && keywords.has_key?(deprecated_argname) deprecated_argname = keywords.denormalize(deprecated_argname) Sass::Util.sass_warn("DEPRECATION WARNING: The `$#{deprecated_argname}' argument for " + "`#{@name}()' has been renamed to `$#{argname}'.") keywords.delete(deprecated_argname) else raise Sass::SyntaxError.new("Function #{name} requires an argument named $#{argname}") end end if keywords.size > 0 if signature.var_kwargs # Don't pass a NormalizedMap to a Ruby function. args << keywords.to_hash else argname = keywords.keys.sort.first if signature.args.include?(argname) raise Sass::SyntaxError.new( "Function #{name} was passed argument $#{argname} both by position and by name") else raise Sass::SyntaxError.new( "Function #{name} doesn't have an argument named $#{argname}") end end end args end
def deep_copy
- See: Node#deep_copy -
def deep_copy node = dup node.instance_variable_set('@args', args.map {|a| a.deep_copy}) copied_keywords = Sass::Util::NormalizedMap.new @keywords.as_stored.each {|k, v| copied_keywords[k] = v.deep_copy} node.instance_variable_set('@keywords', copied_keywords) node end
def initialize(name_or_callable, args, keywords, splat, kwarg_splat)
-
kwarg_splat
(Node
) -- See \{#kwarg_splat} -
splat
(Node
) -- See \{#splat} -
keywords
(Sass::Util::NormalizedMap
) -- See \{#keywords} -
args
(Array
) -- See \{#args} -
name_or_callable
(String, Sass::Callable
) -- See \{#name}
def initialize(name_or_callable, args, keywords, splat, kwarg_splat) if name_or_callable.is_a?(Sass::Callable) @callable = name_or_callable @name = name_or_callable.name else @callable = nil @name = name_or_callable end @args = args @keywords = keywords @splat = splat @kwarg_splat = kwarg_splat super() end
def inspect
-
(String)
- A string representation of the function call
def inspect args = @args.map {|a| a.inspect}.join(', ') keywords = @keywords.as_stored.to_a.map {|k, v| "$#{k}: #{v.inspect}"}.join(', ') if self.splat splat = args.empty? && keywords.empty? ? "" : ", " splat = "#{splat}#{self.splat.inspect}..." splat = "#{splat}, #{kwarg_splat.inspect}..." if kwarg_splat end "#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})" end
def perform_arg(argument, environment, name)
def perform_arg(argument, environment, name) return argument if signature && signature.delayed_args.include?(name) argument.perform(environment) end
def perform_sass_fn(function, args, splat, environment)
def perform_sass_fn(function, args, splat, environment) Sass::Tree::Visitors::Perform.perform_arguments(function, args, splat, environment) do |env| env.caller = Sass::Environment.new(environment) val = catch :_sass_return do function.tree.each {|c| Sass::Tree::Visitors::Perform.visit(c, env)} raise Sass::SyntaxError.new("Function #{@name} finished without @return") end val end end
def reformat_argument_error(e)
def reformat_argument_error(e) message = e.message # If this is a legitimate Ruby-raised argument error, re-raise it. # Otherwise, it's an error in the user's stylesheet, so wrap it. if Sass::Util.rbx? # Rubinius has a different error report string than vanilla Ruby. It # also doesn't put the actual method for which the argument error was # thrown in the backtrace, nor does it include `send`, so we look for # `_perform`. if e.message =~ /^method '([^']+)': given (\d+), expected (\d+)/ error_name, given, expected = $1, $2, $3 raise e if error_name != ruby_name || e.backtrace[0] !~ /:in `_perform'$/ message = "wrong number of arguments (#{given} for #{expected})" end elsif Sass::Util.jruby? should_maybe_raise = e.message =~ /^wrong number of arguments calling `[^`]+` \((\d+) for (\d+)\)/ given, expected = $1, $2 if should_maybe_raise # JRuby 1.7 includes __send__ before send and _perform. trace = e.backtrace.dup raise e if trace.shift !~ /:in `__send__'$/ # JRuby (as of 1.7.2) doesn't put the actual method # for which the argument error was thrown in the backtrace, so we # detect whether our send threw an argument error. if !(trace[0] =~ /:in `send'$/ && trace[1] =~ /:in `_perform'$/) raise e else # JRuby 1.7 doesn't use standard formatting for its ArgumentErrors. message = "wrong number of arguments (#{given} for #{expected})" end end elsif (md = /^wrong number of arguments \(given (\d+), expected (\d+)\)/.match(e.message)) && e.backtrace[0] =~ /:in `#{ruby_name}'$/ # Handle ruby 2.3 error formatting message = "wrong number of arguments (#{md[1]} for #{md[2]})" elsif e.message =~ /^wrong number of arguments/ && e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/ raise e end raise Sass::SyntaxError.new("#{message} for `#{name}'") end
def ruby_name
def ruby_name @ruby_name ||= @name.tr('-', '_') end
def signature
def signature @signature ||= Sass::Script::Functions.signature(name.to_sym, @args.size, @keywords.size) end
def to_literal(args)
Compass historically overrode this before it changed name to {Funcall#to_value}.
def to_literal(args) to_value(args) end
def to_sass(opts = {})
- See: Node#to_sass -
def to_sass(opts = {}) arg_to_sass = lambda do |arg| sass = arg.to_sass(opts) sass = "(#{sass})" if arg.is_a?(Sass::Script::Tree::ListLiteral) && arg.separator == :comma sass end args = @args.map(&arg_to_sass) keywords = @keywords.as_stored.to_a.map {|k, v| "$#{dasherize(k, opts)}: #{arg_to_sass[v]}"} if self.splat splat = "#{arg_to_sass[self.splat]}..." kwarg_splat = "#{arg_to_sass[self.kwarg_splat]}..." if self.kwarg_splat end arglist = [args, splat, keywords, kwarg_splat].flatten.compact.join(', ') "#{dasherize(name, opts)}(#{arglist})" end
def to_value(args)
it with a cross-browser implementation for functions that require vendor prefixes
This method is factored out from `_perform` so that compass can override
def to_value(args) Sass::Script::Value::String.new("#{name}(#{args.join(', ')})") end
def without_original(value)
def without_original(value) return value unless value.is_a?(Sass::Script::Value::Number) value = value.dup value.original = nil value end