haml-edge 2.3.179 → 2.3.180

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. data/EDGE_GEM_VERSION +1 -1
  2. data/README.md +88 -149
  3. data/VERSION +1 -1
  4. data/bin/css2sass +7 -1
  5. data/bin/sass-convert +7 -0
  6. data/lib/haml/exec.rb +95 -22
  7. data/lib/haml/template.rb +1 -1
  8. data/lib/haml/util.rb +50 -0
  9. data/lib/sass.rb +1 -1
  10. data/lib/sass/css.rb +38 -210
  11. data/lib/sass/engine.rb +121 -47
  12. data/lib/sass/files.rb +28 -19
  13. data/lib/sass/plugin.rb +32 -43
  14. data/lib/sass/repl.rb +1 -1
  15. data/lib/sass/script.rb +25 -6
  16. data/lib/sass/script/bool.rb +1 -0
  17. data/lib/sass/script/color.rb +2 -2
  18. data/lib/sass/script/css_lexer.rb +22 -0
  19. data/lib/sass/script/css_parser.rb +28 -0
  20. data/lib/sass/script/funcall.rb +17 -9
  21. data/lib/sass/script/functions.rb +46 -1
  22. data/lib/sass/script/interpolation.rb +42 -0
  23. data/lib/sass/script/lexer.rb +142 -34
  24. data/lib/sass/script/literal.rb +28 -12
  25. data/lib/sass/script/node.rb +57 -1
  26. data/lib/sass/script/number.rb +18 -3
  27. data/lib/sass/script/operation.rb +44 -8
  28. data/lib/sass/script/parser.rb +149 -24
  29. data/lib/sass/script/string.rb +50 -2
  30. data/lib/sass/script/unary_operation.rb +25 -10
  31. data/lib/sass/script/variable.rb +20 -11
  32. data/lib/sass/scss.rb +14 -0
  33. data/lib/sass/scss/css_parser.rb +39 -0
  34. data/lib/sass/scss/parser.rb +683 -0
  35. data/lib/sass/scss/rx.rb +112 -0
  36. data/lib/sass/scss/script_lexer.rb +13 -0
  37. data/lib/sass/scss/script_parser.rb +25 -0
  38. data/lib/sass/tree/comment_node.rb +58 -16
  39. data/lib/sass/tree/debug_node.rb +7 -2
  40. data/lib/sass/tree/directive_node.rb +38 -34
  41. data/lib/sass/tree/for_node.rb +6 -0
  42. data/lib/sass/tree/if_node.rb +13 -0
  43. data/lib/sass/tree/import_node.rb +26 -7
  44. data/lib/sass/tree/mixin_def_node.rb +18 -0
  45. data/lib/sass/tree/mixin_node.rb +16 -1
  46. data/lib/sass/tree/node.rb +98 -27
  47. data/lib/sass/tree/prop_node.rb +97 -20
  48. data/lib/sass/tree/root_node.rb +37 -0
  49. data/lib/sass/tree/rule_node.rb +88 -60
  50. data/lib/sass/tree/variable_node.rb +9 -5
  51. data/lib/sass/tree/while_node.rb +4 -0
  52. data/test/haml/results/filters.xhtml +1 -1
  53. data/test/haml/util_test.rb +28 -0
  54. data/test/sass/conversion_test.rb +884 -0
  55. data/test/sass/css2sass_test.rb +46 -21
  56. data/test/sass/engine_test.rb +680 -160
  57. data/test/sass/functions_test.rb +27 -0
  58. data/test/sass/more_results/more_import.css +1 -1
  59. data/test/sass/more_templates/more_import.sass +3 -3
  60. data/test/sass/plugin_test.rb +28 -8
  61. data/test/sass/results/compact.css +1 -1
  62. data/test/sass/results/complex.css +5 -5
  63. data/test/sass/results/compressed.css +1 -1
  64. data/test/sass/results/expanded.css +1 -1
  65. data/test/sass/results/import.css +3 -1
  66. data/test/sass/results/mixins.css +12 -12
  67. data/test/sass/results/nested.css +1 -1
  68. data/test/sass/results/parent_ref.css +4 -4
  69. data/test/sass/results/script.css +3 -3
  70. data/test/sass/results/scss_import.css +15 -0
  71. data/test/sass/results/scss_importee.css +2 -0
  72. data/test/sass/script_conversion_test.rb +153 -0
  73. data/test/sass/script_test.rb +44 -54
  74. data/test/sass/scss/css_test.rb +811 -0
  75. data/test/sass/scss/rx_test.rb +156 -0
  76. data/test/sass/scss/scss_test.rb +871 -0
  77. data/test/sass/scss/test_helper.rb +37 -0
  78. data/test/sass/templates/alt.sass +2 -2
  79. data/test/sass/templates/bork1.sass +1 -1
  80. data/test/sass/templates/import.sass +4 -4
  81. data/test/sass/templates/importee.sass +3 -3
  82. data/test/sass/templates/line_numbers.sass +1 -1
  83. data/test/sass/templates/mixins.sass +2 -2
  84. data/test/sass/templates/nested_mixin_bork.sass +1 -1
  85. data/test/sass/templates/options.sass +1 -1
  86. data/test/sass/templates/parent_ref.sass +2 -2
  87. data/test/sass/templates/script.sass +69 -69
  88. data/test/sass/templates/scss_import.scss +10 -0
  89. data/test/sass/templates/scss_importee.scss +1 -0
  90. data/test/sass/templates/units.sass +10 -10
  91. data/test/test_helper.rb +4 -4
  92. metadata +27 -2
@@ -1,12 +1,60 @@
1
1
  require 'sass/script/literal'
2
2
 
3
3
  module Sass::Script
4
- # A SassScript object representing a string of text.
4
+ # A SassScript object representing a CSS string *or* a CSS identifier.
5
5
  class String < Literal
6
6
  # The Ruby value of the string.
7
7
  #
8
8
  # @return [String]
9
9
  attr_reader :value
10
- alias_method :to_s, :value
10
+
11
+ # Whether this is a CSS string or a CSS identifier.
12
+ # The difference is that strings are written with double-quotes,
13
+ # while identifiers aren't.
14
+ #
15
+ # @return [Symbol] `:string` or `:identifier`
16
+ attr_reader :type
17
+
18
+ def context=(context)
19
+ super
20
+ @type = :identifier if context == :equals
21
+ end
22
+
23
+ # Creates a new string.
24
+ #
25
+ # @param value [String] See \{#value}
26
+ # @param type [Symbol] See \{#type}
27
+ def initialize(value, type = :identifier)
28
+ super(value)
29
+ @type = type
30
+ end
31
+
32
+ def plus(other)
33
+ other_str = other.is_a?(Sass::Script::String) ? other.value : other.to_s
34
+ Sass::Script::String.new(self.value + other_str, self.type)
35
+ end
36
+
37
+ # @see Node#to_s
38
+ def to_s
39
+ to_sass
40
+ end
41
+
42
+ # @param type [Symbol] The type of string to render this as.
43
+ # `:string`s have double quotes, `:identifier`s do not.
44
+ # @see Node#to_sass
45
+ def to_sass(type = self.type)
46
+ if type == :identifier
47
+ if context == :equals && Sass::SCSS::RX.escape_ident(self.value).include?(?\\)
48
+ return "unquote(#{Sass::Script::String.new(self.value, :string).to_sass})"
49
+ end
50
+ return self.value
51
+ end
52
+
53
+ # Replace single backslashes with double. Really.
54
+ value = self.value.gsub("\\", "\\\\\\\\")
55
+ return "\"#{value}\"" unless value.include?('"')
56
+ return "'#{value}'" unless value.include?("'")
57
+ "\"#{value.gsub('"', "\\\"")}\"" #'
58
+ end
11
59
  end
12
60
  end
@@ -1,6 +1,6 @@
1
1
  module Sass::Script
2
2
  # A SassScript parse node representing a unary operation,
3
- # such as `-!b` or `not true`.
3
+ # such as `-$b` or `not true`.
4
4
  #
5
5
  # Currently only `-`, `/`, and `not` are unary operators.
6
6
  class UnaryOperation < Node
@@ -10,6 +10,7 @@ module Sass::Script
10
10
  def initialize(operand, operator)
11
11
  @operand = operand
12
12
  @operator = operator
13
+ super()
13
14
  end
14
15
 
15
16
  # @return [String] A human-readable s-expression representation of the operation
@@ -17,12 +18,34 @@ module Sass::Script
17
18
  "(#{@operator.inspect} #{@operand.inspect})"
18
19
  end
19
20
 
21
+ # @see Node#to_sass
22
+ def to_sass
23
+ operand = @operand.to_sass
24
+ if @operand.is_a?(Operation) ||
25
+ (@operator == :minus &&
26
+ (operand =~ Sass::SCSS::RX::IDENT) == 0)
27
+ operand = "(#{@operand.to_sass})"
28
+ end
29
+ op = Lexer::OPERATORS_REVERSE[@operator]
30
+ op + (op =~ /[a-z]/ ? " " : "") + operand
31
+ end
32
+
33
+ # Returns the operand of the operation.
34
+ #
35
+ # @return [Array<Node>]
36
+ # @see Node#children
37
+ def children
38
+ [@operand]
39
+ end
40
+
41
+ protected
42
+
20
43
  # Evaluates the operation.
21
44
  #
22
45
  # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
23
46
  # @return [Literal] The SassScript object that is the value of the operation
24
47
  # @raise [Sass::SyntaxError] if the operation is undefined for the operand
25
- def perform(environment)
48
+ def _perform(environment)
26
49
  operator = "unary_#{@operator}"
27
50
  literal = @operand.perform(environment)
28
51
  literal.send(operator)
@@ -30,13 +53,5 @@ module Sass::Script
30
53
  raise e unless e.name.to_s == operator.to_s
31
54
  raise Sass::SyntaxError.new("Undefined unary operation: \"#{@operator} #{literal}\".")
32
55
  end
33
-
34
- # Returns the operand of the operation.
35
- #
36
- # @return [Array<Node>]
37
- # @see Node#children
38
- def children
39
- [@operand]
40
- end
41
56
  end
42
57
  end
@@ -10,22 +10,15 @@ module Sass
10
10
  # @param name [String] See \{#name}
11
11
  def initialize(name)
12
12
  @name = name
13
+ super()
13
14
  end
14
15
 
15
16
  # @return [String] A string representation of the variable
16
17
  def inspect
17
- "!#{name}"
18
- end
19
-
20
- # Evaluates the variable.
21
- #
22
- # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
23
- # @return [Literal] The SassScript object that is the value of the variable
24
- # @raise [Sass::SyntaxError] if the variable is undefined
25
- def perform(environment)
26
- (val = environment.var(name)) && (return val)
27
- raise SyntaxError.new("Undefined variable: \"!#{name}\".")
18
+ return "!important" if name == "important"
19
+ "$#{name}"
28
20
  end
21
+ alias_method :to_sass, :inspect
29
22
 
30
23
  # Returns an empty array.
31
24
  #
@@ -34,6 +27,22 @@ module Sass
34
27
  def children
35
28
  []
36
29
  end
30
+
31
+ protected
32
+
33
+ # Evaluates the variable.
34
+ #
35
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
36
+ # @return [Literal] The SassScript object that is the value of the variable
37
+ # @raise [Sass::SyntaxError] if the variable is undefined
38
+ def _perform(environment)
39
+ raise SyntaxError.new("Undefined variable: \"$#{name}\".") unless val = environment.var(name)
40
+ if val.is_a?(Number)
41
+ val = val.dup
42
+ val.original = nil
43
+ end
44
+ return val
45
+ end
37
46
  end
38
47
  end
39
48
  end
data/lib/sass/scss.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'sass/scss/rx'
2
+ require 'sass/scss/script_lexer'
3
+ require 'sass/scss/script_parser'
4
+ require 'sass/scss/parser'
5
+
6
+ module Sass
7
+ # SCSS is the CSS syntax for Sass.
8
+ # It parses into the same syntax tree as Sass,
9
+ # and generates the same sort of output CSS.
10
+ #
11
+ # This module contains code for the parsing of SCSS.
12
+ # The evaluation is handled by the broader {Sass} module.
13
+ module SCSS; end
14
+ end
@@ -0,0 +1,39 @@
1
+ require 'sass/script/css_parser'
2
+
3
+ module Sass
4
+ module SCSS
5
+ class CssParser < Parser
6
+ private
7
+
8
+ def variable; nil; end
9
+ def parent_selector; nil; end
10
+ def interpolation; nil; end
11
+ def interp_string; tok(STRING); end
12
+ def expected_property_separator; '":"'; end
13
+ def use_css_import?; true; end
14
+
15
+ def special_directive(name)
16
+ return unless name == 'media' || name == 'import'
17
+ super
18
+ end
19
+
20
+ def block_child(context)
21
+ case context
22
+ when :ruleset
23
+ declaration
24
+ when :stylesheet
25
+ directive || ruleset
26
+ when :directive
27
+ directive || declaration_or_ruleset
28
+ end
29
+ end
30
+
31
+ def nested_properties!(node, space)
32
+ expected('expression (e.g. 1px, bold)');
33
+ end
34
+
35
+ @sass_script_parser = Class.new(Sass::Script::CssParser)
36
+ @sass_script_parser.send(:include, ScriptParser)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,683 @@
1
+ require 'strscan'
2
+ require 'set'
3
+
4
+ module Sass
5
+ module SCSS
6
+ # The parser for SCSS.
7
+ # It parses a string of code into a tree of {Sass::Tree::Node}s.
8
+ class Parser
9
+ # @param str [String] The source document to parse
10
+ def initialize(str)
11
+ @template = str
12
+ @line = 1
13
+ @strs = []
14
+ end
15
+
16
+ # Parses an SCSS document.
17
+ #
18
+ # @return [Sass::Tree::RootNode] The root node of the document tree
19
+ # @raise [Sass::SyntaxError] if there's a syntax error in the document
20
+ def parse
21
+ @scanner = StringScanner.new(
22
+ Haml::Util.check_encoding(@template) do |msg, line|
23
+ raise Sass::SyntaxError.new(msg, :line => line)
24
+ end.gsub("\r", ""))
25
+ root = stylesheet
26
+ expected("selector or at-rule") unless @scanner.eos?
27
+ root
28
+ end
29
+
30
+ private
31
+
32
+ include Sass::SCSS::RX
33
+
34
+ def stylesheet
35
+ node = node(Sass::Tree::RootNode.new(@scanner.string))
36
+ block_contents(node, :stylesheet) {s(node)}
37
+ end
38
+
39
+ def s(node)
40
+ while tok(S) || tok(CDC) || tok(CDO) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
41
+ next unless c
42
+ process_comment c, node
43
+ c = nil
44
+ end
45
+ true
46
+ end
47
+
48
+ def ss
49
+ nil while tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
50
+ true
51
+ end
52
+
53
+ def ss_comments(node)
54
+ while tok(S) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
55
+ next unless c
56
+ process_comment c, node
57
+ c = nil
58
+ end
59
+
60
+ true
61
+ end
62
+
63
+ def whitespace
64
+ return unless tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
65
+ ss
66
+ end
67
+
68
+ def process_comment(text, node)
69
+ single_line = text =~ /^\/\//
70
+ pre_str = single_line ? "" : @scanner.
71
+ string[0...@scanner.pos].
72
+ reverse[/.*?\*\/(.*?)($|\Z)/, 1].
73
+ reverse.gsub(/[^\s]/, ' ')
74
+ text = text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */' if single_line
75
+ comment = Sass::Tree::CommentNode.new(pre_str + text, single_line)
76
+ comment.line = @line - text.count("\n")
77
+ node << comment
78
+ end
79
+
80
+ DIRECTIVES = Set[:mixin, :include, :debug, :for, :while, :if, :import, :media]
81
+
82
+ def directive
83
+ return unless tok(/@/)
84
+ name = tok!(IDENT)
85
+ ss
86
+
87
+ if dir = special_directive(name)
88
+ return dir
89
+ end
90
+
91
+ val = str do
92
+ # Most at-rules take expressions (e.g. @import),
93
+ # but some (e.g. @page) take selector-like arguments
94
+ expr || selector
95
+ end
96
+ node = node(Sass::Tree::DirectiveNode.new("@#{name} #{val}".strip))
97
+
98
+ if tok(/\{/)
99
+ node.has_children = true
100
+ block_contents(node, :directive)
101
+ tok!(/\}/)
102
+ end
103
+
104
+ node
105
+ end
106
+
107
+ def special_directive(name)
108
+ sym = name.gsub('-', '_').to_sym
109
+ DIRECTIVES.include?(sym) && send(sym)
110
+ end
111
+
112
+ def mixin
113
+ name = tok! IDENT
114
+ args = sass_script(:parse_mixin_definition_arglist)
115
+ ss
116
+ block(node(Sass::Tree::MixinDefNode.new(name, args)), :directive)
117
+ end
118
+
119
+ def include
120
+ name = tok! IDENT
121
+ args = sass_script(:parse_mixin_include_arglist)
122
+ ss
123
+ node(Sass::Tree::MixinNode.new(name, args))
124
+ end
125
+
126
+ def debug
127
+ node(Sass::Tree::DebugNode.new(sass_script(:parse)))
128
+ end
129
+
130
+ def for
131
+ tok!(/\$/)
132
+ var = tok! IDENT
133
+ ss
134
+
135
+ tok!(/from/)
136
+ from = sass_script(:parse_until, Set["to", "through"])
137
+ ss
138
+
139
+ @expected = '"to" or "through"'
140
+ exclusive = (tok(/to/) || tok!(/through/)) == 'to'
141
+ to = sass_script(:parse)
142
+ ss
143
+
144
+ block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)), :directive)
145
+ end
146
+
147
+ def while
148
+ expr = sass_script(:parse)
149
+ ss
150
+ block(node(Sass::Tree::WhileNode.new(expr)), :directive)
151
+ end
152
+
153
+ def if
154
+ expr = sass_script(:parse)
155
+ ss
156
+ node = block(node(Sass::Tree::IfNode.new(expr)), :directive)
157
+ ss
158
+ else_block(node)
159
+ end
160
+
161
+ def else_block(node)
162
+ return node unless tok(/@else/)
163
+ ss
164
+ else_node = block(
165
+ Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
166
+ :directive)
167
+ node.add_else(else_node)
168
+ ss
169
+ else_block(node)
170
+ end
171
+
172
+ def import
173
+ @expected = "string or url()"
174
+ arg = tok(STRING) || tok!(URI)
175
+ path = @scanner[1] || @scanner[2] || @scanner[3]
176
+ ss
177
+
178
+ media = str {media_query_list}.strip
179
+
180
+ if !media.strip.empty? || use_css_import?
181
+ return node(Sass::Tree::DirectiveNode.new("@import #{arg} #{media}".strip))
182
+ end
183
+
184
+ node(Sass::Tree::ImportNode.new(path.strip))
185
+ end
186
+
187
+ def use_css_import?; false; end
188
+
189
+ def media
190
+ val = str {media_query_list}.strip
191
+ block(node(Sass::Tree::DirectiveNode.new("@media #{val}")), :directive)
192
+ end
193
+
194
+ # http://www.w3.org/TR/css3-mediaqueries/#syntax
195
+ def media_query_list
196
+ return unless media_query
197
+
198
+ ss
199
+ while tok(/,/)
200
+ ss; expr!(:media_query); ss
201
+ end
202
+
203
+ true
204
+ end
205
+
206
+ def media_query
207
+ if tok(/only|not/i)
208
+ ss
209
+ @expected = "media type (e.g. print, screen)"
210
+ tok!(IDENT)
211
+ ss
212
+ elsif !tok(IDENT) && !media_expr
213
+ return
214
+ end
215
+
216
+ ss
217
+ while tok(/and/i)
218
+ ss; expr!(:media_expr); ss
219
+ end
220
+
221
+ true
222
+ end
223
+
224
+ def media_expr
225
+ return unless tok(/\(/)
226
+ ss
227
+ @expected = "media feature (e.g. min-device-width, color)"
228
+ tok!(IDENT)
229
+ ss
230
+
231
+ if tok(/:/)
232
+ ss; expr!(:expr)
233
+ end
234
+ tok!(/\)/)
235
+ ss
236
+
237
+ true
238
+ end
239
+
240
+ def variable
241
+ return unless tok(/\$/)
242
+ name = tok!(IDENT)
243
+ ss; tok!(/:/); ss
244
+
245
+ expr = sass_script(:parse)
246
+ guarded = tok(DEFAULT)
247
+ node(Sass::Tree::VariableNode.new(name, expr, guarded))
248
+ end
249
+
250
+ def operator
251
+ # Many of these operators (all except / and ,)
252
+ # are disallowed by the CSS spec,
253
+ # but they're included here for compatibility
254
+ # with some proprietary MS properties
255
+ str {ss if tok(/[\/,:.=]/)}
256
+ end
257
+
258
+ def unary_operator
259
+ tok(/[+-]/)
260
+ end
261
+
262
+ def property
263
+ return unless e = (tok(IDENT) || interpolation)
264
+ res = [e, str{ss}]
265
+
266
+ while e = (interpolation || tok(IDENT))
267
+ res << e
268
+ end
269
+
270
+ ss
271
+ res
272
+ end
273
+
274
+ def ruleset
275
+ rules = []
276
+ return unless v = selector
277
+ rules.concat v
278
+
279
+ while tok(/,/)
280
+ rules << ',' << str {ss}
281
+ rules.concat expr!(:selector)
282
+ end
283
+
284
+ block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
285
+ end
286
+
287
+ def block(node, context)
288
+ node.has_children = true
289
+ tok!(/\{/)
290
+ block_contents(node, context)
291
+ tok!(/\}/)
292
+ node
293
+ end
294
+
295
+ # A block may contain declarations and/or rulesets
296
+ def block_contents(node, context)
297
+ block_given? ? yield : ss_comments(node)
298
+ node << (child = block_child(context))
299
+ while tok(/;/) || (child && child.has_children)
300
+ block_given? ? yield : ss_comments(node)
301
+ node << (child = block_child(context))
302
+ end
303
+ node
304
+ end
305
+
306
+ def block_child(context)
307
+ variable || directive || declaration_or_ruleset
308
+ end
309
+
310
+ # This is a nasty hack, and the only place in the parser
311
+ # that requires backtracking.
312
+ # The reason is that we can't figure out if certain strings
313
+ # are declarations or rulesets with fixed finite lookahead.
314
+ # For example, "foo:bar baz baz baz..." could be either a property
315
+ # or a selector.
316
+ #
317
+ # To handle this, we simply check if it works as a property
318
+ # (which is the most common case)
319
+ # and, if it doesn't, try it as a ruleset.
320
+ #
321
+ # We could eke some more efficiency out of this
322
+ # by handling some easy cases (first token isn't an identifier,
323
+ # no colon after the identifier, whitespace after the colon),
324
+ # but I'm not sure the gains would be worth the added complexity.
325
+ def declaration_or_ruleset
326
+ pos = @scanner.pos
327
+ line = @line
328
+ old_use_property_exception, @use_property_exception =
329
+ @use_property_exception, false
330
+ begin
331
+ decl = declaration
332
+ # We want an exception if it's not there,
333
+ # but we don't want to consume if it is
334
+ tok!(/[;}]/) unless tok?(/[;}]/)
335
+ return decl
336
+ rescue Sass::SyntaxError => decl_err
337
+ end
338
+
339
+ @line = line
340
+ @scanner.pos = pos
341
+
342
+ begin
343
+ return ruleset
344
+ rescue Sass::SyntaxError => ruleset_err
345
+ raise @use_property_exception ? decl_err : ruleset_err
346
+ end
347
+ ensure
348
+ @use_property_exception = old_use_property_exception
349
+ end
350
+
351
+ def selector
352
+ # The combinator here allows the "> E" hack
353
+ return unless (comb = combinator) || (seq = simple_selector_sequence)
354
+ res = [comb] + (seq || [])
355
+
356
+ while v = combinator
357
+ res << v
358
+ res.concat(simple_selector_sequence || [])
359
+ end
360
+ res
361
+ end
362
+
363
+ def combinator
364
+ tok(PLUS) || tok(GREATER) || tok(TILDE) || str?{whitespace}
365
+ end
366
+
367
+ def simple_selector_sequence
368
+ # This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
369
+ return expr unless e = element_name || tok(HASH) || class_expr ||
370
+ attrib || negation || pseudo || parent_selector || interpolation
371
+ res = [e]
372
+
373
+ # The tok(/\*/) allows the "E*" hack
374
+ while v = element_name || tok(HASH) || class_expr ||
375
+ attrib || negation || pseudo || tok(/\*/) || interpolation
376
+ res << v
377
+ end
378
+ res
379
+ end
380
+
381
+ def parent_selector
382
+ tok(/&/)
383
+ end
384
+
385
+ def class_expr
386
+ return unless tok(/\./)
387
+ '.' + tok!(IDENT)
388
+ end
389
+
390
+ def element_name
391
+ return unless name = tok(IDENT) || tok(/\*/) || tok?(/\|/)
392
+ if tok(/\|/)
393
+ @expected = "element name or *"
394
+ name << "|" << (tok(IDENT) || tok!(/\*/))
395
+ end
396
+ name
397
+ end
398
+
399
+ def attrib
400
+ return unless tok(/\[/)
401
+ res = ['[', str{ss}, str{attrib_name!}, str{ss}]
402
+
403
+ if m = tok(/=/) ||
404
+ tok(INCLUDES) ||
405
+ tok(DASHMATCH) ||
406
+ tok(PREFIXMATCH) ||
407
+ tok(SUFFIXMATCH) ||
408
+ tok(SUBSTRINGMATCH)
409
+ @expected = "identifier or string"
410
+ res << m << str{ss} << (tok(IDENT) || expr!(:interp_string)) << str{ss}
411
+ end
412
+ res << tok!(/\]/)
413
+ end
414
+
415
+ def attrib_name!
416
+ if tok(IDENT)
417
+ # E, E|E, or E|
418
+ # The last is allowed so that E|="foo" will work
419
+ tok(IDENT) if tok(/\|/)
420
+ elsif tok(/\*/)
421
+ # *|E
422
+ tok!(/\|/)
423
+ tok! IDENT
424
+ else
425
+ # |E or E
426
+ tok(/\|/)
427
+ tok! IDENT
428
+ end
429
+ end
430
+
431
+ def pseudo
432
+ return unless s = tok(/::?/)
433
+
434
+ @expected = "pseudoclass or pseudoelement"
435
+ [s, functional_pseudo || tok!(IDENT)]
436
+ end
437
+
438
+ def functional_pseudo
439
+ return unless fn = tok(FUNCTION)
440
+ [fn, str{ss}, expr!(:pseudo_expr), tok!(/\)/)]
441
+ end
442
+
443
+ def pseudo_expr
444
+ return unless e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
445
+ interp_string || tok(IDENT) || interpolation
446
+ res = [e, str{ss}]
447
+ while e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
448
+ interp_string || tok(IDENT) || interpolation
449
+ res << e << str{ss}
450
+ end
451
+ res
452
+ end
453
+
454
+ def negation
455
+ return unless tok(NOT)
456
+ res = [":not(", str{ss}]
457
+ @expected = "selector"
458
+ res << (element_name || tok(HASH) || class_expr || attrib || expr!(:pseudo))
459
+ res << tok!(/\)/)
460
+ end
461
+
462
+ def declaration
463
+ # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
464
+ if s = tok(/[:\*\.]/)
465
+ @use_property_exception = s != '.'
466
+ name = [s, str{ss}] + expr!(:property)
467
+ else
468
+ return unless name = property
469
+ end
470
+
471
+ @expected = expected_property_separator
472
+ space, value = expr!(:value)
473
+ ss
474
+ require_block = tok?(/\{/)
475
+
476
+ node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new))
477
+
478
+ return node unless require_block
479
+ nested_properties! node, space
480
+ end
481
+
482
+ def expected_property_separator
483
+ '":" or "="'
484
+ end
485
+
486
+ def value
487
+ return unless tok(/:/)
488
+ space = !str {ss}.empty?
489
+ @use_property_exception ||= space || !tok?(IDENT)
490
+
491
+ return true, Sass::Script::String.new("") if tok?(/\{/)
492
+ return space, sass_script(:parse)
493
+ end
494
+
495
+ def plain_value
496
+ return unless tok(/:/)
497
+ space = !str {ss}.empty?
498
+ @use_property_exception ||= space || !tok?(IDENT)
499
+
500
+ expression = expr
501
+ expression << tok(IMPORTANT) if expression
502
+ # expression, space, value
503
+ return expression, space, expression || [""]
504
+ end
505
+
506
+ def nested_properties!(node, space)
507
+ raise Sass::SyntaxError.new(<<MESSAGE, :line => @line) unless space
508
+ Invalid CSS: a space is required between a property and its definition
509
+ when it has other properties nested beneath it.
510
+ MESSAGE
511
+
512
+ @use_property_exception = true
513
+ @expected = 'expression (e.g. 1px, bold) or "{"'
514
+ block(node, :property)
515
+ end
516
+
517
+ def expr
518
+ return unless t = term
519
+ res = [t, str{ss}]
520
+
521
+ while (o = operator) && (t = term)
522
+ res << o << t << str{ss}
523
+ end
524
+
525
+ res
526
+ end
527
+
528
+ def term
529
+ unless e = tok(NUMBER) ||
530
+ tok(URI) ||
531
+ function ||
532
+ interp_string ||
533
+ tok(UNICODERANGE) ||
534
+ tok(IDENT) ||
535
+ tok(HEXCOLOR) ||
536
+ interpolation
537
+
538
+ return unless op = unary_operator
539
+ @expected = "number or function"
540
+ return [op, tok(NUMBER) || expr!(:function)]
541
+ end
542
+ e
543
+ end
544
+
545
+ def function
546
+ return unless name = tok(FUNCTION)
547
+ if name == "expression(" || name == "calc("
548
+ str, _ = Haml::Shared.balance(@scanner, ?(, ?), 1)
549
+ [name, str]
550
+ else
551
+ [name, str{ss}, expr, tok!(/\)/)]
552
+ end
553
+ end
554
+
555
+ def interpolation
556
+ return unless tok(/#\{/)
557
+ sass_script(:parse_interpolated)
558
+ end
559
+
560
+ def interp_string
561
+ _interp_string(:double) || _interp_string(:single)
562
+ end
563
+
564
+ def _interp_string(type)
565
+ return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, false]])
566
+ res = [start]
567
+
568
+ mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, true]]
569
+ # @scanner[2].empty? means we've started an interpolated section
570
+ res << expr!(:interpolation) << tok(mid_re) while @scanner[2].empty?
571
+ res
572
+ end
573
+
574
+ def str
575
+ @strs.push ""
576
+ yield
577
+ @strs.last
578
+ ensure
579
+ @strs.pop
580
+ end
581
+
582
+ def str?
583
+ @strs.push ""
584
+ yield && @strs.last
585
+ ensure
586
+ @strs.pop
587
+ end
588
+
589
+ def node(node)
590
+ node.line = @line
591
+ node
592
+ end
593
+
594
+ @sass_script_parser = Class.new(Sass::Script::Parser)
595
+ @sass_script_parser.send(:include, ScriptParser)
596
+ # @private
597
+ def self.sass_script_parser; @sass_script_parser; end
598
+
599
+ def sass_script(*args)
600
+ parser = self.class.sass_script_parser.new(@scanner, @line,
601
+ @scanner.pos - (@scanner.string[0...@scanner.pos].rindex("\n") || 0))
602
+ result = parser.send(*args)
603
+ @line = parser.line
604
+ result
605
+ end
606
+
607
+ EXPR_NAMES = {
608
+ :media_query => "media query (e.g. print, screen, print and screen)",
609
+ :media_expr => "media expression (e.g. (min-device-width: 800px)))",
610
+ :pseudo_expr => "expression (e.g. fr, 2n+1)",
611
+ :expr => "expression (e.g. 1px, bold)",
612
+ }
613
+
614
+ TOK_NAMES = Haml::Util.to_hash(
615
+ Sass::SCSS::RX.constants.map {|c| [Sass::SCSS::RX.const_get(c), c.downcase]}).
616
+ merge(IDENT => "identifier", /[;}]/ => '";"', /[=:]/ => '":"')
617
+
618
+ def tok?(rx)
619
+ @scanner.match?(rx)
620
+ end
621
+
622
+ def expr!(name)
623
+ (e = send(name)) && (return e)
624
+ expected(EXPR_NAMES[name] || name.to_s)
625
+ end
626
+
627
+ def tok!(rx)
628
+ (t = tok(rx)) && (return t)
629
+ name = TOK_NAMES[rx]
630
+
631
+ unless name
632
+ # Display basic regexps as plain old strings
633
+ string = rx.source.gsub(/\\(.)/, '\1')
634
+ name = rx.source == Regexp.escape(string) ? string.inspect : rx.inspect
635
+ end
636
+
637
+ expected(name)
638
+ end
639
+
640
+ def expected(name)
641
+ self.class.expected(@scanner, @expected || name, @line)
642
+ end
643
+
644
+ # @private
645
+ def self.expected(scanner, expected, line)
646
+ pos = scanner.pos
647
+
648
+ after = scanner.string[0...pos]
649
+ # Get rid of whitespace between pos and the last token,
650
+ # but only if there's a newline in there
651
+ after.gsub!(/\s*\n\s*$/, '')
652
+ # Also get rid of stuff before the last newline
653
+ after.gsub!(/.*\n/, '')
654
+ after = "..." + after[-15..-1] if after.size > 18
655
+
656
+ was = scanner.rest.dup
657
+ # Get rid of whitespace between pos and the next token,
658
+ # but only if there's a newline in there
659
+ was.gsub!(/^\s*\n\s*/, '')
660
+ # Also get rid of stuff after the next newline
661
+ was.gsub!(/\n.*/, '')
662
+ was = was[0...15] + "..." if was.size > 18
663
+
664
+ raise Sass::SyntaxError.new(
665
+ "Invalid CSS after \"#{after}\": expected #{expected}, was \"#{was}\"",
666
+ :line => line)
667
+ end
668
+
669
+ def tok(rx)
670
+ res = @scanner.scan(rx)
671
+ if res
672
+ @line += res.count("\n")
673
+ @expected = nil
674
+ if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
675
+ @strs.each {|s| s << res}
676
+ end
677
+ end
678
+
679
+ res
680
+ end
681
+ end
682
+ end
683
+ end