haml 2.2.24 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

Files changed (168) hide show
  1. data/.yardopts +0 -1
  2. data/README.md +91 -151
  3. data/REMEMBER +11 -1
  4. data/Rakefile +73 -55
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/css2sass +7 -1
  8. data/bin/sass-convert +7 -0
  9. data/extra/haml-mode.el +2 -1
  10. data/lib/haml/buffer.rb +22 -4
  11. data/lib/haml/engine.rb +5 -1
  12. data/lib/haml/exec.rb +231 -46
  13. data/lib/haml/filters.rb +19 -8
  14. data/lib/haml/helpers.rb +47 -20
  15. data/lib/haml/helpers/action_view_extensions.rb +2 -4
  16. data/lib/haml/helpers/action_view_mods.rb +11 -8
  17. data/lib/haml/helpers/xss_mods.rb +13 -2
  18. data/lib/haml/html.rb +179 -48
  19. data/lib/haml/html/erb.rb +141 -0
  20. data/lib/haml/precompiler.rb +40 -15
  21. data/lib/haml/railtie.rb +1 -5
  22. data/lib/haml/root.rb +3 -0
  23. data/lib/haml/template.rb +4 -14
  24. data/lib/haml/util.rb +120 -30
  25. data/lib/haml/version.rb +25 -2
  26. data/lib/sass.rb +5 -1
  27. data/lib/sass/callbacks.rb +50 -0
  28. data/lib/sass/css.rb +40 -191
  29. data/lib/sass/engine.rb +170 -74
  30. data/lib/sass/environment.rb +8 -2
  31. data/lib/sass/error.rb +163 -25
  32. data/lib/sass/files.rb +31 -28
  33. data/lib/sass/plugin.rb +268 -87
  34. data/lib/sass/plugin/rails.rb +9 -4
  35. data/lib/sass/repl.rb +1 -1
  36. data/lib/sass/script.rb +31 -29
  37. data/lib/sass/script/bool.rb +1 -0
  38. data/lib/sass/script/color.rb +290 -23
  39. data/lib/sass/script/css_lexer.rb +22 -0
  40. data/lib/sass/script/css_parser.rb +28 -0
  41. data/lib/sass/script/funcall.rb +22 -3
  42. data/lib/sass/script/functions.rb +523 -33
  43. data/lib/sass/script/interpolation.rb +42 -0
  44. data/lib/sass/script/lexer.rb +169 -52
  45. data/lib/sass/script/literal.rb +58 -9
  46. data/lib/sass/script/node.rb +79 -1
  47. data/lib/sass/script/number.rb +20 -5
  48. data/lib/sass/script/operation.rb +49 -3
  49. data/lib/sass/script/parser.rb +162 -28
  50. data/lib/sass/script/string.rb +50 -2
  51. data/lib/sass/script/unary_operation.rb +25 -2
  52. data/lib/sass/script/variable.rb +21 -4
  53. data/lib/sass/scss.rb +14 -0
  54. data/lib/sass/scss/css_parser.rb +39 -0
  55. data/lib/sass/scss/parser.rb +683 -0
  56. data/lib/sass/scss/rx.rb +112 -0
  57. data/lib/sass/scss/script_lexer.rb +13 -0
  58. data/lib/sass/scss/script_parser.rb +25 -0
  59. data/lib/sass/tree/comment_node.rb +69 -27
  60. data/lib/sass/tree/debug_node.rb +7 -2
  61. data/lib/sass/tree/directive_node.rb +41 -35
  62. data/lib/sass/tree/for_node.rb +6 -0
  63. data/lib/sass/tree/if_node.rb +13 -1
  64. data/lib/sass/tree/import_node.rb +52 -27
  65. data/lib/sass/tree/mixin_def_node.rb +18 -0
  66. data/lib/sass/tree/mixin_node.rb +41 -6
  67. data/lib/sass/tree/node.rb +197 -70
  68. data/lib/sass/tree/prop_node.rb +152 -57
  69. data/lib/sass/tree/root_node.rb +118 -0
  70. data/lib/sass/tree/rule_node.rb +193 -96
  71. data/lib/sass/tree/variable_node.rb +9 -5
  72. data/lib/sass/tree/while_node.rb +4 -0
  73. data/test/benchmark.rb +5 -5
  74. data/test/haml/engine_test.rb +147 -10
  75. data/test/haml/{rhtml/_av_partial_1.rhtml → erb/_av_partial_1.erb} +1 -1
  76. data/test/haml/{rhtml/_av_partial_2.rhtml → erb/_av_partial_2.erb} +1 -1
  77. data/test/haml/{rhtml/action_view.rhtml → erb/action_view.erb} +1 -1
  78. data/test/haml/{rhtml/standard.rhtml → erb/standard.erb} +0 -0
  79. data/test/haml/helper_test.rb +91 -24
  80. data/test/haml/html2haml/erb_tests.rb +410 -0
  81. data/test/haml/html2haml_test.rb +210 -66
  82. data/test/haml/results/filters.xhtml +1 -1
  83. data/test/haml/results/just_stuff.xhtml +2 -0
  84. data/test/haml/spec_test.rb +44 -0
  85. data/test/haml/template_test.rb +22 -2
  86. data/test/haml/templates/helpers.haml +0 -13
  87. data/test/haml/templates/just_stuff.haml +2 -0
  88. data/test/haml/util_test.rb +48 -0
  89. data/test/sass/callbacks_test.rb +61 -0
  90. data/test/sass/conversion_test.rb +884 -0
  91. data/test/sass/css2sass_test.rb +99 -18
  92. data/test/sass/data/hsl-rgb.txt +319 -0
  93. data/test/sass/engine_test.rb +1049 -131
  94. data/test/sass/functions_test.rb +398 -47
  95. data/test/sass/more_results/more_import.css +1 -1
  96. data/test/sass/more_templates/more_import.sass +3 -3
  97. data/test/sass/plugin_test.rb +184 -10
  98. data/test/sass/results/compact.css +1 -1
  99. data/test/sass/results/complex.css +5 -5
  100. data/test/sass/results/compressed.css +1 -1
  101. data/test/sass/results/expanded.css +1 -1
  102. data/test/sass/results/import.css +3 -1
  103. data/test/sass/results/mixins.css +12 -12
  104. data/test/sass/results/nested.css +1 -1
  105. data/test/sass/results/options.css +1 -0
  106. data/test/sass/results/parent_ref.css +4 -4
  107. data/test/sass/results/script.css +3 -3
  108. data/test/sass/results/scss_import.css +15 -0
  109. data/test/sass/results/scss_importee.css +2 -0
  110. data/test/sass/script_conversion_test.rb +153 -0
  111. data/test/sass/script_test.rb +137 -70
  112. data/test/sass/scss/css_test.rb +811 -0
  113. data/test/sass/scss/rx_test.rb +156 -0
  114. data/test/sass/scss/scss_test.rb +871 -0
  115. data/test/sass/scss/test_helper.rb +37 -0
  116. data/test/sass/templates/alt.sass +2 -2
  117. data/test/sass/templates/bork1.sass +2 -0
  118. data/test/sass/templates/bork3.sass +2 -0
  119. data/test/sass/templates/bork4.sass +2 -0
  120. data/test/sass/templates/import.sass +4 -4
  121. data/test/sass/templates/importee.sass +3 -3
  122. data/test/sass/templates/line_numbers.sass +1 -1
  123. data/test/sass/templates/mixin_bork.sass +5 -0
  124. data/test/sass/templates/mixins.sass +2 -2
  125. data/test/sass/templates/nested_bork1.sass +2 -0
  126. data/test/sass/templates/nested_bork2.sass +2 -0
  127. data/test/sass/templates/nested_bork3.sass +2 -0
  128. data/test/sass/templates/nested_bork4.sass +2 -0
  129. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  130. data/test/sass/templates/options.sass +2 -0
  131. data/test/sass/templates/parent_ref.sass +2 -2
  132. data/test/sass/templates/script.sass +69 -69
  133. data/test/sass/templates/scss_import.scss +10 -0
  134. data/test/sass/templates/scss_importee.scss +1 -0
  135. data/test/sass/templates/units.sass +10 -10
  136. data/test/test_helper.rb +20 -8
  137. data/vendor/fssm/LICENSE +20 -0
  138. data/vendor/fssm/README.markdown +55 -0
  139. data/vendor/fssm/Rakefile +59 -0
  140. data/vendor/fssm/VERSION.yml +5 -0
  141. data/vendor/fssm/example.rb +9 -0
  142. data/vendor/fssm/fssm.gemspec +77 -0
  143. data/vendor/fssm/lib/fssm.rb +33 -0
  144. data/vendor/fssm/lib/fssm/backends/fsevents.rb +36 -0
  145. data/vendor/fssm/lib/fssm/backends/inotify.rb +26 -0
  146. data/vendor/fssm/lib/fssm/backends/polling.rb +25 -0
  147. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +131 -0
  148. data/vendor/fssm/lib/fssm/monitor.rb +26 -0
  149. data/vendor/fssm/lib/fssm/path.rb +91 -0
  150. data/vendor/fssm/lib/fssm/pathname.rb +502 -0
  151. data/vendor/fssm/lib/fssm/state/directory.rb +57 -0
  152. data/vendor/fssm/lib/fssm/state/file.rb +24 -0
  153. data/vendor/fssm/lib/fssm/support.rb +63 -0
  154. data/vendor/fssm/lib/fssm/tree.rb +176 -0
  155. data/vendor/fssm/profile/prof-cache.rb +40 -0
  156. data/vendor/fssm/profile/prof-fssm-pathname.html +1231 -0
  157. data/vendor/fssm/profile/prof-pathname.rb +68 -0
  158. data/vendor/fssm/profile/prof-plain-pathname.html +988 -0
  159. data/vendor/fssm/profile/prof.html +2379 -0
  160. data/vendor/fssm/spec/path_spec.rb +75 -0
  161. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  162. data/vendor/fssm/spec/root/file.css +0 -0
  163. data/vendor/fssm/spec/root/file.rb +0 -0
  164. data/vendor/fssm/spec/root/file.yml +0 -0
  165. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  166. data/vendor/fssm/spec/spec_helper.rb +14 -0
  167. metadata +94 -14
  168. data/test/sass/templates/bork.sass +0 -2
@@ -3,12 +3,90 @@ module Sass::Script
3
3
  #
4
4
  # Use \{#perform} to evaluate a parse tree.
5
5
  class Node
6
+ # The options hash for this node.
7
+ #
8
+ # @return [{Symbol => Object}]
9
+ attr_reader :options
10
+
11
+ # The context in which this node was parsed,
12
+ # which determines how some operations are performed.
13
+ #
14
+ # Can be `:equals`, which means it's part of a `$var = val` or `prop = val` assignment,
15
+ # or `:default`, which means it's anywhere else
16
+ # (including `$var: val` and `prop: val` assignments,
17
+ # `#{}`-interpolations,
18
+ # and other script contexts such as `@if` conditions).
19
+ #
20
+ # @return [Symbol]
21
+ attr_reader :context
22
+
23
+ # The line of the document on which this node appeared.
24
+ #
25
+ # @return [Fixnum]
26
+ attr_accessor :line
27
+
28
+ # Sets the options hash for this node,
29
+ # as well as for all child nodes.
30
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
31
+ #
32
+ # @param options [{Symbol => Object}] The options
33
+ def options=(options)
34
+ @options = options
35
+ children.each {|c| c.options = options}
36
+ end
37
+
38
+ # Sets the options hash for this node,
39
+ # as well as for all child nodes.
40
+ #
41
+ # @param context [Symbol]
42
+ # @see #context
43
+ def context=(context)
44
+ @context = context
45
+ children.each {|c| c.context = context}
46
+ end
47
+
48
+ # Creates a new script node.
49
+ def initialize
50
+ @context = :default
51
+ end
52
+
6
53
  # Evaluates the node.
7
54
  #
55
+ # \{#perform} shouldn't be overridden directly;
56
+ # instead, override \{#\_perform}.
57
+ #
8
58
  # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
9
59
  # @return [Literal] The SassScript object that is the value of the SassScript
10
60
  def perform(environment)
11
- raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #perform.")
61
+ _perform(environment)
62
+ rescue Sass::SyntaxError => e
63
+ e.modify_backtrace(:line => line)
64
+ raise e
65
+ end
66
+
67
+ # Returns all child nodes of this node.
68
+ #
69
+ # @return [Array<Node>]
70
+ def children
71
+ raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #children.")
72
+ end
73
+
74
+ # Returns the text of this SassScript expression.
75
+ #
76
+ # @return [String]
77
+ def to_sass
78
+ raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #to_sass.")
79
+ end
80
+
81
+ protected
82
+
83
+ # Evaluates this node.
84
+ #
85
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
86
+ # @return [Literal] The SassScript object that is the value of the SassScript
87
+ # @see #perform
88
+ def _perform(environment)
89
+ raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #_perform.")
12
90
  end
13
91
  end
14
92
  end
@@ -25,6 +25,8 @@ module Sass::Script
25
25
  # @return [Array<String>]
26
26
  attr_reader :denominator_units
27
27
 
28
+ attr_accessor :original
29
+
28
30
  # The precision with which numbers will be printed to CSS files.
29
31
  # For example, if this is `1000.0`,
30
32
  # `3.1415926` will be printed as `3.142`.
@@ -65,7 +67,7 @@ module Sass::Script
65
67
  end
66
68
  end
67
69
 
68
- # The SassScript binary `-` operation (e.g. `!a - !b`).
70
+ # The SassScript binary `-` operation (e.g. `$a - $b`).
69
71
  # Its functionality depends on the type of its argument:
70
72
  #
71
73
  # {Number}
@@ -85,7 +87,14 @@ module Sass::Script
85
87
  end
86
88
  end
87
89
 
88
- # The SassScript unary `-` operation (e.g. `-!a`).
90
+ # The SassScript unary `+` operation (e.g. `+$a`).
91
+ #
92
+ # @return [Number] The value of this number
93
+ def unary_plus
94
+ self
95
+ end
96
+
97
+ # The SassScript unary `-` operation (e.g. `-$a`).
89
98
  #
90
99
  # @return [Number] The negative value of this number
91
100
  def unary_minus
@@ -106,7 +115,7 @@ module Sass::Script
106
115
  # @raise [NoMethodError] if `other` is an invalid type
107
116
  def times(other)
108
117
  if other.is_a? Number
109
- operate(other, :*)
118
+ self.operate(other, :*)
110
119
  elsif other.is_a? Color
111
120
  other.times(self)
112
121
  else
@@ -127,7 +136,11 @@ module Sass::Script
127
136
  # @return [Literal] The result of the operation
128
137
  def div(other)
129
138
  if other.is_a? Number
130
- operate(other, :/)
139
+ res = operate(other, :/)
140
+ if self.original && other.original && context != :equals
141
+ res.original = "#{self.original}/#{other.original}"
142
+ end
143
+ res
131
144
  else
132
145
  super
133
146
  end
@@ -214,6 +227,7 @@ module Sass::Script
214
227
  # @raise [Sass::SyntaxError] if this number has units that can't be used in CSS
215
228
  # (e.g. `px*in`)
216
229
  def to_s
230
+ return original if original
217
231
  raise Sass::SyntaxError.new("#{inspect} isn't a valid CSS value.") unless legal_units?
218
232
  inspect
219
233
  end
@@ -235,6 +249,7 @@ module Sass::Script
235
249
  end
236
250
  "#{value}#{unit_str}"
237
251
  end
252
+ alias_method :to_sass, :inspect
238
253
 
239
254
  # @return [Fixnum] The integer value of the number
240
255
  # @raise [Sass::SyntaxError] if the number isn't an integer
@@ -284,7 +299,7 @@ module Sass::Script
284
299
  end, num_units, den_units)
285
300
  end
286
301
 
287
- private
302
+ protected
288
303
 
289
304
  def operate(other, operation)
290
305
  this = self
@@ -4,11 +4,16 @@ require 'sass/script/number'
4
4
  require 'sass/script/color'
5
5
  require 'sass/script/functions'
6
6
  require 'sass/script/unary_operation'
7
+ require 'sass/script/interpolation'
7
8
 
8
9
  module Sass::Script
9
10
  # A SassScript parse node representing a binary operation,
10
- # such as `!a + !b` or `"foo" + 1`.
11
+ # such as `$a + $b` or `"foo" + 1`.
11
12
  class Operation < Node
13
+ attr_reader :operand1
14
+ attr_reader :operand2
15
+ attr_reader :operator
16
+
12
17
  # @param operand1 [Script::Node] The parse-tree node
13
18
  # for the right-hand side of the operator
14
19
  # @param operand2 [Script::Node] The parse-tree node
@@ -19,6 +24,7 @@ module Sass::Script
19
24
  @operand1 = operand1
20
25
  @operand2 = operand2
21
26
  @operator = operator
27
+ super()
22
28
  end
23
29
 
24
30
  # @return [String] A human-readable s-expression representation of the operation
@@ -26,20 +32,60 @@ module Sass::Script
26
32
  "(#{@operator.inspect} #{@operand1.inspect} #{@operand2.inspect})"
27
33
  end
28
34
 
35
+ # @see Node#to_sass
36
+ def to_sass
37
+ pred = Sass::Script::Parser.precedence_of(@operator)
38
+ o1 = operand_to_sass pred, @operand1
39
+ o2 = operand_to_sass pred, @operand2
40
+ sep =
41
+ case @operator
42
+ when :comma; ", "
43
+ when :concat; " "
44
+ else; " #{Lexer::OPERATORS_REVERSE[@operator]} "
45
+ end
46
+ "#{o1}#{sep}#{o2}"
47
+ end
48
+
49
+ # Returns the operands for this operation.
50
+ #
51
+ # @return [Array<Node>]
52
+ # @see Node#children
53
+ def children
54
+ [@operand1, @operand2]
55
+ end
56
+
57
+ protected
58
+
29
59
  # Evaluates the operation.
30
60
  #
31
61
  # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
32
62
  # @return [Literal] The SassScript object that is the value of the operation
33
63
  # @raise [Sass::SyntaxError] if the operation is undefined for the operands
34
- def perform(environment)
64
+ def _perform(environment)
35
65
  literal1 = @operand1.perform(environment)
36
66
  literal2 = @operand2.perform(environment)
67
+
68
+ if @operator == :concat && context == :equals
69
+ literal1 = Sass::Script::String.new(literal1.value) if literal1.is_a?(Sass::Script::String)
70
+ literal2 = Sass::Script::String.new(literal2.value) if literal2.is_a?(Sass::Script::String)
71
+ end
72
+
37
73
  begin
38
- literal1.send(@operator, literal2)
74
+ res = literal1.send(@operator, literal2)
75
+ res.options = environment.options
76
+ res
39
77
  rescue NoMethodError => e
40
78
  raise e unless e.name.to_s == @operator.to_s
41
79
  raise Sass::SyntaxError.new("Undefined operation: \"#{literal1} #{@operator} #{literal2}\".")
42
80
  end
43
81
  end
82
+
83
+ private
84
+
85
+ def operand_to_sass(pred, op)
86
+ return "(#{op.to_sass})" if op.is_a?(Operation) &&
87
+ Sass::Script::Parser.precedence_of(op.operator) < pred
88
+ op.to_sass
89
+ end
44
90
  end
45
91
  end
@@ -5,16 +5,23 @@ module Sass
5
5
  # The parser for SassScript.
6
6
  # It parses a string of code into a tree of {Script::Node}s.
7
7
  class Parser
8
+ # The line number of the parser's current position.
9
+ #
10
+ # @return [Fixnum]
11
+ def line
12
+ @lexer.line
13
+ end
14
+
8
15
  # @param str [String, StringScanner] The source text to parse
9
16
  # @param line [Fixnum] The line on which the SassScript appears.
10
17
  # Used for error reporting
11
18
  # @param offset [Fixnum] The number of characters in on which the SassScript appears.
12
19
  # Used for error reporting
13
- # @param filename [String] The name of the file in which the SassScript appears.
14
- # Used for error reporting
15
- def initialize(str, line, offset, filename = nil)
16
- @filename = filename
17
- @lexer = Lexer.new(str, line, offset, filename)
20
+ # @param options [{Symbol => Object}] An options hash;
21
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
22
+ def initialize(str, line, offset, options = {})
23
+ @options = options
24
+ @lexer = lexer_class.new(str, line, offset, options)
18
25
  end
19
26
 
20
27
  # Parses a SassScript expression within an interpolated segment (`#{}`).
@@ -27,7 +34,11 @@ module Sass
27
34
  def parse_interpolated
28
35
  expr = assert_expr :expr
29
36
  assert_tok :end_interpolation
37
+ expr.options = @options
30
38
  expr
39
+ rescue Sass::SyntaxError => e
40
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
41
+ raise e
31
42
  end
32
43
 
33
44
  # Parses a SassScript expression.
@@ -37,7 +48,28 @@ module Sass
37
48
  def parse
38
49
  expr = assert_expr :expr
39
50
  assert_done
51
+ expr.options = @options
40
52
  expr
53
+ rescue Sass::SyntaxError => e
54
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
55
+ raise e
56
+ end
57
+
58
+ # Parses a SassScript expression,
59
+ # ending it when it encounters one of the given identifier tokens.
60
+ #
61
+ # @param [#include?(String)] A set of strings that delimit the expression.
62
+ # @return [Script::Node] The root node of the parse tree
63
+ # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
64
+ def parse_until(tokens)
65
+ @stop_at = tokens
66
+ expr = assert_expr :expr
67
+ assert_done
68
+ expr.options = @options
69
+ expr
70
+ rescue Sass::SyntaxError => e
71
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
72
+ raise e
41
73
  end
42
74
 
43
75
  # Parses the argument list for a mixin include.
@@ -53,7 +85,11 @@ module Sass
53
85
  end
54
86
  assert_done
55
87
 
88
+ args.each {|a| a.options = @options}
56
89
  args
90
+ rescue Sass::SyntaxError => e
91
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
92
+ raise e
57
93
  end
58
94
 
59
95
  # Parses the argument list for a mixin definition.
@@ -69,7 +105,14 @@ module Sass
69
105
  end
70
106
  assert_done
71
107
 
108
+ args.each do |k, v|
109
+ k.options = @options
110
+ v.options = @options if v
111
+ end
72
112
  args
113
+ rescue Sass::SyntaxError => e
114
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
115
+ raise e
73
116
  end
74
117
 
75
118
  # Parses a SassScript expression.
@@ -82,7 +125,28 @@ module Sass
82
125
  new(*args).parse
83
126
  end
84
127
 
128
+ # @private
129
+ PRECEDENCE = [
130
+ :comma, :concat, :or, :and,
131
+ [:eq, :neq],
132
+ [:gt, :gte, :lt, :lte],
133
+ [:plus, :minus],
134
+ [:times, :div, :mod],
135
+ ]
136
+
85
137
  class << self
138
+ # Returns an integer representing the precedence
139
+ # of the given operator.
140
+ # A lower integer indicates a looser binding.
141
+ #
142
+ # @private
143
+ def precedence_of(op)
144
+ PRECEDENCE.each_with_index do |e, i|
145
+ return i if Array(e).include?(op)
146
+ end
147
+ raise "[BUG] Unknown operator #{op}"
148
+ end
149
+
86
150
  private
87
151
 
88
152
  # Defines a simple left-associative production.
@@ -94,7 +158,9 @@ module Sass
94
158
  def #{name}
95
159
  return unless e = #{sub}
96
160
  while tok = try_tok(#{ops.map {|o| o.inspect}.join(', ')})
161
+ line = @lexer.line
97
162
  e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type)
163
+ e.line = line
98
164
  end
99
165
  e
100
166
  end
@@ -105,7 +171,10 @@ RUBY
105
171
  class_eval <<RUBY
106
172
  def unary_#{op}
107
173
  return #{sub} unless try_tok(:#{op})
108
- UnaryOperation.new(assert_expr(:unary_#{op}), :#{op})
174
+ line = @lexer.line
175
+ op = UnaryOperation.new(assert_expr(:unary_#{op}), :#{op})
176
+ op.line = line
177
+ op
109
178
  end
110
179
  RUBY
111
180
  end
@@ -113,12 +182,28 @@ RUBY
113
182
 
114
183
  private
115
184
 
116
- production :expr, :concat, :comma
185
+ # @private
186
+ def lexer_class; Lexer; end
187
+
188
+ production :expr, :interpolation, :comma
189
+
190
+ def interpolation
191
+ e = concat
192
+ while interp = try_tok(:begin_interpolation)
193
+ wb = @lexer.whitespace?(interp)
194
+ line = @lexer.line
195
+ mid = parse_interpolated
196
+ wa = @lexer.whitespace?
197
+ e = Script::Interpolation.new(e, mid, concat, wb, wa)
198
+ e.line = line
199
+ end
200
+ e
201
+ end
117
202
 
118
203
  def concat
119
204
  return unless e = or_expr
120
205
  while sub = or_expr
121
- e = Operation.new(e, sub, :concat)
206
+ e = node(Operation.new(e, sub, :concat))
122
207
  end
123
208
  e
124
209
  end
@@ -128,37 +213,47 @@ RUBY
128
213
  production :eq_or_neq, :relational, :eq, :neq
129
214
  production :relational, :plus_or_minus, :gt, :gte, :lt, :lte
130
215
  production :plus_or_minus, :times_div_or_mod, :plus, :minus
131
- production :times_div_or_mod, :unary_minus, :times, :div, :mod
216
+ production :times_div_or_mod, :unary_plus, :times, :div, :mod
132
217
 
218
+ unary :plus, :unary_minus
133
219
  unary :minus, :unary_div
134
220
  unary :div, :unary_not # For strings, so /foo/bar works
135
221
  unary :not, :funcall
136
222
 
137
223
  def funcall
138
- return paren unless name = try_tok(:ident)
224
+ return raw unless @lexer.peek && @lexer.peek.type == :ident
225
+ return if @stop_at && @stop_at.include?(@lexer.peek.value)
226
+
227
+ name = @lexer.next
139
228
  # An identifier without arguments is just a string
140
229
  unless try_tok(:lparen)
141
- warn(<<END)
142
- DEPRECATION WARNING:
143
- On line #{name.line}, character #{name.offset}#{" of '#{@filename}'" if @filename}
144
- Implicit strings have been deprecated and will be removed in version 3.0.
145
- '#{name.value}' was not quoted. Please add double quotes (e.g. "#{name.value}").
146
- END
147
- Script::String.new(name.value)
230
+ if color = Color::HTML4_COLORS[name.value]
231
+ return node(Color.new(color))
232
+ end
233
+ node(Script::String.new(name.value, :identifier))
148
234
  else
149
235
  args = arglist || []
150
236
  assert_tok(:rparen)
151
- Script::Funcall.new(name.value, args)
237
+ node(Script::Funcall.new(name.value, args))
152
238
  end
153
239
  end
154
240
 
155
241
  def defn_arglist(must_have_default)
242
+ line = @lexer.line
243
+ offset = @lexer.offset + 1
156
244
  return unless c = try_tok(:const)
157
245
  var = Script::Variable.new(c.value)
158
- if try_tok(:single_eq)
246
+ if tok = (try_tok(:colon) || try_tok(:single_eq))
159
247
  val = assert_expr(:concat)
248
+
249
+ if tok.type == :single_eq
250
+ val.context = :equals
251
+ val.options = @options
252
+ Script.equals_warning("mixin argument defaults", "$#{c.value}",
253
+ val.to_sass, false, line, offset, @options[:filename])
254
+ end
160
255
  elsif must_have_default
161
- raise SyntaxError.new("Required argument #{var.inspect} must come before any optional arguments.", @line)
256
+ raise SyntaxError.new("Required argument #{var.inspect} must come before any optional arguments.")
162
257
  end
163
258
 
164
259
  return [[var, val]] unless try_tok(:comma)
@@ -166,46 +261,80 @@ END
166
261
  end
167
262
 
168
263
  def arglist
169
- return unless e = concat
264
+ return unless e = interpolation
170
265
  return [e] unless try_tok(:comma)
171
266
  [e, *arglist]
172
267
  end
173
268
 
269
+ def raw
270
+ return special_fun unless tok = try_tok(:raw)
271
+ node(Script::String.new(tok.value))
272
+ end
273
+
274
+ def special_fun
275
+ return paren unless tok = try_tok(:special_fun)
276
+ first = node(Script::String.new(tok.value.first))
277
+ Haml::Util.enum_slice(tok.value[1..-1], 2).inject(first) do |l, (i, r)|
278
+ Script::Interpolation.new(
279
+ l, i, r && node(Script::String.new(r)),
280
+ false, false)
281
+ end
282
+ end
283
+
174
284
  def paren
175
285
  return variable unless try_tok(:lparen)
286
+ was_in_parens = @in_parens
287
+ @in_parens = true
176
288
  e = assert_expr(:expr)
177
289
  assert_tok(:rparen)
178
290
  return e
291
+ ensure
292
+ @in_parens = was_in_parens
179
293
  end
180
294
 
181
295
  def variable
182
296
  return string unless c = try_tok(:const)
183
- Variable.new(c.value)
297
+ node(Variable.new(*c.value))
184
298
  end
185
299
 
186
300
  def string
187
- return literal unless first = try_tok(:string)
301
+ return number unless first = try_tok(:string)
188
302
  return first.value unless try_tok(:begin_interpolation)
303
+ line = @lexer.line
189
304
  mid = parse_interpolated
190
305
  last = assert_expr(:string)
191
- Operation.new(first.value, Operation.new(mid, last, :plus), :plus)
306
+ op = Operation.new(first.value, node(Operation.new(mid, last, :plus)), :plus)
307
+ op.line = line
308
+ op
309
+ end
310
+
311
+ def number
312
+ return literal unless tok = try_tok(:number)
313
+ num = tok.value
314
+ num.original = num.to_s unless @in_parens
315
+ num
192
316
  end
193
317
 
194
318
  def literal
195
- (t = try_tok(:number, :color, :bool)) && (return t.value)
319
+ (t = try_tok(:color, :bool)) && (return t.value)
196
320
  end
197
321
 
198
322
  # It would be possible to have unified #assert and #try methods,
199
323
  # but detecting the method/token difference turns out to be quite expensive.
200
324
 
325
+ EXPR_NAMES = {
326
+ :string => "string",
327
+ :default => "expression (e.g. 1px, bold)",
328
+ }
329
+
201
330
  def assert_expr(name)
202
331
  (e = send(name)) && (return e)
203
- raise Sass::SyntaxError.new("Expected expression, was #{@lexer.done? ? 'end of text' : "#{@lexer.peek.type} token"}.")
332
+ @lexer.expected!(EXPR_NAMES[name] || EXPR_NAMES[:default])
204
333
  end
205
334
 
206
335
  def assert_tok(*names)
207
336
  (t = try_tok(*names)) && (return t)
208
- raise Sass::SyntaxError.new("Expected #{names.join(' or ')} token, was #{@lexer.done? ? 'end of text' : "#{@lexer.peek.type} token"}.")
337
+ @lexer.expected!(names.map {|tok| Lexer::TOKEN_NAMES[tok] || tok}.join(" or "))
209
338
  end
210
339
 
211
340
  def try_tok(*names)
@@ -215,7 +344,12 @@ END
215
344
 
216
345
  def assert_done
217
346
  return if @lexer.done?
218
- raise Sass::SyntaxError.new("Unexpected #{@lexer.peek.type} token.")
347
+ @lexer.expected!(EXPR_NAMES[:default])
348
+ end
349
+
350
+ def node(node)
351
+ node.line = @lexer.line
352
+ node
219
353
  end
220
354
  end
221
355
  end