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 [Fixnum] The 1-based line on which the source string appeared,

if it's part of another document.

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

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

@comment

rubocop:disable ParameterLists
# File lib/sass/scss/parser.rb, line 25
def initialize(str, filename, importer, line = 1, offset = 1)
  # rubocop:enable ParameterLists
  @template = str
  @filename = filename
  @importer = importer
  @line = line
  @offset = offset
  @strs = []
end

Private Class Methods

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

@private

# File lib/sass/scss/parser.rb, line 1293
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 39
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 74
def parse_at_root_query
  init_scanner!
  query = at_root_query
  expected("@at-root query list") unless query && @scanner.eos?
  query
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 52
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 62
def parse_media_query_list
  init_scanner!
  ql = media_query_list
  expected("media query list") unless ql && @scanner.eos?
  ql
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 86
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 1099
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 493
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
_selector() click to toggle source
# File lib/sass/scss/parser.rb, line 750
def _selector
  # The combinator here allows the "> E" hack
  val = combinator || simple_selector_sequence
  return unless val
  nl = str {ss}.include?("\n")
  res = []
  res << val
  res << "\n" if nl

  while (val = combinator || simple_selector_sequence)
    res << val
    res << "\n" if str {ss}.include?("\n")
  end
  Selector::Sequence.new(res.compact)
end
at_root_directive(start_pos) click to toggle source
# File lib/sass/scss/parser.rb, line 514
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 526
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
attrib() click to toggle source
# File lib/sass/scss/parser.rb, line 868
def attrib
  return unless tok(/\[/)
  ss
  ns, name = attrib_name!
  ss

  op = tok(/=/) ||
       tok(INCLUDES) ||
       tok(DASHMATCH) ||
       tok(PREFIXMATCH) ||
       tok(SUFFIXMATCH) ||
       tok(SUBSTRINGMATCH)
  if op
    @expected = "identifier or string"
    ss
    val = interp_ident || expr!(:interp_string)
    ss
  end
  flags = interp_ident || interp_string
  tok!(/\]/)

  Selector::Attribute.new(merge(name), merge(ns), op, merge(val), merge(flags))
end
attrib_name!() click to toggle source
# File lib/sass/scss/parser.rb, line 892
def attrib_name!
  if (name_or_ns = interp_ident)
    # E, E|E
    if tok(/\|(?!=)/)
      ns = name_or_ns
      name = interp_ident
    else
      name = name_or_ns
    end
  else
    # *|E or |E
    ns = [tok(/\*/) || ""]
    tok!(/\|/)
    name = expr!(:interp_ident)
  end
  return ns, name
end
block(node, context) click to toggle source
# File lib/sass/scss/parser.rb, line 638
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 657
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 647
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 1263
def catch_error(&block)
  old_throw_error, @throw_error = @throw_error, true
  pos = @scanner.pos
  line = @line
  offset = @offset
  expected = @expected
  if catch(:_sass_parser_error) {yield; false}
    @scanner.pos = pos
    @line = line
    @offset = offset
    @expected = expected
    {:pos => pos, :line => line, :expected => @expected, :block => block}
  end
ensure
  @throw_error = old_throw_error
end
charset_directive(start_pos) click to toggle source
# File lib/sass/scss/parser.rb, line 478
def charset_directive(start_pos)
  tok! STRING
  name = @scanner[1] || @scanner[2]
  ss
  node(Sass::Tree::CharsetNode.new(name), start_pos)
end
class_selector() click to toggle source
# File lib/sass/scss/parser.rb, line 823
def class_selector
  return unless tok(/\./)
  @expected = "class name"
  Selector::Class.new(merge(expr!(:interp_ident)))
end
combinator() click to toggle source
# File lib/sass/scss/parser.rb, line 766
def combinator
  tok(PLUS) || tok(GREATER) || tok(TILDE) || reference_combinator
end
content_directive(start_pos) click to toggle source
# File lib/sass/scss/parser.rb, line 250
def content_directive(start_pos)
  ss
  node(Sass::Tree::ContentNode.new, start_pos)
end
debug_directive(start_pos) click to toggle source
# File lib/sass/scss/parser.rb, line 266
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 967
def declaration
  # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
  name_start_pos = source_position
  if (s = tok(/[:\*\.]|\#(?!\{)/))
    @use_property_exception = s !~ /[\.\#]/
    name = [s, str {ss}, *expr!(:interp_ident)]
  else
    name = interp_ident
    return unless name
    name = [name] if name.is_a?(String)
  end
  if (comment = tok(COMMENT))
    name << comment
  end
  name_end_pos = source_position
  ss

  tok!(/:/)
  value_start_pos, space, 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, space
end
declaration_or_ruleset() click to toggle source

This is a nasty hack, and the only place in the parser that requires a large amount of backtracking. The reason is that we can't figure out if certain strings are declarations or rulesets with fixed finite lookahead. For example, “foo:bar baz baz baz…” could be either a property or a selector.

To handle this, we simply check if it works as a property (which is the most common case) and, if it doesn't, try it as a ruleset.

We could eke some more efficiency out of this by handling some easy cases (first token isn't an identifier, no colon after the identifier, whitespace after the colon), but I'm not sure the gains would be worth the added complexity.

# File lib/sass/scss/parser.rb, line 684
def declaration_or_ruleset
  old_use_property_exception, @use_property_exception =
    @use_property_exception, false
  decl_err = catch_error do
    decl = declaration
    unless decl && decl.has_children
      # We want an exception if it's not there,
      # but we don't want to consume if it is
      tok!(/[;}]/) unless tok?(/[;}]/)
    end
    return decl
  end

  ruleset_err = catch_error {return ruleset}
  rethrow(@use_property_exception ? decl_err : ruleset_err)
ensure
  @use_property_exception = old_use_property_exception
end
directive() click to toggle source
# File lib/sass/scss/parser.rb, line 187
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

  # Most at-rules take expressions (e.g. @import),
  # but some (e.g. @page) take selector-like arguments.
  # Some take no arguments at all.
  val = expr || selector
  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 207
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 291
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
element_name() click to toggle source
# File lib/sass/scss/parser.rb, line 841
def element_name
  ns, name = Sass::Util.destructure(qualified_name(:allow_star_name))
  return unless ns || name

  if name == '*'
    Selector::Universal.new(merge(ns))
  else
    Selector::Element.new(merge(name), merge(ns))
  end
end
else_block(node) click to toggle source
# File lib/sass/scss/parser.rb, line 332
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 353
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 1251
def err(msg)
  throw(:_sass_parser_error, true) if @throw_error
  raise Sass::SyntaxError.new(msg, :line => @line)
end
expected(name) click to toggle source
# File lib/sass/scss/parser.rb, line 1246
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 1037
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 1225
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 357
def extend_directive(start_pos)
  selector, selector_range = expr!(:selector_sequence)
  optional = tok(OPTIONAL)
  ss
  node(Sass::Tree::ExtendNode.new(selector, !!optional, selector_range), start_pos)
end
for_directive(start_pos) click to toggle source
# File lib/sass/scss/parser.rb, line 274
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 1067
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 255
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 663
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
id_selector() click to toggle source
# File lib/sass/scss/parser.rb, line 829
def id_selector
  return unless tok(/#(?!\{)/)
  @expected = "id name"
  Selector::Id.new(merge(expr!(:interp_name)))
end
if_directive(start_pos) click to toggle source
# File lib/sass/scss/parser.rb, line 315
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 377
def import_arg
  start_pos = source_position
  return unless (str = tok(STRING)) || (uri = tok?(/url\(/i))
  if uri
    str = sass_script(:parse_string)
    ss
    media = media_query_list
    ss
    return node(Tree::CssImportNode.new(str, media.to_a), start_pos)
  end

  path = @scanner[1] || @scanner[2]
  ss

  media = media_query_list
  if path =~ %r{^(https?:)?//} || media || use_css_import?
    return node(Sass::Tree::CssImportNode.new(str, media.to_a), start_pos)
  end

  node(Sass::Tree::ImportNode.new(path.strip), start_pos)
end
import_directive(start_pos) click to toggle source
# File lib/sass/scss/parser.rb, line 364
def import_directive(start_pos)
  values = []

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

  values
end
include_directive(start_pos) click to toggle source
# File lib/sass/scss/parser.rb, line 236
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 105
def init_scanner!
  @scanner =
    if @template.is_a?(StringScanner)
      @template
    else
      Sass::Util::MultibyteStringScanner.new(@template.gsub("\r", ""))
    end
end
interp_ident(start = IDENT) click to toggle source
# File lib/sass/scss/parser.rb, line 1114
def interp_ident(start = IDENT)
  val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
  return unless val
  res = [val]
  while (val = tok(NAME) || interpolation)
    res << val
  end
  res
end
interp_ident_or_var() click to toggle source
# File lib/sass/scss/parser.rb, line 1124
def interp_ident_or_var
  id = interp_ident
  return id if id
  var = var_expr
  return [var] if var
end
interp_name() click to toggle source
# File lib/sass/scss/parser.rb, line 1131
def interp_name
  interp_ident NAME
end
interp_string() click to toggle source
# File lib/sass/scss/parser.rb, line 1091
def interp_string
  _interp_string(:double) || _interp_string(:single)
end
interp_uri() click to toggle source
# File lib/sass/scss/parser.rb, line 1095
def interp_uri
  _interp_string(:uri)
end
interpolation() click to toggle source
# File lib/sass/scss/parser.rb, line 1086
def interpolation
  return unless tok(INTERP_START)
  sass_script(:parse_interpolated)
end
interpolation_selector() click to toggle source
# File lib/sass/scss/parser.rb, line 862
def interpolation_selector
  if (script = interpolation)
    Selector::Interpolation.new(script)
  end
end
media_directive(start_pos) click to toggle source
# File lib/sass/scss/parser.rb, line 401
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 420
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 406
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 1189
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 229
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 506
def moz_document_function
  val = interp_uri || _interp_string(:url_prefix) ||
    _interp_string(:domain) || function(!:allow_var) || interpolation
  return unless val
  ss
  val
end
nested_properties!(node, space) click to toggle source
# File lib/sass/scss/parser.rb, line 1026
      def nested_properties!(node, space)
        err("Invalid CSS: a space is required between a property and its definition
when it has other properties nested beneath it.
") unless space

        @use_property_exception = true
        @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 1158
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 622
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(/[\/,:.=]/)}
end
parent_selector() click to toggle source
# File lib/sass/scss/parser.rb, line 818
def parent_selector
  return unless tok(/&/)
  Selector::Parent.new(interp_ident(NAME) || [])
end
placeholder_selector() click to toggle source
# File lib/sass/scss/parser.rb, line 835
def placeholder_selector
  return unless tok(/%/)
  @expected = "placeholder name"
  Selector::Placeholder.new(merge(expr!(:interp_ident)))
end
prefixed_directive(name, start_pos) click to toggle source
# File lib/sass/scss/parser.rb, line 224
def prefixed_directive(name, start_pos)
  sym = name.gsub(/^-[a-z0-9]+-/i, '').gsub('-', '_').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 148
def process_comment(text, node)
  silent = text =~ %r{\A//}
  loud = !silent && text =~ %r{\A/[/*]!}
  line = @line - text.count("\n")

  if silent
    value = [text.sub(%r{\A\s*//}, '/*').gsub(%r{^\s*//}, ' *') + ' */']
  else
    value = Sass::Engine.parse_interp(
      text, line, @scanner.pos - text.size, :filename => @filename)
    string_before_comment = @scanner.string[0...@scanner.pos - text.length]
    newline_before_comment = string_before_comment.rindex("\n")
    last_line_before_comment =
      if newline_before_comment
        string_before_comment[newline_before_comment + 1..-1]
      else
        string_before_comment
      end
    value.unshift(last_line_before_comment.gsub(/[^\s]/, ' '))
  end

  type = if silent
           :silent
         elsif loud
           :loud
         else
           :normal
         end
  comment = Sass::Tree::CommentNode.new(value, type)
  comment.line = line
  node << comment
end
pseudo() click to toggle source
# File lib/sass/scss/parser.rb, line 910
def pseudo
  s = tok(/::?/)
  return unless s
  @expected = "pseudoclass or pseudoelement"
  name = expr!(:interp_ident)
  if tok(/\(/)
    ss
    arg = expr!(:pseudo_arg)
    while tok(/,/)
      arg << ',' << str {ss}
      arg.concat expr!(:pseudo_arg)
    end
    tok!(/\)/)
  end
  Selector::Pseudo.new(s == ':' ? :class : :element, merge(name), merge(arg))
end
pseudo_arg() click to toggle source
# File lib/sass/scss/parser.rb, line 927
def pseudo_arg
  # In the CSS spec, every pseudo-class/element either takes a pseudo
  # expression or a selector comma sequence as an argument. However, we
  # don't want to have to know which takes which, so we handle both at
  # once.
  #
  # However, there are some ambiguities between the two. For instance, "n"
  # could start a pseudo expression like "n+1", or it could start a
  # selector like "n|m". In order to handle this, we must regrettably
  # backtrack.
  expr, sel = nil, nil
  pseudo_err = catch_error do
    expr = pseudo_expr
    next if tok?(/[,)]/)
    expr = nil
    expected '")"'
  end

  return expr if expr
  sel_err = catch_error {sel = selector}
  return sel if sel
  rethrow pseudo_err if pseudo_err
  rethrow sel_err if sel_err
  nil
end
pseudo_expr() click to toggle source
# File lib/sass/scss/parser.rb, line 957
def pseudo_expr
  e = pseudo_expr_token
  return unless e
  res = [e, str {ss}]
  while (e = pseudo_expr_token)
    res << e << str {ss}
  end
  res
end
pseudo_expr_token() click to toggle source
# File lib/sass/scss/parser.rb, line 953
def pseudo_expr_token
  tok(PLUS) || tok(/[-*]/) || tok(NUMBER) || interp_string || tok(IDENT) || interpolation
end
qualified_name(allow_star_name = false) click to toggle source
# File lib/sass/scss/parser.rb, line 852
def qualified_name(allow_star_name = false)
  name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
  return unless name
  return nil, name unless tok(/\|/)

  return name, expr!(:interp_ident) unless allow_star_name
  @expected = "identifier or *"
  return name, interp_ident || tok!(/\*/)
end
query_expr() click to toggle source
# File lib/sass/scss/parser.rb, line 455
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 101
def range(start_pos, end_pos = source_position)
  Sass::Source::Range.new(start_pos, end_pos, @filename, @importer)
end
reference_combinator() click to toggle source
# File lib/sass/scss/parser.rb, line 770
def reference_combinator
  return unless tok(/\//)
  res = ['/']
  ns, name = expr!(:qualified_name)
  res << ns << '|' if ns
  res << name << tok!(/\//)
  res = res.flatten
  res = res.join '' if res.all? {|e| e.is_a?(String)}
  res
end
rethrow(err) click to toggle source
# File lib/sass/scss/parser.rb, line 1280
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 262
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 630
def ruleset
  start_pos = source_position
  rules, source_range = selector_sequence
  return unless rules
  block(node(
    Sass::Tree::RuleNode.new(rules.flatten.compact, source_range), start_pos), :ruleset)
end
s(node) click to toggle source
# File lib/sass/scss/parser.rb, line 119
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 1172
def sass_script(*args)
  parser = self.class.sass_script_parser.new(@scanner, @line, @offset,
                                             :filename => @filename, :importer => @importer)
  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
selector() click to toggle source
# File lib/sass/scss/parser.rb, line 726
def selector
  sel = _selector
  return unless sel
  sel.to_a
end
selector_comma_sequence() click to toggle source
# File lib/sass/scss/parser.rb, line 732
def selector_comma_sequence
  sel = _selector
  return unless sel
  selectors = [sel]
  ws = ''
  while tok(/,/)
    ws << str {ss}
    if (sel = _selector)
      selectors << sel
      if ws.include?("\n")
        selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members)
      end
      ws = ''
    end
  end
  Selector::CommaSequence.new(selectors)
end
selector_sequence() click to toggle source
# File lib/sass/scss/parser.rb, line 703
def selector_sequence
  start_pos = source_position
  if (sel = tok(STATIC_SELECTOR, true))
    return [sel], range(start_pos)
  end

  rules = []
  v = selector
  return unless v
  rules.concat v

  ws = ''
  while tok(/,/)
    ws << str {ss}
    if (v = selector)
      rules << ',' << ws
      rules.concat v
      ws = ''
    end
  end
  return rules, range(start_pos)
end
simple_selector_sequence() click to toggle source
# File lib/sass/scss/parser.rb, line 781
def simple_selector_sequence
  # Returning expr by default allows for stuff like
  # http://www.w3.org/TR/css3-animations/#keyframes-

  start_pos = source_position
  e = element_name || id_selector ||
    class_selector || placeholder_selector || attrib || pseudo ||
    parent_selector || interpolation_selector
  return expr(!:allow_var) unless e
  res = [e]

  # The tok(/\*/) allows the "E*" hack
  while (v = id_selector || class_selector || placeholder_selector ||
             attrib || pseudo || interpolation_selector ||
             (tok(/\*/) && Selector::Universal.new(nil)))
    res << v
  end

  pos = @scanner.pos
  line = @line
  if (sel = str? {simple_selector_sequence})
    @scanner.pos = pos
    @line = line
    begin
      # If we see "*E", don't force a throw because this could be the
      # "*prop: val" hack.
      expected('"{"') if res.length == 1 && res[0].is_a?(Selector::Universal)
      throw_error {expected('"{"')}
    rescue Sass::SyntaxError => e
      e.message << "\n\n\"#{sel}\" may only be used at the beginning of a compound selector."
      raise e
    end
  end

  Selector::SimpleSequence.new(res, tok(/!/), range(start_pos))
end
source_position() click to toggle source
# File lib/sass/scss/parser.rb, line 97
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 219
def special_directive(name, start_pos)
  sym = name.gsub('-', '_').to_sym
  DIRECTIVES.include?(sym) && send("#{sym}_directive", start_pos)
end
ss() click to toggle source
# File lib/sass/scss/parser.rb, line 128
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 133
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 1135
def str
  @strs.push ""
  yield
  @strs.last
ensure
  @strs.pop
end
str?() { || ... } click to toggle source
# File lib/sass/scss/parser.rb, line 1143
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
stylesheet() click to toggle source
# File lib/sass/scss/parser.rb, line 114
def stylesheet
  node = node(Sass::Tree::RootNode.new(@scanner.string), source_position)
  block_contents(node, :stylesheet) {s(node)}
end
supports_condition() click to toggle source
# File lib/sass/scss/parser.rb, line 550
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 571
def supports_condition_in_parens
  interp = supports_interpolation
  return interp if interp
  return unless tok(/\(/); ss
  if (cond = supports_condition)
    tok!(/\)/); ss
    cond
  else
    name = sass_script(:parse)
    tok!(/:/); ss
    value = sass_script(:parse)
    tok!(/\)/); ss
    Sass::Supports::Declaration.new(name, value)
  end
end
supports_declaration_condition() click to toggle source
# File lib/sass/scss/parser.rb, line 587
def supports_declaration_condition
  return unless tok(/\(/); ss
  supports_declaration_body
end
supports_directive(name, start_pos) click to toggle source

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

# File lib/sass/scss/parser.rb, line 538
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 592
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 554
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 560
def supports_operator
  cond = supports_condition_in_parens
  return unless cond
  while (op = tok(/and|or/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 1049
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 1256
def throw_error
  old_throw_error, @throw_error = @throw_error, false
  yield
ensure
  @throw_error = old_throw_error
end
tok(rx, last_group_lookahead = false) click to toggle source
# File lib/sass/scss/parser.rb, line 1321
def tok(rx, last_group_lookahead = false)
  res = @scanner.scan(rx)
  if res
    # This fixes https://github.com/nex3/sass/issues/104, which affects
    # Ruby 1.8.7 and REE. This fix is to replace the ?= zero-width
    # positive lookahead operator in the Regexp (which matches without
    # consuming the matched group), with a match that does consume the
    # group, but then rewinds the scanner and removes the group from the
    # end of the matched string. This fix makes the assumption that the
    # matched group will always occur at the end of the match.
    if last_group_lookahead && @scanner[-1]
      @scanner.pos -= @scanner[-1].length
      res.slice!(-@scanner[-1].length..-1)
    end

    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
end
tok!(rx) click to toggle source
# File lib/sass/scss/parser.rb, line 1231
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(/\\//, '/')
    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 1221
def tok?(rx)
  @scanner.match?(rx)
end
use_css_import?() click to toggle source
# File lib/sass/scss/parser.rb, line 399
def use_css_import?; false; end
value!() click to toggle source
# File lib/sass/scss/parser.rb, line 999
def value!
  space = !str {ss}.empty?
  value_start_pos = source_position
  @use_property_exception ||= space || !tok?(IDENT)

  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 value_start_pos, true, 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, true))
    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 value_start_pos, space, str
  end
  return value_start_pos, space, sass_script(:parse)
end
var_expr() click to toggle source
# File lib/sass/scss/parser.rb, line 1078
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 599
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 270
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 309
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 143
def whitespace
  return unless tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
  ss
end