class Sass::SCSS::Parser

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

def self.expected(scanner, expected, line)

Other tags:
    Private: -
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

def self.sass_script_parser; @sass_script_parser; end

Other tags:
    Private: -
def self.sass_script_parser; @sass_script_parser; end

def _interp_string(type)

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

def _selector

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

def attrib

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
    if val = tok(IDENT)
      val = [val]
    else
      val = expr!(:interp_string)
    end
    ss
  end
  tok(/\]/)
  Selector::Attribute.new(merge(name), merge(ns), op, merge(val))
end

def attrib_name!

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

def block(node, context)

def block(node, context)
  node.has_children = true
  tok!(/\{/)
  block_contents(node, context)
  tok!(/\}/)
  node
end

def block_child(context)

def block_child(context)
  return variable || directive || ruleset if context == :stylesheet
  variable || directive || declaration_or_ruleset
end

def block_contents(node, context)

A block may contain declarations and/or rulesets
def block_contents(node, context)
  block_given? ? yield : ss_comments(node)
  node << (child = block_child(context))
  while tok(/;/) || (child && child.has_children)
    block_given? ? yield : ss_comments(node)
    node << (child = block_child(context))
  end
  node
end

def class_selector

def class_selector
  return unless tok(/\./)
  @expected = "class name"
  Selector::Class.new(merge(expr!(:interp_ident)))
end

def combinator

def combinator
  tok(PLUS) || tok(GREATER) || tok(TILDE)
end

def debug_directive

def debug_directive
  node(Sass::Tree::DebugNode.new(sass_script(:parse)))
end

def declaration

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

def declaration_or_ruleset

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

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

or a selector.
For example, "foo:bar baz baz baz..." could be either a property
are declarations or rulesets with fixed finite lookahead.
The reason is that we can't figure out if certain strings
that requires backtracking.
This is a nasty hack, and the only place in the parser
def declaration_or_ruleset
  pos = @scanner.pos
  line = @line
  old_use_property_exception, @use_property_exception =
    @use_property_exception, false
  begin
    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
  rescue Sass::SyntaxError => decl_err
  end
  @line = line
  @scanner.pos = pos
  begin
    return ruleset
  rescue Sass::SyntaxError => ruleset_err
    raise @use_property_exception ? decl_err : ruleset_err
  end
ensure
  @use_property_exception = old_use_property_exception
end

def directive

def directive
  return unless tok(/@/)
  name = tok!(IDENT)
  ss
  if dir = special_directive(name)
    return dir
  end
  val = str do
    # Most at-rules take expressions (e.g. @import),
    # but some (e.g. @page) take selector-like arguments
    expr || selector
  end
  node = node(Sass::Tree::DirectiveNode.new("@#{name} #{val}".strip))
  if tok(/\{/)
    node.has_children = true
    block_contents(node, :directive)
    tok!(/\}/)
  end
  node
end

def element_name

def element_name
  return unless name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
  if tok(/\|/)
    @expected = "element name or *"
    ns = name
    name = interp_ident || tok!(/\*/)
  end
  if name == '*'
    Selector::Universal.new(merge(ns))
  else
    Selector::Element.new(merge(name), merge(ns))
  end
end

def else_block(node)

def else_block(node)
  return node unless tok(/@else/)
  ss
  else_node = block(
    Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
    :directive)
  node.add_else(else_node)
  ss
  else_block(node)
end

def expected(name)

def expected(name)
  self.class.expected(@scanner, @expected || name, @line)
end

def expr

def expr
  return unless t = term
  res = [t, str{ss}]
  while (o = operator) && (t = term)
    res << o << t << str{ss}
  end
  res
end

def expr!(name)

def expr!(name)
  (e = send(name)) && (return e)
  expected(EXPR_NAMES[name] || name.to_s)
end

def extend_directive

def extend_directive
  node(Sass::Tree::ExtendNode.new(expr!(:selector)))
end

def for_directive

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

def function

def function
  return unless name = tok(FUNCTION)
  if name == "expression(" || name == "calc("
    str, _ = Haml::Shared.balance(@scanner, ?(, ?), 1)
    [name, str]
  else
    [name, str{ss}, expr, tok!(/\)/)]
  end
end

def id_selector

def id_selector
  return unless tok(/#(?!\{)/)
  @expected = "id name"
  Selector::Id.new(merge(expr!(:interp_name)))
end

def if_directive

def if_directive
  expr = sass_script(:parse)
  ss
  node = block(node(Sass::Tree::IfNode.new(expr)), :directive)
  ss
  else_block(node)
end

def import_directive

def import_directive
  @expected = "string or url()"
  arg = tok(STRING) || tok!(URI)
  path = @scanner[1] || @scanner[2] || @scanner[3]
  ss
  media = str {media_query_list}.strip
  if !media.strip.empty? || use_css_import?
    return node(Sass::Tree::DirectiveNode.new("@import #{arg} #{media}".strip))
  end
  node(Sass::Tree::ImportNode.new(path.strip))
end

def include_directive

def include_directive
  name = tok! IDENT
  args = sass_script(:parse_mixin_include_arglist)
  ss
  node(Sass::Tree::MixinNode.new(name, args))
end

def init_scanner!

def init_scanner!
  @scanner =
    if @template.is_a?(StringScanner)
      @template
    else
      StringScanner.new(@template.gsub("\r", ""))
    end
end

def initialize(str, line = 1)

Parameters:
  • line (Fixnum) -- The line on which the source string appeared,
  • str (String, StringScanner) -- The source document to parse.
def initialize(str, line = 1)
  @template = str
  @line = line
  @strs = []
end

def interp_ident(start = IDENT)

def interp_ident(start = IDENT)
  return unless val = tok(start) || interpolation
  res = [val]
  while val = tok(NAME) || interpolation
    res << val
  end
  res
end

def interp_name

def interp_name
  interp_ident NAME
end

def interp_string

def interp_string
  _interp_string(:double) || _interp_string(:single)
end

def interpolation

def interpolation
  return unless tok(INTERP_START)
  sass_script(:parse_interpolated)
end

def interpolation_selector

def interpolation_selector
  return unless script = interpolation
  Selector::Interpolation.new(script)
end

def media_directive

def media_directive
  val = str {media_query_list}.strip
  block(node(Sass::Tree::DirectiveNode.new("@media #{val}")), :directive)
end

def media_expr

def media_expr
  return unless tok(/\(/)
  ss
  @expected = "media feature (e.g. min-device-width, color)"
  tok!(IDENT)
  ss
  if tok(/:/)
    ss; expr!(:expr)
  end
  tok!(/\)/)
  ss
  true
end

def media_query

def media_query
  if tok(/only|not/i)
    ss
    @expected = "media type (e.g. print, screen)"
    tok!(IDENT)
    ss
  elsif !tok(IDENT) && !media_expr
    return
  end
  ss
  while tok(/and/i)
    ss; expr!(:media_expr); ss
  end
  true
end

def media_query_list

http://www.w3.org/TR/css3-mediaqueries/#syntax
def media_query_list
  return unless media_query
  ss
  while tok(/,/)
    ss; expr!(:media_query); ss
  end
  true
end

def merge(arr)

def merge(arr)
  arr && Haml::Util.merge_adjacent_strings([arr].flatten)
end

def mixin_directive

def mixin_directive
  name = tok! IDENT
  args = sass_script(:parse_mixin_definition_arglist)
  ss
  block(node(Sass::Tree::MixinDefNode.new(name, args)), :directive)
end

def negation

def negation
  return unless tok(NOT)
  ss
  @expected = "selector"
  sel = element_name || id_selector || class_selector || attrib || expr!(:pseudo)
  tok!(/\)/)
  Selector::Negation.new(sel)
end

def nested_properties!(node, space)

def nested_properties!(node, space)
  raise Sass::SyntaxError.new(<<MESSAGE, :line => @line) unless space
d CSS: a space is required between a property and its definition
t has other properties nested beneath it.
E
  @use_property_exception = true
  @expected = 'expression (e.g. 1px, bold) or "{"'
  block(node, :property)
end

def node(node)

def node(node)
  node.line = @line
  node
end

def operator

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

def parent_selector

def parent_selector
  return unless tok(/&/)
  Selector::Parent.new
end

def parse

Raises:
  • (Sass::SyntaxError) - if there's a syntax error in the document

Returns:
  • (Sass::Tree::RootNode) - The root node of the document tree
def parse
  init_scanner!
  root = stylesheet
  expected("selector or at-rule") unless @scanner.eos?
  root
end

def parse_interp_ident

Returns:
  • (Array, nil) -
def parse_interp_ident
  init_scanner!
  interp_ident
end

def plain_value

def plain_value
  return unless tok(/:/)
  space = !str {ss}.empty?
  @use_property_exception ||= space || !tok?(IDENT)
  expression = expr
  expression << tok(IMPORTANT) if expression
  # expression, space, value
  return expression, space, expression || [""]
end

def process_comment(text, node)

def process_comment(text, node)
  single_line = text =~ /^\/\//
  pre_str = single_line ? "" : @scanner.
    string[0...@scanner.pos].
    reverse[/.*?\*\/(.*?)($|\Z)/, 1].
    reverse.gsub(/[^\s]/, ' ')
  text = text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */' if single_line
  comment = Sass::Tree::CommentNode.new(pre_str + text, single_line)
  comment.line = @line - text.count("\n")
  node << comment
end

def pseudo

def pseudo
  return unless s = tok(/::?/)
  @expected = "pseudoclass or pseudoelement"
  name = expr!(:interp_ident)
  if tok(/\(/)
    ss
    arg = expr!(:pseudo_expr)
    tok!(/\)/)
  end
  Selector::Pseudo.new(s == ':' ? :class : :element, merge(name), merge(arg))
end

def pseudo_expr

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

def ruleset

def ruleset
  return unless rules = selector_sequence
  block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
end

def s(node)

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

def sass_script(*args)

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)
  @line = parser.line
  result
end

def selector

def selector
  return unless sel = _selector
  sel.to_a
end

def selector_sequence

def selector_sequence
  if sel = tok(STATIC_SELECTOR)
    return [sel]
  end
  rules = []
  return unless v = selector
  rules.concat v
  while tok(/,/)
    rules << ',' << str {ss}
    rules.concat expr!(:selector)
  end
  rules
end

def simple_selector_sequence

def simple_selector_sequence
  # This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
  return expr unless e = element_name || id_selector || class_selector ||
    attrib || negation || pseudo || parent_selector || interpolation_selector
  res = [e]
  # The tok(/\*/) allows the "E*" hack
  while v = element_name || id_selector || class_selector ||
      attrib || negation || pseudo || interpolation_selector ||
      (tok(/\*/) && Selector::Universal.new(nil))
    res << v
  end
  if tok?(/&/)
    begin
      expected('"{"')
    rescue Sass::SyntaxError => e
      e.message << "\n\n" << <<MESSAGE
s 3, the parent selector & can only be used where element names are valid,
it could potentially be replaced by an element name.
E
      raise e
    end
  end
  Selector::SimpleSequence.new(res)
end

def special_directive(name)

def special_directive(name)
  sym = name.gsub('-', '_').to_sym
  DIRECTIVES.include?(sym) && send("#{sym}_directive")
end

def ss

def ss
  nil while tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
  true
end

def ss_comments(node)

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

def str

def str
  @strs.push ""
  yield
  @strs.last
ensure
  @strs.pop
end

def str?

def str?
  @strs.push ""
  yield && @strs.last
ensure
  @strs.pop
end

def stylesheet

def stylesheet
  node = node(Sass::Tree::RootNode.new(@scanner.string))
  block_contents(node, :stylesheet) {s(node)}
end

def term

def term
  unless e = tok(NUMBER) ||
      tok(URI) ||
      function ||
      interp_string ||
      tok(UNICODERANGE) ||
      tok(IDENT) ||
      tok(HEXCOLOR) ||
      interpolation
    return unless op = unary_operator
    @expected = "number or function"
    return [op, tok(NUMBER) || expr!(:function)]
  end
  e
end

def tok(rx)

def tok(rx)
  res = @scanner.scan(rx)
  if res
    @line += res.count("\n")
    @expected = nil
    if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
      @strs.each {|s| s << res}
    end
  end
  res
end

def tok!(rx)

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

def tok?(rx)

def tok?(rx)
  @scanner.match?(rx)
end

def unary_operator

def unary_operator
  tok(/[+-]/)
end

def use_css_import?; false; end

def use_css_import?; false; end

def value!

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)
    return space, Sass::Script::String.new(val.strip)
  end
  return space, sass_script(:parse)
end

def variable

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

def warn_directive

def warn_directive
  node(Sass::Tree::WarnNode.new(sass_script(:parse)))
end

def while_directive

def while_directive
  expr = sass_script(:parse)
  ss
  block(node(Sass::Tree::WhileNode.new(expr)), :directive)
end

def whitespace

def whitespace
  return unless tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
  ss
end