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