oreorenasass 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +11 -0
  3. data/CONTRIBUTING +3 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +221 -0
  6. data/Rakefile +370 -0
  7. data/VERSION +1 -0
  8. data/VERSION_NAME +1 -0
  9. data/bin/sass +13 -0
  10. data/bin/sass-convert +12 -0
  11. data/bin/scss +13 -0
  12. data/extra/update_watch.rb +13 -0
  13. data/init.rb +18 -0
  14. data/lib/sass/cache_stores/base.rb +88 -0
  15. data/lib/sass/cache_stores/chain.rb +34 -0
  16. data/lib/sass/cache_stores/filesystem.rb +60 -0
  17. data/lib/sass/cache_stores/memory.rb +47 -0
  18. data/lib/sass/cache_stores/null.rb +25 -0
  19. data/lib/sass/cache_stores.rb +15 -0
  20. data/lib/sass/callbacks.rb +67 -0
  21. data/lib/sass/css.rb +407 -0
  22. data/lib/sass/engine.rb +1181 -0
  23. data/lib/sass/environment.rb +191 -0
  24. data/lib/sass/error.rb +198 -0
  25. data/lib/sass/exec/base.rb +187 -0
  26. data/lib/sass/exec/sass_convert.rb +264 -0
  27. data/lib/sass/exec/sass_scss.rb +424 -0
  28. data/lib/sass/exec.rb +9 -0
  29. data/lib/sass/features.rb +47 -0
  30. data/lib/sass/importers/base.rb +182 -0
  31. data/lib/sass/importers/filesystem.rb +211 -0
  32. data/lib/sass/importers.rb +22 -0
  33. data/lib/sass/logger/base.rb +30 -0
  34. data/lib/sass/logger/log_level.rb +45 -0
  35. data/lib/sass/logger.rb +12 -0
  36. data/lib/sass/media.rb +210 -0
  37. data/lib/sass/plugin/compiler.rb +565 -0
  38. data/lib/sass/plugin/configuration.rb +118 -0
  39. data/lib/sass/plugin/generic.rb +15 -0
  40. data/lib/sass/plugin/merb.rb +48 -0
  41. data/lib/sass/plugin/rack.rb +60 -0
  42. data/lib/sass/plugin/rails.rb +47 -0
  43. data/lib/sass/plugin/staleness_checker.rb +199 -0
  44. data/lib/sass/plugin.rb +133 -0
  45. data/lib/sass/railtie.rb +10 -0
  46. data/lib/sass/repl.rb +57 -0
  47. data/lib/sass/root.rb +7 -0
  48. data/lib/sass/script/css_lexer.rb +33 -0
  49. data/lib/sass/script/css_parser.rb +34 -0
  50. data/lib/sass/script/functions.rb +2626 -0
  51. data/lib/sass/script/lexer.rb +449 -0
  52. data/lib/sass/script/parser.rb +637 -0
  53. data/lib/sass/script/tree/funcall.rb +306 -0
  54. data/lib/sass/script/tree/interpolation.rb +118 -0
  55. data/lib/sass/script/tree/list_literal.rb +77 -0
  56. data/lib/sass/script/tree/literal.rb +45 -0
  57. data/lib/sass/script/tree/map_literal.rb +64 -0
  58. data/lib/sass/script/tree/node.rb +109 -0
  59. data/lib/sass/script/tree/operation.rb +103 -0
  60. data/lib/sass/script/tree/selector.rb +26 -0
  61. data/lib/sass/script/tree/string_interpolation.rb +104 -0
  62. data/lib/sass/script/tree/unary_operation.rb +69 -0
  63. data/lib/sass/script/tree/variable.rb +57 -0
  64. data/lib/sass/script/tree.rb +16 -0
  65. data/lib/sass/script/value/arg_list.rb +36 -0
  66. data/lib/sass/script/value/base.rb +240 -0
  67. data/lib/sass/script/value/bool.rb +35 -0
  68. data/lib/sass/script/value/color.rb +680 -0
  69. data/lib/sass/script/value/helpers.rb +262 -0
  70. data/lib/sass/script/value/list.rb +113 -0
  71. data/lib/sass/script/value/map.rb +70 -0
  72. data/lib/sass/script/value/null.rb +44 -0
  73. data/lib/sass/script/value/number.rb +530 -0
  74. data/lib/sass/script/value/string.rb +97 -0
  75. data/lib/sass/script/value.rb +11 -0
  76. data/lib/sass/script.rb +66 -0
  77. data/lib/sass/scss/css_parser.rb +42 -0
  78. data/lib/sass/scss/parser.rb +1209 -0
  79. data/lib/sass/scss/rx.rb +141 -0
  80. data/lib/sass/scss/script_lexer.rb +15 -0
  81. data/lib/sass/scss/script_parser.rb +25 -0
  82. data/lib/sass/scss/static_parser.rb +368 -0
  83. data/lib/sass/scss.rb +16 -0
  84. data/lib/sass/selector/abstract_sequence.rb +109 -0
  85. data/lib/sass/selector/comma_sequence.rb +175 -0
  86. data/lib/sass/selector/pseudo.rb +256 -0
  87. data/lib/sass/selector/sequence.rb +600 -0
  88. data/lib/sass/selector/simple.rb +117 -0
  89. data/lib/sass/selector/simple_sequence.rb +325 -0
  90. data/lib/sass/selector.rb +326 -0
  91. data/lib/sass/shared.rb +76 -0
  92. data/lib/sass/source/map.rb +210 -0
  93. data/lib/sass/source/position.rb +39 -0
  94. data/lib/sass/source/range.rb +41 -0
  95. data/lib/sass/stack.rb +120 -0
  96. data/lib/sass/supports.rb +227 -0
  97. data/lib/sass/tree/at_root_node.rb +83 -0
  98. data/lib/sass/tree/charset_node.rb +22 -0
  99. data/lib/sass/tree/comment_node.rb +82 -0
  100. data/lib/sass/tree/content_node.rb +9 -0
  101. data/lib/sass/tree/css_import_node.rb +60 -0
  102. data/lib/sass/tree/debug_node.rb +18 -0
  103. data/lib/sass/tree/directive_node.rb +59 -0
  104. data/lib/sass/tree/each_node.rb +24 -0
  105. data/lib/sass/tree/error_node.rb +18 -0
  106. data/lib/sass/tree/extend_node.rb +43 -0
  107. data/lib/sass/tree/for_node.rb +36 -0
  108. data/lib/sass/tree/function_node.rb +39 -0
  109. data/lib/sass/tree/if_node.rb +52 -0
  110. data/lib/sass/tree/import_node.rb +74 -0
  111. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  112. data/lib/sass/tree/media_node.rb +48 -0
  113. data/lib/sass/tree/mixin_def_node.rb +38 -0
  114. data/lib/sass/tree/mixin_node.rb +52 -0
  115. data/lib/sass/tree/node.rb +238 -0
  116. data/lib/sass/tree/prop_node.rb +171 -0
  117. data/lib/sass/tree/return_node.rb +19 -0
  118. data/lib/sass/tree/root_node.rb +44 -0
  119. data/lib/sass/tree/rule_node.rb +145 -0
  120. data/lib/sass/tree/supports_node.rb +38 -0
  121. data/lib/sass/tree/trace_node.rb +33 -0
  122. data/lib/sass/tree/variable_node.rb +36 -0
  123. data/lib/sass/tree/visitors/base.rb +72 -0
  124. data/lib/sass/tree/visitors/check_nesting.rb +177 -0
  125. data/lib/sass/tree/visitors/convert.rb +334 -0
  126. data/lib/sass/tree/visitors/cssize.rb +369 -0
  127. data/lib/sass/tree/visitors/deep_copy.rb +107 -0
  128. data/lib/sass/tree/visitors/extend.rb +68 -0
  129. data/lib/sass/tree/visitors/perform.rb +539 -0
  130. data/lib/sass/tree/visitors/set_options.rb +139 -0
  131. data/lib/sass/tree/visitors/to_css.rb +381 -0
  132. data/lib/sass/tree/warn_node.rb +18 -0
  133. data/lib/sass/tree/while_node.rb +18 -0
  134. data/lib/sass/util/cross_platform_random.rb +19 -0
  135. data/lib/sass/util/multibyte_string_scanner.rb +157 -0
  136. data/lib/sass/util/normalized_map.rb +130 -0
  137. data/lib/sass/util/ordered_hash.rb +192 -0
  138. data/lib/sass/util/subset_map.rb +110 -0
  139. data/lib/sass/util/test.rb +9 -0
  140. data/lib/sass/util.rb +1318 -0
  141. data/lib/sass/version.rb +124 -0
  142. data/lib/sass.rb +102 -0
  143. data/rails/init.rb +1 -0
  144. data/test/sass/cache_test.rb +131 -0
  145. data/test/sass/callbacks_test.rb +61 -0
  146. data/test/sass/compiler_test.rb +232 -0
  147. data/test/sass/conversion_test.rb +2054 -0
  148. data/test/sass/css2sass_test.rb +477 -0
  149. data/test/sass/data/hsl-rgb.txt +319 -0
  150. data/test/sass/encoding_test.rb +219 -0
  151. data/test/sass/engine_test.rb +3301 -0
  152. data/test/sass/exec_test.rb +86 -0
  153. data/test/sass/extend_test.rb +1661 -0
  154. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  155. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  156. data/test/sass/functions_test.rb +1926 -0
  157. data/test/sass/importer_test.rb +412 -0
  158. data/test/sass/logger_test.rb +58 -0
  159. data/test/sass/mock_importer.rb +49 -0
  160. data/test/sass/more_results/more1.css +9 -0
  161. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  162. data/test/sass/more_results/more_import.css +29 -0
  163. data/test/sass/more_templates/_more_partial.sass +2 -0
  164. data/test/sass/more_templates/more1.sass +23 -0
  165. data/test/sass/more_templates/more_import.sass +11 -0
  166. data/test/sass/plugin_test.rb +554 -0
  167. data/test/sass/results/alt.css +4 -0
  168. data/test/sass/results/basic.css +9 -0
  169. data/test/sass/results/cached_import_option.css +3 -0
  170. data/test/sass/results/compact.css +5 -0
  171. data/test/sass/results/complex.css +86 -0
  172. data/test/sass/results/compressed.css +1 -0
  173. data/test/sass/results/expanded.css +19 -0
  174. data/test/sass/results/filename_fn.css +3 -0
  175. data/test/sass/results/if.css +3 -0
  176. data/test/sass/results/import.css +31 -0
  177. data/test/sass/results/import_charset.css +5 -0
  178. data/test/sass/results/import_charset_1_8.css +5 -0
  179. data/test/sass/results/import_charset_ibm866.css +5 -0
  180. data/test/sass/results/import_content.css +1 -0
  181. data/test/sass/results/line_numbers.css +49 -0
  182. data/test/sass/results/mixins.css +95 -0
  183. data/test/sass/results/multiline.css +24 -0
  184. data/test/sass/results/nested.css +22 -0
  185. data/test/sass/results/options.css +1 -0
  186. data/test/sass/results/parent_ref.css +13 -0
  187. data/test/sass/results/script.css +16 -0
  188. data/test/sass/results/scss_import.css +31 -0
  189. data/test/sass/results/scss_importee.css +2 -0
  190. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  191. data/test/sass/results/subdir/subdir.css +3 -0
  192. data/test/sass/results/units.css +11 -0
  193. data/test/sass/results/warn.css +0 -0
  194. data/test/sass/results/warn_imported.css +0 -0
  195. data/test/sass/script_conversion_test.rb +328 -0
  196. data/test/sass/script_test.rb +1054 -0
  197. data/test/sass/scss/css_test.rb +1215 -0
  198. data/test/sass/scss/rx_test.rb +156 -0
  199. data/test/sass/scss/scss_test.rb +3900 -0
  200. data/test/sass/scss/test_helper.rb +37 -0
  201. data/test/sass/source_map_test.rb +977 -0
  202. data/test/sass/superselector_test.rb +191 -0
  203. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  204. data/test/sass/templates/_double_import_loop2.sass +1 -0
  205. data/test/sass/templates/_filename_fn_import.scss +11 -0
  206. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  207. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  208. data/test/sass/templates/_imported_content.sass +3 -0
  209. data/test/sass/templates/_partial.sass +2 -0
  210. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  211. data/test/sass/templates/alt.sass +16 -0
  212. data/test/sass/templates/basic.sass +23 -0
  213. data/test/sass/templates/bork1.sass +2 -0
  214. data/test/sass/templates/bork2.sass +2 -0
  215. data/test/sass/templates/bork3.sass +2 -0
  216. data/test/sass/templates/bork4.sass +2 -0
  217. data/test/sass/templates/bork5.sass +3 -0
  218. data/test/sass/templates/cached_import_option.scss +3 -0
  219. data/test/sass/templates/compact.sass +17 -0
  220. data/test/sass/templates/complex.sass +305 -0
  221. data/test/sass/templates/compressed.sass +15 -0
  222. data/test/sass/templates/double_import_loop1.sass +1 -0
  223. data/test/sass/templates/expanded.sass +17 -0
  224. data/test/sass/templates/filename_fn.scss +18 -0
  225. data/test/sass/templates/if.sass +11 -0
  226. data/test/sass/templates/import.sass +12 -0
  227. data/test/sass/templates/import_charset.sass +9 -0
  228. data/test/sass/templates/import_charset_1_8.sass +6 -0
  229. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  230. data/test/sass/templates/import_content.sass +4 -0
  231. data/test/sass/templates/importee.less +2 -0
  232. data/test/sass/templates/importee.sass +19 -0
  233. data/test/sass/templates/line_numbers.sass +13 -0
  234. data/test/sass/templates/mixin_bork.sass +5 -0
  235. data/test/sass/templates/mixins.sass +76 -0
  236. data/test/sass/templates/multiline.sass +20 -0
  237. data/test/sass/templates/nested.sass +25 -0
  238. data/test/sass/templates/nested_bork1.sass +2 -0
  239. data/test/sass/templates/nested_bork2.sass +2 -0
  240. data/test/sass/templates/nested_bork3.sass +2 -0
  241. data/test/sass/templates/nested_bork4.sass +2 -0
  242. data/test/sass/templates/nested_import.sass +2 -0
  243. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  244. data/test/sass/templates/options.sass +2 -0
  245. data/test/sass/templates/parent_ref.sass +25 -0
  246. data/test/sass/templates/same_name_different_ext.sass +2 -0
  247. data/test/sass/templates/same_name_different_ext.scss +1 -0
  248. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  249. data/test/sass/templates/script.sass +101 -0
  250. data/test/sass/templates/scss_import.scss +12 -0
  251. data/test/sass/templates/scss_importee.scss +1 -0
  252. data/test/sass/templates/single_import_loop.sass +1 -0
  253. data/test/sass/templates/subdir/import_up1.scss +1 -0
  254. data/test/sass/templates/subdir/import_up2.scss +1 -0
  255. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  256. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  257. data/test/sass/templates/subdir/subdir.sass +6 -0
  258. data/test/sass/templates/units.sass +11 -0
  259. data/test/sass/templates/warn.sass +3 -0
  260. data/test/sass/templates/warn_imported.sass +4 -0
  261. data/test/sass/test_helper.rb +8 -0
  262. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  263. data/test/sass/util/normalized_map_test.rb +51 -0
  264. data/test/sass/util/subset_map_test.rb +91 -0
  265. data/test/sass/util_test.rb +467 -0
  266. data/test/sass/value_helpers_test.rb +179 -0
  267. data/test/test_helper.rb +109 -0
  268. metadata +386 -0
@@ -0,0 +1,97 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Sass::Script::Value
3
+ # A SassScript object representing a CSS string *or* a CSS identifier.
4
+ class String < Base
5
+ # The Ruby value of the string.
6
+ #
7
+ # @return [String]
8
+ attr_reader :value
9
+
10
+ # Whether this is a CSS string or a CSS identifier.
11
+ # The difference is that strings are written with double-quotes,
12
+ # while identifiers aren't.
13
+ #
14
+ # @return [Symbol] `:string` or `:identifier`
15
+ attr_reader :type
16
+
17
+ def self.value(contents)
18
+ contents.gsub("\\\n", "").gsub(/\\(?:([0-9a-fA-F]{1,6})\s?|(.))/) do
19
+ next $2 if $2
20
+ # Handle unicode escapes as per CSS Syntax Level 3 section 4.3.8.
21
+ code_point = $1.to_i(16)
22
+ if code_point == 0 || code_point > 0x10FFFF ||
23
+ (code_point >= 0xD800 && code_point <= 0xDFFF)
24
+ '�'
25
+ else
26
+ [code_point].pack("U")
27
+ end
28
+ end
29
+ end
30
+
31
+ def self.quote(contents, quote = nil)
32
+ # Short-circuit if there are no characters that need quoting.
33
+ unless contents =~ /[\n\\"']/
34
+ quote ||= '"'
35
+ return "#{quote}#{contents}#{quote}"
36
+ end
37
+
38
+ if quote.nil?
39
+ if contents.include?('"')
40
+ if contents.include?("'")
41
+ quote = '"'
42
+ else
43
+ quote = "'"
44
+ end
45
+ else
46
+ quote = '"'
47
+ end
48
+ end
49
+
50
+ # Replace single backslashes with multiples.
51
+ contents = contents.gsub("\\", "\\\\\\\\")
52
+
53
+ if quote == '"'
54
+ contents = contents.gsub('"', "\\\"")
55
+ else
56
+ contents = contents.gsub("'", "\\'")
57
+ end
58
+
59
+ contents = contents.gsub(/\n(?![a-fA-F0-9\s])/, "\\a").gsub("\n", "\\a ")
60
+ "#{quote}#{contents}#{quote}"
61
+ end
62
+
63
+ # Creates a new string.
64
+ #
65
+ # @param value [String] See \{#value}
66
+ # @param type [Symbol] See \{#type}
67
+ def initialize(value, type = :identifier)
68
+ super(value)
69
+ @type = type
70
+ end
71
+
72
+ # @see Value#plus
73
+ def plus(other)
74
+ other_value = if other.is_a?(Sass::Script::Value::String)
75
+ other.value
76
+ else
77
+ other.to_s(:quote => :none)
78
+ end
79
+ Sass::Script::Value::String.new(value + other_value, type)
80
+ end
81
+
82
+ # @see Value#to_s
83
+ def to_s(opts = {})
84
+ return @value.gsub(/\n\s*/, ' ') if opts[:quote] == :none || @type == :identifier
85
+ Sass::Script::Value::String.quote(value, opts[:quote])
86
+ end
87
+
88
+ # @see Value#to_sass
89
+ def to_sass(opts = {})
90
+ to_s
91
+ end
92
+
93
+ def inspect
94
+ String.quote(value)
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,11 @@
1
+ module Sass::Script::Value; end
2
+
3
+ require 'sass/script/value/base'
4
+ require 'sass/script/value/string'
5
+ require 'sass/script/value/number'
6
+ require 'sass/script/value/color'
7
+ require 'sass/script/value/bool'
8
+ require 'sass/script/value/null'
9
+ require 'sass/script/value/list'
10
+ require 'sass/script/value/arg_list'
11
+ require 'sass/script/value/map'
@@ -0,0 +1,66 @@
1
+ require 'sass/scss/rx'
2
+
3
+ module Sass
4
+ # SassScript is code that's embedded in Sass documents
5
+ # to allow for property values to be computed from variables.
6
+ #
7
+ # This module contains code that handles the parsing and evaluation of SassScript.
8
+ module Script
9
+ # The regular expression used to parse variables.
10
+ MATCH = /^\$(#{Sass::SCSS::RX::IDENT})\s*:\s*(.+?)
11
+ (!#{Sass::SCSS::RX::IDENT}(?:\s+!#{Sass::SCSS::RX::IDENT})*)?$/x
12
+
13
+ # The regular expression used to validate variables without matching.
14
+ VALIDATE = /^\$#{Sass::SCSS::RX::IDENT}$/
15
+
16
+ # Parses a string of SassScript
17
+ #
18
+ # @param value [String] The SassScript
19
+ # @param line [Fixnum] The number of the line on which the SassScript appeared.
20
+ # Used for error reporting
21
+ # @param offset [Fixnum] The number of characters in on `line` that the SassScript started.
22
+ # Used for error reporting
23
+ # @param options [{Symbol => Object}] An options hash;
24
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
25
+ # @return [Script::Tree::Node] The root node of the parse tree
26
+ def self.parse(value, line, offset, options = {})
27
+ Parser.parse(value, line, offset, options)
28
+ rescue Sass::SyntaxError => e
29
+ e.message << ": #{value.inspect}." if e.message == "SassScript error"
30
+ e.modify_backtrace(:line => line, :filename => options[:filename])
31
+ raise e
32
+ end
33
+
34
+ require 'sass/script/functions'
35
+ require 'sass/script/parser'
36
+ require 'sass/script/tree'
37
+ require 'sass/script/value'
38
+
39
+ # @private
40
+ CONST_RENAMES = {
41
+ :Literal => Sass::Script::Value::Base,
42
+ :ArgList => Sass::Script::Value::ArgList,
43
+ :Bool => Sass::Script::Value::Bool,
44
+ :Color => Sass::Script::Value::Color,
45
+ :List => Sass::Script::Value::List,
46
+ :Null => Sass::Script::Value::Null,
47
+ :Number => Sass::Script::Value::Number,
48
+ :String => Sass::Script::Value::String,
49
+ :Node => Sass::Script::Tree::Node,
50
+ :Funcall => Sass::Script::Tree::Funcall,
51
+ :Interpolation => Sass::Script::Tree::Interpolation,
52
+ :Operation => Sass::Script::Tree::Operation,
53
+ :StringInterpolation => Sass::Script::Tree::StringInterpolation,
54
+ :UnaryOperation => Sass::Script::Tree::UnaryOperation,
55
+ :Variable => Sass::Script::Tree::Variable,
56
+ }
57
+
58
+ # @private
59
+ def self.const_missing(name)
60
+ klass = CONST_RENAMES[name]
61
+ super unless klass
62
+ CONST_RENAMES.each {|n, k| const_set(n, k)}
63
+ klass
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,42 @@
1
+ require 'sass/script/css_parser'
2
+
3
+ module Sass
4
+ module SCSS
5
+ # This is a subclass of {Parser} which only parses plain CSS.
6
+ # It doesn't support any Sass extensions, such as interpolation,
7
+ # parent references, nested selectors, and so forth.
8
+ # It does support all the same CSS hacks as the SCSS parser, though.
9
+ class CssParser < StaticParser
10
+ private
11
+
12
+ def placeholder_selector; nil; end
13
+ def parent_selector; nil; end
14
+ def interpolation(warn_for_color = false); nil; end
15
+ def use_css_import?; true; end
16
+
17
+ def block_child(context)
18
+ case context
19
+ when :ruleset
20
+ declaration
21
+ when :stylesheet
22
+ directive || ruleset
23
+ when :directive
24
+ directive || declaration_or_ruleset
25
+ end
26
+ end
27
+
28
+ def nested_properties!(node)
29
+ expected('expression (e.g. 1px, bold)')
30
+ end
31
+
32
+ def ruleset
33
+ start_pos = source_position
34
+ return unless (selector = selector_comma_sequence)
35
+ block(node(Sass::Tree::RuleNode.new(selector, range(start_pos)), start_pos), :ruleset)
36
+ end
37
+
38
+ @sass_script_parser = Class.new(Sass::Script::CssParser)
39
+ @sass_script_parser.send(:include, ScriptParser)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,1209 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'set'
3
+
4
+ module Sass
5
+ module SCSS
6
+ # The parser for SCSS.
7
+ # It parses a string of code into a tree of {Sass::Tree::Node}s.
8
+ class Parser
9
+ # Expose for the SASS parser.
10
+ attr_accessor :offset
11
+
12
+ # @param str [String, StringScanner] The source document to parse.
13
+ # Note that `Parser` *won't* raise a nice error message if this isn't properly parsed;
14
+ # for that, you should use the higher-level {Sass::Engine} or {Sass::CSS}.
15
+ # @param filename [String] The name of the file being parsed. Used for
16
+ # warnings and source maps.
17
+ # @param importer [Sass::Importers::Base] The importer used to import the
18
+ # file being parsed. Used for source maps.
19
+ # @param line [Fixnum] The 1-based line on which the source string appeared,
20
+ # if it's part of another document.
21
+ # @param offset [Fixnum] The 1-based character (not byte) offset in the line on
22
+ # which the source string starts. Used for error reporting and sourcemap
23
+ # building.
24
+ # @comment
25
+ # rubocop:disable ParameterLists
26
+ def initialize(str, filename, importer, line = 1, offset = 1)
27
+ # rubocop:enable ParameterLists
28
+ @template = str
29
+ @filename = filename
30
+ @importer = importer
31
+ @line = line
32
+ @offset = offset
33
+ @strs = []
34
+ end
35
+
36
+ # Parses an SCSS document.
37
+ #
38
+ # @return [Sass::Tree::RootNode] The root node of the document tree
39
+ # @raise [Sass::SyntaxError] if there's a syntax error in the document
40
+ def parse
41
+ init_scanner!
42
+ root = stylesheet
43
+ expected("selector or at-rule") unless root && @scanner.eos?
44
+ root
45
+ end
46
+
47
+ # Parses an identifier with interpolation.
48
+ # Note that this won't assert that the identifier takes up the entire input string;
49
+ # it's meant to be used with `StringScanner`s as part of other parsers.
50
+ #
51
+ # @return [Array<String, Sass::Script::Tree::Node>, nil]
52
+ # The interpolated identifier, or nil if none could be parsed
53
+ def parse_interp_ident
54
+ init_scanner!
55
+ interp_ident
56
+ end
57
+
58
+ # Parses a media query list.
59
+ #
60
+ # @return [Sass::Media::QueryList] The parsed query list
61
+ # @raise [Sass::SyntaxError] if there's a syntax error in the query list,
62
+ # or if it doesn't take up the entire input string.
63
+ def parse_media_query_list
64
+ init_scanner!
65
+ ql = media_query_list
66
+ expected("media query list") unless ql && @scanner.eos?
67
+ ql
68
+ end
69
+
70
+ # Parses an at-root query.
71
+ #
72
+ # @return [Array<String, Sass::Script;:Tree::Node>] The interpolated query.
73
+ # @raise [Sass::SyntaxError] if there's a syntax error in the query,
74
+ # or if it doesn't take up the entire input string.
75
+ def parse_at_root_query
76
+ init_scanner!
77
+ query = at_root_query
78
+ expected("@at-root query list") unless query && @scanner.eos?
79
+ query
80
+ end
81
+
82
+ # Parses a supports query condition.
83
+ #
84
+ # @return [Sass::Supports::Condition] The parsed condition
85
+ # @raise [Sass::SyntaxError] if there's a syntax error in the condition,
86
+ # or if it doesn't take up the entire input string.
87
+ def parse_supports_condition
88
+ init_scanner!
89
+ condition = supports_condition
90
+ expected("supports condition") unless condition && @scanner.eos?
91
+ condition
92
+ end
93
+
94
+ private
95
+
96
+ include Sass::SCSS::RX
97
+
98
+ def source_position
99
+ Sass::Source::Position.new(@line, @offset)
100
+ end
101
+
102
+ def range(start_pos, end_pos = source_position)
103
+ Sass::Source::Range.new(start_pos, end_pos, @filename, @importer)
104
+ end
105
+
106
+ def init_scanner!
107
+ @scanner =
108
+ if @template.is_a?(StringScanner)
109
+ @template
110
+ else
111
+ Sass::Util::MultibyteStringScanner.new(@template.gsub("\r", ""))
112
+ end
113
+ end
114
+
115
+ def stylesheet
116
+ node = node(Sass::Tree::RootNode.new(@scanner.string), source_position)
117
+ block_contents(node, :stylesheet) {s(node)}
118
+ end
119
+
120
+ def s(node)
121
+ while tok(S) || tok(CDC) || tok(CDO) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
122
+ next unless c
123
+ process_comment c, node
124
+ c = nil
125
+ end
126
+ true
127
+ end
128
+
129
+ def ss
130
+ nil while tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
131
+ true
132
+ end
133
+
134
+ def ss_comments(node)
135
+ while tok(S) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
136
+ next unless c
137
+ process_comment c, node
138
+ c = nil
139
+ end
140
+
141
+ true
142
+ end
143
+
144
+ def whitespace
145
+ return unless tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
146
+ ss
147
+ end
148
+
149
+ def process_comment(text, node)
150
+ silent = text =~ %r{\A//}
151
+ loud = !silent && text =~ %r{\A/[/*]!}
152
+ line = @line - text.count("\n")
153
+
154
+ if silent
155
+ value = [text.sub(%r{\A\s*//}, '/*').gsub(%r{^\s*//}, ' *') + ' */']
156
+ else
157
+ value = Sass::Engine.parse_interp(
158
+ text, line, @scanner.pos - text.size, :filename => @filename)
159
+ string_before_comment = @scanner.string[0...@scanner.pos - text.length]
160
+ newline_before_comment = string_before_comment.rindex("\n")
161
+ last_line_before_comment =
162
+ if newline_before_comment
163
+ string_before_comment[newline_before_comment + 1..-1]
164
+ else
165
+ string_before_comment
166
+ end
167
+ value.unshift(last_line_before_comment.gsub(/[^\s]/, ' '))
168
+ end
169
+
170
+ type = if silent
171
+ :silent
172
+ elsif loud
173
+ :loud
174
+ else
175
+ :normal
176
+ end
177
+ comment = Sass::Tree::CommentNode.new(value, type)
178
+ comment.line = line
179
+ node << comment
180
+ end
181
+
182
+ DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
183
+ :each, :while, :if, :else, :extend, :import, :media, :charset, :content,
184
+ :_moz_document, :at_root, :error]
185
+
186
+ PREFIXED_DIRECTIVES = Set[:supports]
187
+
188
+ def directive
189
+ start_pos = source_position
190
+ return unless tok(/@/)
191
+ name = tok!(IDENT)
192
+ ss
193
+
194
+ if (dir = special_directive(name, start_pos))
195
+ return dir
196
+ elsif (dir = prefixed_directive(name, start_pos))
197
+ return dir
198
+ end
199
+
200
+ val = almost_any_value
201
+ val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"]
202
+ directive_body(val, start_pos)
203
+ end
204
+
205
+ def directive_body(value, start_pos)
206
+ node = Sass::Tree::DirectiveNode.new(value)
207
+
208
+ if tok(/\{/)
209
+ node.has_children = true
210
+ block_contents(node, :directive)
211
+ tok!(/\}/)
212
+ end
213
+
214
+ node(node, start_pos)
215
+ end
216
+
217
+ def special_directive(name, start_pos)
218
+ sym = name.gsub('-', '_').to_sym
219
+ DIRECTIVES.include?(sym) && send("#{sym}_directive", start_pos)
220
+ end
221
+
222
+ def prefixed_directive(name, start_pos)
223
+ sym = deprefix(name).gsub('-', '_').to_sym
224
+ PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name, start_pos)
225
+ end
226
+
227
+ def mixin_directive(start_pos)
228
+ name = tok! IDENT
229
+ args, splat = sass_script(:parse_mixin_definition_arglist)
230
+ ss
231
+ block(node(Sass::Tree::MixinDefNode.new(name, args, splat), start_pos), :directive)
232
+ end
233
+
234
+ def include_directive(start_pos)
235
+ name = tok! IDENT
236
+ args, keywords, splat, kwarg_splat = sass_script(:parse_mixin_include_arglist)
237
+ ss
238
+ include_node = node(
239
+ Sass::Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat), start_pos)
240
+ if tok?(/\{/)
241
+ include_node.has_children = true
242
+ block(include_node, :directive)
243
+ else
244
+ include_node
245
+ end
246
+ end
247
+
248
+ def content_directive(start_pos)
249
+ ss
250
+ node(Sass::Tree::ContentNode.new, start_pos)
251
+ end
252
+
253
+ def function_directive(start_pos)
254
+ name = tok! IDENT
255
+ args, splat = sass_script(:parse_function_definition_arglist)
256
+ ss
257
+ block(node(Sass::Tree::FunctionNode.new(name, args, splat), start_pos), :function)
258
+ end
259
+
260
+ def return_directive(start_pos)
261
+ node(Sass::Tree::ReturnNode.new(sass_script(:parse)), start_pos)
262
+ end
263
+
264
+ def debug_directive(start_pos)
265
+ node(Sass::Tree::DebugNode.new(sass_script(:parse)), start_pos)
266
+ end
267
+
268
+ def warn_directive(start_pos)
269
+ node(Sass::Tree::WarnNode.new(sass_script(:parse)), start_pos)
270
+ end
271
+
272
+ def for_directive(start_pos)
273
+ tok!(/\$/)
274
+ var = tok! IDENT
275
+ ss
276
+
277
+ tok!(/from/)
278
+ from = sass_script(:parse_until, Set["to", "through"])
279
+ ss
280
+
281
+ @expected = '"to" or "through"'
282
+ exclusive = (tok(/to/) || tok!(/through/)) == 'to'
283
+ to = sass_script(:parse)
284
+ ss
285
+
286
+ block(node(Sass::Tree::ForNode.new(var, from, to, exclusive), start_pos), :directive)
287
+ end
288
+
289
+ def each_directive(start_pos)
290
+ tok!(/\$/)
291
+ vars = [tok!(IDENT)]
292
+ ss
293
+ while tok(/,/)
294
+ ss
295
+ tok!(/\$/)
296
+ vars << tok!(IDENT)
297
+ ss
298
+ end
299
+
300
+ tok!(/in/)
301
+ list = sass_script(:parse)
302
+ ss
303
+
304
+ block(node(Sass::Tree::EachNode.new(vars, list), start_pos), :directive)
305
+ end
306
+
307
+ def while_directive(start_pos)
308
+ expr = sass_script(:parse)
309
+ ss
310
+ block(node(Sass::Tree::WhileNode.new(expr), start_pos), :directive)
311
+ end
312
+
313
+ def if_directive(start_pos)
314
+ expr = sass_script(:parse)
315
+ ss
316
+ node = block(node(Sass::Tree::IfNode.new(expr), start_pos), :directive)
317
+ pos = @scanner.pos
318
+ line = @line
319
+ ss
320
+
321
+ else_block(node) ||
322
+ begin
323
+ # Backtrack in case there are any comments we want to parse
324
+ @scanner.pos = pos
325
+ @line = line
326
+ node
327
+ end
328
+ end
329
+
330
+ def else_block(node)
331
+ start_pos = source_position
332
+ return unless tok(/@else/)
333
+ ss
334
+ else_node = block(
335
+ node(Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))), start_pos),
336
+ :directive)
337
+ node.add_else(else_node)
338
+ pos = @scanner.pos
339
+ line = @line
340
+ ss
341
+
342
+ else_block(node) ||
343
+ begin
344
+ # Backtrack in case there are any comments we want to parse
345
+ @scanner.pos = pos
346
+ @line = line
347
+ node
348
+ end
349
+ end
350
+
351
+ def else_directive(start_pos)
352
+ err("Invalid CSS: @else must come after @if")
353
+ end
354
+
355
+ def extend_directive(start_pos)
356
+ selector_start_pos = source_position
357
+ @expected = "selector"
358
+ selector = Sass::Util.strip_string_array(expr!(:almost_any_value))
359
+ optional = tok(OPTIONAL)
360
+ ss
361
+ node(Sass::Tree::ExtendNode.new(selector, !!optional, range(selector_start_pos)), start_pos)
362
+ end
363
+
364
+ def import_directive(start_pos)
365
+ values = []
366
+
367
+ loop do
368
+ values << expr!(:import_arg)
369
+ break if use_css_import?
370
+ break unless tok(/,/)
371
+ ss
372
+ end
373
+
374
+ values
375
+ end
376
+
377
+ def import_arg
378
+ start_pos = source_position
379
+ return unless (str = string) || (uri = tok?(/url\(/i))
380
+ if uri
381
+ str = sass_script(:parse_string)
382
+ ss
383
+ media = media_query_list
384
+ ss
385
+ return node(Tree::CssImportNode.new(str, media.to_a), start_pos)
386
+ end
387
+ ss
388
+
389
+ media = media_query_list
390
+ if str =~ %r{^(https?:)?//} || media || use_css_import?
391
+ return node(Sass::Tree::CssImportNode.new(
392
+ Sass::Script::Value::String.quote(str), media.to_a), start_pos)
393
+ end
394
+
395
+ node(Sass::Tree::ImportNode.new(str.strip), start_pos)
396
+ end
397
+
398
+ def use_css_import?; false; end
399
+
400
+ def media_directive(start_pos)
401
+ block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a), start_pos), :directive)
402
+ end
403
+
404
+ # http://www.w3.org/TR/css3-mediaqueries/#syntax
405
+ def media_query_list
406
+ query = media_query
407
+ return unless query
408
+ queries = [query]
409
+
410
+ ss
411
+ while tok(/,/)
412
+ ss; queries << expr!(:media_query)
413
+ end
414
+ ss
415
+
416
+ Sass::Media::QueryList.new(queries)
417
+ end
418
+
419
+ def media_query
420
+ if (ident1 = interp_ident)
421
+ ss
422
+ ident2 = interp_ident
423
+ ss
424
+ if ident2 && ident2.length == 1 && ident2[0].is_a?(String) && ident2[0].downcase == 'and'
425
+ query = Sass::Media::Query.new([], ident1, [])
426
+ else
427
+ if ident2
428
+ query = Sass::Media::Query.new(ident1, ident2, [])
429
+ else
430
+ query = Sass::Media::Query.new([], ident1, [])
431
+ end
432
+ return query unless tok(/and/i)
433
+ ss
434
+ end
435
+ end
436
+
437
+ if query
438
+ expr = expr!(:media_expr)
439
+ else
440
+ expr = media_expr
441
+ return unless expr
442
+ end
443
+ query ||= Sass::Media::Query.new([], [], [])
444
+ query.expressions << expr
445
+
446
+ ss
447
+ while tok(/and/i)
448
+ ss; query.expressions << expr!(:media_expr)
449
+ end
450
+
451
+ query
452
+ end
453
+
454
+ def query_expr
455
+ interp = interpolation
456
+ return interp if interp
457
+ return unless tok(/\(/)
458
+ res = ['(']
459
+ ss
460
+ res << sass_script(:parse)
461
+
462
+ if tok(/:/)
463
+ res << ': '
464
+ ss
465
+ res << sass_script(:parse)
466
+ end
467
+ res << tok!(/\)/)
468
+ ss
469
+ res
470
+ end
471
+
472
+ # Aliases allow us to use different descriptions if the same
473
+ # expression fails in different contexts.
474
+ alias_method :media_expr, :query_expr
475
+ alias_method :at_root_query, :query_expr
476
+
477
+ def charset_directive(start_pos)
478
+ name = expr!(:string)
479
+ ss
480
+ node(Sass::Tree::CharsetNode.new(name), start_pos)
481
+ end
482
+
483
+ # The document directive is specified in
484
+ # http://www.w3.org/TR/css3-conditional/, but Gecko allows the
485
+ # `url-prefix` and `domain` functions to omit quotation marks, contrary to
486
+ # the standard.
487
+ #
488
+ # We could parse all document directives according to Mozilla's syntax,
489
+ # but if someone's using e.g. @-webkit-document we don't want them to
490
+ # think WebKit works sans quotes.
491
+ def _moz_document_directive(start_pos)
492
+ res = ["@-moz-document "]
493
+ loop do
494
+ res << str {ss} << expr!(:moz_document_function)
495
+ if (c = tok(/,/))
496
+ res << c
497
+ else
498
+ break
499
+ end
500
+ end
501
+ directive_body(res.flatten, start_pos)
502
+ end
503
+
504
+ def moz_document_function
505
+ val = interp_uri || _interp_string(:url_prefix) ||
506
+ _interp_string(:domain) || function(!:allow_var) || interpolation
507
+ return unless val
508
+ ss
509
+ val
510
+ end
511
+
512
+ def at_root_directive(start_pos)
513
+ if tok?(/\(/) && (expr = at_root_query)
514
+ return block(node(Sass::Tree::AtRootNode.new(expr), start_pos), :directive)
515
+ end
516
+
517
+ at_root_node = node(Sass::Tree::AtRootNode.new, start_pos)
518
+ rule_node = ruleset
519
+ return block(at_root_node, :stylesheet) unless rule_node
520
+ at_root_node << rule_node
521
+ at_root_node
522
+ end
523
+
524
+ def at_root_directive_list
525
+ return unless (first = tok(IDENT))
526
+ arr = [first]
527
+ ss
528
+ while (e = tok(IDENT))
529
+ arr << e
530
+ ss
531
+ end
532
+ arr
533
+ end
534
+
535
+ def error_directive(start_pos)
536
+ node(Sass::Tree::ErrorNode.new(sass_script(:parse)), start_pos)
537
+ end
538
+
539
+ # http://www.w3.org/TR/css3-conditional/
540
+ def supports_directive(name, start_pos)
541
+ condition = expr!(:supports_condition)
542
+ node = Sass::Tree::SupportsNode.new(name, condition)
543
+
544
+ tok!(/\{/)
545
+ node.has_children = true
546
+ block_contents(node, :directive)
547
+ tok!(/\}/)
548
+
549
+ node(node, start_pos)
550
+ end
551
+
552
+ def supports_condition
553
+ supports_negation || supports_operator || supports_interpolation
554
+ end
555
+
556
+ def supports_negation
557
+ return unless tok(/not/i)
558
+ ss
559
+ Sass::Supports::Negation.new(expr!(:supports_condition_in_parens))
560
+ end
561
+
562
+ def supports_operator
563
+ cond = supports_condition_in_parens
564
+ return unless cond
565
+ while (op = tok(/and|or/i))
566
+ ss
567
+ cond = Sass::Supports::Operator.new(
568
+ cond, expr!(:supports_condition_in_parens), op)
569
+ end
570
+ cond
571
+ end
572
+
573
+ def supports_condition_in_parens
574
+ interp = supports_interpolation
575
+ return interp if interp
576
+ return unless tok(/\(/); ss
577
+ if (cond = supports_condition)
578
+ tok!(/\)/); ss
579
+ cond
580
+ else
581
+ name = sass_script(:parse)
582
+ tok!(/:/); ss
583
+ value = sass_script(:parse)
584
+ tok!(/\)/); ss
585
+ Sass::Supports::Declaration.new(name, value)
586
+ end
587
+ end
588
+
589
+ def supports_declaration_condition
590
+ return unless tok(/\(/); ss
591
+ supports_declaration_body
592
+ end
593
+
594
+ def supports_interpolation
595
+ interp = interpolation
596
+ return unless interp
597
+ ss
598
+ Sass::Supports::Interpolation.new(interp)
599
+ end
600
+
601
+ def variable
602
+ return unless tok(/\$/)
603
+ start_pos = source_position
604
+ name = tok!(IDENT)
605
+ ss; tok!(/:/); ss
606
+
607
+ expr = sass_script(:parse)
608
+ while tok(/!/)
609
+ flag_name = tok!(IDENT)
610
+ if flag_name == 'default'
611
+ guarded ||= true
612
+ elsif flag_name == 'global'
613
+ global ||= true
614
+ else
615
+ raise Sass::SyntaxError.new("Invalid flag \"!#{flag_name}\".", :line => @line)
616
+ end
617
+ ss
618
+ end
619
+
620
+ result = Sass::Tree::VariableNode.new(name, expr, guarded, global)
621
+ node(result, start_pos)
622
+ end
623
+
624
+ def operator
625
+ # Many of these operators (all except / and ,)
626
+ # are disallowed by the CSS spec,
627
+ # but they're included here for compatibility
628
+ # with some proprietary MS properties
629
+ str {ss if tok(/[\/,:.=]/)}
630
+ end
631
+
632
+ def ruleset
633
+ start_pos = source_position
634
+ return unless (rules = almost_any_value)
635
+ block(node(
636
+ Sass::Tree::RuleNode.new(rules, range(start_pos)), start_pos), :ruleset)
637
+ end
638
+
639
+ def block(node, context)
640
+ node.has_children = true
641
+ tok!(/\{/)
642
+ block_contents(node, context)
643
+ tok!(/\}/)
644
+ node
645
+ end
646
+
647
+ # A block may contain declarations and/or rulesets
648
+ def block_contents(node, context)
649
+ block_given? ? yield : ss_comments(node)
650
+ node << (child = block_child(context))
651
+ while tok(/;/) || has_children?(child)
652
+ block_given? ? yield : ss_comments(node)
653
+ node << (child = block_child(context))
654
+ end
655
+ node
656
+ end
657
+
658
+ def block_child(context)
659
+ return variable || directive if context == :function
660
+ return variable || directive || ruleset if context == :stylesheet
661
+ variable || directive || declaration_or_ruleset
662
+ end
663
+
664
+ def has_children?(child_or_array)
665
+ return false unless child_or_array
666
+ return child_or_array.last.has_children if child_or_array.is_a?(Array)
667
+ child_or_array.has_children
668
+ end
669
+
670
+ # When parsing the contents of a ruleset, it can be difficult to tell
671
+ # declarations apart from nested rulesets. Since we don't thoroughly parse
672
+ # selectors until after resolving interpolation, we can share a bunch of
673
+ # the parsing of the two, but we need to disambiguate them first. We use
674
+ # the following criteria:
675
+ #
676
+ # * If the entity doesn't start with an identifier followed by a colon,
677
+ # it's a selector. There are some additional mostly-unimportant cases
678
+ # here to support various declaration hacks.
679
+ #
680
+ # * If the colon is followed by another colon, it's a selector.
681
+ #
682
+ # * Otherwise, if the colon is followed by anything other than
683
+ # interpolation or a character that's valid as the beginning of an
684
+ # identifier, it's a declaration.
685
+ #
686
+ # * If the colon is followed by interpolation or a valid identifier, try
687
+ # parsing it as a declaration value. If this fails, backtrack and parse
688
+ # it as a selector.
689
+ #
690
+ # * If the declaration value value valid but is followed by "{", backtrack
691
+ # and parse it as a selector anyway. This ensures that ".foo:bar {" is
692
+ # always parsed as a selector and never as a property with nested
693
+ # properties beneath it.
694
+ def declaration_or_ruleset
695
+ start_pos = source_position
696
+ declaration = try_declaration
697
+
698
+ if declaration.nil?
699
+ return unless (selector = almost_any_value)
700
+ elsif declaration.is_a?(Array)
701
+ selector = declaration
702
+ else
703
+ # Declaration should be a PropNode.
704
+ return declaration
705
+ end
706
+
707
+ if (additional_selector = almost_any_value)
708
+ selector << additional_selector
709
+ end
710
+
711
+ block(node(
712
+ Sass::Tree::RuleNode.new(merge(selector), range(start_pos)), start_pos), :ruleset)
713
+ end
714
+
715
+ # Tries to parse a declaration, and returns the value parsed so far if it
716
+ # fails.
717
+ #
718
+ # This has three possible return types. It can return `nil`, indicating
719
+ # that parsing failed completely and the scanner hasn't moved forward at
720
+ # all. It can return an Array, indicating that parsing failed after
721
+ # consuming some text (possibly containing interpolation), which is
722
+ # returned. Or it can return a PropNode, indicating that parsing
723
+ # succeeded.
724
+ def try_declaration
725
+ # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
726
+ # val" hacks.
727
+ name_start_pos = source_position
728
+ if (s = tok(/[:\*\.]|\#(?!\{)/))
729
+ name = [s, str {ss}]
730
+ return name unless (ident = interp_ident)
731
+ name << ident
732
+ else
733
+ return unless (name = interp_ident)
734
+ name = Array(name)
735
+ end
736
+
737
+ if (comment = tok(COMMENT))
738
+ name << comment
739
+ end
740
+ name_end_pos = source_position
741
+
742
+ mid = [str {ss}]
743
+ return name + mid unless tok(/:/)
744
+ mid << ':'
745
+ return name + mid + [':'] if tok(/:/)
746
+ mid << str {ss}
747
+ post_colon_whitespace = !mid.last.empty?
748
+ could_be_selector = !post_colon_whitespace && (tok?(IDENT_START) || tok?(INTERP_START))
749
+
750
+ value_start_pos = source_position
751
+ value = nil
752
+ error = catch_error do
753
+ value = value!
754
+ if tok?(/\{/)
755
+ # Properties that are ambiguous with selectors can't have additional
756
+ # properties nested beneath them.
757
+ tok!(/;/) if could_be_selector
758
+ elsif !tok?(/[;{}]/)
759
+ # We want an exception if there's no valid end-of-property character
760
+ # exists, but we don't want to consume it if it does.
761
+ tok!(/[;{}]/)
762
+ end
763
+ end
764
+
765
+ if error
766
+ rethrow error unless could_be_selector
767
+
768
+ # If the value would be followed by a semicolon, it's definitely
769
+ # supposed to be a property, not a selector.
770
+ additional_selector = almost_any_value
771
+ rethrow error if tok?(/;/)
772
+
773
+ return name + mid + (additional_selector || [])
774
+ end
775
+
776
+ value_end_pos = source_position
777
+ ss
778
+ require_block = tok?(/\{/)
779
+
780
+ node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
781
+ name_start_pos, value_end_pos)
782
+ node.name_source_range = range(name_start_pos, name_end_pos)
783
+ node.value_source_range = range(value_start_pos, value_end_pos)
784
+
785
+ return node unless require_block
786
+ nested_properties! node
787
+ end
788
+
789
+ # This production is similar to the CSS [`<any-value>`][any-value]
790
+ # production, but as the name implies, not quite the same. It's meant to
791
+ # consume values that could be a selector, an expression, or a combination
792
+ # of both. It respects strings and comments and supports interpolation. It
793
+ # will consume up to "{", "}", ";", or "!".
794
+ #
795
+ # [any-value]: http://dev.w3.org/csswg/css-variables/#typedef-any-value
796
+ #
797
+ # Values consumed by this production will usually be parsed more
798
+ # thoroughly once interpolation has been resolved.
799
+ def almost_any_value
800
+ return unless (tok = almost_any_value_token)
801
+ sel = [tok]
802
+ while (tok = almost_any_value_token)
803
+ sel << tok
804
+ end
805
+ merge(sel)
806
+ end
807
+
808
+ def almost_any_value_token
809
+ tok(%r{
810
+ (
811
+ (?!url\()
812
+ [^"/\#!;\{\}] # "
813
+ |
814
+ /(?![/*])
815
+ |
816
+ \#(?!\{)
817
+ |
818
+ !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed.
819
+ )+
820
+ }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri ||
821
+ interpolation(:warn_for_color)
822
+ end
823
+
824
+ def declaration
825
+ # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
826
+ # val" hacks.
827
+ name_start_pos = source_position
828
+ if (s = tok(/[:\*\.]|\#(?!\{)/))
829
+ name = [s, str {ss}, *expr!(:interp_ident)]
830
+ else
831
+ return unless (name = interp_ident)
832
+ name = Array(name)
833
+ end
834
+
835
+ if (comment = tok(COMMENT))
836
+ name << comment
837
+ end
838
+ name_end_pos = source_position
839
+ ss
840
+
841
+ tok!(/:/)
842
+ ss
843
+ value_start_pos = source_position
844
+ value = value!
845
+ value_end_pos = source_position
846
+ ss
847
+ require_block = tok?(/\{/)
848
+
849
+ node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
850
+ name_start_pos, value_end_pos)
851
+ node.name_source_range = range(name_start_pos, name_end_pos)
852
+ node.value_source_range = range(value_start_pos, value_end_pos)
853
+
854
+ return node unless require_block
855
+ nested_properties! node
856
+ end
857
+
858
+ def value!
859
+ if tok?(/\{/)
860
+ str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
861
+ str.line = source_position.line
862
+ str.source_range = range(source_position)
863
+ return str
864
+ end
865
+
866
+ start_pos = source_position
867
+ # This is a bit of a dirty trick:
868
+ # if the value is completely static,
869
+ # we don't parse it at all, and instead return a plain old string
870
+ # containing the value.
871
+ # This results in a dramatic speed increase.
872
+ if (val = tok(STATIC_VALUE, true))
873
+ str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(val.strip))
874
+ str.line = start_pos.line
875
+ str.source_range = range(start_pos)
876
+ return str
877
+ end
878
+ sass_script(:parse)
879
+ end
880
+
881
+ def nested_properties!(node)
882
+ @expected = 'expression (e.g. 1px, bold) or "{"'
883
+ block(node, :property)
884
+ end
885
+
886
+ def expr(allow_var = true)
887
+ t = term(allow_var)
888
+ return unless t
889
+ res = [t, str {ss}]
890
+
891
+ while (o = operator) && (t = term(allow_var))
892
+ res << o << t << str {ss}
893
+ end
894
+
895
+ res.flatten
896
+ end
897
+
898
+ def term(allow_var)
899
+ e = tok(NUMBER) ||
900
+ interp_uri ||
901
+ function(allow_var) ||
902
+ interp_string ||
903
+ tok(UNICODERANGE) ||
904
+ interp_ident ||
905
+ tok(HEXCOLOR) ||
906
+ (allow_var && var_expr)
907
+ return e if e
908
+
909
+ op = tok(/[+-]/)
910
+ return unless op
911
+ @expected = "number or function"
912
+ [op,
913
+ tok(NUMBER) || function(allow_var) || (allow_var && var_expr) || expr!(:interpolation)]
914
+ end
915
+
916
+ def function(allow_var)
917
+ name = tok(FUNCTION)
918
+ return unless name
919
+ if name == "expression(" || name == "calc("
920
+ str, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
921
+ [name, str]
922
+ else
923
+ [name, str {ss}, expr(allow_var), tok!(/\)/)]
924
+ end
925
+ end
926
+
927
+ def var_expr
928
+ return unless tok(/\$/)
929
+ line = @line
930
+ var = Sass::Script::Tree::Variable.new(tok!(IDENT))
931
+ var.line = line
932
+ var
933
+ end
934
+
935
+ def interpolation(warn_for_color = false)
936
+ return unless tok(INTERP_START)
937
+ sass_script(:parse_interpolated, warn_for_color)
938
+ end
939
+
940
+ def string
941
+ return unless tok(STRING)
942
+ Sass::Script::Value::String.value(@scanner[1] || @scanner[2])
943
+ end
944
+
945
+ def interp_string
946
+ _interp_string(:double) || _interp_string(:single)
947
+ end
948
+
949
+ def interp_uri
950
+ _interp_string(:uri)
951
+ end
952
+
953
+ def _interp_string(type)
954
+ start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][false])
955
+ return unless start
956
+ res = [start]
957
+
958
+ mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][true]
959
+ # @scanner[2].empty? means we've started an interpolated section
960
+ while @scanner[2] == '#{'
961
+ @scanner.pos -= 2 # Don't consume the #{
962
+ res.last.slice!(-2..-1)
963
+ res << expr!(:interpolation) << tok(mid_re)
964
+ end
965
+ res
966
+ end
967
+
968
+ def interp_ident(start = IDENT)
969
+ val = tok(start) || interpolation(:warn_for_color) || tok(IDENT_HYPHEN_INTERP, true)
970
+ return unless val
971
+ res = [val]
972
+ while (val = tok(NAME) || interpolation(:warn_for_color))
973
+ res << val
974
+ end
975
+ res
976
+ end
977
+
978
+ def interp_ident_or_var
979
+ id = interp_ident
980
+ return id if id
981
+ var = var_expr
982
+ return [var] if var
983
+ end
984
+
985
+ def str
986
+ @strs.push ""
987
+ yield
988
+ @strs.last
989
+ ensure
990
+ @strs.pop
991
+ end
992
+
993
+ def str?
994
+ pos = @scanner.pos
995
+ line = @line
996
+ offset = @offset
997
+ @strs.push ""
998
+ throw_error {yield} && @strs.last
999
+ rescue Sass::SyntaxError
1000
+ @scanner.pos = pos
1001
+ @line = line
1002
+ @offset = offset
1003
+ nil
1004
+ ensure
1005
+ @strs.pop
1006
+ end
1007
+
1008
+ def node(node, start_pos, end_pos = source_position)
1009
+ node.line = start_pos.line
1010
+ node.source_range = range(start_pos, end_pos)
1011
+ node
1012
+ end
1013
+
1014
+ @sass_script_parser = Class.new(Sass::Script::Parser)
1015
+ @sass_script_parser.send(:include, ScriptParser)
1016
+
1017
+ class << self
1018
+ # @private
1019
+ attr_accessor :sass_script_parser
1020
+ end
1021
+
1022
+ def sass_script(*args)
1023
+ parser = self.class.sass_script_parser.new(@scanner, @line, @offset,
1024
+ :filename => @filename, :importer => @importer)
1025
+ result = parser.send(*args)
1026
+ unless @strs.empty?
1027
+ # Convert to CSS manually so that comments are ignored.
1028
+ src = result.to_sass
1029
+ @strs.each {|s| s << src}
1030
+ end
1031
+ @line = parser.line
1032
+ @offset = parser.offset
1033
+ result
1034
+ rescue Sass::SyntaxError => e
1035
+ throw(:_sass_parser_error, true) if @throw_error
1036
+ raise e
1037
+ end
1038
+
1039
+ def merge(arr)
1040
+ arr && Sass::Util.merge_adjacent_strings([arr].flatten)
1041
+ end
1042
+
1043
+ EXPR_NAMES = {
1044
+ :media_query => "media query (e.g. print, screen, print and screen)",
1045
+ :media_query_list => "media query (e.g. print, screen, print and screen)",
1046
+ :media_expr => "media expression (e.g. (min-device-width: 800px))",
1047
+ :at_root_query => "@at-root query (e.g. (without: media))",
1048
+ :at_root_directive_list => '* or identifier',
1049
+ :pseudo_args => "expression (e.g. fr, 2n+1)",
1050
+ :interp_ident => "identifier",
1051
+ :qualified_name => "identifier",
1052
+ :expr => "expression (e.g. 1px, bold)",
1053
+ :selector_comma_sequence => "selector",
1054
+ :string => "string",
1055
+ :import_arg => "file to import (string or url())",
1056
+ :moz_document_function => "matching function (e.g. url-prefix(), domain())",
1057
+ :supports_condition => "@supports condition (e.g. (display: flexbox))",
1058
+ :supports_condition_in_parens => "@supports condition (e.g. (display: flexbox))",
1059
+ :a_n_plus_b => "An+B expression",
1060
+ :keyframes_selector_component => "from, to, or a percentage",
1061
+ :keyframes_selector => "keyframes selector (e.g. 10%)"
1062
+ }
1063
+
1064
+ TOK_NAMES = Sass::Util.to_hash(Sass::SCSS::RX.constants.map do |c|
1065
+ [Sass::SCSS::RX.const_get(c), c.downcase]
1066
+ end).merge(
1067
+ IDENT => "identifier",
1068
+ /[;{}]/ => '";"',
1069
+ /\b(without|with)\b/ => '"with" or "without"'
1070
+ )
1071
+
1072
+ def tok?(rx)
1073
+ @scanner.match?(rx)
1074
+ end
1075
+
1076
+ def expr!(name)
1077
+ e = send(name)
1078
+ return e if e
1079
+ expected(EXPR_NAMES[name] || name.to_s)
1080
+ end
1081
+
1082
+ def tok!(rx)
1083
+ t = tok(rx)
1084
+ return t if t
1085
+ name = TOK_NAMES[rx]
1086
+
1087
+ unless name
1088
+ # Display basic regexps as plain old strings
1089
+ source = rx.source.gsub(/\\\//, '/')
1090
+ string = rx.source.gsub(/\\(.)/, '\1')
1091
+ name = source == Regexp.escape(string) ? string.inspect : rx.inspect
1092
+ end
1093
+
1094
+ expected(name)
1095
+ end
1096
+
1097
+ def expected(name)
1098
+ throw(:_sass_parser_error, true) if @throw_error
1099
+ self.class.expected(@scanner, @expected || name, @line)
1100
+ end
1101
+
1102
+ def err(msg)
1103
+ throw(:_sass_parser_error, true) if @throw_error
1104
+ raise Sass::SyntaxError.new(msg, :line => @line)
1105
+ end
1106
+
1107
+ def throw_error
1108
+ old_throw_error, @throw_error = @throw_error, false
1109
+ yield
1110
+ ensure
1111
+ @throw_error = old_throw_error
1112
+ end
1113
+
1114
+ def catch_error(&block)
1115
+ old_throw_error, @throw_error = @throw_error, true
1116
+ pos = @scanner.pos
1117
+ line = @line
1118
+ offset = @offset
1119
+ expected = @expected
1120
+ if catch(:_sass_parser_error) {yield; false}
1121
+ @scanner.pos = pos
1122
+ @line = line
1123
+ @offset = offset
1124
+ @expected = expected
1125
+ {:pos => pos, :line => line, :expected => @expected, :block => block}
1126
+ end
1127
+ ensure
1128
+ @throw_error = old_throw_error
1129
+ end
1130
+
1131
+ def rethrow(err)
1132
+ if @throw_error
1133
+ throw :_sass_parser_error, err
1134
+ else
1135
+ @scanner = Sass::Util::MultibyteStringScanner.new(@scanner.string)
1136
+ @scanner.pos = err[:pos]
1137
+ @line = err[:line]
1138
+ @expected = err[:expected]
1139
+ err[:block].call
1140
+ end
1141
+ end
1142
+
1143
+ # @private
1144
+ def self.expected(scanner, expected, line)
1145
+ pos = scanner.pos
1146
+
1147
+ after = scanner.string[0...pos]
1148
+ # Get rid of whitespace between pos and the last token,
1149
+ # but only if there's a newline in there
1150
+ after.gsub!(/\s*\n\s*$/, '')
1151
+ # Also get rid of stuff before the last newline
1152
+ after.gsub!(/.*\n/, '')
1153
+ after = "..." + after[-15..-1] if after.size > 18
1154
+
1155
+ was = scanner.rest.dup
1156
+ # Get rid of whitespace between pos and the next token,
1157
+ # but only if there's a newline in there
1158
+ was.gsub!(/^\s*\n\s*/, '')
1159
+ # Also get rid of stuff after the next newline
1160
+ was.gsub!(/\n.*/, '')
1161
+ was = was[0...15] + "..." if was.size > 18
1162
+
1163
+ raise Sass::SyntaxError.new(
1164
+ "Invalid CSS after \"#{after}\": expected #{expected}, was \"#{was}\"",
1165
+ :line => line)
1166
+ end
1167
+
1168
+ # Avoid allocating lots of new strings for `#tok`.
1169
+ # This is important because `#tok` is called all the time.
1170
+ NEWLINE = "\n"
1171
+
1172
+ def tok(rx, last_group_lookahead = false)
1173
+ res = @scanner.scan(rx)
1174
+ if res
1175
+ # This fixes https://github.com/nex3/sass/issues/104, which affects
1176
+ # Ruby 1.8.7 and REE. This fix is to replace the ?= zero-width
1177
+ # positive lookahead operator in the Regexp (which matches without
1178
+ # consuming the matched group), with a match that does consume the
1179
+ # group, but then rewinds the scanner and removes the group from the
1180
+ # end of the matched string. This fix makes the assumption that the
1181
+ # matched group will always occur at the end of the match.
1182
+ if last_group_lookahead && @scanner[-1]
1183
+ @scanner.pos -= @scanner[-1].length
1184
+ res.slice!(-@scanner[-1].length..-1)
1185
+ end
1186
+
1187
+ newline_count = res.count(NEWLINE)
1188
+ if newline_count > 0
1189
+ @line += newline_count
1190
+ @offset = res[res.rindex(NEWLINE)..-1].size
1191
+ else
1192
+ @offset += res.size
1193
+ end
1194
+
1195
+ @expected = nil
1196
+ if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
1197
+ @strs.each {|s| s << res}
1198
+ end
1199
+ res
1200
+ end
1201
+ end
1202
+
1203
+ # Remove a vendor prefix from `str`.
1204
+ def deprefix(str)
1205
+ str.gsub(/^-[a-zA-Z0-9]+-/, '')
1206
+ end
1207
+ end
1208
+ end
1209
+ end