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,518 @@
1
+ require 'sass/scss/rx'
2
+
3
+ module Sass
4
+ module Script
5
+ # The lexical analyzer for SassScript.
6
+ # It takes a raw string and converts it to individual tokens
7
+ # that are easier to parse.
8
+ class Lexer
9
+ include Sass::SCSS::RX
10
+
11
+ # A struct containing information about an individual token.
12
+ #
13
+ # `type`: \[`Symbol`\]
14
+ # : The type of token.
15
+ #
16
+ # `value`: \[`Object`\]
17
+ # : The Ruby object corresponding to the value of the token.
18
+ #
19
+ # `source_range`: \[`Sass::Source::Range`\]
20
+ # : The range in the source file in which the token appeared.
21
+ #
22
+ # `pos`: \[`Integer`\]
23
+ # : The scanner position at which the SassScript token appeared.
24
+ Token = Struct.new(:type, :value, :source_range, :pos)
25
+
26
+ # The line number of the lexer's current position.
27
+ #
28
+ # @return [Integer]
29
+ def line
30
+ return @line unless @tok
31
+ @tok.source_range.start_pos.line
32
+ end
33
+
34
+ # The number of bytes into the current line
35
+ # of the lexer's current position (1-based).
36
+ #
37
+ # @return [Integer]
38
+ def offset
39
+ return @offset unless @tok
40
+ @tok.source_range.start_pos.offset
41
+ end
42
+
43
+ # A hash from operator strings to the corresponding token types.
44
+ OPERATORS = {
45
+ '+' => :plus,
46
+ '-' => :minus,
47
+ '*' => :times,
48
+ '/' => :div,
49
+ '%' => :mod,
50
+ '=' => :single_eq,
51
+ ':' => :colon,
52
+ '(' => :lparen,
53
+ ')' => :rparen,
54
+ '[' => :lsquare,
55
+ ']' => :rsquare,
56
+ ',' => :comma,
57
+ 'and' => :and,
58
+ 'or' => :or,
59
+ 'not' => :not,
60
+ '==' => :eq,
61
+ '!=' => :neq,
62
+ '>=' => :gte,
63
+ '<=' => :lte,
64
+ '>' => :gt,
65
+ '<' => :lt,
66
+ '#{' => :begin_interpolation,
67
+ '}' => :end_interpolation,
68
+ ';' => :semicolon,
69
+ '{' => :lcurly,
70
+ '...' => :splat,
71
+ }
72
+
73
+ OPERATORS_REVERSE = Sass::Util.map_hash(OPERATORS) {|k, v| [v, k]}
74
+
75
+ TOKEN_NAMES = Sass::Util.map_hash(OPERATORS_REVERSE) {|k, v| [k, v.inspect]}.merge(
76
+ :const => "variable (e.g. $foo)",
77
+ :ident => "identifier (e.g. middle)")
78
+
79
+ # A list of operator strings ordered with longer names first
80
+ # so that `>` and `<` don't clobber `>=` and `<=`.
81
+ OP_NAMES = OPERATORS.keys.sort_by {|o| -o.size}
82
+
83
+ # A sub-list of {OP_NAMES} that only includes operators
84
+ # with identifier names.
85
+ IDENT_OP_NAMES = OP_NAMES.select {|k, _v| k =~ /^\w+/}
86
+
87
+ PARSEABLE_NUMBER = /(?:(\d*\.\d+)|(\d+))(?:[eE]([+-]?\d+))?(#{UNIT})?/
88
+
89
+ # A hash of regular expressions that are used for tokenizing.
90
+ REGULAR_EXPRESSIONS = {
91
+ :whitespace => /\s+/,
92
+ :comment => COMMENT,
93
+ :single_line_comment => SINGLE_LINE_COMMENT,
94
+ :variable => /(\$)(#{IDENT})/,
95
+ :ident => /(#{IDENT})(\()?/,
96
+ :number => PARSEABLE_NUMBER,
97
+ :unary_minus_number => /-#{PARSEABLE_NUMBER}/,
98
+ :color => HEXCOLOR,
99
+ :id => /##{IDENT}/,
100
+ :selector => /&/,
101
+ :ident_op => /(#{Regexp.union(*IDENT_OP_NAMES.map do |s|
102
+ Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)")
103
+ end)})/,
104
+ :op => /(#{Regexp.union(*OP_NAMES)})/,
105
+ }
106
+
107
+ class << self
108
+ private
109
+
110
+ def string_re(open, close)
111
+ /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m
112
+ end
113
+ end
114
+
115
+ # A hash of regular expressions that are used for tokenizing strings.
116
+ #
117
+ # The key is a `[Symbol, Boolean]` pair.
118
+ # The symbol represents which style of quotation to use,
119
+ # while the boolean represents whether or not the string
120
+ # is following an interpolated segment.
121
+ STRING_REGULAR_EXPRESSIONS = {
122
+ :double => {
123
+ false => string_re('"', '"'),
124
+ true => string_re('', '"')
125
+ },
126
+ :single => {
127
+ false => string_re("'", "'"),
128
+ true => string_re('', "'")
129
+ },
130
+ :uri => {
131
+ false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
132
+ true => /(#{URLCHAR}*?)(#{W}\)|#\{)/
133
+ },
134
+ # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a
135
+ # non-standard version of http://www.w3.org/TR/css3-conditional/
136
+ :url_prefix => {
137
+ false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
138
+ true => /(#{URLCHAR}*?)(#{W}\)|#\{)/
139
+ },
140
+ :domain => {
141
+ false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
142
+ true => /(#{URLCHAR}*?)(#{W}\)|#\{)/
143
+ }
144
+ }
145
+
146
+ # @param str [String, StringScanner] The source text to lex
147
+ # @param line [Integer] The 1-based line on which the SassScript appears.
148
+ # Used for error reporting and sourcemap building
149
+ # @param offset [Integer] The 1-based character (not byte) offset in the line in the source.
150
+ # Used for error reporting and sourcemap building
151
+ # @param options [{Symbol => Object}] An options hash;
152
+ # see {file:SASS_REFERENCE.md#Options the Sass options documentation}
153
+ def initialize(str, line, offset, options)
154
+ @scanner = str.is_a?(StringScanner) ? str : Sass::Util::MultibyteStringScanner.new(str)
155
+ @line = line
156
+ @offset = offset
157
+ @options = options
158
+ @interpolation_stack = []
159
+ @prev = nil
160
+ @tok = nil
161
+ @next_tok = nil
162
+ end
163
+
164
+ # Moves the lexer forward one token.
165
+ #
166
+ # @return [Token] The token that was moved past
167
+ def next
168
+ @tok ||= read_token
169
+ @tok, tok = nil, @tok
170
+ @prev = tok
171
+ tok
172
+ end
173
+
174
+ # Returns whether or not there's whitespace before the next token.
175
+ #
176
+ # @return [Boolean]
177
+ def whitespace?(tok = @tok)
178
+ if tok
179
+ @scanner.string[0...tok.pos] =~ /\s\Z/
180
+ else
181
+ @scanner.string[@scanner.pos, 1] =~ /^\s/ ||
182
+ @scanner.string[@scanner.pos - 1, 1] =~ /\s\Z/
183
+ end
184
+ end
185
+
186
+ # Returns the given character.
187
+ #
188
+ # @return [String]
189
+ def char(pos = @scanner.pos)
190
+ @scanner.string[pos, 1]
191
+ end
192
+
193
+ # Consumes and returns single raw character from the input stream.
194
+ #
195
+ # @return [String]
196
+ def next_char
197
+ unpeek!
198
+ scan(/./)
199
+ end
200
+
201
+ # Returns the next token without moving the lexer forward.
202
+ #
203
+ # @return [Token] The next token
204
+ def peek
205
+ @tok ||= read_token
206
+ end
207
+
208
+ # Rewinds the underlying StringScanner
209
+ # to before the token returned by \{#peek}.
210
+ def unpeek!
211
+ raise "[BUG] Can't unpeek before a queued token!" if @next_tok
212
+ return unless @tok
213
+ @scanner.pos = @tok.pos
214
+ @line = @tok.source_range.start_pos.line
215
+ @offset = @tok.source_range.start_pos.offset
216
+ end
217
+
218
+ # @return [Boolean] Whether or not there's more source text to lex.
219
+ def done?
220
+ return if @next_tok
221
+ whitespace unless after_interpolation? && !@interpolation_stack.empty?
222
+ @scanner.eos? && @tok.nil?
223
+ end
224
+
225
+ # @return [Boolean] Whether or not the last token lexed was `:end_interpolation`.
226
+ def after_interpolation?
227
+ @prev && @prev.type == :end_interpolation
228
+ end
229
+
230
+ # Raise an error to the effect that `name` was expected in the input stream
231
+ # and wasn't found.
232
+ #
233
+ # This calls \{#unpeek!} to rewind the scanner to immediately after
234
+ # the last returned token.
235
+ #
236
+ # @param name [String] The name of the entity that was expected but not found
237
+ # @raise [Sass::SyntaxError]
238
+ def expected!(name)
239
+ unpeek!
240
+ Sass::SCSS::Parser.expected(@scanner, name, @line)
241
+ end
242
+
243
+ # Records all non-comment text the lexer consumes within the block
244
+ # and returns it as a string.
245
+ #
246
+ # @yield A block in which text is recorded
247
+ # @return [String]
248
+ def str
249
+ old_pos = @tok ? @tok.pos : @scanner.pos
250
+ yield
251
+ new_pos = @tok ? @tok.pos : @scanner.pos
252
+ @scanner.string[old_pos...new_pos]
253
+ end
254
+
255
+ # Runs a block, and rewinds the state of the lexer to the beginning of the
256
+ # block if it returns `nil` or `false`.
257
+ def try
258
+ old_pos = @scanner.pos
259
+ old_line = @line
260
+ old_offset = @offset
261
+ old_interpolation_stack = @interpolation_stack.dup
262
+ old_prev = @prev
263
+ old_tok = @tok
264
+ old_next_tok = @next_tok
265
+
266
+ result = yield
267
+ return result if result
268
+
269
+ @scanner.pos = old_pos
270
+ @line = old_line
271
+ @offset = old_offset
272
+ @interpolation_stack = old_interpolation_stack
273
+ @prev = old_prev
274
+ @tok = old_tok
275
+ @next_tok = old_next_tok
276
+ nil
277
+ end
278
+
279
+ private
280
+
281
+ def read_token
282
+ if (tok = @next_tok)
283
+ @next_tok = nil
284
+ return tok
285
+ end
286
+
287
+ return if done?
288
+ start_pos = source_position
289
+ value = token
290
+ return unless value
291
+ type, val = value
292
+ Token.new(type, val, range(start_pos), @scanner.pos - @scanner.matched_size)
293
+ end
294
+
295
+ def whitespace
296
+ nil while scan(REGULAR_EXPRESSIONS[:whitespace]) ||
297
+ scan(REGULAR_EXPRESSIONS[:comment]) ||
298
+ scan(REGULAR_EXPRESSIONS[:single_line_comment])
299
+ end
300
+
301
+ def token
302
+ if after_interpolation?
303
+ interp_type, interp_value = @interpolation_stack.pop
304
+ if interp_type == :special_fun
305
+ return special_fun_body(interp_value)
306
+ elsif interp_type.nil?
307
+ if @scanner.string[@scanner.pos - 1] == '}' && scan(REGULAR_EXPRESSIONS[:ident])
308
+ return [@scanner[2] ? :funcall : :ident, Sass::Util.normalize_ident_escapes(@scanner[1], start: false)]
309
+ end
310
+ else
311
+ raise "[BUG]: Unknown interp_type #{interp_type}" unless interp_type == :string
312
+ return string(interp_value, true)
313
+ end
314
+ end
315
+
316
+ variable || string(:double, false) || string(:single, false) || number || id || color ||
317
+ selector || string(:uri, false) || raw(UNICODERANGE) || special_fun || special_val ||
318
+ ident_op || ident || op
319
+ end
320
+
321
+ def variable
322
+ _variable(REGULAR_EXPRESSIONS[:variable])
323
+ end
324
+
325
+ def _variable(rx)
326
+ return unless scan(rx)
327
+ [:const, Sass::Util.normalize_ident_escapes(@scanner[2])]
328
+ end
329
+
330
+ def ident
331
+ return unless scan(REGULAR_EXPRESSIONS[:ident])
332
+ [@scanner[2] ? :funcall : :ident, Sass::Util.normalize_ident_escapes(@scanner[1])]
333
+ end
334
+
335
+ def string(re, open)
336
+ line, offset = @line, @offset
337
+ return unless scan(STRING_REGULAR_EXPRESSIONS[re][open])
338
+ if @scanner[0] =~ /([^\\]|^)\n/
339
+ filename = @options[:filename]
340
+ Sass::Util.sass_warn <<MESSAGE
341
+ DEPRECATION WARNING on line #{line}, column #{offset}#{" of #{filename}" if filename}:
342
+ Unescaped multiline strings are deprecated and will be removed in a future version of Sass.
343
+ To include a newline in a string, use "\\a" or "\\a " as in CSS.
344
+ MESSAGE
345
+ end
346
+
347
+ if @scanner[2] == '#{' # '
348
+ @interpolation_stack << [:string, re]
349
+ start_pos = Sass::Source::Position.new(@line, @offset - 2)
350
+ @next_tok = Token.new(:string_interpolation, range(start_pos), @scanner.pos - 2)
351
+ end
352
+ str =
353
+ if re == :uri
354
+ url = "#{'url(' unless open}#{@scanner[1]}#{')' unless @scanner[2] == '#{'}"
355
+ Script::Value::String.new(url)
356
+ else
357
+ Script::Value::String.new(Sass::Script::Value::String.value(@scanner[1]), :string)
358
+ end
359
+ [:string, str]
360
+ end
361
+
362
+ def number
363
+ # Handling unary minus is complicated by the fact that whitespace is an
364
+ # operator in SassScript. We want "1-2" to be parsed as "1 - 2", but we
365
+ # want "1 -2" to be parsed as "1 (-2)". To accomplish this, we only
366
+ # parse a unary minus as part of a number literal if there's whitespace
367
+ # before and not after it. Cases like "(-2)" are handled by the unary
368
+ # minus logic in the parser instead.
369
+ if @scanner.peek(1) == '-'
370
+ return if @scanner.pos == 0
371
+ unary_minus_allowed =
372
+ case @scanner.string[@scanner.pos - 1, 1]
373
+ when /\s/; true
374
+ when '/'; @scanner.pos != 1 && @scanner.string[@scanner.pos - 2, 1] == '*'
375
+ else; false
376
+ end
377
+
378
+ return unless unary_minus_allowed
379
+ return unless scan(REGULAR_EXPRESSIONS[:unary_minus_number])
380
+ minus = true
381
+ else
382
+ return unless scan(REGULAR_EXPRESSIONS[:number])
383
+ minus = false
384
+ end
385
+
386
+ value = (@scanner[1] ? @scanner[1].to_f : @scanner[2].to_i) * (minus ? -1 : 1)
387
+ value *= 10**@scanner[3].to_i if @scanner[3]
388
+ units = @scanner[4]
389
+ units = Sass::Util::normalize_ident_escapes(units) if units
390
+ script_number = Script::Value::Number.new(value, Array(units))
391
+ [:number, script_number]
392
+ end
393
+
394
+ def id
395
+ # Colors and ids are tough to tell apart, because they overlap but
396
+ # neither is a superset of the other. "#xyz" is an id but not a color,
397
+ # "#000" is a color but not an id, "#abc" is both, and "#0" is neither.
398
+ # We need to handle all these cases correctly.
399
+ #
400
+ # To do so, we first try to parse something as an id. If this works and
401
+ # the id is also a valid color, we return the color. Otherwise, we
402
+ # return the id. If it didn't parse as an id, we then try to parse it as
403
+ # a color. If *this* works, we return the color, and if it doesn't we
404
+ # give up and throw an error.
405
+ #
406
+ # IDs in properties are used in the Basic User Interface Module
407
+ # (http://www.w3.org/TR/css3-ui/).
408
+ return unless scan(REGULAR_EXPRESSIONS[:id])
409
+ if @scanner[0] =~ /^\#[0-9a-fA-F]+$/ &&
410
+ (@scanner[0].length == 4 || @scanner[0].length == 5 ||
411
+ @scanner[0].length == 7 || @scanner[0].length == 9)
412
+ return [:color, Script::Value::Color.from_hex(@scanner[0])]
413
+ end
414
+ [:ident, Sass::Util.normalize_ident_escapes(@scanner[0])]
415
+ end
416
+
417
+ def color
418
+ return unless @scanner.match?(REGULAR_EXPRESSIONS[:color])
419
+ unless @scanner[0].length == 4 || @scanner[0].length == 5 ||
420
+ @scanner[0].length == 7 || @scanner[0].length == 9
421
+ return
422
+ end
423
+ script_color = Script::Value::Color.from_hex(scan(REGULAR_EXPRESSIONS[:color]))
424
+ [:color, script_color]
425
+ end
426
+
427
+ def selector
428
+ start_pos = source_position
429
+ return unless scan(REGULAR_EXPRESSIONS[:selector])
430
+
431
+ if @scanner.peek(1) == '&'
432
+ filename = @options[:filename]
433
+ Sass::Util.sass_warn <<MESSAGE
434
+ WARNING on line #{line}, column #{offset}#{" of #{filename}" if filename}:
435
+ In Sass, "&&" means two copies of the parent selector. You probably want to use "and" instead.
436
+ MESSAGE
437
+ end
438
+
439
+ script_selector = Script::Tree::Selector.new
440
+ script_selector.source_range = range(start_pos)
441
+ [:selector, script_selector]
442
+ end
443
+
444
+ def special_fun
445
+ prefix = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
446
+ return unless prefix
447
+ special_fun_body(1, prefix)
448
+ end
449
+
450
+ def special_fun_body(parens, prefix = nil)
451
+ str = prefix || ''
452
+ while (scanned = scan(/.*?([()]|\#\{)/m))
453
+ str << scanned
454
+ if scanned[-1] == ?(
455
+ parens += 1
456
+ next
457
+ elsif scanned[-1] == ?)
458
+ parens -= 1
459
+ next unless parens == 0
460
+ else
461
+ raise "[BUG] Unreachable" unless @scanner[1] == '#{' # '
462
+ str.slice!(-2..-1)
463
+ @interpolation_stack << [:special_fun, parens]
464
+ start_pos = Sass::Source::Position.new(@line, @offset - 2)
465
+ @next_tok = Token.new(:string_interpolation, range(start_pos), @scanner.pos - 2)
466
+ end
467
+
468
+ return [:special_fun, Sass::Script::Value::String.new(str)]
469
+ end
470
+
471
+ scan(/.*/)
472
+ expected!('")"')
473
+ end
474
+
475
+ def special_val
476
+ return unless scan(/!#{W}important/i)
477
+ [:string, Script::Value::String.new("!important")]
478
+ end
479
+
480
+ def ident_op
481
+ op = scan(REGULAR_EXPRESSIONS[:ident_op])
482
+ return unless op
483
+ [OPERATORS[op]]
484
+ end
485
+
486
+ def op
487
+ op = scan(REGULAR_EXPRESSIONS[:op])
488
+ return unless op
489
+ name = OPERATORS[op]
490
+ @interpolation_stack << nil if name == :begin_interpolation
491
+ [name]
492
+ end
493
+
494
+ def raw(rx)
495
+ val = scan(rx)
496
+ return unless val
497
+ [:raw, val]
498
+ end
499
+
500
+ def scan(re)
501
+ str = @scanner.scan(re)
502
+ return unless str
503
+ c = str.count("\n")
504
+ @line += c
505
+ @offset = (c == 0 ? @offset + str.size : str.size - str.rindex("\n"))
506
+ str
507
+ end
508
+
509
+ def range(start_pos, end_pos = source_position)
510
+ Sass::Source::Range.new(start_pos, end_pos, @options[:filename], @options[:importer])
511
+ end
512
+
513
+ def source_position
514
+ Sass::Source::Position.new(@line, @offset)
515
+ end
516
+ end
517
+ end
518
+ end