# class Sass::SCSS::Parser

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

### Constants

DIRECTIVES
EXPR_NAMES
NEWLINE

  Avoid allocating lots of new strings for `#tok`. This is important because `#tok` is called all the time.

PREFIXED_DIRECTIVES
TOK_NAMES

### Attributes

sass_script_parser[RW]

@private

offset[RW]

Expose for the SASS parser.

### Public Class Methods

new(str, filename, importer, line = 1, offset = 1) click to toggle source

@param str [String, StringScanner] The source document to parse.

    Note that `Parser` *won't* raise a nice error message if this isn't properly parsed;
    for that, you should use the higher-level {Sass::Engine} or {Sass::CSS}.

@param filename [String] The name of the file being parsed. Used for

    warnings and source maps.

@param importer [Sass::Importers::Base] The importer used to import the

    file being parsed. Used for source maps.

@param line [Integer] The 1-based line on which the source string appeared,

    if it's part of another document.

@param offset [Integer] The 1-based character (not byte) offset in the line on

    which the source string starts. Used for error reporting and sourcemap
    building.

    # File lib/sass/scss/parser.rb, line 23
    def initialize(str, filename, importer, line = 1, offset = 1)
      @template = str
      @filename = filename
      @importer = importer
      @line = line
      @offset = offset
      @strs = []
      @expected = nil
      @throw_error = false
    end

### Private Class Methods

expected(scanner, expected, line) click to toggle source

@private

    # File lib/sass/scss/parser.rb, line 1258
    def self.expected(scanner, expected, line)
      pos = scanner.pos

      after = scanner.string[0...pos]
      # Get rid of whitespace between pos and the last token,
      # but only if there's a newline in there
      after.gsub!(/\s*\n\s*$/, '')
      # Also get rid of stuff before the last newline
      after.gsub!(/.*\n/, '')
      after = "..." + after[-15..-1] if after.size > 18

      was = scanner.rest.dup
      # Get rid of whitespace between pos and the next token,
      # but only if there's a newline in there
      was.gsub!(/^\s*\n\s*/, '')
      # Also get rid of stuff after the next newline
      was.gsub!(/\n.*/, '')
      was = was[0...15] + "..." if was.size > 18

      raise Sass::SyntaxError.new(
        "Invalid CSS after \"#{after}\": expected #{expected}, was \"#{was}\"",
        :line => line)
    end

### Public Instance Methods

parse() click to toggle source

Parses an SCSS document.

@return [Sass::Tree::RootNode] The root node of the document tree @raise [Sass::SyntaxError] if there's a syntax error in the document

    # File lib/sass/scss/parser.rb, line 38
    def parse
      init_scanner!
      root = stylesheet
      expected("selector or at-rule") unless root && @scanner.eos?
      root
    end

parse_at_root_query() click to toggle source

Parses an at-root query.

@return [Array<String, Sass::Script;:Tree::Node>] The interpolated query. @raise [Sass::SyntaxError] if there's a syntax error in the query,

    or if it doesn't take up the entire input string.

    # File lib/sass/scss/parser.rb, line 82
    def parse_at_root_query
      init_scanner!
      query = at_root_query
      expected("@at-root query list") unless query && @scanner.eos?
      query
    end

parse_declaration_value() click to toggle source

Parses a custom property value.

@return [Array<String, Sass::Script;:Tree::Node>] The interpolated value. @raise [Sass::SyntaxError] if there's a syntax error in the value,

    or if it doesn't take up the entire input string.

    # File lib/sass/scss/parser.rb, line 106
    def parse_declaration_value
      init_scanner!
      value = declaration_value
      expected('"}"') unless value && @scanner.eos?
      value
    end

parse_interp_ident() click to toggle source

Parses an identifier with interpolation. Note that this won't assert that the identifier takes up the entire input string; it's meant to be used with `StringScanner`s as part of other parsers.

@return [Array<String, Sass::Script::Tree::Node>, nil]

    The interpolated identifier, or nil if none could be parsed

    # File lib/sass/scss/parser.rb, line 51
    def parse_interp_ident
      init_scanner!
      interp_ident
    end

parse_media_query_list() click to toggle source

Parses a media query list.

@return [Sass::Media::QueryList] The parsed query list @raise [Sass::SyntaxError] if there's a syntax error in the query list,

    or if it doesn't take up the entire input string.

    # File lib/sass/scss/parser.rb, line 70
    def parse_media_query_list
      init_scanner!
      ql = media_query_list
      expected("media query list") unless ql && @scanner.eos?
      ql
    end

parse_supports_clause() click to toggle source

Parses a supports clause for an @import directive

    # File lib/sass/scss/parser.rb, line 57
    def parse_supports_clause
      init_scanner!
      ss
      clause = supports_clause
      ss
      clause
    end

parse_supports_condition() click to toggle source

Parses a supports query condition.

@return [Sass::Supports::Condition] The parsed condition @raise [Sass::SyntaxError] if there's a syntax error in the condition,

    or if it doesn't take up the entire input string.

    # File lib/sass/scss/parser.rb, line 94
    def parse_supports_condition
      init_scanner!
      condition = supports_condition
      expected("supports condition") unless condition && @scanner.eos?
      condition
    end

### Private Instance Methods

_interp_string(type) click to toggle source

    # File lib/sass/scss/parser.rb, line 1062
    def _interp_string(type)
      start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][false])
      return unless start
      res = [start]

      mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][true]
      # @scanner[2].empty? means we've started an interpolated section
      while @scanner[2] == '#{'
        @scanner.pos -= 2 # Don't consume the #{
        res.last.slice!(-2..-1)
        res << expr!(:interpolation) << tok(mid_re)
      end
      res
    end

_moz_document_directive(start_pos) click to toggle source

The document directive is specified in www.w3.org/TR/css3-conditional/, but Gecko allows the `url-prefix` and `domain` functions to omit quotation marks, contrary to the standard.

We could parse all document directives according to Mozilla's syntax, but if someone's using e.g. @-webkit-document we don't want them to think WebKit works sans quotes.

    # File lib/sass/scss/parser.rb, line 510
    def _moz_document_directive(start_pos)
      res = ["@-moz-document "]
      loop do
        res << str {ss} << expr!(:moz_document_function)
        if (c = tok(/,/))
          res << c
        else
          break
        end
      end
      directive_body(res.flatten, start_pos)
    end

almost_any_value() click to toggle source

This production consumes values that could be a selector, an expression, or a combination of both. It respects strings and comments and supports interpolation. It will consume up to “{”, “}”, “;”, or “!”.

Values consumed by this production will usually be parsed more thoroughly once interpolation has been resolved.

    # File lib/sass/scss/parser.rb, line 849
    def almost_any_value
      return unless (tok = almost_any_value_token)
      sel = [tok]
      while (tok = almost_any_value_token)
        sel << tok
      end
      merge(sel)
    end

almost_any_value_token() click to toggle source

    # File lib/sass/scss/parser.rb, line 858
    def almost_any_value_token
      tok(%r{
        (
          \\.
        |
          (?!url\()
          [^"'/\#!;\{\}] # "
        |
          # interp_uri will handle most url() calls, but not ones that take strings
          url\(#{W}(?=")
        |
          /(?![/*])
        |
          \#(?!\{)
        |
          !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed.
        )+
      }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri ||
              interpolation(:warn_for_color)
    end

at_root_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 531
    def at_root_directive(start_pos)
      if tok?(/\(/) && (expr = at_root_query)
        return block(node(Sass::Tree::AtRootNode.new(expr), start_pos), :directive)
      end

      at_root_node = node(Sass::Tree::AtRootNode.new, start_pos)
      rule_node = ruleset
      return block(at_root_node, :stylesheet) unless rule_node
      at_root_node << rule_node
      at_root_node
    end

at_root_directive_list() click to toggle source

    # File lib/sass/scss/parser.rb, line 543
    def at_root_directive_list
      return unless (first = tok(IDENT))
      arr = [first]
      ss
      while (e = tok(IDENT))
        arr << e
        ss
      end
      arr
    end

at_root_query()
Alias for: query_expr
block(node, context) click to toggle source

    # File lib/sass/scss/parser.rb, line 674
    def block(node, context)
      node.has_children = true
      tok!(/\{/)
      block_contents(node, context)
      tok!(/\}/)
      node
    end

block_child(context) click to toggle source

    # File lib/sass/scss/parser.rb, line 693
    def block_child(context)
      return variable || directive if context == :function
      return variable || directive || ruleset if context == :stylesheet
      variable || directive || declaration_or_ruleset
    end

block_contents(node, context) { |: ss_comments(node)| ... } click to toggle source

A block may contain declarations and/or rulesets

    # File lib/sass/scss/parser.rb, line 683
    def block_contents(node, context)
      block_given? ? yield : ss_comments(node)
      node << (child = block_child(context))
      while tok(/;/) || has_children?(child)
        block_given? ? yield : ss_comments(node)
        node << (child = block_child(context))
      end
      node
    end

catch_error() { || ... } click to toggle source

    # File lib/sass/scss/parser.rb, line 1222
    def catch_error(&block)
      old_throw_error, @throw_error = @throw_error, true
      pos = @scanner.pos
      line = @line
      offset = @offset
      expected = @expected

      logger = Sass::Logger::Delayed.install!
      if catch(:_sass_parser_error) {yield; false}
        @scanner.pos = pos
        @line = line
        @offset = offset
        @expected = expected
        {:pos => pos, :line => line, :expected => @expected, :block => block}
      else
        logger.flush
        nil
      end
    ensure
      logger.uninstall! if logger
      @throw_error = old_throw_error
    end

charset_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 496
    def charset_directive(start_pos)
      name = expr!(:string)
      ss
      node(Sass::Tree::CharsetNode.new(name), start_pos)
    end

content_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 262
    def content_directive(start_pos)
      ss
      node(Sass::Tree::ContentNode.new, start_pos)
    end

css_variable_declaration(name, name_start_pos, name_end_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 831
    def css_variable_declaration(name, name_start_pos, name_end_pos)
      value_start_pos = source_position
      value = declaration_value
      value_end_pos = source_position

      node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
                  name_start_pos, value_end_pos)
      node.name_source_range = range(name_start_pos, name_end_pos)
      node.value_source_range = range(value_start_pos, value_end_pos)
      node
    end

debug_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 278
    def debug_directive(start_pos)
      node(Sass::Tree::DebugNode.new(sass_script(:parse)), start_pos)
    end

declaration() click to toggle source

    # File lib/sass/scss/parser.rb, line 933
    def declaration
      # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
      # val" hacks.
      name_start_pos = source_position
      if (s = tok(/[:\*\.]|\#(?!\{)/))
        name = [s, str {ss}, *expr!(:interp_ident)]
      else
        return unless (name = interp_ident)
        name = Array(name)
      end

      if (comment = tok(COMMENT))
        name << comment
      end
      name_end_pos = source_position
      ss

      tok!(/:/)
      ss
      value_start_pos = source_position
      value = value!
      value_end_pos = source_position
      ss
      require_block = tok?(/\{/)

      node = node(Sass::Tree::PropNode.new(name.flatten.compact, [value], :new),
                  name_start_pos, value_end_pos)
      node.name_source_range = range(name_start_pos, name_end_pos)
      node.value_source_range = range(value_start_pos, value_end_pos)

      return node unless require_block
      nested_properties! node
    end

declaration_or_ruleset() click to toggle source

When parsing the contents of a ruleset, it can be difficult to tell declarations apart from nested rulesets. Since we don't thoroughly parse selectors until after resolving interpolation, we can share a bunch of the parsing of the two, but we need to disambiguate them first. We use the following criteria:

- If the entity doesn't start with an identifier followed by a colon, it's a selector. There are some additional mostly-unimportant cases here to support various declaration hacks.

- If the colon is followed by another colon, it's a selector.

- Otherwise, if the colon is followed by anything other than interpolation or a character that's valid as the beginning of an identifier, it's a declaration.

- If the colon is followed by interpolation or a valid identifier, try parsing it as a declaration value. If this fails, backtrack and parse it as a selector.

- If the declaration value value valid but is followed by “{”, backtrack and parse it as a selector anyway. This ensures that “.foo:bar {” is always parsed as a selector and never as a property with nested properties beneath it.

    # File lib/sass/scss/parser.rb, line 729
    def declaration_or_ruleset
      start_pos = source_position
      declaration = try_declaration

      if declaration.nil?
        return unless (selector = almost_any_value)
      elsif declaration.is_a?(Array)
        selector = declaration
      else
        # Declaration should be a PropNode.
        return declaration
      end

      if (additional_selector = almost_any_value)
        selector << additional_selector
      end

      block(
        node(
          Sass::Tree::RuleNode.new(merge(selector), range(start_pos)), start_pos), :ruleset)
    end

declaration_value(top_level: true) click to toggle source

    # File lib/sass/scss/parser.rb, line 879
    def declaration_value(top_level: true)
      return unless (tok = declaration_value_token(top_level))
      value = [tok]
      while (tok = declaration_value_token(top_level))
        value << tok
      end
      merge(value)
    end

declaration_value_token(top_level) click to toggle source

    # File lib/sass/scss/parser.rb, line 888
    def declaration_value_token(top_level)
      # This comes, more or less, from the [token consumption algorithm][].
      # However, since we don't have to worry about the token semantics, we
      # just consume everything until we come across a token with special
      # semantics.
      #
      # [token consumption algorithm]: https://drafts.csswg.org/css-syntax-3/#consume-token.
      result = tok(%r{
        (
          (?!
            url\(
          )
          [^()\[\]{}"'#/ \t\r\n\f#{top_level ? ";" : ""}]
        |
          \#(?!\{)
        |
          /(?!\*)
        )+
      }xi) || interp_string || interp_uri || interpolation || tok(COMMENT)
      return result if result

      # Fold together multiple characters of whitespace that don't include
      # newlines. The value only cares about the tokenization, so this is safe
      # as long as we don't delete whitespace entirely. It's important that we
      # fold here rather than post-processing, since we aren't allowed to fold
      # whitespace within strings and we lose that context later on.
      if (ws = tok(S))
        return ws.include?("\n") ? ws.gsub(/\A[^\n]*/, '') : ' '
      end

      if tok(/\(/)
        value = declaration_value(top_level: false)
        tok!(/\)/)
        ['(', *value, ')']
      elsif tok(/\[/)
        value = declaration_value(top_level: false)
        tok!(/\]/)
        ['[', *value, ']']
      elsif tok(/\{/)
        value = declaration_value(top_level: false)
        tok!(/\}/)
        ['{', *value, '}']
      end
    end

deprefix(str) click to toggle source

Remove a vendor prefix from `str`.

    # File lib/sass/scss/parser.rb, line 1307
    def deprefix(str)
      str.gsub(/^-[a-zA-Z0-9]+-/, '')
    end

directive() click to toggle source

    # File lib/sass/scss/parser.rb, line 202
    def directive
      start_pos = source_position
      return unless tok(/@/)
      name = tok!(IDENT)
      ss

      if (dir = special_directive(name, start_pos))
        return dir
      elsif (dir = prefixed_directive(name, start_pos))
        return dir
      end

      val = almost_any_value
      val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"]
      directive_body(val, start_pos)
    end

directive_body(value, start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 219
    def directive_body(value, start_pos)
      node = Sass::Tree::DirectiveNode.new(value)

      if tok(/\{/)
        node.has_children = true
        block_contents(node, :directive)
        tok!(/\}/)
      end

      node(node, start_pos)
    end

each_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 303
    def each_directive(start_pos)
      tok!(/\$/)
      vars = [tok!(IDENT)]
      ss
      while tok(/,/)
        ss
        tok!(/\$/)
        vars << tok!(IDENT)
        ss
      end

      tok!(/in/)
      list = sass_script(:parse)
      ss

      block(node(Sass::Tree::EachNode.new(vars, list), start_pos), :directive)
    end

else_block(node) click to toggle source

    # File lib/sass/scss/parser.rb, line 344
    def else_block(node)
      start_pos = source_position
      return unless tok(/@else/)
      ss
      else_node = block(
        node(Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))), start_pos),
        :directive)
      node.add_else(else_node)
      pos = @scanner.pos
      line = @line
      ss

      else_block(node) ||
        begin
          # Backtrack in case there are any comments we want to parse
          @scanner.pos = pos
          @line = line
          node
        end
    end

else_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 365
    def else_directive(start_pos)
      err("Invalid CSS: @else must come after @if")
    end

err(msg) click to toggle source

    # File lib/sass/scss/parser.rb, line 1210
    def err(msg)
      throw(:_sass_parser_error, true) if @throw_error
      raise Sass::SyntaxError.new(msg, :line => @line)
    end

error_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 554
    def error_directive(start_pos)
      node(Sass::Tree::ErrorNode.new(sass_script(:parse)), start_pos)
    end

expected(name) click to toggle source

    # File lib/sass/scss/parser.rb, line 1205
    def expected(name)
      throw(:_sass_parser_error, true) if @throw_error
      self.class.expected(@scanner, @expected || name, @line)
    end

expr(allow_var = true) click to toggle source

    # File lib/sass/scss/parser.rb, line 995
    def expr(allow_var = true)
      t = term(allow_var)
      return unless t
      res = [t, str {ss}]

      while (o = operator) && (t = term(allow_var))
        res << o << t << str {ss}
      end

      res.flatten
    end

expr!(name) click to toggle source

    # File lib/sass/scss/parser.rb, line 1184
    def expr!(name)
      e = send(name)
      return e if e
      expected(EXPR_NAMES[name] || name.to_s)
    end

extend_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 369
    def extend_directive(start_pos)
      selector_start_pos = source_position
      @expected = "selector"
      selector = Sass::Util.strip_string_array(expr!(:almost_any_value))
      optional = tok(OPTIONAL)
      ss
      node(Sass::Tree::ExtendNode.new(selector, !!optional, range(selector_start_pos)), start_pos)
    end

for_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 286
    def for_directive(start_pos)
      tok!(/\$/)
      var = tok! IDENT
      ss

      tok!(/from/)
      from = sass_script(:parse_until, Set["to", "through"])
      ss

      @expected = '"to" or "through"'
      exclusive = (tok(/to/) || tok!(/through/)) == 'to'
      to = sass_script(:parse)
      ss

      block(node(Sass::Tree::ForNode.new(var, from, to, exclusive), start_pos), :directive)
    end

function(allow_var) click to toggle source

    # File lib/sass/scss/parser.rb, line 1025
    def function(allow_var)
      name = tok(FUNCTION)
      return unless name
      if name == "expression(" || name == "calc("
        str, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
        [name, str]
      else
        [name, str {ss}, expr(allow_var), tok!(/\)/)]
      end
    end

function_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 267
    def function_directive(start_pos)
      name = tok! IDENT
      args, splat = sass_script(:parse_function_definition_arglist)
      ss
      block(node(Sass::Tree::FunctionNode.new(name, args, splat), start_pos), :function)
    end

has_children?(child_or_array) click to toggle source

    # File lib/sass/scss/parser.rb, line 699
    def has_children?(child_or_array)
      return false unless child_or_array
      return child_or_array.last.has_children if child_or_array.is_a?(Array)
      child_or_array.has_children
    end

if_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 327
    def if_directive(start_pos)
      expr = sass_script(:parse)
      ss
      node = block(node(Sass::Tree::IfNode.new(expr), start_pos), :directive)
      pos = @scanner.pos
      line = @line
      ss

      else_block(node) ||
        begin
          # Backtrack in case there are any comments we want to parse
          @scanner.pos = pos
          @line = line
          node
        end
    end

import_arg() click to toggle source

    # File lib/sass/scss/parser.rb, line 391
    def import_arg
      start_pos = source_position
      return unless (str = string) || (uri = tok?(/url\(/i))
      if uri
        str = sass_script(:parse_string)
        ss
        supports = supports_clause
        ss
        media = media_query_list
        ss
        return node(Tree::CssImportNode.new(str, media.to_a, supports), start_pos)
      end
      ss

      supports = supports_clause
      ss
      media = media_query_list
      if str =~ %r{^(https?:)?//} || media || supports || use_css_import?
        return node(
          Sass::Tree::CssImportNode.new(
            Sass::Script::Value::String.quote(str), media.to_a, supports), start_pos)
      end

      node(Sass::Tree::ImportNode.new(str.strip), start_pos)
    end

import_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 378
    def import_directive(start_pos)
      values = []

      loop do
        values << expr!(:import_arg)
        break if use_css_import?
        break unless tok(/,/)
        ss
      end

      values
    end

import_supports_condition() click to toggle source

    # File lib/sass/scss/parser.rb, line 584
    def import_supports_condition
      supports_condition || supports_declaration
    end

include_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 248
    def include_directive(start_pos)
      name = tok! IDENT
      args, keywords, splat, kwarg_splat = sass_script(:parse_mixin_include_arglist)
      ss
      include_node = node(
        Sass::Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat), start_pos)
      if tok?(/\{/)
        include_node.has_children = true
        block(include_node, :directive)
      else
        include_node
      end
    end

init_scanner!() click to toggle source

    # File lib/sass/scss/parser.rb, line 125
    def init_scanner!
      @scanner =
        if @template.is_a?(StringScanner)
          @template
        else
          Sass::Util::MultibyteStringScanner.new(@template.tr("\r", ""))
        end
    end

interp_ident(start = IDENT) click to toggle source

    # File lib/sass/scss/parser.rb, line 1077
    def interp_ident(start = IDENT)
      val = tok(start) || interpolation(:warn_for_color) || tok(IDENT_HYPHEN_INTERP)
      return unless val
      res = [val]
      while (val = tok(NAME) || interpolation(:warn_for_color))
        res << val
      end
      res
    end

interp_ident_or_var() click to toggle source

    # File lib/sass/scss/parser.rb, line 1087
    def interp_ident_or_var
      id = interp_ident
      return id if id
      var = var_expr
      return [var] if var
    end

interp_string() click to toggle source

    # File lib/sass/scss/parser.rb, line 1054
    def interp_string
      _interp_string(:double) || _interp_string(:single)
    end

interp_uri() click to toggle source

    # File lib/sass/scss/parser.rb, line 1058
    def interp_uri
      _interp_string(:uri)
    end

interpolation(warn_for_color = false) click to toggle source

    # File lib/sass/scss/parser.rb, line 1044
    def interpolation(warn_for_color = false)
      return unless tok(INTERP_START)
      sass_script(:parse_interpolated, warn_for_color)
    end

media_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 419
    def media_directive(start_pos)
      block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a), start_pos), :directive)
    end

media_expr()

Aliases allow us to use different descriptions if the same expression fails in different contexts.

Alias for: query_expr
media_query() click to toggle source

    # File lib/sass/scss/parser.rb, line 438
    def media_query
      if (ident1 = interp_ident)
        ss
        ident2 = interp_ident
        ss
        if ident2 && ident2.length == 1 && ident2[0].is_a?(String) && ident2[0].downcase == 'and'
          query = Sass::Media::Query.new([], ident1, [])
        else
          if ident2
            query = Sass::Media::Query.new(ident1, ident2, [])
          else
            query = Sass::Media::Query.new([], ident1, [])
          end
          return query unless tok(/and/i)
          ss
        end
      end

      if query
        expr = expr!(:media_expr)
      else
        expr = media_expr
        return unless expr
      end
      query ||= Sass::Media::Query.new([], [], [])
      query.expressions << expr

      ss
      while tok(/and/i)
        ss; query.expressions << expr!(:media_expr)
      end

      query
    end

media_query_list() click to toggle source

www.w3.org/TR/css3-mediaqueries/#syntax

    # File lib/sass/scss/parser.rb, line 424
    def media_query_list
      query = media_query
      return unless query
      queries = [query]

      ss
      while tok(/,/)
        ss; queries << expr!(:media_query)
      end
      ss

      Sass::Media::QueryList.new(queries)
    end

merge(arr) click to toggle source

    # File lib/sass/scss/parser.rb, line 1147
    def merge(arr)
      arr && Sass::Util.merge_adjacent_strings([arr].flatten)
    end

mixin_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 241
    def mixin_directive(start_pos)
      name = tok! IDENT
      args, splat = sass_script(:parse_mixin_definition_arglist)
      ss
      block(node(Sass::Tree::MixinDefNode.new(name, args, splat), start_pos), :directive)
    end

moz_document_function() click to toggle source

    # File lib/sass/scss/parser.rb, line 523
    def moz_document_function
      val = interp_uri || _interp_string(:url_prefix) ||
        _interp_string(:domain) || function(false) || interpolation
      return unless val
      ss
      val
    end

nested_properties!(node) click to toggle source

    # File lib/sass/scss/parser.rb, line 990
    def nested_properties!(node)
      @expected = 'expression (e.g. 1px, bold) or "{"'
      block(node, :property)
    end

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

    # File lib/sass/scss/parser.rb, line 1117
    def node(node, start_pos, end_pos = source_position)
      node.line = start_pos.line
      node.source_range = range(start_pos, end_pos)
      node
    end

operator() click to toggle source

    # File lib/sass/scss/parser.rb, line 658
    def operator
      # Many of these operators (all except / and ,)
      # are disallowed by the CSS spec,
      # but they're included here for compatibility
      # with some proprietary MS properties
      str {ss if tok(%r{[/,:.=]})}
    end

prefixed_directive(name, start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 236
    def prefixed_directive(name, start_pos)
      sym = deprefix(name).tr('-', '_').to_sym
      PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name, start_pos)
    end

process_comment(text, node) click to toggle source

    # File lib/sass/scss/parser.rb, line 168
    def process_comment(text, node)
      silent = text =~ %r{\A//}
      loud = !silent && text =~ %r{\A/[/*]!}
      line = @line - text.count("\n")
      comment_start = @scanner.pos - text.length
      index_before_line = @scanner.string.rindex("\n", comment_start) || -1
      offset = comment_start - index_before_line

      if silent
        value = [text.sub(%r{\A\s*//}, '/*').gsub(%r{^\s*//}, ' *') + ' */']
      else
        value = Sass::Engine.parse_interp(text, line, offset, :filename => @filename)
        line_before_comment = @scanner.string[index_before_line + 1...comment_start]
        value.unshift(line_before_comment.gsub(/[^\s]/, ' '))
      end

      type = if silent
               :silent
             elsif loud
               :loud
             else
               :normal
             end
      start_pos = Sass::Source::Position.new(line, offset)
      comment = node(Sass::Tree::CommentNode.new(value, type), start_pos)
      node << comment
    end

query_expr() click to toggle source

    # File lib/sass/scss/parser.rb, line 473
    def query_expr
      interp = interpolation
      return interp if interp
      return unless tok(/\(/)
      res = ['(']
      ss
      res << sass_script(:parse)

      if tok(/:/)
        res << ': '
        ss
        res << sass_script(:parse)
      end
      res << tok!(/\)/)
      ss
      res
    end

Also aliased as: media_expr, at_root_query
range(start_pos, end_pos = source_position) click to toggle source

    # File lib/sass/scss/parser.rb, line 121
    def range(start_pos, end_pos = source_position)
      Sass::Source::Range.new(start_pos, end_pos, @filename, @importer)
    end

rethrow(err) click to toggle source

    # File lib/sass/scss/parser.rb, line 1245
    def rethrow(err)
      if @throw_error
        throw :_sass_parser_error, err
      else
        @scanner = Sass::Util::MultibyteStringScanner.new(@scanner.string)
        @scanner.pos = err[:pos]
        @line = err[:line]
        @expected = err[:expected]
        err[:block].call
      end
    end

return_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 274
    def return_directive(start_pos)
      node(Sass::Tree::ReturnNode.new(sass_script(:parse)), start_pos)
    end

ruleset() click to toggle source

    # File lib/sass/scss/parser.rb, line 666
    def ruleset
      start_pos = source_position
      return unless (rules = almost_any_value)
      block(
        node(
          Sass::Tree::RuleNode.new(rules, range(start_pos)), start_pos), :ruleset)
    end

s(node) click to toggle source

    # File lib/sass/scss/parser.rb, line 139
    def s(node)
      while tok(S) || tok(CDC) || tok(CDO) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
        next unless c
        process_comment c, node
        c = nil
      end
      true
    end

sass_script(*args) click to toggle source

    # File lib/sass/scss/parser.rb, line 1130
    def sass_script(*args)
      parser = self.class.sass_script_parser.new(@scanner, @line, @offset,
        :filename => @filename, :importer => @importer, :allow_extra_text => true)
      result = parser.send(*args)
      unless @strs.empty?
        # Convert to CSS manually so that comments are ignored.
        src = result.to_sass
        @strs.each {|s| s << src}
      end
      @line = parser.line
      @offset = parser.offset
      result
    rescue Sass::SyntaxError => e
      throw(:_sass_parser_error, true) if @throw_error
      raise e
    end

source_position() click to toggle source

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

special_directive(name, start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 231
    def special_directive(name, start_pos)
      sym = name.tr('-', '_').to_sym
      DIRECTIVES.include?(sym) && send("#{sym}_directive", start_pos)
    end

ss() click to toggle source

    # File lib/sass/scss/parser.rb, line 148
    def ss
      nil while tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
      true
    end

ss_comments(node) click to toggle source

    # File lib/sass/scss/parser.rb, line 153
    def ss_comments(node)
      while tok(S) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
        next unless c
        process_comment c, node
        c = nil
      end

      true
    end

str() { || ... } click to toggle source

    # File lib/sass/scss/parser.rb, line 1094
    def str
      @strs.push String.new("")
      yield
      @strs.last
    ensure
      @strs.pop
    end

str?() { || ... } click to toggle source

    # File lib/sass/scss/parser.rb, line 1102
    def str?
      pos = @scanner.pos
      line = @line
      offset = @offset
      @strs.push ""
      throw_error {yield} && @strs.last
    rescue Sass::SyntaxError
      @scanner.pos = pos
      @line = line
      @offset = offset
      nil
    ensure
      @strs.pop
    end

string() click to toggle source

    # File lib/sass/scss/parser.rb, line 1049
    def string
      return unless tok(STRING)
      Sass::Script::Value::String.value(@scanner[1] || @scanner[2])
    end

stylesheet() click to toggle source

    # File lib/sass/scss/parser.rb, line 134
    def stylesheet
      node = node(Sass::Tree::RootNode.new(@scanner.string), source_position)
      block_contents(node, :stylesheet) {s(node)}
    end

supports_clause() click to toggle source

    # File lib/sass/scss/parser.rb, line 571
    def supports_clause
      return unless tok(/supports\(/i)
      ss
      supports = import_supports_condition
      ss
      tok!(/\)/)
      supports
    end

supports_condition() click to toggle source

    # File lib/sass/scss/parser.rb, line 580
    def supports_condition
      supports_negation || supports_operator || supports_interpolation
    end

supports_condition_in_parens() click to toggle source

    # File lib/sass/scss/parser.rb, line 614
    def supports_condition_in_parens
      interp = supports_interpolation
      return interp if interp
      return unless tok(/\(/); ss
      if (cond = supports_condition)
        tok!(/\)/); ss
        cond
      else
        decl = supports_declaration
        tok!(/\)/); ss
        decl
      end
    end

supports_declaration() click to toggle source

    # File lib/sass/scss/parser.rb, line 607
    def supports_declaration
        name = sass_script(:parse)
        tok!(/:/); ss
        value = sass_script(:parse)
        Sass::Supports::Declaration.new(name, value)
    end

supports_directive(name, start_pos) click to toggle source

www.w3.org/TR/css3-conditional/

    # File lib/sass/scss/parser.rb, line 559
    def supports_directive(name, start_pos)
      condition = expr!(:supports_condition)
      node = Sass::Tree::SupportsNode.new(name, condition)

      tok!(/\{/)
      node.has_children = true
      block_contents(node, :directive)
      tok!(/\}/)

      node(node, start_pos)
    end

supports_interpolation() click to toggle source

    # File lib/sass/scss/parser.rb, line 628
    def supports_interpolation
      interp = interpolation
      return unless interp
      ss
      Sass::Supports::Interpolation.new(interp)
    end

supports_negation() click to toggle source

    # File lib/sass/scss/parser.rb, line 588
    def supports_negation
      return unless tok(/not/i)
      ss
      Sass::Supports::Negation.new(expr!(:supports_condition_in_parens))
    end

supports_operator() click to toggle source

    # File lib/sass/scss/parser.rb, line 594
    def supports_operator
      cond = supports_condition_in_parens
      return unless cond
      re = /and|or/i
      while (op = tok(re))
        re = /#{op}/i
        ss
        cond = Sass::Supports::Operator.new(
          cond, expr!(:supports_condition_in_parens), op)
      end
      cond
    end

term(allow_var) click to toggle source

    # File lib/sass/scss/parser.rb, line 1007
    def term(allow_var)
      e = tok(NUMBER) ||
          interp_uri ||
          function(allow_var) ||
          interp_string ||
          tok(UNICODERANGE) ||
          interp_ident ||
          tok(HEXCOLOR) ||
          (allow_var && var_expr)
      return e if e

      op = tok(/[+-]/)
      return unless op
      @expected = "number or function"
      [op,
       tok(NUMBER) || function(allow_var) || (allow_var && var_expr) || expr!(:interpolation)]
    end

throw_error() { || ... } click to toggle source

    # File lib/sass/scss/parser.rb, line 1215
    def throw_error
      old_throw_error, @throw_error = @throw_error, false
      yield
    ensure
      @throw_error = old_throw_error
    end

tok(rx) click to toggle source

    # File lib/sass/scss/parser.rb, line 1286
    def tok(rx)
      res = @scanner.scan(rx)

      return unless res

      newline_count = res.count(NEWLINE)
      if newline_count > 0
        @line += newline_count
        @offset = res[res.rindex(NEWLINE)..-1].size
      else
        @offset += res.size
      end

      @expected = nil
      if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
        @strs.each {|s| s << res}
      end
      res
    end

tok!(rx) click to toggle source

    # File lib/sass/scss/parser.rb, line 1190
    def tok!(rx)
      t = tok(rx)
      return t if t
      name = TOK_NAMES[rx]

      unless name
        # Display basic regexps as plain old strings
        source = rx.source.gsub(%r{\\/}, '/')
        string = rx.source.gsub(/\\(.)/, '\1')
        name = source == Regexp.escape(string) ? string.inspect : rx.inspect
      end

      expected(name)
    end

tok?(rx) click to toggle source

    # File lib/sass/scss/parser.rb, line 1180
    def tok?(rx)
      @scanner.match?(rx)
    end

try_declaration() click to toggle source

Tries to parse a declaration, and returns the value parsed so far if it fails.

This has three possible return types. It can return `nil`, indicating that parsing failed completely and the scanner hasn't moved forward at all. It can return an Array, indicating that parsing failed after consuming some text (possibly containing interpolation), which is returned. Or it can return a PropNode, indicating that parsing succeeded.

    # File lib/sass/scss/parser.rb, line 760
    def try_declaration
      # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
      # val" hacks.
      name_start_pos = source_position
      if (s = tok(/[:\*\.]|\#(?!\{)/))
        name = [s, str {ss}]
        return name unless (ident = interp_ident)
        name << ident
      else
        return unless (name = interp_ident)
        name = Array(name)
      end

      if (comment = tok(COMMENT))
        name << comment
      end
      name_end_pos = source_position

      mid = [str {ss}]
      return name + mid unless tok(/:/)
      mid << ':'

      # If this is a CSS variable, parse it as a property no matter what.
      if name.first.is_a?(String) && name.first.start_with?("--")
        return css_variable_declaration(name, name_start_pos, name_end_pos)
      end

      return name + mid + [':'] if tok(/:/)
      mid << str {ss}
      post_colon_whitespace = !mid.last.empty?
      could_be_selector = !post_colon_whitespace && (tok?(IDENT_START) || tok?(INTERP_START))

      value_start_pos = source_position
      value = nil
      error = catch_error do
        value = value!
        if tok?(/\{/)
          # Properties that are ambiguous with selectors can't have additional
          # properties nested beneath them.
          tok!(/;/) if could_be_selector
        elsif !tok?(/[;{}]/)
          # We want an exception if there's no valid end-of-property character
          # exists, but we don't want to consume it if it does.
          tok!(/[;{}]/)
        end
      end

      if error
        rethrow error unless could_be_selector

        # If the value would be followed by a semicolon, it's definitely
        # supposed to be a property, not a selector.
        additional_selector = almost_any_value
        rethrow error if tok?(/;/)

        return name + mid + (additional_selector || [])
      end

      value_end_pos = source_position
      ss
      require_block = tok?(/\{/)

      node = node(Sass::Tree::PropNode.new(name.flatten.compact, [value], :new),
                  name_start_pos, value_end_pos)
      node.name_source_range = range(name_start_pos, name_end_pos)
      node.value_source_range = range(value_start_pos, value_end_pos)

      return node unless require_block
      nested_properties! node
    end

use_css_import?() click to toggle source

    # File lib/sass/scss/parser.rb, line 417
    def use_css_import?; false; end

value!() click to toggle source

    # File lib/sass/scss/parser.rb, line 967
    def value!
      if tok?(/\{/)
        str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
        str.line = source_position.line
        str.source_range = range(source_position)
        return str
      end

      start_pos = source_position
      # This is a bit of a dirty trick:
      # if the value is completely static,
      # we don't parse it at all, and instead return a plain old string
      # containing the value.
      # This results in a dramatic speed increase.
      if (val = tok(STATIC_VALUE))
        str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(val.strip))
        str.line = start_pos.line
        str.source_range = range(start_pos)
        return str
      end
      sass_script(:parse)
    end

var_expr() click to toggle source

    # File lib/sass/scss/parser.rb, line 1036
    def var_expr
      return unless tok(/\$/)
      line = @line
      var = Sass::Script::Tree::Variable.new(tok!(IDENT))
      var.line = line
      var
    end

variable() click to toggle source

    # File lib/sass/scss/parser.rb, line 635
    def variable
      return unless tok(/\$/)
      start_pos = source_position
      name = tok!(IDENT)
      ss; tok!(/:/); ss

      expr = sass_script(:parse)
      while tok(/!/)
        flag_name = tok!(IDENT)
        if flag_name == 'default'
          guarded ||= true
        elsif flag_name == 'global'
          global ||= true
        else
          raise Sass::SyntaxError.new("Invalid flag \"!#{flag_name}\".", :line => @line)
        end
        ss
      end

      result = Sass::Tree::VariableNode.new(name, expr, guarded, global)
      node(result, start_pos)
    end

warn_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 282
    def warn_directive(start_pos)
      node(Sass::Tree::WarnNode.new(sass_script(:parse)), start_pos)
    end

while_directive(start_pos) click to toggle source

    # File lib/sass/scss/parser.rb, line 321
    def while_directive(start_pos)
      expr = sass_script(:parse)
      ss
      block(node(Sass::Tree::WhileNode.new(expr), start_pos), :directive)
    end

whitespace() click to toggle source

    # File lib/sass/scss/parser.rb, line 163
    def whitespace
      return unless tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
      ss
    end