/scalate-jruby/src/main/resources/haml-3.0.25/lib/sass/scss/parser.rb

http://github.com/scalate/scalate · Ruby · 855 lines · 656 code · 133 blank · 66 comment · 134 complexity · a21ec8dae92495ba3affbf42fbe72684 MD5 · raw file

  1. require 'strscan'
  2. require 'set'
  3. module Sass
  4. module SCSS
  5. # The parser for SCSS.
  6. # It parses a string of code into a tree of {Sass::Tree::Node}s.
  7. class Parser
  8. # @param str [String, StringScanner] The source document to parse.
  9. # Note that `Parser` *won't* raise a nice error message if this isn't properly parsed;
  10. # for that, you should use the higher-level {Sass::Engine} or {Sass::CSS}.
  11. # @param line [Fixnum] The line on which the source string appeared,
  12. # if it's part of another document
  13. def initialize(str, line = 1)
  14. @template = str
  15. @line = line
  16. @strs = []
  17. end
  18. # Parses an SCSS document.
  19. #
  20. # @return [Sass::Tree::RootNode] The root node of the document tree
  21. # @raise [Sass::SyntaxError] if there's a syntax error in the document
  22. def parse
  23. init_scanner!
  24. root = stylesheet
  25. expected("selector or at-rule") unless @scanner.eos?
  26. root
  27. end
  28. # Parses an identifier with interpolation.
  29. # Note that this won't assert that the identifier takes up the entire input string;
  30. # it's meant to be used with `StringScanner`s as part of other parsers.
  31. #
  32. # @return [Array<String, Sass::Script::Node>, nil]
  33. # The interpolated identifier, or nil if none could be parsed
  34. def parse_interp_ident
  35. init_scanner!
  36. interp_ident
  37. end
  38. private
  39. include Sass::SCSS::RX
  40. def init_scanner!
  41. @scanner =
  42. if @template.is_a?(StringScanner)
  43. @template
  44. else
  45. StringScanner.new(@template.gsub("\r", ""))
  46. end
  47. end
  48. def stylesheet
  49. node = node(Sass::Tree::RootNode.new(@scanner.string))
  50. block_contents(node, :stylesheet) {s(node)}
  51. end
  52. def s(node)
  53. while tok(S) || tok(CDC) || tok(CDO) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
  54. next unless c
  55. process_comment c, node
  56. c = nil
  57. end
  58. true
  59. end
  60. def ss
  61. nil while tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
  62. true
  63. end
  64. def ss_comments(node)
  65. while tok(S) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
  66. next unless c
  67. process_comment c, node
  68. c = nil
  69. end
  70. true
  71. end
  72. def whitespace
  73. return unless tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
  74. ss
  75. end
  76. def process_comment(text, node)
  77. single_line = text =~ /^\/\//
  78. pre_str = single_line ? "" : @scanner.
  79. string[0...@scanner.pos].
  80. reverse[/.*?\*\/(.*?)($|\Z)/, 1].
  81. reverse.gsub(/[^\s]/, ' ')
  82. text = text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */' if single_line
  83. comment = Sass::Tree::CommentNode.new(pre_str + text, single_line)
  84. comment.line = @line - text.count("\n")
  85. node << comment
  86. end
  87. DIRECTIVES = Set[:mixin, :include, :debug, :warn, :for, :while, :if, :else,
  88. :extend, :import, :media, :charset]
  89. def directive
  90. return unless tok(/@/)
  91. name = tok!(IDENT)
  92. ss
  93. if dir = special_directive(name)
  94. return dir
  95. end
  96. # Most at-rules take expressions (e.g. @import),
  97. # but some (e.g. @page) take selector-like arguments
  98. val = str {break unless expr}
  99. val ||= CssParser.new(@scanner, @line).parse_selector_string
  100. node = node(Sass::Tree::DirectiveNode.new("@#{name} #{val}".strip))
  101. if tok(/\{/)
  102. node.has_children = true
  103. block_contents(node, :directive)
  104. tok!(/\}/)
  105. end
  106. node
  107. end
  108. def special_directive(name)
  109. sym = name.gsub('-', '_').to_sym
  110. DIRECTIVES.include?(sym) && send("#{sym}_directive")
  111. end
  112. def mixin_directive
  113. name = tok! IDENT
  114. args = sass_script(:parse_mixin_definition_arglist)
  115. ss
  116. block(node(Sass::Tree::MixinDefNode.new(name, args)), :directive)
  117. end
  118. def include_directive
  119. name = tok! IDENT
  120. args = sass_script(:parse_mixin_include_arglist)
  121. ss
  122. node(Sass::Tree::MixinNode.new(name, args))
  123. end
  124. def debug_directive
  125. node(Sass::Tree::DebugNode.new(sass_script(:parse)))
  126. end
  127. def warn_directive
  128. node(Sass::Tree::WarnNode.new(sass_script(:parse)))
  129. end
  130. def for_directive
  131. tok!(/\$/)
  132. var = tok! IDENT
  133. ss
  134. tok!(/from/)
  135. from = sass_script(:parse_until, Set["to", "through"])
  136. ss
  137. @expected = '"to" or "through"'
  138. exclusive = (tok(/to/) || tok!(/through/)) == 'to'
  139. to = sass_script(:parse)
  140. ss
  141. block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)), :directive)
  142. end
  143. def while_directive
  144. expr = sass_script(:parse)
  145. ss
  146. block(node(Sass::Tree::WhileNode.new(expr)), :directive)
  147. end
  148. def if_directive
  149. expr = sass_script(:parse)
  150. ss
  151. node = block(node(Sass::Tree::IfNode.new(expr)), :directive)
  152. pos = @scanner.pos
  153. line = @line
  154. ss
  155. else_block(node) ||
  156. begin
  157. # Backtrack in case there are any comments we want to parse
  158. @scanner.pos = pos
  159. @line = line
  160. node
  161. end
  162. end
  163. def else_block(node)
  164. return unless tok(/@else/)
  165. ss
  166. else_node = block(
  167. Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
  168. :directive)
  169. node.add_else(else_node)
  170. pos = @scanner.pos
  171. line = @line
  172. ss
  173. else_block(node) ||
  174. begin
  175. # Backtrack in case there are any comments we want to parse
  176. @scanner.pos = pos
  177. @line = line
  178. node
  179. end
  180. end
  181. def else_directive
  182. raise Sass::SyntaxError.new(
  183. "Invalid CSS: @else must come after @if", :line => @line)
  184. end
  185. def extend_directive
  186. node(Sass::Tree::ExtendNode.new(expr!(:selector)))
  187. end
  188. def import_directive
  189. values = []
  190. loop do
  191. values << expr!(:import_arg)
  192. break if use_css_import? || !tok(/,\s*/)
  193. end
  194. return values
  195. end
  196. def import_arg
  197. return unless arg = tok(STRING) || (uri = tok!(URI))
  198. path = @scanner[1] || @scanner[2] || @scanner[3]
  199. ss
  200. media = str {media_query_list}.strip
  201. if uri || path =~ /^http:\/\// || !media.strip.empty? || use_css_import?
  202. return node(Sass::Tree::DirectiveNode.new("@import #{arg} #{media}".strip))
  203. end
  204. node(Sass::Tree::ImportNode.new(path.strip))
  205. end
  206. def use_css_import?; false; end
  207. def media_directive
  208. val = str {media_query_list}.strip
  209. block(node(Sass::Tree::DirectiveNode.new("@media #{val}")), :directive)
  210. end
  211. # http://www.w3.org/TR/css3-mediaqueries/#syntax
  212. def media_query_list
  213. return unless media_query
  214. ss
  215. while tok(/,/)
  216. ss; expr!(:media_query); ss
  217. end
  218. true
  219. end
  220. def media_query
  221. if tok(/only|not/i)
  222. ss
  223. @expected = "media type (e.g. print, screen)"
  224. tok!(IDENT)
  225. ss
  226. elsif !tok(IDENT) && !media_expr
  227. return
  228. end
  229. ss
  230. while tok(/and/i)
  231. ss; expr!(:media_expr); ss
  232. end
  233. true
  234. end
  235. def media_expr
  236. return unless tok(/\(/)
  237. ss
  238. @expected = "media feature (e.g. min-device-width, color)"
  239. tok!(IDENT)
  240. ss
  241. if tok(/:/)
  242. ss; expr!(:expr)
  243. end
  244. tok!(/\)/)
  245. ss
  246. true
  247. end
  248. def charset_directive
  249. tok! STRING
  250. name = @scanner[1] || @scanner[2]
  251. ss
  252. node(Sass::Tree::CharsetNode.new(name))
  253. end
  254. def variable
  255. return unless tok(/\$/)
  256. name = tok!(IDENT)
  257. ss; tok!(/:/); ss
  258. expr = sass_script(:parse)
  259. guarded = tok(DEFAULT)
  260. node(Sass::Tree::VariableNode.new(name, expr, guarded))
  261. end
  262. def operator
  263. # Many of these operators (all except / and ,)
  264. # are disallowed by the CSS spec,
  265. # but they're included here for compatibility
  266. # with some proprietary MS properties
  267. str {ss if tok(/[\/,:.=]/)}
  268. end
  269. def unary_operator
  270. tok(/[+-]/)
  271. end
  272. def ruleset
  273. return unless rules = selector_sequence
  274. block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
  275. end
  276. def block(node, context)
  277. node.has_children = true
  278. tok!(/\{/)
  279. block_contents(node, context)
  280. tok!(/\}/)
  281. node
  282. end
  283. # A block may contain declarations and/or rulesets
  284. def block_contents(node, context)
  285. block_given? ? yield : ss_comments(node)
  286. node << (child = block_child(context))
  287. while tok(/;/) || has_children?(child)
  288. block_given? ? yield : ss_comments(node)
  289. node << (child = block_child(context))
  290. end
  291. node
  292. end
  293. def block_child(context)
  294. return variable || directive || ruleset if context == :stylesheet
  295. variable || directive || declaration_or_ruleset
  296. end
  297. def has_children?(child_or_array)
  298. return false unless child_or_array
  299. return child_or_array.last.has_children if child_or_array.is_a?(Array)
  300. return child_or_array.has_children
  301. end
  302. # This is a nasty hack, and the only place in the parser
  303. # that requires backtracking.
  304. # The reason is that we can't figure out if certain strings
  305. # are declarations or rulesets with fixed finite lookahead.
  306. # For example, "foo:bar baz baz baz..." could be either a property
  307. # or a selector.
  308. #
  309. # To handle this, we simply check if it works as a property
  310. # (which is the most common case)
  311. # and, if it doesn't, try it as a ruleset.
  312. #
  313. # We could eke some more efficiency out of this
  314. # by handling some easy cases (first token isn't an identifier,
  315. # no colon after the identifier, whitespace after the colon),
  316. # but I'm not sure the gains would be worth the added complexity.
  317. def declaration_or_ruleset
  318. pos = @scanner.pos
  319. line = @line
  320. old_use_property_exception, @use_property_exception =
  321. @use_property_exception, false
  322. begin
  323. decl = declaration
  324. unless decl && decl.has_children
  325. # We want an exception if it's not there,
  326. # but we don't want to consume if it is
  327. tok!(/[;}]/) unless tok?(/[;}]/)
  328. end
  329. return decl
  330. rescue Sass::SyntaxError => decl_err
  331. end
  332. @line = line
  333. @scanner.pos = pos
  334. begin
  335. return ruleset
  336. rescue Sass::SyntaxError => ruleset_err
  337. raise @use_property_exception ? decl_err : ruleset_err
  338. end
  339. ensure
  340. @use_property_exception = old_use_property_exception
  341. end
  342. def selector_sequence
  343. if sel = tok(STATIC_SELECTOR)
  344. return [sel]
  345. end
  346. rules = []
  347. return unless v = selector
  348. rules.concat v
  349. while tok(/,/)
  350. rules << ',' << str {ss}
  351. rules.concat expr!(:selector)
  352. end
  353. rules
  354. end
  355. def selector
  356. return unless sel = _selector
  357. sel.to_a
  358. end
  359. def selector_comma_sequence
  360. return unless sel = _selector
  361. selectors = [sel]
  362. while tok(/,/)
  363. ws = str{ss}
  364. selectors << expr!(:_selector)
  365. selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members) if ws.include?("\n")
  366. end
  367. Selector::CommaSequence.new(selectors)
  368. end
  369. def _selector
  370. # The combinator here allows the "> E" hack
  371. return unless val = combinator || simple_selector_sequence
  372. nl = str{ss}.include?("\n")
  373. res = []
  374. res << val
  375. res << "\n" if nl
  376. while val = combinator || simple_selector_sequence
  377. res << val
  378. res << "\n" if str{ss}.include?("\n")
  379. end
  380. Selector::Sequence.new(res.compact)
  381. end
  382. def combinator
  383. tok(PLUS) || tok(GREATER) || tok(TILDE)
  384. end
  385. def simple_selector_sequence
  386. # This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
  387. return expr unless e = element_name || id_selector || class_selector ||
  388. attrib || negation || pseudo || parent_selector || interpolation_selector
  389. res = [e]
  390. # The tok(/\*/) allows the "E*" hack
  391. while v = element_name || id_selector || class_selector ||
  392. attrib || negation || pseudo || interpolation_selector ||
  393. (tok(/\*/) && Selector::Universal.new(nil))
  394. res << v
  395. end
  396. if tok?(/&/)
  397. begin
  398. expected('"{"')
  399. rescue Sass::SyntaxError => e
  400. e.message << "\n\n" << <<MESSAGE
  401. In Sass 3, the parent selector & can only be used where element names are valid,
  402. since it could potentially be replaced by an element name.
  403. MESSAGE
  404. raise e
  405. end
  406. end
  407. Selector::SimpleSequence.new(res)
  408. end
  409. def parent_selector
  410. return unless tok(/&/)
  411. Selector::Parent.new
  412. end
  413. def class_selector
  414. return unless tok(/\./)
  415. @expected = "class name"
  416. Selector::Class.new(merge(expr!(:interp_ident)))
  417. end
  418. def id_selector
  419. return unless tok(/#(?!\{)/)
  420. @expected = "id name"
  421. Selector::Id.new(merge(expr!(:interp_name)))
  422. end
  423. def element_name
  424. return unless name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
  425. if tok(/\|/)
  426. @expected = "element name or *"
  427. ns = name
  428. name = interp_ident || tok!(/\*/)
  429. end
  430. if name == '*'
  431. Selector::Universal.new(merge(ns))
  432. else
  433. Selector::Element.new(merge(name), merge(ns))
  434. end
  435. end
  436. def interpolation_selector
  437. return unless script = interpolation
  438. Selector::Interpolation.new(script)
  439. end
  440. def attrib
  441. return unless tok(/\[/)
  442. ss
  443. ns, name = attrib_name!
  444. ss
  445. if op = tok(/=/) ||
  446. tok(INCLUDES) ||
  447. tok(DASHMATCH) ||
  448. tok(PREFIXMATCH) ||
  449. tok(SUFFIXMATCH) ||
  450. tok(SUBSTRINGMATCH)
  451. @expected = "identifier or string"
  452. ss
  453. if val = tok(IDENT)
  454. val = [val]
  455. else
  456. val = expr!(:interp_string)
  457. end
  458. ss
  459. end
  460. tok(/\]/)
  461. Selector::Attribute.new(merge(name), merge(ns), op, merge(val))
  462. end
  463. def attrib_name!
  464. if name_or_ns = interp_ident
  465. # E, E|E
  466. if tok(/\|(?!=)/)
  467. ns = name_or_ns
  468. name = interp_ident
  469. else
  470. name = name_or_ns
  471. end
  472. else
  473. # *|E or |E
  474. ns = [tok(/\*/) || ""]
  475. tok!(/\|/)
  476. name = expr!(:interp_ident)
  477. end
  478. return ns, name
  479. end
  480. def pseudo
  481. return unless s = tok(/::?/)
  482. @expected = "pseudoclass or pseudoelement"
  483. name = expr!(:interp_ident)
  484. if tok(/\(/)
  485. ss
  486. arg = expr!(:pseudo_expr)
  487. tok!(/\)/)
  488. end
  489. Selector::Pseudo.new(s == ':' ? :class : :element, merge(name), merge(arg))
  490. end
  491. def pseudo_expr
  492. return unless e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
  493. interp_string || tok(IDENT) || interpolation
  494. res = [e, str{ss}]
  495. while e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
  496. interp_string || tok(IDENT) || interpolation
  497. res << e << str{ss}
  498. end
  499. res
  500. end
  501. def negation
  502. return unless name = tok(NOT) || tok(MOZ_ANY)
  503. ss
  504. @expected = "selector"
  505. sel = selector_comma_sequence
  506. tok!(/\)/)
  507. Selector::SelectorPseudoClass.new(name[1...-1], sel)
  508. end
  509. def declaration
  510. # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
  511. if s = tok(/[:\*\.]|\#(?!\{)/)
  512. @use_property_exception = s !~ /[\.\#]/
  513. name = [s, str{ss}, *expr!(:interp_ident)]
  514. else
  515. return unless name = interp_ident
  516. name = [name] if name.is_a?(String)
  517. end
  518. if comment = tok(COMMENT)
  519. name << comment
  520. end
  521. ss
  522. tok!(/:/)
  523. space, value = value!
  524. ss
  525. require_block = tok?(/\{/)
  526. node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new))
  527. return node unless require_block
  528. nested_properties! node, space
  529. end
  530. def value!
  531. space = !str {ss}.empty?
  532. @use_property_exception ||= space || !tok?(IDENT)
  533. return true, Sass::Script::String.new("") if tok?(/\{/)
  534. # This is a bit of a dirty trick:
  535. # if the value is completely static,
  536. # we don't parse it at all, and instead return a plain old string
  537. # containing the value.
  538. # This results in a dramatic speed increase.
  539. if val = tok(STATIC_VALUE)
  540. return space, Sass::Script::String.new(val.strip)
  541. end
  542. return space, sass_script(:parse)
  543. end
  544. def plain_value
  545. return unless tok(/:/)
  546. space = !str {ss}.empty?
  547. @use_property_exception ||= space || !tok?(IDENT)
  548. expression = expr
  549. expression << tok(IMPORTANT) if expression
  550. # expression, space, value
  551. return expression, space, expression || [""]
  552. end
  553. def nested_properties!(node, space)
  554. raise Sass::SyntaxError.new(<<MESSAGE, :line => @line) unless space
  555. Invalid CSS: a space is required between a property and its definition
  556. when it has other properties nested beneath it.
  557. MESSAGE
  558. @use_property_exception = true
  559. @expected = 'expression (e.g. 1px, bold) or "{"'
  560. block(node, :property)
  561. end
  562. def expr
  563. return unless t = term
  564. res = [t, str{ss}]
  565. while (o = operator) && (t = term)
  566. res << o << t << str{ss}
  567. end
  568. res
  569. end
  570. def term
  571. unless e = tok(NUMBER) ||
  572. tok(URI) ||
  573. function ||
  574. tok(STRING) ||
  575. tok(UNICODERANGE) ||
  576. tok(IDENT) ||
  577. tok(HEXCOLOR)
  578. return unless op = unary_operator
  579. @expected = "number or function"
  580. return [op, tok(NUMBER) || expr!(:function)]
  581. end
  582. e
  583. end
  584. def function
  585. return unless name = tok(FUNCTION)
  586. if name == "expression(" || name == "calc("
  587. str, _ = Haml::Shared.balance(@scanner, ?(, ?), 1)
  588. [name, str]
  589. else
  590. [name, str{ss}, expr, tok!(/\)/)]
  591. end
  592. end
  593. def interpolation
  594. return unless tok(INTERP_START)
  595. sass_script(:parse_interpolated)
  596. end
  597. def interp_string
  598. _interp_string(:double) || _interp_string(:single)
  599. end
  600. def _interp_string(type)
  601. return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, false]])
  602. res = [start]
  603. mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, true]]
  604. # @scanner[2].empty? means we've started an interpolated section
  605. while @scanner[2] == '#{'
  606. @scanner.pos -= 2 # Don't consume the #{
  607. res.last.slice!(-2..-1)
  608. res << expr!(:interpolation) << tok(mid_re)
  609. end
  610. res
  611. end
  612. def interp_ident(start = IDENT)
  613. return unless val = tok(start) || interpolation
  614. res = [val]
  615. while val = tok(NAME) || interpolation
  616. res << val
  617. end
  618. res
  619. end
  620. def interp_name
  621. interp_ident NAME
  622. end
  623. def str
  624. @strs.push ""
  625. yield
  626. @strs.last
  627. ensure
  628. @strs.pop
  629. end
  630. def str?
  631. @strs.push ""
  632. yield && @strs.last
  633. ensure
  634. @strs.pop
  635. end
  636. def node(node)
  637. node.line = @line
  638. node
  639. end
  640. @sass_script_parser = Class.new(Sass::Script::Parser)
  641. @sass_script_parser.send(:include, ScriptParser)
  642. # @private
  643. def self.sass_script_parser; @sass_script_parser; end
  644. def sass_script(*args)
  645. parser = self.class.sass_script_parser.new(@scanner, @line,
  646. @scanner.pos - (@scanner.string[0...@scanner.pos].rindex("\n") || 0))
  647. result = parser.send(*args)
  648. @line = parser.line
  649. result
  650. end
  651. def merge(arr)
  652. arr && Haml::Util.merge_adjacent_strings([arr].flatten)
  653. end
  654. EXPR_NAMES = {
  655. :media_query => "media query (e.g. print, screen, print and screen)",
  656. :media_expr => "media expression (e.g. (min-device-width: 800px)))",
  657. :pseudo_expr => "expression (e.g. fr, 2n+1)",
  658. :interp_ident => "identifier",
  659. :interp_name => "identifier",
  660. :expr => "expression (e.g. 1px, bold)",
  661. :selector_comma_sequence => "selector",
  662. :simple_selector_sequence => "selector",
  663. :import_arg => "file to import (string or url())",
  664. }
  665. TOK_NAMES = Haml::Util.to_hash(
  666. Sass::SCSS::RX.constants.map {|c| [Sass::SCSS::RX.const_get(c), c.downcase]}).
  667. merge(IDENT => "identifier", /[;}]/ => '";"')
  668. def tok?(rx)
  669. @scanner.match?(rx)
  670. end
  671. def expr!(name)
  672. (e = send(name)) && (return e)
  673. expected(EXPR_NAMES[name] || name.to_s)
  674. end
  675. def tok!(rx)
  676. (t = tok(rx)) && (return t)
  677. name = TOK_NAMES[rx]
  678. unless name
  679. # Display basic regexps as plain old strings
  680. string = rx.source.gsub(/\\(.)/, '\1')
  681. name = rx.source == Regexp.escape(string) ? string.inspect : rx.inspect
  682. end
  683. expected(name)
  684. end
  685. def expected(name)
  686. self.class.expected(@scanner, @expected || name, @line)
  687. end
  688. # @private
  689. def self.expected(scanner, expected, line)
  690. pos = scanner.pos
  691. after = scanner.string[0...pos]
  692. # Get rid of whitespace between pos and the last token,
  693. # but only if there's a newline in there
  694. after.gsub!(/\s*\n\s*$/, '')
  695. # Also get rid of stuff before the last newline
  696. after.gsub!(/.*\n/, '')
  697. after = "..." + after[-15..-1] if after.size > 18
  698. was = scanner.rest.dup
  699. # Get rid of whitespace between pos and the next token,
  700. # but only if there's a newline in there
  701. was.gsub!(/^\s*\n\s*/, '')
  702. # Also get rid of stuff after the next newline
  703. was.gsub!(/\n.*/, '')
  704. was = was[0...15] + "..." if was.size > 18
  705. raise Sass::SyntaxError.new(
  706. "Invalid CSS after \"#{after}\": expected #{expected}, was \"#{was}\"",
  707. :line => line)
  708. end
  709. def tok(rx)
  710. res = @scanner.scan(rx)
  711. if res
  712. @line += res.count("\n")
  713. @expected = nil
  714. if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
  715. @strs.each {|s| s << res}
  716. end
  717. end
  718. res
  719. end
  720. end
  721. end
  722. end