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