diff --git a/lib/sass/script/funcall.rb b/lib/sass/script/funcall.rb index 819962ea..88783cc7 100644 --- a/lib/sass/script/funcall.rb +++ b/lib/sass/script/funcall.rb @@ -34,12 +34,22 @@ module Sass "#{name}(#{args.map {|a| a.to_sass}.join(', ')})" end + # Returns the arguments to the function. + # + # @return [Array] + # @see Node#children + def children + @args + end + + protected + # Evaluates the function call. # # @param environment [Sass::Environment] The environment in which to evaluate the SassScript # @return [Literal] The SassScript object that is the value of the function call # @raise [Sass::SyntaxError] if the function call raises an ArgumentError - def perform(environment) + def _perform(environment) args = self.args.map {|a| a.perform(environment)} ruby_name = name.gsub('-', '_') unless Haml::Util.has?(:public_instance_method, Functions, ruby_name) && ruby_name !~ /^__/ @@ -53,14 +63,6 @@ module Sass raise e unless e.backtrace.any? {|t| t =~ /:in `(block in )?(#{name}|perform)'$/} raise Sass::SyntaxError.new("#{e.message} for `#{name}'") end - - # Returns the arguments to the function. - # - # @return [Array] - # @see Node#children - def children - @args - end end end end diff --git a/lib/sass/script/lexer.rb b/lib/sass/script/lexer.rb index c3c2a7f8..092555b9 100644 --- a/lib/sass/script/lexer.rb +++ b/lib/sass/script/lexer.rb @@ -157,6 +157,8 @@ module Sass unless value raise SyntaxError.new("Syntax error in '#{@scanner.string}' at character #{current_position}.") end + + value.last.line = @line if value.last.is_a?(Script::Node) Token.new(value.first, value.last, @line, last_match_position) end diff --git a/lib/sass/script/literal.rb b/lib/sass/script/literal.rb index 5a4345ec..ae6a431c 100644 --- a/lib/sass/script/literal.rb +++ b/lib/sass/script/literal.rb @@ -23,14 +23,6 @@ module Sass::Script @value = value end - # Evaluates the literal. - # - # @param environment [Sass::Environment] The environment in which to evaluate the SassScript - # @return [Literal] This literal - def perform(environment) - self - end - # Returns an empty array. # # @return [Array] empty @@ -207,5 +199,15 @@ MSG raise Sass::SyntaxError.new("[BUG] All subclasses of Sass::Literal must implement #to_s.") end alias_method :to_sass, :to_s + + protected + + # Evaluates the literal. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Literal] This literal + def _perform(environment) + self + end end end diff --git a/lib/sass/script/node.rb b/lib/sass/script/node.rb index 93400ee0..63ae2ff1 100644 --- a/lib/sass/script/node.rb +++ b/lib/sass/script/node.rb @@ -8,6 +8,11 @@ module Sass::Script # @return [{Symbol => Object}] attr_reader :options + # The line of the document on which this node appeared. + # + # @return [Fixnum] + attr_accessor :line + # Sets the options hash for this node, # as well as for all child nodes. # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}. @@ -20,10 +25,16 @@ module Sass::Script # Evaluates the node. # + # \{#perform} shouldn't be overridden directly; + # instead, override \{#\_perform}. + # # @param environment [Sass::Environment] The environment in which to evaluate the SassScript # @return [Literal] The SassScript object that is the value of the SassScript def perform(environment) - raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #perform.") + _perform(environment) + rescue Sass::SyntaxError => e + e.modify_backtrace(:line => line) + raise e end # Returns all child nodes of this node. @@ -39,5 +50,16 @@ module Sass::Script def to_sass raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #to_sass.") end + + protected + + # Evaluates this node. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Literal] The SassScript object that is the value of the SassScript + # @see #perform + def _perform(environment) + raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #_perform.") + end end end diff --git a/lib/sass/script/operation.rb b/lib/sass/script/operation.rb index 1a638cfe..d53c8a88 100644 --- a/lib/sass/script/operation.rb +++ b/lib/sass/script/operation.rb @@ -42,12 +42,22 @@ module Sass::Script "#{o1}#{sep}#{o2}" end + # Returns the operands for this operation. + # + # @return [Array] + # @see Node#children + def children + [@operand1, @operand2] + end + + protected + # Evaluates the operation. # # @param environment [Sass::Environment] The environment in which to evaluate the SassScript # @return [Literal] The SassScript object that is the value of the operation # @raise [Sass::SyntaxError] if the operation is undefined for the operands - def perform(environment) + def _perform(environment) literal1 = @operand1.perform(environment) literal2 = @operand2.perform(environment) begin @@ -60,14 +70,6 @@ module Sass::Script end end - # Returns the operands for this operation. - # - # @return [Array] - # @see Node#children - def children - [@operand1, @operand2] - end - private def operand_to_sass(pred, op) diff --git a/lib/sass/script/parser.rb b/lib/sass/script/parser.rb index e803439d..cbbf7475 100644 --- a/lib/sass/script/parser.rb +++ b/lib/sass/script/parser.rb @@ -5,6 +5,13 @@ module Sass # The parser for SassScript. # It parses a string of code into a tree of {Script::Node}s. class Parser + # The line number of the parser's current position. + # + # @return [Fixnum] + def line + @lexer.line + end + # @param str [String, StringScanner] The source text to parse # @param line [Fixnum] The line on which the SassScript appears. # Used for error reporting @@ -151,7 +158,9 @@ module Sass def #{name} return unless e = #{sub} while tok = try_tok(#{ops.map {|o| o.inspect}.join(', ')}) + line = @lexer.line e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type) + e.line = line end e end @@ -162,7 +171,10 @@ RUBY class_eval < e - raise e unless e.name.to_s == operator.to_s - raise Sass::SyntaxError.new("Undefined unary operation: \"#{@operator} #{literal}\".") - end - # Returns the operand of the operation. # # @return [Array] @@ -45,5 +31,21 @@ module Sass::Script def children [@operand] end + + protected + + # Evaluates the operation. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Literal] The SassScript object that is the value of the operation + # @raise [Sass::SyntaxError] if the operation is undefined for the operand + def _perform(environment) + operator = "unary_#{@operator}" + literal = @operand.perform(environment) + literal.send(operator) + rescue NoMethodError => e + raise e unless e.name.to_s == operator.to_s + raise Sass::SyntaxError.new("Undefined unary operation: \"#{@operator} #{literal}\".") + end end end diff --git a/lib/sass/script/variable.rb b/lib/sass/script/variable.rb index 346594d7..2fb4d98e 100644 --- a/lib/sass/script/variable.rb +++ b/lib/sass/script/variable.rb @@ -18,16 +18,6 @@ module Sass end alias_method :to_sass, :inspect - # Evaluates the variable. - # - # @param environment [Sass::Environment] The environment in which to evaluate the SassScript - # @return [Literal] The SassScript object that is the value of the variable - # @raise [Sass::SyntaxError] if the variable is undefined - def perform(environment) - (val = environment.var(name)) && (return val) - raise SyntaxError.new("Undefined variable: \"!#{name}\".") - end - # Returns an empty array. # # @return [Array] empty @@ -35,6 +25,18 @@ module Sass def children [] end + + protected + + # Evaluates the variable. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Literal] The SassScript object that is the value of the variable + # @raise [Sass::SyntaxError] if the variable is undefined + def _perform(environment) + (val = environment.var(name)) && (return val) + raise SyntaxError.new("Undefined variable: \"!#{name}\".") + end end end end diff --git a/lib/sass/scss/parser.rb b/lib/sass/scss/parser.rb index c5385e9e..f9dac564 100644 --- a/lib/sass/scss/parser.rb +++ b/lib/sass/scss/parser.rb @@ -584,8 +584,11 @@ MESSAGE end def sass_script(*args) - ScriptParser.new(@scanner, @line, - @scanner.pos - (@scanner.string.rindex("\n") || 0)).send(*args) + parser = ScriptParser.new(@scanner, @line, + @scanner.pos - (@scanner.string.rindex("\n") || 0)) + result = parser.send(*args) + @line = parser.line + result end EXPR_NAMES = { diff --git a/test/sass/scss/scss_test.rb b/test/sass/scss/scss_test.rb index 4fa4e275..36b85882 100755 --- a/test/sass/scss/scss_test.rb +++ b/test/sass/scss/scss_test.rb @@ -824,7 +824,7 @@ SCSS assert_equal 2, e.sass_line end - def test_multiline_script_error + def test_multiline_script_syntax_error render < e + assert_equal "Undefined variable: \"!bang\".", e.message + assert_equal 4, e.sass_line + end + + def test_post_multiline_script_runtime_error + render < e + assert_equal "Undefined variable: \"!bop\".", e.message + assert_equal 5, e.sass_line + end + + def test_multiline_property_runtime_error + render < e + assert_equal "Undefined variable: \"!bang\".", e.message + assert_equal 4, e.sass_line + end end