sass 3.2.7 → 3.3.0.rc.1

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 (184) hide show
  1. data/MIT-LICENSE +2 -2
  2. data/README.md +14 -2
  3. data/Rakefile +25 -1
  4. data/VERSION +1 -1
  5. data/VERSION_DATE +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/lib/sass/cache_stores/base.rb +4 -2
  8. data/lib/sass/cache_stores/chain.rb +2 -1
  9. data/lib/sass/cache_stores/filesystem.rb +2 -6
  10. data/lib/sass/cache_stores/memory.rb +1 -1
  11. data/lib/sass/cache_stores/null.rb +2 -2
  12. data/lib/sass/callbacks.rb +1 -0
  13. data/lib/sass/css.rb +10 -10
  14. data/lib/sass/engine.rb +403 -150
  15. data/lib/sass/environment.rb +136 -57
  16. data/lib/sass/error.rb +7 -7
  17. data/lib/sass/exec.rb +123 -39
  18. data/lib/sass/features.rb +41 -0
  19. data/lib/sass/importers/base.rb +33 -2
  20. data/lib/sass/importers/deprecated_path.rb +45 -0
  21. data/lib/sass/importers/filesystem.rb +25 -14
  22. data/lib/sass/importers.rb +1 -0
  23. data/lib/sass/logger/base.rb +3 -3
  24. data/lib/sass/logger/log_level.rb +4 -6
  25. data/lib/sass/media.rb +19 -19
  26. data/lib/sass/plugin/compiler.rb +141 -101
  27. data/lib/sass/plugin/configuration.rb +18 -22
  28. data/lib/sass/plugin/merb.rb +1 -1
  29. data/lib/sass/plugin/staleness_checker.rb +24 -8
  30. data/lib/sass/plugin.rb +4 -2
  31. data/lib/sass/repl.rb +3 -3
  32. data/lib/sass/script/css_lexer.rb +9 -4
  33. data/lib/sass/script/css_parser.rb +6 -2
  34. data/lib/sass/script/functions.rb +1343 -590
  35. data/lib/sass/script/lexer.rb +84 -52
  36. data/lib/sass/script/parser.rb +217 -97
  37. data/lib/sass/script/tree/funcall.rb +290 -0
  38. data/lib/sass/script/{interpolation.rb → tree/interpolation.rb} +34 -13
  39. data/lib/sass/script/tree/list_literal.rb +80 -0
  40. data/lib/sass/script/tree/literal.rb +47 -0
  41. data/lib/sass/script/tree/map_literal.rb +64 -0
  42. data/lib/sass/script/{node.rb → tree/node.rb} +22 -12
  43. data/lib/sass/script/{operation.rb → tree/operation.rb} +17 -25
  44. data/lib/sass/script/tree/selector.rb +30 -0
  45. data/lib/sass/script/{string_interpolation.rb → tree/string_interpolation.rb} +5 -4
  46. data/lib/sass/script/{unary_operation.rb → tree/unary_operation.rb} +14 -9
  47. data/lib/sass/script/tree/variable.rb +57 -0
  48. data/lib/sass/script/tree.rb +16 -0
  49. data/lib/sass/script/{arg_list.rb → value/arg_list.rb} +4 -24
  50. data/lib/sass/script/value/base.rb +248 -0
  51. data/lib/sass/script/value/bool.rb +36 -0
  52. data/lib/sass/script/{color.rb → value/color.rb} +239 -195
  53. data/lib/sass/script/value/helpers.rb +155 -0
  54. data/lib/sass/script/value/list.rb +119 -0
  55. data/lib/sass/script/value/map.rb +70 -0
  56. data/lib/sass/script/value/null.rb +45 -0
  57. data/lib/sass/script/{number.rb → value/number.rb} +91 -65
  58. data/lib/sass/script/{string.rb → value/string.rb} +9 -11
  59. data/lib/sass/script/value.rb +11 -0
  60. data/lib/sass/script.rb +35 -8
  61. data/lib/sass/scss/css_parser.rb +2 -1
  62. data/lib/sass/scss/parser.rb +338 -170
  63. data/lib/sass/scss/rx.rb +5 -6
  64. data/lib/sass/scss/script_lexer.rb +1 -0
  65. data/lib/sass/scss/script_parser.rb +1 -0
  66. data/lib/sass/scss/static_parser.rb +23 -6
  67. data/lib/sass/selector/abstract_sequence.rb +2 -2
  68. data/lib/sass/selector/comma_sequence.rb +21 -16
  69. data/lib/sass/selector/sequence.rb +60 -34
  70. data/lib/sass/selector/simple.rb +11 -12
  71. data/lib/sass/selector/simple_sequence.rb +55 -33
  72. data/lib/sass/selector.rb +52 -48
  73. data/lib/sass/source/map.rb +211 -0
  74. data/lib/sass/source/position.rb +39 -0
  75. data/lib/sass/source/range.rb +41 -0
  76. data/lib/sass/stack.rb +120 -0
  77. data/lib/sass/supports.rb +12 -13
  78. data/lib/sass/tree/at_root_node.rb +82 -0
  79. data/lib/sass/tree/comment_node.rb +3 -3
  80. data/lib/sass/tree/css_import_node.rb +11 -11
  81. data/lib/sass/tree/debug_node.rb +2 -2
  82. data/lib/sass/tree/directive_node.rb +13 -2
  83. data/lib/sass/tree/each_node.rb +8 -8
  84. data/lib/sass/tree/extend_node.rb +13 -6
  85. data/lib/sass/tree/for_node.rb +4 -4
  86. data/lib/sass/tree/function_node.rb +5 -4
  87. data/lib/sass/tree/if_node.rb +1 -1
  88. data/lib/sass/tree/import_node.rb +4 -5
  89. data/lib/sass/tree/media_node.rb +4 -14
  90. data/lib/sass/tree/mixin_def_node.rb +4 -4
  91. data/lib/sass/tree/mixin_node.rb +21 -8
  92. data/lib/sass/tree/node.rb +29 -12
  93. data/lib/sass/tree/prop_node.rb +38 -18
  94. data/lib/sass/tree/return_node.rb +3 -2
  95. data/lib/sass/tree/root_node.rb +19 -3
  96. data/lib/sass/tree/rule_node.rb +25 -17
  97. data/lib/sass/tree/supports_node.rb +0 -13
  98. data/lib/sass/tree/trace_node.rb +2 -1
  99. data/lib/sass/tree/variable_node.rb +9 -3
  100. data/lib/sass/tree/visitors/base.rb +6 -6
  101. data/lib/sass/tree/visitors/check_nesting.rb +12 -9
  102. data/lib/sass/tree/visitors/convert.rb +63 -38
  103. data/lib/sass/tree/visitors/cssize.rb +63 -23
  104. data/lib/sass/tree/visitors/deep_copy.rb +6 -5
  105. data/lib/sass/tree/visitors/extend.rb +7 -7
  106. data/lib/sass/tree/visitors/perform.rb +256 -151
  107. data/lib/sass/tree/visitors/set_options.rb +6 -6
  108. data/lib/sass/tree/visitors/to_css.rb +231 -81
  109. data/lib/sass/tree/warn_node.rb +2 -2
  110. data/lib/sass/tree/while_node.rb +2 -2
  111. data/lib/sass/util/multibyte_string_scanner.rb +2 -0
  112. data/lib/sass/util/normalized_map.rb +65 -0
  113. data/lib/sass/util/ordered_hash.rb +188 -0
  114. data/lib/sass/util/subset_map.rb +3 -2
  115. data/lib/sass/util/test.rb +9 -0
  116. data/lib/sass/util.rb +220 -34
  117. data/lib/sass/version.rb +9 -9
  118. data/lib/sass.rb +14 -7
  119. data/test/sass/compiler_test.rb +213 -0
  120. data/test/sass/conversion_test.rb +235 -9
  121. data/test/sass/engine_test.rb +230 -60
  122. data/test/sass/exec_test.rb +86 -0
  123. data/test/sass/extend_test.rb +215 -147
  124. data/test/sass/functions_test.rb +584 -99
  125. data/test/sass/importer_test.rb +165 -17
  126. data/test/sass/plugin_test.rb +19 -13
  127. data/test/sass/script_conversion_test.rb +40 -0
  128. data/test/sass/script_test.rb +231 -21
  129. data/test/sass/scss/css_test.rb +14 -5
  130. data/test/sass/scss/scss_test.rb +1266 -66
  131. data/test/sass/source_map_test.rb +879 -0
  132. data/test/sass/templates/bork5.sass +3 -0
  133. data/test/sass/util/normalized_map_test.rb +30 -0
  134. data/test/sass/util_test.rb +90 -0
  135. data/test/sass/value_helpers_test.rb +181 -0
  136. data/test/test_helper.rb +7 -2
  137. metadata +316 -291
  138. data/lib/sass/script/bool.rb +0 -18
  139. data/lib/sass/script/funcall.rb +0 -231
  140. data/lib/sass/script/list.rb +0 -84
  141. data/lib/sass/script/literal.rb +0 -239
  142. data/lib/sass/script/null.rb +0 -34
  143. data/lib/sass/script/variable.rb +0 -58
  144. data/test/Gemfile +0 -3
  145. data/vendor/listen/CHANGELOG.md +0 -221
  146. data/vendor/listen/CONTRIBUTING.md +0 -38
  147. data/vendor/listen/Gemfile +0 -30
  148. data/vendor/listen/Guardfile +0 -8
  149. data/vendor/listen/LICENSE +0 -20
  150. data/vendor/listen/README.md +0 -315
  151. data/vendor/listen/Rakefile +0 -47
  152. data/vendor/listen/Vagrantfile +0 -96
  153. data/vendor/listen/lib/listen/adapter.rb +0 -214
  154. data/vendor/listen/lib/listen/adapters/bsd.rb +0 -112
  155. data/vendor/listen/lib/listen/adapters/darwin.rb +0 -85
  156. data/vendor/listen/lib/listen/adapters/linux.rb +0 -113
  157. data/vendor/listen/lib/listen/adapters/polling.rb +0 -67
  158. data/vendor/listen/lib/listen/adapters/windows.rb +0 -87
  159. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  160. data/vendor/listen/lib/listen/directory_record.rb +0 -371
  161. data/vendor/listen/lib/listen/listener.rb +0 -225
  162. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  163. data/vendor/listen/lib/listen/turnstile.rb +0 -28
  164. data/vendor/listen/lib/listen/version.rb +0 -3
  165. data/vendor/listen/lib/listen.rb +0 -40
  166. data/vendor/listen/listen.gemspec +0 -22
  167. data/vendor/listen/spec/listen/adapter_spec.rb +0 -183
  168. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +0 -36
  169. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +0 -37
  170. data/vendor/listen/spec/listen/adapters/linux_spec.rb +0 -47
  171. data/vendor/listen/spec/listen/adapters/polling_spec.rb +0 -68
  172. data/vendor/listen/spec/listen/adapters/windows_spec.rb +0 -30
  173. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  174. data/vendor/listen/spec/listen/directory_record_spec.rb +0 -1225
  175. data/vendor/listen/spec/listen/listener_spec.rb +0 -169
  176. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
  177. data/vendor/listen/spec/listen/turnstile_spec.rb +0 -56
  178. data/vendor/listen/spec/listen_spec.rb +0 -73
  179. data/vendor/listen/spec/spec_helper.rb +0 -21
  180. data/vendor/listen/spec/support/adapter_helper.rb +0 -629
  181. data/vendor/listen/spec/support/directory_record_helper.rb +0 -55
  182. data/vendor/listen/spec/support/fixtures_helper.rb +0 -29
  183. data/vendor/listen/spec/support/listeners_helper.rb +0 -156
  184. data/vendor/listen/spec/support/platform_helper.rb +0 -15
@@ -3,7 +3,7 @@ require 'sass/script/lexer'
3
3
  module Sass
4
4
  module Script
5
5
  # The parser for SassScript.
6
- # It parses a string of code into a tree of {Script::Node}s.
6
+ # It parses a string of code into a tree of {Script::Tree::Node}s.
7
7
  class Parser
8
8
  # The line number of the parser's current position.
9
9
  #
@@ -12,11 +12,18 @@ module Sass
12
12
  @lexer.line
13
13
  end
14
14
 
15
+ # The column number of the parser's current position.
16
+ #
17
+ # @return [Fixnum]
18
+ def offset
19
+ @lexer.offset
20
+ end
21
+
15
22
  # @param str [String, StringScanner] The source text to parse
16
23
  # @param line [Fixnum] The line on which the SassScript appears.
17
- # Used for error reporting
18
- # @param offset [Fixnum] The number of characters in on which the SassScript appears.
19
- # Used for error reporting
24
+ # Used for error reporting and sourcemap building
25
+ # @param offset [Fixnum] The character (not byte) offset where the script starts in the line.
26
+ # Used for error reporting and sourcemap building
20
27
  # @param options [{Symbol => Object}] An options hash;
21
28
  # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
22
29
  def initialize(str, line, offset, options = {})
@@ -29,7 +36,7 @@ module Sass
29
36
  # which signals the end of an interpolated segment,
30
37
  # it returns rather than throwing an error.
31
38
  #
32
- # @return [Script::Node] The root node of the parse tree
39
+ # @return [Script::Tree::Node] The root node of the parse tree
33
40
  # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
34
41
  def parse_interpolated
35
42
  expr = assert_expr :expr
@@ -43,7 +50,7 @@ module Sass
43
50
 
44
51
  # Parses a SassScript expression.
45
52
  #
46
- # @return [Script::Node] The root node of the parse tree
53
+ # @return [Script::Tree::Node] The root node of the parse tree
47
54
  # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
48
55
  def parse
49
56
  expr = assert_expr :expr
@@ -58,8 +65,8 @@ module Sass
58
65
  # Parses a SassScript expression,
59
66
  # ending it when it encounters one of the given identifier tokens.
60
67
  #
61
- # @param [#include?(String)] A set of strings that delimit the expression.
62
- # @return [Script::Node] The root node of the parse tree
68
+ # @param tokens [#include?(String)] A set of strings that delimit the expression.
69
+ # @return [Script::Tree::Node] The root node of the parse tree
63
70
  # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
64
71
  def parse_until(tokens)
65
72
  @stop_at = tokens
@@ -74,14 +81,17 @@ module Sass
74
81
 
75
82
  # Parses the argument list for a mixin include.
76
83
  #
77
- # @return [(Array<Script::Node>, {String => Script::Node}, Script::Node)]
84
+ # @return [(Array<Script::Tree::Node>,
85
+ # {String => Script::Tree::Node},
86
+ # Script::Tree::Node,
87
+ # Script::Tree::Node)]
78
88
  # The root nodes of the positional arguments, keyword arguments, and
79
- # splat argument. Keyword arguments are in a hash from names to values.
89
+ # splat argument(s). Keyword arguments are in a hash from names to values.
80
90
  # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
81
91
  def parse_mixin_include_arglist
82
92
  args, keywords = [], {}
83
93
  if try_tok(:lparen)
84
- args, keywords, splat = mixin_arglist || [[], {}]
94
+ args, keywords, splat, kwarg_splat = mixin_arglist
85
95
  assert_tok(:rparen)
86
96
  end
87
97
  assert_done
@@ -89,7 +99,8 @@ module Sass
89
99
  args.each {|a| a.options = @options}
90
100
  keywords.each {|k, v| v.options = @options}
91
101
  splat.options = @options if splat
92
- return args, keywords, splat
102
+ kwarg_splat.options = @options if kwarg_splat
103
+ return args, keywords, splat, kwarg_splat
93
104
  rescue Sass::SyntaxError => e
94
105
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
95
106
  raise e
@@ -97,7 +108,7 @@ module Sass
97
108
 
98
109
  # Parses the argument list for a mixin definition.
99
110
  #
100
- # @return [(Array<Script::Node>, Script::Node)]
111
+ # @return [(Array<Script::Tree::Node>, Script::Tree::Node)]
101
112
  # The root nodes of the arguments, and the splat argument.
102
113
  # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
103
114
  def parse_mixin_definition_arglist
@@ -117,7 +128,7 @@ module Sass
117
128
 
118
129
  # Parses the argument list for a function definition.
119
130
  #
120
- # @return [(Array<Script::Node>, Script::Node)]
131
+ # @return [(Array<Script::Tree::Node>, Script::Tree::Node)]
121
132
  # The root nodes of the arguments, and the splat argument.
122
133
  # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
123
134
  def parse_function_definition_arglist
@@ -138,7 +149,7 @@ module Sass
138
149
  # Parse a single string value, possibly containing interpolation.
139
150
  # Doesn't assert that the scanner is finished after parsing.
140
151
  #
141
- # @return [Script::Node] The root node of the parse tree.
152
+ # @return [Script::Tree::Node] The root node of the parse tree.
142
153
  # @raise [Sass::SyntaxError] if the string isn't valid SassScript
143
154
  def parse_string
144
155
  unless (peek = @lexer.peek) &&
@@ -159,7 +170,7 @@ module Sass
159
170
  # Parses a SassScript expression.
160
171
  #
161
172
  # @overload parse(str, line, offset, filename = nil)
162
- # @return [Script::Node] The root node of the parse tree
173
+ # @return [Script::Tree::Node] The root node of the parse tree
163
174
  # @see Parser#initialize
164
175
  # @see Parser#parse
165
176
  def self.parse(*args)
@@ -205,17 +216,18 @@ module Sass
205
216
  def production(name, sub, *ops)
206
217
  class_eval <<RUBY, __FILE__, __LINE__ + 1
207
218
  def #{name}
208
- interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}) and return interp
219
+ interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect})
220
+ return interp if interp
209
221
  return unless e = #{sub}
210
222
  while tok = try_tok(#{ops.map {|o| o.inspect}.join(', ')})
211
223
  if interp = try_op_before_interp(tok, e)
212
- return interp unless other_interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}, interp)
224
+ other_interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}, interp)
225
+ return interp unless other_interp
213
226
  return other_interp
214
227
  end
215
228
 
216
- line = @lexer.line
217
- e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type)
218
- e.line = line
229
+ start_pos = source_position
230
+ e = node(Tree::Operation.new(e, assert_expr(#{sub.inspect}), tok.type), start_pos)
219
231
  end
220
232
  e
221
233
  end
@@ -226,11 +238,10 @@ RUBY
226
238
  class_eval <<RUBY, __FILE__, __LINE__ + 1
227
239
  def unary_#{op}
228
240
  return #{sub} unless tok = try_tok(:#{op})
229
- interp = try_op_before_interp(tok) and return interp
230
- line = @lexer.line
231
- op = UnaryOperation.new(assert_expr(:unary_#{op}), :#{op})
232
- op.line = line
233
- op
241
+ interp = try_op_before_interp(tok)
242
+ return interp if interp
243
+ start_pos = source_position
244
+ node(Tree::UnaryOperation.new(assert_expr(:unary_#{op}), :#{op}), start_pos)
234
245
  end
235
246
  RUBY
236
247
  end
@@ -238,22 +249,61 @@ RUBY
238
249
 
239
250
  private
240
251
 
252
+ def source_position
253
+ Sass::Source::Position.new(line, offset)
254
+ end
255
+
256
+ def range(start_pos, end_pos = source_position)
257
+ Sass::Source::Range.new(start_pos, end_pos, @options[:filename], @options[:importer])
258
+ end
259
+
241
260
  # @private
242
261
  def lexer_class; Lexer; end
243
262
 
263
+ def map
264
+ start_pos = source_position
265
+ e = interpolation
266
+ return unless e
267
+ return list e, start_pos unless @lexer.peek && @lexer.peek.type == :colon
268
+
269
+ key, value = map_pair(e)
270
+ map = node(Sass::Script::Tree::MapLiteral.new([[key, value]]), start_pos)
271
+ while try_tok(:comma)
272
+ return map unless (pair = map_pair)
273
+ key, value = pair
274
+ map.pairs << [key, value]
275
+ end
276
+ map
277
+ end
278
+
279
+ def map_pair(key = nil)
280
+ return unless key ||= interpolation
281
+ assert_tok :colon
282
+ return key, assert_expr(:interpolation)
283
+ end
284
+
244
285
  def expr
245
- interp = try_ops_after_interp([:comma], :expr) and return interp
246
- line = @lexer.line
247
- return unless e = interpolation
248
- arr = [e]
249
- while tok = try_tok(:comma)
250
- if interp = try_op_before_interp(tok, e)
251
- return interp unless other_interp = try_ops_after_interp([:comma], :expr, interp)
286
+ start_pos = source_position
287
+ e = interpolation
288
+ return unless e
289
+ list e, start_pos
290
+ end
291
+
292
+ def list(first, start_pos)
293
+ return first unless @lexer.peek && @lexer.peek.type == :comma
294
+
295
+ list = node(Sass::Script::Tree::ListLiteral.new([first], :comma), start_pos)
296
+ while (tok = try_tok(:comma))
297
+ element_before_interp = list.elements.length == 1 ? list.elements.first : list
298
+ if (interp = try_op_before_interp(tok, element_before_interp))
299
+ other_interp = try_ops_after_interp([:comma], :expr, interp)
300
+ return interp unless other_interp
252
301
  return other_interp
253
302
  end
254
- arr << assert_expr(:interpolation)
303
+ return list unless (e = interpolation)
304
+ list.elements << e
255
305
  end
256
- arr.size == 1 ? arr.first : node(List.new(arr, :comma), line)
306
+ list
257
307
  end
258
308
 
259
309
  production :equals, :interpolation, :single_eq
@@ -261,47 +311,57 @@ RUBY
261
311
  def try_op_before_interp(op, prev = nil)
262
312
  return unless @lexer.peek && @lexer.peek.type == :begin_interpolation
263
313
  wb = @lexer.whitespace?(op)
264
- str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
265
- str.line = @lexer.line
266
- interp = Script::Interpolation.new(prev, str, nil, wb, !:wa, :originally_text)
267
- interp.line = @lexer.line
314
+ str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]),
315
+ op.source_range)
316
+ interp = node(
317
+ Script::Tree::Interpolation.new(prev, str, nil, wb, !:wa, :originally_text),
318
+ (prev || str).source_range.start_pos)
268
319
  interpolation(interp)
269
320
  end
270
321
 
271
322
  def try_ops_after_interp(ops, name, prev = nil)
272
323
  return unless @lexer.after_interpolation?
273
- return unless op = try_tok(*ops)
274
- interp = try_op_before_interp(op, prev) and return interp
324
+ op = try_tok(*ops)
325
+ return unless op
326
+ interp = try_op_before_interp(op, prev)
327
+ return interp if interp
275
328
 
276
329
  wa = @lexer.whitespace?
277
- str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
330
+ str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]),
331
+ op.source_range)
278
332
  str.line = @lexer.line
279
- interp = Script::Interpolation.new(prev, str, assert_expr(name), !:wb, wa, :originally_text)
280
- interp.line = @lexer.line
281
- return interp
333
+ interp = node(
334
+ Script::Tree::Interpolation.new(prev, str, assert_expr(name), !:wb, wa, :originally_text),
335
+ (prev || str).source_range.start_pos)
336
+ interp
282
337
  end
283
338
 
284
339
  def interpolation(first = space)
285
340
  e = first
286
- while interp = try_tok(:begin_interpolation)
341
+ while (interp = try_tok(:begin_interpolation))
287
342
  wb = @lexer.whitespace?(interp)
288
- line = @lexer.line
289
343
  mid = parse_interpolated
290
344
  wa = @lexer.whitespace?
291
- e = Script::Interpolation.new(e, mid, space, wb, wa)
292
- e.line = line
345
+ e = node(
346
+ Script::Tree::Interpolation.new(e, mid, space, wb, wa),
347
+ (e || mid).source_range.start_pos)
293
348
  end
294
349
  e
295
350
  end
296
351
 
297
352
  def space
298
- line = @lexer.line
299
- return unless e = or_expr
353
+ start_pos = source_position
354
+ e = or_expr
355
+ return unless e
300
356
  arr = [e]
301
- while e = or_expr
357
+ while (e = or_expr)
302
358
  arr << e
303
359
  end
304
- arr.size == 1 ? arr.first : node(List.new(arr, :space), line)
360
+ if arr.size == 1
361
+ arr.first
362
+ else
363
+ node(Sass::Script::Tree::ListLiteral.new(arr, :space), start_pos)
364
+ end
305
365
  end
306
366
 
307
367
  production :or_expr, :and_expr, :or
@@ -321,17 +381,19 @@ RUBY
321
381
  return if @stop_at && @stop_at.include?(@lexer.peek.value)
322
382
 
323
383
  name = @lexer.next
324
- if color = Color::COLOR_NAMES[name.value.downcase]
325
- return node(Color.new(color))
384
+ if (color = Sass::Script::Value::Color::COLOR_NAMES[name.value.downcase])
385
+ return literal_node(Sass::Script::Value::Color.new(color), name.source_range)
326
386
  end
327
- node(Script::String.new(name.value, :identifier))
387
+ literal_node(Script::Value::String.new(name.value, :identifier), name.source_range)
328
388
  end
329
389
 
330
390
  def funcall
331
- return raw unless tok = try_tok(:funcall)
332
- args, keywords, splat = fn_arglist || [[], {}]
391
+ tok = try_tok(:funcall)
392
+ return raw unless tok
393
+ args, keywords, splat, kwarg_splat = fn_arglist
333
394
  assert_tok(:rparen)
334
- node(Script::Funcall.new(tok.value, args, keywords, splat))
395
+ node(Script::Tree::Funcall.new(tok.value, args, keywords, splat, kwarg_splat),
396
+ tok.source_range.start_pos, source_position)
335
397
  end
336
398
 
337
399
  def defn_arglist!(must_have_parens)
@@ -347,12 +409,13 @@ RUBY
347
409
  must_have_default = false
348
410
  loop do
349
411
  c = assert_tok(:const)
350
- var = Script::Variable.new(c.value)
412
+ var = node(Script::Tree::Variable.new(c.value), c.source_range)
351
413
  if try_tok(:colon)
352
414
  val = assert_expr(:space)
353
415
  must_have_default = true
354
416
  elsif must_have_default
355
- raise SyntaxError.new("Required argument #{var.inspect} must come before any optional arguments.")
417
+ raise SyntaxError.new(
418
+ "Required argument #{var.inspect} must come before any optional arguments.")
356
419
  elsif try_tok(:splat)
357
420
  splat = var
358
421
  break
@@ -373,48 +436,65 @@ RUBY
373
436
  end
374
437
 
375
438
  def arglist(subexpr, description)
376
- return unless e = send(subexpr)
377
-
378
439
  args = []
379
- keywords = {}
440
+ keywords = Sass::Util::NormalizedMap.new
441
+ e = send(subexpr)
442
+
443
+ return [args, keywords] unless e
444
+
445
+ splat = nil
380
446
  loop do
381
447
  if @lexer.peek && @lexer.peek.type == :colon
382
448
  name = e
383
- @lexer.expected!("comma") unless name.is_a?(Variable)
449
+ @lexer.expected!("comma") unless name.is_a?(Tree::Variable)
384
450
  assert_tok(:colon)
385
451
  value = assert_expr(subexpr, description)
386
452
 
387
- if keywords[name.underscored_name]
453
+ if keywords[name.name]
388
454
  raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once")
389
455
  end
390
456
 
391
- keywords[name.underscored_name] = value
457
+ keywords[name.name] = value
392
458
  else
393
- if !keywords.empty?
459
+ if try_tok(:splat)
460
+ return args, keywords, splat, e if splat
461
+ splat, e = e, nil
462
+ elsif splat
463
+ raise SyntaxError.new("Only keyword arguments may follow variable arguments (...).")
464
+ elsif !keywords.empty?
394
465
  raise SyntaxError.new("Positional arguments must come before keyword arguments.")
395
466
  end
396
467
 
397
- return args, keywords, e if try_tok(:splat)
398
- args << e
468
+ args << e if e
399
469
  end
400
470
 
401
- return args, keywords unless try_tok(:comma)
471
+ return args, keywords, splat unless try_tok(:comma)
402
472
  e = assert_expr(subexpr, description)
403
473
  end
404
474
  end
405
475
 
406
476
  def raw
407
- return special_fun unless tok = try_tok(:raw)
408
- node(Script::String.new(tok.value))
477
+ tok = try_tok(:raw)
478
+ return special_fun unless tok
479
+ literal_node(Script::Value::String.new(tok.value), tok.source_range)
409
480
  end
410
481
 
411
482
  def special_fun
412
- return paren unless tok = try_tok(:special_fun)
413
- first = node(Script::String.new(tok.value.first))
483
+ start_pos = source_position
484
+ tok = try_tok(:special_fun)
485
+ return paren unless tok
486
+ first = literal_node(Script::Value::String.new(tok.value.first),
487
+ start_pos, start_pos.after(tok.value.first))
414
488
  Sass::Util.enum_slice(tok.value[1..-1], 2).inject(first) do |l, (i, r)|
415
- Script::Interpolation.new(
416
- l, i, r && node(Script::String.new(r)),
417
- false, false)
489
+ end_pos = i.source_range.end_pos
490
+ end_pos = end_pos.after(r) if r
491
+ node(
492
+ Script::Tree::Interpolation.new(
493
+ l, i,
494
+ r && literal_node(Script::Value::String.new(r),
495
+ i.source_range.end_pos, end_pos),
496
+ false, false),
497
+ start_pos, end_pos)
418
498
  end
419
499
  end
420
500
 
@@ -422,39 +502,49 @@ RUBY
422
502
  return variable unless try_tok(:lparen)
423
503
  was_in_parens = @in_parens
424
504
  @in_parens = true
425
- line = @lexer.line
426
- e = expr
505
+ start_pos = source_position
506
+ e = map
507
+ end_pos = source_position
427
508
  assert_tok(:rparen)
428
- return e || node(List.new([], :space), line)
509
+ return e || node(Sass::Script::Tree::ListLiteral.new([], nil), start_pos, end_pos)
429
510
  ensure
430
511
  @in_parens = was_in_parens
431
512
  end
432
513
 
433
514
  def variable
434
- return string unless c = try_tok(:const)
435
- node(Variable.new(*c.value))
515
+ start_pos = source_position
516
+ c = try_tok(:const)
517
+ return string unless c
518
+ node(Tree::Variable.new(*c.value), start_pos)
436
519
  end
437
520
 
438
521
  def string
439
- return number unless first = try_tok(:string)
440
- return first.value unless try_tok(:begin_interpolation)
441
- line = @lexer.line
522
+ first = try_tok(:string)
523
+ return number unless first
524
+ str = literal_node(first.value, first.source_range)
525
+ return str unless try_tok(:begin_interpolation)
442
526
  mid = parse_interpolated
443
527
  last = assert_expr(:string)
444
- interp = StringInterpolation.new(first.value, mid, last)
445
- interp.line = line
446
- interp
528
+ node(Tree::StringInterpolation.new(str, mid, last), first.source_range.start_pos)
447
529
  end
448
530
 
449
531
  def number
450
- return literal unless tok = try_tok(:number)
532
+ tok = try_tok(:number)
533
+ return selector unless tok
451
534
  num = tok.value
452
535
  num.original = num.to_s unless @in_parens
453
- num
536
+ literal_node(num, tok.source_range.start_pos)
537
+ end
538
+
539
+ def selector
540
+ tok = try_tok(:selector)
541
+ return literal unless tok
542
+ node(tok.value, tok.source_range.start_pos)
454
543
  end
455
544
 
456
545
  def literal
457
- (t = try_tok(:color, :bool, :null)) && (return t.value)
546
+ t = try_tok(:color, :bool, :null)
547
+ return literal_node(t.value, t.source_range) if t
458
548
  end
459
549
 
460
550
  # It would be possible to have unified #assert and #try methods,
@@ -465,20 +555,23 @@ RUBY
465
555
  :default => "expression (e.g. 1px, bold)",
466
556
  :mixin_arglist => "mixin argument",
467
557
  :fn_arglist => "function argument",
558
+ :splat => "...",
468
559
  }
469
560
 
470
561
  def assert_expr(name, expected = nil)
471
- (e = send(name)) && (return e)
562
+ e = send(name)
563
+ return e if e
472
564
  @lexer.expected!(expected || EXPR_NAMES[name] || EXPR_NAMES[:default])
473
565
  end
474
566
 
475
567
  def assert_tok(*names)
476
- (t = try_tok(*names)) && (return t)
568
+ t = try_tok(*names)
569
+ return t if t
477
570
  @lexer.expected!(names.map {|tok| Lexer::TOKEN_NAMES[tok] || tok}.join(" or "))
478
571
  end
479
572
 
480
573
  def try_tok(*names)
481
- peeked = @lexer.peek
574
+ peeked = @lexer.peek
482
575
  peeked && names.include?(peeked.type) && @lexer.next
483
576
  end
484
577
 
@@ -487,8 +580,35 @@ RUBY
487
580
  @lexer.expected!(EXPR_NAMES[:default])
488
581
  end
489
582
 
490
- def node(node, line = @lexer.line)
491
- node.line = line
583
+ # @overload node(value, source_range)
584
+ # @param value [Sass::Script::Value::Base]
585
+ # @param source_range [Sass::Source::Range]
586
+ # @overload node(value, start_pos, end_pos = source_position)
587
+ # @param value [Sass::Script::Value::Base]
588
+ # @param start_pos [Sass::Source::Position]
589
+ # @param end_pos [Sass::Source::Position]
590
+ def literal_node(value, source_range_or_start_pos, end_pos = source_position)
591
+ node(Sass::Script::Tree::Literal.new(value), source_range_or_start_pos, end_pos)
592
+ end
593
+
594
+ # @overload node(node, source_range)
595
+ # @param node [Sass::Script::Tree::Node]
596
+ # @param source_range [Sass::Source::Range]
597
+ # @overload node(node, start_pos, end_pos = source_position)
598
+ # @param node [Sass::Script::Tree::Node]
599
+ # @param start_pos [Sass::Source::Position]
600
+ # @param end_pos [Sass::Source::Position]
601
+ def node(node, source_range_or_start_pos, end_pos = source_position)
602
+ source_range =
603
+ if source_range_or_start_pos.is_a?(Sass::Source::Range)
604
+ source_range_or_start_pos
605
+ else
606
+ range(source_range_or_start_pos, end_pos)
607
+ end
608
+
609
+ node.line = source_range.start_pos.line
610
+ node.source_range = source_range
611
+ node.filename = @options[:filename]
492
612
  node
493
613
  end
494
614
  end