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

Public Class Methods

new(str, filename, line = 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. @param line [Fixnum] The line on which the source string appeared,

if it's part of another document.
# File lib/sass/scss/parser.rb, line 14
def initialize(str, filename, line = 1)
  @template = str
  @filename = filename
  @line = line
  @strs = []
end

Private Class Methods

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

@private

# File lib/sass/scss/parser.rb, line 1128
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
sass_script_parser() click to toggle source

@private

# File lib/sass/scss/parser.rb, line 1017
def self.sass_script_parser; @sass_script_parser; 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 25
def parse
  init_scanner!
  root = stylesheet
  expected("selector or at-rule") unless @scanner.eos?
  root
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::Node>, nil]

The interpolated identifier, or nil if none could be parsed
# File lib/sass/scss/parser.rb, line 38
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 48
def parse_media_query_list
  init_scanner!
  ql = media_query_list
  expected("media query list") unless @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 60
def parse_supports_condition
  init_scanner!
  condition = supports_condition
  expected("supports condition") unless @scanner.eos?
  condition
end

Private Instance Methods

_interp_string(type) click to toggle source
# File lib/sass/scss/parser.rb, line 956
def _interp_string(type)
  return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][false])
  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() 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 432
def _moz_document_directive
  res = ["@-moz-document "]
  loop do
    res << str{ss} << expr!(:moz_document_function)
    break unless c = tok(/,/)
    res << c
  end
  directive_body(res.flatten)
end
_selector() click to toggle source
# File lib/sass/scss/parser.rb, line 638
def _selector
  # The combinator here allows the "> E" hack
  return unless val = combinator || simple_selector_sequence
  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
attrib() click to toggle source
# File lib/sass/scss/parser.rb, line 750
def attrib
  return unless tok(/\[/)
  ss
  ns, name = attrib_name!
  ss

  if op = tok(/=/) ||
      tok(INCLUDES) ||
      tok(DASHMATCH) ||
      tok(PREFIXMATCH) ||
      tok(SUFFIXMATCH) ||
      tok(SUBSTRINGMATCH)
    @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 773
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 532
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 551
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 541
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 1100
def catch_error(&block)
  old_throw_error, @throw_error = @throw_error, true
  pos = @scanner.pos
  line = @line
  expected = @expected
  if catch(:_sass_parser_error) {yield; false}
    @scanner.pos = pos
    @line = line
    @expected = expected
    {:pos => pos, :line => line, :expected => @expected, :block => block}
  end
ensure
  @throw_error = old_throw_error
end
charset_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 417
def charset_directive
  tok! STRING
  name = @scanner[1] || @scanner[2]
  ss
  node(Sass::Tree::CharsetNode.new(name))
end
class_selector() click to toggle source
# File lib/sass/scss/parser.rb, line 707
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 653
def combinator
  tok(PLUS) || tok(GREATER) || tok(TILDE) || reference_combinator
end
content_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 202
def content_directive
  ss
  node(Sass::Tree::ContentNode.new)
end
debug_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 218
def debug_directive
  node(Sass::Tree::DebugNode.new(sass_script(:parse)))
end
declaration() click to toggle source
# File lib/sass/scss/parser.rb, line 844
def declaration
  # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
  if s = tok(/[:\*\.]|\#(?!\{)/)
    @use_property_exception = s !~ /[\.\#]/
    name = [s, str{ss}, *expr!(:interp_ident)]
  else
    return unless name = interp_ident
    name = [name] if name.is_a?(String)
  end
  if comment = tok(COMMENT)
    name << comment
  end
  ss

  tok!(/:/)
  space, value = value!
  ss
  require_block = tok?(/\{/)

  node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new))

  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 578
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 141
def directive
  return unless tok(/@/)
  name = tok!(IDENT)
  ss

  if dir = special_directive(name)
    return dir
  elsif dir = prefixed_directive(name)
    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)
end
directive_body(value) click to toggle source
# File lib/sass/scss/parser.rb, line 160
def directive_body(value)
  node = node(Sass::Tree::DirectiveNode.new(value))

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

  node
end
each_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 243
def each_directive
  tok!(/\$/)
  var = tok! IDENT
  ss

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

  block(node(Sass::Tree::EachNode.new(var, list)), :directive)
end
element_name() click to toggle source
# File lib/sass/scss/parser.rb, line 725
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 278
def else_block(node)
  return unless tok(/@else/)
  ss
  else_node = block(
    Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
    :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() click to toggle source
# File lib/sass/scss/parser.rb, line 298
def else_directive
  err("Invalid CSS: @else must come after @if")
end
err(msg) click to toggle source
# File lib/sass/scss/parser.rb, line 1088
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 1083
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 896
def expr(allow_var = true)
  return unless t = term(allow_var)
  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 1065
def expr!(name)
  (e = send(name)) && (return e)
  expected(EXPR_NAMES[name] || name.to_s)
end
extend_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 302
def extend_directive
  selector = expr!(:selector_sequence)
  optional = tok(OPTIONAL)
  ss
  node(Sass::Tree::ExtendNode.new(selector, !!optional))
end
for_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 226
def for_directive
  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)), :directive)
end
function(allow_var) click to toggle source
# File lib/sass/scss/parser.rb, line 925
def function(allow_var)
  return unless name = tok(FUNCTION)
  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() click to toggle source
# File lib/sass/scss/parser.rb, line 207
def function_directive
  name = tok! IDENT
  args, splat = sass_script(:parse_function_definition_arglist)
  ss
  block(node(Sass::Tree::FunctionNode.new(name, args, splat)), :function)
end
has_children?(child_or_array) click to toggle source
# File lib/sass/scss/parser.rb, line 557
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)
  return child_or_array.has_children
end
id_selector() click to toggle source
# File lib/sass/scss/parser.rb, line 713
def id_selector
  return unless tok(/#(?!\{)/)
  @expected = "id name"
  Selector::Id.new(merge(expr!(:interp_name)))
end
if_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 261
def if_directive
  expr = sass_script(:parse)
  ss
  node = block(node(Sass::Tree::IfNode.new(expr)), :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 322
def import_arg
  line = @line
  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))
  end

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

  media = media_query_list
  if path =~ /^(https?:)?\/\// || media || use_css_import?
    node = Sass::Tree::CssImportNode.new(str, media.to_a)
  else
    node = Sass::Tree::ImportNode.new(path.strip)
  end
  node.line = line
  node
end
import_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 309
def import_directive
  values = []

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

  return values
end
include_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 189
def include_directive
  name = tok! IDENT
  args, keywords, splat = sass_script(:parse_mixin_include_arglist)
  ss
  include_node = node(Sass::Tree::MixinNode.new(name, args, keywords, splat))
  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 71
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 970
def interp_ident(start = IDENT)
  return unless val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
  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 979
def interp_ident_or_var
  (id = interp_ident) and return id
  (var = var_expr) and return [var]
end
interp_name() click to toggle source
# File lib/sass/scss/parser.rb, line 984
def interp_name
  interp_ident NAME
end
interp_string() click to toggle source
# File lib/sass/scss/parser.rb, line 948
def interp_string
  _interp_string(:double) || _interp_string(:single)
end
interp_uri() click to toggle source
# File lib/sass/scss/parser.rb, line 952
def interp_uri
  _interp_string(:uri)
end
interpolation() click to toggle source
# File lib/sass/scss/parser.rb, line 943
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 745
def interpolation_selector
  return unless script = interpolation
  Selector::Interpolation.new(script)
end
media_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 348
def media_directive
  block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a)), :directive)
end
media_expr() click to toggle source
# File lib/sass/scss/parser.rb, line 400
def media_expr
  interp = interpolation and return interp
  return unless tok(/\(/)
  res = ['(']
  ss
  res << sass_script(:parse)

  if tok(/:/)
    res << ': '
    ss
    res << sass_script(:parse)
  end
  res << tok!(/\)/)
  ss
  res
end
media_query() click to toggle source
# File lib/sass/scss/parser.rb, line 366
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
    return unless expr = media_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 353
def media_query_list
  return unless query = media_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 1035
def merge(arr)
  arr && Sass::Util.merge_adjacent_strings([arr].flatten)
end
mixin_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 182
def mixin_directive
  name = tok! IDENT
  args, splat = sass_script(:parse_mixin_definition_arglist)
  ss
  block(node(Sass::Tree::MixinDefNode.new(name, args, splat)), :directive)
end
moz_document_function() click to toggle source
# File lib/sass/scss/parser.rb, line 442
def moz_document_function
  return unless val = interp_uri || _interp_string(:url_prefix) ||
    _interp_string(:domain) || function(!:allow_var) || interpolation
  ss
  val
end
nested_properties!(node, space) click to toggle source
# File lib/sass/scss/parser.rb, line 885
      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) click to toggle source
# File lib/sass/scss/parser.rb, line 1009
def node(node)
  node.line = @line
  node
end
operator() click to toggle source
# File lib/sass/scss/parser.rb, line 519
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 702
def parent_selector
  return unless tok(/&/)
  Selector::Parent.new
end
placeholder_selector() click to toggle source
# File lib/sass/scss/parser.rb, line 719
def placeholder_selector
  return unless tok(/%/)
  @expected = "placeholder name"
  Selector::Placeholder.new(merge(expr!(:interp_ident)))
end
prefixed_directive(name) click to toggle source
# File lib/sass/scss/parser.rb, line 177
def prefixed_directive(name)
  sym = name.gsub(/^-[a-z0-9]+-/i, '').gsub('-', '_').to_sym
  PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name)
end
process_comment(text, node) click to toggle source
# File lib/sass/scss/parser.rb, line 114
def process_comment(text, node)
  silent = text =~ /^\/\//
  loud = !silent && text =~ %r{^/[/*]!}
  line = @line - text.count("\n")

  if silent
    value = [text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */']
  else
    value = Sass::Engine.parse_interp(text, line, @scanner.pos - text.size, :filename => @filename)
    value.unshift(@scanner.
      string[0...@scanner.pos].
      reverse[/.*?\*\/(.*?)($|\Z)/, 1].
      reverse.gsub(/[^\s]/, ' '))
  end

  type = if silent then :silent elsif loud then :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 791
def pseudo
  return unless s = tok(/::?/)
  @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 807
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
  return
end
pseudo_expr() click to toggle source
# File lib/sass/scss/parser.rb, line 833
def pseudo_expr
  return unless e = tok(PLUS) || tok(/[-*]/) || tok(NUMBER) ||
    interp_string || tok(IDENT) || interpolation
  res = [e, str{ss}]
  while e = tok(PLUS) || tok(/[-*]/) || tok(NUMBER) ||
      interp_string || tok(IDENT) || interpolation
    res << e << str{ss}
  end
  res
end
qualified_name(allow_star_name=false) click to toggle source
# File lib/sass/scss/parser.rb, line 736
def qualified_name(allow_star_name=false)
  return unless name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
  return nil, name unless tok(/\|/)

  return name, expr!(:interp_ident) unless allow_star_name
  @expected = "identifier or *"
  return name, interp_ident || tok!(/\*/)
end
reference_combinator() click to toggle source
# File lib/sass/scss/parser.rb, line 657
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 1115
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() click to toggle source
# File lib/sass/scss/parser.rb, line 214
def return_directive
  node(Sass::Tree::ReturnNode.new(sass_script(:parse)))
end
ruleset() click to toggle source
# File lib/sass/scss/parser.rb, line 527
def ruleset
  return unless rules = selector_sequence
  block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
end
s(node) click to toggle source
# File lib/sass/scss/parser.rb, line 85
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 1019
def sass_script(*args)
  parser = self.class.sass_script_parser.new(@scanner, @line,
    @scanner.pos - (@scanner.string[0...@scanner.pos].rindex("\n") || 0))
  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
  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 618
def selector
  return unless sel = _selector
  sel.to_a
end
selector_comma_sequence() click to toggle source
# File lib/sass/scss/parser.rb, line 623
def selector_comma_sequence
  return unless sel = _selector
  selectors = [sel]
  ws = ''
  while tok(/,/)
    ws << str{ss}
    if sel = _selector
      selectors << sel
      selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members) if ws.include?("\n")
      ws = ''
    end
  end
  Selector::CommaSequence.new(selectors)
end
selector_sequence() click to toggle source
# File lib/sass/scss/parser.rb, line 597
def selector_sequence
  if sel = tok(STATIC_SELECTOR, true)
    return [sel]
  end

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

  ws = ''
  while tok(/,/)
    ws << str {ss}
    if v = selector
      rules << ',' << ws
      rules.concat v
      ws = ''
    end
  end
  rules
end
simple_selector_sequence() click to toggle source
# File lib/sass/scss/parser.rb, line 668
def simple_selector_sequence
  # Returning expr by default allows for stuff like
  # http://www.w3.org/TR/css3-animations/#keyframes-
  return expr(!:allow_var) unless e = element_name || id_selector ||
    class_selector || placeholder_selector || attrib || pseudo ||
    parent_selector || interpolation_selector
  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(/!/))
end
special_directive(name) click to toggle source
# File lib/sass/scss/parser.rb, line 172
def special_directive(name)
  sym = name.gsub('-', '_').to_sym
  DIRECTIVES.include?(sym) && send("#{sym}_directive")
end
ss() click to toggle source
# File lib/sass/scss/parser.rb, line 94
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 99
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 988
def str
  @strs.push ""
  yield
  @strs.last
ensure
  @strs.pop
end
str?() { || ... } click to toggle source
# File lib/sass/scss/parser.rb, line 996
def str?
  pos = @scanner.pos
  line = @line
  @strs.push ""
  throw_error {yield} && @strs.last
rescue Sass::SyntaxError
  @scanner.pos = pos
  @line = line
  nil
ensure
  @strs.pop
end
stylesheet() click to toggle source
# File lib/sass/scss/parser.rb, line 80
def stylesheet
  node = node(Sass::Tree::RootNode.new(@scanner.string))
  block_contents(node, :stylesheet) {s(node)}
end
supports_condition() click to toggle source
# File lib/sass/scss/parser.rb, line 462
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 483
def supports_condition_in_parens
  interp = supports_interpolation and return 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 498
def supports_declaration_condition
  return unless tok(/\(/); ss
  supports_declaration_body
end
supports_directive(name) click to toggle source

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

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

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

  node
end
supports_interpolation() click to toggle source
# File lib/sass/scss/parser.rb, line 503
def supports_interpolation
  return unless interp = interpolation
  ss
  Sass::Supports::Interpolation.new(interp)
end
supports_negation() click to toggle source
# File lib/sass/scss/parser.rb, line 466
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 472
def supports_operator
  return unless cond = supports_condition_in_parens
  return cond unless op = tok(/and|or/i)
  begin
    ss
    cond = Sass::Supports::Operator.new(
      cond, expr!(:supports_condition_in_parens), op)
  end while op = tok(/and|or/i)
  cond
end
term(allow_var) click to toggle source
# File lib/sass/scss/parser.rb, line 907
def term(allow_var)
  if e = tok(NUMBER) ||
      interp_uri ||
      function(allow_var) ||
      interp_string ||
      tok(UNICODERANGE) ||
      interp_ident ||
      tok(HEXCOLOR) ||
      (allow_var && var_expr)
    return e
  end

  return unless op = tok(/[+-]/)
  @expected = "number or function"
  return [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 1093
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 1156
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
    @line += res.count(NEWLINE)
    @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 1070
def tok!(rx)
  (t = tok(rx)) && (return t)
  name = TOK_NAMES[rx]

  unless name
    # Display basic regexps as plain old strings
    string = rx.source.gsub(/\(.)/, '\1')
    name = rx.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 1061
def tok?(rx)
  @scanner.match?(rx)
end
use_css_import?() click to toggle source
# File lib/sass/scss/parser.rb, line 346
def use_css_import?; false; end
value!() click to toggle source
# File lib/sass/scss/parser.rb, line 869
def value!
  space = !str {ss}.empty?
  @use_property_exception ||= space || !tok?(IDENT)

  return true, Sass::Script::String.new("") if tok?(/\{/)
  # 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)
    return space, Sass::Script::String.new(val.strip)
  end
  return space, sass_script(:parse)
end
var_expr() click to toggle source
# File lib/sass/scss/parser.rb, line 935
def var_expr
  return unless tok(/\$/)
  line = @line
  var = Sass::Script::Variable.new(tok!(IDENT))
  var.line = line
  var
end
variable() click to toggle source
# File lib/sass/scss/parser.rb, line 509
def variable
  return unless tok(/\$/)
  name = tok!(IDENT)
  ss; tok!(/:/); ss

  expr = sass_script(:parse)
  guarded = tok(DEFAULT)
  node(Sass::Tree::VariableNode.new(name, expr, guarded))
end
warn_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 222
def warn_directive
  node(Sass::Tree::WarnNode.new(sass_script(:parse)))
end
while_directive() click to toggle source
# File lib/sass/scss/parser.rb, line 255
def while_directive
  expr = sass_script(:parse)
  ss
  block(node(Sass::Tree::WhileNode.new(expr)), :directive)
end
whitespace() click to toggle source
# File lib/sass/scss/parser.rb, line 109
def whitespace
  return unless tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
  ss
end