Class | Sass::SCSS::Parser |
In: |
lib/sass/scss/parser.rb
|
Parent: | Object |
The parser for SCSS. It parses a string of code into a tree of {Sass::Tree::Node}s.
DIRECTIVES | = | Set[:mixin, :include, :debug, :warn, :for, :while, :if, :extend, :import, :media] |
EXPR_NAMES | = | { :media_query => "media query (e.g. print, screen, print and screen)", :media_expr => "media expression (e.g. (min-device-width: 800px)))", :pseudo_expr => "expression (e.g. fr, 2n+1)", :interp_ident => "identifier", :interp_name => "identifier", :expr => "expression (e.g. 1px, bold)", :selector_comma_sequence => "selector", :simple_selector_sequence => "selector", } |
TOK_NAMES | = | Haml::Util.to_hash( Sass::SCSS::RX.constants.map {|c| [Sass::SCSS::RX.const_get(c), c.downcase]}). merge(IDENT => "identifier", /[;}]/ => '";"') |
@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 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 14: def initialize(str, line = 1) 15: @template = str 16: @line = line 17: @strs = [] 18: end
@private
# File lib/sass/scss/parser.rb, line 787 787: def self.expected(scanner, expected, line) 788: pos = scanner.pos 789: 790: after = scanner.string[0...pos] 791: # Get rid of whitespace between pos and the last token, 792: # but only if there's a newline in there 793: after.gsub!(/\s*\n\s*$/, '') 794: # Also get rid of stuff before the last newline 795: after.gsub!(/.*\n/, '') 796: after = "..." + after[-15..-1] if after.size > 18 797: 798: was = scanner.rest.dup 799: # Get rid of whitespace between pos and the next token, 800: # but only if there's a newline in there 801: was.gsub!(/^\s*\n\s*/, '') 802: # Also get rid of stuff after the next newline 803: was.gsub!(/\n.*/, '') 804: was = was[0...15] + "..." if was.size > 18 805: 806: raise Sass::SyntaxError.new( 807: "Invalid CSS after \"#{after}\": expected #{expected}, was \"#{was}\"", 808: :line => line) 809: end
@private
# File lib/sass/scss/parser.rb, line 731 731: def self.sass_script_parser; @sass_script_parser; end
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 24 24: def parse 25: init_scanner! 26: root = stylesheet 27: expected("selector or at-rule") unless @scanner.eos? 28: root 29: end
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 37 37: def parse_interp_ident 38: init_scanner! 39: interp_ident 40: end
# File lib/sass/scss/parser.rb, line 681 681: def _interp_string(type) 682: return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, false]]) 683: res = [start] 684: 685: mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, true]] 686: # @scanner[2].empty? means we've started an interpolated section 687: while @scanner[2] == '#{' 688: @scanner.pos -= 2 # Don't consume the #{ 689: res.last.slice!(-2..-1) 690: res << expr!(:interpolation) << tok(mid_re) 691: end 692: res 693: end
# File lib/sass/scss/parser.rb, line 409 409: def _selector 410: # The combinator here allows the "> E" hack 411: return unless val = combinator || simple_selector_sequence 412: nl = str{ss}.include?("\n") 413: res = [] 414: res << val 415: res << "\n" if nl 416: 417: while val = combinator || simple_selector_sequence 418: res << val 419: res << "\n" if str{ss}.include?("\n") 420: end 421: Selector::Sequence.new(res.compact) 422: end
# File lib/sass/scss/parser.rb, line 494 494: def attrib 495: return unless tok(/\[/) 496: ss 497: ns, name = attrib_name! 498: ss 499: 500: if op = tok(/=/) || 501: tok(INCLUDES) || 502: tok(DASHMATCH) || 503: tok(PREFIXMATCH) || 504: tok(SUFFIXMATCH) || 505: tok(SUBSTRINGMATCH) 506: @expected = "identifier or string" 507: ss 508: if val = tok(IDENT) 509: val = [val] 510: else 511: val = expr!(:interp_string) 512: end 513: ss 514: end 515: tok(/\]/) 516: 517: Selector::Attribute.new(merge(name), merge(ns), op, merge(val)) 518: end
# File lib/sass/scss/parser.rb, line 520 520: def attrib_name! 521: if name_or_ns = interp_ident 522: # E, E|E 523: if tok(/\|(?!=)/) 524: ns = name_or_ns 525: name = interp_ident 526: else 527: name = name_or_ns 528: end 529: else 530: # *|E or |E 531: ns = [tok(/\*/) || ""] 532: tok!(/\|/) 533: name = expr!(:interp_ident) 534: end 535: return ns, name 536: end
# File lib/sass/scss/parser.rb, line 310 310: def block(node, context) 311: node.has_children = true 312: tok!(/\{/) 313: block_contents(node, context) 314: tok!(/\}/) 315: node 316: end
# File lib/sass/scss/parser.rb, line 329 329: def block_child(context) 330: return variable || directive || ruleset if context == :stylesheet 331: variable || directive || declaration_or_ruleset 332: end
A block may contain declarations and/or rulesets
# File lib/sass/scss/parser.rb, line 319 319: def block_contents(node, context) 320: block_given? ? yield : ss_comments(node) 321: node << (child = block_child(context)) 322: while tok(/;/) || (child && child.has_children) 323: block_given? ? yield : ss_comments(node) 324: node << (child = block_child(context)) 325: end 326: node 327: end
# File lib/sass/scss/parser.rb, line 462 462: def class_selector 463: return unless tok(/\./) 464: @expected = "class name" 465: Selector::Class.new(merge(expr!(:interp_ident))) 466: end
# File lib/sass/scss/parser.rb, line 424 424: def combinator 425: tok(PLUS) || tok(GREATER) || tok(TILDE) 426: end
# File lib/sass/scss/parser.rb, line 147 147: def debug_directive 148: node(Sass::Tree::DebugNode.new(sass_script(:parse))) 149: end
# File lib/sass/scss/parser.rb, line 570 570: def declaration 571: # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks 572: if s = tok(/[:\*\.]|\#(?!\{)/) 573: @use_property_exception = s !~ /[\.\#]/ 574: name = [s, str{ss}, *expr!(:interp_ident)] 575: else 576: return unless name = interp_ident 577: name = [name] if name.is_a?(String) 578: end 579: if comment = tok(COMMENT) 580: name << comment 581: end 582: ss 583: 584: tok!(/:/) 585: space, value = value! 586: ss 587: require_block = tok?(/\{/) 588: 589: node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new)) 590: 591: return node unless require_block 592: nested_properties! node, space 593: end
This is a nasty hack, and the only place in the parser that requires 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 349 349: def declaration_or_ruleset 350: pos = @scanner.pos 351: line = @line 352: old_use_property_exception, @use_property_exception = 353: @use_property_exception, false 354: begin 355: decl = declaration 356: unless decl && decl.has_children 357: # We want an exception if it's not there, 358: # but we don't want to consume if it is 359: tok!(/[;}]/) unless tok?(/[;}]/) 360: end 361: return decl 362: rescue Sass::SyntaxError => decl_err 363: end 364: 365: @line = line 366: @scanner.pos = pos 367: 368: begin 369: return ruleset 370: rescue Sass::SyntaxError => ruleset_err 371: raise @use_property_exception ? decl_err : ruleset_err 372: end 373: ensure 374: @use_property_exception = old_use_property_exception 375: end
# File lib/sass/scss/parser.rb, line 103 103: def directive 104: return unless tok(/@/) 105: name = tok!(IDENT) 106: ss 107: 108: if dir = special_directive(name) 109: return dir 110: end 111: 112: val = str do 113: # Most at-rules take expressions (e.g. @import), 114: # but some (e.g. @page) take selector-like arguments 115: expr || selector 116: end 117: node = node(Sass::Tree::DirectiveNode.new("@#{name} #{val}".strip)) 118: 119: if tok(/\{/) 120: node.has_children = true 121: block_contents(node, :directive) 122: tok!(/\}/) 123: end 124: 125: node 126: end
# File lib/sass/scss/parser.rb, line 474 474: def element_name 475: return unless name = interp_ident || tok(/\*/) || (tok?(/\|/) && "") 476: if tok(/\|/) 477: @expected = "element name or *" 478: ns = name 479: name = interp_ident || tok!(/\*/) 480: end 481: 482: if name == '*' 483: Selector::Universal.new(merge(ns)) 484: else 485: Selector::Element.new(merge(name), merge(ns)) 486: end 487: end
# File lib/sass/scss/parser.rb, line 193 193: def else_block(node) 194: return unless tok(/@else/) 195: ss 196: else_node = block( 197: Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))), 198: :directive) 199: node.add_else(else_node) 200: pos = @scanner.pos 201: ss 202: 203: else_block(node) || 204: begin 205: # Backtrack in case there are any comments we want to parse 206: @scanner.pos = pos 207: node 208: end 209: end
# File lib/sass/scss/parser.rb, line 782 782: def expected(name) 783: self.class.expected(@scanner, @expected || name, @line) 784: end
# File lib/sass/scss/parser.rb, line 634 634: def expr 635: return unless t = term 636: res = [t, str{ss}] 637: 638: while (o = operator) && (t = term) 639: res << o << t << str{ss} 640: end 641: 642: res 643: end
# File lib/sass/scss/parser.rb, line 764 764: def expr!(name) 765: (e = send(name)) && (return e) 766: expected(EXPR_NAMES[name] || name.to_s) 767: end
# File lib/sass/scss/parser.rb, line 211 211: def extend_directive 212: node(Sass::Tree::ExtendNode.new(expr!(:selector))) 213: end
# File lib/sass/scss/parser.rb, line 155 155: def for_directive 156: tok!(/\$/) 157: var = tok! IDENT 158: ss 159: 160: tok!(/from/) 161: from = sass_script(:parse_until, Set["to", "through"]) 162: ss 163: 164: @expected = '"to" or "through"' 165: exclusive = (tok(/to/) || tok!(/through/)) == 'to' 166: to = sass_script(:parse) 167: ss 168: 169: block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)), :directive) 170: end
# File lib/sass/scss/parser.rb, line 662 662: def function 663: return unless name = tok(FUNCTION) 664: if name == "expression(" || name == "calc(" 665: str, _ = Haml::Shared.balance(@scanner, ?(, ?), 1) 666: [name, str] 667: else 668: [name, str{ss}, expr, tok!(/\)/)] 669: end 670: end
# File lib/sass/scss/parser.rb, line 468 468: def id_selector 469: return unless tok(/#(?!\{)/) 470: @expected = "id name" 471: Selector::Id.new(merge(expr!(:interp_name))) 472: end
# File lib/sass/scss/parser.rb, line 178 178: def if_directive 179: expr = sass_script(:parse) 180: ss 181: node = block(node(Sass::Tree::IfNode.new(expr)), :directive) 182: pos = @scanner.pos 183: ss 184: 185: else_block(node) || 186: begin 187: # Backtrack in case there are any comments we want to parse 188: @scanner.pos = pos 189: node 190: end 191: end
# File lib/sass/scss/parser.rb, line 215 215: def import_directive 216: @expected = "string or url()" 217: arg = tok(STRING) || (uri = tok!(URI)) 218: path = @scanner[1] || @scanner[2] || @scanner[3] 219: ss 220: 221: media = str {media_query_list}.strip 222: 223: if uri || path =~ /^http:\/\// || !media.strip.empty? || use_css_import? 224: return node(Sass::Tree::DirectiveNode.new("@import #{arg} #{media}".strip)) 225: end 226: 227: node(Sass::Tree::ImportNode.new(path.strip)) 228: end
# File lib/sass/scss/parser.rb, line 140 140: def include_directive 141: name = tok! IDENT 142: args = sass_script(:parse_mixin_include_arglist) 143: ss 144: node(Sass::Tree::MixinNode.new(name, args)) 145: end
# File lib/sass/scss/parser.rb, line 46 46: def init_scanner! 47: @scanner = 48: if @template.is_a?(StringScanner) 49: @template 50: else 51: StringScanner.new(@template.gsub("\r", "")) 52: end 53: end
# File lib/sass/scss/parser.rb, line 695 695: def interp_ident(start = IDENT) 696: return unless val = tok(start) || interpolation 697: res = [val] 698: while val = tok(NAME) || interpolation 699: res << val 700: end 701: res 702: end
# File lib/sass/scss/parser.rb, line 677 677: def interp_string 678: _interp_string(:double) || _interp_string(:single) 679: end
# File lib/sass/scss/parser.rb, line 672 672: def interpolation 673: return unless tok(INTERP_START) 674: sass_script(:parse_interpolated) 675: end
# File lib/sass/scss/parser.rb, line 489 489: def interpolation_selector 490: return unless script = interpolation 491: Selector::Interpolation.new(script) 492: end
# File lib/sass/scss/parser.rb, line 232 232: def media_directive 233: val = str {media_query_list}.strip 234: block(node(Sass::Tree::DirectiveNode.new("@media #{val}")), :directive) 235: end
# File lib/sass/scss/parser.rb, line 267 267: def media_expr 268: return unless tok(/\(/) 269: ss 270: @expected = "media feature (e.g. min-device-width, color)" 271: tok!(IDENT) 272: ss 273: 274: if tok(/:/) 275: ss; expr!(:expr) 276: end 277: tok!(/\)/) 278: ss 279: 280: true 281: end
# File lib/sass/scss/parser.rb, line 249 249: def media_query 250: if tok(/only|not/i) 251: ss 252: @expected = "media type (e.g. print, screen)" 253: tok!(IDENT) 254: ss 255: elsif !tok(IDENT) && !media_expr 256: return 257: end 258: 259: ss 260: while tok(/and/i) 261: ss; expr!(:media_expr); ss 262: end 263: 264: true 265: end
www.w3.org/TR/css3-mediaqueries/#syntax
# File lib/sass/scss/parser.rb, line 238 238: def media_query_list 239: return unless media_query 240: 241: ss 242: while tok(/,/) 243: ss; expr!(:media_query); ss 244: end 245: 246: true 247: end
# File lib/sass/scss/parser.rb, line 741 741: def merge(arr) 742: arr && Haml::Util.merge_adjacent_strings([arr].flatten) 743: end
# File lib/sass/scss/parser.rb, line 133 133: def mixin_directive 134: name = tok! IDENT 135: args = sass_script(:parse_mixin_definition_arglist) 136: ss 137: block(node(Sass::Tree::MixinDefNode.new(name, args)), :directive) 138: end
# File lib/sass/scss/parser.rb, line 561 561: def negation 562: return unless name = tok(NOT) || tok(MOZ_ANY) 563: ss 564: @expected = "selector" 565: sel = selector_comma_sequence 566: tok!(/\)/) 567: Selector::SelectorPseudoClass.new(name[1...-1], sel) 568: end
# File lib/sass/scss/parser.rb, line 622 622: def nested_properties!(node, space) 623: raise Sass::SyntaxError.new("Invalid CSS: a space is required between a property and its definition\nwhen it has other properties nested beneath it.\n", :line => @line) unless space 624: 625: @use_property_exception = true 626: @expected = 'expression (e.g. 1px, bold) or "{"' 627: block(node, :property) 628: end
# File lib/sass/scss/parser.rb, line 723 723: def node(node) 724: node.line = @line 725: node 726: end
# File lib/sass/scss/parser.rb, line 293 293: def operator 294: # Many of these operators (all except / and ,) 295: # are disallowed by the CSS spec, 296: # but they're included here for compatibility 297: # with some proprietary MS properties 298: str {ss if tok(/[\/,:.=]/)} 299: end
# File lib/sass/scss/parser.rb, line 457 457: def parent_selector 458: return unless tok(/&/) 459: Selector::Parent.new 460: end
# File lib/sass/scss/parser.rb, line 611 611: def plain_value 612: return unless tok(/:/) 613: space = !str {ss}.empty? 614: @use_property_exception ||= space || !tok?(IDENT) 615: 616: expression = expr 617: expression << tok(IMPORTANT) if expression 618: # expression, space, value 619: return expression, space, expression || [""] 620: end
# File lib/sass/scss/parser.rb, line 89 89: def process_comment(text, node) 90: single_line = text =~ /^\/\// 91: pre_str = single_line ? "" : @scanner. 92: string[0...@scanner.pos]. 93: reverse[/.*?\*\/(.*?)($|\Z)/, 1]. 94: reverse.gsub(/[^\s]/, ' ') 95: text = text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */' if single_line 96: comment = Sass::Tree::CommentNode.new(pre_str + text, single_line) 97: comment.line = @line - text.count("\n") 98: node << comment 99: end
# File lib/sass/scss/parser.rb, line 538 538: def pseudo 539: return unless s = tok(/::?/) 540: @expected = "pseudoclass or pseudoelement" 541: name = expr!(:interp_ident) 542: if tok(/\(/) 543: ss 544: arg = expr!(:pseudo_expr) 545: tok!(/\)/) 546: end 547: Selector::Pseudo.new(s == ':' ? :class : :element, merge(name), merge(arg)) 548: end
# File lib/sass/scss/parser.rb, line 550 550: def pseudo_expr 551: return unless e = tok(PLUS) || tok(/-/) || tok(NUMBER) || 552: interp_string || tok(IDENT) || interpolation 553: res = [e, str{ss}] 554: while e = tok(PLUS) || tok(/-/) || tok(NUMBER) || 555: interp_string || tok(IDENT) || interpolation 556: res << e << str{ss} 557: end 558: res 559: end
# File lib/sass/scss/parser.rb, line 305 305: def ruleset 306: return unless rules = selector_sequence 307: block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset) 308: end
# File lib/sass/scss/parser.rb, line 60 60: def s(node) 61: while tok(S) || tok(CDC) || tok(CDO) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT)) 62: next unless c 63: process_comment c, node 64: c = nil 65: end 66: true 67: end
# File lib/sass/scss/parser.rb, line 733 733: def sass_script(*args) 734: parser = self.class.sass_script_parser.new(@scanner, @line, 735: @scanner.pos - (@scanner.string[0...@scanner.pos].rindex("\n") || 0)) 736: result = parser.send(*args) 737: @line = parser.line 738: result 739: end
# File lib/sass/scss/parser.rb, line 393 393: def selector 394: return unless sel = _selector 395: sel.to_a 396: end
# File lib/sass/scss/parser.rb, line 398 398: def selector_comma_sequence 399: return unless sel = _selector 400: selectors = [sel] 401: while tok(/,/) 402: ws = str{ss} 403: selectors << expr!(:_selector) 404: selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members) if ws.include?("\n") 405: end 406: Selector::CommaSequence.new(selectors) 407: end
# File lib/sass/scss/parser.rb, line 377 377: def selector_sequence 378: if sel = tok(STATIC_SELECTOR) 379: return [sel] 380: end 381: 382: rules = [] 383: return unless v = selector 384: rules.concat v 385: 386: while tok(/,/) 387: rules << ',' << str {ss} 388: rules.concat expr!(:selector) 389: end 390: rules 391: end
# File lib/sass/scss/parser.rb, line 428 428: def simple_selector_sequence 429: # This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes- 430: return expr unless e = element_name || id_selector || class_selector || 431: attrib || negation || pseudo || parent_selector || interpolation_selector 432: res = [e] 433: 434: # The tok(/\*/) allows the "E*" hack 435: while v = element_name || id_selector || class_selector || 436: attrib || negation || pseudo || interpolation_selector || 437: (tok(/\*/) && Selector::Universal.new(nil)) 438: res << v 439: end 440: 441: if tok?(/&/) 442: begin 443: expected('"{"') 444: rescue Sass::SyntaxError => e 445: e.message << "\n\n" << "In Sass 3, the parent selector & can only be used where element names are valid,\nsince it could potentially be replaced by an element name.\n" 446: raise e 447: end 448: end 449: 450: Selector::SimpleSequence.new(res) 451: end
# File lib/sass/scss/parser.rb, line 128 128: def special_directive(name) 129: sym = name.gsub('-', '_').to_sym 130: DIRECTIVES.include?(sym) && send("#{sym}_directive") 131: end
# File lib/sass/scss/parser.rb, line 69 69: def ss 70: nil while tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT) 71: true 72: end
# File lib/sass/scss/parser.rb, line 74 74: def ss_comments(node) 75: while tok(S) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT)) 76: next unless c 77: process_comment c, node 78: c = nil 79: end 80: 81: true 82: end
# File lib/sass/scss/parser.rb, line 708 708: def str 709: @strs.push "" 710: yield 711: @strs.last 712: ensure 713: @strs.pop 714: end
# File lib/sass/scss/parser.rb, line 716 716: def str? 717: @strs.push "" 718: yield && @strs.last 719: ensure 720: @strs.pop 721: end
# File lib/sass/scss/parser.rb, line 55 55: def stylesheet 56: node = node(Sass::Tree::RootNode.new(@scanner.string)) 57: block_contents(node, :stylesheet) {s(node)} 58: end
# File lib/sass/scss/parser.rb, line 645 645: def term 646: unless e = tok(NUMBER) || 647: tok(URI) || 648: function || 649: interp_string || 650: tok(UNICODERANGE) || 651: tok(IDENT) || 652: tok(HEXCOLOR) || 653: interpolation 654: 655: return unless op = unary_operator 656: @expected = "number or function" 657: return [op, tok(NUMBER) || expr!(:function)] 658: end 659: e 660: end
# File lib/sass/scss/parser.rb, line 811 811: def tok(rx) 812: res = @scanner.scan(rx) 813: if res 814: @line += res.count("\n") 815: @expected = nil 816: if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT 817: @strs.each {|s| s << res} 818: end 819: end 820: 821: res 822: end
# File lib/sass/scss/parser.rb, line 769 769: def tok!(rx) 770: (t = tok(rx)) && (return t) 771: name = TOK_NAMES[rx] 772: 773: unless name 774: # Display basic regexps as plain old strings 775: string = rx.source.gsub(/\\(.)/, '\1') 776: name = rx.source == Regexp.escape(string) ? string.inspect : rx.inspect 777: end 778: 779: expected(name) 780: end
# File lib/sass/scss/parser.rb, line 595 595: def value! 596: space = !str {ss}.empty? 597: @use_property_exception ||= space || !tok?(IDENT) 598: 599: return true, Sass::Script::String.new("") if tok?(/\{/) 600: # This is a bit of a dirty trick: 601: # if the value is completely static, 602: # we don't parse it at all, and instead return a plain old string 603: # containing the value. 604: # This results in a dramatic speed increase. 605: if val = tok(STATIC_VALUE) 606: return space, Sass::Script::String.new(val.strip) 607: end 608: return space, sass_script(:parse) 609: end
# File lib/sass/scss/parser.rb, line 283 283: def variable 284: return unless tok(/\$/) 285: name = tok!(IDENT) 286: ss; tok!(/:/); ss 287: 288: expr = sass_script(:parse) 289: guarded = tok(DEFAULT) 290: node(Sass::Tree::VariableNode.new(name, expr, guarded)) 291: end
# File lib/sass/scss/parser.rb, line 151 151: def warn_directive 152: node(Sass::Tree::WarnNode.new(sass_script(:parse))) 153: end
# File lib/sass/scss/parser.rb, line 172 172: def while_directive 173: expr = sass_script(:parse) 174: ss 175: block(node(Sass::Tree::WhileNode.new(expr)), :directive) 176: end