# class Sass::Script::Parser

The parser for SassScript. It parses a string of code into a tree of {Script::Tree::Node}s.

### Constants

ASSOCIATIVE
EXPR_NAMES

  It would be possible to have unified assert and try methods, but detecting the method/token difference turns out to be quite expensive.

PRECEDENCE

### Public Class Methods

associative?(op) click to toggle source

Returns whether or not the given operation is associative.

@private

    # File lib/sass/script/parser.rb, line 257
    def associative?(op)
      ASSOCIATIVE.include?(op)
    end

new(str, line, offset, options = {}) click to toggle source

@param str [String, StringScanner] The source text to parse @param line [Integer] The line on which the SassScript appears.

    Used for error reporting and sourcemap building

@param offset [Integer] The character (not byte) offset where the script starts in the line.

    Used for error reporting and sourcemap building

@param options [{Symbol => Object}] An options hash; see

    {file:SASS_REFERENCE.md#Options the Sass options documentation}.
    This supports an additional `:allow_extra_text` option that controls
    whether the parser throws an error when extra text is encountered
    after the parsed construct.

    # File lib/sass/script/parser.rb, line 32
    def initialize(str, line, offset, options = {})
      @options = options
      @allow_extra_text = options.delete(:allow_extra_text)
      @lexer = lexer_class.new(str, line, offset, options)
      @stop_at = nil
    end

parse(*args) click to toggle source

Parses a SassScript expression.

@overload parse(str, line, offset, filename = nil) @return [Script::Tree::Node] The root node of the parse tree @see Parser#initialize @see #parse

    # File lib/sass/script/parser.rb, line 227
    def self.parse(*args)
      new(*args).parse
    end

precedence_of(op) click to toggle source

Returns an integer representing the precedence of the given operator. A lower integer indicates a looser binding.

@private

    # File lib/sass/script/parser.rb, line 247
    def precedence_of(op)
      PRECEDENCE.each_with_index do |e, i|
        return i if Array(e).include?(op)
      end
      raise "[BUG] Unknown operator #{op.inspect}"
    end

### Private Class Methods

production(name, sub, *ops) click to toggle source

Defines a simple left-associative production. name is the name of the production, sub is the name of the production beneath it, and ops is a list of operators for this precedence level

    # File lib/sass/script/parser.rb, line 267
            def production(name, sub, *ops)
              class_eval <<RUBY, __FILE__, __LINE__ + 1
                def #{name}
                  interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect})
                  return interp if interp
                  return unless e = #{sub}
                  while tok = try_toks(#{ops.map {|o| o.inspect}.join(', ')})
                    if interp = try_op_before_interp(tok, e)
                      other_interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}, interp)
                      return interp unless other_interp
                      return other_interp
                    end

                    e = node(Tree::Operation.new(e, assert_expr(#{sub.inspect}), tok.type),
                             e.source_range.start_pos)
                  end
                  e
                end
    RUBY
            end

unary(op, sub) click to toggle source

    # File lib/sass/script/parser.rb, line 288
            def unary(op, sub)
              class_eval <<RUBY, __FILE__, __LINE__ + 1
                def unary_#{op}
                  return #{sub} unless tok = try_tok(:#{op})
                  interp = try_op_before_interp(tok)
                  return interp if interp
                  start_pos = source_position
                  node(Tree::UnaryOperation.new(assert_expr(:unary_#{op}), :#{op}), start_pos)
                end
    RUBY
            end

### Public Instance Methods

line() click to toggle source

The line number of the parser's current position.

@return [Integer]

    # File lib/sass/script/parser.rb, line 11
    def line
      @lexer.line
    end

offset() click to toggle source

The column number of the parser's current position.

@return [Integer]

    # File lib/sass/script/parser.rb, line 18
    def offset
      @lexer.offset
    end

parse() click to toggle source

Parses a SassScript expression.

@return [Script::Tree::Node] The root node of the parse tree @raise [Sass::SyntaxError] if the expression isn't valid SassScript

    # File lib/sass/script/parser.rb, line 67
    def parse
      expr = assert_expr :expr
      assert_done
      expr.options = @options
      check_for_interpolation expr
      expr
    rescue Sass::SyntaxError => e
      e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
      raise e
    end

parse_function_definition_arglist() click to toggle source

Parses the argument list for a function definition.

@return [(Array<Script::Tree::Node>, Script::Tree::Node)]

    The root nodes of the arguments, and the splat argument.

@raise [Sass::SyntaxError] if the argument list isn't valid SassScript

    # File lib/sass/script/parser.rb, line 174
    def parse_function_definition_arglist
      args, splat = defn_arglist!(true)
      assert_done

      args.each do |k, v|
        check_for_interpolation k
        k.options = @options

        if v
          check_for_interpolation v
          v.options = @options
        end
      end

      if splat
        check_for_interpolation splat
        splat.options = @options
      end

      return args, splat
    rescue Sass::SyntaxError => e
      e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
      raise e
    end

parse_interpolated(warn_for_color = false) click to toggle source

Parses a SassScript expression within an interpolated segment (`#{}`). This means that it stops when it comes across an unmatched `}`, which signals the end of an interpolated segment, it returns rather than throwing an error.

@param warn_for_color [Boolean] Whether raw color values passed to

    interoplation should cause a warning.

@return [Script::Tree::Node] The root node of the parse tree @raise [Sass::SyntaxError] if the expression isn't valid SassScript

    # File lib/sass/script/parser.rb, line 48
    def parse_interpolated(warn_for_color = false)
      # Start two characters back to compensate for #{
      start_pos = Sass::Source::Position.new(line, offset - 2)
      expr = assert_expr :expr
      assert_tok :end_interpolation
      expr = Sass::Script::Tree::Interpolation.new(
        nil, expr, nil, false, false, :warn_for_color => warn_for_color)
      check_for_interpolation expr
      expr.options = @options
      node(expr, start_pos)
    rescue Sass::SyntaxError => e
      e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
      raise e
    end

parse_mixin_definition_arglist() click to toggle source

Parses the argument list for a mixin definition.

@return [(Array<Script::Tree::Node>, Script::Tree::Node)]

    The root nodes of the arguments, and the splat argument.

@raise [Sass::SyntaxError] if the argument list isn't valid SassScript

    # File lib/sass/script/parser.rb, line 144
    def parse_mixin_definition_arglist
      args, splat = defn_arglist!(false)
      assert_done

      args.each do |k, v|
        check_for_interpolation k
        k.options = @options

        if v
          check_for_interpolation v
          v.options = @options
        end
      end

      if splat
        check_for_interpolation splat
        splat.options = @options
      end

      return args, splat
    rescue Sass::SyntaxError => e
      e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
      raise e
    end

parse_mixin_include_arglist() click to toggle source

Parses the argument list for a mixin include.

@return [(Array<Script::Tree::Node>,

           {String => Script::Tree::Node},
           Script::Tree::Node,
           Script::Tree::Node)]
    The root nodes of the positional arguments, keyword arguments, and
    splat argument(s). Keyword arguments are in a hash from names to values.

@raise [Sass::SyntaxError] if the argument list isn't valid SassScript

    # File lib/sass/script/parser.rb, line 105
    def parse_mixin_include_arglist
      args, keywords = [], {}
      if try_tok(:lparen)
        args, keywords, splat, kwarg_splat = mixin_arglist
        assert_tok(:rparen)
      end
      assert_done

      args.each do |a|
        check_for_interpolation a
        a.options = @options
      end

      keywords.each do |_, v|
        check_for_interpolation v
        v.options = @options
      end

      if splat
        check_for_interpolation splat
        splat.options = @options
      end

      if kwarg_splat
        check_for_interpolation kwarg_splat
        kwarg_splat.options = @options
      end

      return args, keywords, splat, kwarg_splat
    rescue Sass::SyntaxError => e
      e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
      raise e
    end

parse_string() click to toggle source

Parse a single string value, possibly containing interpolation. Doesn't assert that the scanner is finished after parsing.

@return [Script::Tree::Node] The root node of the parse tree. @raise [Sass::SyntaxError] if the string isn't valid SassScript

    # File lib/sass/script/parser.rb, line 204
    def parse_string
      unless (peek = @lexer.peek) &&
          (peek.type == :string ||
          (peek.type == :funcall && peek.value.downcase == 'url'))
        lexer.expected!("string")
      end

      expr = assert_expr :funcall
      check_for_interpolation expr
      expr.options = @options
      @lexer.unpeek!
      expr
    rescue Sass::SyntaxError => e
      e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
      raise e
    end

parse_until(tokens) click to toggle source

Parses a SassScript expression, ending it when it encounters one of the given identifier tokens.

@param tokens [#include?(String)] A set of strings that delimit the expression. @return [Script::Tree::Node] The root node of the parse tree @raise [Sass::SyntaxError] if the expression isn't valid SassScript

    # File lib/sass/script/parser.rb, line 84
    def parse_until(tokens)
      @stop_at = tokens
      expr = assert_expr :expr
      assert_done
      expr.options = @options
      check_for_interpolation expr
      expr
    rescue Sass::SyntaxError => e
      e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
      raise e
    end

### Private Instance Methods

arglist(subexpr, description) click to toggle source

    # File lib/sass/script/parser.rb, line 565
    def arglist(subexpr, description)
      args = []
      keywords = Sass::Util::NormalizedMap.new
      splat = nil
      while (e = send(subexpr))
        if @lexer.peek && @lexer.peek.type == :colon
          name = e
          @lexer.expected!("comma") unless name.is_a?(Tree::Variable)
          assert_tok(:colon)
          value = assert_expr(subexpr, description)

          if keywords[name.name]
            raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once")
          end

          keywords[name.name] = value
        else
          if try_tok(:splat)
            return args, keywords, splat, e if splat
            splat, e = e, nil
          elsif splat
            raise SyntaxError.new("Only keyword arguments may follow variable arguments (...).")
          elsif !keywords.empty?
            raise SyntaxError.new("Positional arguments must come before keyword arguments.")
          end
          args << e if e
        end

        return args, keywords, splat unless try_tok(:comma)
      end
      return args, keywords
    end

assert_done() click to toggle source

    # File lib/sass/script/parser.rb, line 755
    def assert_done
      if @allow_extra_text
        # If extra text is allowed, just rewind the lexer so that the
        # StringScanner is pointing to the end of the parsed text.
        @lexer.unpeek!
      else
        return if @lexer.done?
        @lexer.expected!(EXPR_NAMES[:default])
      end
    end

assert_expr(name, expected = nil) click to toggle source

    # File lib/sass/script/parser.rb, line 721
    def assert_expr(name, expected = nil)
      e = send(name)
      return e if e
      @lexer.expected!(expected || EXPR_NAMES[name] || EXPR_NAMES[:default])
    end

assert_tok(name) click to toggle source

    # File lib/sass/script/parser.rb, line 727
    def assert_tok(name)
      # Avoids an array allocation caused by argument globbing in assert_toks.
      t = try_tok(name)
      return t if t
      @lexer.expected!(Lexer::TOKEN_NAMES[name] || name.to_s)
    end

assert_toks(*names) click to toggle source

    # File lib/sass/script/parser.rb, line 734
    def assert_toks(*names)
      t = try_toks(*names)
      return t if t
      @lexer.expected!(names.map {|tok| Lexer::TOKEN_NAMES[tok] || tok}.join(" or "))
    end

check_for_interpolation(node) click to toggle source

Checks a script node for any immediately-deprecated interpolations, and emits warnings for them.

@param node [Sass::Script::Tree::Node]

    # File lib/sass/script/parser.rb, line 802
    def check_for_interpolation(node)
      nodes = [node]
      until nodes.empty?
        node = nodes.pop
        unless node.is_a?(Sass::Script::Tree::Interpolation) &&
               node.deprecation == :immediate
          nodes.concat node.children
          next
        end

        interpolation_deprecation(node)
      end
    end

defn_arglist!(must_have_parens) click to toggle source

    # File lib/sass/script/parser.rb, line 526
    def defn_arglist!(must_have_parens)
      if must_have_parens
        assert_tok(:lparen)
      else
        return [], nil unless try_tok(:lparen)
      end

      res = []
      splat = nil
      must_have_default = false
      loop do
        break if peek_tok(:rparen)
        c = assert_tok(:const)
        var = node(Script::Tree::Variable.new(c.value), c.source_range)
        if try_tok(:colon)
          val = assert_expr(:space)
          must_have_default = true
        elsif try_tok(:splat)
          splat = var
          break
        elsif must_have_default
          raise SyntaxError.new(
            "Required argument #{var.inspect} must come before any optional arguments.")
        end
        res << [var, val]
        break unless try_tok(:comma)
      end
      assert_tok(:rparen)
      return res, splat
    end

expr() click to toggle source

    # File lib/sass/script/parser.rb, line 337
    def expr
      start_pos = source_position
      e = interpolation
      return unless e
      list e, start_pos
    end

fn_arglist() click to toggle source

    # File lib/sass/script/parser.rb, line 557
    def fn_arglist
      arglist(:equals, "function argument")
    end

funcall() click to toggle source

    # File lib/sass/script/parser.rb, line 517
    def funcall
      tok = try_tok(:funcall)
      return raw unless tok
      args, keywords, splat, kwarg_splat = fn_arglist
      assert_tok(:rparen)
      node(Script::Tree::Funcall.new(tok.value, args, keywords, splat, kwarg_splat),
        tok.source_range.start_pos, source_position)
    end

ident() click to toggle source

    # File lib/sass/script/parser.rb, line 499
    def ident
      return funcall unless @lexer.peek && @lexer.peek.type == :ident
      return if @stop_at && @stop_at.include?(@lexer.peek.value)

      name = @lexer.next
      if (color = Sass::Script::Value::Color::COLOR_NAMES[name.value.downcase])
        literal_node(Sass::Script::Value::Color.new(color, name.value), name.source_range)
      elsif name.value == "true"
        literal_node(Sass::Script::Value::Bool.new(true), name.source_range)
      elsif name.value == "false"
        literal_node(Sass::Script::Value::Bool.new(false), name.source_range)
      elsif name.value == "null"
        literal_node(Sass::Script::Value::Null.new, name.source_range)
      else
        literal_node(Sass::Script::Value::String.new(name.value, :identifier), name.source_range)
      end
    end

interpolation(first: nil, inner: :space) click to toggle source

    # File lib/sass/script/parser.rb, line 414
    def interpolation(first: nil, inner: :space)
      e = first || send(inner)
      while (interp = try_tok(:begin_interpolation))
        wb = @lexer.whitespace?(interp)
        char_before = @lexer.char(interp.pos - 1)
        mid = assert_expr :expr
        assert_tok :end_interpolation
        wa = @lexer.whitespace?
        char_after = @lexer.char

        after = send(inner)
        before_deprecation = e.is_a?(Script::Tree::Interpolation) ? e.deprecation : :none
        after_deprecation = after.is_a?(Script::Tree::Interpolation) ? after.deprecation : :none

        deprecation =
          if before_deprecation == :immediate || after_deprecation == :immediate ||
             # Warn for #{foo}$var and #{foo}(1) but not #{$foo}1.
             (after && !wa && char_after =~ /[$(]/) ||
             # Warn for $var#{foo} and (a)#{foo} but not a#{foo}.
             (e && !wb && is_unsafe_before?(e, char_before))
            :immediate
          else
            :potential
          end

        e = node(
          Script::Tree::Interpolation.new(e, mid, after, wb, wa, :deprecation => deprecation),
          (e || interp).source_range.start_pos)
      end
      e
    end

interpolation_deprecation(interpolation) click to toggle source

Emits a deprecation warning for an interpolation node.

@param node [Sass::Script::Tree::Node]

    # File lib/sass/script/parser.rb, line 819
          def interpolation_deprecation(interpolation)
            return if @options[:_convert]
            location = "on line #{interpolation.line}"
            location << " of #{interpolation.filename}" if interpolation.filename
            Sass::Util.sass_warn <<WARNING
    DEPRECATION WARNING #{location}:
    \#{} interpolation near operators will be simplified in a future version of Sass.
    To preserve the current behavior, use quotes:

      #{interpolation.to_quoted_equivalent.to_sass}

    You can use the sass-convert command to automatically fix most cases.
    WARNING
          end

is_safe_value?(expr) click to toggle source

Returns whether `expr` is safe as the value immediately before an interpolation.

It's safe as long as the previous expression is an identifier or number, or a list whose last element is also safe.

    # File lib/sass/script/parser.rb, line 465
    def is_safe_value?(expr)
      return is_safe_value?(expr.elements.last) if expr.is_a?(Script::Tree::ListLiteral)
      return false unless expr.is_a?(Script::Tree::Literal)
      expr.value.is_a?(Script::Value::Number) ||
        (expr.value.is_a?(Script::Value::String) && expr.value.type == :identifier)
    end

is_unsafe_before?(expr, char_before) click to toggle source

Returns whether `expr` is unsafe to include before an interpolation.

@param expr [Node] The expression to check. @param char_before [String] The character immediately before the

    interpolation being checked (and presumably the last character of
    `expr`).

@return [Boolean]

    # File lib/sass/script/parser.rb, line 453
    def is_unsafe_before?(expr, char_before)
      return char_before == ')' if is_safe_value?(expr)

      # Otherwise, it's only safe if it was another interpolation.
      !expr.is_a?(Script::Tree::Interpolation)
    end

lexer_class() click to toggle source

@private

    # File lib/sass/script/parser.rb, line 312
    def lexer_class; Lexer; end

list(first, start_pos) click to toggle source

    # File lib/sass/script/parser.rb, line 344
    def list(first, start_pos)
      return first unless @lexer.peek && @lexer.peek.type == :comma

      list = node(Sass::Script::Tree::ListLiteral.new([first], separator: :comma), start_pos)
      while (tok = try_tok(:comma))
        element_before_interp = list.elements.length == 1 ? list.elements.first : list
        if (interp = try_op_before_interp(tok, element_before_interp))
          other_interp = try_ops_after_interp([:comma], :expr, interp)
          return interp unless other_interp
          return other_interp
        end
        return list unless (e = interpolation)
        list.elements << e
        list.source_range.end_pos = list.elements.last.source_range.end_pos
      end
      list
    end

literal() click to toggle source

    # File lib/sass/script/parser.rb, line 704
    def literal
      t = try_tok(:color)
      return literal_node(t.value, t.source_range) if t
    end

literal_node(value, source_range_or_start_pos, end_pos = source_position) click to toggle source

@overload node(value, source_range)

    @param value [Sass::Script::Value::Base]
    @param source_range [Sass::Source::Range]

@overload node(value, start_pos, end_pos = #source_position)

    @param value [Sass::Script::Value::Base]
    @param start_pos [Sass::Source::Position]
    @param end_pos [Sass::Source::Position]

    # File lib/sass/script/parser.rb, line 773
    def literal_node(value, source_range_or_start_pos, end_pos = source_position)
      node(Sass::Script::Tree::Literal.new(value), source_range_or_start_pos, end_pos)
    end

map() click to toggle source

    # File lib/sass/script/parser.rb, line 314
    def map
      start_pos = source_position
      e = interpolation
      return unless e
      return list e, start_pos unless @lexer.peek && @lexer.peek.type == :colon

      pair = map_pair(e)
      map = node(Sass::Script::Tree::MapLiteral.new([pair]), start_pos)
      while try_tok(:comma)
        pair = map_pair
        return map unless pair
        map.pairs << pair
        map.source_range.end_pos = map.pairs.last.last.source_range.end_pos
      end
      map
    end

map_pair(key = nil) click to toggle source

    # File lib/sass/script/parser.rb, line 331
    def map_pair(key = nil)
      return unless key ||= interpolation
      assert_tok :colon
      return key, assert_expr(:interpolation)
    end

mixin_arglist() click to toggle source

    # File lib/sass/script/parser.rb, line 561
    def mixin_arglist
      arglist(:interpolation, "mixin argument")
    end

node(node, source_range_or_start_pos, end_pos = source_position) click to toggle source

@overload node(node, source_range)

    @param node [Sass::Script::Tree::Node]
    @param source_range [Sass::Source::Range]

@overload node(node, start_pos, end_pos = #source_position)

    @param node [Sass::Script::Tree::Node]
    @param start_pos [Sass::Source::Position]
    @param end_pos [Sass::Source::Position]

    # File lib/sass/script/parser.rb, line 784
    def node(node, source_range_or_start_pos, end_pos = source_position)
      source_range =
        if source_range_or_start_pos.is_a?(Sass::Source::Range)
          source_range_or_start_pos
        else
          range(source_range_or_start_pos, end_pos)
        end

      node.line = source_range.start_pos.line
      node.source_range = source_range
      node.filename = @options[:filename]
      node
    end

number() click to toggle source

    # File lib/sass/script/parser.rb, line 689
    def number
      tok = try_tok(:number)
      return selector unless tok
      num = tok.value
      num.options = @options
      num.original = num.to_s
      literal_node(num, tok.source_range.start_pos)
    end

paren() click to toggle source

    # File lib/sass/script/parser.rb, line 661
    def paren
      return variable unless try_tok(:lparen)
      start_pos = source_position
      e = map
      e.force_division! if e
      end_pos = source_position
      assert_tok(:rparen)
      e || node(Sass::Script::Tree::ListLiteral.new([]), start_pos, end_pos)
    end

peek_tok(name) click to toggle source

    # File lib/sass/script/parser.rb, line 740
    def peek_tok(name)
      # Avoids an array allocation caused by argument globbing in the try_toks method.
      peeked = @lexer.peek
      peeked && name == peeked.type
    end

range(start_pos, end_pos = source_position) click to toggle source

    # File lib/sass/script/parser.rb, line 307
    def range(start_pos, end_pos = source_position)
      Sass::Source::Range.new(start_pos, end_pos, @options[:filename], @options[:importer])
    end

raw() click to toggle source

    # File lib/sass/script/parser.rb, line 598
    def raw
      tok = try_tok(:raw)
      return special_fun unless tok
      literal_node(Script::Value::String.new(tok.value), tok.source_range)
    end

selector() click to toggle source

    # File lib/sass/script/parser.rb, line 698
    def selector
      tok = try_tok(:selector)
      return literal unless tok
      node(tok.value, tok.source_range.start_pos)
    end

source_position() click to toggle source

    # File lib/sass/script/parser.rb, line 303
    def source_position
      Sass::Source::Position.new(line, offset)
    end

space() click to toggle source

    # File lib/sass/script/parser.rb, line 472
    def space
      start_pos = source_position
      e = or_expr
      return unless e
      arr = [e]
      while (e = or_expr)
        arr << e
      end
      if arr.size == 1
        arr.first
      else
        node(Sass::Script::Tree::ListLiteral.new(arr, separator: :space), start_pos)
      end
    end

special_fun() click to toggle source

    # File lib/sass/script/parser.rb, line 604
    def special_fun
      first = try_tok(:special_fun)
      return square_list unless first
      str = literal_node(first.value, first.source_range)
      return str unless try_tok(:string_interpolation)
      mid = assert_expr :expr
      assert_tok :end_interpolation
      last = assert_expr(:special_fun)
      node(
        Tree::Interpolation.new(str, mid, last, false, false),
        first.source_range.start_pos)
    end

square_list() click to toggle source

    # File lib/sass/script/parser.rb, line 617
    def square_list
      start_pos = source_position
      return paren unless try_tok(:lsquare)

      space_start_pos = source_position
      e = interpolation(inner: :or_expr)
      separator = nil
      if e
        elements = [e]
        while (e = interpolation(inner: :or_expr))
          elements << e
        end

        # If there's a comma after a space-separated list, it's actually a
        # space-separated list nested in a comma-separated list.
        if try_tok(:comma)
          e = if elements.length == 1
                elements.first
              else
                node(
                  Sass::Script::Tree::ListLiteral.new(elements, separator: :space),
                  space_start_pos)
              end
          elements = [e]

          while (e = space)
            elements << e
            break unless try_tok(:comma)
          end
          separator = :comma
        else
          separator = :space if elements.length > 1
        end
      else
        elements = []
      end

      assert_tok(:rsquare)
      end_pos = source_position

      node(Sass::Script::Tree::ListLiteral.new(elements, separator: separator, bracketed: true),
           start_pos, end_pos)
    end

string() click to toggle source

    # File lib/sass/script/parser.rb, line 678
    def string
      first = try_tok(:string)
      return number unless first
      str = literal_node(first.value, first.source_range)
      return str unless try_tok(:string_interpolation)
      mid = assert_expr :expr
      assert_tok :end_interpolation
      last = assert_expr(:string)
      node(Tree::StringInterpolation.new(str, mid, last), first.source_range.start_pos)
    end

try_op_before_interp(op, prev = nil, after_interp = false) click to toggle source

    # File lib/sass/script/parser.rb, line 364
    def try_op_before_interp(op, prev = nil, after_interp = false)
      return unless @lexer.peek && @lexer.peek.type == :begin_interpolation
      unary = !prev && !after_interp
      wb = @lexer.whitespace?(op)
      str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]),
                         op.source_range)

      deprecation =
        case op.type
        when :comma; :potential
        when :div, :single_eq; :none
        when :plus; unary ? :none : :immediate
        when :minus; @lexer.whitespace?(@lexer.peek) ? :immediate : :none
        else; :immediate
        end

      interp = node(
        Script::Tree::Interpolation.new(
          prev, str, nil, wb, false, :originally_text => true, :deprecation => deprecation),
        (prev || str).source_range.start_pos)
      interpolation(first: interp)
    end

try_ops_after_interp(ops, name, prev = nil) click to toggle source

    # File lib/sass/script/parser.rb, line 387
    def try_ops_after_interp(ops, name, prev = nil)
      return unless @lexer.after_interpolation?
      op = try_toks(*ops)
      return unless op
      interp = try_op_before_interp(op, prev, :after_interp)
      return interp if interp

      wa = @lexer.whitespace?
      str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]),
                         op.source_range)
      str.line = @lexer.line

      deprecation =
        case op.type
        when :comma; :potential
        when :div, :single_eq; :none
        when :minus; @lexer.whitespace?(op) ? :immediate : :none
        else; :immediate
        end
      interp = node(
        Script::Tree::Interpolation.new(
          prev, str, assert_expr(name), false, wa,
          :originally_text => true, :deprecation => deprecation),
        (prev || str).source_range.start_pos)
      interp
    end

try_tok(name) click to toggle source

    # File lib/sass/script/parser.rb, line 746
    def try_tok(name)
      peek_tok(name) && @lexer.next
    end

try_toks(*names) click to toggle source

    # File lib/sass/script/parser.rb, line 750
    def try_toks(*names)
      peeked = @lexer.peek
      peeked && names.include?(peeked.type) && @lexer.next
    end

variable() click to toggle source

    # File lib/sass/script/parser.rb, line 671
    def variable
      start_pos = source_position
      c = try_tok(:const)
      return string unless c
      node(Tree::Variable.new(*c.value), start_pos)
    end