oreorenasass 3.4.4 → 3.4.5

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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +50 -70
  4. data/Rakefile +5 -26
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/sass +1 -1
  8. data/bin/scss +1 -1
  9. data/lib/sass.rb +12 -19
  10. data/lib/sass/cache_stores/base.rb +2 -2
  11. data/lib/sass/cache_stores/chain.rb +1 -2
  12. data/lib/sass/cache_stores/filesystem.rb +5 -1
  13. data/lib/sass/cache_stores/memory.rb +1 -1
  14. data/lib/sass/cache_stores/null.rb +2 -2
  15. data/lib/sass/callbacks.rb +0 -1
  16. data/lib/sass/css.rb +13 -11
  17. data/lib/sass/engine.rb +173 -424
  18. data/lib/sass/environment.rb +58 -148
  19. data/lib/sass/error.rb +14 -11
  20. data/lib/sass/exec.rb +703 -5
  21. data/lib/sass/importers/base.rb +6 -49
  22. data/lib/sass/importers/filesystem.rb +19 -44
  23. data/lib/sass/logger.rb +4 -1
  24. data/lib/sass/logger/base.rb +4 -2
  25. data/lib/sass/logger/log_level.rb +7 -3
  26. data/lib/sass/media.rb +23 -20
  27. data/lib/sass/plugin.rb +7 -7
  28. data/lib/sass/plugin/compiler.rb +145 -304
  29. data/lib/sass/plugin/configuration.rb +23 -18
  30. data/lib/sass/plugin/merb.rb +1 -1
  31. data/lib/sass/plugin/staleness_checker.rb +3 -3
  32. data/lib/sass/repl.rb +3 -3
  33. data/lib/sass/script.rb +8 -35
  34. data/lib/sass/script/{value/arg_list.rb → arg_list.rb} +25 -9
  35. data/lib/sass/script/bool.rb +18 -0
  36. data/lib/sass/script/color.rb +606 -0
  37. data/lib/sass/script/css_lexer.rb +4 -8
  38. data/lib/sass/script/css_parser.rb +2 -5
  39. data/lib/sass/script/funcall.rb +245 -0
  40. data/lib/sass/script/functions.rb +408 -1491
  41. data/lib/sass/script/interpolation.rb +79 -0
  42. data/lib/sass/script/lexer.rb +68 -172
  43. data/lib/sass/script/list.rb +85 -0
  44. data/lib/sass/script/literal.rb +221 -0
  45. data/lib/sass/script/{tree/node.rb → node.rb} +12 -22
  46. data/lib/sass/script/{value/null.rb → null.rb} +7 -14
  47. data/lib/sass/script/{value/number.rb → number.rb} +75 -152
  48. data/lib/sass/script/{tree/operation.rb → operation.rb} +24 -17
  49. data/lib/sass/script/parser.rb +110 -245
  50. data/lib/sass/script/string.rb +51 -0
  51. data/lib/sass/script/{tree/string_interpolation.rb → string_interpolation.rb} +4 -5
  52. data/lib/sass/script/{tree/unary_operation.rb → unary_operation.rb} +6 -6
  53. data/lib/sass/script/variable.rb +58 -0
  54. data/lib/sass/scss/css_parser.rb +3 -9
  55. data/lib/sass/scss/parser.rb +421 -450
  56. data/lib/sass/scss/rx.rb +11 -19
  57. data/lib/sass/scss/static_parser.rb +7 -321
  58. data/lib/sass/selector.rb +194 -68
  59. data/lib/sass/selector/abstract_sequence.rb +14 -29
  60. data/lib/sass/selector/comma_sequence.rb +25 -108
  61. data/lib/sass/selector/sequence.rb +66 -159
  62. data/lib/sass/selector/simple.rb +25 -23
  63. data/lib/sass/selector/simple_sequence.rb +63 -173
  64. data/lib/sass/shared.rb +1 -1
  65. data/lib/sass/supports.rb +15 -13
  66. data/lib/sass/tree/charset_node.rb +1 -1
  67. data/lib/sass/tree/comment_node.rb +3 -3
  68. data/lib/sass/tree/css_import_node.rb +11 -11
  69. data/lib/sass/tree/debug_node.rb +2 -2
  70. data/lib/sass/tree/directive_node.rb +4 -21
  71. data/lib/sass/tree/each_node.rb +8 -8
  72. data/lib/sass/tree/extend_node.rb +7 -14
  73. data/lib/sass/tree/for_node.rb +4 -4
  74. data/lib/sass/tree/function_node.rb +4 -9
  75. data/lib/sass/tree/if_node.rb +1 -1
  76. data/lib/sass/tree/import_node.rb +5 -4
  77. data/lib/sass/tree/media_node.rb +14 -4
  78. data/lib/sass/tree/mixin_def_node.rb +4 -4
  79. data/lib/sass/tree/mixin_node.rb +8 -21
  80. data/lib/sass/tree/node.rb +12 -54
  81. data/lib/sass/tree/prop_node.rb +20 -39
  82. data/lib/sass/tree/return_node.rb +2 -3
  83. data/lib/sass/tree/root_node.rb +3 -19
  84. data/lib/sass/tree/rule_node.rb +22 -35
  85. data/lib/sass/tree/supports_node.rb +13 -0
  86. data/lib/sass/tree/trace_node.rb +1 -2
  87. data/lib/sass/tree/variable_node.rb +3 -9
  88. data/lib/sass/tree/visitors/base.rb +8 -5
  89. data/lib/sass/tree/visitors/check_nesting.rb +19 -49
  90. data/lib/sass/tree/visitors/convert.rb +56 -74
  91. data/lib/sass/tree/visitors/cssize.rb +74 -202
  92. data/lib/sass/tree/visitors/deep_copy.rb +5 -10
  93. data/lib/sass/tree/visitors/extend.rb +7 -7
  94. data/lib/sass/tree/visitors/perform.rb +185 -278
  95. data/lib/sass/tree/visitors/set_options.rb +6 -20
  96. data/lib/sass/tree/visitors/to_css.rb +81 -234
  97. data/lib/sass/tree/warn_node.rb +2 -2
  98. data/lib/sass/tree/while_node.rb +2 -2
  99. data/lib/sass/util.rb +152 -522
  100. data/lib/sass/util/multibyte_string_scanner.rb +0 -2
  101. data/lib/sass/util/subset_map.rb +3 -4
  102. data/lib/sass/util/test.rb +1 -0
  103. data/lib/sass/version.rb +22 -20
  104. data/test/Gemfile +3 -0
  105. data/test/Gemfile.lock +10 -0
  106. data/test/sass/cache_test.rb +20 -62
  107. data/test/sass/callbacks_test.rb +1 -1
  108. data/test/sass/conversion_test.rb +2 -296
  109. data/test/sass/css2sass_test.rb +4 -23
  110. data/test/sass/engine_test.rb +354 -411
  111. data/test/sass/exec_test.rb +2 -2
  112. data/test/sass/extend_test.rb +145 -324
  113. data/test/sass/functions_test.rb +86 -873
  114. data/test/sass/importer_test.rb +21 -241
  115. data/test/sass/logger_test.rb +1 -1
  116. data/test/sass/more_results/more_import.css +1 -1
  117. data/test/sass/plugin_test.rb +26 -16
  118. data/test/sass/results/compact.css +1 -1
  119. data/test/sass/results/complex.css +4 -4
  120. data/test/sass/results/expanded.css +1 -1
  121. data/test/sass/results/import.css +1 -1
  122. data/test/sass/results/import_charset_ibm866.css +2 -2
  123. data/test/sass/results/mixins.css +17 -17
  124. data/test/sass/results/nested.css +1 -1
  125. data/test/sass/results/parent_ref.css +2 -2
  126. data/test/sass/results/script.css +3 -3
  127. data/test/sass/results/scss_import.css +1 -1
  128. data/test/sass/script_conversion_test.rb +7 -36
  129. data/test/sass/script_test.rb +53 -485
  130. data/test/sass/scss/css_test.rb +28 -143
  131. data/test/sass/scss/rx_test.rb +4 -4
  132. data/test/sass/scss/scss_test.rb +325 -2119
  133. data/test/sass/templates/scss_import.scss +1 -2
  134. data/test/sass/test_helper.rb +1 -1
  135. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  136. data/test/sass/util/subset_map_test.rb +2 -2
  137. data/test/sass/util_test.rb +1 -86
  138. data/test/test_helper.rb +8 -37
  139. metadata +19 -66
  140. data/lib/sass/exec/base.rb +0 -187
  141. data/lib/sass/exec/sass_convert.rb +0 -264
  142. data/lib/sass/exec/sass_scss.rb +0 -424
  143. data/lib/sass/features.rb +0 -47
  144. data/lib/sass/script/tree.rb +0 -16
  145. data/lib/sass/script/tree/funcall.rb +0 -306
  146. data/lib/sass/script/tree/interpolation.rb +0 -118
  147. data/lib/sass/script/tree/list_literal.rb +0 -77
  148. data/lib/sass/script/tree/literal.rb +0 -45
  149. data/lib/sass/script/tree/map_literal.rb +0 -64
  150. data/lib/sass/script/tree/selector.rb +0 -26
  151. data/lib/sass/script/tree/variable.rb +0 -57
  152. data/lib/sass/script/value.rb +0 -11
  153. data/lib/sass/script/value/base.rb +0 -240
  154. data/lib/sass/script/value/bool.rb +0 -35
  155. data/lib/sass/script/value/color.rb +0 -680
  156. data/lib/sass/script/value/helpers.rb +0 -262
  157. data/lib/sass/script/value/list.rb +0 -113
  158. data/lib/sass/script/value/map.rb +0 -70
  159. data/lib/sass/script/value/string.rb +0 -97
  160. data/lib/sass/selector/pseudo.rb +0 -256
  161. data/lib/sass/source/map.rb +0 -210
  162. data/lib/sass/source/position.rb +0 -39
  163. data/lib/sass/source/range.rb +0 -41
  164. data/lib/sass/stack.rb +0 -120
  165. data/lib/sass/tree/at_root_node.rb +0 -83
  166. data/lib/sass/tree/error_node.rb +0 -18
  167. data/lib/sass/tree/keyframe_rule_node.rb +0 -15
  168. data/lib/sass/util/cross_platform_random.rb +0 -19
  169. data/lib/sass/util/normalized_map.rb +0 -130
  170. data/lib/sass/util/ordered_hash.rb +0 -192
  171. data/test/sass/compiler_test.rb +0 -232
  172. data/test/sass/encoding_test.rb +0 -219
  173. data/test/sass/source_map_test.rb +0 -977
  174. data/test/sass/superselector_test.rb +0 -191
  175. data/test/sass/util/normalized_map_test.rb +0 -51
  176. data/test/sass/value_helpers_test.rb +0 -179
@@ -0,0 +1,51 @@
1
+ require 'sass/script/literal'
2
+
3
+ module Sass::Script
4
+ # A SassScript object representing a CSS string *or* a CSS identifier.
5
+ class String < Literal
6
+ # The Ruby value of the string.
7
+ #
8
+ # @return [String]
9
+ attr_reader :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
+ # Creates a new string.
19
+ #
20
+ # @param value [String] See \{#value}
21
+ # @param type [Symbol] See \{#type}
22
+ def initialize(value, type = :identifier)
23
+ super(value)
24
+ @type = type
25
+ end
26
+
27
+ # @see Literal#plus
28
+ def plus(other)
29
+ other_str = other.is_a?(Sass::Script::String) ? other.value : other.to_s
30
+ Sass::Script::String.new(self.value + other_str, self.type)
31
+ end
32
+
33
+ # @see Node#to_s
34
+ def to_s(opts = {})
35
+ if @type == :identifier
36
+ return @value.gsub(/\n\s*/, " ")
37
+ end
38
+
39
+ return "\"#{value.gsub('"', "\\\"")}\"" if opts[:quote] == %q{"}
40
+ return "'#{value.gsub("'", "\\'")}'" if opts[:quote] == %q{'}
41
+ return "\"#{value}\"" unless value.include?('"')
42
+ return "'#{value}'" unless value.include?("'")
43
+ "\"#{value.gsub('"', "\\\"")}\"" #'
44
+ end
45
+
46
+ # @see Node#to_sass
47
+ def to_sass(opts = {})
48
+ to_s
49
+ end
50
+ end
51
+ end
@@ -1,4 +1,4 @@
1
- module Sass::Script::Tree
1
+ module Sass::Script
2
2
  # A SassScript object representing `#{}` interpolation within a string.
3
3
  #
4
4
  # @see Interpolation
@@ -74,16 +74,15 @@ module Sass::Script::Tree
74
74
  # Evaluates the interpolation.
75
75
  #
76
76
  # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
77
- # @return [Sass::Script::Value::String]
78
- # The SassScript string that is the value of the interpolation
77
+ # @return [Sass::Script::String] The SassScript string that is the value of the interpolation
79
78
  def _perform(environment)
80
79
  res = ""
81
80
  before = @before.perform(environment)
82
81
  res << before.value
83
82
  mid = @mid.perform(environment)
84
- res << (mid.is_a?(Sass::Script::Value::String) ? mid.value : mid.to_s(:quote => :none))
83
+ res << (mid.is_a?(Sass::Script::String) ? mid.value : mid.to_s)
85
84
  res << @after.perform(environment).value
86
- opts(Sass::Script::Value::String.new(res, before.type))
85
+ opts(Sass::Script::String.new(res, before.type))
87
86
  end
88
87
 
89
88
  private
@@ -1,4 +1,4 @@
1
- module Sass::Script::Tree
1
+ module Sass::Script
2
2
  # A SassScript parse node representing a unary operation,
3
3
  # such as `-$b` or `not true`.
4
4
  #
@@ -31,7 +31,7 @@ module Sass::Script::Tree
31
31
  (operand =~ Sass::SCSS::RX::IDENT) == 0)
32
32
  operand = "(#{@operand.to_sass(opts)})"
33
33
  end
34
- op = Sass::Script::Lexer::OPERATORS_REVERSE[@operator]
34
+ op = Lexer::OPERATORS_REVERSE[@operator]
35
35
  op + (op =~ /[a-z]/ ? " " : "") + operand
36
36
  end
37
37
 
@@ -55,15 +55,15 @@ module Sass::Script::Tree
55
55
  # Evaluates the operation.
56
56
  #
57
57
  # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
58
- # @return [Sass::Script::Value] The SassScript object that is the value of the operation
58
+ # @return [Literal] The SassScript object that is the value of the operation
59
59
  # @raise [Sass::SyntaxError] if the operation is undefined for the operand
60
60
  def _perform(environment)
61
61
  operator = "unary_#{@operator}"
62
- value = @operand.perform(environment)
63
- value.send(operator)
62
+ literal = @operand.perform(environment)
63
+ literal.send(operator)
64
64
  rescue NoMethodError => e
65
65
  raise e unless e.name.to_s == operator.to_s
66
- raise Sass::SyntaxError.new("Undefined unary operation: \"#{@operator} #{value}\".")
66
+ raise Sass::SyntaxError.new("Undefined unary operation: \"#{@operator} #{literal}\".")
67
67
  end
68
68
  end
69
69
  end
@@ -0,0 +1,58 @@
1
+ module Sass
2
+ module Script
3
+ # A SassScript parse node representing a variable.
4
+ class Variable < Node
5
+ # The name of the variable.
6
+ #
7
+ # @return [String]
8
+ attr_reader :name
9
+
10
+ # The underscored name of the variable.
11
+ #
12
+ # @return [String]
13
+ attr_reader :underscored_name
14
+
15
+ # @param name [String] See \{#name}
16
+ def initialize(name)
17
+ @name = name
18
+ @underscored_name = name.gsub(/-/,"_")
19
+ super()
20
+ end
21
+
22
+ # @return [String] A string representation of the variable
23
+ def inspect(opts = {})
24
+ "$#{dasherize(name, opts)}"
25
+ end
26
+ alias_method :to_sass, :inspect
27
+
28
+ # Returns an empty array.
29
+ #
30
+ # @return [Array<Node>] empty
31
+ # @see Node#children
32
+ def children
33
+ []
34
+ end
35
+
36
+ # @see Node#deep_copy
37
+ def deep_copy
38
+ dup
39
+ end
40
+
41
+ protected
42
+
43
+ # Evaluates the variable.
44
+ #
45
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
46
+ # @return [Literal] The SassScript object that is the value of the variable
47
+ # @raise [Sass::SyntaxError] if the variable is undefined
48
+ def _perform(environment)
49
+ raise SyntaxError.new("Undefined variable: \"$#{name}\".") unless val = environment.var(name)
50
+ if val.is_a?(Number)
51
+ val = val.dup
52
+ val.original = nil
53
+ end
54
+ return val
55
+ end
56
+ end
57
+ end
58
+ end
@@ -11,7 +11,7 @@ module Sass
11
11
 
12
12
  def placeholder_selector; nil; end
13
13
  def parent_selector; nil; end
14
- def interpolation(warn_for_color = false); nil; end
14
+ def interpolation; nil; end
15
15
  def use_css_import?; true; end
16
16
 
17
17
  def block_child(context)
@@ -25,14 +25,8 @@ module Sass
25
25
  end
26
26
  end
27
27
 
28
- def nested_properties!(node)
29
- expected('expression (e.g. 1px, bold)')
30
- end
31
-
32
- def ruleset
33
- start_pos = source_position
34
- return unless (selector = selector_comma_sequence)
35
- block(node(Sass::Tree::RuleNode.new(selector, range(start_pos)), start_pos), :ruleset)
28
+ def nested_properties!(node, space)
29
+ expected('expression (e.g. 1px, bold)');
36
30
  end
37
31
 
38
32
  @sass_script_parser = Class.new(Sass::Script::CssParser)
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  require 'set'
3
2
 
4
3
  module Sass
@@ -6,30 +5,16 @@ module Sass
6
5
  # The parser for SCSS.
7
6
  # It parses a string of code into a tree of {Sass::Tree::Node}s.
8
7
  class Parser
9
- # Expose for the SASS parser.
10
- attr_accessor :offset
11
-
12
8
  # @param str [String, StringScanner] The source document to parse.
13
9
  # Note that `Parser` *won't* raise a nice error message if this isn't properly parsed;
14
10
  # for that, you should use the higher-level {Sass::Engine} or {Sass::CSS}.
15
- # @param filename [String] The name of the file being parsed. Used for
16
- # warnings and source maps.
17
- # @param importer [Sass::Importers::Base] The importer used to import the
18
- # file being parsed. Used for source maps.
19
- # @param line [Fixnum] The 1-based line on which the source string appeared,
11
+ # @param filename [String] The name of the file being parsed. Used for warnings.
12
+ # @param line [Fixnum] The line on which the source string appeared,
20
13
  # if it's part of another document.
21
- # @param offset [Fixnum] The 1-based character (not byte) offset in the line on
22
- # which the source string starts. Used for error reporting and sourcemap
23
- # building.
24
- # @comment
25
- # rubocop:disable ParameterLists
26
- def initialize(str, filename, importer, line = 1, offset = 1)
27
- # rubocop:enable ParameterLists
14
+ def initialize(str, filename, line = 1)
28
15
  @template = str
29
16
  @filename = filename
30
- @importer = importer
31
17
  @line = line
32
- @offset = offset
33
18
  @strs = []
34
19
  end
35
20
 
@@ -40,7 +25,7 @@ module Sass
40
25
  def parse
41
26
  init_scanner!
42
27
  root = stylesheet
43
- expected("selector or at-rule") unless root && @scanner.eos?
28
+ expected("selector or at-rule") unless @scanner.eos?
44
29
  root
45
30
  end
46
31
 
@@ -48,7 +33,7 @@ module Sass
48
33
  # Note that this won't assert that the identifier takes up the entire input string;
49
34
  # it's meant to be used with `StringScanner`s as part of other parsers.
50
35
  #
51
- # @return [Array<String, Sass::Script::Tree::Node>, nil]
36
+ # @return [Array<String, Sass::Script::Node>, nil]
52
37
  # The interpolated identifier, or nil if none could be parsed
53
38
  def parse_interp_ident
54
39
  init_scanner!
@@ -63,22 +48,10 @@ module Sass
63
48
  def parse_media_query_list
64
49
  init_scanner!
65
50
  ql = media_query_list
66
- expected("media query list") unless ql && @scanner.eos?
51
+ expected("media query list") unless @scanner.eos?
67
52
  ql
68
53
  end
69
54
 
70
- # Parses an at-root query.
71
- #
72
- # @return [Array<String, Sass::Script;:Tree::Node>] The interpolated query.
73
- # @raise [Sass::SyntaxError] if there's a syntax error in the query,
74
- # or if it doesn't take up the entire input string.
75
- def parse_at_root_query
76
- init_scanner!
77
- query = at_root_query
78
- expected("@at-root query list") unless query && @scanner.eos?
79
- query
80
- end
81
-
82
55
  # Parses a supports query condition.
83
56
  #
84
57
  # @return [Sass::Supports::Condition] The parsed condition
@@ -87,7 +60,7 @@ module Sass
87
60
  def parse_supports_condition
88
61
  init_scanner!
89
62
  condition = supports_condition
90
- expected("supports condition") unless condition && @scanner.eos?
63
+ expected("supports condition") unless @scanner.eos?
91
64
  condition
92
65
  end
93
66
 
@@ -95,14 +68,6 @@ module Sass
95
68
 
96
69
  include Sass::SCSS::RX
97
70
 
98
- def source_position
99
- Sass::Source::Position.new(@line, @offset)
100
- end
101
-
102
- def range(start_pos, end_pos = source_position)
103
- Sass::Source::Range.new(start_pos, end_pos, @filename, @importer)
104
- end
105
-
106
71
  def init_scanner!
107
72
  @scanner =
108
73
  if @template.is_a?(StringScanner)
@@ -113,7 +78,7 @@ module Sass
113
78
  end
114
79
 
115
80
  def stylesheet
116
- node = node(Sass::Tree::RootNode.new(@scanner.string), source_position)
81
+ node = node(Sass::Tree::RootNode.new(@scanner.string))
117
82
  block_contents(node, :stylesheet) {s(node)}
118
83
  end
119
84
 
@@ -147,33 +112,21 @@ module Sass
147
112
  end
148
113
 
149
114
  def process_comment(text, node)
150
- silent = text =~ %r{\A//}
151
- loud = !silent && text =~ %r{\A/[/*]!}
115
+ silent = text =~ /^\/\//
116
+ loud = !silent && text =~ %r{^/[/*]!}
152
117
  line = @line - text.count("\n")
153
118
 
154
119
  if silent
155
- value = [text.sub(%r{\A\s*//}, '/*').gsub(%r{^\s*//}, ' *') + ' */']
120
+ value = [text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */']
156
121
  else
157
- value = Sass::Engine.parse_interp(
158
- text, line, @scanner.pos - text.size, :filename => @filename)
159
- string_before_comment = @scanner.string[0...@scanner.pos - text.length]
160
- newline_before_comment = string_before_comment.rindex("\n")
161
- last_line_before_comment =
162
- if newline_before_comment
163
- string_before_comment[newline_before_comment + 1..-1]
164
- else
165
- string_before_comment
166
- end
167
- value.unshift(last_line_before_comment.gsub(/[^\s]/, ' '))
122
+ value = Sass::Engine.parse_interp(text, line, @scanner.pos - text.size, :filename => @filename)
123
+ value.unshift(@scanner.
124
+ string[0...@scanner.pos].
125
+ reverse[/.*?\*\/(.*?)($|\Z)/, 1].
126
+ reverse.gsub(/[^\s]/, ' '))
168
127
  end
169
128
 
170
- type = if silent
171
- :silent
172
- elsif loud
173
- :loud
174
- else
175
- :normal
176
- end
129
+ type = if silent then :silent elsif loud then :loud else :normal end
177
130
  comment = Sass::Tree::CommentNode.new(value, type)
178
131
  comment.line = line
179
132
  node << comment
@@ -181,29 +134,31 @@ module Sass
181
134
 
182
135
  DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
183
136
  :each, :while, :if, :else, :extend, :import, :media, :charset, :content,
184
- :_moz_document, :at_root, :error]
137
+ :_moz_document]
185
138
 
186
139
  PREFIXED_DIRECTIVES = Set[:supports]
187
140
 
188
141
  def directive
189
- start_pos = source_position
190
142
  return unless tok(/@/)
191
143
  name = tok!(IDENT)
192
144
  ss
193
145
 
194
- if (dir = special_directive(name, start_pos))
146
+ if dir = special_directive(name)
195
147
  return dir
196
- elsif (dir = prefixed_directive(name, start_pos))
148
+ elsif dir = prefixed_directive(name)
197
149
  return dir
198
150
  end
199
151
 
200
- val = almost_any_value
152
+ # Most at-rules take expressions (e.g. @import),
153
+ # but some (e.g. @page) take selector-like arguments.
154
+ # Some take no arguments at all.
155
+ val = expr || selector
201
156
  val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"]
202
- directive_body(val, start_pos)
157
+ directive_body(val)
203
158
  end
204
159
 
205
- def directive_body(value, start_pos)
206
- node = Sass::Tree::DirectiveNode.new(value)
160
+ def directive_body(value)
161
+ node = node(Sass::Tree::DirectiveNode.new(value))
207
162
 
208
163
  if tok(/\{/)
209
164
  node.has_children = true
@@ -211,32 +166,31 @@ module Sass
211
166
  tok!(/\}/)
212
167
  end
213
168
 
214
- node(node, start_pos)
169
+ node
215
170
  end
216
171
 
217
- def special_directive(name, start_pos)
172
+ def special_directive(name)
218
173
  sym = name.gsub('-', '_').to_sym
219
- DIRECTIVES.include?(sym) && send("#{sym}_directive", start_pos)
174
+ DIRECTIVES.include?(sym) && send("#{sym}_directive")
220
175
  end
221
176
 
222
- def prefixed_directive(name, start_pos)
223
- sym = deprefix(name).gsub('-', '_').to_sym
224
- PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name, start_pos)
177
+ def prefixed_directive(name)
178
+ sym = name.gsub(/^-[a-z0-9]+-/i, '').gsub('-', '_').to_sym
179
+ PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name)
225
180
  end
226
181
 
227
- def mixin_directive(start_pos)
182
+ def mixin_directive
228
183
  name = tok! IDENT
229
184
  args, splat = sass_script(:parse_mixin_definition_arglist)
230
185
  ss
231
- block(node(Sass::Tree::MixinDefNode.new(name, args, splat), start_pos), :directive)
186
+ block(node(Sass::Tree::MixinDefNode.new(name, args, splat)), :directive)
232
187
  end
233
188
 
234
- def include_directive(start_pos)
189
+ def include_directive
235
190
  name = tok! IDENT
236
- args, keywords, splat, kwarg_splat = sass_script(:parse_mixin_include_arglist)
191
+ args, keywords, splat = sass_script(:parse_mixin_include_arglist)
237
192
  ss
238
- include_node = node(
239
- Sass::Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat), start_pos)
193
+ include_node = node(Sass::Tree::MixinNode.new(name, args, keywords, splat))
240
194
  if tok?(/\{/)
241
195
  include_node.has_children = true
242
196
  block(include_node, :directive)
@@ -245,31 +199,31 @@ module Sass
245
199
  end
246
200
  end
247
201
 
248
- def content_directive(start_pos)
202
+ def content_directive
249
203
  ss
250
- node(Sass::Tree::ContentNode.new, start_pos)
204
+ node(Sass::Tree::ContentNode.new)
251
205
  end
252
206
 
253
- def function_directive(start_pos)
207
+ def function_directive
254
208
  name = tok! IDENT
255
209
  args, splat = sass_script(:parse_function_definition_arglist)
256
210
  ss
257
- block(node(Sass::Tree::FunctionNode.new(name, args, splat), start_pos), :function)
211
+ block(node(Sass::Tree::FunctionNode.new(name, args, splat)), :function)
258
212
  end
259
213
 
260
- def return_directive(start_pos)
261
- node(Sass::Tree::ReturnNode.new(sass_script(:parse)), start_pos)
214
+ def return_directive
215
+ node(Sass::Tree::ReturnNode.new(sass_script(:parse)))
262
216
  end
263
217
 
264
- def debug_directive(start_pos)
265
- node(Sass::Tree::DebugNode.new(sass_script(:parse)), start_pos)
218
+ def debug_directive
219
+ node(Sass::Tree::DebugNode.new(sass_script(:parse)))
266
220
  end
267
221
 
268
- def warn_directive(start_pos)
269
- node(Sass::Tree::WarnNode.new(sass_script(:parse)), start_pos)
222
+ def warn_directive
223
+ node(Sass::Tree::WarnNode.new(sass_script(:parse)))
270
224
  end
271
225
 
272
- def for_directive(start_pos)
226
+ def for_directive
273
227
  tok!(/\$/)
274
228
  var = tok! IDENT
275
229
  ss
@@ -283,37 +237,31 @@ module Sass
283
237
  to = sass_script(:parse)
284
238
  ss
285
239
 
286
- block(node(Sass::Tree::ForNode.new(var, from, to, exclusive), start_pos), :directive)
240
+ block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)), :directive)
287
241
  end
288
242
 
289
- def each_directive(start_pos)
243
+ def each_directive
290
244
  tok!(/\$/)
291
- vars = [tok!(IDENT)]
245
+ var = tok! IDENT
292
246
  ss
293
- while tok(/,/)
294
- ss
295
- tok!(/\$/)
296
- vars << tok!(IDENT)
297
- ss
298
- end
299
247
 
300
248
  tok!(/in/)
301
249
  list = sass_script(:parse)
302
250
  ss
303
251
 
304
- block(node(Sass::Tree::EachNode.new(vars, list), start_pos), :directive)
252
+ block(node(Sass::Tree::EachNode.new(var, list)), :directive)
305
253
  end
306
254
 
307
- def while_directive(start_pos)
255
+ def while_directive
308
256
  expr = sass_script(:parse)
309
257
  ss
310
- block(node(Sass::Tree::WhileNode.new(expr), start_pos), :directive)
258
+ block(node(Sass::Tree::WhileNode.new(expr)), :directive)
311
259
  end
312
260
 
313
- def if_directive(start_pos)
261
+ def if_directive
314
262
  expr = sass_script(:parse)
315
263
  ss
316
- node = block(node(Sass::Tree::IfNode.new(expr), start_pos), :directive)
264
+ node = block(node(Sass::Tree::IfNode.new(expr)), :directive)
317
265
  pos = @scanner.pos
318
266
  line = @line
319
267
  ss
@@ -328,11 +276,10 @@ module Sass
328
276
  end
329
277
 
330
278
  def else_block(node)
331
- start_pos = source_position
332
279
  return unless tok(/@else/)
333
280
  ss
334
281
  else_node = block(
335
- node(Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))), start_pos),
282
+ Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
336
283
  :directive)
337
284
  node.add_else(else_node)
338
285
  pos = @scanner.pos
@@ -348,20 +295,18 @@ module Sass
348
295
  end
349
296
  end
350
297
 
351
- def else_directive(start_pos)
298
+ def else_directive
352
299
  err("Invalid CSS: @else must come after @if")
353
300
  end
354
301
 
355
- def extend_directive(start_pos)
356
- selector_start_pos = source_position
357
- @expected = "selector"
358
- selector = Sass::Util.strip_string_array(expr!(:almost_any_value))
302
+ def extend_directive
303
+ selector = expr!(:selector_sequence)
359
304
  optional = tok(OPTIONAL)
360
305
  ss
361
- node(Sass::Tree::ExtendNode.new(selector, !!optional, range(selector_start_pos)), start_pos)
306
+ node(Sass::Tree::ExtendNode.new(selector, !!optional))
362
307
  end
363
308
 
364
- def import_directive(start_pos)
309
+ def import_directive
365
310
  values = []
366
311
 
367
312
  loop do
@@ -371,40 +316,42 @@ module Sass
371
316
  ss
372
317
  end
373
318
 
374
- values
319
+ return values
375
320
  end
376
321
 
377
322
  def import_arg
378
- start_pos = source_position
379
- return unless (str = string) || (uri = tok?(/url\(/i))
323
+ line = @line
324
+ return unless (str = tok(STRING)) || (uri = tok?(/url\(/i))
380
325
  if uri
381
326
  str = sass_script(:parse_string)
382
327
  ss
383
328
  media = media_query_list
384
329
  ss
385
- return node(Tree::CssImportNode.new(str, media.to_a), start_pos)
330
+ return node(Tree::CssImportNode.new(str, media.to_a))
386
331
  end
332
+
333
+ path = @scanner[1] || @scanner[2]
387
334
  ss
388
335
 
389
336
  media = media_query_list
390
- if str =~ %r{^(https?:)?//} || media || use_css_import?
391
- return node(Sass::Tree::CssImportNode.new(
392
- Sass::Script::Value::String.quote(str), media.to_a), start_pos)
337
+ if path =~ /^(https?:)?\/\// || media || use_css_import?
338
+ node = Sass::Tree::CssImportNode.new(str, media.to_a)
339
+ else
340
+ node = Sass::Tree::ImportNode.new(path.strip)
393
341
  end
394
-
395
- node(Sass::Tree::ImportNode.new(str.strip), start_pos)
342
+ node.line = line
343
+ node
396
344
  end
397
345
 
398
346
  def use_css_import?; false; end
399
347
 
400
- def media_directive(start_pos)
401
- block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a), start_pos), :directive)
348
+ def media_directive
349
+ block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a)), :directive)
402
350
  end
403
351
 
404
352
  # http://www.w3.org/TR/css3-mediaqueries/#syntax
405
353
  def media_query_list
406
- query = media_query
407
- return unless query
354
+ return unless query = media_query
408
355
  queries = [query]
409
356
 
410
357
  ss
@@ -417,7 +364,7 @@ module Sass
417
364
  end
418
365
 
419
366
  def media_query
420
- if (ident1 = interp_ident)
367
+ if ident1 = interp_ident
421
368
  ss
422
369
  ident2 = interp_ident
423
370
  ss
@@ -437,8 +384,7 @@ module Sass
437
384
  if query
438
385
  expr = expr!(:media_expr)
439
386
  else
440
- expr = media_expr
441
- return unless expr
387
+ return unless expr = media_expr
442
388
  end
443
389
  query ||= Sass::Media::Query.new([], [], [])
444
390
  query.expressions << expr
@@ -451,9 +397,8 @@ module Sass
451
397
  query
452
398
  end
453
399
 
454
- def query_expr
455
- interp = interpolation
456
- return interp if interp
400
+ def media_expr
401
+ interp = interpolation and return interp
457
402
  return unless tok(/\(/)
458
403
  res = ['(']
459
404
  ss
@@ -469,15 +414,11 @@ module Sass
469
414
  res
470
415
  end
471
416
 
472
- # Aliases allow us to use different descriptions if the same
473
- # expression fails in different contexts.
474
- alias_method :media_expr, :query_expr
475
- alias_method :at_root_query, :query_expr
476
-
477
- def charset_directive(start_pos)
478
- name = expr!(:string)
417
+ def charset_directive
418
+ tok! STRING
419
+ name = @scanner[1] || @scanner[2]
479
420
  ss
480
- node(Sass::Tree::CharsetNode.new(name), start_pos)
421
+ node(Sass::Tree::CharsetNode.new(name))
481
422
  end
482
423
 
483
424
  # The document directive is specified in
@@ -488,65 +429,34 @@ module Sass
488
429
  # We could parse all document directives according to Mozilla's syntax,
489
430
  # but if someone's using e.g. @-webkit-document we don't want them to
490
431
  # think WebKit works sans quotes.
491
- def _moz_document_directive(start_pos)
432
+ def _moz_document_directive
492
433
  res = ["@-moz-document "]
493
434
  loop do
494
- res << str {ss} << expr!(:moz_document_function)
495
- if (c = tok(/,/))
496
- res << c
497
- else
498
- break
499
- end
435
+ res << str{ss} << expr!(:moz_document_function)
436
+ break unless c = tok(/,/)
437
+ res << c
500
438
  end
501
- directive_body(res.flatten, start_pos)
439
+ directive_body(res.flatten)
502
440
  end
503
441
 
504
442
  def moz_document_function
505
- val = interp_uri || _interp_string(:url_prefix) ||
443
+ return unless val = interp_uri || _interp_string(:url_prefix) ||
506
444
  _interp_string(:domain) || function(!:allow_var) || interpolation
507
- return unless val
508
445
  ss
509
446
  val
510
447
  end
511
448
 
512
- def at_root_directive(start_pos)
513
- if tok?(/\(/) && (expr = at_root_query)
514
- return block(node(Sass::Tree::AtRootNode.new(expr), start_pos), :directive)
515
- end
516
-
517
- at_root_node = node(Sass::Tree::AtRootNode.new, start_pos)
518
- rule_node = ruleset
519
- return block(at_root_node, :stylesheet) unless rule_node
520
- at_root_node << rule_node
521
- at_root_node
522
- end
523
-
524
- def at_root_directive_list
525
- return unless (first = tok(IDENT))
526
- arr = [first]
527
- ss
528
- while (e = tok(IDENT))
529
- arr << e
530
- ss
531
- end
532
- arr
533
- end
534
-
535
- def error_directive(start_pos)
536
- node(Sass::Tree::ErrorNode.new(sass_script(:parse)), start_pos)
537
- end
538
-
539
449
  # http://www.w3.org/TR/css3-conditional/
540
- def supports_directive(name, start_pos)
450
+ def supports_directive(name)
541
451
  condition = expr!(:supports_condition)
542
- node = Sass::Tree::SupportsNode.new(name, condition)
452
+ node = node(Sass::Tree::SupportsNode.new(name, condition))
543
453
 
544
454
  tok!(/\{/)
545
455
  node.has_children = true
546
456
  block_contents(node, :directive)
547
457
  tok!(/\}/)
548
458
 
549
- node(node, start_pos)
459
+ node
550
460
  end
551
461
 
552
462
  def supports_condition
@@ -560,21 +470,20 @@ module Sass
560
470
  end
561
471
 
562
472
  def supports_operator
563
- cond = supports_condition_in_parens
564
- return unless cond
565
- while (op = tok(/and|or/i))
473
+ return unless cond = supports_condition_in_parens
474
+ return cond unless op = tok(/and|or/i)
475
+ begin
566
476
  ss
567
477
  cond = Sass::Supports::Operator.new(
568
478
  cond, expr!(:supports_condition_in_parens), op)
569
- end
479
+ end while op = tok(/and|or/i)
570
480
  cond
571
481
  end
572
482
 
573
483
  def supports_condition_in_parens
574
- interp = supports_interpolation
575
- return interp if interp
484
+ interp = supports_interpolation and return interp
576
485
  return unless tok(/\(/); ss
577
- if (cond = supports_condition)
486
+ if cond = supports_condition
578
487
  tok!(/\)/); ss
579
488
  cond
580
489
  else
@@ -592,33 +501,19 @@ module Sass
592
501
  end
593
502
 
594
503
  def supports_interpolation
595
- interp = interpolation
596
- return unless interp
504
+ return unless interp = interpolation
597
505
  ss
598
506
  Sass::Supports::Interpolation.new(interp)
599
507
  end
600
508
 
601
509
  def variable
602
510
  return unless tok(/\$/)
603
- start_pos = source_position
604
511
  name = tok!(IDENT)
605
512
  ss; tok!(/:/); ss
606
513
 
607
514
  expr = sass_script(:parse)
608
- while tok(/!/)
609
- flag_name = tok!(IDENT)
610
- if flag_name == 'default'
611
- guarded ||= true
612
- elsif flag_name == 'global'
613
- global ||= true
614
- else
615
- raise Sass::SyntaxError.new("Invalid flag \"!#{flag_name}\".", :line => @line)
616
- end
617
- ss
618
- end
619
-
620
- result = Sass::Tree::VariableNode.new(name, expr, guarded, global)
621
- node(result, start_pos)
515
+ guarded = tok(DEFAULT)
516
+ node(Sass::Tree::VariableNode.new(name, expr, guarded))
622
517
  end
623
518
 
624
519
  def operator
@@ -630,10 +525,8 @@ module Sass
630
525
  end
631
526
 
632
527
  def ruleset
633
- start_pos = source_position
634
- return unless (rules = almost_any_value)
635
- block(node(
636
- Sass::Tree::RuleNode.new(rules, range(start_pos)), start_pos), :ruleset)
528
+ return unless rules = selector_sequence
529
+ block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
637
530
  end
638
531
 
639
532
  def block(node, context)
@@ -664,239 +557,355 @@ module Sass
664
557
  def has_children?(child_or_array)
665
558
  return false unless child_or_array
666
559
  return child_or_array.last.has_children if child_or_array.is_a?(Array)
667
- child_or_array.has_children
560
+ return child_or_array.has_children
668
561
  end
669
562
 
670
- # When parsing the contents of a ruleset, it can be difficult to tell
671
- # declarations apart from nested rulesets. Since we don't thoroughly parse
672
- # selectors until after resolving interpolation, we can share a bunch of
673
- # the parsing of the two, but we need to disambiguate them first. We use
674
- # the following criteria:
675
- #
676
- # * If the entity doesn't start with an identifier followed by a colon,
677
- # it's a selector. There are some additional mostly-unimportant cases
678
- # here to support various declaration hacks.
679
- #
680
- # * If the colon is followed by another colon, it's a selector.
563
+ # This is a nasty hack, and the only place in the parser
564
+ # that requires a large amount of backtracking.
565
+ # The reason is that we can't figure out if certain strings
566
+ # are declarations or rulesets with fixed finite lookahead.
567
+ # For example, "foo:bar baz baz baz..." could be either a property
568
+ # or a selector.
681
569
  #
682
- # * Otherwise, if the colon is followed by anything other than
683
- # interpolation or a character that's valid as the beginning of an
684
- # identifier, it's a declaration.
570
+ # To handle this, we simply check if it works as a property
571
+ # (which is the most common case)
572
+ # and, if it doesn't, try it as a ruleset.
685
573
  #
686
- # * If the colon is followed by interpolation or a valid identifier, try
687
- # parsing it as a declaration value. If this fails, backtrack and parse
688
- # it as a selector.
689
- #
690
- # * If the declaration value value valid but is followed by "{", backtrack
691
- # and parse it as a selector anyway. This ensures that ".foo:bar {" is
692
- # always parsed as a selector and never as a property with nested
693
- # properties beneath it.
574
+ # We could eke some more efficiency out of this
575
+ # by handling some easy cases (first token isn't an identifier,
576
+ # no colon after the identifier, whitespace after the colon),
577
+ # but I'm not sure the gains would be worth the added complexity.
694
578
  def declaration_or_ruleset
695
- start_pos = source_position
696
- declaration = try_declaration
579
+ old_use_property_exception, @use_property_exception =
580
+ @use_property_exception, false
581
+ decl_err = catch_error do
582
+ decl = declaration
583
+ unless decl && decl.has_children
584
+ # We want an exception if it's not there,
585
+ # but we don't want to consume if it is
586
+ tok!(/[;}]/) unless tok?(/[;}]/)
587
+ end
588
+ return decl
589
+ end
697
590
 
698
- if declaration.nil?
699
- return unless (selector = almost_any_value)
700
- elsif declaration.is_a?(Array)
701
- selector = declaration
702
- else
703
- # Declaration should be a PropNode.
704
- return declaration
591
+ ruleset_err = catch_error {return ruleset}
592
+ rethrow(@use_property_exception ? decl_err : ruleset_err)
593
+ ensure
594
+ @use_property_exception = old_use_property_exception
595
+ end
596
+
597
+ def selector_sequence
598
+ if sel = tok(STATIC_SELECTOR, true)
599
+ return [sel]
705
600
  end
706
601
 
707
- if (additional_selector = almost_any_value)
708
- selector << additional_selector
602
+ rules = []
603
+ return unless v = selector
604
+ rules.concat v
605
+
606
+ ws = ''
607
+ while tok(/,/)
608
+ ws << str {ss}
609
+ if v = selector
610
+ rules << ',' << ws
611
+ rules.concat v
612
+ ws = ''
613
+ end
709
614
  end
615
+ rules
616
+ end
710
617
 
711
- block(node(
712
- Sass::Tree::RuleNode.new(merge(selector), range(start_pos)), start_pos), :ruleset)
618
+ def selector
619
+ return unless sel = _selector
620
+ sel.to_a
713
621
  end
714
622
 
715
- # Tries to parse a declaration, and returns the value parsed so far if it
716
- # fails.
717
- #
718
- # This has three possible return types. It can return `nil`, indicating
719
- # that parsing failed completely and the scanner hasn't moved forward at
720
- # all. It can return an Array, indicating that parsing failed after
721
- # consuming some text (possibly containing interpolation), which is
722
- # returned. Or it can return a PropNode, indicating that parsing
723
- # succeeded.
724
- def try_declaration
725
- # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
726
- # val" hacks.
727
- name_start_pos = source_position
728
- if (s = tok(/[:\*\.]|\#(?!\{)/))
729
- name = [s, str {ss}]
730
- return name unless (ident = interp_ident)
731
- name << ident
732
- else
733
- return unless (name = interp_ident)
734
- name = Array(name)
623
+ def selector_comma_sequence
624
+ return unless sel = _selector
625
+ selectors = [sel]
626
+ ws = ''
627
+ while tok(/,/)
628
+ ws << str{ss}
629
+ if sel = _selector
630
+ selectors << sel
631
+ selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members) if ws.include?("\n")
632
+ ws = ''
633
+ end
735
634
  end
635
+ Selector::CommaSequence.new(selectors)
636
+ end
736
637
 
737
- if (comment = tok(COMMENT))
738
- name << comment
638
+ def _selector
639
+ # The combinator here allows the "> E" hack
640
+ return unless val = combinator || simple_selector_sequence
641
+ nl = str{ss}.include?("\n")
642
+ res = []
643
+ res << val
644
+ res << "\n" if nl
645
+
646
+ while val = combinator || simple_selector_sequence
647
+ res << val
648
+ res << "\n" if str{ss}.include?("\n")
649
+ end
650
+ Selector::Sequence.new(res.compact)
651
+ end
652
+
653
+ def combinator
654
+ tok(PLUS) || tok(GREATER) || tok(TILDE) || reference_combinator
655
+ end
656
+
657
+ def reference_combinator
658
+ return unless tok(/\//)
659
+ res = ['/']
660
+ ns, name = expr!(:qualified_name)
661
+ res << ns << '|' if ns
662
+ res << name << tok!(/\//)
663
+ res = res.flatten
664
+ res = res.join '' if res.all? {|e| e.is_a?(String)}
665
+ res
666
+ end
667
+
668
+ def simple_selector_sequence
669
+ # Returning expr by default allows for stuff like
670
+ # http://www.w3.org/TR/css3-animations/#keyframes-
671
+ return expr(!:allow_var) unless e = element_name || id_selector ||
672
+ class_selector || placeholder_selector || attrib || pseudo ||
673
+ parent_selector || interpolation_selector
674
+ res = [e]
675
+
676
+ # The tok(/\*/) allows the "E*" hack
677
+ while v = id_selector || class_selector || placeholder_selector || attrib ||
678
+ pseudo || interpolation_selector ||
679
+ (tok(/\*/) && Selector::Universal.new(nil))
680
+ res << v
739
681
  end
740
- name_end_pos = source_position
741
-
742
- mid = [str {ss}]
743
- return name + mid unless tok(/:/)
744
- mid << ':'
745
- return name + mid + [':'] if tok(/:/)
746
- mid << str {ss}
747
- post_colon_whitespace = !mid.last.empty?
748
- could_be_selector = !post_colon_whitespace && (tok?(IDENT_START) || tok?(INTERP_START))
749
-
750
- value_start_pos = source_position
751
- value = nil
752
- error = catch_error do
753
- value = value!
754
- if tok?(/\{/)
755
- # Properties that are ambiguous with selectors can't have additional
756
- # properties nested beneath them.
757
- tok!(/;/) if could_be_selector
758
- elsif !tok?(/[;{}]/)
759
- # We want an exception if there's no valid end-of-property character
760
- # exists, but we don't want to consume it if it does.
761
- tok!(/[;{}]/)
682
+
683
+ pos = @scanner.pos
684
+ line = @line
685
+ if sel = str? {simple_selector_sequence}
686
+ @scanner.pos = pos
687
+ @line = line
688
+ begin
689
+ # If we see "*E", don't force a throw because this could be the
690
+ # "*prop: val" hack.
691
+ expected('"{"') if res.length == 1 && res[0].is_a?(Selector::Universal)
692
+ throw_error {expected('"{"')}
693
+ rescue Sass::SyntaxError => e
694
+ e.message << "\n\n\"#{sel}\" may only be used at the beginning of a compound selector."
695
+ raise e
762
696
  end
763
697
  end
764
698
 
765
- if error
766
- rethrow error unless could_be_selector
699
+ Selector::SimpleSequence.new(res, tok(/!/))
700
+ end
701
+
702
+ def parent_selector
703
+ return unless tok(/&/)
704
+ Selector::Parent.new
705
+ end
706
+
707
+ def class_selector
708
+ return unless tok(/\./)
709
+ @expected = "class name"
710
+ Selector::Class.new(merge(expr!(:interp_ident)))
711
+ end
767
712
 
768
- # If the value would be followed by a semicolon, it's definitely
769
- # supposed to be a property, not a selector.
770
- additional_selector = almost_any_value
771
- rethrow error if tok?(/;/)
713
+ def id_selector
714
+ return unless tok(/#(?!\{)/)
715
+ @expected = "id name"
716
+ Selector::Id.new(merge(expr!(:interp_name)))
717
+ end
718
+
719
+ def placeholder_selector
720
+ return unless tok(/%/)
721
+ @expected = "placeholder name"
722
+ Selector::Placeholder.new(merge(expr!(:interp_ident)))
723
+ end
772
724
 
773
- return name + mid + (additional_selector || [])
725
+ def element_name
726
+ ns, name = Sass::Util.destructure(qualified_name(:allow_star_name))
727
+ return unless ns || name
728
+
729
+ if name == '*'
730
+ Selector::Universal.new(merge(ns))
731
+ else
732
+ Selector::Element.new(merge(name), merge(ns))
774
733
  end
734
+ end
735
+
736
+ def qualified_name(allow_star_name=false)
737
+ return unless name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
738
+ return nil, name unless tok(/\|/)
775
739
 
776
- value_end_pos = source_position
740
+ return name, expr!(:interp_ident) unless allow_star_name
741
+ @expected = "identifier or *"
742
+ return name, interp_ident || tok!(/\*/)
743
+ end
744
+
745
+ def interpolation_selector
746
+ return unless script = interpolation
747
+ Selector::Interpolation.new(script)
748
+ end
749
+
750
+ def attrib
751
+ return unless tok(/\[/)
752
+ ss
753
+ ns, name = attrib_name!
777
754
  ss
778
- require_block = tok?(/\{/)
779
755
 
780
- node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
781
- name_start_pos, value_end_pos)
782
- node.name_source_range = range(name_start_pos, name_end_pos)
783
- node.value_source_range = range(value_start_pos, value_end_pos)
756
+ if op = tok(/=/) ||
757
+ tok(INCLUDES) ||
758
+ tok(DASHMATCH) ||
759
+ tok(PREFIXMATCH) ||
760
+ tok(SUFFIXMATCH) ||
761
+ tok(SUBSTRINGMATCH)
762
+ @expected = "identifier or string"
763
+ ss
764
+ val = interp_ident || expr!(:interp_string)
765
+ ss
766
+ end
767
+ flags = interp_ident || interp_string
768
+ tok!(/\]/)
784
769
 
785
- return node unless require_block
786
- nested_properties! node
770
+ Selector::Attribute.new(merge(name), merge(ns), op, merge(val), merge(flags))
787
771
  end
788
772
 
789
- # This production is similar to the CSS [`<any-value>`][any-value]
790
- # production, but as the name implies, not quite the same. It's meant to
791
- # consume values that could be a selector, an expression, or a combination
792
- # of both. It respects strings and comments and supports interpolation. It
793
- # will consume up to "{", "}", ";", or "!".
794
- #
795
- # [any-value]: http://dev.w3.org/csswg/css-variables/#typedef-any-value
796
- #
797
- # Values consumed by this production will usually be parsed more
798
- # thoroughly once interpolation has been resolved.
799
- def almost_any_value
800
- return unless (tok = almost_any_value_token)
801
- sel = [tok]
802
- while (tok = almost_any_value_token)
803
- sel << tok
773
+ def attrib_name!
774
+ if name_or_ns = interp_ident
775
+ # E, E|E
776
+ if tok(/\|(?!=)/)
777
+ ns = name_or_ns
778
+ name = interp_ident
779
+ else
780
+ name = name_or_ns
781
+ end
782
+ else
783
+ # *|E or |E
784
+ ns = [tok(/\*/) || ""]
785
+ tok!(/\|/)
786
+ name = expr!(:interp_ident)
804
787
  end
805
- merge(sel)
788
+ return ns, name
806
789
  end
807
790
 
808
- def almost_any_value_token
809
- tok(%r{
810
- (
811
- (?!url\()
812
- [^"/\#!;\{\}] # "
813
- |
814
- /(?![/*])
815
- |
816
- \#(?!\{)
817
- |
818
- !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed.
819
- )+
820
- }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri ||
821
- interpolation(:warn_for_color)
791
+ def pseudo
792
+ return unless s = tok(/::?/)
793
+ @expected = "pseudoclass or pseudoelement"
794
+ name = expr!(:interp_ident)
795
+ if tok(/\(/)
796
+ ss
797
+ arg = expr!(:pseudo_arg)
798
+ while tok(/,/)
799
+ arg << ',' << str{ss}
800
+ arg.concat expr!(:pseudo_arg)
801
+ end
802
+ tok!(/\)/)
803
+ end
804
+ Selector::Pseudo.new(s == ':' ? :class : :element, merge(name), merge(arg))
805
+ end
806
+
807
+ def pseudo_arg
808
+ # In the CSS spec, every pseudo-class/element either takes a pseudo
809
+ # expression or a selector comma sequence as an argument. However, we
810
+ # don't want to have to know which takes which, so we handle both at
811
+ # once.
812
+ #
813
+ # However, there are some ambiguities between the two. For instance, "n"
814
+ # could start a pseudo expression like "n+1", or it could start a
815
+ # selector like "n|m". In order to handle this, we must regrettably
816
+ # backtrack.
817
+ expr, sel = nil, nil
818
+ pseudo_err = catch_error do
819
+ expr = pseudo_expr
820
+ next if tok?(/[,)]/)
821
+ expr = nil
822
+ expected '")"'
823
+ end
824
+
825
+ return expr if expr
826
+ sel_err = catch_error {sel = selector}
827
+ return sel if sel
828
+ rethrow pseudo_err if pseudo_err
829
+ rethrow sel_err if sel_err
830
+ return
831
+ end
832
+
833
+ def pseudo_expr
834
+ return unless e = tok(PLUS) || tok(/[-*]/) || tok(NUMBER) ||
835
+ interp_string || tok(IDENT) || interpolation
836
+ res = [e, str{ss}]
837
+ while e = tok(PLUS) || tok(/[-*]/) || tok(NUMBER) ||
838
+ interp_string || tok(IDENT) || interpolation
839
+ res << e << str{ss}
840
+ end
841
+ res
822
842
  end
823
843
 
824
844
  def declaration
825
- # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
826
- # val" hacks.
827
- name_start_pos = source_position
828
- if (s = tok(/[:\*\.]|\#(?!\{)/))
829
- name = [s, str {ss}, *expr!(:interp_ident)]
845
+ # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
846
+ if s = tok(/[:\*\.]|\#(?!\{)/)
847
+ @use_property_exception = s !~ /[\.\#]/
848
+ name = [s, str{ss}, *expr!(:interp_ident)]
830
849
  else
831
- return unless (name = interp_ident)
832
- name = Array(name)
850
+ return unless name = interp_ident
851
+ name = [name] if name.is_a?(String)
833
852
  end
834
-
835
- if (comment = tok(COMMENT))
853
+ if comment = tok(COMMENT)
836
854
  name << comment
837
855
  end
838
- name_end_pos = source_position
839
856
  ss
840
857
 
841
858
  tok!(/:/)
842
- ss
843
- value_start_pos = source_position
844
- value = value!
845
- value_end_pos = source_position
859
+ space, value = value!
846
860
  ss
847
861
  require_block = tok?(/\{/)
848
862
 
849
- node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
850
- name_start_pos, value_end_pos)
851
- node.name_source_range = range(name_start_pos, name_end_pos)
852
- node.value_source_range = range(value_start_pos, value_end_pos)
863
+ node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new))
853
864
 
854
865
  return node unless require_block
855
- nested_properties! node
866
+ nested_properties! node, space
856
867
  end
857
868
 
858
869
  def value!
859
- if tok?(/\{/)
860
- str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
861
- str.line = source_position.line
862
- str.source_range = range(source_position)
863
- return str
864
- end
870
+ space = !str {ss}.empty?
871
+ @use_property_exception ||= space || !tok?(IDENT)
865
872
 
866
- start_pos = source_position
873
+ return true, Sass::Script::String.new("") if tok?(/\{/)
867
874
  # This is a bit of a dirty trick:
868
875
  # if the value is completely static,
869
876
  # we don't parse it at all, and instead return a plain old string
870
877
  # containing the value.
871
878
  # This results in a dramatic speed increase.
872
- if (val = tok(STATIC_VALUE, true))
873
- str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(val.strip))
874
- str.line = start_pos.line
875
- str.source_range = range(start_pos)
876
- return str
879
+ if val = tok(STATIC_VALUE, true)
880
+ return space, Sass::Script::String.new(val.strip)
877
881
  end
878
- sass_script(:parse)
882
+ return space, sass_script(:parse)
879
883
  end
880
884
 
881
- def nested_properties!(node)
885
+ def nested_properties!(node, space)
886
+ err(<<MESSAGE) unless space
887
+ Invalid CSS: a space is required between a property and its definition
888
+ when it has other properties nested beneath it.
889
+ MESSAGE
890
+
891
+ @use_property_exception = true
882
892
  @expected = 'expression (e.g. 1px, bold) or "{"'
883
893
  block(node, :property)
884
894
  end
885
895
 
886
896
  def expr(allow_var = true)
887
- t = term(allow_var)
888
- return unless t
889
- res = [t, str {ss}]
897
+ return unless t = term(allow_var)
898
+ res = [t, str{ss}]
890
899
 
891
900
  while (o = operator) && (t = term(allow_var))
892
- res << o << t << str {ss}
901
+ res << o << t << str{ss}
893
902
  end
894
903
 
895
904
  res.flatten
896
905
  end
897
906
 
898
907
  def term(allow_var)
899
- e = tok(NUMBER) ||
908
+ if e = tok(NUMBER) ||
900
909
  interp_uri ||
901
910
  function(allow_var) ||
902
911
  interp_string ||
@@ -904,42 +913,36 @@ module Sass
904
913
  interp_ident ||
905
914
  tok(HEXCOLOR) ||
906
915
  (allow_var && var_expr)
907
- return e if e
916
+ return e
917
+ end
908
918
 
909
- op = tok(/[+-]/)
910
- return unless op
919
+ return unless op = tok(/[+-]/)
911
920
  @expected = "number or function"
912
- [op,
913
- tok(NUMBER) || function(allow_var) || (allow_var && var_expr) || expr!(:interpolation)]
921
+ return [op, tok(NUMBER) || function(allow_var) ||
922
+ (allow_var && var_expr) || expr!(:interpolation)]
914
923
  end
915
924
 
916
925
  def function(allow_var)
917
- name = tok(FUNCTION)
918
- return unless name
926
+ return unless name = tok(FUNCTION)
919
927
  if name == "expression(" || name == "calc("
920
928
  str, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
921
929
  [name, str]
922
930
  else
923
- [name, str {ss}, expr(allow_var), tok!(/\)/)]
931
+ [name, str{ss}, expr(allow_var), tok!(/\)/)]
924
932
  end
925
933
  end
926
934
 
927
935
  def var_expr
928
936
  return unless tok(/\$/)
929
937
  line = @line
930
- var = Sass::Script::Tree::Variable.new(tok!(IDENT))
938
+ var = Sass::Script::Variable.new(tok!(IDENT))
931
939
  var.line = line
932
940
  var
933
941
  end
934
942
 
935
- def interpolation(warn_for_color = false)
943
+ def interpolation
936
944
  return unless tok(INTERP_START)
937
- sass_script(:parse_interpolated, warn_for_color)
938
- end
939
-
940
- def string
941
- return unless tok(STRING)
942
- Sass::Script::Value::String.value(@scanner[1] || @scanner[2])
945
+ sass_script(:parse_interpolated)
943
946
  end
944
947
 
945
948
  def interp_string
@@ -951,8 +954,7 @@ module Sass
951
954
  end
952
955
 
953
956
  def _interp_string(type)
954
- start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][false])
955
- return unless start
957
+ return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][false])
956
958
  res = [start]
957
959
 
958
960
  mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][true]
@@ -966,20 +968,21 @@ module Sass
966
968
  end
967
969
 
968
970
  def interp_ident(start = IDENT)
969
- val = tok(start) || interpolation(:warn_for_color) || tok(IDENT_HYPHEN_INTERP, true)
970
- return unless val
971
+ return unless val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
971
972
  res = [val]
972
- while (val = tok(NAME) || interpolation(:warn_for_color))
973
+ while val = tok(NAME) || interpolation
973
974
  res << val
974
975
  end
975
976
  res
976
977
  end
977
978
 
978
979
  def interp_ident_or_var
979
- id = interp_ident
980
- return id if id
981
- var = var_expr
982
- return [var] if var
980
+ (id = interp_ident) and return id
981
+ (var = var_expr) and return [var]
982
+ end
983
+
984
+ def interp_name
985
+ interp_ident NAME
983
986
  end
984
987
 
985
988
  def str
@@ -993,35 +996,29 @@ module Sass
993
996
  def str?
994
997
  pos = @scanner.pos
995
998
  line = @line
996
- offset = @offset
997
999
  @strs.push ""
998
1000
  throw_error {yield} && @strs.last
999
1001
  rescue Sass::SyntaxError
1000
1002
  @scanner.pos = pos
1001
1003
  @line = line
1002
- @offset = offset
1003
1004
  nil
1004
1005
  ensure
1005
1006
  @strs.pop
1006
1007
  end
1007
1008
 
1008
- def node(node, start_pos, end_pos = source_position)
1009
- node.line = start_pos.line
1010
- node.source_range = range(start_pos, end_pos)
1009
+ def node(node)
1010
+ node.line = @line
1011
1011
  node
1012
1012
  end
1013
1013
 
1014
1014
  @sass_script_parser = Class.new(Sass::Script::Parser)
1015
1015
  @sass_script_parser.send(:include, ScriptParser)
1016
-
1017
- class << self
1018
- # @private
1019
- attr_accessor :sass_script_parser
1020
- end
1016
+ # @private
1017
+ def self.sass_script_parser; @sass_script_parser; end
1021
1018
 
1022
1019
  def sass_script(*args)
1023
- parser = self.class.sass_script_parser.new(@scanner, @line, @offset,
1024
- :filename => @filename, :importer => @importer)
1020
+ parser = self.class.sass_script_parser.new(@scanner, @line,
1021
+ @scanner.pos - (@scanner.string[0...@scanner.pos].rindex("\n") || 0))
1025
1022
  result = parser.send(*args)
1026
1023
  unless @strs.empty?
1027
1024
  # Convert to CSS manually so that comments are ignored.
@@ -1029,7 +1026,6 @@ module Sass
1029
1026
  @strs.each {|s| s << src}
1030
1027
  end
1031
1028
  @line = parser.line
1032
- @offset = parser.offset
1033
1029
  result
1034
1030
  rescue Sass::SyntaxError => e
1035
1031
  throw(:_sass_parser_error, true) if @throw_error
@@ -1044,51 +1040,41 @@ module Sass
1044
1040
  :media_query => "media query (e.g. print, screen, print and screen)",
1045
1041
  :media_query_list => "media query (e.g. print, screen, print and screen)",
1046
1042
  :media_expr => "media expression (e.g. (min-device-width: 800px))",
1047
- :at_root_query => "@at-root query (e.g. (without: media))",
1048
- :at_root_directive_list => '* or identifier',
1049
- :pseudo_args => "expression (e.g. fr, 2n+1)",
1043
+ :pseudo_arg => "expression (e.g. fr, 2n+1)",
1050
1044
  :interp_ident => "identifier",
1045
+ :interp_name => "identifier",
1051
1046
  :qualified_name => "identifier",
1052
1047
  :expr => "expression (e.g. 1px, bold)",
1048
+ :_selector => "selector",
1053
1049
  :selector_comma_sequence => "selector",
1054
- :string => "string",
1050
+ :simple_selector_sequence => "selector",
1055
1051
  :import_arg => "file to import (string or url())",
1056
1052
  :moz_document_function => "matching function (e.g. url-prefix(), domain())",
1057
1053
  :supports_condition => "@supports condition (e.g. (display: flexbox))",
1058
1054
  :supports_condition_in_parens => "@supports condition (e.g. (display: flexbox))",
1059
- :a_n_plus_b => "An+B expression",
1060
- :keyframes_selector_component => "from, to, or a percentage",
1061
- :keyframes_selector => "keyframes selector (e.g. 10%)"
1062
1055
  }
1063
1056
 
1064
- TOK_NAMES = Sass::Util.to_hash(Sass::SCSS::RX.constants.map do |c|
1065
- [Sass::SCSS::RX.const_get(c), c.downcase]
1066
- end).merge(
1067
- IDENT => "identifier",
1068
- /[;{}]/ => '";"',
1069
- /\b(without|with)\b/ => '"with" or "without"'
1070
- )
1057
+ TOK_NAMES = Sass::Util.to_hash(
1058
+ Sass::SCSS::RX.constants.map {|c| [Sass::SCSS::RX.const_get(c), c.downcase]}).
1059
+ merge(IDENT => "identifier", /[;}]/ => '";"')
1071
1060
 
1072
1061
  def tok?(rx)
1073
1062
  @scanner.match?(rx)
1074
1063
  end
1075
1064
 
1076
1065
  def expr!(name)
1077
- e = send(name)
1078
- return e if e
1066
+ (e = send(name)) && (return e)
1079
1067
  expected(EXPR_NAMES[name] || name.to_s)
1080
1068
  end
1081
1069
 
1082
1070
  def tok!(rx)
1083
- t = tok(rx)
1084
- return t if t
1071
+ (t = tok(rx)) && (return t)
1085
1072
  name = TOK_NAMES[rx]
1086
1073
 
1087
1074
  unless name
1088
1075
  # Display basic regexps as plain old strings
1089
- source = rx.source.gsub(/\\\//, '/')
1090
1076
  string = rx.source.gsub(/\\(.)/, '\1')
1091
- name = source == Regexp.escape(string) ? string.inspect : rx.inspect
1077
+ name = rx.source == Regexp.escape(string) ? string.inspect : rx.inspect
1092
1078
  end
1093
1079
 
1094
1080
  expected(name)
@@ -1115,12 +1101,10 @@ module Sass
1115
1101
  old_throw_error, @throw_error = @throw_error, true
1116
1102
  pos = @scanner.pos
1117
1103
  line = @line
1118
- offset = @offset
1119
1104
  expected = @expected
1120
1105
  if catch(:_sass_parser_error) {yield; false}
1121
1106
  @scanner.pos = pos
1122
1107
  @line = line
1123
- @offset = offset
1124
1108
  @expected = expected
1125
1109
  {:pos => pos, :line => line, :expected => @expected, :block => block}
1126
1110
  end
@@ -1183,15 +1167,7 @@ module Sass
1183
1167
  @scanner.pos -= @scanner[-1].length
1184
1168
  res.slice!(-@scanner[-1].length..-1)
1185
1169
  end
1186
-
1187
- newline_count = res.count(NEWLINE)
1188
- if newline_count > 0
1189
- @line += newline_count
1190
- @offset = res[res.rindex(NEWLINE)..-1].size
1191
- else
1192
- @offset += res.size
1193
- end
1194
-
1170
+ @line += res.count(NEWLINE)
1195
1171
  @expected = nil
1196
1172
  if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
1197
1173
  @strs.each {|s| s << res}
@@ -1199,11 +1175,6 @@ module Sass
1199
1175
  res
1200
1176
  end
1201
1177
  end
1202
-
1203
- # Remove a vendor prefix from `str`.
1204
- def deprefix(str)
1205
- str.gsub(/^-[a-zA-Z0-9]+-/, '')
1206
- end
1207
1178
  end
1208
1179
  end
1209
1180
  end