PageRenderTime 63ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/vendor/bundle/ruby/1.9.1/gems/sass-3.1.15/lib/sass/scss/parser.rb

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